Browse Source

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

romanlesnikov 1 năm trước cách đây
mục cha
commit
e7e0ac3002
100 tập tin đã thay đổi với 20751 bổ sung và 0 xóa
  1. 51 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/install/config.php
  2. 13 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/install/installer.php
  3. 4 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/install/langs/en.xml
  4. 110 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/source/classes/BxAccntConfig.php
  5. 808 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/source/classes/BxAccntGridAdministration.php
  6. 71 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/source/install/config.php
  7. 125 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/source/install/langs/en.xml
  8. 1 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/source/template/images/icons/gi-add.svg
  9. 1 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/source/template/images/icons/gi-export.svg
  10. 0 0
      modules/boonex/accounts/updates/14.0.3_14.0.4/source/template/images/icons/gi-settings.svg
  11. 51 0
      modules/boonex/ads/updates/14.0.4_14.0.5/install/config.php
  12. 13 0
      modules/boonex/ads/updates/14.0.4_14.0.5/install/installer.php
  13. 5 0
      modules/boonex/ads/updates/14.0.4_14.0.5/install/langs/en.xml
  14. 2773 0
      modules/boonex/ads/updates/14.0.4_14.0.5/source/classes/BxAdsModule.php
  15. 151 0
      modules/boonex/ads/updates/14.0.4_14.0.5/source/install/config.php
  16. 695 0
      modules/boonex/ads/updates/14.0.4_14.0.5/source/install/langs/en.xml
  17. 57 0
      modules/boonex/ads/updates/14.0.4_14.0.5/source/install/sql/disable.sql
  18. 19 0
      modules/boonex/ads/updates/14.0.4_14.0.5/source/template/category_form.html
  19. 51 0
      modules/boonex/albums/updates/14.0.4_14.0.5/install/config.php
  20. 13 0
      modules/boonex/albums/updates/14.0.4_14.0.5/install/installer.php
  21. 234 0
      modules/boonex/albums/updates/14.0.4_14.0.5/source/classes/BxAlbumsConfig.php
  22. 1070 0
      modules/boonex/albums/updates/14.0.4_14.0.5/source/classes/BxAlbumsModule.php
  23. 152 0
      modules/boonex/albums/updates/14.0.4_14.0.5/source/classes/BxAlbumsSearchResult.php
  24. 143 0
      modules/boonex/albums/updates/14.0.4_14.0.5/source/install/config.php
  25. 59 0
      modules/boonex/albums/updates/14.0.4_14.0.5/source/install/sql/disable.sql
  26. 17 0
      modules/boonex/albums/updates/14.0.4_14.0.5/source/template/unit_gallery.html
  27. 51 0
      modules/boonex/analytics/updates/14.0.0_14.0.1/install/config.php
  28. 13 0
      modules/boonex/analytics/updates/14.0.0_14.0.1/install/installer.php
  29. 77 0
      modules/boonex/analytics/updates/14.0.0_14.0.1/source/install/config.php
  30. 52 0
      modules/boonex/analytics/updates/14.0.0_14.0.1/source/plugins/datatables/package.json
  31. 17 0
      modules/boonex/analytics/updates/14.0.0_14.0.1/source/plugins/daterangepicker/package.js
  32. 51 0
      modules/boonex/antispam/updates/14.0.1_14.0.2/install/config.php
  33. 13 0
      modules/boonex/antispam/updates/14.0.1_14.0.2/install/installer.php
  34. 635 0
      modules/boonex/antispam/updates/14.0.1_14.0.2/source/classes/BxAntispamModule.php
  35. 68 0
      modules/boonex/antispam/updates/14.0.1_14.0.2/source/install/config.php
  36. 27 0
      modules/boonex/antispam/updates/14.0.1_14.0.2/source/template/images/icons/std-icon.svg
  37. 51 0
      modules/boonex/channels/updates/14.0.4_14.0.5/install/config.php
  38. 13 0
      modules/boonex/channels/updates/14.0.4_14.0.5/install/installer.php
  39. 4 0
      modules/boonex/channels/updates/14.0.4_14.0.5/install/langs/en.xml
  40. 153 0
      modules/boonex/channels/updates/14.0.4_14.0.5/source/install/config.php
  41. 260 0
      modules/boonex/channels/updates/14.0.4_14.0.5/source/install/langs/en.xml
  42. 51 0
      modules/boonex/classes/updates/14.0.1_14.0.2/install/config.php
  43. 13 0
      modules/boonex/classes/updates/14.0.1_14.0.2/install/installer.php
  44. 152 0
      modules/boonex/classes/updates/14.0.1_14.0.2/source/install/config.php
  45. 51 0
      modules/boonex/classes/updates/14.0.1_14.0.2/source/install/sql/disable.sql
  46. 51 0
      modules/boonex/contact/updates/14.0.0_14.0.1/install/config.php
  47. 13 0
      modules/boonex/contact/updates/14.0.0_14.0.1/install/installer.php
  48. 198 0
      modules/boonex/contact/updates/14.0.0_14.0.1/source/classes/BxContactModule.php
  49. 71 0
      modules/boonex/contact/updates/14.0.0_14.0.1/source/install/config.php
  50. 51 0
      modules/boonex/courses/updates/14.0.3_14.0.4/install/config.php
  51. 13 0
      modules/boonex/courses/updates/14.0.3_14.0.4/install/installer.php
  52. 15 0
      modules/boonex/courses/updates/14.0.3_14.0.4/install/langs/en.xml
  53. 21 0
      modules/boonex/courses/updates/14.0.3_14.0.4/install/sql/enable.sql
  54. 2 0
      modules/boonex/courses/updates/14.0.3_14.0.4/install/sql/install.sql
  55. 398 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesConfig.php
  56. 464 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesDb.php
  57. 38 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesFormEntry.php
  58. 265 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesGridCntDataManage.php
  59. 421 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesGridCntStructureManage.php
  60. 48 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesMenuViewActions.php
  61. 60 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesMenuViewActionsAll.php
  62. 814 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesModule.php
  63. 47 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesPageEntry.php
  64. 214 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesSearchResult.php
  65. 731 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesTemplate.php
  66. 152 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/install/config.php
  67. 549 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/install/langs/en.xml
  68. 744 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/install/sql/enable.sql
  69. 588 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/install/sql/install.sql
  70. 86 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/js/entry.js
  71. 20 0
      modules/boonex/courses/updates/14.0.3_14.0.4/source/template/entries_summary.html
  72. 51 0
      modules/boonex/developer/updates/14.0.3_14.0.4/install/config.php
  73. 13 0
      modules/boonex/developer/updates/14.0.3_14.0.4/install/installer.php
  74. 11 0
      modules/boonex/developer/updates/14.0.3_14.0.4/install/langs/en.xml
  75. 37 0
      modules/boonex/developer/updates/14.0.3_14.0.4/install/sql/install.sql
  76. 517 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevBuilderPage.php
  77. 51 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevForms.php
  78. 515 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevModule.php
  79. 39 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevNavigation.php
  80. 182 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevNavigationItems.php
  81. 257 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevNavigationMenus.php
  82. 77 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevPolyglot.php
  83. 172 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevStudioPage.php
  84. 69 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/install/config.php
  85. 551 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/install/langs/en.xml
  86. 741 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/install/sql/install.sql
  87. 138 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/template/css/main.css
  88. 8 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/template/page_content.html
  89. 8 0
      modules/boonex/developer/updates/14.0.3_14.0.4/source/template/page_menu.html
  90. 51 0
      modules/boonex/events/updates/14.0.6_14.0.7/install/config.php
  91. 13 0
      modules/boonex/events/updates/14.0.6_14.0.7/install/installer.php
  92. 4 0
      modules/boonex/events/updates/14.0.6_14.0.7/install/langs/en.xml
  93. 93 0
      modules/boonex/events/updates/14.0.6_14.0.7/source/classes/BxEventsMenuViewActionsAll.php
  94. 935 0
      modules/boonex/events/updates/14.0.6_14.0.7/source/classes/BxEventsModule.php
  95. 152 0
      modules/boonex/events/updates/14.0.6_14.0.7/source/install/config.php
  96. 694 0
      modules/boonex/events/updates/14.0.6_14.0.7/source/install/langs/en.xml
  97. 51 0
      modules/boonex/files/updates/14.0.3_14.0.4/install/config.php
  98. 13 0
      modules/boonex/files/updates/14.0.3_14.0.4/install/installer.php
  99. 653 0
      modules/boonex/files/updates/14.0.3_14.0.4/source/classes/BxFilesModule.php
  100. 142 0
      modules/boonex/files/updates/14.0.3_14.0.4/source/install/config.php

+ 51 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Accounts Manager',
+    'version_from' => '14.0.3',
+    'version_to' => '14.0.4',
+    'vendor' => 'BoonEx',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/accounts/updates/update_14.0.3_14.0.4/',
+    'home_uri' => 'accounts_update_1403_1404',
+
+    'module_dir' => 'boonex/accounts/',
+    'module_uri' => 'accounts',
+
+    'db_prefix' => 'bx_accnt_',
+    'class_prefix' => 'BxAccnt',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Accounts',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxAccntUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 4 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/install/langs/en.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string_add name="_bx_accnt_grid_action_msg_delete_scheduled"><![CDATA[Account(s) deletion was scheduled and would be done in a few minutes.]]></string_add>
+</resources>

+ 110 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/source/classes/BxAccntConfig.php

@@ -0,0 +1,110 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Accounts Accounts
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxAccntConfig extends BxBaseModGeneralConfig
+{
+    protected $_oDb;
+    protected $_aHtmlIds;
+
+    /**
+     * Constructor
+     */
+    public function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->CNF = array (
+            // some params
+            'PARAM_PROFILE_NAME_LENGTH_MAX' => 10,
+            'PARAM_LA_SORTING' => 'bx_accounts_last_active_sorting',
+            'PARAM_EXPORT_TO' => 'bx_accounts_export_to',
+            'PARAM_EXPORT_FIELDS' => 'bx_accounts_export_fields',
+
+            // page URIs
+            'URL_MANAGE_ADMINISTRATION' => 'page.php?i=accounts-administration',
+
+            // objects
+            'OBJECT_STORAGE_FILES' => 'sys_files',
+            'OBJECT_FORM_ACCOUNT' => 'bx_accounts_account',
+            'OBJECT_FORM_ACCOUNT_DISPLAY_SETTINGS_EMAIL' => 'bx_accounts_account_settings_email',
+            'OBJECT_FORM_ACCOUNT_DISPLAY_CREATE' => 'bx_accounts_account_create',
+            'OBJECT_MENU_MANAGE_TOOLS' => 'bx_accounts_menu_manage_tools', //manage menu in content administration tools
+            'OBJECT_GRID_ADMINISTRATION' => 'bx_accounts_administration',
+            'OBJECT_GRID_MODERATION' => 'bx_accounts_moderation',
+
+                // some language keys
+            'T' => array (
+                'grid_action_msg_delete_scheduled' => '_bx_accnt_grid_action_msg_delete_scheduled',
+                'grid_action_err_delete' => '_bx_accnt_grid_action_err_delete',
+                'grid_action_err_perform' => '_bx_accnt_grid_action_err_perform',
+                'filter_item_active' => '_bx_accnt_grid_filter_item_title_adm_active',
+                'filter_item_operators' => '_bx_accnt_grid_filter_item_title_adm_operators',
+                'filter_item_pending' => '_bx_accnt_grid_filter_item_title_adm_pending',
+                'filter_item_suspended' => '_bx_accnt_grid_filter_item_title_adm_suspended',
+                'filter_item_unconfirmed' => '_bx_accnt_grid_filter_item_title_adm_unconfirmed',
+                'filter_item_locked' => '_bx_accnt_grid_filter_item_title_adm_locked',
+                'filter_item_without_profile' => '_bx_accnt_grid_filter_item_title_adm_without_profile',
+                'filter_item_select_one_filter1' => '_bx_accnt_grid_filter_item_title_adm_select_one_filter1',
+                'filter_item_select_one_filter2' => '_bx_accnt_grid_filter_item_title_adm_select_one_filter2',
+            )
+        );
+
+        $this->_aObjects = array(
+            'alert' => $this->_sName,
+        );
+
+        $this->_aJsClasses = array(
+            'main' => 'BxAccntMain',
+            'manage_tools' => 'BxAccntManageTools'
+        );
+
+        $this->_aJsObjects = array(
+            'main' => 'oBxAccntMain',
+            'manage_tools' => 'oBxAccntManageTools'
+        );
+
+        $this->_aGridObjects = array(
+            'moderation' => $this->CNF['OBJECT_GRID_MODERATION'],
+            'administration' => $this->CNF['OBJECT_GRID_ADMINISTRATION'],
+
+        );
+
+        $sHtmlPrefix = str_replace('_', '-', $this->_sName);
+        $this->_aHtmlIds = array(
+            'profile' => $sHtmlPrefix . '-profile-',
+            'profile_more_popup' => $sHtmlPrefix . '-profile-more-popup-',
+
+            'password_text' => $sHtmlPrefix . '-txt-password',
+            'password_button' => $sHtmlPrefix . '-btn-copy',
+            'password_popup' => $sHtmlPrefix . '-password-popup',
+        );
+    }
+
+    public function init(&$oDb)
+    {
+        $this->_oDb = &$oDb;
+    }
+
+    public function isLastActiveSorting()
+    {
+        return getParam($this->CNF['PARAM_LA_SORTING']) == 'on';
+    }
+
+    public function getHtmlIds($sKey = '')
+    {
+        if(empty($sKey))
+            return $this->_aHtmlIds;
+
+        return isset($this->_aHtmlIds[$sKey]) ? $this->_aHtmlIds[$sKey] : '';
+    }
+}
+
+/** @} */

+ 808 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/source/classes/BxAccntGridAdministration.php

@@ -0,0 +1,808 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Accounts Accounts
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxAccntGridAdministration extends BxBaseModProfileGridAdministration
+{
+    protected $_sFilter2Name;
+    protected $_sFilter2Value;
+    protected $_aFilter2Values;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+    	$this->MODULE = 'bx_accounts';
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_bDeleteWithBgJobs = true;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_aFilter1Values = array_merge($this->_aFilter1Values, array(
+            'unconfirmed' => $CNF['T']['filter_item_unconfirmed'],
+            'locked' => $CNF['T']['filter_item_locked'],
+            'without_profile' => $CNF['T']['filter_item_without_profile'],
+        ));
+
+        $this->_sFilter2Name = 'filter2';
+        $this->_aFilter2Values = array(
+            'operators' => $CNF['T']['filter_item_operators']
+        );
+
+        $sFilter2 = bx_get($this->_sFilter2Name);
+        if(!empty($sFilter2)) {
+            $this->_sFilter2Value = bx_process_input($sFilter2);
+            $this->_aQueryAppend[$this->_sFilter2Name] = $this->_sFilter2Value;
+        }
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if(strpos($sFilter, $this->_sParamsDivider) !== false)
+            list($this->_sFilter1Value, $this->_sFilter2Value, $sFilter) = explode($this->_sParamsDivider, $sFilter);
+
+        $sSelectClause = $sJoinClause = '';
+        if($this->_oModule->_oConfig->isLastActiveSorting()) {
+            $sSelectClause = ', `ts`.`date` AS `last_active`';
+            $sJoinClause = ' LEFT JOIN `sys_sessions` AS `ts` ON `ts`.`id` = (SELECT `id` FROM `sys_sessions` WHERE `user_id`=`ta`.`id` ORDER BY `date` DESC LIMIT 1)';
+        }
+
+        $this->_aOptions['source'] = bx_replace_markers($this->_aOptions['source'], [
+            'select' => $sSelectClause,
+            'join' => $sJoinClause
+        ]);
+
+    	if(!empty($this->_sFilter1Value))
+            switch($this->_sFilter1Value) {
+                case 'unconfirmed':
+                    $sCnfnType = getParam('sys_account_confirmation_type');
+                    switch($sCnfnType) {
+                        case 'email':
+                            $this->_aOptions['source'] .= " AND `ta`.`email_confirmed`='0'";
+                            break;
+                        case 'phone':
+                            $this->_aOptions['source'] .= " AND `ta`.`phone_confirmed`='0'";
+                            break;
+                        case 'email_and_phone':
+                            $this->_aOptions['source'] .= " AND (`ta`.`email_confirmed`='0' OR `ta`.`phone_confirmed`='0')";
+                            break;
+                        case 'email_or_phone':
+                            $this->_aOptions['source'] .= " AND (`ta`.`email_confirmed`='0' AND `ta`.`phone_confirmed`='0')";
+                            break;
+                    }
+                    break;
+
+                case 'locked':
+                    $this->_aOptions['source'] .= " AND `ta`.`locked` = 1";
+                    break;
+                
+                case 'without_profile':
+                    $this->_aOptions['source'] .= " AND `ta`.`id` NOT IN (SELECT `account_id` FROM `sys_profiles` WHERE `type` <> 'system')";
+                    break;
+
+                default:
+        	        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `tp`.`status`=?", $this->_sFilter1Value);
+            }
+
+        if(!empty($this->_sFilter2Value))
+        	$this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `ta`.`role` & " . BX_DOL_ROLE_ADMIN ." = " . BX_DOL_ROLE_ADMIN);
+
+        $this->_aOptions['source'] .= " AND `ta`.`email` <> '' ";
+        
+        return parent::_getDataSqlInner($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+    
+    protected function _getFilterControls()
+    {
+        parent::_getFilterControls();
+
+        return  $this->_getFilterSelectOne($this->_sFilter1Name, $this->_sFilter1Value, $this->_aFilter1Values) . $this->_getFilterSelectOne($this->_sFilter2Name, $this->_sFilter2Value, $this->_aFilter2Values) . $this->_getSearchInput();
+    }
+    
+    public function getCode($isDisplayHeader = true)
+    {
+        return $this->_oModule->_oTemplate->getJsCode('main', array(
+        	'aHtmlIds' => $this->_oModule->_oConfig->getHtmlIds()
+        )) . parent::getCode($isDisplayHeader);
+    }
+
+    public function performActionActivate()
+    {
+    	$this->_performActionEnable(true);
+    }
+
+	public function performActionSuspend()
+    {
+    	$this->_performActionEnable(false);
+    }
+
+    public function performActionEditEmail()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            $iId = (int)bx_get('id');
+            if(!$iId)
+                return echoJson(array());
+
+            $aIds = array($iId);
+        }
+
+        $iId = $aIds[0];
+        $oAccount = BxDolAccount::getInstance($iId);
+        if(!$oAccount)
+            return echoJson(array());
+
+        $aAccount = $oAccount->getInfo();
+        $sAction = 'edit_email';
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ACCOUNT'], $CNF['OBJECT_FORM_ACCOUNT_DISPLAY_SETTINGS_EMAIL']);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction . '&id=' . $iId;
+        $oForm->initChecker($aAccount);
+
+        if($oForm->isSubmittedAndValid()) {
+            $sEmail = $oForm->getCleanValue('email');
+            if(strcmp($aAccount['email'], $sEmail) !== 0 && BxDolAccountQuery::getInstance()->getIdByEmail($sEmail))
+                return echoJson(array('msg' => _t('_sys_form_account_input_email_uniq_error_loggedin')));
+
+            if($oForm->update($aAccount['id'])) {
+                bx_alert('account', 'edited', $aAccount['id'], BxDolAccount::getInstance()->id(), array('display' => $CNF['OBJECT_FORM_ACCOUNT_DISPLAY_SETTINGS_EMAIL']));
+
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+                
+                $oAccount->doAudit($iId, '_sys_audit_action_account_email_changed', array('old_email' => $oAccount->getEmail(), 'new_email' => $sEmail));
+            }
+            else
+                $aRes = array('msg' => _t('_sys_txt_error_account_update'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-account-edit-email-popup', _t('_bx_accounts_form_display_account_settings_email_popup'), $this->_oModule->_oTemplate->parseHtmlByName('edit_email.html', array(
+                'form_id' => $oForm->id,
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+	
+    public function performActionSendMessage()
+    {
+        $sAction = 'send_message';
+		
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+		
+        $oForm = BxDolForm::getObjectInstance('bx_accounts_account', 'bx_accounts_send_test');
+        if (!$oForm)
+            return '';
+		
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?' . bx_encode_url_params($_GET, array('_r'));
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $aIdsAffected = array();
+                foreach($aIds as $iId) {
+                    if(sendMail(BxDolAccount::getInstance()->getEmail($iId), $oForm->getCleanValue('message_subject'), $oForm->getCleanValue('message_text'), 0, [], BX_EMAIL_MASS, 'text', false, ['From' => "=?UTF-8?B?" . base64_encode(getParam('site_title')) . "?= <" . getParam('site_email_notify') . ">"], false)){
+                        $aIdsAffected[] = $iId;
+                    }	
+                }
+
+            echoJson(count($aIdsAffected) > 0 ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t($CNF['T']['grid_action_err_perform'])));
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-account-send-message', _t('_bx_accounts_form_display_account_send_message'), $this->_oModule->_oTemplate->parseHtmlByName('manage_item.html', array(
+                'form_id' => $oForm->id,
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+    
+    public function performActionAdd()
+    {
+      
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $sAction = 'add';
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ACCOUNT'], $CNF['OBJECT_FORM_ACCOUNT_DISPLAY_CREATE']);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction;
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+           
+            $iAccountId = $oForm->insert();
+            
+            if (!$iAccountId) {
+                $aRes = array('msg' => _t('_sys_txt_error_account_insert'));
+            }
+            else{
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iAccountId);
+                
+                $oBxTemplAccountForms = new BxTemplAccountForms();
+                
+                $iProfileId = $oBxTemplAccountForms->onAccountCreated($iAccountId, $oForm->isSetPendingApproval(), BX_PROFILE_ACTION_MANUAL, false);
+
+                // perform action
+                BxDolAccount::isAllowedCreate ($iProfileId, true);
+                
+                // check
+                $aModulesProfile = array(); 
+                $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1));
+                foreach($aModules as $aModule) {
+                    $oModule = BxDolModule::getInstance($aModule['name']);
+                    if($oModule instanceof iBxDolProfileService && BxDolService::call($aModule['name'], 'act_as_profile') === true)
+                        $aModulesProfile[] = $aModule;
+                }
+
+                $sDefaultProfileType = getParam('sys_account_default_profile_type');
+                if(count($aModulesProfile) == 1)
+                    $sProfileModule = $aModulesProfile[0]['name'];
+                else if(!empty($sDefaultProfileType)) 
+                    $sProfileModule = $sDefaultProfileType;
+
+                if (getParam('sys_account_auto_profile_creation') && !empty($sProfileModule)) {
+                    $oAccount = BxDolAccount::getInstance($iAccountId);
+                    $aProfileInfo = BxDolService::call($sProfileModule, 'prepare_fields', array(array(
+                        'author' => $iProfileId,
+                        'name' => $oAccount->getDisplayName(),
+                    )));
+                    $a = BxDolService::call($sProfileModule, 'entity_add', array($iProfileId, $aProfileInfo));
+                    if (isset($a['content']['profile_id']) && (int)$a['content']['profile_id'] > 0){
+                        BxDolAcl::getInstance()->setMembership((int)$a['content']['profile_id'], MEMBERSHIP_ID_STANDARD);
+                    }
+                    echoJson($aRes);
+                }
+            }
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-account-edit-email-popup', _t('_bx_accounts_form_display_account_create_popup'), $this->_oModule->_oTemplate->parseHtmlByName('create.html', array(
+                'form_id' => $oForm->id,
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    public function performActionExport()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aFields = explode(',', getParam($CNF['PARAM_EXPORT_FIELDS']));
+        if(empty($aFields) || !is_array($aFields))
+            return echoJson(array());
+
+        
+        $aTitles = $aConverters = array();
+        foreach($aFields as $sField) {
+            $aTitles[$sField] = _t('_bx_accnt_txt_field_' . $sField);
+            
+            $sConverter = '_exportFieldConverter' . bx_gen_method_name($sField);
+            if(method_exists($this, $sConverter))
+                $aConverters[$sField] = $sConverter;
+        }
+
+        $sMethod = '_exportTo' . bx_gen_method_name(getParam($CNF['PARAM_EXPORT_TO']));
+        if(!method_exists($this, $sMethod))
+            return echoJson(array());
+
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            $aIds = $this->_oModule->_oDb->getAccountIds();
+
+        if(($aFile = $this->$sMethod($aIds, $aFields, $aTitles, $aConverters)) === false)
+            return echoJson(array());
+
+        list($sFilePath, $sFileUrl) = $aFile;
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE_FILES']);
+        if($oStorage && ($iFileId = $oStorage->storeFileFromPath($sFilePath, true, bx_get_logged_profile_id())) !== false) {
+            $sFileUrl = $oStorage->getFileUrlById($iFileId);
+
+            @unlink($sFilePath);
+        }
+
+        return echoJson(array('redirect' => $sFileUrl));
+    }
+    
+    public function performActionConfirm()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+
+        $oAccount = BxDolAccount::getInstance();
+
+        $iAffected = 0;
+        $aIdsAffected = array();
+        foreach($aIds as $iId){
+            if (BxDolAccount::isNeedConfirmEmail()) {
+                if($oAccount->updateEmailConfirmed(true, true, $iId)) {
+                    $aIdsAffected[] = $iId;
+                    $iAffected++;  
+                }
+            }
+
+            if (BxDolAccount::isNeedConfirmPhone()){
+                if($oAccount->updatePhoneConfirmed(true, $iId)) {
+                    $aIdsAffected[] = $iId;
+                    $iAffected++;  
+                }
+            }
+        }
+		echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t($CNF['T']['grid_action_err_perform'])));
+    }
+    
+    public function performActionResendCemail()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+
+        $oAccount = BxDolAccount::getInstance();
+
+        $iAffected = 0;
+        $aIdsAffected = array();
+        foreach($aIds as $iId)
+            if($oAccount->sendConfirmationEmail($iId)) {
+                $aIdsAffected[] = $iId;
+                $iAffected++;  
+            }
+
+        echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t($CNF['T']['grid_action_err_perform'])));
+    }
+
+    public function performActionResetPassword()
+    {
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+
+        $iId = $aIds[0];
+        $oAccount = BxDolAccount::getInstance($iId);
+        if(!$oAccount)
+            return echoJson(array());
+
+        $aRes = array();
+        $sPwd = genRndPwd();
+        if (BxDolAccount::getInstance()->updatePassword($sPwd, $iId)){
+            $sPopupId = $this->_oModule->_oConfig->getHtmlIds('password_popup');
+            $sPopupTitle = _t('_bx_accounts_form_display_account_settings_password_popup');
+            $sPopupContent = $this->_oModule->_oTemplate->parseHtmlByName('reset_password.html', array(
+                'js_object' => $this->_oModule->_oConfig->getJsObject('main'),
+                'html_id_text' => $this->_oModule->_oConfig->getHtmlIds('password_text'),
+                'html_id_button' => $this->_oModule->_oConfig->getHtmlIds('password_button'),
+                'password' => $sPwd,
+            ));
+
+            $aRes = array('popup' => BxTemplStudioFunctions::getInstance()->popupBox($sPopupId, $sPopupTitle, $sPopupContent));
+        }
+        else 
+            $aRes = array('msg' => _t('_bx_accnt_grid_action_err_perform'));
+
+        return echoJson($aRes);
+    }
+
+    public function performActionResendRemail()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+
+        $oAccount = BxDolAccount::getInstance();
+
+        $iAffected = 0;
+        $aIdsAffected = array();
+        foreach($aIds as $iId)
+            if($oAccount->sendResetPasswordEmail($iId)) {
+                $aIdsAffected[] = $iId;
+                $iAffected++;  
+            }
+
+        echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t($CNF['T']['grid_action_err_perform'])));
+    }
+
+    public function performActionUnlockAccount()
+    {
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            echoJson(array());
+            return;
+        }
+
+        $oAccountQuery = BxDolAccountQuery::getInstance();
+        foreach($aIds as $iId) {
+            $oAccount = BxDolAccount::getInstance($iId);
+            if(!$oAccount)
+                continue;
+
+            if ($oAccount->isLocked()){
+                $oAccountQuery->unlockAccount($iId);
+            }
+            
+            $oAccount->doAudit($iId, '_sys_audit_action_account_unlock');
+        }
+
+        return echoJson(array('grid' => $this->getCode(false), 'blink' => $aIds));
+    }
+
+    public function performActionSetOperatorRole()
+    {
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+        
+        $oUtils = BxDolStudioRolesUtils::getInstance();
+
+        $aRoles = $oUtils->getRoles();
+        if(empty($aRoles) || !is_array($aRoles))
+            return echoJson(array('msg' => _t('_sys_txt_error_occured')));
+
+        $iId = (int)array_shift($aIds);
+
+        $sPopupName = str_replace('_', '-', $this->MODULE) . '-set-role-popup';
+        $sPopupContent = $this->_oModule->_oTemplate->getPopupSetRole($aRoles, $iId, $oUtils->getRole($iId));
+
+        return echoJson(array('popup' => array(
+            'html' => BxTemplFunctions::getInstance()->transBox($sPopupName, $sPopupContent),
+            'options' => array(
+                'closeOnOuterClick' => true,
+                'removeOnClose' => true
+            )
+        )));
+    }
+
+    public function performActionSetOperatorRoleSubmit()
+    {
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+
+        $iId = (int)array_shift($aIds);
+        if(!$iId)
+            return echoJson(array('msg' => _t('_sys_txt_error_occured')));
+
+        if(!BxDolStudioRolesUtils::getInstance()->setRole($iId, bx_process_input(bx_get('role'), BX_DATA_INT)))
+            return echoJson(array('msg' => _t('_error occured')));
+
+        echoJson(array('grid' => $this->getCode(false), 'blink' => $iId));
+    }
+
+    public function performActionMakeOperator()
+    {
+    	$this->_performActionChangeRole(3);
+    }
+
+    public function performActionUnmakeOperator()
+    {
+    	$this->_performActionChangeRole(1);
+    }
+
+    protected function _performActionChangeRole($iRole)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+
+        $oAccount = BxDolAccount::getInstance();
+        $oUtils = BxDolStudioRolesUtils::getInstance();
+
+        $iAffected = 0;
+        $aIdsAffected = array();
+        foreach($aIds as $iId) {
+            if(!$this->_oModule->_oDb->updateAccount(array('role' => $iRole), array('id' => $iId))) 
+                continue;
+
+            $oUtils->setRole($iId, $iRole == 3 ? BX_DOL_STUDIO_ROLE_OPERATOR : 0);
+
+            $oAccount->doAudit($iId, '_sys_audit_action_account_change_role_to_' . $iRole);
+
+            $aIdsAffected[] = $iId;
+            $iAffected++;
+        }
+
+        echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t($CNF['T']['grid_action_err_perform'])));
+    }
+
+    protected function _performActionEnable($isChecked)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            echoJson(array());
+            return;
+        }
+        $iAffected = 0;
+        $aIdsAffected = array();
+        foreach($aIds as $iId)
+        	if($this->_enable($iId, $isChecked)) {
+        		$aIdsAffected[] = $iId;
+        		$iAffected++;
+                
+                $oAccount = BxDolAccount::getInstance($iId);
+                $oAccount->doAudit($iId, '_sys_audit_action_account_' . $isChecked ? 'activated' : 'suspended');
+        	}
+
+        echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t($CNF['T']['grid_action_err_perform'])));
+    }
+
+    protected function _enable($mixedId, $isChecked)
+    {
+    	$oProfile = BxDolProfile::getInstanceAccountProfile($mixedId);
+    	if(!$oProfile)
+    	    return false;
+
+        $iAction = BX_PROFILE_ACTION_MANUAL;
+        $sMethod = $isChecked ? 'activate' : 'suspend';
+        if(!$oProfile->$sMethod($iAction, 0 ,false))
+            return false;
+
+        sendMailTemplate('t_ChangeStatusAccount' . ucfirst($sMethod), $oProfile->getAccountObject()->id(), $oProfile->id(), array(), BX_EMAIL_SYSTEM);
+        
+        $aProfiles = $oProfile->getAccountObject()->getProfiles();
+        foreach($aProfiles as $aProfile){
+            BxDolProfile::getInstance($aProfile['id'])->$sMethod($iAction, 0 ,false);
+        }
+        
+    	return true;
+    }
+
+    //--- Layout methods ---//
+    protected function _getActionEditEmail($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        return '';
+    }
+    
+    protected function _getCellEmailConfirmed($mixedValue, $sKey, $aField, $aRow)
+    {
+    	$mixedValue = (int)$mixedValue == 1 ? '_Yes' : '_No';
+        return parent::_getCellDefault(_t($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionResetPassword($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        return '';
+    }
+
+    protected function _getActionResendRemail($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if($sType == 'single')
+            return '';
+
+        return parent::_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionUnlockAccount($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        return '';
+    }
+
+    protected function _getCellName($mixedValue, $sKey, $aField, $aRow)
+    {
+        $oAccount = BxDolAccount::getInstance($aRow['id']);
+        if ($oAccount)
+            $s = ($aRow['locked'] == 1 ? $this->_oTemplate->parseIcon("lock col-red1") . ' ' : '') . $oAccount->getDisplayName();
+        return parent::_getCellDefault ($s, $sKey, $aField, $aRow);
+    }
+    
+	protected function _getCellProfiles($mixedValue, $sKey, $aField, $aRow)
+    {
+        $s = $this->_oModule->_oTemplate->getProfilesByAccount($aRow);
+
+        return parent::_getCellDefault ($s, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellLogged($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = !empty($mixedValue) ? bx_time_js($mixedValue) : _t('_sys_not_available');
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    } 
+
+    protected function _getCellLastActive($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(!isset($aRow['last_active']))
+            $mixedValue = (new BxDolSessionQuery())->getLastActivityAccount($aRow['id']);
+
+        $iTs = max((int)$mixedValue, (int)$aRow['active']);
+        return parent::_getCellDefault($iTs ? bx_time_js($iTs) : _t('_sys_not_available'), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAdded($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = !empty($mixedValue) ? bx_time_js($mixedValue) : _t('_sys_not_available');
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    } 
+    
+    protected function _getCellIsConfirmed($mixedValue, $sKey, $aField, $aRow)
+    {
+        $oAccount = BxDolAccount::getInstance($aRow['id']);
+        $s = "";
+        if ($oAccount)
+            $s = $oAccount->getCurrentConfirmationStatusValue();
+        return parent::_getCellDefault(_t('_bx_accnt_grid_confirmation_status_' . $s), $sKey, $aField, $aRow);
+    }
+
+    protected function _getOrderFields($bTranslatable = false)
+    {
+        $aResult = parent::_getOrderFields($bTranslatable);
+
+        if(!$bTranslatable && $this->_oModule->_oConfig->isLastActiveSorting())
+            $aResult[] = 'last_active';
+
+        return $aResult;
+    }
+    
+    protected function _isCheckboxDisabled($aRow)
+    {
+        return false;
+    }
+
+    protected function _getContentInfo($iId)
+    {
+    	return BxDolAccountQuery::getInstance()->getInfoById($iId);
+    }
+
+    protected function _doDelete($iId, $aParams = [])
+    {
+        return ($oAccount = BxDolAccount::getInstance($iId)) !== false && $oAccount->delete(isset($aParams['with_content']) && $aParams['with_content'] === true, true);
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+        
+        $this->_oTemplate->addJs(array(
+            'main.css'
+        ));
+
+        $this->_oTemplate->addJs(array(
+        	'jquery.form.min.js',
+            'clipboard.min.js',
+            'main.js'
+        ));
+    }
+
+    protected function _isVisibleGrid($a)
+    {
+        if(isAdmin())
+            return true;
+        
+        return parent::_isVisibleGrid($a);
+    }
+
+    protected function _exportToCsv($aIds, $aFields, $aTitles, $aConverters)
+    {
+        $sFileDiv = ';';
+        $sFileName = 'accounts-' . date('Y.m.d') . '.csv';
+        $sFilePath = BX_DIRECTORY_PATH_TMP . $sFileName;
+        $oFileHandler = fopen($sFilePath, 'w');
+        if(!$oFileHandler)
+            return false;
+
+        fputcsv($oFileHandler, $aTitles, $sFileDiv);
+
+        $aFieldsFlip = array_flip($aFields);
+        $oAccountQuery = BxDolAccountQuery::getInstance();
+
+        foreach($aIds as $iId) {
+            $aAccount = $oAccountQuery->getInfoById($iId);
+            if(empty($aAccount) || !is_array($aAccount))
+                continue;
+            
+            $aConvert = array_intersect_key($aConverters, $aAccount);
+            foreach($aConvert as $sField => $sMethod)
+                $aAccount[$sField] = $this->$sMethod($aAccount[$sField]);
+
+            fputcsv($oFileHandler, array_intersect_key($aAccount, $aFieldsFlip), $sFileDiv);
+        }
+
+        fclose($oFileHandler);
+
+        return array($sFilePath, BX_DOL_URL_ROOT . 'tmp/' . $sFileName);
+    }
+
+    protected function _exportToXml($aIds, $aFields, $aTitles, $aConverters)
+    {
+        $sFileName = 'accounts-' . date('Y.m.d') . '.xml';
+        $sFilePath = BX_DIRECTORY_PATH_TMP . $sFileName;
+        $oFileHandler = fopen($sFilePath, 'w');
+        if(!$oFileHandler)
+            return false;
+
+        $aFieldsFlip = array_flip($aFields);
+        $oAccountQuery = BxDolAccountQuery::getInstance();
+
+        $oXML = xmlwriter_open_memory();
+        xmlwriter_set_indent($oXML, 1);
+        xmlwriter_set_indent_string($oXML, '  ');
+        
+        xmlwriter_start_document($oXML, '1.0', 'UTF-8');
+        xmlwriter_start_element($oXML, 'accounts');
+
+        foreach($aIds as $iId) {
+            $aAccount = $oAccountQuery->getInfoById($iId);
+            if(empty($aAccount) || !is_array($aAccount))
+                continue;
+
+            $aConvert = array_intersect_key($aConverters, $aAccount);
+            foreach($aConvert as $sField => $sMethod)
+                $aAccount[$sField] = $this->$sMethod($aAccount[$sField]);
+
+            xmlwriter_start_element($oXML, 'account');
+            $aAccountFields = array_intersect_key($aAccount, $aFieldsFlip);
+            foreach($aAccountFields as $sAccountField => $sAccountValue) {
+                xmlwriter_start_element($oXML, $sAccountField);
+                xmlwriter_write_cdata($oXML, $sAccountValue);
+                xmlwriter_end_element($oXML);
+            }
+            xmlwriter_end_element($oXML);
+        }
+
+        xmlwriter_end_document($oXML);
+
+        fwrite($oFileHandler, xmlwriter_output_memory($oXML));
+        fclose($oFileHandler);
+
+        return array($sFilePath, BX_DOL_URL_ROOT . 'tmp/' . $sFileName);
+    }
+
+    protected function _exportFieldConverterAdded($mixedValue)
+    {
+        return $this->__exportFieldConverterDate($mixedValue);
+    }
+
+    protected function _exportFieldConverterChanged($mixedValue)
+    {
+        return $this->__exportFieldConverterDate($mixedValue);
+    }
+
+    protected function _exportFieldConverterLogged($mixedValue)
+    {
+        return $this->__exportFieldConverterDateTime($mixedValue);
+    }
+
+    protected function _exportFieldConverterLangId($mixedValue)
+    {
+        return $mixedValue ? BxDolLanguages::getInstance()->getLangName($mixedValue) : '';
+    }
+
+    protected function __exportFieldConverterDate($mixedValue)
+    {
+        return date('Y.m.d', $mixedValue);
+    }
+
+    protected function __exportFieldConverterDateTime($mixedValue)
+    {
+        return date('Y.m.d H:i', $mixedValue);
+    }
+}
+
+/** @} */

+ 71 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/source/install/config.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Accounts Accounts
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_accounts',
+    'title' => 'Accounts Manager',
+    'note' => 'Accounts Manager module.',
+    'version' => '14.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/accounts/',
+    'home_uri' => 'accounts',
+
+    'db_prefix' => 'bx_accnt_',
+    'class_prefix' => 'BxAccnt',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Accounts',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+);
+
+/** @} */

+ 125 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/source/install/langs/en.xml

@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_accnt"><![CDATA[Accounts]]></string>
+    <string name="_bx_accounts"><![CDATA[Accounts]]></string>
+
+    <string name="_bx_accounts_option_last_active_sorting"><![CDATA[Enable sorting by Last Active]]></string>
+    <string name="_bx_accounts_option_export_to"><![CDATA[Export to]]></string>
+    <string name="_bx_accounts_option_export_fields"><![CDATA[Export fields]]></string>
+
+    <string name="_bx_accounts_form_display_account_settings_email_popup"><![CDATA[Edit Email]]></string>
+    <string name="_bx_accounts_form_display_account_create_popup"><![CDATA[Create new Account]]></string>
+    <string name="_bx_accounts_form_display_account_settings_password_popup"><![CDATA[Reset Password]]></string>
+    <string name="_bx_accounts_form_display_account_send_message"><![CDATA[Send test email]]></string>
+
+    <string name="_bx_accnt_page_title_sys_manage_administration"><![CDATA[Manage All Accounts]]></string>
+    <string name="_bx_accnt_page_title_manage"><![CDATA[Manage Accounts]]></string>
+    <string name="_bx_accnt_page_title_browse"><![CDATA[Accounts]]></string>
+
+    <string name="_bx_accnt_page_block_title_system_manage_administration"><![CDATA[Manage All Accounts]]></string>
+    <string name="_bx_accnt_page_block_title_manage"><![CDATA[Manage Accounts]]></string>
+
+    <string name="_bx_accnt_menu_set_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_accnt_menu_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_accnt_menu_item_title_system_resend_cemail"><![CDATA[Resend Confirmation]]></string>
+    <string name="_bx_accnt_menu_item_title_resend_cemail"><![CDATA[Resend Confirmation]]></string>
+    <string name="_bx_accnt_menu_item_title_system_confirm"><![CDATA[Confirm]]></string>
+    <string name="_bx_accnt_menu_item_title_confirm"><![CDATA[Confirm]]></string>
+    <string name="_bx_accnt_menu_item_title_system_edit_email"><![CDATA[Edit Email]]></string>
+    <string name="_bx_accnt_menu_item_title_edit_email"><![CDATA[Edit Email]]></string>
+    <string name="_bx_accnt_menu_item_title_system_reset_password"><![CDATA[Reset Password]]></string>
+    <string name="_bx_accnt_menu_item_title_reset_password"><![CDATA[Reset Password]]></string>
+    <string name="_bx_accnt_menu_item_title_system_resend_remail"><![CDATA[Resend Reset Password]]></string>
+    <string name="_bx_accnt_menu_item_title_resend_remail"><![CDATA[Resend Reset Password]]></string>
+    <string name="_bx_accnt_menu_item_title_system_unlock_account"><![CDATA[Unlock]]></string>
+    <string name="_bx_accnt_menu_item_title_unlock_account"><![CDATA[Unlock]]></string>
+    <string name="_bx_accnt_menu_item_title_system_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_accnt_menu_item_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_accnt_menu_item_title_system_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_accnt_menu_item_title_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_accnt_menu_item_title_system_make_operator"><![CDATA[Make Operator]]></string>
+    <string name="_bx_accnt_menu_item_title_make_operator"><![CDATA[Make Operator]]></string>
+    <string name="_bx_accnt_menu_item_title_system_unmake_operator"><![CDATA[Unmake Operator]]></string>
+    <string name="_bx_accnt_menu_item_title_unmake_operator"><![CDATA[Unmake Operator]]></string>
+    <string name="_bx_accnt_menu_item_title_system_set_operator_role"><![CDATA[Set Operator Role]]></string>
+    <string name="_bx_accnt_menu_item_title_set_operator_role"><![CDATA[Set Operator Role]]></string>
+    <string name="_bx_accnt_menu_item_title_system_admt_accounts"><![CDATA[Accounts]]></string>
+    <string name="_bx_accnt_menu_item_title_admt_accounts"><![CDATA[Accounts]]></string>
+    <string name="_bx_accnt_menu_item_title_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_accnt_menu_dashboard_manage_tools_addon_counter1_caption"><![CDATA[unconfirmed]]></string>
+
+    <string name="_bx_accnt_acl_action_edit_any_account"><![CDATA[Edit Any Account]]></string>
+    <string name="_bx_accnt_acl_action_delete_any_account"><![CDATA[Delete Any Account]]></string>
+
+    <string name="_bx_accnt_grid_filter_item_title_adm_select_one_filter1"><![CDATA[All Statuses]]></string>
+    <string name="_bx_accnt_grid_filter_item_title_adm_select_one_filter2"><![CDATA[All]]></string>
+    <string name="_bx_accnt_grid_filter_item_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_accnt_grid_filter_item_title_adm_operators"><![CDATA[Operators]]></string>
+    <string name="_bx_accnt_grid_filter_item_title_adm_pending"><![CDATA[Pending]]></string>
+    <string name="_bx_accnt_grid_filter_item_title_adm_suspended"><![CDATA[Suspended]]></string>    
+    <string name="_bx_accnt_grid_filter_item_title_adm_unconfirmed"><![CDATA[Unconfirmed]]></string>
+    <string name="_bx_accnt_grid_filter_item_title_adm_locked"><![CDATA[Locked]]></string>
+    <string name="_bx_accnt_grid_filter_item_title_adm_without_profile"><![CDATA[Without profile]]></string>
+    <string name="_bx_accnt_grid_column_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_accnt_grid_column_title_adm_name"><![CDATA[Name]]></string>
+    <string name="_bx_accnt_grid_column_title_adm_email"><![CDATA[Email]]></string>
+    <string name="_bx_accnt_grid_column_title_adm_ip"><![CDATA[IP]]></string>
+
+    <string name="_bx_accnt_grid_column_title_adm_is_confirmed"><![CDATA[Confirmed]]></string>
+    <string name="_bx_accnt_grid_column_title_adm_profiles"><![CDATA[Profiles]]></string>
+    <string name="_bx_accnt_grid_column_title_adm_logged"><![CDATA[Last Logged]]></string>
+    <string name="_bx_accnt_grid_column_title_adm_last_active"><![CDATA[Last Active]]></string>
+    <string name="_bx_accnt_grid_column_title_adm_added"><![CDATA[Created]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_activate"><![CDATA[Activate]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_suspend"><![CDATA[Suspend]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_confirm"><![CDATA[Confirm]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_edit_email"><![CDATA[Edit Email]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_export"><![CDATA[Export]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_export_all"><![CDATA[Export All]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_resend_cemail"><![CDATA[Resend Confirmation]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_resend_remail"><![CDATA[Resend Reset Password]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_reset_password"><![CDATA[Reset Password]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_unlock_account"><![CDATA[Unlock]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_send_message"><![CDATA[Send email]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
+    <string name="_bx_accnt_grid_action_title_adm_more_add"><![CDATA[Add new]]></string>
+    <string name="_bx_accnt_grid_action_msg_delete_scheduled"><![CDATA[Account(s) deletion was scheduled and would be done in a few minutes.]]></string>
+    <string name="_bx_accnt_grid_action_err_delete"><![CDATA[Cannot delete the selected account(s).]]></string>
+    <string name="_bx_accnt_grid_action_err_perform"><![CDATA[Cannot perform the action for selected account(s).]]></string>
+    <string name="_bx_accnt_grid_action_unlock_successed"><![CDATA[Successfully unlocked]]></string>
+    <string name="_bx_accnt_grid_action_unlock_error"><![CDATA[Account is not blocked]]></string>
+    <string name="_bx_accnt_grid_confirmation_status_none"><![CDATA[No]]></string>
+    <string name="_bx_accnt_grid_confirmation_status_email"><![CDATA[Email]]></string>
+    <string name="_bx_accnt_grid_confirmation_status_phone"><![CDATA[Phone]]></string>
+    <string name="_bx_accnt_grid_confirmation_status_email_and_phone"><![CDATA[All]]></string>
+
+    <string name="_bx_accnt_form_login_input_caption_system_email_confirmed"><![CDATA[Email confirmed]]></string>
+    <string name="_bx_accnt_form_account_input_email_confirmed"><![CDATA[Email confirmed]]></string>
+    <string name="_bx_accnt_form_account_input_caption_system_message_subject"><![CDATA[Subject]]></string>
+    <string name="_bx_accnt_form_account_input_caption_message_subject"><![CDATA[Subject]]></string>
+    <string name="_bx_accnt_form_account_input_message_subject_error"><![CDATA[Subject can't be empty]]></string>
+    <string name="_bx_accnt_form_account_input_caption_system_message_text"><![CDATA[Text]]></string>
+    <string name="_bx_accnt_form_account_input_caption_message_text"><![CDATA[Text]]></string>
+    <string name="_bx_accnt_form_account_input_message_text_error"><![CDATA[Text can't be empty]]></string>
+    <string name="_bx_accnt_form_display_send_message"><![CDATA[Send email]]></string>
+ 
+    <string name="_bx_accnt_txt_more"><![CDATA[{0} more]]></string>
+    <string name="_bx_accnt_txt_see_more"><![CDATA[See more]]></string>
+    <string name="_bx_accnt_txt_copy_to_clipboard"><![CDATA[Copy to Clipboard]]></string>
+    <string name="_bx_accnt_txt_not_operator"><![CDATA[Not an Operator]]></string>
+    <string name="_bx_accnt_txt_export_to_csv"><![CDATA[CSV]]></string>
+    <string name="_bx_accnt_txt_export_to_xml"><![CDATA[XML]]></string>
+    <string name="_bx_accnt_txt_field_name"><![CDATA[Name]]></string>
+    <string name="_bx_accnt_txt_field_email"><![CDATA[Email]]></string>
+    <string name="_bx_accnt_txt_field_email_confirmed"><![CDATA[Email Confirmed]]></string>
+    <string name="_bx_accnt_txt_field_phone"><![CDATA[Phone]]></string>
+    <string name="_bx_accnt_txt_field_receive_updates"><![CDATA[Receive Updates]]></string>
+    <string name="_bx_accnt_txt_field_receive_news"><![CDATA[Receive News]]></string>
+    <string name="_bx_accnt_txt_field_added"><![CDATA[Added]]></string>
+    <string name="_bx_accnt_txt_field_changed"><![CDATA[Changed]]></string>
+    <string name="_bx_accnt_txt_field_logged"><![CDATA[Logged]]></string>
+    <string name="_bx_accnt_txt_field_ip"><![CDATA[IP]]></string>
+    <string name="_bx_accnt_txt_field_lang_id"><![CDATA[Language]]></string>
+</resources>

+ 1 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/source/template/images/icons/gi-add.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"></path></svg>

+ 1 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/source/template/images/icons/gi-export.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96H80a8,8,0,0,1,0,16H56v96H200V112H176a8,8,0,0,1,0-16h24A16,16,0,0,1,216,112ZM93.66,69.66,120,43.31V136a8,8,0,0,0,16,0V43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66Z"></path></svg>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
modules/boonex/accounts/updates/14.0.3_14.0.4/source/template/images/icons/gi-settings.svg


+ 51 - 0
modules/boonex/ads/updates/14.0.4_14.0.5/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Ads',
+    'version_from' => '14.0.4',
+    'version_to' => '14.0.5',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/ads/updates/update_14.0.4_14.0.5/',
+    'home_uri' => 'ads_update_1404_1405',
+
+    'module_dir' => 'boonex/ads/',
+    'module_uri' => 'ads',
+
+    'db_prefix' => 'bx_ads_',
+    'class_prefix' => 'BxAds',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Ads',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/ads/updates/14.0.4_14.0.5/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxAdsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 5 - 0
modules/boonex/ads/updates/14.0.4_14.0.5/install/langs/en.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string_add name="_bx_ads_alert_action_info_doInterest_personal"><![CDATA[A member has shown interest in your advertisement.]]></string_add>
+	<string_add name="_bx_ads_alert_action_info_license_register_personal"><![CDATA[A member purchased the item or service you advertised.]]></string_add>
+</resources>

+ 2773 - 0
modules/boonex/ads/updates/14.0.4_14.0.5/source/classes/BxAdsModule.php

@@ -0,0 +1,2773 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Ads Ads
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_ADS_STATUS_OFFER', 'offer');
+define('BX_ADS_STATUS_SOLD', 'sold');
+
+define('BX_ADS_STATUS_ADMIN_UNPAID', 'unpaid');
+
+define('BX_ADS_COMMODITY_TYPE_PRODUCT', 'product');
+define('BX_ADS_COMMODITY_TYPE_PROMOTION', 'promotion');
+
+define('BX_ADS_OFFER_STATUS_ACCEPTED', 'accepted');
+define('BX_ADS_OFFER_STATUS_AWAITING', 'awaiting');
+define('BX_ADS_OFFER_STATUS_DECLINED', 'declined');
+define('BX_ADS_OFFER_STATUS_CANCELED', 'canceled');
+define('BX_ADS_OFFER_STATUS_PAID', 'paid');
+
+/**
+ * Ads module
+ */
+class BxAdsModule extends BxBaseModTextModule
+{
+    protected $_aOfferStatuses;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_oConfig->init($this->_oDb);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->_aSearchableNamesExcept = array_merge($this->_aSearchableNamesExcept, array(
+            $CNF['FIELD_CATEGORY_VIEW'],
+            $CNF['FIELD_CATEGORY_SELECT']
+        ));
+
+        $this->_aOfferStatuses = array(
+            BX_ADS_OFFER_STATUS_ACCEPTED,
+            BX_ADS_OFFER_STATUS_AWAITING,
+            BX_ADS_OFFER_STATUS_DECLINED
+        );
+    }
+    
+    public function decodeDataApi ($aData, $bExtended = false)
+    {
+        $CNF = $this->_oConfig->CNF;
+        
+        $aResult = array_merge(parent::decodeDataApi($aData, $bExtended), [
+            $CNF['FIELD_PRICE'] => $aData[$CNF['FIELD_PRICE']],
+            $CNF['FIELD_QUANTITY'] => $aData[$CNF['FIELD_QUANTITY']],
+            $CNF['FIELD_NOTES_PURCHASED'] => $aData[$CNF['FIELD_NOTES_PURCHASED']]
+        ]);
+
+        if($this->_oConfig->isSources() && !empty($aData[$CNF['FIELD_URL']]))
+            $aResult[$CNF['FIELD_URL']] = $aData[$CNF['FIELD_URL']];
+
+        return $aResult;
+    }
+
+    public function actionGetSource()
+    {
+        $aEntries = $this->serviceGetSourceData();
+
+        header('Content-Type:text/javascript; charset=utf-8');
+        echo(json_encode($aEntries));
+    }
+    
+    public function serviceGetSourceData()
+    {
+        $sSourceType = bx_get('source_type');
+        if(empty($sSourceType)) {
+            $aSource = $this->serviceGetSource($this->_iProfileId);
+            if(!empty($aSource) && is_array($aSource))
+                $sSourceType = $aSource['name'];
+        }
+
+        $sTerm = bx_get('term');
+
+        $aEntries = $this->serviceLoadEntriesFromSourceByTerm($sSourceType, $sTerm);
+
+        return $aEntries;
+    }
+
+    public function actionLoadEntryFromSource()
+    {
+        $sSourceType = bx_process_url_param(bx_process_input(bx_get('source_type')));
+        $sSource = bx_process_url_param(bx_process_input(bx_get('source')));
+        if(empty($sSourceType) || empty($sSource))
+            return echoJson(['code' => 1]);
+
+        $aEntry = $this->serviceLoadEntryFromSource($sSourceType, $sSource);
+        if(empty($aEntry) || !is_array($aEntry))
+            return echoJson(['code' => 2]);
+
+        return echoJson([
+            'code' => 0,
+            'fields' => $aEntry
+        ]);
+    }
+
+    public function actionGetCategoryForm()
+    {
+        if(($iCategory = bx_get('category')) === false || (int)$iCategory == 0)
+            return echoJson(['msg' => _t('_bx_ads_form_entry_input_category_select_err')]);
+
+        return echoJson([
+            'eval' => $this->_oConfig->getJsObject('form') . '.onSelectCategory(oData)',
+            'content' => $this->serviceGetCreatePostForm([
+                'absolute_action_url' => true,
+                'dynamic_mode' => true,
+                'context_id' => ($iContextId = bx_get('context_id')) !== false ? (int)$iContextId : false
+            ])
+        ]);
+    }
+
+    public function actionCheckName()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iId = (int)bx_get($CNF['FIELD_ID']);
+    	$sTitle = bx_process_input(bx_get($CNF['FIELD_TITLE']));
+
+    	echoJson($this->serviceCheckName($sTitle, $iId));
+    }
+
+    public function actionInterested()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return echoJson(array());
+
+        $iViewer = bx_get_logged_profile_id();        
+        $oViewer = BxDolProfile::getInstance($iViewer);
+        if(!$oViewer)
+            return echoJson(array());
+
+        $iContentAuthor = (int)$aContentInfo[$CNF['FIELD_AUTHOR']];
+        if($iContentAuthor == $iViewer)
+            return echoJson(array('msg' => _t('_bx_ads_txt_err_your_own')));
+
+        if($this->_oDb->isInterested($iContentId, $iViewer))
+            return echoJson(array('msg' => _t('_bx_ads_txt_err_duplicate')));
+
+        $iInterestId = $this->_oDb->insertInterested(array('entry_id' => $iContentId, 'profile_id' => $iViewer));
+        if(!$iInterestId)
+            return echoJson(array('msg' => _t('_bx_ads_txt_err_cannot_perform_action')));
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-doInterest 'bx_ads', 'doInterest' - hook after 'interested' action was performed
+         * - $unit_name - equals `bx_ads`
+         * - $action - equals `doInterest`
+         * - $object_id - content id
+         * - $sender_id - profile id who performed the action
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `subobject_id` - [int] id of a database table record, created during the action
+         *      - `subobject_author_id` - [int] profile id who performed the action
+         *      - `object_author_id` - [int] content author profile id
+         * @hook @ref hook-bx_ads-doInterest
+         */
+        bx_alert($this->getName(), 'doInterest', $iContentId, $iViewer, [
+            'subobject_id' => $iInterestId, 
+            'subobject_author_id' => $iViewer, 
+
+            'object_author_id' => $iContentAuthor
+        ]);
+
+        if(getParam($CNF['PARAM_USE_IIN']) == 'on')
+            sendMailTemplate($CNF['ETEMPLATE_INTERESTED'], 0, $iContentAuthor, array(
+                'viewer_name' => $oViewer->getDisplayName(),
+                'viewer_url' => $oViewer->getUrl(),
+                'ad_name' => $aContentInfo[$CNF['FIELD_TITLE']],
+                'ad_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php', array(
+                    'i' => $CNF['URI_VIEW_ENTRY'], 
+                    $CNF['FIELD_ID'] => $iContentId
+                )))
+            ));
+
+        return echoJson(array('msg' => _t('_bx_ads_txt_msg_author_notified')));
+    }
+
+    public function actionShow()
+    {
+        return echoJson($this->_actionChangeStatus(BX_BASE_MOD_TEXT_STATUS_ACTIVE));
+    }
+
+    public function actionHide()
+    {
+        return echoJson($this->_actionChangeStatus(BX_BASE_MOD_TEXT_STATUS_HIDDEN));
+    }
+
+    public function actionMakeOffer()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sJsObject = $this->_oConfig->getJsObject('entry');
+
+        $iAuthorId = bx_get_logged_profile_id();
+        $iContentId = bx_process_input(bx_get('content_id'), BX_DATA_INT);
+
+        if(empty($iAuthorId))
+            return echoJson(['code' => 1, 'eval' => 'window.open("' . BxDolPermalinks::getInstance()->permalink('page.php?i=login') . '", "_self");']);
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return echoJson([]);
+
+        if(($mixedCheck = $this->checkAllowedMakeOffer($aContentInfo)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return echoJson(['code' => 2, 'msg' => $mixedCheck]);
+
+        $aOffer = $this->_oDb->getOffersBy([
+            'type' => 'content_and_author_ids', 
+            'content_id' => $iContentId, 
+            'author_id' => $iAuthorId, 
+            'status' => BX_ADS_OFFER_STATUS_AWAITING
+        ]);
+
+        if(!empty($aOffer) && is_array($aOffer))
+            return echoJson(['code' => 3, 'msg' => _t('_bx_ads_txt_err_duplicate')]);
+
+        $aOffer = $this->_oDb->getOffersBy([
+            'type' => 'content_and_author_ids', 
+            'content_id' => $iContentId, 
+            'author_id' => $iAuthorId, 
+            'status' => BX_ADS_OFFER_STATUS_ACCEPTED
+        ]);
+
+        if(!empty($aOffer) && is_array($aOffer))
+            return echoJson(['code' => 4, 'msg' => _t('_bx_ads_txt_err_offer_accepted')]);
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_OFFER'], $CNF['OBJECT_FORM_OFFER_DISPLAY_ADD']);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . bx_append_url_params($this->_oConfig->getBaseUri() . 'make_offer', ['content_id' => $iContentId]);
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $iQuantity = (int)$oForm->getCleanValue($CNF['FIELD_OFR_QUANTITY']);
+            $iQuantityAvailable = $this->getAvailableQuantity($aContentInfo);
+            if($iQuantity > $iQuantityAvailable)
+                return echoJson(['code' => 5, 'msg' => _t('_bx_ads_txt_err_offer_wrong_quantity', $iQuantityAvailable)]);
+
+            $aValToAdd = ['content_id' => $iContentId, 'author_id' => $iAuthorId];
+
+            if(($iId = (int)$oForm->insert($aValToAdd)) != 0) {
+                $this->checkAllowedMakeOffer($aContentInfo, true);
+
+                $this->onOfferAdded($iId, $aResult);
+
+                $aResult = ['code' => 0, 'msg' => _t('_bx_ads_txt_msg_offer_added'), 'eval' => $sJsObject . '.onMakeOffer(oData);', 'id' => $iId];
+            }
+            else
+                $aResult = ['code' => 6, 'msg' => _t('_bx_ads_txt_err_cannot_perform_action')];
+
+            return echoJson($aResult);
+        }
+
+        $sContent = BxTemplFunctions::getInstance()->transBox($this->_oConfig->getHtmlIds('offer_popup'), $this->_oTemplate->parseHtmlByName('offer_popup.html', array(
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true)
+        )));
+
+        return echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false, 'removeOnClose' => true))));
+    }
+
+    public function actionViewOffer()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iAuthorId = bx_get_logged_profile_id();
+        $iOfferId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        $aOffer = $this->_oDb->getOffersBy(array('type' => 'id', 'id' => $iOfferId));
+        if(empty($aOffer) || !is_array($aOffer))
+            return echoJson(array());
+        
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_OFFER'], $CNF['OBJECT_FORM_OFFER_DISPLAY_VIEW']);
+        $oForm->initChecker($aOffer);
+
+        $sContent = BxTemplFunctions::getInstance()->transBox($this->_oConfig->getHtmlIds('offer_popup'), $this->_oTemplate->parseHtmlByName('offer_popup.html', array(
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true)
+        )));
+
+        return echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => true, 'removeOnClose' => true))));
+    }
+
+    public function actionAcceptOffer()
+    {
+        $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(($mixedResult = $this->offerAccept($iId)) !== true)
+            return echoJson(['msg' => $mixedResult !== false ? $mixedResult : _t('_bx_ads_txt_err_cannot_perform_action')]);
+
+        return echoJson(['msg' => _t('_bx_ads_txt_msg_offer_accepted'), 'reload' => 1]);
+    }
+
+    public function actionDeclineOffer()
+    {
+        $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$this->offerDecline($iId))
+            return echoJson(array('msg' => _t('_bx_ads_txt_err_cannot_perform_action')));
+
+        return echoJson(array('msg' => _t('_bx_ads_txt_msg_offer_declined'), 'reload' => 1));
+    }
+
+    public function actionCancelOffer()
+    {
+        $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$this->offerCancel($iId))
+            return echoJson(['msg' => _t('_bx_ads_txt_err_cannot_perform_action')]);
+
+        return echoJson(['msg' => _t('_bx_ads_txt_msg_offer_canceled'), 'reload' => 1]);
+    }
+
+    public function actionPayOffer()
+    {
+        $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(($mixedResult = $this->offerPay($iId)) !== false)
+            return echoJson($mixedResult);
+
+        return echoJson(array('msg' => _t('_bx_ads_txt_err_cannot_perform_action')));     
+    }
+
+    public function actionShipped()
+    {
+        return echoJson($this->_actionMarkAs('shipped'));
+    }
+
+    public function actionReceived()
+    {
+        return echoJson($this->_actionMarkAs('received'));
+    }
+
+    public function actionRegisterImpression($iId)
+    {
+        echoJson($this->serviceRegisterImpression($iId));
+    }
+
+    public function actionRegisterClick($iId)
+    {
+        echoJson($this->serviceRegisterClick($iId));
+    }
+
+    public function serviceGetSafeServices()
+    {
+        return array_merge(parent::serviceGetSafeServices(), [
+            'IsSourcesAvaliable' => '',
+            'LoadEntryFromSource' => '',
+            'LoadEntriesFromSourceByTerm' => '',
+            'EntityReviews' => '',
+            'EntityReviewsRating' => '',
+            'CategoriesList' => '',
+            'BrowseCategory' => '',
+            'RegisterImpression' => '',
+            'RegisterClick' => '',
+            'BlockSourcesDetails' => '',
+            'GetSourceData' => '',
+        ]);
+    }
+
+    public function serviceRegisterImpression($iId)
+    {
+        return ['code' => $this->_oDb->updatePromotionTracker($iId, 'impressions') ? 0 : 1];
+    }
+    
+    public function serviceRegisterClick($iId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oDb->updatePromotionTracker($iId, 'clicks'))
+            return ['code' => 1];
+
+        $sUrl = '';
+        if($this->_oConfig->isSources())  {
+            $aEntry = $this->_oDb->getContentInfoById($iId);
+            if(!empty($aEntry) && is_array($aEntry) && !empty($aEntry[$CNF['FIELD_URL']]))
+                $sUrl = $aEntry[$CNF['FIELD_URL']];
+        }
+
+        return [
+            'code' => 0,
+            'redirect' => !empty($sUrl) ? $sUrl : $this->_oConfig->getViewEntryUrl($iId)
+        ];
+    }
+
+    public function serviceCheckName($sTitle, $iId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	if(empty($sTitle))
+            return array();
+
+        $sName = '';
+        if(!empty($iId)) {
+            $aEntry = $this->_oDb->getContentInfoById($iId); 
+            if(strcmp($sTitle, $aEntry[$CNF['FIELD_NAME']]) == 0) 
+                $sName = $sTitle;
+        }
+
+        return array(
+            'title' => $sTitle,
+            'name' => !empty($sName) ? $sName : $this->_oConfig->getEntryName($sTitle, $iId)
+    	);
+    }
+
+    public function serviceUpdateCategoriesStats($mixedContentInfo = false)
+    {
+        $aContentInfo = [];
+        if(!empty($mixedContentInfo))
+            $aContentInfo = !is_array($mixedContentInfo) ? $this->_oDb->getContentInfoById((int)$mixedContentInfo) : $mixedContentInfo;
+
+        $iCategoryId = 0;
+        if($aContentInfo && !empty($aContentInfo['category']))
+            $iCategoryId = (int)$aContentInfo['category'];
+
+        return $this->serviceUpdateCategoriesStatsByCategory($iCategoryId);
+    }
+
+    public function serviceUpdateCategoriesStatsByCategory($iCategoryId = 0)
+    {
+        $aParams = ['type' => 'collect_stats'];
+        if($iCategoryId)
+            $aParams['category_id'] = (int)$iCategoryId;
+
+        $aStats = $this->_oDb->getCategories($aParams);
+        if(empty($aStats) || !is_array($aStats))
+            return true;
+
+        $iUpdated = 0;
+        foreach($aStats as $aStat)
+            if($this->_oDb->updateCategory(array('items' => $aStat['count']), array('id' => $aStat['id'])))
+                $iUpdated++;
+
+        return count($aStats) == $iUpdated;
+    }
+
+    public function serviceGetCategoryOptions($iParentId, $bPleaseSelect = false)
+    {
+        $aValues = array();
+        if($bPleaseSelect)
+            $aValues[] = array('key' => '', 'value' => _t('_sys_please_select'));
+
+        $this->_getCategoryOptions($iParentId, $aValues);
+
+        return $aValues;
+    }
+
+    public function serviceGetSearchableFields($aInputsAdd = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = parent::serviceGetSearchableFields(array_merge($aInputsAdd, $this->_getSearchableFields()));
+        unset($aResult[$CNF['FIELD_NOTES_PURCHASED']]);
+        unset($aResult[$CNF['FIELD_CATEGORY_VIEW']], $aResult[$CNF['FIELD_CATEGORY_SELECT']]);
+        unset($aResult[$CNF['FIELD_PRICE']], $aResult[$CNF['FIELD_YEAR']]);
+
+        return $aResult;
+    }
+
+    public function serviceGetSearchableFieldsExtended($aInputsAdd = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aInputsAdd = array_merge($aInputsAdd, $this->_getSearchableFields());
+
+        if(isset($aInputsAdd[$CNF['FIELD_CATEGORY']])) {
+            $aInputsAdd[$CNF['FIELD_CATEGORY']]['type'] = 'select';
+            $aInputsAdd[$CNF['FIELD_CATEGORY']]['values_src'] = BxDolService::getSerializedService($this->_oConfig->getName(), 'get_category_options', array(0));
+        }
+
+        if(isset($aInputsAdd[$CNF['FIELD_PRICE']])) {
+            $aInputsAdd[$CNF['FIELD_PRICE']]['search_type'] = 'text_range';
+            $aInputsAdd[$CNF['FIELD_PRICE']]['search_operator'] = 'between';
+        }
+
+        if(isset($aInputsAdd[$CNF['FIELD_YEAR']])) {
+            $aInputsAdd[$CNF['FIELD_YEAR']]['search_type'] = 'text_range';
+            $aInputsAdd[$CNF['FIELD_YEAR']]['search_operator'] = 'between';
+        }
+
+        return parent::serviceGetSearchableFieldsExtended($aInputsAdd);
+    }
+
+    /**
+     * Mark an ad as shipped/received by seller/bauer accordingly.
+     * @param type $sAction - shipped/received action.
+     * @param type $iContentId - an ad the action will be performed with.
+     * @param integer $iProfileSrc - profile, who performed the action.
+     * @param integer $iProfileDst - profile, the action is pointed on.
+     * @return boolean - true or a string value with error message.
+     */
+    public function serviceMarkAs($sAction, $iContentId, $iProfileSrc = 0, $iProfileDst = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileSrc)
+            $iProfileSrc = bx_get_logged_profile_id();
+
+        $sTxtError = '_bx_ads_txt_err_cannot_perform_action';
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+
+        $sMethodCheck = 'checkAllowedMark' . bx_gen_method_name($sAction) . 'ForProfile';
+        if(!method_exists($this, $sMethodCheck))
+            return _t($sTxtError);
+
+        if(($mixedCheckResult = $this->$sMethodCheck($aContentInfo, false, $iProfileSrc)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedCheckResult;
+
+        if((int)$this->_oDb->updateEntriesBy(array($CNF['FIELD_' . strtoupper($sAction)] => time()), array($CNF['FIELD_ID'] => $iContentId)) == 0)
+            return _t($sTxtError);
+
+        $this->$sMethodCheck($aContentInfo, true, $iProfileSrc);
+
+        $sMethodOnResult = 'on' . bx_gen_method_name($sAction);
+        if(method_exists($this, $sMethodOnResult))
+            $this->$sMethodOnResult($aContentInfo, $iProfileSrc, $iProfileDst);        
+
+        return true;
+    }
+
+    public function serviceEntityCreate ($sParams = false)
+    {
+        $iCategory = is_numeric($sParams) ? (int)$sParams : 0;
+
+        if(($sDisplay = $this->getCategoryDisplay('add', $iCategory)) !== false) {
+            if(empty($sParams) || !is_array($sParams))
+                $sParams = array();
+
+            $sParams['display'] = $sDisplay;
+        }
+
+        if($iCategory)
+            BxDolSession::getInstance()->setValue($sDisplay . '_category', $iCategory);
+
+        return parent::serviceEntityCreate($sParams);
+    }
+    
+    public function serviceEntityEditBudget ($iContentId = 0, $sDisplay = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return parent::serviceEntityEdit($iContentId, $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT_BUDGET']);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads 
+     * @subsection bx_ads-page_blocks Page Blocks
+     * @subsubsection bx_ads-entity_reviews entity_reviews
+     * 
+     * @code bx_srv('bx_ads', 'entity_reviews', [...]); @endcode
+     * 
+     * Get reviews for particular content
+     * @param $iContentId content ID
+     * 
+     * @see BxAdsModule::serviceEntityReviews
+     */
+    /** 
+     * @ref bx_ads-entity_reviews "entity_reviews"
+     */
+    public function serviceEntityReviews($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_REVIEWS']))
+            return false;
+
+        return $this->_entityComments($CNF['OBJECT_REVIEWS'], $iContentId);
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads 
+     * @subsection bx_ads-page_blocks Page Blocks
+     * @subsubsection bx_ads-entity_reviews_rating entity_reviews_rating
+     * 
+     * @code bx_srv('bx_ads', 'entity_reviews_rating', [...]); @endcode
+     * 
+     * Get reviews rating for particular content
+     * @param $iContentId content ID
+     * 
+     * @see BxAdsModule::serviceEntityReviewsRating
+     */
+    /** 
+     * @ref bx_ads-entity_reviews_rating "entity_reviews_rating"
+     */
+    public function serviceEntityReviewsRating($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_REVIEWS']))
+            return false;
+
+        if(!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$iContentId)
+            return false;
+
+        $oCmts = BxDolCmts::getObjectInstance($CNF['OBJECT_REVIEWS'], $iContentId);
+        if (!$oCmts || !$oCmts->isEnabled())
+            return false;
+
+        return $oCmts->getRatingBlock(array('in_designbox' => false));
+    }
+
+    public function serviceEntityPromotionGrowth($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc('entryPromotionGrowth', $iContentId);
+    }
+
+    public function serviceEntityPromotionSummary($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc('entryPromotionSummary', $iContentId);
+    }
+    
+    public function serviceEntityPromotionRoi($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc('entryPromotionRoi', $iContentId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads 
+     * @subsection bx_ads-page_blocks Page Blocks
+     * @subsubsection bx_ads-categories_list categories_list
+     * 
+     * @code bx_srv('bx_ads', 'categories_list', [...]); @endcode
+     * 
+     * Get reviews rating for particular content
+     * @param $aParams additional params array, such as 'show_empty'
+     * 
+     * @see BxAdsModule::serviceCategoriesList
+     */
+    /** 
+     * @ref bx_ads-categories_list "categories_list"
+     */
+    public function serviceCategoriesList($aParams = array())
+    {
+        if(!isset($aParams['show_empty']))
+            $aParams['show_empty'] = true;
+
+        return $this->_oTemplate->categoriesList($aParams);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads 
+     * @subsection bx_ads-browse Browse
+     * @subsubsection bx_ads-browse_category browse_category
+     * 
+     * @code bx_srv('bx_ads', 'browse_category', [...]); @endcode
+     * 
+     * Get reviews rating for particular content
+     * @param $iCategoryId category ID
+     * @param $aParams additional params array, such as empty_message, ajax_paginate, etc
+     * 
+     * @see BxAdsModule::serviceBrowseCategory
+     */
+    /**
+     * @ref bx_ads-browse_category "browse_category"
+     */
+    public function serviceBrowseCategory($sUnitView = false, $bEmptyMessage = true, $bAjaxPaginate = true, $aParams = [])
+    {
+        $mixedResult = parent::serviceBrowseCategory($sUnitView, $bEmptyMessage, $bAjaxPaginate, $aParams);
+        if(empty($mixedResult['content']))
+            return $mixedResult;
+
+        $aCategory = $this->_oDb->getCategories([
+            'type' => 'id', 
+            'id' => !empty($aParams['category']) ? (int)$aParams['category'] : bx_process_input(bx_get('category'), BX_DATA_INT)
+        ]);
+
+        if(!empty($aCategory['title']))
+            $mixedResult['title'] = _t('_bx_ads_page_block_title_entries_by_category_mask', _t($aCategory['title']));
+
+        return $mixedResult;
+    }
+
+    public function serviceGetTimelinePost($aEvent, $aBrowseParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $mixedResult = parent::serviceGetTimelinePost($aEvent, $aBrowseParams);
+        if(!$mixedResult)
+            return $mixedResult;
+        
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+
+        if($this->_oConfig->isSources() && !empty($aContentInfo[$CNF['FIELD_URL']]))
+            $mixedResult['url'] = $aContentInfo[$CNF['FIELD_URL']];
+
+        return $mixedResult;
+    }
+
+    public function serviceGetNotificationsData()
+    {
+        $sModule = $this->_aModule['name'];
+
+        $sEventPrivacy = $sModule . '_allow_view_event_to';
+        if(BxDolPrivacy::getObjectInstance($sEventPrivacy) === false)
+            $sEventPrivacy = '';
+
+        $aResult = parent::serviceGetNotificationsData();
+        $aResult['handlers'] = array_merge($aResult['handlers'], array(
+            array('group' => $sModule . '_interest', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doInterest', 'module_name' => $sModule, 'module_method' => 'get_notifications_interest', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            array('group' => $sModule . '_paid', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'license_register', 'module_name' => $sModule, 'module_method' => 'get_notifications_license_register', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            //---> To Buyer
+            array('group' => $sModule . '_shipped', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'shipped', 'module_name' => $sModule, 'module_method' => 'get_notifications_shipped', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            //<---
+            array('group' => $sModule . '_received', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'received', 'module_name' => $sModule, 'module_method' => 'get_notifications_received', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+
+            array('group' => $sModule . '_offer_added', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'offer_added', 'module_name' => $sModule, 'module_method' => 'get_notifications_offer_added', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            //---> To Offerer
+            array('group' => $sModule . '_offer_accepted', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'offer_accepted', 'module_name' => $sModule, 'module_method' => 'get_notifications_offer_accepted', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            array('group' => $sModule . '_offer_declined', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'offer_declined', 'module_name' => $sModule, 'module_method' => 'get_notifications_offer_declined', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            //<---
+            array('group' => $sModule . '_offer_canceled', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'offer_canceled', 'module_name' => $sModule, 'module_method' => 'get_notifications_offer_canceled', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy)
+        ));
+
+        $aResult['settings'] = array_merge($aResult['settings'], array(
+            array('group' => 'interest', 'unit' => $sModule, 'action' => 'doInterest', 'types' => array('personal')),
+
+            array('group' => 'usage', 'unit' => $sModule, 'action' => 'license_register', 'types' => array('personal')),
+            array('group' => 'usage', 'unit' => $sModule, 'action' => 'shipped', 'types' => array('personal')),
+            array('group' => 'usage', 'unit' => $sModule, 'action' => 'received', 'types' => array('personal')),
+            array('group' => 'usage', 'unit' => $sModule, 'action' => 'offer_added', 'types' => array('personal')),
+            array('group' => 'usage', 'unit' => $sModule, 'action' => 'offer_accepted', 'types' => array('personal')),
+            array('group' => 'usage', 'unit' => $sModule, 'action' => 'offer_declined', 'types' => array('personal')),
+            array('group' => 'usage', 'unit' => $sModule, 'action' => 'offer_canceled', 'types' => array('personal')),
+        ));
+
+        $aResult['alerts'] = array_merge($aResult['alerts'], array(
+            array('unit' => $sModule, 'action' => 'doInterest'),
+            array('unit' => $sModule, 'action' => 'license_register'),
+            array('unit' => $sModule, 'action' => 'shipped'),
+            array('unit' => $sModule, 'action' => 'received'),
+            array('unit' => $sModule, 'action' => 'offer_added'),
+            array('unit' => $sModule, 'action' => 'offer_accepted'),
+            array('unit' => $sModule, 'action' => 'offer_declined'),
+            array('unit' => $sModule, 'action' => 'offer_canceled'),
+        ));
+
+        return $aResult; 
+    }
+
+    public function serviceGetNotificationsInsertData($oAlert, $aHandler, $aDataItems)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = [];
+        switch($oAlert->sAction) {
+            case 'license_register':
+                $aContentInfo = $this->_oDb->getContentInfoById($oAlert->aExtras['product_id']);
+                if(empty($aContentInfo) || !is_array($aContentInfo)) {
+                    $aResult = $aDataItems;
+                    break;
+                }
+
+                foreach($aDataItems as $aDataItem) {
+                    $aDataItem = array_merge($aDataItem, [
+                        'owner_id' => $oAlert->aExtras['profile_id'],
+                        'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                        'object_owner_id' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+                        'object_privacy_view' => BX_DOL_PG_ALL
+                    ]);
+
+                    $aResult[] = $aDataItem;
+                }
+                break;
+
+            case 'shipped':
+                foreach($aDataItems as $aDataItem) {
+                    $aLicense = $this->_oDb->getLicense(['type' => 'entry_id', 'entry_id' => $aDataItem['object_id'], 'newest' => true]);
+                    if(!empty($aLicense) && is_array($aLicense))
+                        $aDataItem['object_owner_id'] = $aLicense['profile_id'];
+
+                    $aResult[] = $aDataItem;
+                }
+                break;
+
+            case 'offer_added':
+            case 'offer_canceled':
+                foreach($aDataItems as $aDataItem) {
+                    $aDataItem['object_owner_id'] = $oAlert->aExtras['object_author_id'];
+
+                    $aResult[] = $aDataItem;
+                }
+                break;
+
+            case 'offer_accepted':
+            case 'offer_declined':
+                foreach($aDataItems as $aDataItem) {
+                    $aDataItem['object_owner_id'] = $oAlert->aExtras['offer_author_id'];
+
+                    $aResult[] = $aDataItem;
+                }
+                break;
+
+            default:
+                $aResult = $aDataItems;
+        }
+
+        return $aResult;
+    }
+
+    public function serviceGetNotificationsInterest($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+    	$aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return [];
+
+        $iInterestedId = (int)$aEvent['subobject_id'];
+        $aInterestedInfo = $this->_oDb->getInterested(['type' => 'id', 'id' => $iInterestedId]);
+        if(empty($aInterestedInfo) || !is_array($aInterestedInfo))
+            return [];
+
+        if(($oInterestedProfile = BxDolProfile::getInstance($aInterestedInfo['profile_id'])) !== false)
+            $sEntryUrl = $oInterestedProfile->getUrl();
+        else
+            $sEntryUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId), '{bx_url_root}');
+
+        return [
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $aContentInfo[$CNF['FIELD_TITLE']],
+            'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'subentry_sample' => $CNF['T']['txt_sample_interest_single'],
+            'subentry_url' => '',
+            'lang_key' => '_bx_ads_txt_ntfs_subobject_interested', //may be empty or not specified. In this case the default one from Notification module will be used.
+        ];
+    }
+
+    public function serviceGetNotificationsLicenseRegister($aEvent)
+    {
+        return $this->_serviceGetNotificationsByEntryAndAction($aEvent, 'license_register');
+    }
+
+    public function serviceGetNotificationsShipped($aEvent)
+    {
+        $aResult = $this->_serviceGetNotificationsByEntryAndAction($aEvent, 'shipped');
+        $aResult['entry_author'] = $aEvent['object_owner_id'];
+
+        return $aResult;
+    }
+
+    public function serviceGetNotificationsReceived($aEvent)
+    {
+        return $this->_serviceGetNotificationsByEntryAndAction($aEvent, 'received');
+    }
+
+    public function serviceGetNotificationsOfferAdded($aEvent)
+    {
+        return $this->_serviceGetNotificationsByOfferAndAction($aEvent, 'offer_added');
+    }
+
+    public function serviceGetNotificationsOfferAccepted($aEvent)
+    {
+        $aResult = $this->_serviceGetNotificationsByOfferAndAction($aEvent, 'offer_accepted');
+        $aResult['entry_author'] = $aEvent['object_owner_id'];
+
+        return $aResult;
+    }
+
+    public function serviceGetNotificationsOfferDeclined($aEvent)
+    {
+        $aResult = $this->_serviceGetNotificationsByOfferAndAction($aEvent, 'offer_declined');
+        $aResult['entry_author'] = $aEvent['object_owner_id'];
+
+        return $aResult;
+    }
+
+    public function serviceGetNotificationsOfferCanceled($aEvent)
+    {
+        return $this->_serviceGetNotificationsByOfferAndAction($aEvent, 'offer_canceled');
+    }
+
+    protected function _serviceGetNotificationsByEntryAndAction($aEvent, $sAction)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+    	$aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $sEntryUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId), '{bx_url_root}');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $aContentInfo[$CNF['FIELD_TITLE']],
+            'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'subentry_sample' => '',
+            'subentry_url' => '',
+            'lang_key' => '_bx_ads_txt_ntfs_object_' . $sAction, //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    protected function _serviceGetNotificationsByOfferAndAction($aEvent, $sAction)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iOfferId = (int)$aEvent['object_id'];
+    	$aOfferInfo = $this->_oDb->getOffersBy(['type' => 'id', 'id' => $iOfferId]);
+        if(empty($aOfferInfo) || !is_array($aOfferInfo))
+            return array();
+
+        $iContentId = $aOfferInfo[$CNF['FIELD_OFR_CONTENT']];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $sEntryUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId), '{bx_url_root}');
+
+        return array(
+            'object_id' => $iContentId,
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $aContentInfo[$CNF['FIELD_TITLE']],
+            'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'subentry_sample' => '',
+            'subentry_url' => '',
+            'lang_key' => '_bx_ads_txt_ntfs_' . $sAction, //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-page_blocks Page Blocks
+     * @subsubsection bx_ads-block_licenses block_licenses
+     * 
+     * @code bx_srv('bx_ads', 'block_licenses', [...]); @endcode
+     * 
+     * Get page block with a list of licenses purchased by currently logged member.
+     *
+     * @return an array describing a block to display on the site or false if there is no enough input data. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
+     * 
+     * @see BxAdsModule::serviceBlockLicenses
+     */
+    /** 
+     * @ref bx_ads-block_licenses "block_licenses"
+     */
+    public function serviceBlockLicenses() 
+    {
+        return $this->_getBlockLicenses();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-page_blocks Page Blocks
+     * @subsubsection bx_ads-block_licenses_administration block_licenses_administration
+     * 
+     * @code bx_srv('bx_ads', 'block_licenses_administration', [...]); @endcode
+     * 
+     * Get page block with a list of all licenses. It's needed for moderators/administrators.
+     *
+     * @return an array describing a block to display on the site or false if there is no enough input data. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
+     * 
+     * @see BxAdsModule::serviceBlockLicensesAdministration
+     */
+    /** 
+     * @ref bx_ads-block_licenses "block_licenses"
+     */
+    public function serviceBlockLicensesAdministration() 
+    {
+        return $this->_getBlockLicenses('administration');
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-page_blocks Page Blocks
+     * @subsubsection bx_ads-block_licenses_note block_licenses_note
+     * 
+     * @code bx_srv('bx_ads', 'block_licenses_note', [...]); @endcode
+     * 
+     * Get page block with a notice for licenses usage.
+     *
+     * @return HTML string with block content to display on the site.
+     * 
+     * @see BxAdsModule::serviceBlockLicensesNote
+     */
+    /** 
+     * @ref bx_ads-block_licenses_note "block_licenses_note"
+     */
+    public function serviceBlockLicensesNote()
+    {
+        return MsgBox(_t('_bx_ads_page_block_content_licenses_note'));
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-get_payment_data get_payment_data
+     * 
+     * @code bx_srv('bx_ads', 'get_payment_data', [...]); @endcode
+     * 
+     * Get an array with module's description. Is needed for payments processing module.
+     * 
+     * @return an array with module's description.
+     * 
+     * @see BxAdsModule::serviceGetPaymentData
+     */
+    /** 
+     * @ref bx_ads-get_payment_data "get_payment_data"
+     */
+    public function serviceGetPaymentData()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oPermalink = BxDolPermalinks::getInstance();
+
+        $aResult = $this->_aModule;
+        $aResult['url_browse_order_common'] = bx_absolute_url($oPermalink->permalink($CNF['URL_LICENSES_COMMON'], array('filter' => '{order}')));
+        $aResult['url_browse_order_administration'] = bx_absolute_url($oPermalink->permalink($CNF['URL_LICENSES_ADMINISTRATION'], array('filter' => '{order}')));
+
+        return $aResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-get_cart_item get_cart_item
+     * 
+     * @code bx_srv('bx_ads', 'get_cart_item', [...]); @endcode
+     * 
+     * Get an array with prodict's description. Is used in Shopping Cart in payments processing module.
+     * 
+     * @param $mixedItemId product's ID or Unique Name.
+     * @return an array with prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxAdsModule::serviceGetCartItem
+     */
+    /** 
+     * @ref bx_ads-get_cart_item "get_cart_item"
+     */
+    public function serviceGetCartItem($mixedItemId, $iClientId = 0)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        if(!$mixedItemId || !is_numeric($mixedItemId))
+            return [];
+
+        $aCommodity = $this->_oDb->getCommodity(['sample' => 'id_with_entry', 'id' => (int)$mixedItemId]);
+        if(empty($aCommodity) || !is_array($aCommodity))
+            return [];
+
+        $iItemId = (int)$aCommodity['id'];
+        $sItemType = $aCommodity['type'];
+
+        $iItemAuthor = 0;
+        $fItemPrice = 0;
+        switch($sItemType) {
+            case BX_ADS_COMMODITY_TYPE_PRODUCT:
+                $iItemAuthor = $aCommodity['entry_' . $CNF['FIELD_AUTHOR']];
+                $fItemPrice = (float)$aCommodity['entry_' . $CNF['FIELD_PRICE']];
+                if($this->_oConfig->isAuction() && (int)$aCommodity['entry_' . $CNF['FIELD_AUCTION']] != 0) {
+                    $aOffer = $this->_oDb->getOffersBy([
+                        'type' => 'content_and_author_ids', 
+                        'content_id' => $iItemId, 
+                        'author_id' => $iClientId,
+                        'status' => BX_ADS_OFFER_STATUS_ACCEPTED
+                    ]);
+
+                    if(!empty($aOffer) && is_array($aOffer))
+                        $fItemPrice = (float)$aOffer[$CNF['FIELD_OFR_AMOUNT']];
+                }
+                break;
+
+            case BX_ADS_COMMODITY_TYPE_PROMOTION:
+                $iItemAuthor = BxDolPayments::getInstance()->getOption('site_admin');
+                $fItemPrice = (float)$aCommodity['amount'];
+                break;
+        }
+
+        if(!$iClientId)
+            $iClientId = bx_get_logged_profile_id();
+
+        return [
+            'id' => $iItemId,
+            'author_id' => $iItemAuthor,
+            'name' => $aCommodity['entry_' . $CNF['FIELD_NAME']] . '-' . $sItemType,
+            'title' => $aCommodity['entry_' . $CNF['FIELD_TITLE']],
+            'description' => $sItemDescription = _t($CNF['T']['txt_cd_ct_' . $sItemType]),
+            'url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'], ['id' => $aCommodity['entry_' . $CNF['FIELD_ID']]])),
+            'price_single' => $fItemPrice,
+            'price_recurring' => '',
+            'period_recurring' => 0,
+            'period_unit_recurring' => '',
+            'trial_recurring' => ''
+        ];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-get_cart_items get_cart_items
+     * 
+     * @code bx_srv('bx_ads', 'get_cart_items', [...]); @endcode
+     * 
+     * Get an array with prodicts' descriptions by seller. Is used in Manual Order Processing in payments processing module.
+     * 
+     * @param $iSellerId seller ID.
+     * @return an array with prodicts' descriptions. Empty array is returned if something is wrong or seller doesn't have any products.
+     * 
+     * @see BxAdsModule::serviceGetCartItems
+     */
+    /** 
+     * @ref bx_ads-get_cart_items "get_cart_items"
+     */
+    public function serviceGetCartItems($iSellerId)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        $iSellerId = (int)$iSellerId;
+        if(empty($iSellerId))
+            return [];
+
+        $aItems = $this->_oDb->getCommodity(['sample' => 'entry_author', 'author' => $iSellerId, 'type' => BX_ADS_COMMODITY_TYPE_PRODUCT]);
+
+        $aResult = [];
+        foreach($aItems as $aItem) {
+            $aItemInfo = $this->_oDb->getContentInfoById((int)$aItem['entry_id']);
+            if(empty($aItemInfo) || !is_array($aItemInfo) || $aItemInfo[$CNF['FIELD_STATUS']] != BX_BASE_MOD_TEXT_STATUS_ACTIVE || empty($aItemInfo[$CNF['FIELD_QUANTITY']]))
+                continue;
+
+            $aResult[] = $this->serviceGetCartItem($aItem['id']);
+        }
+
+        return $aResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-authorize_cart_item authorize_cart_item
+     * 
+     * @code bx_srv('bx_ads', 'authorize_cart_item', [...]); @endcode
+     * 
+     * Authorize the order to process a single time payment inside the Ads module in future. Is called with payment processing module after the order was authorized there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @return an array with authorize prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxAdsModule::serviceAuthorizeCartItem
+     */
+    /** 
+     * @ref bx_ads-authorize_cart_item "authorize_cart_item"
+     */
+    public function serviceAuthorizeCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aItem = $this->serviceGetCartItem($iItemId);
+        if(empty($aItem) || !is_array($aItem))
+            return [];
+
+        $aCommodity = $this->_oDb->getCommodity(['sample' => 'id_with_entry', 'id' => (int)$iItemId]);
+        if(empty($aCommodity) || !is_array($aCommodity))
+            return [];
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-order_authorize 'bx_ads', 'order_authorize' - hook after the order was authorized with payment processing module
+         * - $unit_name - equals `bx_ads`
+         * - $action - equals `order_authorize`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `id` - [int] commodity id 
+         *      - `type` - [string] commodity type, 'product' or 'promotion'
+         *      - `product_id` - [int] ad (entry) id the commodity is associated with
+         *      - `order` - [string] order number provided with payment processing module
+         *      - `count` - [int] number of items in order
+         * @hook @ref hook-bx_ads-order_authorize
+         */
+        bx_alert($this->getName(), 'order_authorize', 0, false, [
+            'id' => $aCommodity['id'],
+            'type' => $aCommodity['type'],
+            'product_id' => $aCommodity['entry_id'],
+            'profile_id' => $iClientId,
+            'order' => $sOrder,
+            'count' => $iItemCount
+        ]);
+
+        return $aItem;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-register_cart_item register_cart_item
+     * 
+     * @code bx_srv('bx_ads', 'register_cart_item', [...]); @endcode
+     * 
+     * Register a processed single time payment inside the Ads module. Is called with payment processing module after the payment was registered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @param $sLicense license number genereted with payment processing module for internal usage
+     * @return an array with purchased prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxAdsModule::serviceRegisterCartItem
+     */
+    /** 
+     * @ref bx_ads-register_cart_item "register_cart_item"
+     */
+    public function serviceRegisterCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        $aCommodity = $this->_oDb->getCommodity(['sample' => 'id', 'id' => (int)$iItemId]);
+        if(empty($aCommodity) || !is_array($aCommodity))
+            return [];
+        
+        $sMethod = '_registerCartItem' . bx_gen_method_name($aCommodity['type']);
+        if(!method_exists($this, $sMethod))
+            return [];
+
+        return $this->$sMethod($iClientId, $iSellerId, $aCommodity, $iItemCount, $sOrder, $sLicense);
+    }
+
+    protected function _registerCartItemProduct($iClientId, $iSellerId, $aCommodity, $iItemCount, $sOrder, $sLicense)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sModule = $this->getName();
+
+    	$aItem = $this->serviceGetCartItem($aCommodity['id']);
+        if(empty($aItem) || !is_array($aItem))
+            return [];
+
+        $aEntry = $this->_oDb->getContentInfoById($aCommodity['entry_id']);
+        if(empty($aEntry) || !is_array($aEntry))
+            return [];
+
+        $iEntryId = (int)$aCommodity['entry_id'];
+        $iEntryQnt = (int)$aEntry[$CNF['FIELD_QUANTITY']];
+        if(($iEntryQnt - $iItemCount) < 0 || !$this->_oDb->registerLicense($iClientId, $iEntryId, $iItemCount, $sOrder, $sLicense))
+            return [];
+
+        $aOffer = $this->_oDb->getOffersBy([
+            'type' => 'content_and_author_ids', 
+            'content_id' => $iEntryId, 
+            'author_id' => $iClientId,
+            'status' => BX_ADS_OFFER_STATUS_ACCEPTED
+        ]);
+
+        if(!empty($aOffer) && is_array($aOffer))
+            $this->_oDb->updateOffer([$CNF['FIELD_OFR_STATUS'] => BX_ADS_OFFER_STATUS_PAID], [$CNF['FIELD_OFR_ID'] => $aOffer[$CNF['FIELD_OFR_ID']]]);
+
+        $iEntryQnt -= $iItemCount;
+        $bEntrySold = $iEntryQnt == 0;
+
+        $aUpdate = [
+            $CNF['FIELD_QUANTITY'] => $iEntryQnt
+        ];
+        if($bEntrySold)
+            $aUpdate = array_merge($aUpdate, [
+                $CNF['FIELD_STATUS'] =>  BX_ADS_STATUS_SOLD,
+                $CNF['FIELD_SOLD'] => time()
+            ]);
+
+        $this->_oDb->updateEntriesBy($aUpdate, [$CNF['FIELD_ID'] => $iEntryId]);
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-license_register 'bx_ads', 'license_register' - hook after the payment for an ad was processed with payment processing module
+         * - $unit_name - equals `bx_ads`
+         * - $action - equals `license_register`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `id` - [int] commodity id 
+         *      - `type` - [string] commodity type, 'product' or 'promotion'
+         *      - `product_id` - [int] ad (entry) id the commodity is associated with
+         *      - `profile_id` - [int] client (buyer) profile id
+         *      - `order` - [string] order number provided with payment processing module
+         *      - `license` - [string] license number generated for the order
+         *      - `count` - [int] number of items in order
+         * @hook @ref hook-bx_ads-license_register
+         */
+        bx_alert($sModule, 'license_register', 0, false, [
+            'id' => $aCommodity['id'],
+            'type' => $aCommodity['type'],
+            'product_id' => $iEntryId,
+            'profile_id' => $iClientId,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'count' => $iItemCount
+        ]);
+
+        if($bEntrySold) {
+            $aParams = $this->_alertParams($aEntry);
+            
+            /**
+             * @hooks
+             * @hookdef hook-bx_ads-sold 'bx_ads', 'sold' - hook after the payment was processed with payment processing module and there is no the ad (entry) for selling anymore
+             * - $unit_name - equals `bx_ads`
+             * - $action - equals `sold`
+             * - $object_id - ad (entry) id
+             * - $sender_id - not used
+             * - $extra_params - array of additional params with the following array keys:
+             *      - `status` - [string] content status
+             *      - `status_admin` - [string] content admin status
+             *      - `privacy_view` - [int] or [string] privacy for view content action, @see BxDolPrivacy
+             *      - `cf` - [int] content's audience filter value
+             * @hook @ref hook-bx_ads-sold
+             */
+            bx_alert($sModule, 'sold', $iEntryId, false, $aParams);
+        }
+
+        $oAuthor = BxDolProfile::getInstanceMagic($aEntry[$CNF['FIELD_AUTHOR']]);
+        $oClient = BxDolProfile::getInstanceMagic($iClientId);
+        $oSeller = BxDolProfile::getInstanceMagic($iSellerId);
+        $sSellerUrl = $oSeller->getUrl();
+        $sSellerName = $oSeller->getDisplayName();
+
+        $sNote = $aEntry[$CNF['FIELD_NOTES_PURCHASED']];
+        if(empty($sNote))
+            $sNote = _t('_bx_ads_txt_purchased_note', $sSellerUrl, $sSellerName);
+
+        $sEmailTemplate = $CNF['ETEMPLATE_PURCHASED'];
+        $aEmailParams = [
+            'client_name' => $oClient->getDisplayName(),
+            'entry_name' => $aEntry[$CNF['FIELD_TITLE']],
+            'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'], ['id' => $iEntryId])),
+            'author_url' => $oAuthor->getUrl(),
+            'author_name' => $oAuthor->getDisplayName(),
+            'vendor_url' => $sSellerUrl,
+            'vendor_name' => $sSellerName,
+            'count' => (int)$iItemCount,
+            'license' => $sLicense,
+            'notes' => $sNote,
+        ];
+
+        $bCancel = false;
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-license_register_notif 'bx_ads', 'license_register_notif' - hook before sending a notification about new license. Allows to override a notification message or cancel the notification
+         * - $unit_name - equals `bx_ads`
+         * - $action - equals `license_register_notif`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `entry_id` - [int] ad (entry) id
+         *      - `order` - [string] order number
+         *      - `recipient_id` - [int] by ref, client (buyer) profile id, can be overridden in hook processing
+         *      - `email_template` - [string] by ref, email template name, can be overridden in hook processing
+         *      - `email_params` - [array] by ref, email params array as key&value pairs, which will be parsed in email subject/body, can be overridden in hook processing
+         *      - `cancel` - [boolean] by ref, if email sending should be canceled or not, can be overridden in hook processing
+         * @hook @ref hook-bx_ads-license_register_notif
+         */
+        bx_alert($sModule, 'license_register_notif', 0, false, [
+            'entry_id' => $iEntryId,
+            'order' => $sOrder,
+            'recipient_id' => &$iClientId,
+            'email_template' => &$sEmailTemplate,
+            'email_params' => &$aEmailParams,
+            'cancel' => &$bCancel
+        ]);
+
+        if(!$bCancel)
+            sendMailTemplate($sEmailTemplate, 0, $iClientId, $aEmailParams);
+
+        return $aItem;
+    }
+    
+    protected function _registerCartItemPromotion($iClientId, $iSellerId, $aCommodity, $iItemCount, $sOrder, $sLicense)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aItem = $this->serviceGetCartItem($aCommodity['id']);
+        if(empty($aItem) || !is_array($aItem))
+            return [];
+
+        $aEntry = $this->_oDb->getContentInfoById($aCommodity['entry_id']);
+        if(empty($aEntry) || !is_array($aEntry))
+            return [];
+
+        $iEntryId = (int)$aCommodity['entry_id'];
+        if(!$this->_oDb->registerPromotion($aEntry[$CNF['FIELD_AUTHOR']], $aCommodity['id'], $iEntryId, $aCommodity['amount'], $sOrder, $sLicense))
+            return [];
+
+        if($aEntry[$CNF['FIELD_STATUS_ADMIN']] == BX_ADS_STATUS_ADMIN_UNPAID) {
+            $aUpdate[$CNF['FIELD_STATUS_ADMIN']] = BX_BASE_MOD_GENERAL_STATUS_ACTIVE;
+            if(!$this->_isModerator() && !$this->_oConfig->isAutoApproveEnabled())
+                $aUpdate[$CNF['FIELD_STATUS_ADMIN']] = BX_BASE_MOD_GENERAL_STATUS_PENDING;
+            
+            $this->_oDb->updateEntriesBy($aUpdate, [$CNF['FIELD_ID'] => $iEntryId]);
+        }
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-promotion_register 'bx_ads', 'promotion_register' - hook after the payment for promotion was processed with payment processing module
+         * - $unit_name - equals `bx_ads`
+         * - $action - equals `promotion_register`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `id` - [int] commodity id 
+         *      - `type` - [string] commodity type, 'product' or 'promotion'
+         *      - `product_id` - [int] ad (entry) id the commodity is associated with
+         *      - `profile_id` - [int] client (buyer) profile id
+         *      - `amount` - [float] amount paid for the promotion
+         *      - `order` - [string] order number provided with payment processing module
+         *      - `license` - [string] license number generated for the order
+         * @hook @ref hook-bx_ads-promotion_register
+         */
+        bx_alert($this->getName(), 'promotion_register', 0, false, [
+            'id' => $aCommodity['id'],
+            'type' => $aCommodity['type'],
+            'product_id' => $iEntryId,
+            'profile_id' => $iClientId,
+            'amount' => $aCommodity['amount'],
+            'order' => $sOrder,
+            'license' => $sLicense,
+        ]);
+
+        return $aItem;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-authorize_subscription_item authorize_subscription_item
+     * 
+     * @code bx_srv('bx_ads', 'authorize_subscription_item', [...]); @endcode
+     * 
+     * Authorize the order to process a subscription (recurring payment) inside the Ads module in future. Is called with payment processing module after the order for subscription was authorized there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @return an array with authorize prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxAdsModule::serviceAuthorizeSubscriptionItem
+     */
+    /** 
+     * @ref bx_ads-authorize_subscription_item "authorize_subscription_item"
+     */
+    public function serviceAuthorizeSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder)
+    {
+        return [];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-register_subscription_item register_subscription_item
+     * 
+     * @code bx_srv('bx_ads', 'register_subscription_item', [...]); @endcode
+     * 
+     * Register a processed subscription (recurring payment) inside the Ads module. Is called with payment processing module after the subscription was registered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @param $sLicense license number genereted with payment processing module for internal usage
+     * @return an array with subscribed prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxAdsModule::serviceRegisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_ads-register_subscription_item "register_subscription_item"
+     */
+    public function serviceRegisterSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return [];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-reregister_cart_item reregister_cart_item
+     * 
+     * @code bx_srv('bx_ads', 'reregister_cart_item', [...]); @endcode
+     * 
+     * Reregister a single time payment inside the Ads module. Is called with payment processing module after the payment was reregistered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemIdOld old item ID.
+     * @param $iItemIdNew new item ID.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @return an array with purchased prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxAdsModule::serviceReregisterCartItem
+     */
+    /** 
+     * @ref bx_ads-reregister_cart_item "reregister_cart_item"
+     */
+    public function serviceReregisterCartItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        return [];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-reregister_subscription_item reregister_subscription_item
+     * 
+     * @code bx_srv('bx_ads', 'reregister_subscription_item', [...]); @endcode
+     * 
+     * Reregister a subscription (recurring payment) inside the Ads module. Is called with payment processing module after the subscription was reregistered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemIdOld old item ID.
+     * @param $iItemIdNew new item ID.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @return an array with subscribed prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxAdsModule::serviceReregisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_ads-reregister_subscription_item "reregister_subscription_item"
+     */
+    public function serviceReregisterSubscriptionItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        return [];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-unregister_cart_item unregister_cart_item
+     * 
+     * @code bx_srv('bx_ads', 'unregister_cart_item', [...]); @endcode
+     * 
+     * Unregister an earlier processed single time payment inside the Ads module. Is called with payment processing module after the payment was unregistered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @param $sLicense license number genereted with payment processing module for internal usage
+     * @return boolean value determining where the payment was unregistered or not.
+     * 
+     * @see BxAdsModule::serviceUnregisterCartItem
+     */
+    /** 
+     * @ref bx_ads-unregister_cart_item "unregister_cart_item"
+     */
+    public function serviceUnregisterCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        $aCommodity = $this->_oDb->getCommodity(['sample' => 'id', 'id' => (int)$iItemId]);
+        if(empty($aCommodity) || !is_array($aCommodity))
+            return [];
+        
+        $sMethod = '_unregisterCartItem' . bx_gen_method_name($aCommodity['type']);
+        if(!method_exists($this, $sMethod))
+            return [];
+
+        return $this->$sMethod($iClientId, $iSellerId, $aCommodity, $iItemCount, $sOrder, $sLicense);
+    }
+    
+    protected function _unregisterCartItemProduct($iClientId, $iSellerId, $aCommodity, $iItemCount, $sOrder, $sLicense)
+    {
+        $iEntryId = (int)$aCommodity['entry_id'];
+
+        if(!$this->_oDb->unregisterLicense($iClientId, $iEntryId, $sOrder, $sLicense))
+            return false;
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-license_unregister 'bx_ads', 'license_unregister' - hook after the payment for ad was refunded with payment processing module
+         * It's equivalent to @ref hook-bx_ads-license_register
+         * @hook @ref hook-bx_ads-license_unregister
+         */
+        bx_alert($this->getName(), 'license_unregister', 0, false, [
+            'id' => $aCommodity['id'],
+            'type' => $aCommodity['type'],
+            'product_id' => $iEntryId,
+            'profile_id' => $iClientId,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'count' => $iItemCount
+        ]);
+
+    	return true;
+    }
+
+    protected function _unregisterCartItemPromotion($iClientId, $iSellerId, $aCommodity, $iItemCount, $sOrder, $sLicense)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aEntry = $this->_oDb->getContentInfoById($aCommodity['entry_id']);
+        if(empty($aEntry) || !is_array($aEntry))
+            return false;
+
+        $iEntryId = (int)$aCommodity['entry_id'];
+
+        if(!$this->_oDb->unregisterPromotion($aEntry[$CNF['FIELD_AUTHOR']], $aCommodity['id'], $iEntryId, $sOrder, $sLicense))
+            return false;
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-promotion_unregister 'bx_ads', 'promotion_unregister' - hook after the payment for promotion was refunded with payment processing module
+         * It's equivalent to @ref hook-bx_ads-promotion_register
+         * @hook @ref hook-bx_ads-promotion_unregister
+         */
+        bx_alert($this->getName(), 'promotion_unregister', 0, false, [
+            'id' => $aCommodity['id'],
+            'type' => $aCommodity['type'],
+            'product_id' => $iEntryId,
+            'profile_id' => $iClientId,
+            'amount' => $aCommodity['amount'],
+            'order' => $sOrder,
+            'license' => $sLicense
+        ]);
+
+    	return true;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-unregister_subscription_item unregister_subscription_item
+     * 
+     * @code bx_srv('bx_ads', 'unregister_subscription_item', [...]); @endcode
+     * 
+     * Unregister an earlier processed subscription (recurring payment) inside the Ads module. Is called with payment processing module after the subscription was unregistered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @param $sLicense license number genereted with payment processing module for internal usage
+     * @return boolean value determining where the subscription was unregistered or not.
+     * 
+     * @see BxAdsModule::serviceUnregisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_ads-unregister_subscription_item "unregister_subscription_item"
+     */
+    public function serviceUnregisterSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+    	return true; 
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_ads Ads
+     * @subsection bx_ads-payments Payments
+     * @subsubsection bx_ads-cancel_subscription_item cancel_subscription_item
+     * 
+     * @code bx_srv('bx_ads', 'cancel_subscription_item', [...]); @endcode
+     * 
+     * Cancel an earlier processed subscription (recurring payment) inside the Ads module. Is called with payment processing module after the subscription was canceled there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @return boolean value determining where the subscription was canceled or not.
+     * 
+     * @see BxAdsModule::serviceCancelSubscriptionItem
+     */
+    /** 
+     * @ref bx_ads-cancel_subscription_item "cancel_subscription_item"
+     */
+    public function serviceCancelSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder)
+    {
+    	return true;
+    }
+
+    public function serviceGetOffersCount($sStatus = '')
+    {
+        if(!$this->_oConfig->isAuction())
+            return 0;
+
+        if(empty($sStatus) || !in_array($sStatus, $this->_aOfferStatuses))
+            $sStatus = BX_ADS_OFFER_STATUS_AWAITING;
+
+        $iUserId = bx_get_logged_profile_id();
+        return $this->_oDb->getOffersBy(array('type' => 'content_author_id', 'author_id' => $iUserId, 'status' => $sStatus, 'count' => true));
+    }
+
+    public function serviceGetLiveUpdates($sStatus, $aMenuItemParent, $aMenuItemChild, $iCount = 0)
+    {
+        if(!in_array($sStatus, $this->_aOfferStatuses))
+            return false;
+
+        $iUserId = bx_get_logged_profile_id();
+        $iCountNew = $this->_oDb->getOffersBy(array('type' => 'content_author_id', 'author_id' => $iUserId, 'status' => $sStatus, 'count' => true));
+        if($iCountNew == $iCount)
+            return false;
+
+        return array(
+            'count' => $iCountNew, // required
+            'method' => 'bx_menu_show_live_update(oData)', // required
+            'data' => array(
+                'code' => BxDolTemplate::getInstance()->parseHtmlByTemplateName('menu_item_addon', array(
+                    'content' => '{count}'
+                )),
+            'mi_parent' => $aMenuItemParent,
+            'mi_child' => $aMenuItemChild
+            ),  // optional, may have some additional data to be passed in JS method provided using 'method' param above.
+    	);
+    }
+
+    public function serviceOffers()       
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oConfig->isAuction())
+            return '';
+
+        $oGrid = BxDolGrid::getObjectInstance($CNF['OBJECT_GRID_OFFERS_ALL']);
+        if(!$oGrid)
+            return '';
+
+        return $oGrid->getCode();
+    }
+
+    public function serviceEntityOffers($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oConfig->isAuction())
+            return '';
+
+        $oGrid = BxDolGrid::getObjectInstance($CNF['OBJECT_GRID_OFFERS']);
+        if(!$oGrid)
+            return '';
+
+        if(empty($iContentId) && ($_iContentId = bx_get('id')) !== false)
+            $iContentId = (int)$_iContentId;
+
+        $oGrid->setContentId($iContentId);
+        return $oGrid->getCode();
+    }
+
+    public function serviceEntityOfferAccepted($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oConfig->isAuction())
+            return '';
+
+        $iUserId = bx_get_logged_profile_id();
+
+        if(empty($iContentId) && ($_iContentId = bx_get('id')) !== false)
+            $iContentId = (int)$_iContentId;
+
+        $aContent = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContent) || !is_array($aContent) || $aContent[$CNF['FIELD_QUANTITY']] <= 0)
+            return '';
+
+        $aOffer = $this->_oDb->getOffersBy(array(
+            'type' => 'content_and_author_ids', 
+            'content_id' => $iContentId, 
+            'author_id' => $iUserId,
+            'status' => BX_ADS_OFFER_STATUS_ACCEPTED
+        ));
+
+        if(empty($aOffer) || !is_array($aOffer))
+            return '';
+
+        return $this->_oTemplate->entryOfferAccepted($iUserId, $aContent, $aOffer);
+    }
+
+    public function serviceIsSourcesAvaliable()
+    {
+        return $this->_oConfig->isSources();
+    }
+    
+    public function serviceGetSources($iAuthorId)
+    {
+        $aSources = $this->_oDb->getSources(['sample' => 'all']);
+        $aOptions = $this->_oDb->getSourcesOptions($iAuthorId);
+
+        $aResult = [];
+        foreach($aSources as $sSource => $aSource) {
+            if(!isset($aOptions[$aSource['option_prefix'] . 'active']) || (int)$aOptions[$aSource['option_prefix'] . 'active']['value'] != 1) 
+                continue;
+
+            foreach($aOptions as $sName => $aOption)
+                if(strpos($sName, $aSource['option_prefix']) !== false)
+                    $aSource['options'][$sName] = $aOption;
+            $aResult[$sSource] = $aSource;
+        }
+
+        return $aResult; 
+    }
+
+    public function serviceGetSource($iAuthorId, $sAuthorSource = '')
+    {
+    	$aSourcesOptions = $this->serviceGetSources($iAuthorId);
+        if(empty($aSourcesOptions) || !is_array($aSourcesOptions))
+            return false;
+
+        $mixedResult = false;
+        if(!empty($sAuthorSource)) {
+            if(!empty($aSourcesOptions[$sAuthorSource]) && is_array($aSourcesOptions[$sAuthorSource]))
+                $mixedResult = $aSourcesOptions[$sAuthorSource];
+        }
+        else 
+            $mixedResult = array_shift($aSourcesOptions);
+
+    	return $mixedResult;
+    }
+
+    public function serviceLoadEntriesFromSourceByTerm($sSourceType, $sTerm)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oSource = $this->getObjectSource($sSourceType, $this->_iProfileId);
+        if(!$oSource)
+            return [];
+
+        $aEntries = $oSource->getEntries(['sample' => 'all_id_title_pairs']);
+        if(empty($aEntries) || !is_array($aEntries))
+            return [];
+
+        $aResults = []; 
+        foreach($aEntries as $aEntry) {
+            if(stripos($aEntry[$CNF['FIELD_TITLE']], $sTerm) === false)
+                continue;
+
+            $aResults[] = [
+                'label' => $aEntry[$CNF['FIELD_TITLE']], 
+                'value' => $aEntry[$CNF['FIELD_SOURCE']], 
+            ];
+        }
+
+        return $aResults; 
+    }
+
+    public function serviceLoadEntryFromSource($sSourceType, $sSource)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = [];
+        $oSource = $this->getObjectSource($sSourceType, $this->_iProfileId);
+        if(!$oSource)
+            return $aResult;
+
+        $aEntry = $oSource->getEntry($sSource);
+        if(empty($aEntry) || !is_array($aEntry))
+            return $aResult;
+
+        $aName = $this->serviceCheckName($aEntry[$CNF['FIELD_TITLE']]);
+        if(!empty($aName) && is_array($aName))
+            $aResult[$CNF['FIELD_NAME']] = ['type' => 'text', 'value' => $aName['name']];
+
+        $aKey2Type = [
+            $CNF['FIELD_TITLE'] => 'text',
+            $CNF['FIELD_URL'] => 'hidden',
+            $CNF['FIELD_TEXT'] => 'text_html',
+            $CNF['FIELD_TAGS'] => 'text',
+            $CNF['FIELD_PRICE'] => 'text', 
+            $CNF['FIELD_QUANTITY'] => 'text'
+        ];
+        foreach($aEntry as $sKey => $sValue)
+            if(isset($aKey2Type[$sKey]))
+                $aResult[$sKey] = ['type' => $aKey2Type[$sKey], 'value' => $sValue];
+
+        return $aResult;
+    }
+
+    public function serviceBlockSourcesDetails($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!isLogged())
+             return bx_is_api() ? [bx_api_get_msg(_t('_Access denied'))] : ['content' => MsgBox(_t('_Access denied'))];
+
+        $iProfileId = !empty($iProfileId) ? $iProfileId : bx_get_logged_profile_id();
+
+        $oForm = BxTemplFormView::getObjectInstance($CNF['OBJECT_FORM_SOURCES_DETAILS'], $CNF['OBJECT_FORM_SOURCES_DETAILS_DISPLAY_EDIT']);
+        $oForm->setProfileId($iProfileId);
+        $oForm->initChecker();
+
+        if($oForm->isSubmitted()) {
+            if($oForm->isValid()) {
+                $aOptions = $this->_oDb->getSourcesOptions();
+                foreach($aOptions as $aOption) {
+                    $sValue = bx_get($aOption['name']) !== false ? bx_get($aOption['name']) : '';
+                    $this->_oDb->updateSourceOption($iProfileId, $aOption['id'], bx_process_input($sValue));
+                }
+                if (bx_is_api()){
+                    return [
+                        bx_api_get_block('form', $oForm->getCodeAPI(), ['ext' => ['name' => $this->getName(), 'request' => ['url' => '/api.php?r=' . $this->getName() . '/block_sources_details', 'immutable' => true]]]),
+                        bx_api_get_block('redirect', ['uri' => '/' . BxDolPermalinks::getInstance()->permalink($CNF['URL_SOURCES']), 'timeout' => 100])
+                        ];
+                }
+                else{
+                    header('Location: ' . bx_absolute_url(BxDolPermalinks::getInstance()->permalink($CNF['URL_SOURCES'])));
+                    return;
+                }
+            }
+            else
+                foreach($oForm->aInputs as $aInput)
+                    if(!empty($aInput['error']) && !empty($aInput['attrs']['bx-data-source'])) {
+                        $sSourceBlock = 'source_' . (int)$aInput['attrs']['bx-data-source'] . '_begin';
+                        if(!empty($oForm->aInputs[$sSourceBlock]))
+                            $oForm->aInputs[$sSourceBlock]['collapsed'] = false;
+                    }
+        }
+
+        return bx_is_api() ? [bx_api_get_block('form', $oForm->getCodeAPI(), ['ext' => ['name' => $this->getName(), 'request' => ['url' => '/api.php?r=' . $this->getName() . '/block_sources_details', 'immutable' => true]]])] : ['content' => $oForm->getCode()];
+        
+    }
+
+    public function isEntryActive($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iViewer = bx_get_logged_profile_id();
+        $bModerator = $this->_isModerator();
+
+        $mixedResult = null;
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-is_entry_active 'bx_ads', 'is_entry_active' - hook to override whether an ad (entry) active or not
+         * It's equivalent to @ref hook-bx_base_general-is_entry_active
+         * @hook @ref hook-bx_ads-is_entry_active
+         */
+        bx_alert($this->getName(), 'is_entry_active', 0, 0, [
+            'viewer_id' => $iViewer, 
+            'is_moderator' => $bModerator, 
+            'content_info' => $aContentInfo, 
+            'override_result' => &$mixedResult
+        ]);
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        if($aContentInfo[$CNF['FIELD_AUTHOR']] == $iViewer || $bModerator)
+            return true;
+
+        if(isset($CNF['FIELD_STATUS']) && !in_array($aContentInfo[$CNF['FIELD_STATUS']], array(BX_BASE_MOD_TEXT_STATUS_ACTIVE, BX_ADS_STATUS_OFFER, BX_ADS_STATUS_SOLD)))
+            return false;
+
+        if(isset($CNF['FIELD_STATUS_ADMIN']) && $aContentInfo[$CNF['FIELD_STATUS_ADMIN']] != 'active')
+            return false;
+
+        return true;        
+    }
+
+    public function isAuction($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->_oConfig->isAuction() && (int)$aContentInfo[$CNF['FIELD_AUCTION']] != 0;
+    }
+
+    public function isAllowedMakeOffer($mixedContent, $isPerformAction = false)
+    {
+        return $this->checkAllowedMakeOffer($mixedContent, $isPerformAction) === CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkAllowedMakeOffer($mixedContent, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sTxtError = '_sys_txt_access_denied';
+
+        $iProfileId = bx_get_logged_profile_id();
+
+        if(!is_array($mixedContent))
+            $mixedContent = $this->_oDb->getContentInfoById((int)$mixedContent);
+
+        if(empty($mixedContent) || !is_array($mixedContent))
+            return _t($sTxtError);
+
+        if(!$this->isAuction($mixedContent) || (int)$mixedContent[$CNF['FIELD_QUANTITY']] <= 0)
+            return _t($sTxtError);
+
+        if($mixedContent[$CNF['FIELD_AUTHOR']] == $iProfileId)
+            return _t($sTxtError);
+
+        $aCheck = checkActionModule($iProfileId, 'make offer', $this->getName(), $isPerformAction);
+        if($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function isAllowedViewOffers($mixedContent, $isPerformAction = false)
+    {
+        return $this->checkAllowedViewOffers($mixedContent, $isPerformAction) === CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkAllowedViewOffers($mixedContent, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sTxtError = '_sys_txt_access_denied';
+
+        if(!is_array($mixedContent))
+            $mixedContent = $this->_oDb->getContentInfoById((int)$mixedContent);
+
+        if(empty($mixedContent) || !is_array($mixedContent))
+            return _t($sTxtError);
+
+        if(!$this->isAuction($mixedContent))
+            return _t($sTxtError);
+
+        if($this->_isModerator())
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        if($mixedContent[$CNF['FIELD_AUTHOR']] == bx_get_logged_profile_id())
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t($sTxtError);
+    }
+
+    public function isAllowedMarkShipped($mixedContent, $isPerformAction = false)
+    {
+        return $this->checkAllowedMarkShipped($mixedContent, $isPerformAction) === CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkAllowedMarkShipped($mixedContent, $isPerformAction = false)
+    {
+        return $this->checkAllowedMarkShippedForProfile($mixedContent, $isPerformAction);
+    }
+
+    public function checkAllowedMarkShippedForProfile($mixedContent, $isPerformAction = false, $iProfileId = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sTxtError = '_sys_txt_access_denied';
+
+        if(!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+                
+        if(!is_array($mixedContent))
+            $mixedContent = $this->_oDb->getContentInfoById((int)$mixedContent);
+
+        if(empty($mixedContent) || !is_array($mixedContent) || !$this->isSingle($mixedContent))
+            return _t($sTxtError);
+
+        if((int)$mixedContent[$CNF['FIELD_SHIPPED']] != 0 || $mixedContent[$CNF['FIELD_STATUS']] != BX_ADS_STATUS_SOLD || $mixedContent[$CNF['FIELD_AUTHOR']] != $iProfileId)
+            return _t($sTxtError);
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function isAllowedMarkReceived($mixedContent, $isPerformAction = false)
+    {
+        return $this->checkAllowedMarkReceived($mixedContent, $isPerformAction) === CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkAllowedMarkReceived($mixedContent, $isPerformAction = false)
+    {
+        return $this->checkAllowedMarkReceivedForProfile($mixedContent, $isPerformAction);
+    }
+
+    public function checkAllowedMarkReceivedForProfile($mixedContent, $isPerformAction = false, $iProfileId = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sTxtError = '_sys_txt_access_denied';
+
+        if(!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        if(!is_array($mixedContent))
+            $mixedContent = $this->_oDb->getContentInfoById((int)$mixedContent);
+
+        if(empty($mixedContent) || !is_array($mixedContent) || !$this->isSingle($mixedContent))
+            return _t($sTxtError);
+
+        if((int)$mixedContent[$CNF['FIELD_RECEIVED']] != 0 || !$this->_oDb->hasLicense($iProfileId, $mixedContent[$CNF['FIELD_ID']]))
+            return _t($sTxtError);
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function onPublished($iContentId)
+    {
+        $this->serviceUpdateCategoriesStats($iContentId);
+
+        parent::onPublished($iContentId);
+    }
+
+    public function onOfferAdded($iOfferId, &$aResult)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aOfferInfo = $this->_oDb->getOffersBy(array('type' => 'id', 'id' => $iOfferId));
+        if(empty($aOfferInfo) || !is_array($aOfferInfo))
+            return;
+
+        $iContentId = (int)$aOfferInfo[$CNF['FIELD_OFR_CONTENT']];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return;
+
+        $iViewer = bx_get_logged_profile_id();        
+        $oViewer = BxDolProfile::getInstanceMagic($iViewer);
+
+        if(getParam($CNF['PARAM_USE_IIN']) == 'on')
+            sendMailTemplate($CNF['ETEMPLATE_OFFER_ADDED'], 0, $aContentInfo[$CNF['FIELD_AUTHOR']], array(
+                'viewer_name' => $oViewer->getDisplayName(),
+                'viewer_url' => $oViewer->getUrl(),
+                'entry_name' => $aContentInfo[$CNF['FIELD_TITLE']],
+                'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php', array(
+                    'i' => $CNF['URI_VIEW_ENTRY'], 
+                    $CNF['FIELD_ID'] => $iContentId
+                )))
+            ));
+
+        $aParams = $this->_alertParamsOffer($aContentInfo, $aOfferInfo);
+        $aParams['override_result'] = &$aResult;
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-offer_added 'bx_ads', 'offer_added' - hook after an offer was added. Allows to override action results.
+         * - $unit_name - equals `bx_ads`
+         * - $action - equals `offer_added`
+         * - $object_id - offer id
+         * - $sender_id - offer author profile id
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `object_id` - [int] content id
+         *      - `object_author_id` - [int] content author profile id
+         *      - `offer_id` - [int] offer id
+         *      - `offer_author_id` - [int] offer author profile id
+         *      - `privacy_view` - [int] privacy for view action, equals to BX_DOL_PG_ALL, @see BxDolPrivacy
+         *      - `override_result` - [array] by ref, results array as key&value pairs, can be overridden in hook processing
+         * @hook @ref hook-bx_ads-offer_added
+         */
+        bx_alert($this->getName(), 'offer_added', $iOfferId, $aOfferInfo[$CNF['FIELD_OFR_AUTHOR']], $aParams);
+    }
+
+    public function onOfferAccepted($iOfferId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aOfferInfo = $this->_oDb->getOffersBy(array('type' => 'id', 'id' => $iOfferId));
+        if(empty($aOfferInfo) || !is_array($aOfferInfo))
+            return;
+
+        $iContentId = (int)$aOfferInfo[$CNF['FIELD_OFR_CONTENT']];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return;
+
+        if($aContentInfo[$CNF['FIELD_STATUS']] != BX_ADS_STATUS_OFFER && ($this->isSingle($aContentInfo) || $this->getAvailableQuantity($aContentInfo) <= 0))
+            if(!$this->_oDb->updateEntriesBy([$CNF['FIELD_STATUS'] => BX_ADS_STATUS_OFFER], [$CNF['FIELD_ID'] => $iContentId]))
+                return;
+
+        $this->serviceUpdateCategoriesStats($iContentId);
+
+        $iOfferer = (int)$aOfferInfo[$CNF['FIELD_OFR_AUTHOR']];
+        $oOfferer = BxDolProfile::getInstanceMagic($iOfferer);
+
+        if(getParam($CNF['PARAM_USE_IIN']) == 'on')
+            sendMailTemplate($CNF['ETEMPLATE_OFFER_ACCEPTED'], 0, $iOfferer, array(
+                'offerer_name' => $oOfferer->getDisplayName(),
+                'offerer_url' => $oOfferer->getUrl(),
+                'entry_name' => $aContentInfo[$CNF['FIELD_TITLE']],
+                'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php', array(
+                    'i' => $CNF['URI_VIEW_ENTRY'], 
+                    $CNF['FIELD_ID'] => $iContentId
+                )))
+            ));
+
+        $aParams = $this->_alertParamsOffer($aContentInfo, $aOfferInfo);
+        
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-offer_accepted 'bx_ads', 'offer_accepted' - hook after an offer was accepted.
+         * It's equivalent to @ref hook-bx_ads-offer_added
+         * except content author profile id is passed to $sender_id and
+         * `override_result` parameter in $extra_params is missing
+         * @hook @ref hook-bx_ads-offer_accepted
+         */
+        bx_alert($this->getName(), 'offer_accepted', $iOfferId, $aContentInfo[$CNF['FIELD_AUTHOR']], $aParams);
+    }
+
+    public function onOfferDeclined($iOfferId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aOfferInfo = $this->_oDb->getOffersBy(array('type' => 'id', 'id' => $iOfferId));
+        if(empty($aOfferInfo) || !is_array($aOfferInfo))
+            return;
+
+        $iContentId = (int)$aOfferInfo[$CNF['FIELD_OFR_CONTENT']];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return;
+
+        $iOfferer = (int)$aOfferInfo[$CNF['FIELD_OFR_AUTHOR']];
+        $oOfferer = BxDolProfile::getInstanceMagic($iOfferer);
+
+        if(getParam($CNF['PARAM_USE_IIN']) == 'on')
+            sendMailTemplate($CNF['ETEMPLATE_OFFER_DECLINED'], 0, $iOfferer, array(
+                'offerer_name' => $oOfferer->getDisplayName(),
+                'offerer_url' => $oOfferer->getUrl(),
+                'entry_name' => $aContentInfo[$CNF['FIELD_TITLE']],
+                'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php', array(
+                    'i' => $CNF['URI_VIEW_ENTRY'], 
+                    $CNF['FIELD_ID'] => $iContentId
+                )))
+            ));
+
+        $aParams = $this->_alertParamsOffer($aContentInfo, $aOfferInfo);
+        
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-offer_declined 'bx_ads', 'offer_declined' - hook after an offer was declined.
+         * It's equivalent to @ref hook-bx_ads-offer_accepted
+         * @hook @ref hook-bx_ads-offer_declined
+         */
+        bx_alert($this->getName(), 'offer_declined', $iOfferId, $aContentInfo[$CNF['FIELD_AUTHOR']], $aParams);
+    }
+
+    public function onOfferCanceled($iOfferId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aOfferInfo = $this->_oDb->getOffersBy(['type' => 'id', 'id' => $iOfferId]);
+        if(empty($aOfferInfo) || !is_array($aOfferInfo))
+            return;
+
+        $iContentId = (int)$aOfferInfo[$CNF['FIELD_OFR_CONTENT']];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return;
+
+        if(!$this->_oDb->updateEntriesBy([$CNF['FIELD_STATUS'] => BX_BASE_MOD_TEXT_STATUS_ACTIVE], [$CNF['FIELD_ID'] => $iContentId]))
+            return;
+
+        $this->serviceUpdateCategoriesStats($iContentId);
+
+        $iOfferer = (int)$aOfferInfo[$CNF['FIELD_OFR_AUTHOR']];
+        $oOfferer = BxDolProfile::getInstanceMagic($iOfferer);
+
+        if(getParam($CNF['PARAM_USE_IIN']) == 'on')
+            sendMailTemplate($CNF['ETEMPLATE_OFFER_CANCELED'], 0, (int)$aContentInfo[$CNF['FIELD_AUTHOR']], [
+                'offerer_name' => $oOfferer->getDisplayName(),
+                'offerer_url' => $oOfferer->getUrl(),
+                'entry_name' => $aContentInfo[$CNF['FIELD_TITLE']],
+                'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php', [
+                    'i' => $CNF['URI_VIEW_ENTRY'], 
+                    $CNF['FIELD_ID'] => $iContentId
+                ]))
+            ]);
+
+        $aParams = $this->_alertParamsOffer($aContentInfo, $aOfferInfo);
+        
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-offer_canceled 'bx_ads', 'offer_canceled' - hook after an offer was declined.
+         * It's equivalent to @ref hook-bx_ads-offer_added
+         * except `override_result` parameter in $extra_params is missing
+         * @hook @ref hook-bx_ads-offer_canceled
+         */
+        bx_alert($this->getName(), 'offer_canceled', $iOfferId, $aOfferInfo[$CNF['FIELD_OFR_AUTHOR']], $aParams);
+    }
+
+    /**
+     * Common methods.
+     */
+    public function onShipped($mixedContent, $iProfileSrc = 0, $iProfileDst = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!is_array($mixedContent))
+            $mixedContent = $this->_oDb->getContentInfoById((int)$mixedContent);
+        $iContentId = (int)$mixedContent[$CNF['FIELD_ID']];
+
+        if(empty($iProfileSrc))
+            $iProfileSrc = (int)$mixedContent[$CNF['FIELD_AUTHOR']];
+
+        $oProfileSrc = BxDolProfile::getInstanceMagic($iProfileSrc);
+
+        $aOffer = $this->_oDb->getOffersBy(array('type' => 'accepted', 'content_id' => $iContentId));
+        $bOffer = !empty($aOffer) && is_array($aOffer);
+
+        if(empty($iProfileDst) && $bOffer)
+            $iProfileDst = (int)$aOffer[$CNF['FIELD_OFR_AUTHOR']];        
+
+        if(!empty($oProfileSrc) && !empty($iProfileDst) && getParam($CNF['PARAM_USE_IIN']) == 'on')
+            sendMailTemplate($CNF['ETEMPLATE_SHIPPED'], 0, $iProfileDst, array(
+                'vendor_name' => $oProfileSrc->getDisplayName(),
+                'vendor_url' => $oProfileSrc->getUrl(),
+                'entry_name' => $mixedContent[$CNF['FIELD_TITLE']],
+                'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php', array(
+                    'i' => $CNF['URI_VIEW_ENTRY'], 
+                    $CNF['FIELD_ID'] => $iContentId
+                )))
+            ));
+
+        $aParams = $this->_alertParams($mixedContent);
+        $aParams = array_merge($aParams, [
+            'profile_src' => $iProfileSrc,
+            'profile_dst' => $iProfileDst,
+            'offer_id' => $bOffer ? (int)$aOffer[$CNF['FIELD_OFR_ID']] : 0,
+        ]);
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-shipped 'bx_ads', 'shipped' - hook after the ad (entry) was marked as shipped
+         * - $unit_name - equals `bx_ads`
+         * - $action - equals `shipped`
+         * - $object_id - ad (entry) id
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `status` - [string] content status
+         *      - `status_admin` - [string] content admin status
+         *      - `privacy_view` - [int] or [string] privacy for view content action, @see BxDolPrivacy
+         *      - `cf` - [int] content's audience filter value
+         *      - `profile_src` - [int] sender profile id
+         *      - `profile_dst` - [int] recipient profile id
+         *      - `offer_id` - [int] offer id if the ad was sold by offer
+         * @hook @ref hook-bx_ads-shipped
+         */
+        bx_alert($this->getName(), 'shipped', $iContentId, false, $aParams);
+    }
+
+    public function onReceived($mixedContent, $iProfileSrc = 0, $iProfileDst = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!is_array($mixedContent))
+            $mixedContent = $this->_oDb->getContentInfoById((int)$mixedContent);
+        $iContentId = (int)$mixedContent[$CNF['FIELD_ID']];
+
+        if(empty($iProfileDst))
+            $iProfileDst = (int)$mixedContent[$CNF['FIELD_AUTHOR']];
+
+        $aOffer = $this->_oDb->getOffersBy(array('type' => 'accepted', 'content_id' => $iContentId));
+        $bOffer = !empty($aOffer) && is_array($aOffer);
+
+        if(empty($iProfileSrc) && $bOffer)
+            $iProfileSrc = (int)$aOffer[$CNF['FIELD_OFR_AUTHOR']];
+        $oProfileSrc = BxDolProfile::getInstanceMagic($iProfileSrc);
+
+        if(!empty($iProfileSrc) && !empty($iProfileDst) && getParam($CNF['PARAM_USE_IIN']) == 'on')
+            sendMailTemplate($CNF['ETEMPLATE_RECEIVED'], 0, $iProfileDst, array(
+                'client_name' => $oProfileSrc->getDisplayName(),
+                'client_url' => $oProfileSrc->getUrl(),
+                'entry_name' => $mixedContent[$CNF['FIELD_TITLE']],
+                'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php', array(
+                    'i' => $CNF['URI_VIEW_ENTRY'], 
+                    $CNF['FIELD_ID'] => $iContentId
+                )))
+            ));
+
+        $aParams = $this->_alertParams($mixedContent);
+        $aParams = array_merge($aParams, [
+            'profile_src' => $iProfileSrc,
+            'profile_dst' => $iProfileDst,
+            'offer_id' => $bOffer ? (int)$aOffer[$CNF['FIELD_OFR_ID']] : 0,
+        ]);
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_ads-received 'bx_ads', 'received' - hook after the ad (entry) was marked as received
+         * It's equivalent to @ref hook-bx_ads-shipped
+         * @hook @ref hook-bx_ads-received
+         */
+        bx_alert($this->getName(), 'received', $iContentId, false, $aParams);
+    }
+
+    public function getObjectSource($sSource, $iProfileId = 0)
+    {
+        $aSource = $this->_oDb->getSources(array('sample' => 'by_name', 'name' => $sSource));
+        if(empty($aSource) || !is_array($aSource) || empty($aSource['class_name']))
+            return false;
+
+        if(!empty($iProfileId)) {
+            $aSource['author'] = (int)$iProfileId;
+            $aSource['options'] = $this->_oDb->getSourcesOptions((int)$iProfileId, $aSource['id']);
+        }
+
+        $sClassPath = !empty($aSource['class_file']) ? BX_DIRECTORY_PATH_ROOT . $aSource['class_file'] : $this->_oConfig->getClassPath() . $aSource['class_name'] . '.php';
+        if(!file_exists($sClassPath))
+            return false;
+
+        require_once($sClassPath);
+        return new $aSource['class_name']($this->_iProfileId, $aSource, $this);
+    }
+
+    public function offerAccept($iId) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aOfferInfo = $this->_oDb->getOffersBy(['type' => 'id', 'id' => $iId]);
+        if(empty($aOfferInfo) || !is_array($aOfferInfo))
+            return false;
+
+        $iContentId = (int)$aOfferInfo[$CNF['FIELD_OFR_CONTENT']];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return false;
+
+        $iQuantity = (int)$aOfferInfo[$CNF['FIELD_OFR_QUANTITY']];
+        $iQuantityAvailable = $this->getAvailableQuantity($aContentInfo);
+        if($iQuantity > $iQuantityAvailable)
+            return _t('_bx_ads_txt_err_offer_wrong_quantity', $iQuantityAvailable);
+
+        if(!$this->_oDb->updateOffer([$CNF['FIELD_OFR_STATUS'] => BX_ADS_OFFER_STATUS_ACCEPTED], [$CNF['FIELD_OFR_ID'] => $iId]))
+            return false;
+
+        $this->onOfferAccepted($iId);
+        return true;
+    }
+
+    public function offerDecline($iId) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oDb->updateOffer([$CNF['FIELD_OFR_STATUS'] => BX_ADS_OFFER_STATUS_DECLINED], [$CNF['FIELD_OFR_ID'] => $iId]))
+            return false;
+
+        $this->onOfferDeclined($iId);
+        return true;        
+    }
+
+    public function offerCancel($iId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oDb->updateOffer([$CNF['FIELD_OFR_STATUS'] => BX_ADS_OFFER_STATUS_CANCELED], [$CNF['FIELD_OFR_ID'] => $iId]))
+            return false;
+
+        $this->onOfferCanceled($iId);
+        return true;
+    }
+    
+    public function offerPay($iId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aOffer = $this->_oDb->getOffersBy(array('type' => 'id', 'id' => $iId));
+        if(empty($aOffer) || !is_array($aOffer))
+            return false;
+
+        $iContent = (int)$aOffer[$CNF['FIELD_OFR_CONTENT']];
+        $aContent = $this->_oDb->getContentInfoById($iContent);
+        $iContentAuthor = (int)$aContent[$CNF['FIELD_AUTHOR']];
+
+        $aReturn = array(
+            'redirect' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php', array(
+                'i' => $CNF['URI_VIEW_ENTRY'], 
+                $CNF['FIELD_ID'] => $aOffer[$CNF['FIELD_OFR_CONTENT']]
+            )))
+        );
+
+        $oPayments = BxDolPayments::getInstance();
+        $aResult = $oPayments->addToCart($iContentAuthor, $this->getName(), $iContent, $aOffer[$CNF['FIELD_OFR_QUANTITY']]);
+        if(!empty($aResult) && is_array($aResult)) {
+            if(!empty($aResult['message']))
+                $aReturn['msg'] = $aResult['message'];
+
+            if(isset($aResult['code']) && (int)$aResult['code'] == 0)
+                $aReturn['redirect'] = $oPayments->getCartUrl($iContentAuthor);
+        }
+
+        return $aReturn;
+    }
+
+    public function processMetasAdd($iContentId)
+    {
+        if(!parent::processMetasAdd($iContentId))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+
+        $aCategoryTypes = $this->_oDb->getCategoryTypes(array('type' => 'all'));
+        foreach($aCategoryTypes as $aCategoryType)
+            $oMetatags->metaAddAuto($iContentId, $aContentInfo, $CNF, $aCategoryType['display_add']);
+
+        return true;
+    }
+
+    public function processMetasEdit($iContentId, $oForm)
+    {
+        if(!parent::processMetasEdit($iContentId, $oForm))
+            return false;
+        
+        $CNF = &$this->_oConfig->CNF;
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        
+        $aCategoryTypes = $this->_oDb->getCategoryTypes(array('type' => 'all'));
+        foreach($aCategoryTypes as $aCategoryType)
+            $oMetatags->metaAddAuto($iContentId, $aContentInfo, $CNF, $aCategoryType['display_edit']);
+
+        return true;
+    }
+
+    public function getCategoryDisplay($sDisplayType, $iCategory = 0)
+    {
+        if(empty($iCategory) && bx_get('category') !== false)
+            $iCategory = (int)bx_get('category');
+
+        if(empty($iCategory))
+            return false;
+
+        $aCategory = $this->_oDb->getCategories(array('type' => 'id_full', 'id' => $iCategory));
+
+        $sKey = 'type_display_' . $sDisplayType;
+        if(empty($aCategory[$sKey]))
+            return false;
+
+        return $aCategory[$sKey];
+    }
+
+    public function isSingle($mixedContent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!is_array($mixedContent))
+            $mixedContent = $this->_oDb->getContentInfoById((int)$mixedContent);
+
+        return (int)$mixedContent[$CNF['FIELD_SINGLE']] == 1;
+    }
+
+    public function getAvailableQuantity($mixedContent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!is_array($mixedContent))
+            $mixedContent = $this->_oDb->getContentInfoById((int)$mixedContent);
+
+        if(!$this->_oConfig->isAuction() || (int)$mixedContent[$CNF['FIELD_AUCTION']] == 0)
+            return (int)$mixedContent[$CNF['FIELD_QUANTITY']];
+
+        $iQuantity = (int)$this->_oDb->getOffersBy([
+            'type' => 'quantity_reserved', 
+            'content_id' => $mixedContent[$CNF['FIELD_ID']], 
+            'status' => BX_ADS_OFFER_STATUS_ACCEPTED
+        ]);
+
+        return (int)$mixedContent[$CNF['FIELD_QUANTITY']] - $iQuantity;
+    }
+
+
+    /**
+     * Internal methods.
+     */
+    protected function _actionMarkAs($sAction)
+    {
+        $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(($mixedResult = $this->serviceMarkAs($sAction, $iContentId)) !== true)
+            return array('msg' => $mixedResult); 
+
+        return array(
+            'reload' => 1
+        );
+    }
+
+    protected function _actionChangeStatus($sStatus)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+
+        if(($mixedResult = $this->checkAllowedEdit($aContentInfo)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return array('msg' => $mixedResult);
+
+        if(!$this->_oDb->updateEntriesBy(array($CNF['FIELD_STATUS'] => $sStatus), array($CNF['FIELD_ID'] => $iContentId)))
+            return array('msg' => _t('_bx_ads_txt_err_cannot_perform_action'));
+
+        $this->serviceUpdateCategoriesStats($aContentInfo);
+
+        return array(
+            'reload' => 1
+        );
+    }
+
+    protected function _serviceEntityForm ($sFormMethod, $iContentId = 0, $sDisplay = false, $sCheckFunction = false, $bErrorMsg = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $mixedContent = $this->_getContent($iContentId, true);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $sDisplayType = false;
+        switch($sFormMethod) {
+            case 'editDataForm':
+                if($sDisplay === false)
+                    $sDisplayType = 'edit';
+                break;
+            case 'viewDataForm':
+            case 'viewDataEntry':
+                $sDisplayType = 'view';
+                break;
+        }
+
+        if($sDisplayType !== false && ($sDisplayNew = $this->getCategoryDisplay($sDisplayType, $aContentInfo[$CNF['FIELD_CATEGORY']])) !== false)
+            $sDisplay = $sDisplayNew;
+
+        return parent::_serviceEntityForm ($sFormMethod, $iContentId, $sDisplay, $sCheckFunction, $bErrorMsg);
+    }
+
+    protected function _getCategoryOptions($iParentId, &$aValues)
+    {
+        $aCategories = $this->_oDb->getCategories(array('type' => 'parent_id', 'parent_id' => $iParentId, 'active' => true));
+        foreach($aCategories as $aCategory) {
+            $aValues[] = array('key' => $aCategory['id'], 'value' => str_repeat('--', (int)$aCategory['level']) . ' ' . _t($aCategory['title']));
+
+            $this->_getCategoryOptions($aCategory['id'], $aValues);
+        }
+    }
+
+    protected function _getSearchableFields($mixedDisplayType = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($mixedDisplayType))
+            $mixedDisplayType = array('add', 'edit');
+
+        $aResult = array();
+        $aDisplays = $this->_oDb->getDisplays($this->_oConfig->getName() . '_entry', $mixedDisplayType);
+        foreach($aDisplays as $aDisplay) {
+            if($aDisplay['display_name'] == $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'])
+                continue;
+
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $aDisplay['display_name'], $this->_oTemplate);
+            if(!$oForm)
+                continue;
+
+            $aResult = array_merge($aResult, $oForm->aInputs);
+        }
+
+        return $aResult;
+    }
+    
+    protected function _getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $bDynamic = isset($aBrowseParams['dynamic_mode']) && (bool)$aBrowseParams['dynamic_mode'] === true;
+
+        $sCategory = '';
+        if(!empty($CNF['FIELD_CATEGORY']) && !empty($aContentInfo[$CNF['FIELD_CATEGORY']])) {
+            $iCategory = (int)$aContentInfo[$CNF['FIELD_CATEGORY']];
+            $aCategory = $this->_oDb->getCategories(array('type' => 'id', 'id' => $iCategory));
+            $sCategory = _t($aCategory['title']);
+            $sCategoryLink = bx_absolute_url(BxDolPermalinks::getInstance()->permalink($CNF['URL_CATEGORIES'], array($CNF['GET_PARAM_CATEGORY'] => $iCategory)));
+        }
+
+        $sPrice = _t('_bx_ads_txt_free');
+        if(!empty($CNF['FIELD_PRICE']) && !empty($aContentInfo[$CNF['FIELD_PRICE']]))
+            $sPrice = _t_format_currency_ext((float)$aContentInfo[$CNF['FIELD_PRICE']], [
+                'sign' => BxDolPayments::getInstance()->getCurrencySign((int)$aContentInfo[$CNF['FIELD_AUTHOR']])
+            ]);
+
+        $aResult = parent::_getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams);
+
+        if(!$this->_bIsApi) {
+            if(empty($aResult['raw']))
+                $aResult['raw'] = '';
+
+            $aResult['text'] = $this->_oTemplate->parseHtmlByName('timeline_post_text.html', [
+                'category_link' => $sCategoryLink,
+                'category_title' => $sCategory,
+                'category_title_attr' => bx_html_attribute($sCategory),
+                'price' => $sPrice,
+                'text' => $aResult['text']
+            ]);
+        }
+        else
+            $aResult = array_merge($aResult, [
+                'price' => $sPrice,
+                'category_title' => $sCategory,
+                'register_click' => $this->_oConfig->isPromotion() ? 'bx_ads/register_click&params[]=' . $aContentInfo[$CNF['FIELD_ID']] : false,
+                'register_impression' => $this->_oConfig->isPromotion() ? 'bx_ads/register_impression&params[]=' . $aContentInfo[$CNF['FIELD_ID']] : false,
+            ]);
+
+        if($this->_oConfig->isSources() && !empty($aContentInfo[$CNF['FIELD_URL']]))
+            $aResult['url'] = $aContentInfo[$CNF['FIELD_URL']];
+
+        if($this->_bIsApi)
+            return $aResult;
+
+        if($this->_oConfig->isPromotion()) {
+            $sJsObject = $this->_oConfig->getJsObject('main');
+            $sHtmlId = $this->_oConfig->getHtmlIds('unit') . $aContentInfo[$CNF['FIELD_ID']] . '-' . time() . '-' . mt_rand(0, 100);
+
+            $aResult['onclick'] = 'return ' . $sJsObject . '.registerClick(this, ' . $aContentInfo[$CNF['FIELD_ID']] . ')';
+            $aResult['raw'] .= $this->_oTemplate->parseHtmlByName('timeline_post_promotion.html', [
+                'html_id' => $sHtmlId,
+                'js_code' => $this->_oTemplate->_wrapInTagJsCode($sJsObject . '.registerTrakerForTimeline(' . $aContentInfo[$CNF['FIELD_ID']] . ', \'' . $sHtmlId . '\');')
+            ]);
+        }
+
+        $sInclude = $this->_oTemplate->addCss(['timeline.css'], $bDynamic);
+        if($bDynamic)
+            $aResult['raw'] .= $sInclude;
+
+        return $aResult;
+    }
+
+    protected function _getBlockLicenses($sType = '') 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sGrid = $CNF['OBJECT_GRID_LICENSES' . (!empty($sType) ? '_' . strtoupper($sType) : '')];
+        $oGrid = BxDolGrid::getObjectInstance($sGrid);
+        if(!$oGrid)
+            return '';
+
+        $this->_oDb->updateLicense(array('new' => 0), array('profile_id' => bx_get_logged_profile_id(), 'new' => 1));
+
+        $this->_oTemplate->addJs(array('licenses.js'));
+        return array(
+            'content' => $this->_oTemplate->getJsCode('licenses', array('sObjNameGrid' => $sGrid)) . $oGrid->getCode(),
+            'menu' => $CNF['OBJECT_MENU_LICENSES']
+        );
+    }
+
+    protected function _alertParamsOffer($aContentInfo, $aOfferInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aParams = array(
+            'object_id' => (int)$aContentInfo[$CNF['FIELD_ID']],
+            'object_author_id' => (int)$aContentInfo[$CNF['FIELD_AUTHOR']],
+
+            'offer_id' => (int)$aOfferInfo[$CNF['FIELD_OFR_ID']],
+            'offer_author_id' => (int)$aOfferInfo[$CNF['FIELD_OFR_AUTHOR']],
+        );
+        if(isset($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            $aParams['privacy_view'] = $aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']];
+
+        return $aParams;
+    }
+}
+
+/** @} */

+ 151 - 0
modules/boonex/ads/updates/14.0.4_14.0.5/source/install/config.php

@@ -0,0 +1,151 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Ads Ads
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_ads',
+    'title' => 'Ads',
+    'note' => 'Ads module.',
+    'version' => '14.0.5',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/ads/',
+    'home_uri' => 'ads',
+
+    'db_prefix' => 'bx_ads_',
+    'class_prefix' => 'BxAds',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Ads',
+
+    /**
+     * List of page triggers.
+     */
+    'page_triggers' => array (
+    	'trigger_page_profile_view_entry',
+    ),
+    
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+        'trigger_profile_view_submenu',
+        'trigger_group_view_submenu',
+    ),
+
+    /**
+     * Storage objects to automatically delete files from upon module uninstallation.
+     * Note. Don't add storage objects used in transcoder objects.
+     */
+    'storages' => array(
+        'bx_ads_covers',
+    	'bx_ads_photos',
+        'bx_ads_videos',
+        'bx_ads_files'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+        'bx_ads_preview',
+        'bx_ads_gallery',
+        'bx_ads_cover',
+
+        'bx_ads_preview_photos',
+        'bx_ads_gallery_photos',
+        'bx_ads_view_photos',
+
+        'bx_ads_videos_poster',
+        'bx_ads_videos_poster_preview',
+        'bx_ads_videos_mp4',
+        'bx_ads_videos_mp4_hd',
+
+        'bx_ads_preview_files',
+        'bx_ads_gallery_files'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_ads',
+    	'bx_ads_cmts'
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+    	'process_esearches' => 1,
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable_success' => array(
+        'process_menu_triggers' => 1,
+        'process_page_triggers' => 1,
+    	'process_esearches' => 1,
+        'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'update_relations' => 1,
+        'unregister_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+        'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Relations Section
+     */
+    'relations' => array(
+    	'bx_timeline',
+    	'bx_notifications'
+    ),
+
+);
+
+/** @} */

+ 695 - 0
modules/boonex/ads/updates/14.0.4_14.0.5/source/install/langs/en.xml

@@ -0,0 +1,695 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_ads"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_cmts"><![CDATA[Ads' Comments]]></string>
+    <string name="_bx_ads_reactions"><![CDATA[Ads' Reactions]]></string>
+
+    <string name="_bx_ads_option_enable_auto_approve"><![CDATA[Auto approve an ad after creation]]></string>
+    <string name="_bx_ads_option_summary_chars"><![CDATA[Number of characters in auto-cropped ad summary]]></string>
+    <string name="_bx_ads_option_plain_summary_chars"><![CDATA[Number of characters in plain ad description when ad hasn't thumb]]></string>
+    <string name="_bx_ads_option_per_page_browse"><![CDATA[Number of items per page]]></string>
+    <string name="_bx_ads_option_per_page_profile"><![CDATA[Number of items on profile/group page]]></string>
+    <string name="_bx_ads_option_rss_num"><![CDATA[Number of items in RSS feed]]></string>
+    <string name="_bx_ads_option_searchable_fields"><![CDATA[Searchable fields for keyword search]]></string>
+    <string name="_bx_ads_option_per_page_for_favorites_lists"><![CDATA[Number of favorite lists per page]]></string>
+    <string name="_bx_ads_option_lifetime"><![CDATA[Deactivate automatically after (in days)]]></string>
+    <string name="_bx_ads_option_offer_lifetime"><![CDATA[Decline offers automatically after (in hours)]]></string>
+    <string name="_bx_ads_option_internal_interested_notification"><![CDATA[Use internal notification about Interested action]]></string>
+    <string name="_bx_ads_option_enable_auction"><![CDATA[Enable Auction mode]]></string>
+    <string name="_bx_ads_option_enable_sources"><![CDATA[Enable external sources]]></string>
+    <string name="_bx_ads_option_enable_promotion"><![CDATA[Enable paid promotion]]></string>
+    <string name="_bx_ads_option_promotion_cpm"><![CDATA[Promotion price (CPM)]]></string>
+
+    <string name="_bx_ads_src_cpt_shopify_admin"><![CDATA[Shopify (Admin API)]]></string>
+    <string name="_bx_ads_src_dsc_shopify_admin"><![CDATA[For a custom app created directly in the Shopify admin.]]></string>
+    <string name="_bx_ads_src_opt_cpt_active"><![CDATA[Active]]></string>
+    <string name="_bx_ads_src_opt_dsc_active"><![CDATA[Activate(Deactivate) source]]></string>
+    <string name="_bx_ads_src_opt_cpt_shop_domain"><![CDATA[Shop domain]]></string>
+    <string name="_bx_ads_src_opt_dsc_shop_domain"><![CDATA[]]></string>
+    <string name="_bx_ads_src_opt_cpt_access_token"><![CDATA[Access token]]></string>
+    <string name="_bx_ads_src_opt_dsc_access_token"><![CDATA[]]></string>
+
+
+    <string name="_bx_ads_form_sources_details"><![CDATA[Sources Details]]></string>
+    <string name="_bx_ads_form_sources_details_display_edit"><![CDATA[Edit Sources Details]]></string>
+    <string name="_bx_ads_form_sources_details_input_do_submit"><![CDATA[Submit]]></string>
+
+    <string name="_bx_ads_form_category"><![CDATA[Category]]></string>
+    <string name="_bx_ads_form_category_display_add"><![CDATA[Add Category]]></string>
+    <string name="_bx_ads_form_category_display_edit"><![CDATA[Edit Category]]></string>
+    <string name="_bx_ads_form_category_display_delete"><![CDATA[Delete Category]]></string>
+    <string name="_bx_ads_form_category_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_ads_form_category_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_ads_form_category_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_ads_form_category_input_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_ads_form_category_input_sys_parent_id"><![CDATA[Parent]]></string>
+    <string name="_bx_ads_form_category_input_parent_id"><![CDATA[Parent category]]></string>
+    <string name="_bx_ads_form_category_input_parent_id_empty"><![CDATA[No parent]]></string>
+    <string name="_bx_ads_form_category_input_sys_title"><![CDATA[Title]]></string>
+    <string name="_bx_ads_form_category_input_title"><![CDATA[Title]]></string>
+    <string name="_bx_ads_form_category_input_title_err"><![CDATA[Title is essential. Please, fill in this field.]]></string>
+    <string name="_bx_ads_form_category_input_sys_text"><![CDATA[Text]]></string>
+    <string name="_bx_ads_form_category_input_text"><![CDATA[Text]]></string>
+    <string name="_bx_ads_form_category_input_sys_type"><![CDATA[Type]]></string>
+    <string name="_bx_ads_form_category_input_type"><![CDATA[Type]]></string>
+    <string name="_bx_ads_form_category_input_type_err"><![CDATA[Type is essential. Please, select a value for this field.]]></string>
+    <string name="_bx_ads_form_category_input_type_info"><![CDATA[Type determines a set of fields for entries in this category.]]></string>
+    <string name="_bx_ads_form_category_input_sys_type_clone"><![CDATA[Clone type]]></string>
+    <string name="_bx_ads_form_category_input_type_clone"><![CDATA[Clone type]]></string>
+    <string name="_bx_ads_form_category_input_sys_type_title"><![CDATA[Type title]]></string>
+    <string name="_bx_ads_form_category_input_type_title"><![CDATA[Type title]]></string>
+    <string name="_bx_ads_form_category_input_sys_icon"><![CDATA[Icon]]></string>
+    <string name="_bx_ads_form_category_input_icon"><![CDATA[Icon]]></string>
+    <string name="_bx_ads_form_category_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string>
+    <string name="_bx_ads_form_category_input_delete_confirm"><![CDATA[I am sure I want to delete this category]]></string>
+    <string name="_bx_ads_form_category_input_delete_confirm_info"><![CDATA[This action can not be undone]]></string>
+    <string name="_bx_ads_form_category_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete the category]]></string>
+
+    <string name="_bx_ads_form_entry"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_form_entry_display_add"><![CDATA[Add Ad]]></string>
+    <string name="_bx_ads_form_entry_display_edit_budget"><![CDATA[Edit Ad's Budget]]></string>
+    <string name="_bx_ads_form_entry_display_delete"><![CDATA[Delete Ad]]></string>
+
+    <string name="_bx_ads_form_entry_price_display_add"><![CDATA[With Price: Add]]></string>
+    <string name="_bx_ads_form_entry_price_display_edit"><![CDATA[With Price: Edit]]></string>
+    <string name="_bx_ads_form_entry_price_display_view"><![CDATA[With Price: View]]></string>
+
+    <string name="_bx_ads_form_entry_price_year_display_add"><![CDATA[With Price and Year: Add]]></string>
+    <string name="_bx_ads_form_entry_price_year_display_edit"><![CDATA[With Price and Year: Edit]]></string>
+    <string name="_bx_ads_form_entry_price_year_display_view"><![CDATA[With Price and Year: View]]></string>
+
+    <string name="_bx_ads_form_entry_input_author"><![CDATA[Author]]></string>
+    <string name="_bx_ads_form_entry_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_ads_form_entry_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_ads_form_entry_input_sys_title"><![CDATA[Title]]></string>
+    <string name="_bx_ads_form_entry_input_title"><![CDATA[Title]]></string>
+    <string name="_bx_ads_form_entry_input_title_err"><![CDATA[Title is essential. Please, fill in this field]]></string>
+    <string name="_bx_ads_form_entry_input_sys_name"><![CDATA[Name]]></string>
+    <string name="_bx_ads_form_entry_input_name"><![CDATA[Name]]></string>
+    <string name="_bx_ads_form_entry_input_name_err"><![CDATA[Name should be unique and is needed for internal usage. Please, fill in this field]]></string>
+    <string name="_bx_ads_form_entry_input_sys_price"><![CDATA[Price]]></string>
+    <string name="_bx_ads_form_entry_input_price"><![CDATA[Price]]></string>
+    <string name="_bx_ads_form_entry_input_sys_auction"><![CDATA[Auction]]></string>
+    <string name="_bx_ads_form_entry_input_auction"><![CDATA[Use auction]]></string>
+    <string name="_bx_ads_form_entry_input_auction_info"><![CDATA[Allow buyers not only to pay a defined price but to make offers too.]]></string>
+    <string name="_bx_ads_form_entry_input_sys_quantity"><![CDATA[Quantity]]></string>
+    <string name="_bx_ads_form_entry_input_quantity"><![CDATA[Quantity]]></string>
+    <string name="_bx_ads_form_entry_input_quantity_err"><![CDATA[Quantity is essential. Please, fill in this field]]></string>
+    <string name="_bx_ads_form_entry_input_sys_year"><![CDATA[Year]]></string>
+    <string name="_bx_ads_form_entry_input_year"><![CDATA[Year]]></string>
+    <string name="_bx_ads_form_entry_input_sys_category"><![CDATA[Category]]></string>
+    <string name="_bx_ads_form_entry_input_sys_category_view"><![CDATA[Category]]></string>
+    <string name="_bx_ads_form_entry_input_category_view"><![CDATA[Category]]></string>
+    <string name="_bx_ads_form_entry_input_sys_category_select"><![CDATA[Category]]></string>
+    <string name="_bx_ads_form_entry_input_category_select"><![CDATA[Category]]></string>
+    <string name="_bx_ads_form_entry_input_category_select_err"><![CDATA[Category field is essential. Please, fill it in.]]></string>
+    <string name="_bx_ads_form_entry_input_sys_text"><![CDATA[Text]]></string>
+    <string name="_bx_ads_form_entry_input_text"><![CDATA[Text]]></string>
+    <string name="_bx_ads_form_entry_input_text_err"><![CDATA[Text field can't be empty]]></string>
+    <string name="_bx_ads_form_entry_input_sys_notes_purchased"><![CDATA[After purchase notes]]></string>
+    <string name="_bx_ads_form_entry_input_notes_purchased"><![CDATA[After purchase notes]]></string>
+    <string name="_bx_ads_form_entry_input_notes_purchased_inf"><![CDATA[Will be attached to email message which is sent to buyer after purchase.]]></string>
+    <string name="_bx_ads_form_entry_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string>
+    <string name="_bx_ads_form_entry_input_delete_confirm"><![CDATA[I am sure I want to delete this ad]]></string>
+    <string name="_bx_ads_form_entry_input_delete_confirm_info"><![CDATA[This action can not be undone]]></string>
+    <string name="_bx_ads_form_entry_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete the ad]]></string>
+    <string name="_bx_ads_form_entry_input_sys_covers"><![CDATA[Header Image]]></string>
+    <string name="_bx_ads_form_entry_input_covers"><![CDATA[Header Image]]></string>
+    <string name="_bx_ads_form_entry_input_covers_uploader_simple_title"><![CDATA[Upload]]></string>
+    <string name="_bx_ads_form_entry_input_covers_uploader_html5_title"><![CDATA[Upload]]></string>
+    <string name="_bx_ads_form_entry_input_sys_pictures"><![CDATA[Images]]></string>
+    <string name="_bx_ads_form_entry_input_pictures"><![CDATA[]]></string>
+    <string name="_bx_ads_form_entry_input_sys_videos"><![CDATA[Videos]]></string>
+    <string name="_bx_ads_form_entry_input_videos"><![CDATA[]]></string>
+    <string name="_bx_ads_form_entry_input_sys_files"><![CDATA[Files]]></string>
+    <string name="_bx_ads_form_entry_input_files"><![CDATA[]]></string>
+    <string name="_bx_ads_form_entry_input_sys_polls"><![CDATA[Polls]]></string>
+    <string name="_bx_ads_form_entry_input_sys_link"><![CDATA[Links]]></string>
+    <string name="_bx_ads_form_entry_input_link_err_delete"><![CDATA[Cannot remove the link. Please report.]]></string>
+    <string name="_bx_ads_form_entry_input_sys_allow_view_to"><![CDATA[Visibility]]></string>
+    <string name="_bx_ads_form_entry_input_allow_view_to"><![CDATA[Visibility]]></string>
+    <string name="_bx_ads_form_entry_input_allow_view_favorite_list"><![CDATA[Visibility]]></string>
+    <string name="_bx_ads_form_entry_input_sys_date_added"><![CDATA[Created]]></string>
+    <string name="_bx_ads_form_entry_input_date_added"><![CDATA[Created]]></string>
+    <string name="_bx_ads_form_entry_input_sys_date_changed"><![CDATA[Updated]]></string>
+    <string name="_bx_ads_form_entry_input_date_changed"><![CDATA[Updated]]></string>
+    <string name="_bx_ads_form_entry_input_sys_attachments"><![CDATA[Attachments]]></string>
+    <string name="_bx_ads_form_entry_input_sys_source_type"><![CDATA[Source type]]></string>
+    <string name="_bx_ads_form_entry_input_source_type"><![CDATA[Source type]]></string>
+    <string name="_bx_ads_form_entry_input_sys_source"><![CDATA[Source]]></string>
+    <string name="_bx_ads_form_entry_input_source"><![CDATA[Source]]></string>
+    <string name="_bx_ads_form_entry_input_source_paceholder"><![CDATA[Type product title here...]]></string>
+    <string name="_bx_ads_form_entry_input_sys_url"><![CDATA[Link]]></string>
+    <string name="_bx_ads_form_entry_input_sys_budget_total"><![CDATA[Campaign budget]]></string>
+    <string name="_bx_ads_form_entry_input_budget_total"><![CDATA[Campaign budget]]></string>
+    <string name="_bx_ads_form_entry_input_budget_total_err_decreased"><![CDATA[Campaign budget cannot be decreased.]]></string>
+    <string name="_bx_ads_form_entry_input_sys_budget_daily"><![CDATA[Max daily budget]]></string>
+    <string name="_bx_ads_form_entry_input_budget_daily"><![CDATA[Max daily budget]]></string>
+    <string name="_bx_ads_form_entry_input_sys_header_beg_seg"><![CDATA[Segmentation: begin]]></string>
+    <string name="_bx_ads_form_entry_input_header_beg_seg"><![CDATA[Segmentation]]></string>
+    <string name="_bx_ads_form_entry_input_sys_header_end_seg"><![CDATA[Segmentation: end]]></string>
+    <string name="_bx_ads_form_entry_input_sys_seg"><![CDATA[Enable]]></string>
+    <string name="_bx_ads_form_entry_input_seg"><![CDATA[Enable segmentation]]></string>
+    <string name="_bx_ads_form_entry_input_sys_seg_gender"><![CDATA[Gender]]></string>
+    <string name="_bx_ads_form_entry_input_seg_gender"><![CDATA[Gender]]></string>
+    <string name="_bx_ads_form_entry_input_sys_seg_age"><![CDATA[Age]]></string>
+    <string name="_bx_ads_form_entry_input_seg_age"><![CDATA[Age]]></string>
+    <string name="_bx_ads_form_entry_input_sys_seg_country"><![CDATA[Country]]></string>
+    <string name="_bx_ads_form_entry_input_seg_country"><![CDATA[Country]]></string>
+    <string name="_bx_ads_form_entry_input_seg_country_all"><![CDATA[All countries]]></string>
+
+    <string name="_bx_ads_form_poll"><![CDATA[Polls]]></string>
+    <string name="_bx_ads_form_poll_display_add"><![CDATA[Add Poll]]></string>
+    <string name="_bx_ads_form_poll_input_sys_text"><![CDATA[Question]]></string>
+    <string name="_bx_ads_form_poll_input_text"><![CDATA[Question]]></string>
+    <string name="_bx_ads_form_poll_input_text_err"><![CDATA[Question is essential. Please, fill in this field]]></string>
+    <string name="_bx_ads_form_poll_input_sys_answers"><![CDATA[Answers]]></string>
+    <string name="_bx_ads_form_poll_input_answers"><![CDATA[Answers]]></string>
+    <string name="_bx_ads_form_poll_input_answers_err"><![CDATA[Poll should have at least 2 non-empty answers.]]></string>
+    <string name="_bx_ads_form_poll_input_answers_add"><![CDATA[Add More]]></string>
+    <string name="_bx_ads_form_poll_input_sys_do_submit"><![CDATA[Add]]></string>
+    <string name="_bx_ads_form_poll_input_do_submit"><![CDATA[Add]]></string>
+    <string name="_bx_ads_form_poll_input_do_cancel"><![CDATA[Close]]></string>
+
+    <string name="_bx_ads_form_attach_link"><![CDATA[Attach Link]]></string>
+    <string name="_bx_ads_form_attach_link_display_add"><![CDATA[Attach Link]]></string>
+    <string name="_bx_ads_form_attach_link_input_sys_content_id"><![CDATA[Content Id]]></string>
+    <string name="_bx_ads_form_attach_link_input_sys_url"><![CDATA[Add Link]]></string>
+    <string name="_bx_ads_form_attach_link_input_url_err"><![CDATA[Correct Link is essential. Please, fill in this field.]]></string>
+    <string name="_bx_ads_form_attach_link_input_url"><![CDATA[Add Link]]></string>
+    <string name="_bx_ads_form_attach_link_input_do_submit"><![CDATA[Add]]></string>
+    <string name="_bx_ads_form_attach_link_input_sys_do_submit"><![CDATA[Add]]></string>
+    <string name="_bx_ads_form_attach_link_input_do_cancel"><![CDATA[Close]]></string>
+    
+    <string name="_bx_ads_form_offer"><![CDATA[Offer]]></string>
+    <string name="_bx_ads_form_offer_display_add"><![CDATA[Add Offer]]></string>
+    <string name="_bx_ads_form_offer_display_view"><![CDATA[View Offer]]></string>
+    <string name="_bx_ads_form_offer_input_sys_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_ads_form_offer_input_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_ads_form_offer_input_amount_err"><![CDATA[Amount is essential. Please, fill in this field.]]></string>
+    <string name="_bx_ads_form_offer_input_sys_quantity"><![CDATA[Quantity]]></string>
+    <string name="_bx_ads_form_offer_input_quantity"><![CDATA[Quantity]]></string>
+    <string name="_bx_ads_form_offer_input_quantity_err"><![CDATA[Quantity is essential. Please, fill in this field.]]></string>
+    <string name="_bx_ads_form_offer_input_sys_total"><![CDATA[Total]]></string>
+    <string name="_bx_ads_form_offer_input_total"><![CDATA[Total]]></string>
+    <string name="_bx_ads_form_offer_input_sys_message"><![CDATA[Message]]></string>
+    <string name="_bx_ads_form_offer_input_message"><![CDATA[Message]]></string>
+    <string name="_bx_ads_form_offer_input_sys_do_submit"><![CDATA[Add]]></string>
+    <string name="_bx_ads_form_offer_input_do_submit"><![CDATA[Add]]></string>
+    <string name="_bx_ads_form_offer_input_sys_do_cancel"><![CDATA[Add]]></string>
+    <string name="_bx_ads_form_offer_input_do_cancel"><![CDATA[Close]]></string>
+
+    <string name="_bx_ads_acl_action_create_entry"><![CDATA[Create Ad]]></string>
+    <string name="_bx_ads_acl_action_delete_entry"><![CDATA[Delete Ad]]></string>
+    <string name="_bx_ads_acl_action_view_entry"><![CDATA[View Ad]]></string>
+    <string name="_bx_ads_acl_action_set_thumb"><![CDATA[Set Ad Header Image]]></string>
+    <string name="_bx_ads_acl_action_edit_any_entry"><![CDATA[Edit Any Ad]]></string>
+    <string name="_bx_ads_acl_action_delete_any_entry"><![CDATA[Delete Any Ad]]></string>
+    <string name="_bx_ads_acl_action_make_offer"><![CDATA[Make Offer]]></string>
+
+    <string name="_bx_ads_page_title_sys_create_entry"><![CDATA[New Ad]]></string>
+    <string name="_bx_ads_page_title_create_entry"><![CDATA[New Ad]]></string>
+    <string name="_bx_ads_page_block_title_create_entry"><![CDATA[New Ad]]></string>
+    <string name="_bx_ads_page_title_sys_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_ads_page_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_ads_page_block_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_ads_page_title_sys_edit_entry_budget"><![CDATA[Edit Budget]]></string>
+    <string name="_bx_ads_page_title_edit_entry_budget"><![CDATA[Edit Budget]]></string>
+    <string name="_bx_ads_page_block_title_edit_entry_budget"><![CDATA[Edit Budget]]></string>
+    <string name="_bx_ads_page_title_sys_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_ads_page_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_ads_page_block_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_ads_page_title_sys_view_entry"><![CDATA[View Ad]]></string>
+    <string name="_bx_ads_page_title_view_entry"><![CDATA[{title}]]></string>
+    <string name="_bx_ads_page_block_title_entry_breadcrumb"><![CDATA[Breadcrumb]]></string>
+    <string name="_bx_ads_page_block_title_entry_text"><![CDATA[Ad Text]]></string>
+    <string name="_bx_ads_page_title_browse"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_page_title_search_results"><![CDATA[Ads matching "{0}"]]></string>
+    <string name="_bx_ads_page_title_browse_by_author"><![CDATA[Ads of {display_name}]]></string>
+    <string name="_bx_ads_page_title_browse_by_context"><![CDATA[Ads in {display_name}]]></string>
+    <string name="_bx_ads_page_title_browse_featured"><![CDATA[Featured Ads]]></string>
+    <string name="_bx_ads_page_title_browse_recent"><![CDATA[Latest Ads]]></string>
+    <string name="_bx_ads_page_title_browse_popular"><![CDATA[Popular Ads]]></string>
+    <string name="_bx_ads_page_title_browse_updated"><![CDATA[Updated Ads]]></string>
+    <string name="_bx_ads_page_title_browse_category"><![CDATA[Ads in {display_name}]]]]></string>
+    <string name="_bx_ads_page_title_sys_home"><![CDATA[Ads Home]]></string>
+    <string name="_bx_ads_page_title_home"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_page_block_title_featured_entries_view_extended"><![CDATA[Featured Ads]]></string>
+    <string name="_bx_ads_page_block_title_recent_entries"><![CDATA[Latest Ads (Gallery View)]]></string>
+    <string name="_bx_ads_page_block_title_recent_entries_view_extended"><![CDATA[Latest Ads]]></string>
+    <string name="_bx_ads_page_block_title_recent_entries_view_full"><![CDATA[Latest Ads (Full View)]]></string>
+    <string name="_bx_ads_page_block_title_entry_social_sharing"><![CDATA[Share]]></string>
+    <string name="_bx_ads_page_block_title_entry_author"><![CDATA[Author]]></string>
+    <string name="_bx_ads_page_block_title_entry_actions"><![CDATA[Actions]]></string>
+    <string name="_bx_ads_page_block_title_entry_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_ads_page_block_title_entry_reports"><![CDATA[Reports]]></string>
+    <string name="_bx_ads_page_block_title_entry_comments_link"><![CDATA[Comments to <a href="{entry_link}">{title}</a>]]></string>
+    <string name="_bx_ads_page_block_title_sys_entry_promotion_growth"><![CDATA[Promotion (Growth)]]></string>
+    <string name="_bx_ads_page_block_title_entry_promotion_growth"><![CDATA[Promotion for <a href="{entry_link}">{title}</a>]]></string>
+    <string name="_bx_ads_page_block_title_sys_entry_promotion_summary"><![CDATA[Promotion (Summary)]]></string>
+    <string name="_bx_ads_page_block_title_entry_promotion_summary"><![CDATA[Summary]]></string>
+    <string name="_bx_ads_page_block_title_sys_entry_promotion_roi"><![CDATA[ROI]]></string>
+    <string name="_bx_ads_page_block_title_entry_promotion_roi"><![CDATA[ROI]]></string>
+    <string name="_bx_ads_page_block_title_entry_reviews"><![CDATA[Reviews]]></string>
+    <string name="_bx_ads_page_block_title_entry_reviews_link"><![CDATA[Reviews to <a href="{entry_link}">{title}</a>]]></string>
+    <string name="_bx_ads_page_block_title_entry_reviews_rating"><![CDATA[Rating]]></string>
+    <string name="_bx_ads_page_block_title_entry_location"><![CDATA[Location]]></string>
+    <string name="_bx_ads_page_block_title_entry_attachments"><![CDATA[Attachments]]></string>
+    <string name="_bx_ads_page_block_title_entry_polls"><![CDATA[Polls]]></string>
+    <string name="_bx_ads_page_block_title_entry_all_actions"><![CDATA[All Actions]]></string>
+    <string name="_bx_ads_page_title_sys_entries_popular"><![CDATA[Popular Ads]]></string>
+    <string name="_bx_ads_page_title_entries_popular"><![CDATA[Popular Ads]]></string>
+    <string name="_bx_ads_page_title_sys_entries_updated"><![CDATA[Updated Ads]]></string>
+    <string name="_bx_ads_page_title_entries_updated"><![CDATA[Updated Ads]]></string>
+    <string name="_bx_ads_page_title_sys_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_ads_page_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_ads_page_title_sys_entries_favorites"><![CDATA[Favorives Reviews]]></string>
+    <string name="_bx_ads_page_title_entries_favorites"><![CDATA[Favorives Reviews]]></string>
+    <string name="_bx_ads_page_block_title_search_form"><![CDATA[Search Form]]></string>
+    <string name="_bx_ads_page_block_title_search_results"><![CDATA[Search Results]]></string>
+    <string name="_bx_ads_page_block_title_search_form_cmts"><![CDATA[Comments Search Form]]></string>
+    <string name="_bx_ads_page_block_title_search_results_cmts"><![CDATA[Comments Search Results]]></string>
+    <string name="_bx_ads_page_block_title_popular_entries"><![CDATA[Popular Ads]]></string>
+    <string name="_bx_ads_page_block_title_popular_entries_view_extended"><![CDATA[Popular Ads (Extended View)]]></string>
+    <string name="_bx_ads_page_block_title_popular_entries_view_full"><![CDATA[Popular Ads (Full View)]]></string>
+    <string name="_bx_ads_page_block_title_updated_entries"><![CDATA[Updated Ads]]></string>
+    <string name="_bx_ads_page_block_title_favorites_entries"><![CDATA["{title}" List]]></string>
+    <string name="_bx_ads_page_block_title_favorites_entries_info"><![CDATA[Info]]></string>
+    <string name="_bx_ads_page_title_sys_view_entry_comments"><![CDATA[Ad Comments]]></string>
+    <string name="_bx_ads_page_title_view_entry_comments"><![CDATA[Comments to {title}]]></string>
+    <string name="_bx_ads_page_title_sys_view_entry_promotion"><![CDATA[Ad Promotion]]></string>
+    <string name="_bx_ads_page_title_view_entry_promotion"><![CDATA[Promotion for {title}]]></string>
+    <string name="_bx_ads_page_title_sys_entries_of_author"><![CDATA[Ads of author]]></string>
+    <string name="_bx_ads_page_title_entries_of_author"><![CDATA[Ads by {display_name}]]></string>
+    <string name="_bx_ads_page_title_sys_entries_in_context"><![CDATA[Ads in context]]></string>
+    <string name="_bx_ads_page_title_entries_in_context"><![CDATA[Ads in {display_name}]]></string>
+    <string name="_bx_ads_page_block_title_favorites_of_author"><![CDATA[Favorites by <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_ads_page_block_title_sys_favorites_of_author"><![CDATA[Favorites of author]]></string>
+    <string name="_bx_ads_page_block_title_entries_of_author"><![CDATA[Ads by <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_ads_page_block_title_sys_entries_of_author"><![CDATA[Ads of author]]></string>
+    <string name="_bx_ads_page_block_title_entries_in_context"><![CDATA[Ads in <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_ads_page_block_title_sys_entries_in_context"><![CDATA[Ads in context]]></string>
+    <string name="_bx_ads_page_block_title_entries_actions"><![CDATA[Ads actions]]></string>
+    <string name="_bx_ads_page_block_title_browse_cmts"><![CDATA[Ads' Comments]]></string>
+    <string name="_bx_ads_page_title_sys_manage"><![CDATA[Manage Own]]></string>
+    <string name="_bx_ads_page_title_sys_manage_administration"><![CDATA[Manage All]]></string>
+    <string name="_bx_ads_page_title_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_ads_page_block_title_system_manage"><![CDATA[Manage Own]]></string>
+    <string name="_bx_ads_page_block_title_system_manage_administration"><![CDATA[Manage All]]></string>
+    <string name="_bx_ads_page_block_title_manage"><![CDATA[Manage]]></string>    
+    <string name="_bx_ads_page_block_title_popular_keywords"><![CDATA[Popular Hashtags]]></string>
+    <string name="_bx_ads_page_block_title_categories"><![CDATA[Ads Categories]]></string>
+    <string name="_bx_ads_page_block_title_entry_info"><![CDATA[Info]]></string>
+    <string name="_bx_ads_page_block_title_sys_my_entries"><![CDATA[User Ads]]></string>
+    <string name="_bx_ads_page_block_title_my_entries"><![CDATA[{display_name} Ads]]></string>
+    <string name="_bx_ads_page_block_title_sys_recent_entries_view_showcase"><![CDATA[Latest Ads (Showcase View)]]></string>
+    <string name="_bx_ads_page_block_title_recent_entries_view_showcase"><![CDATA[Latest Ads]]></string>
+    <string name="_bx_ads_page_block_title_sys_popular_entries_view_showcase"><![CDATA[Popular Ads (Showcase View)]]></string>
+    <string name="_bx_ads_page_block_title_popular_entries_view_showcase"><![CDATA[Popular Ads]]></string>
+    <string name="_bx_ads_page_block_title_sys_featured_entries_view_showcase"><![CDATA[Featured Ads (Showcase View)]]></string>
+    <string name="_bx_ads_page_block_title_featured_entries_view_showcase"><![CDATA[Featured Ads]]></string>
+    <string name="_bx_ads_page_block_title_entry_offer_accepted"><![CDATA[Offer]]></string>
+    <string name="_bx_ads_page_block_title_sys_entry_context"><![CDATA[Posted in]]></string>
+    <string name="_bx_ads_page_block_title_entry_context"><![CDATA[Posted in]]></string>
+    <string name="_bx_ads_page_title_sys_entries_categories"><![CDATA[Ads Categories]]></string>
+    <string name="_bx_ads_page_title_entries_categories"><![CDATA[Ads Categories]]></string>
+    <string name="_bx_ads_page_block_title_entries_by_category"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_page_block_title_entries_by_category_mask"><![CDATA[Ads in: {0}]]></string>
+    <string name="_bx_ads_page_title_sys_licenses_administration"><![CDATA[Licenses]]></string>
+    <string name="_bx_ads_page_title_licenses_administration"><![CDATA[Licenses]]></string>
+    <string name="_bx_ads_page_block_title_licenses_administration"><![CDATA[Licenses]]></string>
+    <string name="_bx_ads_page_title_sys_licenses"><![CDATA[Licenses]]></string>
+    <string name="_bx_ads_page_title_licenses"><![CDATA[Licenses]]></string>
+    <string name="_bx_ads_page_block_title_licenses"><![CDATA[Licenses]]></string>
+    <string name="_bx_ads_page_block_title_licenses_note"><![CDATA[Note]]></string>
+    <string name="_bx_ads_page_block_content_licenses_note"><![CDATA[The licenses are for reference only.]]></string>
+    <string name="_bx_ads_page_title_sys_offers"><![CDATA[Offers]]></string>
+    <string name="_bx_ads_page_title_offers"><![CDATA[Offers]]></string>
+    <string name="_bx_ads_page_block_title_offers"><![CDATA[Offers]]></string>
+    <string name="_bx_ads_page_title_sys_offers_all"><![CDATA[All Offers]]></string>
+    <string name="_bx_ads_page_title_offers_all"><![CDATA[All Offers]]></string>
+    <string name="_bx_ads_page_block_title_offers_all"><![CDATA[All Offers]]></string>
+    <string name="_bx_ads_page_title_sys_sources"><![CDATA[Sources]]></string>
+    <string name="_bx_ads_page_title_sources"><![CDATA[Sources]]></string>
+    <string name="_bx_ads_page_block_title_system_sources_details"><![CDATA[Sources Details]]></string>
+    <string name="_bx_ads_page_block_title_sources_details"><![CDATA[Sources Details]]></string>
+    
+
+    <string name="_bx_ads_menu_title_entry_attachments"><![CDATA[Form Attachments]]></string>
+    <string name="_bx_ads_menu_set_title_entry_attachments"><![CDATA[Form Attachments]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_photo_simple"><![CDATA[Add Photo (simple)]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_photo_simple"><![CDATA[Add Photo]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_photo_html5"><![CDATA[Add Photo (html5)]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_photo_html5"><![CDATA[Add Photos]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_video_simple"><![CDATA[Add Video (simple)]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_video_simple"><![CDATA[Add Video]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_video_html5"><![CDATA[Add Video (html5)]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_video_html5"><![CDATA[Add Videos]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_video_record"><![CDATA[Record Video]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_video_record"><![CDATA[Record Video]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_file_simple"><![CDATA[Add File (simple)]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_file_simple"><![CDATA[Add File]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_file_html5"><![CDATA[Add File (html5)]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_file_html5"><![CDATA[Add Files]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_poll"><![CDATA[Add Poll]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_poll"><![CDATA[Add Poll]]></string>
+    <string name="_bx_ads_menu_item_title_system_cpa_link"><![CDATA[Add Link]]></string>
+    <string name="_bx_ads_menu_item_title_cpa_link"><![CDATA[Add Link]]></string>
+
+    <string name="_bx_ads_menu_set_title_view_entry"><![CDATA[Ad Actions]]></string>
+    <string name="_bx_ads_menu_title_view_entry"><![CDATA[Ad Actions]]></string>
+    <string name="_bx_ads_menu_title_submenu"><![CDATA[Ads Submenu]]></string>
+    <string name="_bx_ads_menu_set_title_submenu"><![CDATA[Ads Submenu]]></string>
+    <string name="_bx_ads_menu_item_title_system_create_entry"><![CDATA[Ad]]></string>
+    <string name="_bx_ads_menu_item_title_create_entry"><![CDATA[Ad]]></string>
+    <string name="_bx_ads_menu_item_title_system_interested_entry"><![CDATA[Interested]]></string>
+    <string name="_bx_ads_menu_item_title_interested_entry"><![CDATA[I'm interested]]></string>
+    <string name="_bx_ads_menu_item_title_system_add_to_cart"><![CDATA[Add to Cart]]></string>
+    <string name="_bx_ads_menu_item_title_add_to_cart"><![CDATA[Buy for {0}{1}]]></string>
+    <string name="_bx_ads_menu_item_title_system_make_offer"><![CDATA[Make offer]]></string>
+    <string name="_bx_ads_menu_item_title_make_offer"><![CDATA[Make offer]]></string>
+    <string name="_bx_ads_menu_item_title_system_view_offers"><![CDATA[Оffers]]></string>
+    <string name="_bx_ads_menu_item_title_view_offers"><![CDATA[Оffers]]></string>
+    <string name="_bx_ads_menu_item_title_system_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_ads_menu_item_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_ads_menu_item_title_system_edit_entry_budget"><![CDATA[Edit budget]]></string>
+    <string name="_bx_ads_menu_item_title_edit_entry_budget"><![CDATA[Edit budget]]></string>
+    <string name="_bx_ads_menu_item_title_system_view_promotion"><![CDATA[Promotion]]></string>
+    <string name="_bx_ads_menu_item_title_view_promotion"><![CDATA[Promotion]]></string>
+    <string name="_bx_ads_menu_item_title_system_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_ads_menu_item_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_ads_menu_item_title_system_mark_shipped"><![CDATA[Mark as shipped]]></string>
+    <string name="_bx_ads_menu_item_title_mark_shipped"><![CDATA[Mark as shipped]]></string>
+    <string name="_bx_ads_menu_item_title_system_mark_received"><![CDATA[Mark as received]]></string>
+    <string name="_bx_ads_menu_item_title_mark_received"><![CDATA[Mark as received]]></string>
+    <string name="_bx_ads_menu_item_title_system_review_entry"><![CDATA[Review]]></string>
+    <string name="_bx_ads_menu_item_title_system_view_entry"><![CDATA[View Ad]]></string>
+    <string name="_bx_ads_menu_item_title_system_entries_home"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_item_title_entries_home"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_item_title_system_entries_public"><![CDATA[Latest]]></string>
+    <string name="_bx_ads_menu_item_title_entries_public"><![CDATA[Latest]]></string>
+    <string name="_bx_ads_menu_item_title_system_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_ads_menu_item_title_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_ads_menu_item_title_system_entries_popular"><![CDATA[Popular]]></string>
+    <string name="_bx_ads_menu_item_title_entries_popular"><![CDATA[Popular]]></string>
+    <string name="_bx_ads_menu_item_title_system_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_ads_menu_item_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_ads_menu_title_view_entry_submenu"><![CDATA[View Ad Submenu]]></string>
+    <string name="_bx_ads_menu_set_title_view_entry_submenu"><![CDATA[View Ad Submenu]]></string>
+    <string name="_bx_ads_menu_item_title_view_entry_submenu_entry"><![CDATA[Ad]]></string>
+    <string name="_bx_ads_menu_item_title_view_entry_submenu_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_ads_menu_item_title_view_entry_submenu_promotion"><![CDATA[Promotion]]></string>
+    <string name="_bx_ads_menu_item_title_system_view_entry_comments"><![CDATA[Ad Comments]]></string>
+    <string name="_bx_ads_menu_item_title_system_view_entry_promotion"><![CDATA[Ad Promotion]]></string>
+    <string name="_bx_ads_menu_item_title_system_view_entries_author"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_item_title_view_entries_author"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_item_title_system_view_entries_in_context"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_item_title_view_entries_in_context"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_title_entries_my"><![CDATA[My Ads Action Menu]]></string>
+    <string name="_bx_ads_menu_set_title_entries_my"><![CDATA[My Ads Menu]]></string>
+    <string name="_bx_ads_menu_item_title_system_manage_my_posts"><![CDATA[My ads]]></string>
+    <string name="_bx_ads_menu_item_title_manage_my_posts"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_set_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_ads_menu_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_ads_menu_item_title_system_manage_account"><![CDATA[Manage account]]></string>
+    <string name="_bx_ads_menu_item_title_manage_account"><![CDATA[Manage account]]></string>
+    <string name="_bx_ads_menu_item_title_system_admt_ads"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_item_title_admt_ads"><![CDATA[Ads]]></string>
+    <string name="_bx_ads_menu_item_title_manage_my"><![CDATA[My Ads]]></string>
+    <string name="_bx_ads_menu_item_title_manage_all"><![CDATA[All Ads]]></string>
+    <string name="_bx_ads_menu_item_title_categories"><![CDATA[Categories]]></string>
+    <string name="_bx_ads_menu_item_title_system_entries_categories"><![CDATA[Categories]]></string>
+    <string name="_bx_ads_menu_item_title_entries_categories"><![CDATA[Categories]]></string>
+    <string name="_bx_ads_menu_item_title_system_sm_price"><![CDATA[Price]]></string>
+    <string name="_bx_ads_menu_item_title_sm_price"><![CDATA[Price]]></string>
+    <string name="_bx_ads_menu_title_licenses_submenu"><![CDATA[Licenses Submenu]]></string>
+    <string name="_bx_ads_menu_set_title_licenses_submenu"><![CDATA[Licenses Submenu]]></string>
+    <string name="_bx_ads_menu_item_title_system_ads_licenses_administration"><![CDATA[All Licenses]]></string>
+    <string name="_bx_ads_menu_item_title_ads_licenses_administration"><![CDATA[All Licenses]]></string>
+    <string name="_bx_ads_menu_item_title_system_ads_licenses"><![CDATA[My Licenses]]></string>
+    <string name="_bx_ads_menu_item_title_ads_licenses"><![CDATA[My Licenses]]></string>
+    <string name="_bx_ads_menu_item_title_system_licenses"><![CDATA[Ads Licenses]]></string>
+    <string name="_bx_ads_menu_item_title_licenses"><![CDATA[Ads Licenses]]></string>
+    <string name="_bx_ads_menu_item_title_system_offers_all"><![CDATA[Ads Offers]]></string>
+    <string name="_bx_ads_menu_item_title_offers_all"><![CDATA[Offers]]></string>
+    <string name="_bx_ads_menu_item_title_system_sources"><![CDATA[Sources]]></string>
+    <string name="_bx_ads_menu_item_title_sources"><![CDATA[Sources]]></string>
+    
+
+    <string name="_bx_ads_grid_column_title_icon"><![CDATA[Icon]]></string>
+    <string name="_bx_ads_grid_column_title_title"><![CDATA[Title]]></string>
+    <string name="_bx_ads_grid_column_title_subcategories"><![CDATA[Subcategories]]></string>
+    <string name="_bx_ads_grid_column_value_subcategories"><![CDATA[{0} subcategories]]></string>
+    <string name="_bx_ads_grid_column_info_subcategories"><![CDATA[View subcategories]]></string>
+    <string name="_bx_ads_grid_action_title_add"><![CDATA[Add New]]></string>
+    <string name="_bx_ads_grid_action_title_back"><![CDATA[Back]]></string>
+    <string name="_bx_ads_grid_action_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_ads_grid_popup_title_add"><![CDATA[Add New Category]]></string>
+    <string name="_bx_ads_grid_popup_title_edit"><![CDATA[Edit '{0}' Category]]></string>
+    <string name="_bx_ads_grid_action_err_add_category"><![CDATA[Cannot add a category.]]></string>
+    <string name="_bx_ads_grid_action_err_edit_category"><![CDATA[Cannot edit the category.]]></string>
+
+    <string name="_bx_ads_grid_filter_item_title_adm_select_one_filter1"><![CDATA[All Statuses]]></string>
+    <string name="_bx_ads_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string>
+    <string name="_bx_ads_grid_filter_item_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_ads_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string>
+    <string name="_bx_ads_grid_filter_item_title_adm_pending"><![CDATA[Pending]]></string>
+    <string name="_bx_ads_grid_filter_item_title_adm_unpaid"><![CDATA[Unpaid]]></string>
+    <string name="_bx_ads_grid_column_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_ads_grid_column_title_adm_title"><![CDATA[Title]]></string>
+    <string name="_bx_ads_grid_column_title_adm_added"><![CDATA[Added]]></string>
+    <string name="_bx_ads_grid_column_title_adm_author"><![CDATA[Author]]></string>
+    <string name="_bx_ads_grid_action_title_adm_promotion"><![CDATA[Promotion]]></string>
+    <string name="_bx_ads_grid_action_title_adm_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_ads_grid_action_title_adm_edit_budget"><![CDATA[Edit budget]]></string>
+    <string name="_bx_ads_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_ads_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
+    <string name="_bx_ads_grid_action_title_adm_audit_content"><![CDATA[Audit]]></string>
+    <string name="_bx_ads_grid_column_title_adm_status_admin"><![CDATA[Moderation]]></string>
+    <string name="_bx_ads_grid_action_err_delete"><![CDATA[Cannot delete the selected ad(s).]]></string>
+    <string name="_bx_ads_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+
+    <string name="_bx_ads_grid_column_title_lcs_profile_id"><![CDATA[Owner]]></string>
+    <string name="_bx_ads_grid_column_title_lcs_transaction"><![CDATA[Order]]></string>
+    <string name="_bx_ads_grid_column_title_lcs_entry"><![CDATA[Ad]]></string>
+    <string name="_bx_ads_grid_column_title_lcs_count"><![CDATA[Qt.]]></string>
+    <string name="_bx_ads_grid_column_title_lcs_license"><![CDATA[License]]></string>
+    <string name="_bx_ads_grid_column_title_lcs_added"><![CDATA[Date]]></string>
+
+    <string name="_bx_ads_grid_column_title_ofr_author_id"><![CDATA[Offerer]]></string>
+    <string name="_bx_ads_grid_column_title_ofr_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_ads_grid_column_title_ofr_quantity"><![CDATA[Quantity]]></string>
+    <string name="_bx_ads_grid_column_title_ofr_message"><![CDATA[Message]]></string>
+    <string name="_bx_ads_grid_column_title_ofr_added"><![CDATA[Added]]></string>
+    <string name="_bx_ads_grid_column_title_ofr_status"><![CDATA[Status]]></string>
+    <string name="_bx_ads_grid_action_title_ofr_accept"><![CDATA[Accept the offer]]></string>
+    <string name="_bx_ads_grid_action_title_ofr_decline"><![CDATA[Decline the offer]]></string>
+
+    <string name="_bx_ads_grid_column_title_ofrs_content_id"><![CDATA[Ad]]></string>
+    <string name="_bx_ads_grid_column_title_ofrs_offers_awating"><![CDATA[Awating]]></string>
+    <string name="_bx_ads_grid_column_title_ofrs_offers_total"><![CDATA[Total]]></string>
+    <string name="_bx_ads_grid_action_title_ofr_view"><![CDATA[View offers]]></string>
+
+    <string name="_bx_ads_chart_growth"><![CDATA[Ads: Growth]]></string>
+    <string name="_bx_ads_chart_growth_speed"><![CDATA[Ads: Growth Speed]]></string>
+    <string name="_bx_ads_chart_promotion_growth"><![CDATA[Ad Promotion: Growth]]></string>
+    <string name="_bx_ads_chart_promotion_growth_speed"><![CDATA[Ad Promotion: Growth Speed]]></string>
+    <string name="_bx_ads_chart_label_impressions"><![CDATA[Impressions]]></string>
+    <string name="_bx_ads_chart_label_clicks"><![CDATA[Clicks]]></string>
+    <string name="_bx_ads_chart_label_roi"><![CDATA[ROI]]></string>
+    <string name="_bx_ads_chart_label_roi_local"><![CDATA[Local income]]></string>
+    <string name="_bx_ads_chart_label_roi_source"><![CDATA[External income]]></string>
+    <string name="_bx_ads_chart_label_roi_investment"><![CDATA[Investment]]></string>
+
+    <string name="_bx_ads_search_extended"><![CDATA[Ads Search]]></string>
+    <string name="_bx_ads_search_extended_cmts"><![CDATA[Ads Comments Search]]></string>
+    <string name="_bx_ads_search_extended_reviews"><![CDATA[Ads Reviews Search]]></string>
+
+    <!-- Ctegories -->
+    <string name="_bx_ads_cat_type_price"><![CDATA[With price]]></string>
+    <string name="_bx_ads_cat_type_price_year"><![CDATA[With price and year]]></string>
+
+    <string name="_bx_ads_cat_title_job"><![CDATA[Jobs]]></string>
+    <string name="_bx_ads_cat_title_accounting_finance"><![CDATA[Accounting / finance jobs]]></string>
+    <string name="_bx_ads_cat_title_education_nonprofit"><![CDATA[Education / nonprofit jobs]]></string>
+    <string name="_bx_ads_cat_title_government_legal"><![CDATA[Government / legal jobs]]></string>
+    <string name="_bx_ads_cat_title_programming_web_design"><![CDATA[Programming / web design jobs]]></string>
+
+    <string name="_bx_ads_cat_title_music"><![CDATA[Music Exchange]]></string>
+    <string name="_bx_ads_cat_title_instrument_sale"><![CDATA[Music instruments for sale]]></string>
+    <string name="_bx_ads_cat_title_instrument_wanted"><![CDATA[Music instruments wanted]]></string>
+    
+    <string name="_bx_ads_cat_title_housing"><![CDATA[Housing & Rentals]]></string>
+    <string name="_bx_ads_cat_title_apartments_housing"><![CDATA[Apartments / housing]]></string>
+    <string name="_bx_ads_cat_title_office_commercial"><![CDATA[Office / commercial]]></string>
+    <string name="_bx_ads_cat_title_real_estate_sale"><![CDATA[Real estate for sale]]></string>
+    <string name="_bx_ads_cat_title_roommate"><![CDATA[Roommates]]></string>
+    <string name="_bx_ads_cat_title_temporary_rental"><![CDATA[Temporary rental]]></string>
+    
+    <string name="_bx_ads_cat_title_service"><![CDATA[Services]]></string>
+    <string name="_bx_ads_cat_title_automotive"><![CDATA[Automotive services]]></string>
+    <string name="_bx_ads_cat_title_educational"><![CDATA[Educational services]]></string>
+    <string name="_bx_ads_cat_title_financial"><![CDATA[Financial services]]></string>
+    <string name="_bx_ads_cat_title_labor_move"><![CDATA[Labor / move services]]></string>
+    <string name="_bx_ads_cat_title_legal"><![CDATA[Legal services]]></string>
+
+    <string name="_bx_ads_cat_title_casting"><![CDATA[Casting Calls]]></string>
+    <string name="_bx_ads_cat_title_acting"><![CDATA[Acting castings]]></string>
+    <string name="_bx_ads_cat_title_dance"><![CDATA[Dance castings]]></string>
+    <string name="_bx_ads_cat_title_modeling"><![CDATA[Modeling castings]]></string>
+    <string name="_bx_ads_cat_title_musician"><![CDATA[Musicians castings]]></string>
+    <string name="_bx_ads_cat_title_reality_show"><![CDATA[Reality shows castings]]></string>
+
+    <string name="_bx_ads_cat_title_personal"><![CDATA[Personals]]></string>
+    <string name="_bx_ads_cat_title_men_women"><![CDATA[Men seeking women]]></string>
+    <string name="_bx_ads_cat_title_women_men"><![CDATA[Women seeking men]]></string>
+    <string name="_bx_ads_cat_title_missed_connection"><![CDATA[Missed connections]]></string>
+
+    <string name="_bx_ads_cat_title_sale"><![CDATA[For Sale]]></string>
+    <string name="_bx_ads_cat_title_barter"><![CDATA[Barter]]></string>
+    <string name="_bx_ads_cat_title_clothing"><![CDATA[Sale clothing]]></string>
+    <string name="_bx_ads_cat_title_collectible"><![CDATA[Sale collectibles]]></string>
+
+    <string name="_bx_ads_cat_title_sale_car"><![CDATA[Cars For Sale]]></string>
+    <string name="_bx_ads_cat_title_auto_part"><![CDATA[Sale auto parts]]></string>
+    <string name="_bx_ads_cat_title_auto_truck"><![CDATA[Sale autos / trucks]]></string>
+    <string name="_bx_ads_cat_title_motorcycle"><![CDATA[Sale motorcycles]]></string>
+
+    <string name="_bx_ads_et_txt_name_interested"><![CDATA[I'm interested.]]></string>
+    <string name="_bx_ads_et_txt_subject_interested"><![CDATA[{viewer_name} is interested in your ad.]]></string>
+    <string name="_bx_ads_et_txt_body_interested"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p><b><a href="{viewer_url}">{viewer_name}</a></b> is interested in your ad <b><a href="{ad_url}">{ad_name}</a></b>.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_ads_et_txt_name_purchased"><![CDATA[Ad was purchased.]]></string>
+    <string name="_bx_ads_et_txt_subject_purchased"><![CDATA[You've purchased {entry_name} ad.]]></string>
+    <string name="_bx_ads_et_txt_body_purchased"><![CDATA[{email_header}
+<p>Hello, {client_name}.</p>
+<p>You've purchased <a href="{entry_url}">{entry_name}</a> ad ({count} item(s)) from <a href="{vendor_url}">{vendor_name}</a>.<br />Your license is <b>{license}</b>.</p>
+<p>{notes}</p>
+{email_footer}]]></string>
+
+    <string name="_bx_ads_et_txt_name_shipped"><![CDATA[Ad was shipped.]]></string>
+    <string name="_bx_ads_et_txt_subject_shipped"><![CDATA[Ad {entry_name} was shipped.]]></string>
+    <string name="_bx_ads_et_txt_body_shipped"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p>The <a href="{entry_url}">{entry_name}</a> ad was shipped by <a href="{vendor_url}">{vendor_name}</a>.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_ads_et_txt_name_received"><![CDATA[Ad received.]]></string>
+    <string name="_bx_ads_et_txt_subject_received"><![CDATA[Ad {entry_name} was received.]]></string>
+    <string name="_bx_ads_et_txt_body_received"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p>The <a href="{entry_url}">{entry_name}</a> ad was received by <a href="{client_url}">{client_name}</a>.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_ads_et_txt_name_offer_added"><![CDATA[New offer was added.]]></string>
+    <string name="_bx_ads_et_txt_subject_offer_added"><![CDATA[{viewer_name} added an offer for your ad.]]></string>
+    <string name="_bx_ads_et_txt_body_offer_added"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p><b><a href="{viewer_url}">{viewer_name}</a></b> added an offer for your ad <b><a href="{entry_url}">{entry_name}</a></b>.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_ads_et_txt_name_offer_accepted"><![CDATA[The offer was accepted.]]></string>
+    <string name="_bx_ads_et_txt_subject_offer_accepted"><![CDATA[Your offer for {entry_name} ad was accepted.]]></string>
+    <string name="_bx_ads_et_txt_body_offer_accepted"><![CDATA[{email_header}
+<p>Hello, {offerer_name}.</p>
+<p>Your offer for <b><a href="{entry_url}">{entry_name}</a></b> ad was accepted.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_ads_et_txt_name_offer_declined"><![CDATA[The offer was declined.]]></string>
+    <string name="_bx_ads_et_txt_subject_offer_declined"><![CDATA[Your offer for {entry_name} ad was declined.]]></string>
+    <string name="_bx_ads_et_txt_body_offer_declined"><![CDATA[{email_header}
+<p>Hello, {offerer_name}.</p>
+<p>Your offer for <b><a href="{entry_url}">{entry_name}</a></b> ad was declined.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_ads_et_txt_name_offer_canceled"><![CDATA[The offer was canceled.]]></string>
+    <string name="_bx_ads_et_txt_subject_offer_canceled"><![CDATA[{offerer_name} canceled the offer for {entry_name} ad.]]></string>
+    <string name="_bx_ads_et_txt_body_offer_canceled"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p><a href="{offerer_url}">{offerer_name}</a> canceled the offer for <b><a href="{entry_url}">{entry_name}</a></b> ad.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_ads_txt_all_entries_in"><![CDATA[<a href="{0}">All Ads in {1} ({2})</a>]]></string>
+    <string name="_bx_ads_txt_all_entries_by"><![CDATA[{0} ads]]></string>
+    <string name="_bx_ads_txt_sample_single"><![CDATA[ad]]></string>
+    <string name="_bx_ads_txt_sample_single_with_article"><![CDATA[an ad]]></string>
+    <string name="_bx_ads_txt_sample_comment_single"><![CDATA[comment to ad]]></string>
+    <string name="_bx_ads_txt_sample_vote_single"><![CDATA[vote to ad]]></string>
+    <string name="_bx_ads_txt_sample_reaction_single"><![CDATA['{0}' reaction to ad]]></string>
+    <string name="_bx_ads_txt_sample_score_up_single"><![CDATA[score up vote to ad]]></string>
+    <string name="_bx_ads_txt_sample_score_down_single"><![CDATA[score down vote to ad]]></string>
+    <string name="_bx_ads_txt_sample_interest_single"><![CDATA[interested in ad]]></string>
+    <string name="_bx_ads_txt_status_offer"><![CDATA[Reserved]]></string>
+    <string name="_bx_ads_txt_status_sold"><![CDATA[Sold]]></string>
+    <string name="_bx_ads_txt_display_title_add"><![CDATA[{0}: Add]]></string>
+    <string name="_bx_ads_txt_display_title_edit"><![CDATA[{0}: Edit]]></string>
+    <string name="_bx_ads_txt_display_title_view"><![CDATA[{0}: View]]></string>
+    <string name="_bx_ads_txt_processing_awaiting"><![CDATA[Ad will be published as soon as video is converted]]></string>
+    <string name="_bx_ads_txt_processing_failed"><![CDATA[Ad isn't published because video conversion failed, edit video to fix the issue.]]></string>
+    <string name="_bx_ads_txt_free"><![CDATA[Free]]></string>
+    <string name="_bx_ads_txt_cd_ct_product"><![CDATA[Payment for product]]></string>
+    <string name="_bx_ads_txt_cd_ct_promotion"><![CDATA[Payment for promotion]]></string>
+    <string name="_bx_ads_txt_purchased_note"><![CDATA[Contact <a href="{0}">{1}</a> if you have any questions or need support.]]></string>
+    <string name="_bx_ads_txt_offer_status_awaiting"><![CDATA[Awaiting]]></string>
+    <string name="_bx_ads_txt_offer_status_accepted"><![CDATA[Accepted]]></string>
+    <string name="_bx_ads_txt_offer_status_declined"><![CDATA[Declined]]></string>
+    <string name="_bx_ads_txt_offer_status_canceled"><![CDATA[Canceled]]></string>
+    <string name="_bx_ads_txt_offer_status_paid"><![CDATA[Paid]]></string>
+    <string name="_bx_ads_txt_your_offer"><![CDATA[Your offer]]></string>
+    <string name="_bx_ads_txt_accepted"><![CDATA[was accepted]]></string>
+    <string name="_bx_ads_txt_n_items"><![CDATA[{0} item(s)]]></string>
+    <string name="_bx_ads_txt_pay"><![CDATA[Pay]]></string>
+    <string name="_bx_ads_txt_cancel"><![CDATA[Cancel]]></string>
+
+    <string name="_bx_ads_txt_msg_status_hidden"><![CDATA[This ad was hidden.]]></string>
+    <string name="_bx_ads_txt_msg_status_pending"><![CDATA[This ad is pending approval.]]></string>
+    <string name="_bx_ads_txt_msg_auction_offer"><![CDATA[This ad is under offer.]]></string>
+    <string name="_bx_ads_txt_msg_auction_sold"><![CDATA[This ad was sold.]]></string>
+    <string name="_bx_ads_txt_msg_author_notified"><![CDATA[Advertiser was notified, that you're interested in his ad. Please wait.]]></string>
+    <string name="_bx_ads_txt_msg_offer_added"><![CDATA[Your offer was added. Advertiser will be notified about it. Please wait.]]></string>
+    <string name="_bx_ads_txt_msg_offer_accepted"><![CDATA[The offer was accepted.]]></string>
+    <string name="_bx_ads_txt_msg_offer_declined"><![CDATA[The offer was declined.]]></string>
+    <string name="_bx_ads_txt_msg_offer_canceled"><![CDATA[The offer was canceled.]]></string>
+    <string name="_bx_ads_txt_msg_promotion_unpaid"><![CDATA[The promotion campaign for the ad still hasn't been paid]]></string>
+    <string name="_bx_ads_txt_err_offer_duplicate"><![CDATA[You already made an offer. Please wait for the results.]]></string>
+    <string name="_bx_ads_txt_err_offer_wrong_quantity"><![CDATA[Offered quantity is out of stock. Only {0} items are available.]]></string>
+    <string name="_bx_ads_txt_err_offer_accepted"><![CDATA[Your offer was already accepted.]]></string>
+    <string name="_bx_ads_txt_err_your_own"><![CDATA[You cannot do the action because it's your own ad.]]></string>
+    <string name="_bx_ads_txt_err_duplicate"><![CDATA[You already performed the action.]]></string>
+    <string name="_bx_ads_txt_err_cannot_perform_action"><![CDATA[Cannot perform action. Please report.]]></string>
+
+    <string name="_bx_ads_txt_poll_view_answers"><![CDATA[poll]]></string>
+    <string name="_bx_ads_txt_poll_view_results"><![CDATA[results]]></string>
+    <string name="_bx_ads_txt_poll_answer_vote_do_by"><![CDATA[Voted for]]></string>
+    <string name="_bx_ads_txt_poll_answer_vote_counter"><![CDATA[{0} votes]]></string>
+    <string name="_bx_ads_txt_poll_answer_vote_percent"><![CDATA[({0}%)]]></string>
+
+    <string name="_bx_ads_alert_action_doInterest_personal"><![CDATA[Someone interested in your ad]]></string>
+    <string name="_bx_ads_alert_action_info_doInterest_personal"><![CDATA[A member has shown interest in your advertisement.]]></string>
+    <string name="_bx_ads_txt_ntfs_subobject_interested"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> {subentry_sample} <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+
+    <string name="_bx_ads_alert_action_license_register_personal"><![CDATA[Your ad was purchased]]></string>
+    <string name="_bx_ads_alert_action_info_license_register_personal"><![CDATA[A member purchased the item or service you advertised.]]></string>
+    <string name="_bx_ads_txt_ntfs_object_license_register"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> purchased your ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+
+    <string name="_bx_ads_alert_action_shipped_personal"><![CDATA[Ad was shipped]]></string>
+    <string name="_bx_ads_txt_ntfs_object_shipped"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> shipped the ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+
+    <string name="_bx_ads_alert_action_received_personal"><![CDATA[Ad was received]]></string>
+    <string name="_bx_ads_txt_ntfs_object_received"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> received the ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+
+    <string name="_bx_ads_alert_action_offer_added_personal"><![CDATA[Offer was added]]></string>
+    <string name="_bx_ads_txt_ntfs_offer_added"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> added an offer to your ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+
+    <string name="_bx_ads_alert_action_offer_accepted_personal"><![CDATA[Offer was accepted]]></string>
+    <string name="_bx_ads_txt_ntfs_offer_accepted"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> accepted your offer for the ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+
+    <string name="_bx_ads_alert_action_offer_declined_personal"><![CDATA[Offer was declined]]></string>
+    <string name="_bx_ads_txt_ntfs_offer_declined"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> declined your offer for the ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+
+    <string name="_bx_ads_alert_action_offer_canceled_personal"><![CDATA[Offer was canceled]]></string>
+    <string name="_bx_ads_txt_ntfs_offer_canceled"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> canceled the offer for your ad <b class="bx-ntfs-content-entry">{entry_caption}</b>]]></string>
+</resources>

+ 57 - 0
modules/boonex/ads/updates/14.0.4_14.0.5/source/install/sql/disable.sql

@@ -0,0 +1,57 @@
+
+-- SETTINGS
+SET @iTypeId = (SELECT `ID` FROM `sys_options_types` WHERE `name` = 'bx_ads' LIMIT 1);
+SET @iCategId = (SELECT `ID` FROM `sys_options_categories` WHERE `type_id` = @iTypeId LIMIT 1);
+DELETE FROM `sys_options` WHERE `category_id` = @iCategId;
+DELETE FROM `sys_options_categories` WHERE `type_id` = @iTypeId;
+DELETE FROM `sys_options_types` WHERE `id` = @iTypeId;
+
+-- PAGES
+DELETE FROM `sys_objects_page` WHERE `module` = 'bx_ads';
+DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_ads' OR `object` IN ('bx_ads_create_entry', 'bx_ads_edit_entry', 'bx_ads_edit_entry_budget', 'bx_ads_delete_entry', 'bx_ads_view_entry', 'bx_ads_view_entry_comments', 'bx_ads_home', 'bx_ads_popular', 'bx_ads_updated', 'bx_ads_categories', 'bx_ads_author', 'bx_ads_context', 'bx_ads_search', 'bx_ads_manage', 'bx_ads_administration', 'bx_ads_licenses', 'bx_ads_licenses_administration', 'bx_ads_offers', 'bx_ads_offers_all');
+
+-- MENU
+DELETE FROM `sys_objects_menu` WHERE `module` = 'bx_ads';
+DELETE FROM `sys_menu_sets` WHERE `module` = 'bx_ads';
+DELETE FROM `sys_menu_items` WHERE `module` = 'bx_ads' OR `set_name` IN ('bx_ads_entry_attachments', 'bx_ads_view', 'bx_ads_view_actions', 'bx_ads_submenu', 'bx_ads_view_submenu', 'bx_ads_snippet_meta', 'bx_ads_licenses_submenu', 'bx_ads_my', 'bx_ads_menu_manage_tools');
+
+-- PRIVACY 
+DELETE FROM `sys_objects_privacy` WHERE `object` IN ('bx_ads_allow_view_to', 'bx_ads_allow_view_favorite_list');
+
+-- ACL
+DELETE `sys_acl_actions`, `sys_acl_matrix` FROM `sys_acl_actions`, `sys_acl_matrix` WHERE `sys_acl_matrix`.`IDAction` = `sys_acl_actions`.`ID` AND `sys_acl_actions`.`Module` = 'bx_ads';
+DELETE FROM `sys_acl_actions` WHERE `Module` = 'bx_ads';
+
+-- SEARCH
+DELETE FROM `sys_objects_search` WHERE `ObjectName` IN ('bx_ads', 'bx_ads_cmts');
+
+-- METATAGS
+DELETE FROM `sys_objects_metatags` WHERE `object` = 'bx_ads';
+
+-- STATS
+DELETE FROM `sys_statistics` WHERE `module` = 'bx_ads';
+
+-- CHARTS
+DELETE FROM `sys_objects_chart` WHERE `object` LIKE 'bx_ads_%';
+
+-- GRIDS
+DELETE FROM `sys_objects_grid` WHERE `object` LIKE 'bx_ads_%';
+DELETE FROM `sys_grid_fields` WHERE `object` LIKE 'bx_ads_%';
+DELETE FROM `sys_grid_actions` WHERE `object` LIKE 'bx_ads_%';
+
+-- UPLOADERS
+DELETE FROM `sys_objects_uploader` WHERE `object` LIKE 'bx_ads_%';
+
+-- LIVE UPDATES
+DELETE FROM `sys_objects_live_updates` WHERE `name` = 'bx_ads';
+
+-- ALERTS
+SET @iHandler := (SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = 'bx_ads' LIMIT 1);
+DELETE FROM `sys_alerts` WHERE `handler_id` = @iHandler;
+DELETE FROM `sys_alerts_handlers` WHERE `id` = @iHandler;
+
+-- CRON
+DELETE FROM `sys_cron_jobs` WHERE `name` LIKE 'bx_ads_%';
+
+-- EMAIL TEMPLATES
+DELETE FROM `sys_email_templates` WHERE `Module` = 'bx_ads';

+ 19 - 0
modules/boonex/ads/updates/14.0.4_14.0.5/source/template/category_form.html

@@ -0,0 +1,19 @@
+<div class="bx-ads-category-form">
+    <div class="bx-ads-category-form-cnt">
+        __form__
+        <script language="javascript">
+            $(document).ready(function () {
+                $("#__form_id__").ajaxForm({ 
+                    dataType: "json",
+                    beforeSubmit: function (formData, jqForm, options) {
+                        bx_loading($("#__form_id__"), true);
+                    },
+                    success: function (data) {
+                        $(".bx-popup-applied:visible").dolPopupHide();
+                        glGrids.__object__.processJson(data, '__action__');
+                    }
+                });
+            });
+        </script>
+    </div>
+</div>

+ 51 - 0
modules/boonex/albums/updates/14.0.4_14.0.5/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Albums',
+    'version_from' => '14.0.4',
+    'version_to' => '14.0.5',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/albums/updates/update_14.0.4_14.0.5/',
+    'home_uri' => 'albums_update_1404_1405',
+
+    'module_dir' => 'boonex/albums/',
+    'module_uri' => 'albums',
+
+    'db_prefix' => 'bx_albums_',
+    'class_prefix' => 'BxAlbums',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Albums',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/albums/updates/14.0.4_14.0.5/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxAlbumsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 234 - 0
modules/boonex/albums/updates/14.0.4_14.0.5/source/classes/BxAlbumsConfig.php

@@ -0,0 +1,234 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Albums Albums
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxAlbumsConfig extends BxBaseModTextConfig
+{
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $aMenuItems2Methods = array (
+            'approve' => 'checkAllowedApprove',
+            'add-images-to-album' => 'checkAllowedEdit',
+            'edit-album' => 'checkAllowedEdit',
+            'edit-image' => 'checkAllowedEdit',
+            'move-image' => 'checkAllowedEdit',
+            'delete-album' => 'checkAllowedDelete',
+            'delete-image' => 'checkAllowedDelete',
+        );
+
+        $this->CNF = array (
+
+            // module icon
+            'ICON' => 'far image col-blue1',
+
+            // database tables
+            'TABLE_ENTRIES' => $aModule['db_prefix'] . 'albums',
+            'TABLE_ENTRIES_FULLTEXT' => 'title_text',
+            'TABLE_FILES2ENTRIES' => $aModule['db_prefix'] . 'files2albums',
+            'TABLE_FILES' => $aModule['db_prefix'] . 'files',
+
+            // database fields
+            'FIELD_ID' => 'id',
+            'FIELD_AUTHOR' => 'author',
+            'FIELD_ADDED' => 'added',
+            'FIELD_CHANGED' => 'changed',
+            'FIELD_TITLE' => 'title',
+            'FIELD_TEXT' => 'text',
+            'FIELD_TEXT_ID' => 'album-desc',
+            'FIELD_ALLOW_VIEW_TO' => 'allow_view_to',
+            'FIELD_CF' => 'cf',
+            'FIELD_PHOTO' => 'pictures',
+            'FIELD_THUMB' => 'thumb',
+            'FIELD_VIEWS' => 'views',
+            'FIELD_COMMENTS' => 'comments',
+            'FIELD_STATUS' => 'status',
+            'FIELD_STATUS_ADMIN' => 'status_admin',
+            'FIELD_LOCATION' => 'location',
+            'FIELD_LOCATION_PREFIX' => 'location',
+            'FIELD_LABELS' => 'labels',
+            'FIELDS_WITH_KEYWORDS' => 'auto', // can be 'auto', array of fields or comma separated string of field names, works only when OBJECT_METATAGS is specified
+            'FIELDS_DELAYED_PROCESSING' => 'pictures', // can be array of fields or comma separated string of field names
+
+            // page URIs
+            'URI_VIEW_ENTRY' => 'view-album',
+            'URI_VIEW_MEDIA' => 'view-album-media',
+            'URI_AUTHOR_ENTRIES' => 'albums-author',
+            'URI_ENTRIES_BY_CONTEXT' => 'albums-context',
+            'URI_ADD_ENTRY' => 'create-album',
+            'URI_EDIT_ENTRY' => 'edit-album',
+            'URI_MANAGE_COMMON' => 'albums-manage',
+            'URI_FAVORITES_LIST' => 'albums-favorites',
+
+            'URL_HOME' => 'page.php?i=albums-home',
+            'URL_POPULAR' => 'page.php?i=albums-popular',
+            'URL_TOP' => 'page.php?i=albums-top',
+            'URL_UPDATED' => 'page.php?i=albums-home',
+            'URL_POPULAR_MEDIA' => 'page.php?i=albums-popular-media',
+            'URL_TOP_MEDIA' => 'page.php?i=albums-top-media',
+            'URL_RECENT_MEDIA' => 'page.php?i=albums-recent-media', // TODO: add page
+            'URL_MANAGE_COMMON' => 'page.php?i=albums-manage',
+            'URL_MANAGE_ADMINISTRATION' => 'page.php?i=albums-administration',
+
+            // some params
+            'PARAM_AUTO_APPROVE' => 'bx_albums_enable_auto_approve',
+            'PARAM_CHARS_SUMMARY' => 'bx_albums_summary_chars',
+            'PARAM_CHARS_SUMMARY_PLAIN' => 'bx_albums_plain_summary_chars',
+            'PARAM_NUM_RSS' => 'bx_albums_rss_num',
+            'PARAM_SEARCHABLE_FIELDS' => 'bx_albums_searchable_fields',
+            'PARAM_PER_PAGE_FOR_FAVORITES_LISTS' => 'bx_albums_per_page_for_favorites_lists',
+            'PARAM_ORDER_BY_GHOSTS' => true,
+
+            // objects
+            'OBJECT_STORAGE' => 'bx_albums_files',
+            'OBJECT_IMAGES_TRANSCODER_PREVIEW' => 'bx_albums_proxy_preview',
+            'OBJECT_IMAGES_TRANSCODER_GALLERY' => '',
+            'OBJECT_IMAGES_TRANSCODER_BIG' => 'bx_albums_big',
+            'OBJECT_VIDEOS_TRANSCODERS' => array(
+                'poster' => 'bx_albums_video_poster_big', 
+                'poster_preview' => 'bx_albums_video_poster_preview', 
+                'mp4' => 'bx_albums_video_mp4', 
+                'mp4_hd' => 'bx_albums_video_mp4_hd'
+            ),
+            'OBJECT_VIDEO_TRANSCODER_HEIGHT' => '480px',
+            'OBJECT_TRANSCODER_BROWSE' => 'bx_albums_proxy_browse',
+            'OBJECT_TRANSCODER_COVER' => 'bx_albums_proxy_cover',
+            'OBJECT_REPORTS' => 'bx_albums',
+            'OBJECT_REPORTS_MEDIA' => 'bx_albums_media',
+            'OBJECT_VIEWS' => 'bx_albums',
+            'OBJECT_VIEWS_MEDIA' => 'bx_albums_media',
+            'OBJECT_VOTES' => 'bx_albums',
+            'OBJECT_VOTES_MEDIA' => 'bx_albums_media',
+            'OBJECT_REACTIONS' => 'bx_albums_reactions',
+            'OBJECT_SCORES' => 'bx_albums',
+            'OBJECT_SCORES_MEDIA' => 'bx_albums_media',
+            'OBJECT_FAVORITES' => 'bx_albums',
+            'OBJECT_FAVORITES_MEDIA' => 'bx_albums_media',
+            'OBJECT_FEATURED' => 'bx_albums',
+            'OBJECT_FEATURED_MEDIA' => 'bx_albums_media',
+            'OBJECT_METATAGS' => 'bx_albums',
+            'OBJECT_METATAGS_MEDIA' => 'bx_albums_media',
+            'OBJECT_METATAGS_MEDIA_CAMERA' => 'bx_albums_media_camera',
+            'OBJECT_COMMENTS' => 'bx_albums',
+            'OBJECT_NOTES' => 'bx_albums_notes',
+            'OBJECT_COMMENTS_MEDIA' => 'bx_albums_media',
+            'OBJECT_PRIVACY_VIEW' => 'bx_albums_allow_view_to',
+            'OBJECT_PRIVACY_LIST_VIEW' => 'bx_albums_allow_view_favorite_list',
+            'OBJECT_FORM_ENTRY' => 'bx_albums',
+            'OBJECT_FORM_ENTRY_DISPLAY_VIEW' => 'bx_albums_entry_view',
+            'OBJECT_FORM_ENTRY_DISPLAY_ADD' => 'bx_albums_entry_add',
+            'OBJECT_FORM_ENTRY_DISPLAY_EDIT' => 'bx_albums_entry_edit',
+            'OBJECT_FORM_ENTRY_DISPLAY_DELETE' => 'bx_albums_entry_delete',
+            'OBJECT_FORM_MEDIA' => 'bx_albums_media',
+            'OBJECT_FORM_MEDIA_DISPLAY_EDIT' => 'bx_albums_media_edit',
+            'OBJECT_FORM_MEDIA_DISPLAY_MOVE' => 'bx_albums_media_move',
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY' => 'bx_albums_view', // actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL' => 'bx_albums_view_actions', // all actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_VIEW_MEDIA' => 'bx_albums_view_media', // actions menu on view media page
+            'OBJECT_MENU_ACTIONS_VIEW_MEDIA_ALL' => 'bx_albums_view_actions_media', // all actions menu on view media page
+            'OBJECT_MENU_ACTIONS_VIEW_MEDIA_UNIT' => 'bx_albums_view_actions_media_unit', // actions menu on media unit
+            'OBJECT_MENU_ACTIONS_MY_ENTRIES' => 'bx_albums_my', // actions menu on my entries page
+            'OBJECT_MENU_SUBMENU' => 'bx_albums_submenu', // main module submenu
+            'OBJECT_MENU_SUBMENU_VIEW_ENTRY' => 'bx_albums_view_submenu', // view entry submenu
+            'OBJECT_MENU_SUBMENU_VIEW_ENTRY_MAIN_SELECTION' => 'albums-home', // first item in view entry submenu from main module submenu
+            'OBJECT_MENU_SNIPPET_META' => 'bx_albums_snippet_meta', // menu for snippet meta info
+            'OBJECT_MENU_MANAGE_TOOLS' => 'bx_albums_menu_manage_tools', //manage menu in content administration tools
+            'OBJECT_GRID_ADMINISTRATION' => 'bx_albums_administration',
+            'OBJECT_GRID_COMMON' => 'bx_albums_common',
+            'OBJECT_UPLOADERS' => array('bx_albums_html5', 'bx_albums_crop'),
+            
+            'FUNCTION_FOR_GET_ITEM_INFO' => 'getMediaInfoById',
+
+            // menu items which visibility depends on custom visibility checking
+            'MENU_ITEM_TO_METHOD' => array (
+                'bx_albums_my' => array (
+                    'create-album' => 'checkAllowedAdd',
+                ),
+                'bx_albums_view' => $aMenuItems2Methods,
+                'bx_albums_view_media' => $aMenuItems2Methods,
+            ),
+
+            // informer messages
+            'INFORMERS' => array (
+                'approving' => array (
+                    'name' => 'bx-albums-approving',
+                    'map' => array (
+                        'pending' => array('msg' => '_bx_albums_txt_msg_status_pending', 'type' => BX_INFORMER_ALERT),
+                        'hidden' => array('msg' => '_bx_albums_txt_msg_status_hidden', 'type' => BX_INFORMER_ERROR),
+                    ),
+                ),
+                'processing' => array (
+                    'name' => 'bx_albums-processing',
+                    'map' => array (
+                        'awaiting' => array('msg' => '_bx_albums_txt_processing_awaiting', 'type' => BX_INFORMER_ALERT),
+                        'failed' => array('msg' => '_bx_albums_txt_processing_failed', 'type' => BX_INFORMER_ERROR)
+                    ),
+                ),
+            ),
+
+            // some language keys
+            'T' => array (
+                'txt_sample_single' => '_bx_albums_txt_sample_single',
+            	'txt_sample_single_with_article' => '_bx_albums_txt_sample_single_with_article',
+            	'txt_sample_comment_single' => '_bx_albums_txt_sample_comment_single',
+            	'txt_sample_vote_single' => '_bx_albums_txt_sample_vote_single',
+                'txt_sample_reaction_single' => '_bx_albums_txt_sample_reaction_single',
+                'txt_sample_score_up_single' => '_bx_albums_txt_sample_score_up_single',
+                'txt_sample_score_down_single' => '_bx_albums_txt_sample_score_down_single',
+                'txt_sample_action_changed' => '_bx_albums_txt_sample_action_changed',
+                'txt_media_single' => '_bx_albums_txt_media_single',
+                'txt_media_comment_single' => '_bx_albums_txt_media_comment_single',
+            	'txt_media_vote_single' => '_bx_albums_txt_media_vote_single',
+                'txt_media_score_up_single' => '_bx_albums_txt_media_score_up_single',
+                'txt_media_score_down_single' => '_bx_albums_txt_media_score_down_single',
+            	'form_field_author' => '_bx_albums_form_entry_input_author',
+            	'grid_action_err_delete' => '_bx_albums_grid_action_err_delete',
+            	'grid_txt_account_manager' => '_bx_albums_grid_txt_account_manager',
+                'filter_item_active' => '_bx_albums_grid_filter_item_title_adm_active',
+            	'filter_item_hidden' => '_bx_albums_grid_filter_item_title_adm_hidden',
+                'filter_item_pending' => '_bx_albums_grid_filter_item_title_adm_pending',
+            	'filter_item_select_one_filter1' => '_bx_albums_grid_filter_item_title_adm_select_one_filter1',
+                'filter_item_select_one_filter2' => '_bx_albums_grid_filter_item_title_adm_select_one_filter2',
+            	'menu_item_manage_my' => '_bx_albums_menu_item_title_manage_my',
+            	'menu_item_manage_all' => '_bx_albums_menu_item_title_manage_all',
+                'txt_all_entries_by' => '_bx_albums_txt_all_entries_by',
+                'txt_all_entries_in' => '_bx_albums_txt_all_entries_in',
+            	'txt_all_entries_by_author' => '_bx_albums_page_title_browse_by_author',
+                'txt_all_entries_by_context' => '_bx_albums_page_title_browse_by_context',
+                'txt_media_exif_camera' => '_bx_albums_txt_media_album_camera',
+                'txt_media_exif_focal_length' => '_bx_albums_txt_media_album_focal_length',
+                'txt_media_exif_focal_length_value' => '_bx_albums_txt_media_album_focal_length_value',
+                'txt_media_exif_aperture' => '_bx_albums_txt_media_album_aperture',
+                'txt_media_exif_shutter_speed' => '_bx_albums_txt_media_album_shutter_speed',
+                'txt_media_exif_shutter_speed_value' => '_bx_albums_txt_media_album_shutter_speed_value',
+                'txt_media_exif_iso' => '_bx_albums_txt_media_album_iso',
+            ),
+        );
+
+        $this->_aJsClasses = array(
+        	'main' => 'BxAlbumsMain',
+        	'manage_tools' => 'BxAlbumsManageTools'
+        );
+
+        $this->_aJsObjects = array(
+        	'main' => 'oBxAlbumsMain',
+        	'manage_tools' => 'oBxAlbumsManageTools'
+        );
+
+        $this->_aGridObjects = array(
+        	'common' => $this->CNF['OBJECT_GRID_COMMON'],
+        	'administration' => $this->CNF['OBJECT_GRID_ADMINISTRATION'],
+        	
+        );
+    }
+}
+
+/** @} */

+ 1070 - 0
modules/boonex/albums/updates/14.0.4_14.0.5/source/classes/BxAlbumsModule.php

@@ -0,0 +1,1070 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Albums Albums
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+/**
+ * Albums module
+ */
+class BxAlbumsModule extends BxBaseModTextModule
+{
+    protected $_aContexts = array('popular', 'public', 'author');
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    public function serviceGetSafeServices()
+    {
+        $a = parent::serviceGetSafeServices();
+        return array_merge($a, array (
+            'EntityAddFiles' => '',
+            'MediaComments' => '',
+            'BrowseRecentMedia' => '',
+            'BrowseFeaturedMedia' => '',
+            'BrowsePopularMedia' => '',
+            'BrowseTopMedia' => '',
+            'BrowseFavoriteMedia' => '',
+        ));
+    }
+    
+    public function actionEmbed($iContentId, $sUnitTemplate = '', $sAddCode = '')
+    {
+        return $this->_oTemplate->getJsCode('main') . parent::actionEmbed($iContentId);
+    }
+
+    public function actionEmbedMedia($iContentId)
+    {
+        $oTemplate = BxDolTemplate::getInstance();
+        
+        $aContentInfo = $this->_oDb->getMediaInfoById($iContentId);
+        if(empty($aContentInfo))
+            $oTemplate->getEmbed(false);
+
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit_media_gallery.html';
+
+        $oTemplate->getEmbed($this->_oTemplate->unit($aContentInfo, true, $sUnitTemplate));
+    }
+    
+    /**
+     * Entry actions and social sharing block
+     */
+    public function serviceEntityAllActions ($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+        
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $aMedias = $this->_oDb->getMediaListByContentId($iContentId);
+        if(!empty($aMedias) && is_array($aMedias)) {
+            $aMedia = array_shift($aMedias);
+            if(!empty($aMedia['file_id']))
+                $aParams = array_merge(array(
+                    'entry_thumb' => $aMedia['file_id']
+                ), $aParams);
+        }
+
+        return parent::serviceEntityAllActions(array($iContentId, $aContentInfo), $aParams);
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_albums Albums
+     * @subsection bx_albums-forms Forms
+     * @subsubsection bx_albums-entity_add_files entity_add_files
+     * 
+     * @code bx_srv('bx_albums', 'entity_add_files', [...]); @endcode
+     * 
+     * Display form for adding media to the album.
+     * @param $iContentId album content id where media will be added, if it's not provided then it's determined from 'id' GET variable
+     * @return HTML string with form, all necessary CSS and JS files are automatically added to the HEAD section of the site HTML. On error false or empty string is returned.
+     * 
+     * @see BxAlbumsModule::serviceEntityAddFiles
+     */
+    /** 
+     * @ref bx_albums-entity_add_files "entity_add_files"
+     */
+    public function serviceEntityAddFiles ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId, 'bx_albums_entry_add_images');
+    }
+
+    /**
+     * Delete file and album association, also file views, votes, comments, meta data are also deleted
+     * @param $iFileId file ID
+     * @return true on success of false on error
+     */ 
+    public function serviceDeleteFileAssociations($iFileId)
+    {        
+        $CNF = &$this->_oConfig->CNF;
+
+        if (!($aMediaInfo = $this->_oDb->getMediaInfoSimpleByFileId($iFileId))) // file is already deleted
+            return true; 
+    
+        if (!$this->_oDb->deassociateFileWithContent(0, $iFileId))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aMediaInfo['content_id']);
+        $iSender = isLogged() ? bx_get_logged_profile_id() : $aMediaInfo['author'];
+        $iAuthor = isset($aContentInfo[$CNF['FIELD_AUTHOR']]) ? $aContentInfo[$CNF['FIELD_AUTHOR']] : $aMediaInfo['author'];
+        
+        /**
+         * @hooks
+         * @hookdef hook-bx_albums-media_deleted 'bx_albums', 'media_deleted' - hook on new media deleted from album
+         * - $unit_name - equals `bx_albums`
+         * - $action - equals `media_deleted` 
+         * - $object_id - album_id
+         * - $sender_id - author's profile_id
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `object_author_id` - [int] confirmation type can be none/phone/email/email_and_phone/email_or_phone
+         *      - `subobject_id` - [int] id for added media
+         *      - `media_id` - [int] id for added media
+         *      - `media_info` - [array] media info
+         * @hook @ref hook-bx_albums-media_deleted
+         */
+        bx_alert($this->getName(), 'media_deleted', $aMediaInfo['content_id'], $iSender, array(
+            'object_author_id' => $iAuthor,
+
+            'subobject_id' => $aMediaInfo['id'],
+
+            'media_id' => $aMediaInfo['id'], 
+            'media_info' => $aMediaInfo,
+        ));
+
+        bx_alert($this->getName() . '_media', 'deleted', $aMediaInfo['id'], $iSender, array(
+            'object_id' => $aMediaInfo['content_id'],
+            'object_author_id' => $iAuthor,
+
+            'media_info' => $aMediaInfo,
+        ));        
+
+        if (!empty($CNF['OBJECT_VIEWS_MEDIA'])) {
+            $o = BxDolView::getObjectInstance($CNF['OBJECT_VIEWS_MEDIA'], $aMediaInfo['id']);
+            if ($o) $o->onObjectDelete();
+        }
+
+        if (!empty($CNF['OBJECT_VOTES_MEDIA'])) {
+            $o = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES_MEDIA'], $aMediaInfo['id']);
+            if ($o) $o->onObjectDelete();
+        }
+
+        if (!empty($CNF['OBJECT_SCORES_MEDIA'])) {
+            $o = BxDolScore::getObjectInstance($CNF['OBJECT_SCORES_MEDIA'], $aMediaInfo['id']);
+            if ($o) $o->onObjectDelete();
+        }
+
+        if (!empty($CNF['OBJECT_COMMENTS_MEDIA'])) {
+            $o = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS_MEDIA'], $aMediaInfo['id']);
+            if ($o) $o->onObjectDelete();
+        }
+
+        if (!empty($CNF['OBJECT_METATAGS_MEDIA'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS_MEDIA']);
+            $oMetatags->onDeleteContent($aMediaInfo['id']);
+        }
+
+        if (!empty($CNF['OBJECT_METATAGS_MEDIA_CAMERA'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS_MEDIA_CAMERA']);
+            $oMetatags->onDeleteContent($aMediaInfo['id']);
+        }
+
+        BxDolPage::deleteSeoLink ($this->getName(), 'bx_albums_media', $aMediaInfo['id']);
+
+        return true;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_albums Albums
+     * @subsection bx_albums-page_blocks Page Blocks
+     * @subsubsection bx_albums-media_comments media_comments
+     * 
+     * @code bx_srv('bx_albums', 'media_comments', [...]); @endcode
+     * 
+     * Display media comments block.
+     * @param $iMediaId media ID, if it's omitted then it's taken from 'id' GET variable.
+     * @return HTML string with comments. On error false or empty string is returned.
+     * 
+     * @see BxAlbumsModule::serviceMediaComments
+     */
+    /** 
+     * @ref bx_albums-media_comments "media_comments"
+     */
+    public function serviceMediaComments ($iMediaId = 0)
+    {
+        return $this->_entityComments($this->_oConfig->CNF['OBJECT_COMMENTS_MEDIA'], $iMediaId);
+    }
+
+    /**
+     * Display media author block.
+     * @param $iMediaId media ID, if it's omitted then it's taken from 'id' GET variable.
+     * @return HTML string with block content. On error false or empty string is returned.
+     */
+    public function serviceMediaAuthor ($iMediaId = 0)
+    {
+        return $this->_serviceTemplateFunc ('mediaAuthor', $iMediaId, 'getMediaInfoById');
+    }
+
+    /**
+     * Entry actions and social sharing block
+     */
+    public function serviceMediaAllActions ($mixedContent = false, $aParams = array())
+    {
+        $iMediaId = 0;
+        $aMediaInfo = array();
+
+        $bContent = !empty($mixedContent);
+        if($bContent && is_array($mixedContent))
+            list($iMediaId, $aMediaInfo) = $mixedContent;
+        else {
+            if($bContent)
+                $iMediaId = (int)$mixedContent;
+            else
+                $iMediaId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+            if(!$iMediaId)
+                return false;
+
+            $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        }
+
+        if(!$iMediaId || !$aMediaInfo)
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        return parent::serviceEntityAllActions (array($iMediaId, $aMediaInfo), array_merge(array(
+            'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA_ALL'],
+            'object_transcoder' => $CNF['OBJECT_IMAGES_TRANSCODER_BIG'],
+            'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_MEDIA'] . '&id=' . $iMediaId)),
+            'entry_title' => $aMediaInfo['title'],
+            'entry_thumb' => $aMediaInfo['file_id']
+        ), $aParams));
+    }
+
+    /**
+     * Entry actions block
+     */
+    public function serviceMediaActions ($iContentId = 0)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    /**
+     * Display media social sharing buttons block.
+     * @param $iMediaId media ID, if it's omitted then it's taken from 'id' GET variable.
+     * @return HTML string with block content. On error false or empty string is returned.
+     */
+    public function serviceMediaSocialSharing ($iMediaId = 0)
+    {
+        if(!$iMediaId)
+            $iMediaId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if(!$iMediaId)
+            return false;
+
+        $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        if(!$aMediaInfo)
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->serviceEntitySocialSharing(array($iMediaId, $aMediaInfo), array(
+            'uri' => $CNF['URI_VIEW_MEDIA'],
+            'title' => $aMediaInfo['title'],
+            'id_thumb' => $aMediaInfo['file_id'],
+            'object_transcoder' => $CNF['OBJECT_IMAGES_TRANSCODER_BIG']
+        ));
+    }
+
+    /**
+     * Display media preview block.
+     * @param $iMediaId media ID, if it's omitted then it's taken from 'id' GET variable.
+     * @param $mixedContext current context to navigate to next and previous file, possible values: popular, public, author. If omitted then value from 'context' GTE variable is taken. Default context is 'album' - when nothin is specified.
+     * @param $aParams array of additional params. 
+     * @return HTML string with block content. On error false or empty string is returned.
+     */
+    public function serviceMediaView ($iMediaId = 0, $mixedContext = false, $aParams = array())
+    {
+        if(!$iMediaId)
+            $iMediaId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if(!$iMediaId)
+            return false;
+
+        if(!$mixedContext) {
+            $mixedContext = bx_process_input(bx_get('context'));
+            if(!in_array($mixedContext, $this->_aContexts)) // when no context specified, it is assumed that it is an album context
+                $mixedContext = bx_process_input($mixedContext, BX_DATA_INT); // numeric context is reserved for future use
+        }
+        $aParams['context'] = $mixedContext;
+
+        $iAutoplay = 0;
+        if(!isset($aParams['autoplay']) && ($iAutoplay = bx_get('autoplay')) !== false)
+            $aParams['autoplay'] = (int)$iAutoplay;
+
+        return $this->_oTemplate->entryMediaView ($iMediaId, $aParams);
+    }
+
+    public function checkAllowedSetThumb ($iContentId = 0)
+    {
+        return CHECK_ACTION_RESULT_NOT_ALLOWED;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_albums Albums
+     * @subsection bx_albums-browse Browse
+     * @subsubsection bx_albums-browse_recent_media browse_recent_media
+     * 
+     * @code bx_srv('bx_albums', 'browse_recent_media', [...]); @endcode
+     * 
+     * Display block for browsing recently uploaded media files.
+     * @param $sUnitView unit view mode: gallery
+     * @param $bDisplayEmptyMsg display 'empty' message when nothing to browse, or return empty string.
+     * @param $bAjaxPaginate use AJAX or regular paginate.
+     * @return HTML string with block content. On error false or empty string is returned.
+     * 
+     * @see BxAlbumsModule::serviceBrowseRecentMedia
+     */
+    /** 
+     * @ref bx_albums-browse_recent_media "browse_recent_media"
+     */
+    public function serviceBrowseRecentMedia ($sUnitView = false, $bDisplayEmptyMsg = true, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('recent', array('unit_view' => $sUnitView), BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate, 'SearchResultMedia');
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_albums Albums
+     * @subsection bx_albums-browse Browse
+     * @subsubsection bx_albums-browse_featured_media browse_featured_media
+     * 
+     * @code bx_srv('bx_albums', 'browse_featured_media', [...]); @endcode
+     * 
+     * Display featured media. 
+     * @param $sUnitView unit view mode: gallery
+     * @param $bDisplayEmptyMsg display 'empty' message when nothing to browse, or return empty string.
+     * @param $bAjaxPaginate use AJAX or regular paginate.
+     * @return HTML string with block content. On error false or empty string is returned.
+     * 
+     * @see BxAlbumsModule::serviceBrowseFeaturedMedia
+     */
+    /** 
+     * @ref bx_albums-browse_featured_media "browse_featured_media"
+     */
+    public function serviceBrowseFeaturedMedia ($sUnitView = false, $bDisplayEmptyMsg = false, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('featured', array('unit_view' => $sUnitView), BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate, 'SearchResultMedia');
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_albums Albums
+     * @subsection bx_albums-browse Browse
+     * @subsubsection bx_albums-browse_popular_media browse_popular_media
+     * 
+     * @code bx_srv('bx_albums', 'browse_popular_media', [...]); @endcode
+     * 
+     * Display popular media.
+     * @param $sUnitView unit view mode: gallery
+     * @param $bDisplayEmptyMsg display 'empty' message when nothing to browse, or return empty string.
+     * @param $bAjaxPaginate use AJAX or regular paginate.
+     * @return HTML string with block content. On error false or empty string is returned.
+     * 
+     * @see BxAlbumsModule::serviceBrowsePopularMedia
+     */
+    /** 
+     * @ref bx_albums-browse_popular_media "browse_popular_media"
+     */
+    public function serviceBrowsePopularMedia ($sUnitView = false, $bDisplayEmptyMsg = true, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('popular', array('unit_view' => $sUnitView), BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate, 'SearchResultMedia');
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_albums Albums
+     * @subsection bx_albums-browse Browse
+     * @subsubsection bx_albums-browse_top_media browse_top_media
+     * 
+     * @code bx_srv('bx_albums', 'browse_top_media', [...]); @endcode
+     * 
+     * Display top media.
+     * @param $sUnitView unit view mode: gallery
+     * @param $bDisplayEmptyMsg display 'empty' message when nothing to browse, or return empty string.
+     * @param $bAjaxPaginate use AJAX or regular paginate.
+     * @return HTML string with block content. On error false or empty string is returned.
+     * 
+     * @see BxAlbumsModule::serviceBrowseTopMedia
+     */
+    /** 
+     * @ref bx_albums-browse_top_media "browse_top_media"
+     */
+    public function serviceBrowseTopMedia ($sUnitView = false, $bDisplayEmptyMsg = true, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('top', array('unit_view' => $sUnitView), BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate, 'SearchResultMedia');
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_albums Albums
+     * @subsection bx_albums-browse Browse
+     * @subsubsection bx_albums-browse_favorite_media browse_favorite_media
+     * 
+     * @code bx_srv('bx_albums', 'browse_favorite_media', [...]); @endcode
+     * 
+     * Display favorite media for particular profile. 
+     * @param $iProfileId profile ID, if omitted then 'profile_id' GET variable is used 
+     * @param $aParams additional params for browsing such as 'unit_view'
+     * 
+     * @see BxAlbumsModule::serviceBrowseFavoriteMedia
+     */
+    /** 
+     * @ref bx_albums-browse_favorite_media "browse_favorite_media"
+     */
+    public function serviceBrowseFavoriteMedia ($iProfileId = 0, $aParams = array())
+    {
+        $oProfile = null;
+        if((int)$iProfileId)
+            $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile && bx_get('profile_id') !== false)
+            $oProfile = BxDolProfile:: getInstance(bx_process_input(bx_get('profile_id'), BX_DATA_INT));
+        if(!$oProfile)
+            $oProfile = BxDolProfile::getInstance();
+        if(!$oProfile)
+            return '';
+
+        $bEmptyMessage = false;
+        if(isset($aParams['empty_message'])) {
+            $bEmptyMessage = (bool)$aParams['empty_message'];
+            unset($aParams['empty_message']);
+        }
+
+        return $this->_serviceBrowse ('favorite', array_merge(array('user' => $oProfile->id()), $aParams), BX_DB_PADDING_DEF, $bEmptyMessage, true, 'SearchResultMedia');
+    }
+
+    public function serviceGetTimelineData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+        $aResult = parent::serviceGetTimelineData();
+        $aResult['handlers'] = array_merge($aResult['handlers'], array(
+            array('group' => $sModule . '_object_media', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'medias_added', 'module_name' => $sModule, 'module_method' => 'get_timeline_media', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+        ));
+        $aResult['alerts'] = array_merge($aResult['alerts'], array(
+            array('unit' => $sModule, 'action' => 'medias_added')
+        ));
+
+        return $aResult;
+    }
+
+    public function serviceGetTimelineMedia($aEvent, $aBrowseParams = array())
+    {
+        if(empty($aEvent['content']))
+            return false;
+
+        $aEvent['content'] = unserialize($aEvent['content']);
+        if(empty($aEvent['content']['medias_added']) || !is_array($aEvent['content']['medias_added']))
+            return false;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+        
+        //--- Views
+        $oViews = isset($CNF['OBJECT_VIEWS']) ? BxDolView::getObjectInstance($CNF['OBJECT_VIEWS'], $iContentId) : null;
+
+        $aViews = array();
+        if ($oViews && $oViews->isEnabled())
+            $aViews = array(
+                'system' => $CNF['OBJECT_VIEWS'],
+                'object_id' => $iContentId,
+                'count' => $aContentInfo['views']
+            );
+
+        //--- Votes
+        $oVotes = isset($CNF['OBJECT_VOTES']) ? BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $iContentId) : null;
+
+        $aVotes = array();
+        if ($oVotes && $oVotes->isEnabled())
+            $aVotes = array(
+                'system' => $CNF['OBJECT_VOTES'],
+                'object_id' => $iContentId,
+                'count' => $aContentInfo['votes']
+            );
+
+        //--- Scores
+        $oScores = isset($CNF['OBJECT_SCORES']) ? BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], $iContentId) : null;
+
+        $aScores = array();
+        if ($oScores && $oScores->isEnabled())
+            $aScores = array(
+                'system' => $CNF['OBJECT_SCORES'],
+                'object_id' => $iContentId,
+                'score' => $aContentInfo['score']
+            );
+
+        //--- Reports
+        $oReports = isset($CNF['OBJECT_REPORTS']) ? BxDolReport::getObjectInstance($CNF['OBJECT_REPORTS'], $aEvent['object_id']) : null;
+
+        $aReports = array();
+        if ($oReports && $oReports->isEnabled())
+            $aReports = array(
+                'system' => $CNF['OBJECT_REPORTS'],
+                'object_id' => $iContentId,
+                'count' => $aContentInfo['reports']
+            );
+
+        //--- Comments
+        $oCmts = isset($CNF['OBJECT_COMMENTS']) ? BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $iContentId) : null;
+
+        $aComments = array();
+        if($oCmts && $oCmts->isEnabled())
+            $aComments = array(
+                'system' => $CNF['OBJECT_COMMENTS'],
+                'object_id' => $iContentId,
+                'count' => $aContentInfo['comments']
+            );
+
+        //--- Title & Description
+        $sTitle = isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : (isset($CNF['FIELD_TEXT']) && isset($aContentInfo[$CNF['FIELD_TEXT']]) ? strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...') : '');
+
+        return array(
+            '_cache' => true,
+            'owner_id' => $aEvent['owner_id'],
+            'object_owner_id' => $aContentInfo['author'],
+            'icon' => !empty($CNF['ICON']) ? $CNF['ICON'] : '',
+            'sample' => isset($CNF['T']['txt_sample_single_with_article']) ? $CNF['T']['txt_sample_single_with_article'] : $CNF['T']['txt_sample_single'],
+            'sample_wo_article' => $CNF['T']['txt_sample_single'],
+    	    'sample_action' => $CNF['T']['txt_sample_action_changed'],
+            'url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId)),
+            'content' => $this->_getContentForTimelineMedia($aEvent, $aContentInfo, $aBrowseParams), //a string to display or array to parse default template before displaying.
+            'date' => $aContentInfo[$CNF['FIELD_ADDED']],
+            'views' => $aViews,
+            'votes' => $aVotes,
+            'scores' => $aScores,
+            'reports' => $aReports,
+            'comments' => $aComments,
+            'title' => $sTitle, //may be empty.
+            'description' => '' //may be empty.
+        );
+    }
+
+    public function serviceGetNotificationsData()
+    {
+        $sModule = $this->_aModule['name'];
+
+        $sEventPrivacy = $sModule . '_allow_view_event_to';
+        if(BxDolPrivacy::getObjectInstance($sEventPrivacy) === false)
+                $sEventPrivacy = '';
+
+        $aResult = parent::serviceGetNotificationsData();
+        $aResult['handlers'] = array_merge($aResult['handlers'], array(
+            array('group' => $sModule . '_object_media', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'medias_added', 'module_name' => $sModule, 'module_method' => 'get_notifications_media', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            array('group' => $sModule . '_object_media', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'media_deleted'),
+
+            array('group' => $sModule . '_comment_media', 'type' => 'insert', 'alert_unit' => $sModule . '_media', 'alert_action' => 'commentPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_comment_media', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            array('group' => $sModule . '_comment_media', 'type' => 'delete', 'alert_unit' => $sModule . '_media', 'alert_action' => 'commentRemoved'),
+
+            array('group' => $sModule . '_vote_media', 'type' => 'insert', 'alert_unit' => $sModule . '_media', 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_vote_media', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            array('group' => $sModule . '_vote_media', 'type' => 'delete', 'alert_unit' => $sModule . '_media', 'alert_action' => 'undoVote'),
+            
+            array('group' => $sModule . '_score_up_media', 'type' => 'insert', 'alert_unit' => $sModule . '_media', 'alert_action' => 'doVoteUp', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_up_media', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+
+            array('group' => $sModule . '_score_down_media', 'type' => 'insert', 'alert_unit' => $sModule . '_media', 'alert_action' => 'doVoteDown', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_down_media', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+        ));
+
+        $aResult['settings'] = array_merge($aResult['settings'], array(
+            array('group' => 'content', 'unit' => $sModule, 'action' => 'medias_added', 'types' => array('follow_member', 'follow_context')),
+            array('group' => 'comment', 'unit' => $sModule . '_media', 'action' => 'commentPost', 'types' => array('personal', 'follow_member', 'follow_context')),
+            array('group' => 'vote', 'unit' => $sModule . '_media', 'action' => 'doVote', 'types' => array('personal', 'follow_member', 'follow_context')),
+            array('group' => 'score_up', 'unit' => $sModule . '_media', 'action' => 'doVoteUp', 'types' => array('personal', 'follow_member', 'follow_context')),
+            array('group' => 'score_down', 'unit' => $sModule . '_media', 'action' => 'doVoteDown', 'types' => array('personal', 'follow_member', 'follow_context'))
+        ));
+
+        $aResult['alerts'] = array_merge($aResult['alerts'], array(
+            array('unit' => $sModule, 'action' => 'medias_added'),
+            array('unit' => $sModule, 'action' => 'media_deleted'),
+
+            array('unit' => $sModule . '_media', 'action' => 'commentPost'),
+            array('unit' => $sModule . '_media', 'action' => 'commentRemoved'),
+
+            array('unit' => $sModule . '_media', 'action' => 'doVote'),
+            array('unit' => $sModule . '_media', 'action' => 'undoVote'),
+
+            array('unit' => $sModule . '_media', 'action' => 'doVoteUp'),
+            array('unit' => $sModule . '_media', 'action' => 'doVoteDown'),
+        ));
+
+        return $aResult; 
+    }
+
+    public function serviceGetNotificationsMedia($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+    	$iMediaId = (int)$aEvent['subobject_id'];
+    	$aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        if(empty($aMediaInfo) || !is_array($aMediaInfo))
+            return array();
+
+        $oPermalinks = BxDolPermalinks::getInstance();
+        $sEntryUrl = bx_absolute_url($oPermalinks->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId), '{bx_url_root}');
+        $sSubentryUrl = bx_absolute_url($oPermalinks->permalink('page.php?i=' . $CNF['URI_VIEW_MEDIA'] . '&id=' . $iMediaId), '{bx_url_root}');
+        $sEntryCaption = isset($aMediaInfo['title']) ? $aMediaInfo['title'] : _t('_bx_albums_media');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aMediaInfo['author'],
+            'subentry_sample' => $CNF['T']['txt_media_single'],
+            'subentry_url' => $sSubentryUrl,
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    public function serviceGetNotificationsCommentMedia($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iMediaId = (int)$aEvent['object_id'];
+    	$aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        if(empty($aMediaInfo) || !is_array($aMediaInfo))
+            return array();
+
+        $oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS_MEDIA'], $iMediaId);
+        if(!$oComment || !$oComment->isEnabled())
+            return array();
+
+        $sEntryUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_MEDIA'] . '&id=' . $aMediaInfo['id']), '{bx_url_root}');
+        $sEntryCaption = isset($aMediaInfo['title']) ? $aMediaInfo['title'] : _t('_bx_albums_media');
+
+        return array(
+            'object_id' => $aMediaInfo['content_id'],
+            'entry_sample' => $CNF['T']['txt_media_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aMediaInfo['author'],
+            'subentry_sample' => $CNF['T']['txt_media_comment_single'],
+            'subentry_url' => bx_absolute_url($oComment->getViewUrl((int)$aEvent['subobject_id'], false), '{bx_url_root}'),
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    public function serviceGetNotificationsVoteMedia($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iMediaId = (int)$aEvent['object_id'];
+    	$aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        if(empty($aMediaInfo) || !is_array($aMediaInfo))
+            return array();
+
+        $oVote = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES_MEDIA'], $iMediaId);
+        if(!$oVote || !$oVote->isEnabled())
+            return array();
+
+        $sEntryUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_MEDIA'] . '&id=' . $aMediaInfo['id']), '{bx_url_root}');
+        $sEntryCaption = isset($aMediaInfo['title']) ? $aMediaInfo['title'] : _t('_bx_albums_media');
+
+        return array(
+            'object_id' => $aMediaInfo['content_id'],
+            'entry_sample' => $CNF['T']['txt_media_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aMediaInfo['author'],
+            'subentry_sample' => $CNF['T']['txt_media_vote_single'],
+            'subentry_url' => '',
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    public function serviceGetNotificationsScoreUpMedia($aEvent)
+    {
+    	return $this->_serviceGetNotificationsScoreMedia('up', $aEvent);
+    }
+
+    public function serviceGetNotificationsScoreDownMedia($aEvent)
+    {
+    	return $this->_serviceGetNotificationsScoreMedia('down', $aEvent);
+    }
+
+    protected function _serviceGetNotificationsScoreMedia($sType, $aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$iMediaId = (int)$aEvent['object_id'];
+    	$aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        if(empty($aMediaInfo) || !is_array($aMediaInfo))
+            return array();
+
+        $oVote = BxDolScore::getObjectInstance($CNF['OBJECT_SCORES_MEDIA'], $iMediaId);
+        if(!$oVote || !$oVote->isEnabled())
+            return array();
+
+        $sEntryUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_MEDIA'] . '&id=' . $aMediaInfo['id']), '{bx_url_root}');
+        $sEntryCaption = isset($aMediaInfo['title']) ? $aMediaInfo['title'] : _t('_bx_albums_media');
+
+        return array(
+            'object_id' => $aMediaInfo['content_id'],
+            'entry_sample' => $CNF['T']['txt_media_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aMediaInfo['author'],
+            'subentry_sample' => $CNF['T']['txt_media_score_' . $sType . '_single'],
+            'subentry_url' => '',
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    public function actionEditMedia($iMediaId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iMediaId = (int)$iMediaId;
+        $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        $aContentInfo = $this->_oDb->getContentInfoById($aMediaInfo['content_id']);
+        if(($sMsg = $this->checkAllowedEdit($aContentInfo)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return echoJson(['msg' => $sMsg]);
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_MEDIA'], $CNF['OBJECT_FORM_MEDIA_DISPLAY_EDIT']);
+        $oForm->initForm('edit', $iMediaId);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            if($oForm->update($iMediaId) !== false) {
+                $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+                if(!empty($aMediaInfo) && is_array($aMediaInfo) && !empty($CNF['OBJECT_METATAGS_MEDIA'])) {
+                    $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS_MEDIA']);
+                    if ($oMetatags->keywordsIsEnabled())
+                        $oMetatags->keywordsAdd($aMediaInfo['id'], $aMediaInfo['title']);
+                }
+
+                $aRes = array('reload' => 1);
+            }
+            else
+                $aRes = array('msg' => _t('_bx_albums_txt_err_cannot_perform_action'));
+
+            return echoJson($aRes);
+        }
+
+        $sContent = BxTemplStudioFunctions::getInstance()->transBox('bx-albums-edit-media-popup', $this->_oTemplate->parseHtmlByName('media-edit.html', array(
+            'form_id' => $oForm->aFormAttrs['id'],
+            'form' => $oForm->getCode(true)
+        )));
+
+        echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+    }
+	
+    public function actionDeleteMedia($iMediaId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sUploader = reset($CNF['OBJECT_UPLOADERS']);
+        $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        if(!$sUploader || empty($aMediaInfo) || !is_array($aMediaInfo))
+            return echoJson([]);
+
+        $oUploader = BxDolUploader::getObjectInstance($sUploader, $CNF['OBJECT_STORAGE'], '');
+        if($oUploader === false) 
+            return echoJson(['msg' => _t('_sys_txt_error_occured')]);
+
+        $oUploader->deleteGhost($aMediaInfo['file_id'], bx_get_logged_profile_id());
+
+        return echoJson(['redirect' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aMediaInfo['content_id']))]);            
+    }
+
+    public function actionMoveMedia($iMediaId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iMediaId = (int)$iMediaId;
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_MEDIA'], $CNF['OBJECT_FORM_MEDIA_DISPLAY_MOVE']);
+        $oForm->initForm('move', $iMediaId);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            if($oForm->update($iMediaId) !== false)
+                $aRes = array('reload' => 1);
+            else
+                $aRes = array('msg' => _t('_bx_albums_txt_err_cannot_perform_action'));
+
+            return echoJson($aRes);
+        }
+
+        $sContent = BxTemplStudioFunctions::getInstance()->transBox('bx-albums-move-media-popup', $this->_oTemplate->parseHtmlByName('media-edit.html', array(
+            'form_id' => $oForm->aFormAttrs['id'],
+            'form' => $oForm->getCode(true)
+        )));
+
+        echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+    }
+
+    public function actionGetSiblingMedia($iMediaId, $mixedContext)
+    {
+        $aSiblings = false;
+        $sErrorMsg = false;
+        if (!($aMediaInfo = $this->_oDb->getMediaInfoById((int)$iMediaId))) 
+            $sErrorMsg = _t('_sys_txt_error_occured');
+
+        if (empty($sErrorMsg) && !($aContentInfo = $this->_oDb->getContentInfoById($aMediaInfo['content_id'])))
+            $sErrorMsg = _t('_sys_txt_error_occured');
+
+        if (empty($sErrorMsg) && (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedView($aContentInfo))))
+            $sErrorMsg = $sMsg;
+
+        if (empty($sErrorMsg)) {
+            $aSiblings = array (
+                'next' => $this->_oTemplate->getNextPrevMedia($aMediaInfo, true, $mixedContext),
+                'prev' => $this->_oTemplate->getNextPrevMedia($aMediaInfo, false, $mixedContext),
+            );
+        }
+    
+        $a = $sErrorMsg ? array('error' => $sErrorMsg) : array('next' => $aSiblings['next'], 'prev' => $aSiblings['prev']);
+
+        $s = json_encode($a);
+
+        header('Content-type: text/html; charset=utf-8');
+        echo $s;
+    }
+
+    public function actionRssMedia ()
+    {
+        $aArgs = func_get_args();
+        $this->_rss($aArgs, 'SearchResultMedia');
+    }
+
+    public function getMediaDuration($aMediaInfo) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aMediaInfo) || !is_array($aMediaInfo))
+            return 0;
+
+        $sField = 'duration';
+        if(!empty($aMediaInfo[$sField]))
+            return (int)$aMediaInfo[$sField];
+
+        $iMedia = $aMediaInfo['id'];
+        $sMedia = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4'])->getFileUrl($iMedia);
+        if(empty($sMedia))
+            return 0;
+
+        $iDuration = (int)BxDolTranscoderVideo::getDuration($sMedia);
+        if(!empty($iDuration))
+            $this->_oDb->updateMedia(array($sField => $iDuration), array('id' => $iMedia));
+
+        return $iDuration;
+    }
+
+    protected function _buildRssParams($sMode, $aArgs)
+    {        
+        if ($aParams = parent::_buildRssParams($sMode, $aArgs))
+            return $aParams;
+
+        $sMode = bx_process_input($sMode);
+        switch ($sMode) {
+            case 'album':
+                $aParams = array('album_id' => isset($aArgs[0]) ? (int)$aArgs[0] : '');
+                break;
+        }
+
+        return $aParams;
+    }
+
+    protected function _getImagesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']) || empty($CNF['OBJECT_TRANSCODER_COVER']))
+            return array();
+
+        $aMediaList = $this->_oDb->getMediaListByContentId($aContentInfo[$CNF['FIELD_ID']]);
+        if(empty($aMediaList) || !is_array($aMediaList))
+            return array();
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        $oTcPoster = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['poster']);
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']);
+        $oTranscoderCover = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_TRANSCODER_COVER']);
+        if(!$oTranscoder || !$oTranscoderCover)
+            return array();
+
+        $aOutput = array();
+        foreach ($aMediaList as $aMedia) {
+            $aFileInfo = $oStorage->getFile($aMedia['file_id']);
+
+            $bVideo = $oTcPoster && strncmp('video/', $aFileInfo['mime_type'], 6) === 0 && $oTcPoster->isMimeTypeSupported($aFileInfo['mime_type']);
+            if($bVideo)
+                continue;
+
+            $aOutput[$aMedia['id']] = array (
+                'id' => $aMedia['id'],
+                'url' => $this->_oTemplate->getViewMediaUrl($CNF, $aMedia['id']), 
+                'src' => $oTranscoder->getFileUrl($aMedia['file_id']),
+                'src_orig' => $oTranscoderCover->getFileUrl($aMedia['file_id'])
+            );
+        }
+
+        if(empty($aOutput))
+            return array();
+
+        $iSlice = 4;
+        $iTotal = count($aOutput);
+        return array(
+            'total' => $iTotal,
+            'items' => $iTotal > $iSlice ? array_slice($aOutput, 0, $iSlice) : $aOutput
+        );
+    }
+
+    protected function _getVideosForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['OBJECT_VIDEOS_TRANSCODERS']))
+            return array();
+
+        $aMediaList = $this->_oDb->getMediaListByContentId($aContentInfo[$CNF['FIELD_ID']]);
+        if(empty($aMediaList) || !is_array($aMediaList))
+            return array();
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        $oTcPoster = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['poster']);
+        $oTcMp4 = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4']);
+        $oTcMp4Hd = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4_hd']);
+        if(!$oTcPoster || !$oTcMp4 || !$oTcMp4Hd)
+            return array();
+
+        $aOutput = array();
+        foreach($aMediaList as $k => $aMedia) {
+            $aFileInfo = $oStorage->getFile($aMedia['file_id']);
+
+            $bVideo = $oTcPoster && strncmp('video/', $aFileInfo['mime_type'], 6) === 0 && $oTcPoster->isMimeTypeSupported($aFileInfo['mime_type']);
+            if(!$bVideo)
+                continue;
+
+            $sVideoUrl = $oStorage->getFileUrlById($aMedia['file_id']);
+            $aVideoFile = $oStorage->getFile($aMedia['file_id']);
+
+            $sVideoUrlHd = '';
+            if (!empty($aVideoFile['dimensions']) && $oTcMp4Hd->isProcessHD($aVideoFile['dimensions']))
+                $sVideoUrlHd = $oTcMp4Hd->getFileUrl($aMedia['file_id']);
+
+            $aOutput[$aMedia['id']] = array(
+                'id' => $aMedia['id'],
+                'src_poster' => $oTcPoster->getFileUrl($aMedia['file_id']),
+                'src_mp4' => $oTcMp4->getFileUrl($aMedia['file_id']),
+                'src_mp4_hd' => $sVideoUrlHd,
+                'duration' => $aFileInfo['duration'],
+            );
+        }
+
+        if(empty($aOutput))
+            return array();
+
+        $iSlice = 4;
+        $iTotal = count($aOutput);
+        return array(
+            'total' => $iTotal,
+            'items' => $iTotal > $iSlice ? array_slice($aOutput, 0, $iSlice) : $aOutput
+        );
+    }
+
+    protected function _getContentForTimelineMedia($aEvent, $aContentInfo, $aBrowseParams = array())
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        $sUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]));
+        $sTitle = isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : (isset($CNF['FIELD_TEXT']) && isset($aContentInfo[$CNF['FIELD_TEXT']]) ? strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...') : '');
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        $oTcPoster = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['poster']);
+        $oTcMp4 = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4']);
+        $oTcMp4Hd = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4_hd']);
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']);
+        $oTranscoderCover = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_TRANSCODER_COVER']);
+
+    	//--- Image(s) and Video(s)
+        $aImages = $aVideos = array();
+        foreach($aEvent['content']['medias_added'] as $iMediaId) {
+            $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+            if(empty($aMediaInfo) || !is_array($aMediaInfo))
+                continue;
+
+            $aFileInfo = $oStorage->getFile($aMediaInfo['file_id']);
+            $bVideo = $oTcPoster && strncmp('video/', $aFileInfo['mime_type'], 6) === 0 && $oTcPoster->isMimeTypeSupported($aFileInfo['mime_type']);
+            if($bVideo)
+                $aVideos[$iMediaId] = array(
+                    'id' => $aMediaInfo['id'],
+                    'src_poster' => $oTcPoster->getFileUrl($aMediaInfo['file_id']),
+                    'src_mp4' => $oTcMp4->getFileUrl($aMediaInfo['file_id']),
+                    'src_mp4_hd' => $oTcMp4Hd->getFileUrl($aMediaInfo['file_id']),
+                    'duration' => $aFileInfo['duration'],
+                );
+            else
+                $aImages[$iMediaId] = array(
+                    'id' => $aMediaInfo['id'],
+                    'url' => $this->_oTemplate->getViewMediaUrl($CNF, $aMediaInfo['id']), 
+                    'src' => $oTranscoder->getFileUrl($aMediaInfo['file_id']),
+                    'src_orig' => $oTranscoderCover->getFileUrl($aMediaInfo['file_id'])
+                );
+        }
+
+    	return array(
+            'sample' => isset($CNF['T']['txt_sample_single_with_article']) ? $CNF['T']['txt_sample_single_with_article'] : $CNF['T']['txt_sample_single'],
+            'sample_wo_article' => $CNF['T']['txt_sample_single'],
+            'sample_action' => isset($CNF['T']['txt_sample_action_changed']),
+            'url' => $sUrl,
+            'title' => $sTitle,
+            'text' => '',
+            'images_attach' => $aImages,
+            'videos_attach' => $aVideos
+        );
+    }
+
+    protected function _getImagesForTimelineMedia($aEvent, $aMediaInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aImages = array();
+
+        
+
+        return $aImages;
+    }
+}
+
+/** @} */

+ 152 - 0
modules/boonex/albums/updates/14.0.4_14.0.5/source/classes/BxAlbumsSearchResult.php

@@ -0,0 +1,152 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Albums Albums
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxAlbumsSearchResult extends BxBaseModTextSearchResult
+{
+    function __construct($sMode = '', $aParams = array())
+    {
+        $this->aUnitViews = array('extended' => 'unit.html', 'gallery' => 'unit_gallery.html',);
+    
+        if(empty($aParams['unit_view'])) {
+            if(!is_array($aParams))
+                $aParams = [];
+
+            $aParams['unit_view'] = 'extended';
+        }
+
+        parent::__construct($sMode, $aParams);
+
+        $this->aCurrent = array(
+            'name' => 'bx_albums',
+            'module_name' => 'bx_albums',
+            'object_metatags' => 'bx_albums',
+            'title' => _t('_bx_albums_page_title_browse'),
+            'table' => 'bx_albums_albums',
+            'ownFields' => array('id', 'title', 'text', 'thumb', 'author', 'added'),
+            'searchFields' => array(),
+            'restriction' => array(
+                'author' => array('value' => '', 'field' => 'author', 'operator' => '='),
+        		'featured' => array('value' => '', 'field' => 'featured', 'operator' => '<>'),
+        		'status' => array('value' => 'active', 'field' => 'status', 'operator' => '='),
+        		'statusAdmin' => array('value' => 'active', 'field' => 'status_admin', 'operator' => '='),
+            ),
+            'paginate' => array('perPage' => getParam('bx_albums_per_page_browse'), 'start' => 0),
+            'sorting' => 'last',
+            'rss' => array(
+                'title' => '',
+                'link' => '',
+                'image' => '',
+                'profile' => 0,
+                'fields' => array (
+                    'Guid' => 'link',
+                    'Link' => 'link',
+                    'Title' => 'title',
+                    'DateTimeUTS' => 'added',
+                    'Desc' => 'text',
+            		'Image' => 'thumb'
+                ),
+            ),
+            'ident' => 'id',
+        );
+
+        $this->sFilterName = 'bx_albums_filter';
+        $this->oModule = $this->getMain();
+
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $sSearchFields = getParam($CNF['PARAM_SEARCHABLE_FIELDS']);
+        $this->aCurrent['searchFields'] = !empty($sSearchFields) ? explode(',', $sSearchFields) : '';
+
+        $oProfileAuthor = null;
+
+        switch ($sMode) {
+
+            case 'author':
+                if(!$this->_updateCurrentForAuthor($sMode, $aParams, $oProfileAuthor))
+                    $this->isError = true;
+                break;
+                
+            case 'context':
+                if(!$this->_updateCurrentForContext($sMode, $aParams, $oProfileAuthor))
+                    $this->isError = true;
+                break;
+
+            case 'favorite':
+                if(!$this->_updateCurrentForFavorite($sMode, $aParams, $oProfileAuthor))
+                    $this->isError = true;
+                break;
+
+            case 'public':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_HOME']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_recent');
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss/' . $sMode;
+                break;
+
+            case 'featured':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_HOME']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_featured');
+                $this->aCurrent['restriction']['featured']['value'] = '0';
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss/' . $sMode;
+                $this->aCurrent['sorting'] = 'featured';
+                break;
+
+            case 'popular':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_POPULAR']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_popular');
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss/' . $sMode;
+                $this->aCurrent['sorting'] = 'popular';
+                break;
+
+            case 'top':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_TOP']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_top');
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss/' . $sMode;
+                $this->aCurrent['sorting'] = 'top';
+                break;
+
+            case 'updated':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_UPDATED']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_updated');
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss/' . $sMode;
+                $this->aCurrent['sorting'] = 'updated';
+                break;
+
+            case '': // search results
+                $this->sBrowseUrl = BX_DOL_SEARCH_KEYWORD_PAGE;
+                $this->aCurrent['title'] = _t('_bx_albums');
+                unset($this->aCurrent['paginate']['perPage'], $this->aCurrent['rss']);
+                break;
+
+            default:
+                $sMode = '';
+                $this->isError = true;
+        }
+
+        $this->processReplaceableMarkers($oProfileAuthor);
+
+        $this->addConditionsForPrivateContent($CNF, $oProfileAuthor);
+        $this->addCustomConditions($CNF, $oProfileAuthor, $sMode, $aParams);
+    }
+
+    function displayResultBlock ()
+    {
+        $s = parent::displayResultBlock ();
+        $s = '<div class="bx-albums-wrapper">' . $s . '</div>';
+        return $this->oModule->_oTemplate->getJsCode('main') . $s;
+    }
+
+    function getDesignBoxMenu ()
+    {
+        return BxBaseModGeneralSearchResult::getDesignBoxMenu ();
+    }
+}
+
+/** @} */

+ 143 - 0
modules/boonex/albums/updates/14.0.4_14.0.5/source/install/config.php

@@ -0,0 +1,143 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Albums Albums
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_albums',
+    'title' => 'Albums',
+    'note' => 'Photos & videos',
+    'version' => '14.0.5',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/albums/',
+    'home_uri' => 'albums',
+
+    'db_prefix' => 'bx_albums_',
+    'class_prefix' => 'BxAlbums',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Albums',
+
+    /**
+     * List of page triggers.
+     */
+    'page_triggers' => array (
+    	'trigger_page_profile_view_entry',
+    ),
+
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+    	'trigger_profile_view_submenu',
+        'trigger_group_view_submenu',
+    ),
+
+	/**
+     * Storage objects to automatically delete files from upon module uninstallation.
+     * Note. Don't add storage objects used in transcoder objects.
+     */
+    'storages' => array(
+    	'bx_albums_files'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+        'bx_albums_preview',
+        'bx_albums_browse',
+        'bx_albums_big',
+        'bx_albums_video_poster_browse',
+        'bx_albums_video_poster_preview',
+        'bx_albums_video_poster_big',
+        'bx_albums_video_mp4',
+        'bx_albums_video_mp4_hd',
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_albums',
+    	'bx_albums_media',
+    	'bx_albums_cmts',
+        'bx_albums_media_cmts'
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+    	'process_esearches' => 1,
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    	'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+    	'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable_success' => array(
+        'process_menu_triggers' => 1,
+        'process_page_triggers' => 1,
+    	'process_esearches' => 1,
+    	'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+    	'update_relations' => 1,
+    	'unregister_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+    	'register_transcoders' => 1,
+    	'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Connections Section
+     */
+    'relations' => array(
+    	'bx_timeline',
+    	'bx_notifications'
+    ),
+
+);
+
+/** @} */

+ 59 - 0
modules/boonex/albums/updates/14.0.4_14.0.5/source/install/sql/disable.sql

@@ -0,0 +1,59 @@
+
+-- SETTINGS
+SET @iTypeId = (SELECT `ID` FROM `sys_options_types` WHERE `name` = 'bx_albums' LIMIT 1);
+SET @iCategId = (SELECT `ID` FROM `sys_options_categories` WHERE `type_id` = @iTypeId LIMIT 1);
+DELETE FROM `sys_options` WHERE `category_id` = @iCategId;
+DELETE FROM `sys_options_categories` WHERE `type_id` = @iTypeId;
+DELETE FROM `sys_options_types` WHERE `id` = @iTypeId;
+
+
+-- PAGES
+DELETE FROM `sys_objects_page` WHERE `module` = 'bx_albums';
+DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_albums' OR `object` IN('bx_albums_create_entry', 'bx_albums_edit_entry', 'bx_albums_add_images', 'bx_albums_delete_entry', 'bx_albums_view_entry', 'bx_albums_view_entry_comments', 'bx_albums_home', 'bx_albums_popular', 'bx_albums_top', 'bx_albums_updated', 'bx_albums_author', 'bx_albums_view_media', 'bx_albums_popular_media', 'bx_albums_top_media', 'bx_albums_context', 'bx_albums_search', 'bx_albums_manage');
+
+
+-- MENU
+DELETE FROM `sys_objects_menu` WHERE `module` = 'bx_albums';
+DELETE FROM `sys_menu_sets` WHERE `module` = 'bx_albums';
+DELETE FROM `sys_menu_items` WHERE `module` = 'bx_albums' OR `set_name` IN('bx_albums_view', 'bx_albums_view_actions' 'bx_albums_view_media', 'bx_albums_view_actions_media', 'bx_albums_view_actions_media_unit', 'bx_albums_submenu', 'bx_albums_view_submenu', 'bx_albums_snippet_meta', 'bx_albums_menu_manage_tools', 'bx_albums_my');
+
+
+-- PRIVACY 
+DELETE FROM `sys_objects_privacy` WHERE `object` IN ('bx_albums_allow_view_to', 'bx_albums_allow_view_favorite_list');
+
+
+-- ACL
+DELETE `sys_acl_actions`, `sys_acl_matrix` FROM `sys_acl_actions`, `sys_acl_matrix` WHERE `sys_acl_matrix`.`IDAction` = `sys_acl_actions`.`ID` AND `sys_acl_actions`.`Module` = 'bx_albums';
+DELETE FROM `sys_acl_actions` WHERE `Module` = 'bx_albums';
+
+
+-- SEARCH
+DELETE FROM `sys_objects_search` WHERE `ObjectName` IN ('bx_albums', 'bx_albums_cmts', 'bx_albums_media', 'bx_albums_media_camera', 'bx_albums_media_cmts');
+
+
+-- METATAGS
+DELETE FROM `sys_objects_metatags` WHERE `object` IN('bx_albums', 'bx_albums_media', 'bx_albums_media_camera');
+
+
+-- STATS
+DELETE FROM `sys_statistics` WHERE `name` LIKE 'bx_albums%';
+
+
+-- CHARTS
+DELETE FROM `sys_objects_chart` WHERE `object` LIKE 'bx_albums%';
+
+
+-- GRIDS
+DELETE FROM `sys_objects_grid` WHERE `object` IN ('bx_albums_administration', 'bx_albums_common');
+DELETE FROM `sys_grid_fields` WHERE `object` IN ('bx_albums_administration', 'bx_albums_common');
+DELETE FROM `sys_grid_actions` WHERE `object` IN ('bx_albums_administration', 'bx_albums_common');
+
+
+-- UPLOADERS
+DELETE FROM `sys_objects_uploader` WHERE `object` IN('bx_albums_html5', 'bx_albums_crop', 'bx_albums_record_video');
+
+
+-- ALERTS
+SET @iHandler := (SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = 'bx_albums' LIMIT 1);
+DELETE FROM `sys_alerts` WHERE `handler_id` = @iHandler;
+DELETE FROM `sys_alerts_handlers` WHERE `id` = @iHandler;

+ 17 - 0
modules/boonex/albums/updates/14.0.4_14.0.5/source/template/unit_gallery.html

@@ -0,0 +1,17 @@
+<div class="bx-base-text-unit bx-base-text-unit-gallery bx-def-gal-unit-size p-2">
+    <div class="bx-base-text-unit-cnt bx-def-box bx-def-box-round-corners bx-def-color-bg-box mb-3 md:mb-4 lg:mb-6 text-left">
+        <div class="bx-albums-unit-images-cont">
+            <div class="bx-albums-unit-images rounded-t-2xl overflow-hidden">
+                <bx_repeat:browse><a class="bx-albums-unit-image" title="__title_attr__" href="__url__"><img alt="__title_attr__" src="__img_url__" /></a></bx_repeat:browse>
+            </div> 
+        </div>
+        <div class="bx-albums-unit-text-cont px-4 py-2 text-center">
+            <div class="bx-base-text-unit-title">
+                <a href="__content_url__">__title__</a>
+            </div>
+            <div class="bx-base-text-unit-info">
+                <bx_if:meta><div class="bx-base-general-unit-meta">__meta__</div></bx_if:meta>
+            </div>
+        </div>
+    </div>
+</div>

+ 51 - 0
modules/boonex/analytics/updates/14.0.0_14.0.1/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Analytics',
+    'version_from' => '14.0.0',
+    'version_to' => '14.0.1',
+    'vendor' => 'BoonEx',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/analytics/updates/update_14.0.0_14.0.1/',
+    'home_uri' => 'analytics_update_1400_1401',
+
+    'module_dir' => 'boonex/analytics/',
+    'module_uri' => 'analytics',
+
+    'db_prefix' => 'bx_analytics_',
+    'class_prefix' => 'BxAnalytics',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Analytics',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/analytics/updates/14.0.0_14.0.1/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxAnalyticsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 77 - 0
modules/boonex/analytics/updates/14.0.0_14.0.1/source/install/config.php

@@ -0,0 +1,77 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Analytics Analytics
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_analytics',
+    'title' => 'Analytics',
+    'note' => 'Analytics module.',
+    'version' => '14.0.1',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/analytics/',
+    'home_uri' => 'analytics',
+
+    'db_prefix' => 'bx_analytics_',
+    'class_prefix' => 'BxAnalytics',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Analytics',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable_success' => array(
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+);
+
+/** @} */

+ 52 - 0
modules/boonex/analytics/updates/14.0.0_14.0.1/source/plugins/datatables/package.json

@@ -0,0 +1,52 @@
+{
+	"name": "datatables",
+	"version": "1.10.20",
+	"title": "DataTables",
+	"main": "media/js/jquery.dataTables",
+	"files": [
+		"media/js/jquery.dataTables.js",
+		"media/js/jquery.dataTables.min.js",
+		"media/css/jquery.dataTables.css",
+		"media/css/jquery.dataTables.min.css",
+		"media/images"
+	],
+	"license": "MIT",
+	"dependencies": {
+		"jquery": ">=1.7"
+	},
+	"author": {
+		"name": "Allan Jardine",
+		"url": "http://sprymedia.co.uk"
+	},
+	"description": "DataTables enhances HTML tables with the ability to sort, filter and page the data in the table very easily. It provides a comprehensive API and set of configuration options, allowing you to consume data from virtually any data source.",
+	"keywords": [
+		"DataTables",
+		"DataTable",
+		"table",
+		"grid",
+		"filter",
+		"sort",
+		"page",
+		"internationalisable",
+		"jquery-plugin"
+	],
+	"repository": {
+		"type": "git",
+		"url": "https://github.com/DataTables/DataTables.git"
+	},
+	"homepage": "http://datatables.net",
+
+	"jspm": {
+		"dependencies": {
+			"css": "^0.1.5",
+			"jquery": "*"
+		},
+		"registry": "jspm",
+		"shim": {
+			"media/js/jquery.dataTables": {
+				"deps": ["jquery", "../css/jquery.dataTables.css!"],
+				"exports": "$"
+			}
+		}
+	}
+}

+ 17 - 0
modules/boonex/analytics/updates/14.0.0_14.0.1/source/plugins/daterangepicker/package.js

@@ -0,0 +1,17 @@
+Package.describe({
+  name: 'dangrossman:bootstrap-daterangepicker',
+  version: '3.0.5',
+  summary: 'Date range picker component',
+  git: 'https://github.com/dangrossman/daterangepicker',
+  documentation: 'README.md'
+});
+
+Package.onUse(function(api) {
+  api.versionsFrom('METEOR@0.9.0.1');
+
+  api.use('momentjs:moment@2.22.1', ["client"]);
+  api.use('jquery@3.3.1', ["client"]);
+
+  api.addFiles('daterangepicker.js', ["client"]);
+  api.addFiles('daterangepicker.css', ["client"]);
+});

+ 51 - 0
modules/boonex/antispam/updates/14.0.1_14.0.2/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Antispam',
+    'version_from' => '14.0.1',
+    'version_to' => '14.0.2',
+    'vendor' => 'BoonEx',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/antispam/updates/update_14.0.1_14.0.2/',
+    'home_uri' => 'antispam_update_1401_1402',
+
+    'module_dir' => 'boonex/antispam/',
+    'module_uri' => 'antispam',
+
+    'db_prefix' => 'bx_antispam_',
+    'class_prefix' => 'BxAntispam',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Antispam',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/antispam/updates/14.0.1_14.0.2/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxAntispamUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 635 - 0
modules/boonex/antispam/updates/14.0.1_14.0.2/source/classes/BxAntispamModule.php

@@ -0,0 +1,635 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Antispam Antispam
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxAntispamModule extends BxDolModule
+{
+    private $_bLastSubmittedFormWasToxic;
+
+    public function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+        $this->_bLastSubmittedFormWasToxic = false;
+    }
+
+    public function actionContentChecked()
+    {
+        $iResult = 404;
+
+        if($this->_oConfig->getAntispamOption('lm_enable') == 'on') {
+            $oLassoModeration = bx_instance('BxAntispamLassoModeration', [], $this->getName());
+            $iResult = $oLassoModeration->processEvent();    
+        }
+
+        http_response_code($iResult);
+    }
+
+    public function serviceUpdateDisposableDomainsLists ()
+    {
+        $o = bx_instance('BxAntispamDisposableEmailDomains', array(), 'bx_antispam');
+
+        $o->updateList('blacklist', 'https://raw.githubusercontent.com/martenson/disposable-email-domains/master/disposable_email_blocklist.conf');
+
+        // TODO: uncomment after adding interface for whitelisting
+        // $o->updateList('whitelist', 'https://raw.githubusercontent.com/martenson/disposable-email-domains/master/whitelist.conf');
+    }
+    
+    public function serviceIpTable ()
+    {
+        $o = bx_instance('BxAntispamIP', array(), $this->_aModule);
+        $s = _t('_bx_antispam_ip_table_status', mb_strtolower($o->getIpTableConfigTitle((int)getParam('bx_antispam_ip_list_type'))));
+        $s .= $this->_grid('bx_antispam_grid_ip_table');
+        return $s;
+    }
+
+    public function serviceDnsblList ()
+    {
+        $s = _t('_bx_antispam_dnsbl_status',
+             BxTemplFunctions::getInstance()->statusOnOff((bool)$this->_oConfig->getAntispamOption('dnsbl_enable'), true),
+             BxTemplFunctions::getInstance()->statusOnOff((bool)$this->_oConfig->getAntispamOption('uridnsbl_enable'), true),
+             mb_strtolower(_t('_bx_antispam_dnsbl_behaviour_login_' . $this->_oConfig->getAntispamOption('dnsbl_behaviour_login'))),
+             mb_strtolower(_t('_bx_antispam_dnsbl_behaviour_join_' . $this->_oConfig->getAntispamOption('dnsbl_behaviour_join')))
+        );
+        $s .= $this->_grid('bx_antispam_grid_dnsbl');
+        return $s;
+    }
+
+    public function serviceBlockLog ()
+    {
+        return $this->_grid('bx_antispam_grid_block_log');
+    }
+
+    /**
+     * Filter undesired words
+     *
+     * @param $sContent content to filter
+     * @param $sIp IP address of content poster
+     * @return modified or the same content.
+     */
+    public function serviceFilterSpam ($mContent, $sIp = '')
+    {      
+        if (defined('BX_DOL_CRON_EXECUTE') || isAdmin() || 'on' != $this->_oConfig->getAntispamOption('profanity_enable'))
+            return $mContent;
+        
+        $oProfanityFilter = bx_instance('BxAntispamProfanityFilter', array(), $this->_aModule);
+        return $oProfanityFilter->censor($mContent); 
+    }
+    
+    /**
+     * Check text for spam URL.
+     * It checks URLs found in text for DNSURI black lists (@see BxAntispamDNSURIBlacklists),
+     *
+     * @param $sContent content to check for spam
+     * @return true if spam detected, false if spam isn't detected, null if detection wasn't performed.
+     */
+    public function serviceIsSpamUrl ($sContent)
+    {
+        $bRet = null;
+        if ('on' == $this->_oConfig->getAntispamOption('uridnsbl_enable')) {
+            $oDNSURIBlacklists = bx_instance('BxAntispamDNSURIBlacklists', array(), $this->_aModule);
+            if ($oDNSURIBlacklists->isSpam($sContent)) {
+                $oDNSURIBlacklists->onPositiveDetection($sContent);
+                $bRet = true;
+            } 
+            else {
+                $bRet = false;
+            }
+        }
+        return $bRet;
+    }
+
+    /**
+     * Check text for spam.
+     * First it check if IP is whitelisted(or under cron execution or user is admin) - for whitelisted IPs check for spam isn't performed,
+     * then it checks URLs found in text for DNSURI black lists (@see BxAntispamDNSURIBlacklists),
+     * then it checks text in Akismet service (@see BxAntispamAkismet).
+     * It can send report if spam is found or tries to inform caller to block the content (depending on configuration).
+     *
+     * @param $sContent content to check for spam
+     * @param $sIp IP address of content poster
+     * @param $isStripSlashes slashes parameter:
+     *          BX_SLASHES_AUTO - automatically detect magic_quotes_gpc setting
+     *          BX_SLASHES_NO_ACTION - do not perform any action with slashes
+     * @return true if spam detected and content shouln't be recorded, false if content should be processed as usual.
+     */
+    public function serviceIsSpam ($sContent, $sIp = '', $isStripSlashes = BX_SLASHES_AUTO)
+    {      
+        if (defined('BX_DOL_CRON_EXECUTE') || isAdmin())
+            return false;
+
+        if ($this->serviceIsIpWhitelisted($sIp))
+            return false;
+
+        if (version_compare(phpversion(), '7.4.0', '<') && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() && $isStripSlashes == BX_SLASHES_AUTO)
+            $sContent = stripslashes($sContent);
+        
+        $bRet = false;
+        if ('on' == $this->_oConfig->getAntispamOption('uridnsbl_enable')) {
+            $oDNSURIBlacklists = bx_instance('BxAntispamDNSURIBlacklists', array(), $this->_aModule);
+            if ($oDNSURIBlacklists->isSpam($sContent)) {
+                $oDNSURIBlacklists->onPositiveDetection($sContent);
+                $bRet = true;
+            }
+        }
+
+        if (!$bRet && 'on' == $this->_oConfig->getAntispamOption('akismet_enable')) {
+            $oAkismet = bx_instance('BxAntispamAkismet', array(), $this->_aModule);
+            if ($oAkismet->isSpam($sContent)) {
+                $oAkismet->onPositiveDetection($sContent);
+                $bRet = true;
+            }
+        }
+
+        if ($bRet && 'on' == $this->_oConfig->getAntispamOption('antispam_report')) {
+
+            $oProfile = BxDolProfile::getInstance();
+            $aPlus = array(
+                'SpammerUrl' => $oProfile->getUrl(),
+                'SpammerNickName' => $oProfile->getDisplayName(),
+                'Page' => htmlspecialchars_adv($_SERVER['PHP_SELF']),
+                'Get' => print_r($_GET, true),
+                'Post' => print_r($_POST, true),
+                'SpamContent' => htmlspecialchars_adv($sContent),
+            );
+
+            $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('bx_antispam_spam_report', $aPlus);
+            if (!$aTemplate)
+                trigger_error('Email template or translation missing: bx_antispam_spam_report', E_USER_ERROR);
+
+            sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body']);
+        }
+
+        if ($bRet && 'on' == $this->_oConfig->getAntispamOption('antispam_block'))
+            return true;
+
+        return false;
+    }
+
+    /**
+     * Check text for toxicity.
+     * It can send report if toxic content is found or tries to inform caller to block the content (depending on configuration).
+     *
+     * @param $sContent content to check for spam
+     * @param $sIp IP address of content poster
+     * @param $isStripSlashes slashes parameter:
+     *          BX_SLASHES_AUTO - automatically detect magic_quotes_gpc setting
+     *          BX_SLASHES_NO_ACTION - do not perform any action with slashes
+     * @return true if toxic content detected and content shouln't be recorded, false if content should be processed as usual.
+     */
+    public function serviceIsToxic ($sContent, $sIp = '', $isStripSlashes = BX_SLASHES_AUTO, $bSupressNotification = false)
+    {
+        if (defined('BX_DOL_CRON_EXECUTE') || isAdmin())
+            return false;
+
+        static $aQuickCache;
+        $key = md5($sContent);
+        if (isset($aQuickCache[$key]))
+            return $aQuickCache[$key];
+
+        $aQuickCache[$key] = false;
+
+        if (!$this->serviceIsIpWhitelisted($sIp)) {
+            if (version_compare(phpversion(), '7.4.0', '<') && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() && $isStripSlashes == BX_SLASHES_AUTO)
+                $sContent = stripslashes($sContent);
+
+            if ('on' == $this->_oConfig->getAntispamOption('toxicity_enable')) {
+                $oPerspectiveAPI = bx_instance('BxAntispamPerspectiveAPI', array(), $this->_aModule);
+                if ($oPerspectiveAPI->isToxic($sContent)) {
+                    $oPerspectiveAPI->onPositiveDetection($sContent);
+                    $aQuickCache[$key] = true;
+
+                    if (!$bSupressNotification) $this->serviceOnToxicContentFound($sContent);
+                }
+            }
+        }
+
+        return $aQuickCache[$key];
+    }
+
+    public function serviceCheckFormForToxicity(&$oForm) {
+        $this->_bLastSubmittedFormWasToxic = false;
+
+        if (!$oForm->isValid() || !$oForm->isSubmitted()) return false;
+
+        $sSubmitName = false;
+        $sFormText = '';
+        foreach ($oForm->aInputs as $k => $a) {
+            if (isset($a['visible_for_levels']) && !BxDolForm::isVisible($a))
+                continue;
+
+            if (empty($a['name']) || 'submit' == $a['type'] || 'reset' == $a['type'] || 'button' == $a['type'] || 'value' == $a['type']) {
+                if (isset($a['type']) && 'submit' == $a['type'])
+                    $sSubmitName = $k;
+                continue;
+            }
+
+            if ('input_set' == $a['type'])
+                foreach ($a as $r)
+                    if (isset($r['type']) && 'submit' == $r['type'])
+                        $sSubmitName = $k;
+
+            $a['name'] = str_replace('[]', '', $a['name']);
+
+            if ($a['type'] != 'textarea' && $a['type'] != 'text')
+                continue;
+
+            $val = BxDolForm::getSubmittedValue($a['name'], $oForm->aFormAttrs['method']);
+            if (!$val)
+                continue;
+
+            $sFormText .= " ".$val; // collect all text fields into a single text
+        }
+
+        if ($this->serviceIsToxic($sFormText, '', BX_SLASHES_AUTO, true)) {
+            $this->_bLastSubmittedFormWasToxic = true;
+
+            $sActionrequired = $this->_oConfig->getAntispamOption('toxicity_action');
+
+            if ($sActionrequired == 'block' || $sActionrequired == 'disapprove' && !$oForm->isStatusFieldSupported()) {
+                $oForm->setValid(false);
+                $this->serviceOnToxicContentFound($sFormText);
+
+                if ($sSubmitName)
+                    $oForm->aInputs[$sSubmitName]['error'] = _t('_bx_antispam_form_submission_error');
+            } elseif ($sActionrequired == 'disapprove' && $oForm->isStatusFieldSupported()) {
+                $oForm->setForceSetToPending(true);
+                return false;
+            }
+        }
+    }
+
+    public function serviceIsLastFormSubmittedToxic() {
+        return $this->_bLastSubmittedFormWasToxic;
+    }
+
+    public function serviceSetCommentAsPending($iCmtId) {
+        return $this->_oDb->setCommentStatus($iCmtId, BX_CMT_STATUS_PENDING);
+    }
+
+    public function serviceOnFormSubmitted($sModule, $iEntry, $sAction, &$oForm)
+    {
+        if ($this->_bLastSubmittedFormWasToxic) {
+            $this->serviceOnToxicContentPosted($sModule, $iEntry);
+        }
+
+        if($this->_oConfig->getAntispamOption('lm_enable') == 'on' && (bx_srv('system', 'is_module_content', [$sModule]) || bx_srv('system', 'is_module_context', [$sModule]) || in_array($sModule, ['bx_timeline'])) && ($oModule = BxDolModule::getInstance($sModule)) !== null) {
+            $CNF = &$oModule->_oConfig->CNF;
+
+            if(($iAuthorId = bx_get_logged_profile_id()) !== false && ($oAuthor = BxDolProfile::getInstance()) !== false) {
+                $iDataAdded = time();
+
+                $sText = '';
+                if(isset($CNF['FIELD_TEXT'], $oForm->aInputs[$CNF['FIELD_TEXT']]))
+                    $sText = $oForm->getCleanValue($CNF['FIELD_TEXT']);
+                if(empty($sText) && isset($oForm->aInputs['text']))
+                    $sText = $oForm->getCleanValue('text');
+
+                $aMedias = [
+                    'images' => ['fields' => ['FIELD_PHOTO', 'FIELD_PICTURE', 'FIELD_COVER'], 'data' => []], 
+                    'videos' => ['fields' => ['FIELD_VIDEO'], 'data' => []]
+                ];
+                foreach($aMedias as $sName => $aMedia) 
+                    foreach($aMedia['fields'] as $sField) {
+                        if(!isset($CNF[$sField], $oForm->aInputs[$CNF[$sField]]))
+                            continue;
+
+                        $oStorage = BxDolStorage::getObjectInstance($oForm->aInputs[$CNF[$sField]]['storage_object']);
+                        if(!$oStorage)
+                            continue;
+
+                        $aGhosts = $oStorage->getGhosts($iAuthorId, $sAction == 'insert' ? 0 : $iEntry);
+                        foreach($aGhosts as $aGhost)
+                            $aMedias[$sName]['data'][] = $oStorage->getFileUrlById($aGhost['id']);
+                    }
+
+                $oLassoModeration = bx_instance('BxAntispamLassoModeration', [], $this->getName());
+                $oLassoModeration->addContent($sModule, $iEntry, [
+                    'author_id' => $iAuthorId,
+                    'author_name' => $oAuthor->getDisplayName(),
+                    'date_added' => $iDataAdded,
+                    'text' => $sText,
+                    'images' => $aMedias['images']['data'], 
+                    'videos' => $aMedias['videos']['data']
+                ]);
+            }
+        }
+    }
+
+    public function serviceOnToxicContentFound(&$sText) {
+        if ('on' == $this->_oConfig->getAntispamOption('toxicity_report')) {
+            $oProfile = BxDolProfile::getInstance();
+            if (!$oProfile) return;
+            $aPlus = array(
+                'AuthorUrl' => $oProfile->getUrl(),
+                'AuthorNickName' => $oProfile->getDisplayName(),
+                'Page' => htmlspecialchars_adv($_SERVER['PHP_SELF']),
+                'Content' => htmlspecialchars_adv(strip_tags($sText)),
+            );
+
+            $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('bx_antispam_toxicity_blocked_report', $aPlus);
+            if (!$aTemplate)
+                trigger_error('Email template or translation missing: bx_antispam_toxicity_blocked_report', E_USER_ERROR);
+
+            sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body']);
+
+            bx_alert('bx_antispam', 'toxic_content_found', 0, bx_get_logged_profile_id(), array(
+                'text' => $sText,
+            ));
+        }
+    }
+
+    public function serviceOnToxicContentPosted($sModule, $iContentId) {
+        if ('on' == $this->_oConfig->getAntispamOption('toxicity_report') && $sModule) {
+            $oModule = BxDolModule::getInstance($sModule);
+            $CNF = &$oModule->_oConfig->CNF;
+            $sContentUrl = isset($CNF['URI_VIEW_ENTRY']) ? bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId)) : false;
+            $sManageContentUrl = isset($CNF['URL_MANAGE_ADMINISTRATION']) ? bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URL_MANAGE_ADMINISTRATION'])) : false;
+
+            $oProfile = BxDolProfile::getInstance();
+            if (!$oProfile) return;
+
+            $aPlus = array(
+                'AuthorUrl' => $oProfile->getUrl(),
+                'AuthorNickName' => $oProfile->getDisplayName(),
+                'Page' => htmlspecialchars_adv($_SERVER['PHP_SELF']),
+                'bx_if:content_url' => [
+                    'condition' => boolval($sContentUrl),
+                    'content' => ['c_url' => $sContentUrl],
+                ],
+                'bx_if:manage_content_url' => [
+                    'condition' => boolval($sManageContentUrl),
+                    'content' => ['m_url' => $sManageContentUrl],
+                ],
+            );
+
+            $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('bx_antispam_toxicity_posted_report', $aPlus);
+            if (!$aTemplate)
+                trigger_error('Email template or translation missing: bx_antispam_toxicity_posted_report', E_USER_ERROR);
+
+            sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body']);
+
+            bx_alert('bx_antispam', 'toxic_content_posted', $iContentId, bx_get_logged_profile_id(), array(
+                'module' => $sModule,
+                'entry_id' => $iContentId,
+                'entry_url' => $sContentUrl,
+            ));
+        }
+    }
+
+    /**
+     * Perform complex check if user is allowed to login.
+     * First it checks if IP is directly blocked (@see serviceIsIpBlocked),
+     * then it checks in DNS black lists (@see serviceIsIpDnsBlacklisted).
+     *
+     * @param $sIp IP to check, or empty for current IP
+     * @return empty string - if join should be allowed, error message - if join should be blocked
+     */
+    public function serviceCheckLogin ($sIp = '')
+    {
+        $bLoginBlock = ('block' == $this->_oConfig->getAntispamOption('dnsbl_behaviour_login'));
+        $sErrorMsg = '';
+        $sNote = $bLoginBlock ? 'login block' : 'login log';
+
+        if (!$sIp)
+            $sIp = getVisitorIP();
+
+        if (!$sErrorMsg && $this->serviceIsIpBlocked($sIp))
+            $sErrorMsg = $this->getErrorMessageIpBlocked();
+
+        if (!$sErrorMsg && 'on' == $this->_oConfig->getAntispamOption('dnsbl_enable') && $this->serviceIsIpDnsBlacklisted($sIp, $sNote) && $bLoginBlock)
+            $sErrorMsg = $this->getErrorMessageSpam();
+
+        return $sErrorMsg;
+    }
+
+    /**
+     * Perform complex check if user is allowed to join.
+     * First it checks if IP is directly blocked (@see serviceIsIpBlocked),
+     * then it checks in DNS black lists (@see serviceIsIpDnsBlacklisted),
+     * then it checks in StopForumSpam service (@see BxAntispamStopForumSpam).
+     *
+     * @param $sCurIP IP to check, or empty for current IP
+     * @return empty string - if join should be allowed, error message - if join should be blocked
+     */
+    public function serviceCheckJoin ($sEmail, &$bApproval, $sIp = '')
+    {
+        $bJoinBlock = ('block' == $this->_oConfig->getAntispamOption('dnsbl_behaviour_join'));
+        $bApproval = false;
+        $sErrorMsg = '';
+        $sNote = $bJoinBlock ? 'join block' : 'join approval';
+
+        if (!$sIp)
+            $sIp = getVisitorIP();
+
+        // check if IP is blocked
+        if (!$sErrorMsg && $this->serviceIsIpBlocked($sIp))
+            $sErrorMsg = $this->getErrorMessageIpBlocked();
+
+        // check in DNSBL lists
+        if (!$sErrorMsg && 'on' == $this->_oConfig->getAntispamOption('dnsbl_enable') && $this->serviceIsIpDnsBlacklisted($sIp, $sNote)) {
+            if ('approval' == $this->_oConfig->getAntispamOption('dnsbl_behaviour_join'))
+                $bApproval = true;
+            else
+                $sErrorMsg = $this->getErrorMessageSpam();
+        }
+
+        // check in StopForumSpam service
+        if (!$sErrorMsg) {
+            $oStopForumSpam = bx_instance('BxAntispamStopForumSpam', array(), $this->_aModule);
+            if ($oStopForumSpam->isSpammer(array('email' => $sEmail, 'ip' => $sIp), $sNote))
+                $sErrorMsg = $this->getErrorMessageSpam();
+        }
+
+        // check for disposable email domains
+        if (!$sErrorMsg && 'disable' != ($sMode = $this->_oConfig->getAntispamOption('disposable_email_domains_mode'))) {
+
+            $bDisposableEmailDomainsBehaviour = $this->_oConfig->getAntispamOption('disposable_email_domains_behaviour_join');
+            $oDisposableEmailDomains = bx_instance('BxAntispamDisposableEmailDomains', array(), 'bx_antispam');
+            
+            if ('blacklist' == $sMode && $oDisposableEmailDomains->isBlacklisted($sEmail)) {
+                if ('approval' == $bDisposableEmailDomainsBehaviour)
+                    $bApproval = true;
+                else
+                    $sErrorMsg = $oDisposableEmailDomains->getErrorMessageBlacklisted();
+            }
+            elseif ('whitelist' == $sMode && !$oDisposableEmailDomains->isWhitelisted($sEmail)) {
+                if ('approval' == $bDisposableEmailDomainsBehaviour)
+                    $bApproval = true;
+                else
+                    $sErrorMsg = $oDisposableEmailDomains->getErrorMessageNotWhitelisted();
+            }
+
+        }
+
+        return $sErrorMsg;
+    }
+
+    /**
+     * Check if IP is blacklisted in some DNS chain (@see BxAntispamDNSBlacklists).
+     *
+     * @param $sCurIP IP to check, or empty for current IP
+     * @param $sNote [optional] place where checking is performed, for example 'join', 'login'
+     * @return true if IP blacklisted and not whiteloisted, or false if under cron execution or if IP isn't blacklisted
+     */
+    public function serviceIsIpDnsBlacklisted($sCurIP = '', $sNote = '')
+    {
+        if (defined('BX_DOL_CRON_EXECUTE'))
+            return false;
+
+        if (!$sCurIP)
+            $sCurIP = getVisitorIP();
+
+        if ($this->serviceIsIpWhitelisted($sCurIP))
+            return false;
+
+        $o = bx_instance('BxAntispamDNSBlacklists', array(), $this->_aModule);
+
+        if (BX_DOL_DNSBL_POSITIVE == $o->dnsbl_lookup_ip(BX_DOL_DNSBL_CHAIN_SPAMMERS, $sCurIP) && BX_DOL_DNSBL_POSITIVE != $o->dnsbl_lookup_ip(BX_DOL_DNSBL_CHAIN_WHITELIST, $sCurIP)) {
+            $o->onPositiveDetection ($sCurIP, $sNote);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @see BxAntispamIP::isIpWhitelisted
+     */
+    public function serviceIsIpWhitelisted($sIp = '')
+    {
+        $o = bx_instance('BxAntispamIP', array(), $this->_aModule);
+        return $o->isIpWhitelisted($sIp);
+    }
+
+    /**
+     * @see BxAntispamIP::isIpBlocked
+     */
+    public function serviceIsIpBlocked($sIp = '')
+    {
+        $o = bx_instance('BxAntispamIP', array(), $this->_aModule);
+        return $o->isIpBlocked($sIp);
+    }
+
+    /**
+     * @see BxAntispamIP::blockIp
+     */
+    public function serviceBlockIp($mixedIP, $iExpirationInSec = 86400, $sComment = '')
+    {
+        $o = bx_instance('BxAntispamIP', array(), $this->_aModule);
+        return $o->blockIp($mixedIP, $iExpirationInSec, $sComment);
+    }
+
+    /**
+     * @see BxAntispamIP::pruning
+     */
+    public function servicePruning()
+    {
+        $o = bx_instance('BxAntispamIP', array(), $this->_aModule);
+        return $o->pruning();
+    }
+
+    /**
+     * @see BxAntispamIP::pruning
+     */
+    public function serviceConfigValues($s)
+    {
+        switch ($s) {
+            case 'ip_table':
+                $o = bx_instance('BxAntispamIP', array(), $this->_aModule);
+                return $o->getIpTableConfigValues();
+            case 'dnsbl_login':
+                $o = bx_instance('BxAntispamDNSBlacklists', array(), $this->_aModule);
+                return $o->getDNSBLConfigValues();
+            case 'dnsbl_join';
+                $o = bx_instance('BxAntispamDNSURIBlacklists', array(), $this->_aModule);
+                return $o->getURIDNSBLConfigValues();
+            case 'disposable_email_domains_join';
+                $o = bx_instance('BxAntispamDisposableEmailDomains', array(), $this->_aModule);
+                return $o->getJoinBehaviourValues();                
+            case 'disposable_email_domains_mode';
+                $o = bx_instance('BxAntispamDisposableEmailDomains', array(), $this->_aModule);
+                return $o->getJoinBehaviourModes();                
+        }
+    }
+    
+    /**
+     * @return array with avaliable dictionaries languages
+     */
+    public function serviceGetProfanityFilterDicts ()
+    {
+        $oProfanityFilter = bx_instance('BxAntispamProfanityFilter', array(), $this->_aModule);
+        return $oProfanityFilter->getDicts(); 
+    }
+
+    /**
+     * @return array with avaliable toxicity filter actions
+     */
+    public function serviceGetToxicityFilterActions ()
+    {
+        return [
+            'none' => _t('_bx_antispam_option_toxicity_filter_action_none'),
+            'block' => _t('_bx_antispam_option_toxicity_filter_action_block'),
+            'disapprove' => _t('_bx_antispam_option_toxicity_filter_action_disapprove'),
+        ];
+    }
+
+    /**
+     * @return array with avaliable Lasso Moderation actions
+     */
+    public function serviceGetLassoModerationActions ()
+    {
+        return [
+            'none' => _t('_bx_antispam_option_lasso_moderation_action_none'),
+            'disapprove' => _t('_bx_antispam_option_lasso_moderation_action_disapprove'),
+        ];
+    }
+
+    public function log($mixedContents, $sSection = '', $sTitle = '')
+    {
+        if(is_array($mixedContents))
+            $mixedContents = var_export($mixedContents, true);	
+        else if(is_object($mixedContents))
+            $mixedContents = json_encode($mixedContents);
+
+        if(empty($sSection))
+            $sSection = "Core";
+
+        $sTitle .= "\n";
+
+        bx_log('bx_antispam', ":\n[" . $sSection . "] " . $sTitle . $mixedContents);
+    }
+
+    protected function getErrorMessageIpBlocked ()
+    {
+        bx_import('BxDolLanguages');
+        return _t('_bx_antispam_ip_blocked', $this->getErrorMessageSubmitFalsePositiveReport());
+    }
+
+    protected function getErrorMessageSpam ()
+    {
+        bx_import('BxDolLanguages');
+        return _t('_bx_antispam_spam_detected', $this->getErrorMessageSubmitFalsePositiveReport());
+    }
+
+    protected function getErrorMessageSubmitFalsePositiveReport ()
+    {
+        if (BxDolRequest::serviceExists('bx_contact', 'get_contact_page_url') && ($sUrl = BxDolService::call('bx_contact', 'get_contact_page_url')))
+            return _t('_bx_antispam_submit_false_positive_report', $sUrl);
+        return '';
+    }
+
+    protected function _grid ($sObjectGrid)
+    {
+        $oGrid = BxDolGrid::getObjectInstance($sObjectGrid);
+        return $oGrid ? $oGrid->getCode() : '';
+    }
+}
+
+/** @} */

+ 68 - 0
modules/boonex/antispam/updates/14.0.1_14.0.2/source/install/config.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Antispam Antispam
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_antispam',
+    'title' => 'Antispam',
+    'note' => 'Antispam tools',
+    'version' => '14.0.2',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/antispam/',
+    'home_uri' => 'antispam',
+
+    'db_prefix' => 'bx_antispam_',
+    'class_prefix' => 'BxAntispam',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Antispam',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array(
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+);
+
+/** @} */

+ 27 - 0
modules/boonex/antispam/updates/14.0.1_14.0.2/source/template/images/icons/std-icon.svg

@@ -0,0 +1,27 @@
+<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="80" height="80" fill="url(#paint0_linear_1559_24457)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M61 43.6962C61 57.194 51.8125 63.9429 40.8925 67.8572C40.3207 68.0565 39.6995 68.0469 39.1338 67.8302C28.1875 63.9429 19 57.194 19 43.6962V24.7994C19 24.0834 19.2766 23.3968 19.7688 22.8905C20.2611 22.3843 20.9288 22.0999 21.625 22.0999C26.875 22.0999 33.4375 18.8604 38.005 14.7571C38.5611 14.2685 39.2686 14 40 14C40.7314 14 41.4389 14.2685 41.995 14.7571C46.5888 18.8874 53.125 22.0999 58.375 22.0999C59.0712 22.0999 59.7389 22.3843 60.2312 22.8905C60.7234 23.3968 61 24.0834 61 24.7994V43.6962ZM33 30C33 26.134 36.134 23 40 23C43.866 23 47 26.134 47 30V38C47 41.866 43.866 45 40 45C36.134 45 33 41.866 33 38V30ZM40 47C36.134 47 33 50.134 33 54C33 57.866 36.134 61 40 61C43.866 61 47 57.866 47 54C47 50.134 43.866 47 40 47Z" fill="black" fill-opacity="0.2"/>
+<path d="M44 54C44 56.2091 42.2091 58 40 58C37.7909 58 36 56.2091 36 54C36 51.7909 37.7909 50 40 50C42.2091 50 44 51.7909 44 54Z" fill="black" fill-opacity="0.2"/>
+<path d="M40 42C37.7909 42 36 40.2091 36 38V30C36 27.7909 37.7909 26 40 26C42.2091 26 44 27.7909 44 30V38C44 40.2091 42.2091 42 40 42Z" fill="black" fill-opacity="0.2"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M61 42.6962C61 56.194 51.8125 62.9429 40.8925 66.8572C40.3207 67.0565 39.6995 67.0469 39.1338 66.8302C28.1875 62.9429 19 56.194 19 42.6962V23.7994C19 23.0834 19.2766 22.3968 19.7688 21.8905C20.2611 21.3843 20.9288 21.0999 21.625 21.0999C26.875 21.0999 33.4375 17.8604 38.005 13.7571C38.5611 13.2685 39.2686 13 40 13C40.7314 13 41.4389 13.2685 41.995 13.7571C46.5888 17.8874 53.125 21.0999 58.375 21.0999C59.0712 21.0999 59.7389 21.3843 60.2312 21.8905C60.7234 22.3968 61 23.0834 61 23.7994V42.6962ZM33 29C33 25.134 36.134 22 40 22C43.866 22 47 25.134 47 29V37C47 40.866 43.866 44 40 44C36.134 44 33 40.866 33 37V29ZM40 46C36.134 46 33 49.134 33 53C33 56.866 36.134 60 40 60C43.866 60 47 56.866 47 53C47 49.134 43.866 46 40 46Z" fill="url(#paint1_linear_1559_24457)"/>
+<circle cx="40" cy="53" r="4" fill="url(#paint2_linear_1559_24457)"/>
+<path d="M40 41C37.7909 41 36 39.2091 36 37L36 29C36 26.7909 37.7909 25 40 25C42.2091 25 44 26.7909 44 29V37C44 39.2091 42.2091 41 40 41Z" fill="url(#paint3_linear_1559_24457)"/>
+<defs>
+<linearGradient id="paint0_linear_1559_24457" x1="40" y1="0" x2="40" y2="80" gradientUnits="userSpaceOnUse">
+<stop stop-color="#6B7280"/>
+<stop offset="1" stop-color="#1F2937"/>
+</linearGradient>
+<linearGradient id="paint1_linear_1559_24457" x1="40" y1="13" x2="40" y2="67" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F9FAFB"/>
+<stop offset="1" stop-color="#9CA3AF"/>
+</linearGradient>
+<linearGradient id="paint2_linear_1559_24457" x1="40" y1="49" x2="40" y2="57" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FDE68A"/>
+<stop offset="1" stop-color="#D97706"/>
+</linearGradient>
+<linearGradient id="paint3_linear_1559_24457" x1="40" y1="25" x2="40" y2="41" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FDE68A"/>
+<stop offset="1" stop-color="#D97706"/>
+</linearGradient>
+</defs>
+</svg>

+ 51 - 0
modules/boonex/channels/updates/14.0.4_14.0.5/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Channels',
+    'version_from' => '14.0.4',
+    'version_to' => '14.0.5',
+    'vendor' => 'BoonEx',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/channels/updates/update_14.0.4_14.0.5/',
+    'home_uri' => 'channels_update_1404_1405',
+
+    'module_dir' => 'boonex/channels/',
+    'module_uri' => 'channels',
+
+    'db_prefix' => 'bx_cnl_',
+    'class_prefix' => 'BxCnl',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Channels',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/channels/updates/14.0.4_14.0.5/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxCnlUpdater extends BxDolStudioUpdater
+{
+    public function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 4 - 0
modules/boonex/channels/updates/14.0.4_14.0.5/install/langs/en.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string_add name="_bx_channels_alert_action_info_hashtag_added_notif_follow_context"><![CDATA[New hashtags were added or used in a space you follow.]]></string_add>
+</resources>

+ 153 - 0
modules/boonex/channels/updates/14.0.4_14.0.5/source/install/config.php

@@ -0,0 +1,153 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defdroup    Channels Channels
+ * @indroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_channels',
+    'title' => 'Channels',
+    'note' => 'Channels functionality.',
+    'version' => '14.0.5',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use channels in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/channels/',
+    'home_uri' => 'channels',
+
+    'db_prefix' => 'bx_cnl_',
+    'class_prefix' => 'BxCnl',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Channels',
+
+    /**
+     * Connections.
+     */
+    'connections' => array(
+        'sys_profiles_friends' => array ('type' => 'profiles'),
+        'sys_profiles_subscriptions' => array ('type' => 'profiles'),
+    ),
+
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+        'trigger_profile_view_submenu',
+        'trigger_group_view_submenu',
+        'trigger_group_snippet_meta',
+        'trigger_group_view_actions',
+    ),
+
+    /**
+     * Page triggers.
+     */
+    'page_triggers' => array (
+        'trigger_page_profile_view_entry',
+        'trigger_page_group_view_entry',
+    ),
+
+    /**
+     * Storage objects to automatically delete files from upon module uninstallation.
+     * Note. Don't add storage objects used in transcoder objects.
+     */
+    'storages' => array(
+        'bx_channels_pics'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+        'bx_channels_icon',
+        'bx_channels_thumb',
+        'bx_channels_avatar',
+        'bx_channels_avatar_big',
+        'bx_channels_picture',
+        'bx_channels_cover',
+        'bx_channels_cover_thumb',
+        'bx_channels_gallery'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_channels',
+        'bx_channels_cmts',
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+        'process_esearches' => 1,
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'process_connections' => 1,
+        'process_deleted_profiles' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable_success' => array(
+        'process_menu_triggers' => 1,
+        'process_page_triggers' => 1,
+        'process_esearches' => 1,
+        'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'unregister_transcoders' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+        'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Connections Section
+     */
+    'relations' => array(
+        'bx_timeline',
+        'bx_notifications',
+    ),
+);
+
+/** @} */

+ 260 - 0
modules/boonex/channels/updates/14.0.4_14.0.5/source/install/langs/en.xml

@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_channels"><![CDATA[Channels]]></string>
+    <string name="_bx_channels_cmts"><![CDATA[Channels' Comments]]></string>
+
+    <string name="_bx_channels_option_default_author"><![CDATA[Default author for automatically created channels]]></string>
+    <string name="_bx_channels_option_num_connections_quick"><![CDATA[Number of items in friends block]]></string>
+    <string name="_bx_channels_option_per_page_browse"><![CDATA[Number of items per page]]></string>
+    <string name="_bx_channels_option_num_rss"><![CDATA[Number of items in RSS feed]]></string>
+    <string name="_bx_channels_option_searchable_fields"><![CDATA[Searchable fields]]></string>
+    <string name="_bx_channels_option_labels_autofollow"><![CDATA[Auto-follow channels when used as labels in profiles]]></string>
+    <string name="_bx_channels_option_browse_active_n_posts"><![CDATA[Browse Active: Min number of new posts in channel during the period of time]]></string>
+    <string name="_bx_channels_option_browse_active_x_hours"><![CDATA[Browse Active: The period of time to count new posts in channel]]></string>
+    <string name="_bx_channels_option_browse_trending_x_hours"><![CDATA[Browse Trending: The period of time to count new posts in channel]]></string>
+
+	<string name="_bx_channels_form_profile"><![CDATA[Channels]]></string>
+	<string name="_bx_channels_form_profile_display_add"><![CDATA[Add Channel]]></string>
+	<string name="_bx_channels_form_profile_display_edit"><![CDATA[Edit Channel]]></string>
+	<string name="_bx_channels_form_profile_display_edit_cover"><![CDATA[Edit Cover]]></string>
+	<string name="_bx_channels_form_profile_display_delete"><![CDATA[Delete Channel]]></string>
+	<string name="_bx_channels_form_profile_display_view"><![CDATA[View Channel]]></string>
+	<string name="_bx_channels_form_profile_display_view_full"><![CDATA[Full View Channel]]></string>
+	<string name="_bx_channels_form_profile_display_invite"><![CDATA[Invite to Channel]]></string>
+	<string name="_bx_channels_form_entry_input_author"><![CDATA[Author]]></string>
+	<string name="_bx_channels_form_profile_input_sys_do_submit"><![CDATA[Submit]]></string>
+	<string name="_bx_channels_form_profile_input_sys_allow_view_to"><![CDATA[Privacy]]></string>
+	<string name="_bx_channels_form_profile_input_allow_view_to"><![CDATA[Privacy]]></string>
+	<string name="_bx_channels_form_profile_input_allow_view_to_desc"><![CDATA[Closed - anyone can find the channel and see who's in it, only members can see posts. Secret - only members can find the channel and see posts.]]></string>
+	<string name="_bx_channels_form_profile_input_sys_initial_members"><![CDATA[Invite Members]]></string>
+	<string name="_bx_channels_form_profile_input_initial_members"><![CDATA[Invite Members]]></string>
+  	<string name="_bx_channels_form_profile_input_sys_channel_name"><![CDATA[Channel Name]]></string>
+	<string name="_bx_channels_form_profile_input_channel_name"><![CDATA[Channel Name]]></string>
+	<string name="_bx_channels_form_profile_input_channel_name_err"><![CDATA[Please, fill in this field. Channel name must be unique.]]></string>
+	<string name="_bx_channels_form_profile_input_sys_picture"><![CDATA[Channel Picture]]></string>
+	<string name="_bx_channels_form_profile_input_picture"><![CDATA[Upload New Channel Picture]]></string>
+	<string name="_bx_channels_form_profile_input_picture_err"><![CDATA[Channel picture is mandatory.]]></string>
+	<string name="_bx_channels_form_profile_input_sys_cover"><![CDATA[Cover Picture]]></string>
+	<string name="_bx_channels_form_profile_input_cover"><![CDATA[Upload New Cover Picture]]></string>
+	<string name="_bx_channels_form_profile_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string>
+	<string name="_bx_channels_form_profile_input_delete_confirm"><![CDATA[I am sure I want to delete this channel]]></string>
+	<string name="_bx_channels_form_profile_input_delete_confirm_info"><![CDATA[This action can not be undone.]]></string>
+	<string name="_bx_channels_form_profile_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete channel.]]></string>
+	
+	<string name="_bx_channels_acl_action_delete_profile"><![CDATA[Delete Channel]]></string>
+	<string name="_bx_channels_acl_action_view_profile"><![CDATA[View Channel]]></string>
+	<string name="_bx_channels_acl_action_edit_any_profile"><![CDATA[Edit Any Channel]]></string>
+	<string name="_bx_channels_acl_action_delete_any_profile"><![CDATA[Delete Any Channel]]></string>
+  <string name="_bx_channels_acl_action_create_channel_auto"><![CDATA[Create Channel Auto]]></string>
+  <string name="_bx_channels_acl_action_create_channel_auto_info"><![CDATA[Automatically create new channel when new hashtag is mentioned by anyone with this level]]></string>
+ 
+  <string name="_bx_channels_page_title_sys_edit_profile"><![CDATA[Edit Channel]]></string>
+	<string name="_bx_channels_page_title_edit_profile"><![CDATA[Edit Channel]]></string>
+	<string name="_bx_channels_page_title_sys_edit_profile_cover"><![CDATA[Edit Channel Cover]]></string>
+	<string name="_bx_channels_page_title_edit_profile_cover"><![CDATA[Edit Cover]]></string>
+	<string name="_bx_channels_page_title_sys_delete_profile"><![CDATA[Delete Channel]]></string>
+	<string name="_bx_channels_page_title_delete_profile"><![CDATA[Delete Channel]]></string>
+	<string name="_bx_channels_page_title_sys_view_profile_closed"><![CDATA[View Closed Channel]]></string>
+	<string name="_bx_channels_page_title_sys_view_profile"><![CDATA[View Channel]]></string>
+	<string name="_bx_channels_page_title_view_profile"><![CDATA[{display_name}]]></string>
+	<string name="_bx_channels_page_title_browse"><![CDATA[Channels]]></string>
+	<string name="_bx_channels_page_title_browse_by_author"><![CDATA[Channels of {display_name}]]></string>
+	<string name="_bx_channels_page_title_browse_featured"><![CDATA[Featured Channels]]></string>
+	<string name="_bx_channels_page_title_browse_top"><![CDATA[Top Channels]]></string>
+        <string name="_bx_channels_page_title_browse_active"><![CDATA[Active Channels]]></string>
+        <string name="_bx_channels_page_title_browse_trending"><![CDATA[Trending Channels]]></string>
+	<string name="_bx_channels_page_title_browse_recent"><![CDATA[New Channels]]></string>
+	<string name="_bx_channels_page_title_browse_connections"><![CDATA[{0} connections]]></string>
+	<string name="_bx_channels_page_title_browse_connections_mutual"><![CDATA[{0} and {1} mutual connections]]></string>
+	<string name="_bx_channels_page_title_sys_profile_friends"><![CDATA[Friends]]></string>
+	<string name="_bx_channels_page_title_profile_friends"><![CDATA[Friends]]></string>
+	<string name="_bx_channels_page_title_sys_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_channels_page_block_title_entry_reports"><![CDATA[Reports]]></string>
+	<string name="_bx_channels_page_title_profile_comments"><![CDATA[Comments]]></string>
+	<string name="_bx_channels_page_title_sys_profile_info"><![CDATA[Channel Info]]></string>
+	<string name="_bx_channels_page_title_profile_info"><![CDATA[Info about {display_name}]]></string>
+	<string name="_bx_channels_page_title_sys_recent"><![CDATA[New Channels]]></string>
+	<string name="_bx_channels_page_title_recent"><![CDATA[New Channels]]></string>
+	<string name="_bx_channels_page_title_sys_manage"><![CDATA[Manage Own Channels]]></string>
+	<string name="_bx_channels_page_title_sys_manage_administration"><![CDATA[Manage All Channels]]></string>
+	<string name="_bx_channels_page_title_manage"><![CDATA[Manage Channels]]></string>
+	<string name="_bx_channels_page_title_sys_top"><![CDATA[Top Channels]]></string>
+	<string name="_bx_channels_page_title_top"><![CDATA[Top Channels]]></string>
+	<string name="_bx_channels_page_title_sys_entries_search"><![CDATA[Search]]></string>
+	<string name="_bx_channels_page_title_entries_search"><![CDATA[Search]]></string>
+  <string name="_bx_channels_page_title_sys_entries_toplevel"><![CDATA[Top level]]></string>
+  <string name="_bx_channels_page_title_entries_toplevel"><![CDATA[Top level]]></string>
+  <string name="_bx_channels_page_title_sys_entries_of_author"><![CDATA[Сhannels of author]]></string>
+  <string name="_bx_channels_page_title_entries_of_author"><![CDATA[Сhannels by {display_name}]]></string>
+
+    <string name="_bx_channels_page_block_title_system_profile_subscribed_me"><![CDATA[Followers]]></string>
+    <string name="_bx_channels_page_block_title_profile_subscribed_me"><![CDATA[Followers]]></string>
+    <string name="_bx_channels_page_block_title_sys_entries_actions"><![CDATA[Channels actions]]></string>
+    <string name="_bx_channels_page_block_title_entries_actions"><![CDATA[Channels actions]]></string>
+    <string name="_bx_channels_page_block_title_sys_entries_of_author"><![CDATA[Followed Сhannels]]></string>
+    <string name="_bx_channels_page_block_title_entries_of_author"><![CDATA[Followed Сhannels]]></string>
+    <string name="_bx_channels_page_block_title_favorites_of_author"><![CDATA[Favorite Channels]]></string>
+    <string name="_bx_channels_page_block_title_sys_favorites_of_author"><![CDATA[Favorite Channels]]></string>
+    <string name="_bx_channels_page_block_title_edit_profile"><![CDATA[Edit Channel]]></string>
+    <string name="_bx_channels_page_block_title_edit_profile_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_channels_page_block_title_delete_profile"><![CDATA[Delete Channel]]></string>
+    <string name="_bx_channels_page_block_title_profile_info_link"><![CDATA[About <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_channels_page_block_title_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_channels_page_block_title_system_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_channels_page_block_title_profile_description"><![CDATA[Description]]></string>
+    <string name="_bx_channels_page_block_title_profile_friends_link"><![CDATA[Friends of <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_channels_page_block_title_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_channels_page_block_title_system_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_channels_page_block_title_admins"><![CDATA[Administrators]]></string>
+    <string name="_bx_channels_page_block_title_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_channels_page_block_title_profile_comments_link"><![CDATA[Comments to <a href="{entry_link}">{title}</a>]]></string>
+    <string name="_bx_channels_page_block_title_profile_actions"><![CDATA[Actions]]></string>
+    <string name="_bx_channels_page_block_title_featured_profiles"><![CDATA[Featured Channels]]></string>
+    <string name="_bx_channels_page_block_title_latest_profiles"><![CDATA[New Channels]]></string>
+    <string name="_bx_channels_page_block_title_system_manage"><![CDATA[Manage Own Channels]]></string>
+    <string name="_bx_channels_page_block_title_system_manage_administration"><![CDATA[Manage All Channels]]></string>
+    <string name="_bx_channels_page_block_title_manage"><![CDATA[Manage Channels]]></string>
+    <string name="_bx_channels_page_block_title_top_profiles"><![CDATA[Top Channels]]></string>
+    <string name="_bx_channels_page_block_title_entry_all_actions"><![CDATA[All Actions]]></string>
+    <string name="_bx_channels_page_block_title_entry_social_sharing"><![CDATA[Share]]></string>
+    <string name="_bx_channels_page_block_title_entry_breadcrumb"><![CDATA[Breadcrumb]]></string>
+    <string name="_bx_channels_page_block_title_entry_parent"><![CDATA[Parent Channel]]></string>
+    <string name="_bx_channels_page_block_title_entry_childs"><![CDATA[Child Channels]]></string>
+    <string name="_bx_channels_page_block_title_search_form"><![CDATA[Search Form]]></string>
+    <string name="_bx_channels_page_block_title_toplevel"><![CDATA[Top level]]></string>
+    <string name="_bx_channels_page_block_title_entries_my"><![CDATA[My Followed Channels]]></string>
+    <string name="_bx_channels_page_trigger_block_title_entries_my"><![CDATA[My Followed Channels]]></string>
+    <string name="_bx_channels_page_block_title_search_results"><![CDATA[Search Results]]></string>
+    <string name="_bx_channels_page_block_title_search_results_by_hashtag"><![CDATA[Search Results By Hashtag]]></string>
+    <string name="_bx_channels_page_block_title_search_form_cmts"><![CDATA[Comments Search Form]]></string>
+    <string name="_bx_channels_page_block_title_search_results_cmts"><![CDATA[Comments Search Results]]></string>
+    <string name="_bx_channels_page_block_title_sys_featured_entries_view_showcase"><![CDATA[Featured Channels (Showcase View)]]></string>
+    <string name="_bx_channels_page_block_title_featured_entries_view_showcase"><![CDATA[Featured Channels]]></string>
+    <string name="_bx_channels_page_block_title_sys_recommended_entries_view_showcase"><![CDATA[Recommended Channels (Showcase View)]]></string>
+    <string name="_bx_channels_page_block_title_recommended_entries_view_showcase"><![CDATA[Recommended Channels]]></string>
+    <string name="_bx_channels_page_block_title_sys_active_entries"><![CDATA[Active Channels]]></string>
+    <string name="_bx_channels_page_block_title_active_entries"><![CDATA[Active Channels]]></string>
+    <string name="_bx_channels_page_block_title_sys_trending_entries"><![CDATA[Trending Channels]]></string>
+    <string name="_bx_channels_page_block_title_trending_entries"><![CDATA[Trending Channels]]></string>
+    <string name="_bx_channels_page_block_title_sys_cover_block"><![CDATA[Cover]]></string>
+    <string name="_bx_channels_page_block_title_cover_block"><![CDATA[Cover]]></string>
+
+	<string name="_bx_channels_menu_title_submenu"><![CDATA[Channels Submenu]]></string>
+	<string name="_bx_channels_menu_set_title_submenu"><![CDATA[Channels Submenu]]></string>
+	<string name="_bx_channels_menu_set_title_view_profile_actions"><![CDATA[Channel Actions]]></string>
+	<string name="_bx_channels_menu_title_view_profile_actions"><![CDATA[Channel Actions]]></string>
+	<string name="_bx_channels_menu_set_title_view_profile_actions_more"><![CDATA[Channel More Actions]]></string>
+	<string name="_bx_channels_menu_title_view_profile_actions_more"><![CDATA[Channel More Actions]]></string>
+	<string name="_bx_channels_menu_set_title_view_profile_submenu"><![CDATA[View Channel Submenu]]></string>
+	<string name="_bx_channels_menu_title_entries_my"><![CDATA[Profile Channels Action Menu]]></string>
+	<string name="_bx_channels_menu_set_title_entries_my"><![CDATA[Profile Channels Menu]]></string>
+	<string name="_bx_channels_menu_set_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+	<string name="_bx_channels_menu_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+	<string name="_bx_channels_menu_item_title_system_delete"><![CDATA[Delete]]></string>
+	<string name="_bx_channels_menu_item_title_delete"><![CDATA[Delete]]></string>
+	<string name="_bx_channels_menu_item_title_system_delete_with_content"><![CDATA[Delete With Content]]></string>
+	<string name="_bx_channels_menu_item_title_delete_with_content"><![CDATA[Delete With Content]]></string>
+	<string name="_bx_channels_menu_item_title_system_edit_cover"><![CDATA[Edit Cover]]></string>
+	<string name="_bx_channels_menu_item_title_edit_cover"><![CDATA[Edit Cover]]></string>
+	<string name="_bx_channels_menu_item_title_system_edit_profile"><![CDATA[Edit Channel]]></string>
+	<string name="_bx_channels_menu_item_title_edit_profile"><![CDATA[Edit Channel]]></string>
+	<string name="_bx_channels_menu_item_title_system_delete_profile"><![CDATA[Delete Channel]]></string>
+	<string name="_bx_channels_menu_item_title_delete_profile"><![CDATA[Delete Channel]]></string>
+	<string name="_bx_channels_menu_item_title_system_delete_account"><![CDATA[Delete Account]]></string>
+	<string name="_bx_channels_menu_item_title_delete_account"><![CDATA[Delete Account]]></string>
+	<string name="_bx_channels_menu_item_title_system_profile_type"><![CDATA[Channel]]></string>
+	<string name="_bx_channels_menu_item_title_profile_type"><![CDATA[Channel]]></string>
+	<string name="_bx_channels_menu_item_title_system_view_profile_friends"><![CDATA[Friends]]></string>
+	<string name="_bx_channels_menu_item_title_view_profile_friends"><![CDATA[Friends]]></string>
+	<string name="_bx_channels_menu_item_title_system_view_profile_comments"><![CDATA[Comments]]></string>
+	<string name="_bx_channels_menu_item_title_view_profile_comments"><![CDATA[Comments]]></string>
+	<string name="_bx_channels_menu_item_title_system_view_profile_info"><![CDATA[Info]]></string>
+	<string name="_bx_channels_menu_item_title_view_profile_info"><![CDATA[Info]]></string>
+	<string name="_bx_channels_menu_item_title_system_view_profile_view"><![CDATA[Channel]]></string>
+	<string name="_bx_channels_menu_item_title_view_profile_view"><![CDATA[Channel]]></string>
+        <string name="_bx_channels_menu_item_title_system_view_profile_more_auto"><![CDATA[More]]></string>
+        <string name="_bx_channels_menu_item_title_view_profile_more_auto"><![CDATA[More]]></string>
+	<string name="_bx_channels_menu_item_title_system_invite"><![CDATA[Invite]]></string>
+	<string name="_bx_channels_menu_item_title_invite"><![CDATA[Invite]]></string>
+	
+	<string name="_bx_channels_menu_item_title_system_befriend"><![CDATA[Add friend]]></string>
+	<string name="_bx_channels_menu_item_title_befriend"><![CDATA[Add friend]]></string>
+	<string name="_bx_channels_menu_item_title_befriend_sent"><![CDATA[Add friend (requested)]]></string>
+	<string name="_bx_channels_menu_item_title_befriend_confirm"><![CDATA[Accept friend request]]></string>
+	<string name="_bx_channels_menu_item_title_system_unfriend"><![CDATA[Unfriend]]></string>
+	<string name="_bx_channels_menu_item_title_unfriend"><![CDATA[Unfriend]]></string>
+	<string name="_bx_channels_menu_item_title_unfriend_cancel_request"><![CDATA[Cancel friend request]]></string>
+	<string name="_bx_channels_menu_item_title_unfriend_reject_request"><![CDATA[Reject friend request]]></string>
+	
+	<string name="_bx_channels_menu_item_title_system_subscribe"><![CDATA[Follow]]></string>
+	<string name="_bx_channels_menu_item_title_subscribe"><![CDATA[Follow]]></string>
+	<string name="_bx_channels_menu_item_title_system_unsubscribe"><![CDATA[Unfollow]]></string>
+	<string name="_bx_channels_menu_item_title_unsubscribe"><![CDATA[Unfollow]]></string>
+	<string name="_bx_channels_menu_item_title_system_more_actions"><![CDATA[More Actions]]></string>
+	<string name="_bx_channels_menu_item_title_more_actions"><![CDATA[]]></string>
+	<string name="_bx_channels_menu_item_title_system_friends"><![CDATA[Friends]]></string>
+	<string name="_bx_channels_menu_item_title_friends"><![CDATA[Friends]]></string>
+	<string name="_bx_channels_menu_item_title_system_manage_my_channels"><![CDATA[My channels]]></string>
+	<string name="_bx_channels_menu_item_title_manage_my_channels"><![CDATA[{addon} channels]]></string>
+	<string name="_bx_channels_menu_item_title_system_entries_recent"><![CDATA[New]]></string>
+	<string name="_bx_channels_menu_item_title_entries_recent"><![CDATA[New]]></string>
+	<string name="_bx_channels_menu_item_title_system_entries_search"><![CDATA[Search]]></string>
+	<string name="_bx_channels_menu_item_title_entries_search"><![CDATA[Search]]></string>
+  <string name="_bx_channels_menu_item_title_system_entries_toplevel"><![CDATA[Top level]]></string>
+  <string name="_bx_channels_menu_item_title_entries_toplevel"><![CDATA[Top level]]></string>
+        <string name="_bx_channels_menu_item_title_system_entries_manage"><![CDATA[Manage]]></string>
+	<string name="_bx_channels_menu_item_title_entries_manage"><![CDATA[Manage]]></string>
+	<string name="_bx_channels_menu_item_title_system_entries_home"><![CDATA[Channels]]></string>
+	<string name="_bx_channels_menu_item_title_entries_home"><![CDATA[Channels]]></string>
+	<string name="_bx_channels_menu_item_title_system_admt_channels"><![CDATA[Channels]]></string>
+	<string name="_bx_channels_menu_item_title_admt_channels"><![CDATA[Channels]]></string>
+	<string name="_bx_channels_menu_item_title_manage_my"><![CDATA[My Channels]]></string>
+	<string name="_bx_channels_menu_item_title_manage_all"><![CDATA[All Channels]]></string>
+	<string name="_bx_channels_menu_item_title_system_entries_top"><![CDATA[Top]]></string>
+	<string name="_bx_channels_menu_item_title_entries_top"><![CDATA[Top]]></string>
+        <string name="_bx_channels_menu_item_title_system_view_entries_author"><![CDATA[Channels]]></string>
+        <string name="_bx_channels_menu_item_title_view_entries_author"><![CDATA[Channels]]></string>
+        <string name="_bx_channels_menu_item_title_system_followings"><![CDATA[Channels]]></string>
+        <string name="_bx_channels_menu_item_title_followings"><![CDATA[Channels]]></string>
+
+	<string name="_bx_channels_grid_filter_item_title_adm_select_one_filter1"><![CDATA[All Statuses]]></string>
+        <string name="_bx_channels_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string>
+	<string name="_bx_channels_grid_filter_item_title_adm_active"><![CDATA[Active]]></string>
+        <string name="_bx_channels_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string>
+	<string name="_bx_channels_grid_filter_item_title_adm_pending"><![CDATA[Pending]]></string>
+	<string name="_bx_channels_grid_filter_item_title_adm_suspended"><![CDATA[Suspended]]></string>
+	<string name="_bx_channels_grid_column_title_adm_active"><![CDATA[Active]]></string>
+	<string name="_bx_channels_grid_column_title_adm_name"><![CDATA[Name]]></string>
+	<string name="_bx_channels_grid_column_title_adm_added"><![CDATA[Created]]></string>
+	<string name="_bx_channels_grid_column_title_adm_account"><![CDATA[Account]]></string>
+	<string name="_bx_channels_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
+	<string name="_bx_channels_grid_action_title_adm_delete_with_content"><![CDATA[Delete With Content]]></string>
+	<string name="_bx_channels_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
+  <string name="_bx_channels_grid_action_title_adm_audit_context"><![CDATA[Audit actions in context]]></string>
+  <string name="_bx_channels_grid_action_title_adm_audit_content"><![CDATA[Audit actions with content]]></string>
+	<string name="_bx_channels_grid_action_err_delete"><![CDATA[Cannot delete the selected channel(s).]]></string>
+	<string name="_bx_channels_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+	
+	<string name="_bx_channels_chart_growth"><![CDATA[Channels: Growth]]></string>
+	<string name="_bx_channels_chart_growth_speed"><![CDATA[Channels: Growth Speed]]></string>
+	
+	<string name="_bx_channels_search_extended"><![CDATA[Channels Search]]></string>
+	<string name="_bx_channels_search_extended_cmts"><![CDATA[Channels Comments Search]]></string>
+
+	<string name="_bx_channels_txt_account_pending"><![CDATA[This channel is pending approval, please wait until it is activated.]]></string>
+	<string name="_bx_channels_txt_account_suspended"><![CDATA[This channel is suspended.]]></string>
+	<string name="_bx_channels_txt_sample_single"><![CDATA[channel]]></string>
+	<string name="_bx_channels_txt_sample_single_with_article"><![CDATA[a channel]]></string>
+	<string name="_bx_channels_txt_sample_comment_single"><![CDATA[comment to channel]]></string>
+	<string name="_bx_channels_txt_sample_vote_single"><![CDATA[vote to channel]]></string>
+        <string name="_bx_channels_txt_sample_score_up_single"><![CDATA[score up vote to channel]]></string>
+        <string name="_bx_channels_txt_sample_score_down_single"><![CDATA[score down vote to channel]]></string>
+        <string name="_bx_channels_msg_empty_author"><![CDATA['Default author for automatically created channels' is empty. You need to select some profile in this setting.]]></string>
+
+    <string name="_bx_channels_txt_ntfs_timeline_post_common"><![CDATA[New post <b class="bx-ntfs-content-entry">{subentry_sample}</b> in <b class="bx-ntfs-content-profile">{owner_name}</b> channel timeline]]></string>
+    <string name="_bx_channels_ntfs_txt_subobject_added"><![CDATA[New content <b class="bx-ntfs-content-entry">{entry_caption}</b> in <b class="bx-ntfs-content-profile">{owner_name}</b> channel]]></string>
+    <string name="_bx_channels_ntfs_txt_subobject_added_profile"><![CDATA[<b class="bx-ntfs-content-entry">{entry_caption}</b> was added to <b class="bx-ntfs-content-profile">{owner_name}</b> channel]]></string>
+    <string name="_bx_channels_alert_action_hashtag_added"><![CDATA[New hashtags]]></string>
+    <string name="_bx_channels_alert_action_hashtag_added_notif_follow_context"><![CDATA[New hashtags]]></string>
+    <string name="_bx_channels_alert_action_info_hashtag_added_notif_follow_context"><![CDATA[New hashtags were added or used in a space you follow.]]></string>
+</resources>

+ 51 - 0
modules/boonex/classes/updates/14.0.1_14.0.2/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Classes',
+    'version_from' => '14.0.1',
+    'version_to' => '14.0.2',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/classes/updates/update_14.0.1_14.0.2/',
+    'home_uri' => 'classes_update_1401_1402',
+
+    'module_dir' => 'boonex/classes/',
+    'module_uri' => 'classes',
+
+    'db_prefix' => 'bx_classes_',
+    'class_prefix' => 'BxClss',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Classes',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/classes/updates/14.0.1_14.0.2/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxClssUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 152 - 0
modules/boonex/classes/updates/14.0.1_14.0.2/source/install/config.php

@@ -0,0 +1,152 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Classes Classes
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_classes',
+    'title' => 'Classes',
+    'note' => 'Classes module.',
+    'version' => '14.0.2',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/classes/',
+    'home_uri' => 'classes',
+
+    'db_prefix' => 'bx_classes_',
+    'class_prefix' => 'BxClss',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Classes',
+
+    /**
+     * List of page triggers.
+     */
+    'page_triggers' => array (
+    	'trigger_page_group_view_entry',
+    ),
+    
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+        'trigger_group_view_submenu',
+    ),
+
+    /**
+     * Storage objects to automatically delete files from upon module uninstallation.
+     * Note. Don't add storage objects used in transcoder objects.
+     */
+    'storages' => array(
+        'bx_classes_covers',
+    	'bx_classes_photos',
+        'bx_classes_videos',
+        'bx_classes_sounds',
+        'bx_classes_files'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+        'bx_classes_preview',
+        'bx_classes_gallery',
+        'bx_classes_cover',
+
+        'bx_classes_preview_photos',
+        'bx_classes_gallery_photos', 
+
+        'bx_classes_videos_poster',
+        'bx_classes_videos_poster_preview',
+        'bx_classes_videos_mp4',
+        'bx_classes_videos_mp4_hd',
+
+        'bx_classes_sounds_mp3',
+
+        'bx_classes_preview_files',
+        'bx_classes_gallery_files'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_classes',
+    	'bx_classes_cmts'
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+    	'process_esearches' => 1,
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable_success' => array(
+        'process_menu_triggers' => 1,
+        'process_page_triggers' => 1,
+    	'process_esearches' => 1,
+        'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'update_relations' => 1,
+        'unregister_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+        'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Relations Section
+     */
+    'relations' => array(
+    	'bx_timeline',
+    	'bx_notifications'
+    ),
+
+);
+
+/** @} */

+ 51 - 0
modules/boonex/classes/updates/14.0.1_14.0.2/source/install/sql/disable.sql

@@ -0,0 +1,51 @@
+
+-- SETTINGS
+SET @iTypeId = (SELECT `ID` FROM `sys_options_types` WHERE `name` = 'bx_classes' LIMIT 1);
+SET @iCategId = (SELECT `ID` FROM `sys_options_categories` WHERE `type_id` = @iTypeId LIMIT 1);
+DELETE FROM `sys_options` WHERE `category_id` = @iCategId;
+DELETE FROM `sys_options_categories` WHERE `type_id` = @iTypeId;
+DELETE FROM `sys_options_types` WHERE `id` = @iTypeId;
+
+-- PAGES
+DELETE FROM `sys_objects_page` WHERE `module` = 'bx_classes';
+DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_classes' OR `object` IN('bx_classes_create_entry', 'bx_classes_edit_entry', 'bx_classes_delete_entry', 'bx_classes_view_entry', 'bx_classes_view_entry_comments', 'bx_classes_context', 'bx_classes_manage');
+
+-- MENU
+DELETE FROM `sys_objects_menu` WHERE `module` = 'bx_classes';
+DELETE FROM `sys_menu_sets` WHERE `module` = 'bx_classes';
+DELETE FROM `sys_menu_items` WHERE `module` = 'bx_classes' OR `set_name` IN('bx_classes_entry_attachments', 'bx_classes_view', 'bx_classes_view_actions', 'bx_classes_view_submenu', 'bx_classes_snippet_meta', 'bx_classes_menu_manage_tools');
+
+-- PRIVACY 
+DELETE FROM `sys_objects_privacy` WHERE `object` = 'bx_classes_allow_view_to';
+
+-- ACL
+DELETE `sys_acl_actions`, `sys_acl_matrix` FROM `sys_acl_actions`, `sys_acl_matrix` WHERE `sys_acl_matrix`.`IDAction` = `sys_acl_actions`.`ID` AND `sys_acl_actions`.`Module` = 'bx_classes';
+DELETE FROM `sys_acl_actions` WHERE `Module` = 'bx_classes';
+
+-- SEARCH
+DELETE FROM `sys_objects_search` WHERE `ObjectName` IN ('bx_classes', 'bx_classes_cmts');
+
+-- METATAGS
+DELETE FROM `sys_objects_metatags` WHERE `object` = 'bx_classes';
+
+-- STATS
+DELETE FROM `sys_statistics` WHERE `name` LIKE 'bx_classes%';
+
+-- CHARTS
+DELETE FROM `sys_objects_chart` WHERE `object` LIKE 'bx_classes%';
+
+-- GRIDS
+DELETE FROM `sys_objects_grid` WHERE `object` IN ('bx_classes_administration', 'bx_classes_common');
+DELETE FROM `sys_grid_fields` WHERE `object` IN ('bx_classes_administration', 'bx_classes_common');
+DELETE FROM `sys_grid_actions` WHERE `object` IN ('bx_classes_administration', 'bx_classes_common');
+
+-- UPLOADERS
+DELETE FROM `sys_objects_uploader` WHERE `object` LIKE 'bx_classes_%';
+
+-- ALERTS
+SET @iHandler := (SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = 'bx_classes' LIMIT 1);
+DELETE FROM `sys_alerts` WHERE `handler_id` = @iHandler;
+DELETE FROM `sys_alerts_handlers` WHERE `id` = @iHandler;
+
+-- CRON
+DELETE FROM `sys_cron_jobs` WHERE `name` LIKE 'bx_classes%';

+ 51 - 0
modules/boonex/contact/updates/14.0.0_14.0.1/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Contact',
+    'version_from' => '14.0.0',
+    'version_to' => '14.0.1',
+    'vendor' => 'BoonEx',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/contact/updates/update_14.0.0_14.0.1/',
+    'home_uri' => 'contact_update_1400_1401',
+
+    'module_dir' => 'boonex/contact/',
+    'module_uri' => 'contact',
+
+    'db_prefix' => 'bx_contact_',
+    'class_prefix' => 'BxContact',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Contact',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/contact/updates/14.0.0_14.0.1/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxContactUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 198 - 0
modules/boonex/contact/updates/14.0.0_14.0.1/source/classes/BxContactModule.php

@@ -0,0 +1,198 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Contact Contact
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+class BxContactModule extends BxDolModule
+{
+    /**
+     * Constructor
+     */
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_oConfig->init($this->_oDb);
+    }
+
+    /**
+     * SERVICE METHODS
+     */
+
+    public function serviceGetSafeServices()
+    {
+        return array (
+            'GetBlockForm' => '',
+        );
+    }    
+
+    /**
+     * @page service Service Calls
+     * @section bx_contact Contact
+     * @subsection bx_contact-page_blocks Page Blocks
+     * @subsubsection bx_contact-get_block_form get_block_form
+     * 
+     * @code bx_srv('bx_contact', 'get_block_form'); @endcode
+     * 
+     * Get page block with contact form.
+     *
+     * @return an array describing a block to display on the site, all necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
+     * 
+     * @see BxContactModule::serviceGetBlockForm
+     */
+    /** 
+     * @ref bx_contact-get_block_form "get_block_form"
+     * @api @ref bx_contact-get_block_form "get_block_form"
+     */
+    public function serviceGetBlockForm()
+    {
+        $aDefaultFields = array('name', 'email', 'subject', 'body', 'cfc_do_submit');
+
+        $mixedAllowed = $this->isAllowedContact();
+        if($mixedAllowed !== true)
+            return array(
+                'content' => MsgBox($mixedAllowed)
+            );
+
+        $sResult = '';
+
+        $oForm = BxDolForm::getObjectInstance($this->_oConfig->getObject('form_contact'), $this->_oConfig->getObject('form_display_contact_send'), $this->_oTemplate);
+        if (isLogged()) {
+            $oForm->aInputs['name']['value'] = BxDolProfile::getInstance()->getDisplayName();
+            $oForm->aInputs['email']['value'] = BxDolAccount::getInstance()->getEmail();
+        }
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $iId = $oForm->insert(array(
+                'uri' => $oForm->generateUri(),
+                'date' => time()
+            ));
+
+            if($iId !== false) {
+                $sCustomFields = '';
+                $aCustomFields = array();
+                foreach($oForm->aInputs as $aInput) {
+                    if(in_array($aInput['name'], $aDefaultFields))
+                        continue;
+
+                    $aCustomFields[$aInput['name']] = bx_process_output($oForm->getCleanValue($aInput['name']));
+                    $sCustomFields .= $aInput['caption'] . ': ' . $aCustomFields[$aInput['name']] . '<br />';
+                }
+
+                $aTemplateKeys = array(
+                    'SenderName' => bx_process_output($oForm->getCleanValue('name')),
+                    'SenderEmail' => bx_process_output($oForm->getCleanValue('email')),
+                    'MessageSubject' => bx_process_output($oForm->getCleanValue('subject')),
+                    'MessageBody' => bx_process_output(nl2br($oForm->getCleanValue('body')), BX_DATA_TEXT_MULTILINE),
+                    'CustomFields' => $sCustomFields,
+                );
+                $aTemplateKeys = array_merge($aTemplateKeys, $aCustomFields);
+
+                $aMessage = BxDolEmailTemplates::getInstance()->parseTemplate('bx_contact_contact_form_message', $aTemplateKeys);
+
+                $sResult = '';
+                $sRecipientEmail = $this->_oConfig->getEmail();
+                $aCustomHeaders = array();
+                if (getParam('bx_contact_add_reply_to')) {
+                    $aCustomHeaders = array(
+                        'Reply-To' => $oForm->getCleanValue('name') . ' <' . bx_process_output($oForm->getCleanValue('email')) . '>',
+                        'X-Original-From' => $oForm->getCleanValue('name') . ' <' . bx_process_output($oForm->getCleanValue('email')) . '>',
+                    );
+                }
+                
+                if(sendMail($sRecipientEmail, $aMessage['Subject'], $aMessage['Body'], 0, array(), BX_EMAIL_SYSTEM, 'html', false, $aCustomHeaders)) {
+                    $this->onContact();
+
+                    foreach($oForm->aInputs as $iKey => $aInput) 
+                        if(in_array($aInput['type'], array('text', 'textarea')) && !in_array($aInput['name'], array('name', 'email')))
+                            $oForm->aInputs[$iKey]['value'] = '';
+
+                    $sResult = '_ADM_PROFILE_SEND_MSG';
+                } else
+                    $sResult = '_Email sent failed';
+
+                $sResult = _t($sResult);
+            }
+        }
+
+        if (bx_is_api()){
+            if ($oForm->isSubmittedAndValid()){
+                return [
+                    bx_api_get_msg($sResult, ['ext' => ['msg_type' => 'result']])
+                ];
+            }
+            else{
+                return [
+                    bx_api_get_block('form', $oForm->getCodeAPI(), ['ext' => ['request' => ['url' => '/api.php?r=bx_contact/get_block_form', 'immutable' => true]]]),
+
+                ];
+            }
+        }
+
+        return ['content' => ($sResult ?  MsgBox($sResult) : '') . $oForm->getCode()];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_contact Contact
+     * @subsection bx_contact-other Other
+     * @subsubsection bx_contact-get_contact_page_url get_contact_page_url
+     * 
+     * @code bx_srv('bx_contact', 'get_contact_page_url', [...]); @endcode
+     * 
+     * Get string with contact page URL. Is used in system and inter-modular integration.
+     *
+     * @return string with contact page URL.
+     * 
+     * @see BxContactModule::serviceGetContactPageUrl
+     */
+    /** 
+     * @ref bx_contact-get_block_form "get_block_form"
+     */
+    public function serviceGetContactPageUrl()
+    {
+        //if (true !== $this->isAllowedContact())
+        //    return false;
+
+        return bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=contact'));
+    }
+
+    /*
+     * COMMON METHODS
+     */
+    public function getUserId()
+    {
+        return isLogged() ? bx_get_logged_profile_id() : 0;
+    }
+
+    protected function onContact()
+    {
+        $iUserId = $this->getUserId();
+
+        $this->isAllowedContact(true);
+
+        //--- Event -> Contact for Alerts Engine ---//
+        $oAlert = new BxDolAlerts($this->_oConfig->getObject('alert'), 'contact', 0, $iUserId);
+        $oAlert->alert();
+        //--- Event -> Contact for Alerts Engine ---//
+    }
+
+    protected function isAllowedContact($bPerform = false)
+    {
+        $iUserId = $this->getUserId();
+
+        $aCheckResult = checkActionModule($iUserId, 'contact', $this->getName(), $bPerform);
+        return $aCheckResult[CHECK_ACTION_RESULT] != CHECK_ACTION_RESULT_ALLOWED ? $aCheckResult[CHECK_ACTION_MESSAGE] : true;
+    }
+}
+
+/** @} */

+ 71 - 0
modules/boonex/contact/updates/14.0.0_14.0.1/source/install/config.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Contact Contact
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_contact',
+    'title' => 'Contact',
+    'note' => 'Contact module.',
+    'version' => '14.0.1',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/contact/',
+    'home_uri' => 'contact',
+
+    'db_prefix' => 'bx_contact_',
+    'class_prefix' => 'BxContact',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Contact',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+);
+
+/** @} */

+ 51 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Courses',
+    'version_from' => '14.0.3',
+    'version_to' => '14.0.4',
+    'vendor' => 'BoonEx',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/courses/updates/update_14.0.3_14.0.4/',
+    'home_uri' => 'courses_update_1403_1404',
+
+    'module_dir' => 'boonex/courses/',
+    'module_uri' => 'courses',
+
+    'db_prefix' => 'bx_courses_',
+    'class_prefix' => 'BxCourses',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Courses',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxCoursesUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 15 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/install/langs/en.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string_add name="_bx_courses_page_block_title_sys_joined_entries_summary"><![CDATA[Attended Courses Summary]]></string_add>
+	<string_add name="_bx_courses_page_block_title_joined_entries_summary"><![CDATA[Attended Courses Summary]]></string_add>
+	<string_add name="_bx_courses_menu_item_title_system_hide_profile"><![CDATA[Hide Course]]></string_add>
+	<string_add name="_bx_courses_menu_item_title_hide_profile"><![CDATA[Hide]]></string_add>
+	<string_add name="_bx_courses_menu_item_title_system_unhide_profile"><![CDATA[Unhide Course]]></string_add>
+	<string_add name="_bx_courses_menu_item_title_unhide_profile"><![CDATA[Publish]]></string_add>
+	<string_add name="_bx_courses_txt_summary_title"><![CDATA[Study progress]]></string_add>
+	<string_add name="_bx_courses_txt_summary_joined"><![CDATA[Attended]]></string_add>
+	<string_add name="_bx_courses_txt_summary_passed"><![CDATA[Passed]]></string_add>
+	<string_add name="_bx_courses_txt_summary_not_started"><![CDATA[Not started]]></string_add>
+
+	<string_upd name="_bx_courses_txt_status_hidden"><![CDATA[This course is hidden.]]></string_upd>
+</resources>

+ 21 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/install/sql/enable.sql

@@ -0,0 +1,21 @@
+--PAGES
+DELETE FROM  `sys_pages_blocks` WHERE `object`='bx_courses_joined' AND `title_system`='_bx_courses_page_block_title_sys_joined_entries_summary';
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
+('bx_courses_joined', 1, 'bx_courses', '_bx_courses_page_block_title_sys_joined_entries_summary', '_bx_courses_page_block_title_joined_entries_summary', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"joined_entries_summary";}', 0, 0, 1, 4);
+
+UPDATE `sys_pages_blocks` SET `order`='5' WHERE `object`='bx_courses_joined' AND `title_system`='_bx_courses_page_block_title_sys_joined_entries';
+
+
+-- MENUS
+DELETE FROM `sys_menu_items` WHERE `set_name`='bx_courses_view_actions_more' AND `name` IN ('hide-course-profile', 'unhide-course-profile');
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `order`) VALUES 
+('bx_courses_view_actions_more', 'bx_courses', 'hide-course-profile', '_bx_courses_menu_item_title_system_hide_profile', '_bx_courses_menu_item_title_hide_profile', 'javascript:void(0);', 'javascript:{js_object}.perform(this, ''hide-course-profile'', {content_id});', '', 'stop-circle', '', 2147483647, '', 1, 0, 35),
+('bx_courses_view_actions_more', 'bx_courses', 'unhide-course-profile', '_bx_courses_menu_item_title_system_unhide_profile', '_bx_courses_menu_item_title_unhide_profile', 'javascript:void(0);', 'javascript:{js_object}.perform(this, ''unhide-course-profile'', {content_id});', '', 'play-circle', '', 2147483647, '', 1, 0, 36);
+
+
+-- GRIDS
+DELETE FROM `sys_grid_actions` WHERE `object`='bx_courses_cnt_data_manage' AND `type`='single' AND `name`='edit';
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_courses_cnt_data_manage', 'single', 'edit', '_bx_courses_grid_action_title_cnt_edit', 'pencil-alt', 1, 0, 1);
+
+UPDATE `sys_grid_actions` SET `order`='2' WHERE `object`='bx_courses_cnt_data_manage' AND `type`='single' AND `name`='delete';

+ 2 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/install/sql/install.sql

@@ -0,0 +1,2 @@
+-- FORMS
+DELETE FROM `sys_form_display_inputs` WHERE `display_name` IN ('bx_courses_content_node_add', 'bx_courses_content_node_edit') AND `input_name`='id';

+ 398 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesConfig.php

@@ -0,0 +1,398 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCoursesConfig extends BxBaseModGroupsConfig
+{
+    protected $_aPerPageDefault;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_aMenuItems2MethodsActions = array_merge($this->_aMenuItems2MethodsActions, array(
+            'view-course-profile' => 'checkAllowedView',
+            'edit-course-profile' => 'checkAllowedEdit',
+            'hide-course-profile' => 'checkAllowedHide',
+            'unhide-course-profile' => 'checkAllowedUnhide',
+            'edit-course-cover' => 'checkAllowedChangeCover',
+            'invite-to-course' => 'checkAllowedInvite',
+            'delete-course-profile' => 'checkAllowedDelete',
+            'approve-course-profile' => 'checkAllowedApprove',
+        ));
+
+        $this->CNF = array (
+
+            // module icon
+            'ICON' => 'book-reader col-blue3-dark',
+
+            // database tables
+            'TABLE_ENTRIES' => $aModule['db_prefix'] . 'data',
+            'TABLE_ENTRIES_FULLTEXT' => 'search_fields',
+            'TABLE_ADMINS' => $aModule['db_prefix'] . 'admins',
+            'TABLE_INVITES' => $aModule['db_prefix'] . 'invites',
+            'TABLE_PRICES' => $aModule['db_prefix'] . 'prices',
+            'TABLE_CNT_STRUCTURE' => $aModule['db_prefix'] . 'content_structure',
+            'TABLE_CNT_NODES' => $aModule['db_prefix'] . 'content_nodes',
+            'TABLE_CNT_NODES2USERS' => $aModule['db_prefix'] . 'content_nodes2users',
+            'TABLE_CNT_DATA' => $aModule['db_prefix'] . 'content_data',
+            'TABLE_CNT_DATA2USERS' => $aModule['db_prefix'] . 'content_data2users',
+
+            // database fields
+            'FIELD_ID' => 'id',
+            'FIELD_AUTHOR' => 'author',
+            'FIELD_ADDED' => 'added',
+            'FIELD_CHANGED' => 'changed',
+            'FIELD_NAME' => 'name',
+            'FIELD_TITLE' => 'name',
+            'FIELD_TEXT' => 'desc',
+            'FIELD_CATEGORY' => 'cat',
+            'FIELD_PICTURE' => 'picture',
+            'FIELD_COVER' => 'cover',
+            'FIELD_COVER_POSITION' => 'cover_data',
+            'FIELD_JOIN_CONFIRMATION' => 'join_confirmation',
+            'FIELD_ALLOW_VIEW_TO' => 'allow_view_to',
+            'FIELD_ALLOW_POST_TO' => 'allow_post_to',
+            'FIELD_CF' => 'cf',
+            'FIELD_VIEWS' => 'views',
+            'FIELD_VOTES' => 'votes',
+            'FIELD_REPORTS' => 'reports',
+            'FIELD_STATUS' => 'status',
+            'FIELD_STATUS_ADMIN' => 'status_admin',
+            'FIELD_COMMENTS' => 'comments',
+            'FIELDS_QUICK_SEARCH' => array('name'),
+            'FIELD_LOCATION' => 'location',
+            'FIELD_LOCATION_PREFIX' => 'location',
+            'FIELD_LABELS' => 'labels',
+            'FIELDS_WITH_KEYWORDS' => 'auto', // can be 'auto', array of fields or comma separated string of field names, works only when OBJECT_METATAGS is specified
+
+            'FIELD_PRICE_ROLE_ID' => 'role_id',
+            'FIELD_PRICE_NAME' => 'name',
+
+            // page URIs
+            'URI_VIEW_ENTRY' => 'view-course-profile',
+            'URI_VIEW_ENTRY_NODE' => 'view-course-profile-node',
+            'URI_EDIT_ENTRY' => 'edit-course-profile',
+            'URI_EDIT_ENTRY_CONTENT' => 'edit-course-content',
+            'URI_JOIN_ENTRY' => 'join-course-profile',
+            'URI_JOINED_ENTRIES' => 'joined-courses',
+            'URI_MANAGE_COMMON' => 'courses-manage',
+            'URI_FAVORITES_LIST' => 'courses-favorites',
+
+            'URL_HOME' => 'page.php?i=courses-home',
+            'URL_ENTRY_FANS' => 'page.php?i=course-fans',
+            'URL_MANAGE_STRUCTURE' => 'page.php?i=edit-course-content',
+            'URL_MANAGE_COMMON' => 'page.php?i=courses-manage',
+            'URL_MANAGE_ADMINISTRATION' => 'page.php?i=courses-administration',
+
+            'PARAM_AUTO_APPROVE' => 'bx_courses_enable_auto_approve',
+            'PARAM_NUM_RSS' => 'bx_courses_num_rss',
+            'PARAM_NUM_CONNECTIONS_QUICK' => 'bx_courses_num_connections_quick',
+
+            'PARAM_SEARCHABLE_FIELDS' => 'bx_courses_searchable_fields',
+            'PARAM_PER_PAGE_BROWSE_SHOWCASE' => 'bx_courses_per_page_browse_showcase',
+            'PARAM_PER_PAGE_BROWSE_RECOMMENDED' => 'bx_courses_per_page_browse_recommended',
+
+            'PARAM_MMODE' => 'bx_courses_members_mode',
+            'PARAM_PAID_JOIN_ENABLED' => true,
+            'PARAM_RECURRING_RESERVE' => 3, // 3 days for recurring payment to be registered
+            'PARAM_PER_PAGE_FOR_FAVORITES_LISTS' => 'bx_courses_per_page_for_favorites_lists',
+            'PARAM_USE_IN' => 'bx_courses_internal_notifications',
+
+            'PARAM_CONTENT' => 'bx_courses_enable_content',
+            'PARAM_CONTENT_LEVEL_MAX' => 'bx_courses_content_level_max',
+            'PARAM_CONTENT_MODULES_ST' => 'bx_courses_content_modules_st',
+            'PARAM_CONTENT_MODULES_AT' => 'bx_courses_content_modules_at',
+
+            // objects
+            'OBJECT_STORAGE' => 'bx_courses_pics',
+            'OBJECT_STORAGE_COVER' => 'bx_courses_pics',
+            'OBJECT_IMAGES_TRANSCODER_THUMB' => 'bx_courses_thumb',
+            'OBJECT_IMAGES_TRANSCODER_ICON' => 'bx_courses_icon',
+            'OBJECT_IMAGES_TRANSCODER_AVATAR' => 'bx_courses_avatar',
+            'OBJECT_IMAGES_TRANSCODER_AVATAR_BIG' => 'bx_courses_avatar_big',
+            'OBJECT_IMAGES_TRANSCODER_PICTURE' => 'bx_courses_picture',
+            'OBJECT_IMAGES_TRANSCODER_COVER' => 'bx_courses_cover',
+            'OBJECT_IMAGES_TRANSCODER_COVER_THUMB' => 'bx_courses_cover_thumb',
+            'OBJECT_IMAGES_TRANSCODER_GALLERY' => 'bx_courses_gallery',
+            'OBJECT_VIEWS' => 'bx_courses',
+            'OBJECT_VOTES' => 'bx_courses',
+            'OBJECT_SCORES' => 'bx_courses',
+            'OBJECT_FAVORITES' => 'bx_courses',
+            'OBJECT_FEATURED' => 'bx_courses',
+            'OBJECT_COMMENTS' => 'bx_courses',
+            'OBJECT_NOTES' => 'bx_courses_notes',
+            'OBJECT_REPORTS' => 'bx_courses',
+            'OBJECT_METATAGS' => 'bx_courses',
+            'OBJECT_CATEGORY' => 'bx_courses_cats',
+            'OBJECT_FORM_ENTRY' => 'bx_course',
+            'OBJECT_FORM_ENTRY_DISPLAY_VIEW' => 'bx_course_view',
+            'OBJECT_FORM_ENTRY_DISPLAY_VIEW_FULL' => 'bx_course_view_full', // for "info" tab on view course page
+            'OBJECT_FORM_ENTRY_DISPLAY_ADD' => 'bx_course_add',
+            'OBJECT_FORM_ENTRY_DISPLAY_EDIT' => 'bx_course_edit',
+            'OBJECT_FORM_ENTRY_DISPLAY_EDIT_COVER' => 'bx_course_edit_cover',
+            'OBJECT_FORM_ENTRY_DISPLAY_DELETE' => 'bx_course_delete',
+            'OBJECT_FORM_ENTRY_DISPLAY_INVITE' => 'bx_course_invite',
+            'OBJECT_FORM_PRICE' => 'bx_courses_price',
+            'OBJECT_FORM_PRICE_DISPLAY_ADD' => 'bx_courses_price_add',
+            'OBJECT_FORM_PRICE_DISPLAY_EDIT' => 'bx_courses_price_edit',
+            'OBJECT_FORM_CNT_NODE' => 'bx_courses_content_node',
+            'OBJECT_FORM_CNT_NODE_DISPLAY_ADD' => 'bx_courses_content_node_add',
+            'OBJECT_FORM_CNT_NODE_DISPLAY_EDIT' => 'bx_courses_content_node_edit',
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY' => 'bx_courses_view_actions', // actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE' => 'bx_courses_view_actions_more', // actions menu on view entry page for "more" popup
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL' => 'bx_courses_view_actions_all', // all actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_MY_ENTRIES' => 'bx_courses_my', // actions menu on profile entries page
+            'OBJECT_MENU_SUBMENU' => 'bx_courses_submenu', // main module submenu
+            'OBJECT_MENU_SUBMENU_VIEW_ENTRY' => 'bx_courses_view_submenu',  // view entry submenu
+            'OBJECT_MENU_SUBMENU_VIEW_ENTRY_MAIN_SELECTION' => 'courses-home', // first item in view entry submenu from main module submenu
+            'OBJECT_MENU_MANAGE_VIEW_ENTRY' => '',
+            'OBJECT_MENU_VIEW_ENTRY_META' => 'bx_courses_view_meta', // meta menu on view entry page
+            'OBJECT_MENU_SNIPPET_META' => 'bx_courses_snippet_meta', // menu for snippet meta info
+            'OBJECT_MENU_MANAGE_TOOLS' => 'bx_courses_menu_manage_tools', //manage menu in content administration tools
+            'OBJECT_MENU_CONTENT_ADD' => 'bx_courses_content_add',
+            'OBJECT_PAGE_VIEW_ENTRY' => 'bx_courses_view_profile',
+            'OBJECT_PAGE_VIEW_ENTRY_CLOSED' => 'bx_courses_view_profile_closed',
+            'OBJECT_PAGE_JOINED_ENTRY' => 'bx_courses_join_profile',
+            'OBJECT_PRIVACY_VIEW' => 'bx_courses_allow_view_to',
+            'OBJECT_PRIVACY_LIST_VIEW' => 'bx_courses_allow_view_favorite_list',
+            'OBJECT_PRIVACY_VIEW_NOTIFICATION_EVENT' => 'bx_courses_allow_view_notification_to',
+            'OBJECT_PRIVACY_POST' => 'bx_courses_allow_post_to',
+            'OBJECT_GRID_ADMINISTRATION' => 'bx_courses_administration',
+            'OBJECT_GRID_COMMON' => 'bx_courses_common',
+            'OBJECT_GRID_CONNECTIONS' => 'bx_courses_fans',
+            'OBJECT_GRID_INVITES' => 'bx_courses_invites',
+            'OBJECT_GRID_PRICES_MANAGE' => 'bx_courses_prices_manage',
+            'OBJECT_GRID_PRICES_VIEW' => 'bx_courses_prices_view',
+            'OBJECT_GRID_CNT_STRUCTURE_MANAGE' => 'bx_courses_cnt_structure_manage',
+            'OBJECT_GRID_CNT_DATA_MANAGE' => 'bx_courses_cnt_data_manage',
+            'OBJECT_CONNECTIONS' => 'bx_courses_fans',
+            'OBJECT_UPLOADERS_COVER' => array('bx_courses_cover_crop'),
+            'OBJECT_UPLOADERS_PICTURE' => array('bx_courses_picture_crop'),
+            'OBJECT_PRE_LIST_ROLES' => 'bx_courses_roles',
+            'OBJECT_PRE_LIST_PERIOD_UNITS' => 'bx_courses_period_units',
+
+            'BADGES_AVALIABLE' => true,
+            
+            'INVITES_KEYS_LIFETIME' => 86400,
+
+            'EMAIL_INVITATION' => 'bx_courses_invitation',
+            'EMAIL_JOIN_REQUEST' => 'bx_courses_join_request',
+            'EMAIL_JOIN_CONFIRM' => 'bx_courses_join_confirm',
+            'EMAIL_FAN_BECOME_ADMIN' => 'bx_courses_fan_become_admin',
+            'EMAIL_ADMIN_BECOME_FAN' => 'bx_courses_admin_become_fan',
+            'EMAIL_FAN_REMOVE' => 'bx_courses_fan_remove',
+            'EMAIL_FAN_SET_ROLE' => 'bx_courses_set_role',
+            'EMAIL_JOIN_REJECT' => 'bx_courses_join_reject',
+
+            'TRIGGER_MENU_PROFILE_VIEW_SUBMENU' => 'trigger_group_view_submenu',
+            'TRIGGER_MENU_PROFILE_SNIPPET_META' => 'trigger_group_snippet_meta',
+            'TRIGGER_MENU_PROFILE_VIEW_ACTIONS' => 'trigger_group_view_actions',
+            'TRIGGER_PAGE_VIEW_ENTRY' => 'trigger_page_group_view_entry',
+
+            // menu items which visibility depends on custom visibility checking
+            'MENU_ITEM_TO_METHOD' => array (
+                'bx_courses_view_actions' => $this->_aMenuItems2MethodsActions,
+                'bx_courses_view_actions_more' => $this->_aMenuItems2MethodsActions,
+                'bx_courses_view_actions_all' => $this->_aMenuItems2MethodsActions,
+            ),
+
+            // informer messages
+            'INFORMERS' => array (
+                'status' => array (
+                    'name' => 'bx-courses-status-not-active',
+                    'map' => array (
+                        'hidden' => '_bx_courses_txt_status_hidden',
+                        BX_PROFILE_STATUS_PENDING => '_bx_courses_txt_account_pending',
+                        BX_PROFILE_STATUS_SUSPENDED => '_bx_courses_txt_account_suspended',
+                    ),
+                ),
+                'approving' => array (
+                    'name' => 'bx-groups-approving',
+                    'map' => array (
+                        'pending' => array('msg' => '_bx_courses_txt_account_pending', 'type' => BX_INFORMER_ALERT),
+                        'hidden' => array('msg' => '_bx_courses_txt_status_hidden', 'type' => BX_INFORMER_ERROR),
+                    ),
+                ),
+            ),
+
+            // some language keys
+            'T' => array (
+                'txt_sample_single' => '_bx_courses_txt_sample_single',
+                'txt_sample_single_with_article' => '_bx_courses_txt_sample_single_with_article',
+                'txt_sample_comment_single' => '_bx_courses_txt_sample_comment_single',
+                'txt_sample_vote_single' => '_bx_courses_txt_sample_vote_single',
+                'txt_sample_score_up_single' => '_bx_courses_txt_sample_score_up_single',
+                'txt_sample_score_down_single' => '_bx_courses_txt_sample_score_down_single',
+                'txt_private_group' => '_bx_courses_txt_private_entry',
+                'txt_N_fans' => '_bx_courses_txt_N_fans',
+                'txt_ntfs_join_invitation' => '_bx_courses_txt_ntfs_join_invitation',
+                'txt_ntfs_join_request' => '_bx_courses_txt_ntfs_join_request',
+                'txt_ntfs_fan_added' => '_bx_courses_txt_ntfs_fan_added',
+                'txt_ntfs_timeline_post_common' => '_bx_courses_txt_ntfs_timeline_post_common',
+                'option_members_mode_multi_roles' => '_bx_courses_option_members_mode_multi_roles',
+                'form_field_author' => '_bx_courses_form_entry_input_author',
+                'menu_item_title_befriend_sent' => '_bx_courses_menu_item_title_befriend_sent',
+                'menu_item_title_unfriend_cancel_request' => '_bx_courses_menu_item_title_unfriend_cancel_request',
+                'menu_item_title_befriend_confirm' => '_bx_courses_menu_item_title_befriend_confirm',
+                'menu_item_title_unfriend_reject_request' => '_bx_courses_menu_item_title_unfriend_reject_request',
+                'menu_item_title_befriend' => '_bx_courses_menu_item_title_befriend',
+                'menu_item_title_unfriend' => '_bx_courses_menu_item_title_unfriend',
+                'menu_item_title_hide' => '_bx_courses_menu_item_title_hide_profile',
+                'menu_item_title_unhide' => '_bx_courses_menu_item_title_unhide_profile',
+                'grid_action_err_delete' => '_bx_courses_grid_action_err_delete',
+                'grid_txt_account_manager' => '_bx_courses_grid_txt_account_manager',
+                'filter_item_active' => '_bx_courses_grid_filter_item_title_adm_active',
+                'filter_item_hidden' => '_bx_courses_grid_filter_item_title_adm_hidden',
+                'filter_item_pending' => '_bx_courses_grid_filter_item_title_adm_pending',
+                'filter_item_suspended' => '_bx_courses_grid_filter_item_title_adm_suspended',
+                'filter_item_select_one_filter1' => '_bx_courses_grid_filter_item_title_adm_select_one_filter1',
+                'filter_item_select_one_filter2' => '_bx_courses_grid_filter_item_title_adm_select_one_filter2',
+                'menu_item_manage_my' => '_bx_courses_menu_item_title_manage_my',
+                'menu_item_manage_all' => '_bx_courses_menu_item_title_manage_all',
+                'menu_item_title_sm_join' => '_bx_courses_menu_item_title_sm_join',
+                'menu_item_title_sm_join_paid' => '_bx_courses_menu_item_title_sm_join_paid',
+                'menu_item_title_sm_join_requested' => '_bx_courses_menu_item_title_become_fan_sent',
+                'menu_item_title_sm_join_confirm' => '_bx_courses_menu_item_title_sm_join_confirm',
+                'menu_item_title_sm_leave' => '_bx_courses_menu_item_title_sm_leave',
+                'menu_item_title_sm_leave_cancel' => '_bx_courses_menu_item_title_sm_leave_cancel',
+                'menu_item_title_sm_leave_reject' => '_bx_courses_menu_item_title_sm_leave_reject',
+                'txt_all_entries_by_author' => '_bx_courses_page_title_browse_by_author',
+                'txt_invitation_popup_title' => '_bx_courses_txt_invite_popup_title',
+                'txt_invitation_popup_text' => '_bx_courses_txt_invite_popup_text',
+                'txt_invitation_popup_accept_button' => '_bx_courses_txt_invite_popup_button_accept',
+                'txt_invitation_popup_decline_button' => '_bx_courses_txt_invite_popup_button_decline',
+                'txt_invitation_popup_error_invitation_absent' => '_bx_courses_txt_invite_popup_error_invitation_absent',
+                'txt_invitation_popup_error_wrong_user' => '_bx_courses_txt_invite_popup_error_invitation_wrong_user',
+                'txt_n_unit' => '_bx_courses_txt_n_unit',
+                'txt_buy_title' => '_bx_courses_grid_action_title_buy_title',
+                'txt_cart_item_title' => '_bx_courses_txt_cart_item_title',
+                'txt_cart_item_title_lifetime' => '_bx_courses_txt_cart_item_title_lifetime',
+                'txt_subscribe_title' => '_bx_courses_grid_action_title_subscribe_title',
+                'msg_performed' => '_bx_courses_msg_performed',
+                'popup_title_price_add' => '_bx_courses_popup_title_price_add',
+                'popup_title_price_edit' => '_bx_courses_popup_title_price_edit',
+                'popup_title_content_node_add' => '_bx_courses_popup_title_cnt_add',
+                'popup_title_content_node_edit' => '_bx_courses_popup_title_cnt_edit',
+                'err_period_unit' => '_bx_courses_form_price_input_err_period_unit',
+                'err_price_duplicate' => '_bx_courses_err_price_duplicate',
+                'err_cannot_perform' => '_bx_courses_err_cannot_perform',
+            ),
+
+        );
+
+        $this->_aJsClasses = [
+            'main' => 'BxCoursesMain',
+            'entry' => 'BxCoursesEntry',
+            'manage_tools' => 'BxCoursesManageTools',
+            'invite_popup' => 'BxCoursesInvitePopup',
+            'prices' => 'BxCoursesPrices'
+        ];
+
+        $this->_aJsObjects = [
+            'main' => 'oBxCoursesMain',
+            'entry' => 'oBxCoursesEntry',
+            'manage_tools' => 'oBxCoursesManageTools',
+            'invite_popup' => 'oBxCoursesInvitePopup',
+            'prices' => 'oBxCoursesPrices'
+        ];
+
+        $this->_aGridObjects = [
+            'common' => $this->CNF['OBJECT_GRID_COMMON'],
+            'administration' => $this->CNF['OBJECT_GRID_ADMINISTRATION'],
+        ];
+
+        $sHtmlPrefix = str_replace('_', '-', $this->_sName);
+        $this->_aHtmlIds = [
+            'popup_content_node' => $sHtmlPrefix . '-popup-content-node',
+            'popup_content_data' => $sHtmlPrefix . '-popup-content-data',
+        ];
+
+        $this->_aPerPageDefault = [
+            'default' => 12,
+            'structure_l1' => !$this->_bIsApi ? 2 : 9999
+        ];
+    }
+
+    public function getPerPageDefault($sType)
+    {
+        if(!isset($this->_aPerPageDefault[$sType]))
+            $sType = 'default';
+
+        return (int)$this->_aPerPageDefault[$sType];
+    }
+
+    public function isContent()
+    {
+        return getParam($this->CNF['PARAM_CONTENT']) == 'on';
+    }
+
+    public function getContentModules($iUsage)
+    {
+        return explode(',', getParam($this->CNF['PARAM_CONTENT_MODULES_' . $this->getUsageI2S($iUsage, false)]));
+    }
+
+    public function getContentLevelMax()
+    {
+        return (int)getParam($this->CNF['PARAM_CONTENT_LEVEL_MAX']);
+    }
+
+    public function getContentLevel2Node($bSingle = true)
+    {
+        $aResult = [];
+
+        $sPostfix = $bSingle ? 'single' : 'plural';
+        switch($this->getContentLevelMax()) {
+            case 1:
+                $aResult = [
+                    1 => _t('_bx_courses_txt_sample_l3_' . $sPostfix)
+                ];
+                break;
+            
+            case 2:
+                $aResult = [
+                    1 => _t('_bx_courses_txt_sample_l1_' . $sPostfix),
+                    2 => _t('_bx_courses_txt_sample_l3_' . $sPostfix)
+                ];
+                break;
+            
+            case 3:
+                $aResult = [
+                    1 => _t('_bx_courses_txt_sample_l1_' . $sPostfix),
+                    2 => _t('_bx_courses_txt_sample_l2_' . $sPostfix),
+                    3 => _t('_bx_courses_txt_sample_l3_' . $sPostfix)
+                ];
+                break;
+        }
+        
+        return $aResult;
+    }
+    
+    public function getContentNodeTitle($iLevel, $bSingle = true)
+    {
+        $aLevelToNode = $this->getContentLevel2Node($bSingle);
+        return isset($aLevelToNode[$iLevel]) ? $aLevelToNode[$iLevel] : _t('_undefined');
+    }
+
+    public function getUsageI2S($iUsage, $bLowerCase = true)
+    {
+        $a = [
+            BX_COURSES_CND_USAGE_ST => 'st',
+            BX_COURSES_CND_USAGE_AT => 'at'
+        ];
+
+        if(!isset($a[$iUsage]))
+            $iUsage = BX_COURSES_CND_USAGE_ST;        
+
+        return $bLowerCase ? $a[$iUsage] : strtoupper($a[$iUsage]);
+    }
+}
+
+/** @} */

+ 464 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesDb.php

@@ -0,0 +1,464 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Courses module database queries
+ */
+class BxCoursesDb extends BxBaseModGroupsDb
+{
+    public function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+    }
+
+    public function getContentNodes($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+
+        $sSelectClause = "`tcn`.*";
+        $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        switch($aParams['sample']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'id' => $aParams['id']
+                ];
+
+                $sWhereClause = "AND `tcn`.`id`=:id";
+                break;
+
+            case 'id_full':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'id' => $aParams['id']
+                ];
+
+                $sSelectClause .= ", `tcs`.`parent_id`, `tcs`.`level`, `tcs`.`order`, `tcs`.`cn_l2`, `tcs`.`cn_l3`";
+                $sJoinClause = "LEFT JOIN `" . $CNF['TABLE_CNT_STRUCTURE'] . "` AS `tcs` ON `tcn`.`id`=`tcs`.`node_id`";
+                $sWhereClause = "AND `tcn`.`id`=:id";
+                break;
+        }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = "ORDER BY " . $sOrderClause;
+        
+        if(!empty($sLimitClause))
+            $sLimitClause = "LIMIT " . $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_CNT_NODES'] . "` AS `tcn` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function updateContentNodes($aParamsSet, $aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        return $this->query("UPDATE `" . $CNF['TABLE_CNT_NODES'] . "` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function deleteContentNodes($aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsWhere))
+            return false;
+
+        return $this->query("DELETE FROM `" . $CNF['TABLE_CNT_NODES'] . "` WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function deleteContentNodesWithTracks($iEntryId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->query("DELETE FROM `tcn`, `tcnu` USING `" . $CNF['TABLE_CNT_NODES'] . "` AS `tcn` LEFT JOIN `" . $CNF['TABLE_CNT_NODES2USERS'] . "` AS `tcnu` ON `tcn`.`id`=`tcnu`.`node_id` WHERE `tcn`.`entry_id`=:entry_id", [
+            'entry_id' => $iEntryId
+        ]);
+    }
+
+    public function insertContentNodes2Users($aParamsSet)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet))
+            return false;
+
+        return (int)$this->query("INSERT INTO `" . $CNF['TABLE_CNT_NODES2USERS'] . "` SET " . $this->arrayToSQL($aParamsSet) . " ON DUPLICATE KEY UPDATE `date`=UNIX_TIMESTAMP()") > 0 ? (int)$this->lastId() : false;
+    }
+
+    public function deleteContentNodes2Users($aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsWhere))
+            return false;
+
+        return $this->query("DELETE FROM `" . $CNF['TABLE_CNT_NODES2USERS'] . "` WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function getContentStructure($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+
+        $sSelectClause = "`tcs`.*";
+        $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        switch($aParams['sample']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'id' => $aParams['id']
+                ];
+
+                $sWhereClause = "AND `tcs`.`id`=:id";
+                break;
+
+            case 'node_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'node_id' => $aParams['node_id']
+                ];
+
+                $sWhereClause = "AND `tcs`.`node_id`=:node_id";
+                break;
+            
+            case 'entry_id':
+                $aMethod['params'][1] = [
+                    'entry_id' => $aParams['entry_id']
+                ];
+
+                $sWhereClause = "AND `tcs`.`entry_id`=:entry_id";
+
+                if(isset($aParams['level'])) {
+                    $aMethod['params'][1]['level'] = $aParams['level'];
+                        
+                    $sWhereClause .= " AND `tcs`.`level`=:level";
+                }
+                break;
+
+            case 'entry_id_full':
+                $aMethod['params'][1] = [
+                    'entry_id' => $aParams['entry_id']
+                ];
+
+                $sSelectClause .= ", `tcn`.`title`, `tcn`.`counters`";
+                $sJoinClause = "LEFT JOIN `" . $CNF['TABLE_CNT_NODES'] . "` AS `tcn` ON `tcs`.`node_id`=`tcn`.`id`";
+                $sWhereClause = "AND `tcs`.`entry_id`=:entry_id";
+
+                if(isset($aParams['parent_id'])) {
+                    $aMethod['params'][1]['parent_id'] = $aParams['parent_id'];
+                        
+                    $sWhereClause .= " AND `tcs`.`parent_id`=:parent_id";
+                }
+
+                if(isset($aParams['level'])) {
+                    $aMethod['params'][1]['level'] = $aParams['level'];
+                        
+                    $sWhereClause .= " AND `tcs`.`level`=:level";
+                }
+
+                if(isset($aParams['status'])) {
+                    $aMethod['params'][1]['status'] = $aParams['status'];
+                        
+                    $sWhereClause .= " AND `tcn`.`status`=:status";
+                }
+
+                if(isset($aParams['start'], $aParams['per_page']) && (int)$aParams['per_page'] != 0)
+                    $sLimitClause = $aParams['start'] . ', ' . $aParams['per_page'];
+                
+                $sOrderClause = "`tcs`.`order` ASC";
+                break;
+                
+            case 'entry_id_counters':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'entry_id' => $aParams['entry_id']
+                ];
+
+                $sSelectClause = "COUNT(`tcs`.`id`) AS `cn_l1`, SUM(`tcs`.`cn_l2`) AS `cn_l2`, SUM(`tcs`.`cn_l3`) AS `cn_l3`";
+                $sWhereClause = "AND `tcs`.`entry_id`=:entry_id AND `tcs`.`level`='1'";
+                $sGroupClause = "`tcs`.`entry_id`";
+                break;
+                
+            case 'parent_id':
+                $aMethod['params'][1] = [
+                    'parent_id' => $aParams['parent_id']
+                ];
+
+                $sWhereClause = "AND `tcs`.`parent_id`=:parent_id";
+                break;
+
+            case 'user_passed':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'profile_id' => $aParams['profile_id'],
+                    'node_id' => $aParams['node_id']
+                ];
+
+                $sSelectClause = "`tcnu`.*";
+                $sJoinClause = "INNER JOIN `" . $CNF['TABLE_CNT_NODES2USERS'] . "` AS `tcnu` ON `tcs`.`node_id`=`tcnu`.`node_id` AND `tcnu`.`node_id`=:node_id AND `tcnu`.`profile_id`=:profile_id";
+                break;
+
+            case 'user_track':
+                $aMethod['params'][1] = [
+                    'profile_id' => $aParams['profile_id'],
+                    'entry_id' => $aParams['entry_id'],
+                    'node_id' => $aParams['node_id']
+                ];
+
+                $sJoinClause = "INNER JOIN `" . $CNF['TABLE_CNT_NODES2USERS'] . "` AS `tcnu` ON `tcs`.`node_id`=`tcnu`.`node_id` AND `tcnu`.`profile_id`=:profile_id";
+                $sWhereClause = "AND `tcs`.`entry_id`=:entry_id AND `tcs`.`parent_id`=:node_id";
+                break;
+        }
+
+        if(!empty($sGroupClause))
+            $sGroupClause = "GROUP BY " . $sGroupClause;
+
+        if(!empty($sOrderClause))
+            $sOrderClause = "ORDER BY " . $sOrderClause;
+        
+        if(!empty($sLimitClause))
+            $sLimitClause = "LIMIT " . $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_CNT_STRUCTURE'] . "` AS `tcs` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getContentStructureOrderMax($iEntryId, $iParentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return (int)$this->getOne("SELECT IFNULL(MAX(`order`), 0) FROM `" . $CNF['TABLE_CNT_STRUCTURE'] . "` WHERE `entry_id`=:entry_id AND `parent_id`=:parent_id", [
+            'entry_id' => $iEntryId,
+            'parent_id' => $iParentId
+        ]);
+    }
+    
+    public function insertContentStructureNode($aParamsSet)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet))
+            return false;
+
+        return (int)$this->query("INSERT INTO `" . $CNF['TABLE_CNT_STRUCTURE'] . "` SET " . $this->arrayToSQL($aParamsSet)) > 0 ? (int)$this->lastId() : false;
+    }
+
+    public function updateContentStructureNode($aParamsSet, $aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        return $this->query("UPDATE `" . $CNF['TABLE_CNT_STRUCTURE'] . "` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+    
+    public function updateContentStructureCounters($iParentId, $iLevel, $iAdd)
+    {
+        $aParent = $this->getContentStructure(['sample' => 'node_id', 'node_id' => $iParentId]);
+        if(empty($aParent) || !is_array($aParent))
+            return;
+
+        $sKeyLevel = 'cn_l' . $iLevel;
+        $this->updateContentStructureNode([$sKeyLevel => $aParent[$sKeyLevel] + $iAdd], ['id' => $aParent['id']]);
+        
+        if(!empty($aParent['parent_id']))
+            $this->updateContentStructureCounters($aParent['parent_id'], $iLevel, $iAdd);
+    }
+
+    public function deleteContentStructureNode($aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsWhere))
+            return false;
+
+        return $this->query("DELETE FROM `" . $CNF['TABLE_CNT_STRUCTURE'] . "` WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function getContentData($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+
+        $sSelectClause = "`tcd`.*";
+        $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        switch($aParams['sample']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'id' => $aParams['id']
+                ];
+
+                $sWhereClause = "AND `tcd`.`id`=:id";
+                break;
+            
+            case 'content':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'content_type' => $aParams['content_type'],
+                    'content_id' => $aParams['content_id']
+                ];
+
+                $sWhereClause = "AND `tcd`.`content_type`=:content_type AND `tcd`.`content_id`=:content_id";
+                break;
+
+            case 'entry_id':
+                $aMethod['params'][1] = [
+                    'entry_id' => $aParams['entry_id']
+                ];
+
+                $sWhereClause = "AND `tcd`.`entry_id`=:entry_id";
+                break;
+
+            case 'node_id':
+                $aMethod['params'][1] = [
+                    'node_id' => $aParams['node_id']
+                ];
+
+                $sWhereClause = "AND `tcd`.`node_id`=:node_id";
+                $sOrderClause = "`tcd`.`order` ASC";
+                break;
+
+            case 'entry_node_ids':
+                $aMethod['params'][1] = [
+                    'entry_id' => $aParams['entry_id'],
+                    'node_id' => $aParams['node_id']
+                ];
+
+                $sWhereClause = "AND `tcd`.`entry_id`=:entry_id AND `tcd`.`node_id`=:node_id";
+
+                if(isset($aParams['usage'])) {
+                    $aMethod['params'][1]['usage'] = $aParams['usage'];
+
+                    $sWhereClause .= " AND `tcd`.`usage`=:usage";
+                }
+
+                $sOrderClause = "`tcd`.`order` ASC";
+                break;
+                
+            case 'siblings':
+                $aMethod['name'] = 'getAllWithKey';
+                $aMethod['params'][1] = 'order';
+                $aMethod['params'][2] = [
+                    'entry_id' => $aParams['entry_id'],
+                    'node_id' => $aParams['node_id'],
+                    'usage' => $aParams['usage'],
+                    'order' => $aParams['order']
+                ];
+                $sWhereClause = "AND `tcd`.`entry_id`=:entry_id AND `tcd`.`node_id`=:node_id AND `tcd`.`usage`=:usage AND (`tcd`.`order`=(:order - 1) OR `tcd`.`order`=(:order + 1))";
+                $sOrderClause = "`tcd`.`order` ASC";
+                break;
+
+            case 'user_passed':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'profile_id' => $aParams['profile_id'],
+                    'data_id' => $aParams['data_id']
+                ];
+
+                $sSelectClause = "`tcdu`.*";
+                $sJoinClause = "INNER JOIN `" . $CNF['TABLE_CNT_DATA2USERS'] . "` AS `tcdu` ON `tcd`.`id`=`tcdu`.`data_id` AND `tcdu`.`data_id`=:data_id AND `tcdu`.`profile_id`=:profile_id";
+                break;
+
+            case 'user_track':
+                $aMethod['params'][1] = [
+                    'profile_id' => $aParams['profile_id'],
+                    'entry_id' => $aParams['entry_id'],
+                    'node_id' => $aParams['node_id']
+                ];
+
+                $sJoinClause = "INNER JOIN `" . $CNF['TABLE_CNT_DATA2USERS'] . "` AS `tcdu` ON `tcd`.`id`=`tcdu`.`data_id` AND `tcdu`.`profile_id`=:profile_id";
+                $sWhereClause = "AND `tcd`.`entry_id`=:entry_id AND `tcd`.`node_id`=:node_id AND `tcd`.`usage`='" . BX_COURSES_CND_USAGE_ST . "'";
+                break;
+        }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = "ORDER BY " . $sOrderClause;
+        
+        if(!empty($sLimitClause))
+            $sLimitClause = "LIMIT " . $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_CNT_DATA'] . "` AS `tcd` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getContentDataOrderMax($iEntryId, $iNodeId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return (int)$this->getOne("SELECT IFNULL(MAX(`order`), 0) FROM `" . $CNF['TABLE_CNT_DATA'] . "` WHERE `entry_id`=:entry_id AND `node_id`=:node_id", [
+            'entry_id' => $iEntryId,
+            'node_id' => $iNodeId
+        ]);
+    }
+
+    public function insertContentData($aParamsSet)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet))
+            return false;
+
+        return (int)$this->query("INSERT INTO `" . $CNF['TABLE_CNT_DATA'] . "` SET " . $this->arrayToSQL($aParamsSet)) > 0 ? (int)$this->lastId() : false;
+    }
+
+    public function deleteContentData($aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsWhere))
+            return false;
+
+        return $this->query("DELETE FROM `" . $CNF['TABLE_CNT_DATA'] . "` WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function deleteContentDataWithTracks($iEntryId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->query("DELETE FROM `tcd`, `tcdu` USING `" . $CNF['TABLE_CNT_DATA'] . "` AS `tcd` LEFT JOIN `" . $CNF['TABLE_CNT_DATA2USERS'] . "` AS `tcdu` ON `tcd`.`id`=`tcdu`.`data_id` WHERE `tcd`.`entry_id`=:entry_id", [
+            'entry_id' => $iEntryId
+        ]);
+    }
+
+    public function insertContentData2Users($aParamsSet)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet))
+            return false;
+
+        return (int)$this->query("INSERT INTO `" . $CNF['TABLE_CNT_DATA2USERS'] . "` SET " . $this->arrayToSQL($aParamsSet) . " ON DUPLICATE KEY UPDATE `date`=UNIX_TIMESTAMP()") > 0 ? (int)$this->lastId() : false;
+    }
+
+    public function deleteContentData2Users($aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsWhere))
+            return false;
+
+        return $this->query("DELETE FROM `" . $CNF['TABLE_CNT_DATA2USERS'] . "` WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+}
+
+/** @} */

+ 38 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesFormEntry.php

@@ -0,0 +1,38 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Create/Edit Group Form.
+ */
+class BxCoursesFormEntry extends BxBaseModGroupsFormEntry
+{
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        $this->MODULE = 'bx_courses';
+
+        parent::__construct($aInfo, $oTemplate);
+
+        if(isset($this->aInputs['initial_members']))
+            $this->aInputs['initial_members']['value'] = [];
+    }
+
+    public function insert ($aValsToAdd = [], $isIgnore = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if($this->_oModule->_oConfig->isContent())
+            $aValsToAdd[$CNF['FIELD_STATUS']] = 'hidden';
+
+        return parent::insert ($aValsToAdd, $isIgnore);
+    }
+}
+
+/** @} */

+ 265 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesGridCntDataManage.php

@@ -0,0 +1,265 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxCoursesGridCntDataManage extends BxTemplGrid
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_sPageUrl;
+    protected $_iEntryId;
+    protected $_iEntryPid;
+
+    protected $_iNodeId;
+    protected $_aNodeInfo;
+
+    protected $_iLevelMax;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_courses';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_iEntryId = 0;
+        if(($iEntryId = bx_get('entry_id')) !== false)
+            $this->setEntryId($iEntryId);
+
+        $this->setNodeId(($iNodeId = bx_get('parent_id')) !== false ? $iNodeId : 0);            
+
+        $this->_iLevelMax = $this->_oModule->_oConfig->getContentLevelMax();
+    }
+
+    public function setEntryId($iEntryId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_iEntryId = (int)$iEntryId;
+        $this->_iEntryPid = ($oProfile = BxDolProfile::getInstanceByContentAndType($this->_iEntryId, $this->_sModule)) !== false ? $oProfile->id() : 0;
+
+        $this->_sPageUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_MANAGE_STRUCTURE'], ['profile_id' => $this->_iEntryPid]);
+
+        $this->_aQueryAppend['entry_id'] = $this->_iEntryId;
+    }
+    
+    public function setNodeId($iNodeId)
+    {
+        $this->_iNodeId = (int)$iNodeId;
+
+        $this->_aNodeInfo = [];
+        if($this->_iNodeId)
+            $this->_aNodeInfo = $this->_oModule->_oDb->getContentNodes(['sample' => 'id_full', 'id' => $this->_iNodeId]);
+
+        $this->_aQueryAppend['parent_id'] = $this->_iNodeId;
+    }
+
+    public function getCode ($isDisplayHeader = true)
+    {
+        if(empty($this->_aNodeInfo) || !is_array($this->_aNodeInfo) || $this->_aNodeInfo['level'] != $this->_iLevelMax)
+            return '';
+
+        return parent::getCode($isDisplayHeader);
+    }
+
+    public function getCodeAPI($bForceReturn = false)
+    {
+        if(empty($this->_aNodeInfo) || !is_array($this->_aNodeInfo) || $this->_aNodeInfo['level'] != $this->_iLevelMax)
+            return [];
+
+        return parent::getCodeAPI($bForceReturn);
+    }    
+
+    protected function _getCellContentId($mixedValue, $sKey, $aField, $aRow)
+    {
+        $iContentId = (int)$mixedValue;
+        $mixedValue = '';
+
+        $sModule = $aRow['content_type'];
+        if(($sMethod = 'get_title') && bx_is_srv($sModule, $sMethod))
+            $mixedValue = bx_srv($sModule, $sMethod, [$iContentId]);
+        if(!$mixedValue && ($sMethod = 'get_text') &&  bx_is_srv($sModule, $sMethod))
+            $mixedValue = bx_srv($sModule, $sMethod, [$iContentId]);
+        if(!$mixedValue)
+            $mixedValue = _t('_undefined');
+
+        if(($sMethod = 'get_link') && bx_is_srv($sModule, $sMethod))
+            $mixedValue = $this->_oModule->_oTemplate->parseHtmlByName('name_link.html', [
+                'href' => bx_srv($sModule, $sMethod, [$iContentId]),
+                'title' => bx_html_attribute($mixedValue),
+                'content' => $mixedValue
+            ]);
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellContentType($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(_t('_' . $mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellUsage($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(_t('_bx_courses_txt_data_usage_' . $mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAdded($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'time', 'data' => $mixedValue];
+
+        return parent::_getCellDefault(bx_time_js($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionBack($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = [])
+    {
+        if(empty($this->_aNodeInfo))
+            return $this->_bIsApi ? [] : '';
+
+        $sUrl = $this->_sPageUrl;
+        if(!empty($this->_aNodeInfo['parent_id']))
+            $sUrl = bx_append_url_params($sUrl, ['parent_id' => $this->_aNodeInfo['parent_id']]);
+        
+        if($this->_bIsApi)
+            return array_merge($a, ['name' => $sKey, 'type' => 'link', 'link' => bx_api_get_relative_url($sUrl)]);
+
+        $a['attr'] = array_merge($a['attr'], [
+            "onclick" => "window.open('" . $sUrl . "', '_self');"
+    	]);
+
+    	return $this->_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionAddSt($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = [])
+    {
+        if($this->_bIsApi)
+            return array_merge($a, ['name' => $sKey, 'type' => 'menu', 'action' => $sKey, 'values' => $this->_getValues(BX_COURSES_CND_USAGE_ST)]);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $a['attr'] = array_merge($a['attr'], [
+            "onclick" => "$(this).off('click'); bx_menu_popup('" . $CNF['OBJECT_MENU_CONTENT_ADD'] . "', this, {removeOnClose: 1}, {entry_pid: " . $this->_iEntryPid . ", node_id: " . $this->_iNodeId . ", usage:0});"
+    	]);
+    	return $this->_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionAddAt($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = [])
+    {
+        if($this->_bIsApi)
+            return array_merge($a, ['name' => $sKey, 'type' => 'menu', 'action' => $sKey, 'values' => $this->_getValues(BX_COURSES_CND_USAGE_AT)]);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $a['attr'] = array_merge($a['attr'], [
+            "onclick" => "$(this).off('click'); bx_menu_popup('" . $CNF['OBJECT_MENU_CONTENT_ADD'] . "', this, {removeOnClose: 1}, {entry_pid: " . $this->_iEntryPid . ", node_id: " . $this->_iNodeId . ", usage:1});"
+    	]);
+    	return $this->_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionEdit($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = [])
+    {
+        $oModule = BxDolModule::getInstance($aRow['content_type']);
+        if(!$oModule)
+            return '';
+        
+        $CNF_MODULE = &$oModule->_oConfig->CNF;
+        if(!isset($CNF_MODULE['URI_EDIT_ENTRY']))
+            return '';
+
+        $sUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF_MODULE['URI_EDIT_ENTRY'] . '&id=' . $aRow['content_id']));
+
+        if($this->_bIsApi)
+            return array_merge($a, [
+                'name' => $sKey, 
+                'type' => 'modal', 
+                'callback' => $aRow['content_type'] . '/entity_edit/&params[]=' . $aRow['content_id'],
+                'content_type' => $aRow['content_type']
+            ]);
+
+    	$a['attr'] = array_merge($a['attr'], [
+            "onclick" => "window.open('" . $sUrl . "', '_self');"
+    	]);
+    	return $this->_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _delete($mixedId)
+    {
+        $aData = $this->_oModule->_oDb->getContentData(['sample' => 'id', 'id' => (int)$mixedId]);
+        if(!empty($aData) && is_array($aData)) {
+            $sModule = $aData['content_type'];
+            $sMethod = 'delete_entity';
+            if(bx_is_srv($sModule, $sMethod) && bx_srv($sModule, $sMethod, [$aData['content_id']]) != '')
+                return false;
+        }
+
+        return parent::_delete($mixedId) !== false;
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if(!$this->_iEntryId)
+            return [];
+
+        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `entry_id`=? AND `node_id`=?", $this->_iEntryId, $this->_iNodeId);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+
+    protected function _getIds()
+    {
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            $iId = (int)bx_get('id');
+            if(!$iId) 
+                return false;
+
+            $aIds = [$iId];
+        }
+
+        return $aIds;
+    }
+
+    protected function _getValues($iUsage)
+    {
+        $aValues = [];
+        $oPermalink = BxDolPermalinks::getInstance();
+
+        $aModules = $this->_oModule->_oConfig->getContentModules($iUsage);
+        foreach($aModules as $sModule) {
+            $oModule = BxDolModule::getInstance($sModule);
+            if(!$oModule)
+                continue;
+
+            $CNF = &$oModule->_oConfig->CNF;
+            if(!isset($CNF['URI_ADD_ENTRY']))
+                continue;
+
+            $aParams = [
+                'context_pid' => $this->_iEntryPid,
+                'context_nid' => $this->_iNodeId,
+                'context_usage' => $iUsage
+            ];
+
+            $aValues[] = [
+                'key' => $sModule, 
+                'title' => _t('_' . $sModule),
+                'link' => bx_api_get_relative_url(BX_DOL_URL_ROOT . $oPermalink->permalink('page.php?i=' . $CNF['URI_ADD_ENTRY'], $aParams)), 
+                'callback' => $sModule . '/entity_create/&params[]=' . json_encode($aParams)
+            ];
+        }
+
+        return $aValues;
+    }
+}
+
+/** @} */

+ 421 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesGridCntStructureManage.php

@@ -0,0 +1,421 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxCoursesGridCntStructureManage extends BxTemplGrid
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_sPageUrl;
+    protected $_iEntryId;
+    protected $_iEntryPid;
+
+    protected $_iParentId;
+    protected $_aParentInfo;
+
+    protected $_iLevel;
+    protected $_iLevelMax;
+    protected $_aLevelToNode;
+    protected $_aLevelToNodePl;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_courses';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_iEntryId = 0;
+        if(($iEntryId = bx_get('entry_id')) !== false)
+            $this->setEntryId($iEntryId);
+
+        $this->setParentId(($iParentId = bx_get('parent_id')) !== false ? $iParentId : 0);            
+
+        $this->_iLevel = $this->_getNodeLevel();
+        $this->_iLevelMax = $this->_oModule->_oConfig->getContentLevelMax();
+        $this->_aLevelToNode = $this->_oModule->_oConfig->getContentLevel2Node();
+        $this->_aLevelToNodePl = $this->_oModule->_oConfig->getContentLevel2Node(false);
+    }
+
+    public function setEntryId($iEntryId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_iEntryId = (int)$iEntryId;
+        $this->_iEntryPid = ($oProfile = BxDolProfile::getInstanceByContentAndType($this->_iEntryId, $this->_sModule)) !== false ? $oProfile->id() : 0;
+
+        $this->_sPageUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_MANAGE_STRUCTURE'], ['profile_id' => $this->_iEntryPid]);
+
+        $this->_aQueryAppend['entry_id'] = $this->_iEntryId;
+    }
+
+    public function setParentId($iParentId)
+    {
+        $this->_iParentId = (int)$iParentId;
+
+        $this->_aParentInfo = [];
+        if($this->_iParentId)
+            $this->_aParentInfo = $this->_oModule->_oDb->getContentNodes(['sample' => 'id_full', 'id' => $this->_iParentId]);
+
+        $this->_aQueryAppend['parent_id'] = $this->_iParentId;
+    }
+
+    public function getCode ($isDisplayHeader = true)
+    {
+        if($this->_iLevel > $this->_iLevelMax)
+            return '';
+
+        return parent::getCode($isDisplayHeader);
+    }
+
+    //TODO: Need to check level and return correct title.
+    public function getFormBlockTitleAPI($sAction, $iId = 0)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sResult = '';
+
+        switch($sAction) {
+            case 'add':
+                $sResult = $CNF['T']['popup_title_content_node_add'];
+                break;
+
+            case 'edit':
+                $sResult = $CNF['T']['popup_title_content_node_edit'];
+                break;
+        }
+
+        return $this->_parseNodeName(_t($sResult));
+    }
+
+    public function getFormCallBackUrlAPI($sAction, $iId = 0)
+    {
+         return '/api.php?r=system/perfom_action_api/TemplServiceGrid/&params[]=&o=' . $this->_sObject . '&a=' . $sAction . '&entry_id=' . $this->_iEntryId . '&parent_id=' . $this->_iParentId . '&id=' . $iId;
+    }
+
+    public function getCodeAPI($bForceReturn = false)
+    {
+        if($this->_iLevel > $this->_iLevelMax)
+            return [];
+
+        return parent::getCodeAPI($bForceReturn);
+    }
+
+    protected function _getActionsAPI ($sType)
+    {
+        $aResult = parent::_getActionsAPI($sType);
+
+        if($aResult && $sType == 'independent') {
+            if(!empty($aResult['add']))
+                $aResult['add']['title'] = $this->_parseNodeName($aResult['add']['title']);
+        }
+
+        return $aResult;
+    }
+
+    public function performActionAdd()
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	$sAction = 'add';
+
+        $aEntryInfo = $this->_oModule->_oDb->getContentInfoById($this->_iEntryId);
+        if(($mixedResult = $this->_oModule->checkAllowedEdit($aEntryInfo)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $this->_getActionResult(['msg' => $mixedResult]);
+
+        $sForm = $CNF['OBJECT_FORM_CNT_NODE_DISPLAY_ADD'];
+    	$oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_CNT_NODE'], $CNF['OBJECT_FORM_CNT_NODE_DISPLAY_ADD']);
+    	$oForm->setId($sForm);
+        $oForm->setName($sForm);
+        $oForm->setAction(BX_DOL_URL_ROOT . bx_append_url_params('grid.php', ['o' => $this->_sObject, 'a' => $sAction, 'entry_id' => $this->_iEntryId, 'parent_id' => $this->_iParentId]));
+        $oForm->setData($this->_iParentId, $this->_iLevel);
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+
+            $iId = (int)$oForm->insert(['entry_id' => $this->_iEntryId, 'added' => time()]);
+            if($iId != 0) {
+                $this->_oModule->_oDb->insertContentStructureNode([
+                    'entry_id' => $this->_iEntryId,
+                    'parent_id' => $this->_iParentId,
+                    'node_id' => $iId,
+                    'level' => $this->_iLevel,
+                    'order' => $this->_oModule->_oDb->getContentStructureOrderMax($this->_iEntryId, $this->_iParentId) + 1
+                ]);
+
+                if($this->_iLevel > 1) 
+                    $this->_oModule->_oDb->updateContentStructureCounters($this->_iParentId, $this->_iLevel, 1);
+
+                $aResult = $this->_bIsApi ? [] : ['grid' => $this->getCode(false), 'blink' => $iId];
+            }
+            else
+                $aResult = ['msg' => _t($CNF['T']['err_cannot_perform'])];
+
+            return $this->_getActionResult($aResult);
+        }
+
+        if($this->_bIsApi)
+            return $this->getFormBlockAPI($oForm, $sAction);
+
+        bx_import('BxTemplFunctions');
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('popup_content_node'), $this->_parseNodeName(_t($CNF['T']['popup_title_content_node_add'])), $this->_oModule->_oTemplate->parseHtmlByName('popup_content_node.html', [
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        ]));
+
+        echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
+    public function performActionEdit()
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sAction = 'edit';
+
+        $aEntryInfo = $this->_oModule->_oDb->getContentInfoById($this->_iEntryId);
+        if(($mixedResult = $this->_oModule->checkAllowedEdit($aEntryInfo)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $this->_getActionResult(['msg' => $mixedResult]);
+
+        $aIds = $this->_getIds();
+        if($aIds === false)
+            return $this->_getActionResult([]);
+
+        $aNode = $this->_oModule->_oDb->getContentNodes(['sample' => 'id', 'id' => array_shift($aIds)]);
+        if(!is_array($aNode) || empty($aNode))
+            return $this->_getActionResult([]);
+
+        $sForm = $CNF['OBJECT_FORM_CNT_NODE_DISPLAY_EDIT'];
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_CNT_NODE'], $CNF['OBJECT_FORM_CNT_NODE_DISPLAY_EDIT']);
+        $oForm->setId($sForm);
+        $oForm->setName($sForm);
+    	$oForm->setAction(BX_DOL_URL_ROOT . bx_append_url_params('grid.php', ['o' => $this->_sObject, 'a' => $sAction, 'entry_id' => $this->_iEntryId, 'parent_id' => $this->_iParentId, 'id' => $aNode['id']]));
+        $oForm->setData($this->_iParentId);
+
+        $oForm->initChecker($aNode);
+        if($oForm->isSubmittedAndValid()) {
+            if($oForm->update($aNode['id']) !== false)
+                $aResult = $this->_bIsApi ? [] : ['grid' => $this->getCode(false), 'blink' => $aNode['id']];
+            else
+                $aResult = ['msg' => _t($CNF['T']['err_cannot_perform'])];
+
+            return $this->_getActionResult($aResult);
+        }
+
+        if($this->_bIsApi)
+            return $this->getFormBlockAPI($oForm, $sAction, $aNode['id']);
+
+        bx_import('BxTemplFunctions');
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('popup_content_node'), $this->_parseNodeName(_t($CNF['T']['popup_title_content_node_edit'])), $this->_oModule->_oTemplate->parseHtmlByName('popup_content_node.html', [
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
+    protected function _getCellHeaderCnL2($sKey, $aField)
+    {
+        if($this->_iLevelMax < 2 || $this->_iLevel >= 2)
+            return $this->_bIsApi ? [] : '';
+
+        $aField['title'] = ucfirst($this->_aLevelToNodePl[2]);
+        return parent::_getCellHeaderDefault($sKey, $aField);
+    }
+
+    protected function _getCellHeaderCnL3($sKey, $aField)
+    {
+        if($this->_iLevelMax < 3 || $this->_iLevel >= 3)
+            return $this->_bIsApi ? [] : '';
+
+        $aField['title'] = ucfirst($this->_aLevelToNodePl[3]);
+        return parent::_getCellHeaderDefault($sKey, $aField);
+    }
+
+    protected function _getCellHeaderCounters($sKey, $aField)
+    {
+        if($this->_iLevel != $this->_iLevelMax)
+            return $this->_bIsApi ? [] : '';
+
+        return parent::_getCellHeaderDefault($sKey, $aField);
+    }
+
+    protected function _getCellTitle($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sLink = bx_append_url_params($this->_sPageUrl, ['parent_id' => $aRow['id']]);
+
+        if($this->_bIsApi)
+            return ['type' => 'link', 'data' => [
+                'text' => $mixedValue,
+                'url' => bx_api_get_relative_url($sLink)
+            ]];
+
+        $mixedValue = $this->_oModule->_oTemplate->parseHtmlByName('name_link.html', [
+            'href' => $sLink,
+            'title' => bx_html_attribute($mixedValue),
+            'content' => $mixedValue
+        ]);
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellCnL2($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_iLevelMax < 2 || $this->_iLevel >= 2)
+            return $this->_bIsApi ? [] : '';
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellCnL3($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_iLevelMax < 3 || $this->_iLevel >= 3)
+            return $this->_bIsApi ? [] : '';
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellCounters($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_iLevel != $this->_iLevelMax)
+            return $this->_bIsApi ? [] : '';
+
+        $mixedCounters = '';
+        if(!empty($mixedValue) && ($aCounters = json_decode(html_entity_decode($mixedValue), true)))
+            $mixedCounters = $this->_oModule->_oTemplate->getCounters($aCounters);
+
+        if($this->_bIsApi) {
+            $sResult = '';
+            foreach($mixedCounters as $sUsage => $aCounters) {
+                foreach($aCounters as $iKey => $aCounter)
+                    $aCounters[$iKey] = $aCounter['value'] . ' ' . $aCounter['title'];
+                
+                $sResult .= ucfirst(_t('_bx_courses_txt_' . $sUsage)) . ': ' . implode(', ', $aCounters) . ' ';
+            }
+
+            return ['type' => 'text', 'value' => trim($sResult)];
+        }
+
+        return parent::_getCellDefault($mixedCounters, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAdded($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'time', 'data' => $mixedValue];
+
+        return parent::_getCellDefault(bx_time_js($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionBack($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = [])
+    {
+        if(empty($this->_aParentInfo))
+            return $this->_bIsApi ? [] : '';
+
+        $sUrl = $this->_sPageUrl;
+        if(!empty($this->_aParentInfo['parent_id']))
+            $sUrl = bx_append_url_params($sUrl, ['parent_id' => $this->_aParentInfo['parent_id']]);
+
+        if($this->_bIsApi)
+            return array_merge($a, ['name' => $sKey, 'type' => 'link', 'link' => bx_api_get_relative_url($sUrl)]);
+
+        $a['attr'] = array_merge($a['attr'], [
+            "onclick" => "window.open('" . $sUrl . "','_self');"
+    	]);
+
+    	return $this->_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionAdd($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = [])
+    {
+        if($this->_iLevel > $this->_iLevelMax)
+            return '';
+
+        $a['title'] = $this->_parseNodeName($a['title']);
+    	return $this->_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _delete($mixedId)
+    {
+        if($this->_iLevel > 1) 
+            $this->_oModule->_oDb->updateContentStructureCounters($this->_iParentId, $this->_iLevel, -1);
+
+        $this->_oModule->_oDb->deleteContentStructureNode(['node_id' => (int)$mixedId]);
+
+        return parent::_delete($mixedId);
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if(!$this->_iEntryId)
+            return [];
+
+        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `tcn`.`entry_id`=? AND `tcs`.`parent_id`=?", $this->_iEntryId, $this->_iParentId);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+
+    protected function _updateOrder($mixedId, $iOrder)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sTable = $CNF['TABLE_CNT_STRUCTURE'];
+        $sFieldId = 'node_id';
+        $sFieldOrder = $this->_aOptions['field_order'];
+
+        return BxDolDb::getInstance()->query("UPDATE `{$sTable}` SET `{$sFieldOrder}` = :order WHERE `{$sFieldId}` = :id", [
+            'id' => $mixedId,
+            'order' => $iOrder
+        ]);
+    }
+
+    protected function _switcherChecked2State($isChecked)
+    {
+        return $isChecked ? 'active' : 'hidden';
+    }
+
+    protected function _switcherState2Checked($mixedState)
+    {
+        return 'active' == $mixedState ? true : false;
+    }
+
+    protected function _getNodeLevel()
+    {
+        return $this->_oModule->getNodeLevelByParent($this->_aParentInfo);
+    }
+    
+    protected function _parseNodeName($s)
+    {
+        return bx_replace_markers($s, [
+            'node' => isset($this->_aLevelToNode[$this->_iLevel]) ? $this->_aLevelToNode[$this->_iLevel]: _t('_undefined')
+        ]);
+    }
+    protected function _getIds()
+    {
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            $iId = (int)bx_get('id');
+            if(!$iId) 
+                return false;
+
+            $aIds = [$iId];
+        }
+
+        return $aIds;
+    }
+}
+
+/** @} */

+ 48 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesMenuViewActions.php

@@ -0,0 +1,48 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * 'View group' actions menu.
+ */
+class BxCoursesMenuViewActions extends BxBaseModGroupsMenuViewActions
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->MODULE = 'bx_courses';
+
+        parent::__construct($aObject, $oTemplate);
+
+        $this->addMarkers(array(
+            'js_object' => $this->_oModule->_oConfig->getJsObject('entry')
+        ));
+    }
+
+    protected function _getMenuItem ($a)
+    {
+        $aResult = parent::_getMenuItem($a);
+        if(!$aResult)
+            return $aResult;
+
+        if($this->_bIsApi && (($bHide = $a['name'] == 'hide-course-profile') || $a['name'] == 'unhide-course-profile')) {
+            $aResult = array_merge($aResult, [
+                'display_type' => 'callback',
+                'data' => [
+                    'request_url' => $this->MODULE . '/' . ($bHide ? 'hide' : 'publish') . '/&params[]=' . $this->_iContentId, 
+                    'on_callback' => 'change',
+                ]
+            ]);
+        }
+        
+        return $aResult;
+    }
+}
+
+/** @} */

+ 60 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesMenuViewActionsAll.php

@@ -0,0 +1,60 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * View entry all actions menu
+ */
+class BxCoursesMenuViewActionsAll extends BxBaseModGroupsMenuViewActionsAll
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_courses';
+
+        parent::__construct($aObject, $oTemplate);
+    }
+
+    protected function _getMenuItemJoinCourseProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemEditCourseCover($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemEditCourseProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemEditCoursePricing($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemInviteToCourse($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemDeleteCourseProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemApproveCourseProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+}
+
+/** @} */

+ 814 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesModule.php

@@ -0,0 +1,814 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * How to pass a lesson's steps:
+ * 0 - all at once, the order is not important
+ * 1 - alternate, the order is important
+ */
+define('BX_COURSES_CND_PASSING_ALL', 0);
+define('BX_COURSES_CND_PASSING_ALTERNATE', 1);
+
+/**
+ * Content data usage:
+ * st - step,
+ * at - attachment
+ */
+define('BX_COURSES_CND_USAGE_ST', 0);
+define('BX_COURSES_CND_USAGE_AT', 1);
+
+/**
+ * Courses profiles module.
+ */
+class BxCoursesModule extends BxBaseModGroupsModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_aSearchableNamesExcept[] = $this->_oConfig->CNF['FIELD_JOIN_CONFIRMATION'];
+    }
+
+    public function actionPerform()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $mixedContent = $this->_getContent();
+        if($mixedContent === false)
+            return echoJson(['code' => 1]);
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $sAction = bx_process_input(bx_get('action'));
+        if(empty($CNF['MENU_ITEM_TO_METHOD'][$CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE']][$sAction])) 
+            return echoJson(['code' => 2]);
+
+        $sMethodCheck = $CNF['MENU_ITEM_TO_METHOD'][$CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE']][$sAction];
+        $sMethodPerform = '_perform' . bx_gen_method_name($sAction, ['-', '_']);
+        if(!method_exists($this, $sMethodCheck) || $this->$sMethodCheck($aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED || !method_exists($this, $sMethodPerform))
+            return echoJson(['code' => 3, 'msg' => _t('_sys_txt_access_denied')]);
+
+        return echoJson($this->$sMethodPerform($aContentInfo) ? ['reload' => 1] : []);
+    }
+
+    public function actionPassNode()
+    {
+        $iNodeId = bx_get('node_id');
+        if($iNodeId === false)
+            return echoJson([]);
+
+        return echoJson($this->servicePassNode((int)$iNodeId));
+    }
+
+    public function actionPassData()
+    {
+        $iDataId = bx_get('data_id');
+        if($iDataId === false)
+            return echoJson([]);
+
+        return echoJson($this->servicePassData((int)$iDataId));
+    }
+
+    public function serviceGetSafeServices()
+    {
+        return array_merge(parent::serviceGetSafeServices(), [
+            'Hide' => '',
+            'Publish' => '',
+            'PassNode' => '',
+            'PassData' => '',
+        ]);
+    }
+
+    public function serviceHide($iContentId)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+                
+        if($this->checkAllowedHide($aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+        
+        return $this->_performHideCourseProfile($aContentInfo);
+    }
+
+    public function servicePublish($iContentId)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+                
+        if($this->checkAllowedUnhide($aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+        
+        return $this->_performUnhideCourseProfile($aContentInfo);
+    }
+
+    public function servicePassNode($iNodeId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aNode = $this->_oDb->getContentNodes([
+            'sample' => 'id',
+            'id' => $iNodeId
+        ]);
+
+        if(empty($aNode) || !is_array($aNode))
+            return ['code' => 1];
+
+        $iEntryId = (int)$aNode['entry_id'];
+        $iProfileId = bx_get_logged_profile_id();
+        if($this->isNodePassed($iProfileId, $iNodeId)) {
+            $aDataItems = $this->_oDb->getContentData([
+                'sample' => 'entry_node_ids', 
+                'entry_id' => $iEntryId,
+                'node_id' => $iNodeId,
+            ]);
+
+            if(!empty($aDataItems) && is_array($aDataItems))
+                foreach($aDataItems as $aDataItem)
+                    $this->_oDb->deleteContentData2Users(['data_id' => $aDataItem['id'], 'profile_id' => $iProfileId]);
+
+            $this->_oDb->deleteContentNodes2Users(['node_id' => $iNodeId, 'profile_id' => $iProfileId]);
+        }
+
+        $sLink = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY_NODE'] . '&id=' . $iEntryId, [
+            'node_id' => $iNodeId
+        ]);
+
+        return [
+            'code' => 0, 
+            'redirect' => $this->_bIsApi ? bx_api_get_relative_url($sLink) : $sLink
+        ];
+    }
+
+    public function servicePassData($iDataId)
+    {
+        $iNow = time();
+        $iProfileId = bx_get_logged_profile_id();
+        
+        $aData = $this->_oDb->getContentData([
+            'sample' => 'id',
+            'id' => $iDataId
+        ]);
+        
+        if(empty($aData) || !is_array($aData))
+            return ['code' => 1];
+
+        $this->_oDb->insertContentData2Users([
+            'data_id' => $iDataId, 
+            'profile_id' => $iProfileId, 
+            'date' => $iNow
+        ]);
+
+        $aNode = $this->_oDb->getContentNodes([
+            'sample' => 'id_full',
+            'id' => $aData['node_id']
+        ]);
+
+        $iTotal = $this->getDataTotalByNode($aNode, BX_COURSES_CND_USAGE_ST);
+
+        $aUserTrack = $this->_oDb->getContentData([
+            'sample' => 'user_track', 
+            'entry_id' => $aNode['entry_id'], 
+            'node_id' => $aNode['id'], 
+            'profile_id' => $iProfileId
+        ]);
+
+        if(count($aUserTrack) == $iTotal) {
+            $this->_oDb->insertContentNodes2Users([
+                'node_id' => $aNode['id'], 
+                'profile_id' => $iProfileId, 
+                'date' => $iNow
+            ]);
+            
+            $this->_autoPassNodes($iProfileId, $aNode['parent_id']);
+        }
+
+        $aResult = ['code' => 0];
+        if(($sMethod = 'get_view') && bx_is_srv($aData['content_type'], $sMethod)) {
+            $sView = bx_srv($aData['content_type'], $sMethod, [$aData['content_id']]);
+            $aResult = $this->_oTemplate->entryData($aData, $sView);
+        }
+        else if(($sMethod = 'get_link') && bx_is_srv($aData['content_type'], $sMethod))
+            $aResult['redirect'] = bx_srv($aData['content_type'], $sMethod, [$aData['content_id']]);
+
+        if($this->_bIsApi)
+            $aResult['redirect'] = bx_api_get_relative_url($aResult['redirect']);
+
+        return $aResult;
+    }
+    
+    protected function _autoPassNodes($iProfileId, $iNodeId)
+    {
+        $aNode = $this->_oDb->getContentStructure([
+            'sample' => 'node_id', 
+            'node_id' => $iNodeId, 
+        ]);
+
+        if(empty($aNode) || !is_array($aNode))
+            return;
+
+        $iTotal = (int)$aNode['cn_l' . ($aNode['level'] + 1)];
+        if(!$iTotal)
+            return;
+
+        $aUserTrack = $this->_oDb->getContentStructure([
+            'sample' => 'user_track',
+            'profile_id' => $iProfileId,
+            'entry_id' => $aNode['entry_id'],
+            'node_id' => $iNodeId
+        ]);
+
+        if(count($aUserTrack) == $iTotal) {
+            $this->_oDb->insertContentNodes2Users([
+                'node_id' => $iNodeId, 
+                'profile_id' => $iProfileId, 
+                'date' => time()
+            ]);
+
+            if((int)$aNode['parent_id'] != 0)
+                $this->_autoPassNodes($iProfileId, $aNode['parent_id']);
+        }
+    }
+
+    public function serviceGetOptionsContentModulesSt()
+    {
+        return $this->_getOptionsContentModules();
+    }
+
+    public function serviceGetOptionsContentModulesAt()
+    {
+        return $this->_getOptionsContentModules();
+    }
+
+    public function serviceIsContentAvaliable($iProfileId)
+    {
+        if(!$this->_oConfig->isContent())
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoByProfileId($iProfileId);
+        if($this->checkAllowedEdit($aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        return true;
+    }
+
+    public function serviceOnContentAdded($sContentType, $iContentId, $iContextId, $iContextNodeId, $iContextUsage)
+    {
+        $this->_oDb->insertContentData([
+            'entry_id' => $iContextId,
+            'node_id' => $iContextNodeId,
+            'content_type' => $sContentType,
+            'content_id' => $iContentId,
+            'usage' => $iContextUsage,
+            'added' => time(),
+            'order' => $this->_oDb->getContentDataOrderMax($iContextId, $iContextNodeId) + 1
+        ]);
+
+        $aNode = $this->_oDb->getContentNodes(['sample' => 'id', 'id' => $iContextNodeId]);
+
+        $aCounters = [];
+        if(!empty($aNode['counters']))
+            $aCounters = json_decode($aNode['counters'], true);
+
+        $sUsage = $this->_oConfig->getUsageI2S($iContextUsage);
+        if(!isset($aCounters[$sUsage]))
+            $aCounters[$sUsage] = [];
+
+        if(!isset($aCounters[$sUsage][$sContentType]))
+            $aCounters[$sUsage][$sContentType] = 0;
+        $aCounters[$sUsage][$sContentType] += 1;
+
+        $this->_oDb->updateContentNodes(['counters' => json_encode($aCounters)], ['id' => $iContextNodeId]);
+    }
+
+    public function serviceOnContentAddedRedirect($sContentType, $iContentId)
+    {
+        return $this->serviceOnContentActionRedirect('add', $sContentType, $iContentId);
+    }
+
+    public function serviceOnContentEditedRedirect($sContentType, $iContentId)
+    {
+        return $this->serviceOnContentActionRedirect('edit', $sContentType, $iContentId);
+    }
+
+    public function serviceOnContentActionRedirect($sAction, $sContentType, $iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aData = $this->_oDb->getContentData(['sample' => 'content', 'content_type' => $sContentType, 'content_id' => $iContentId]);
+        if(empty($aData) || !is_array($aData))
+            return false;
+
+        $sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_EDIT_ENTRY_CONTENT'] . '&id=' . $aData['entry_id'], [
+            'parent_id' => $aData['node_id']
+        ]);
+
+        return $this->_bIsApi ? '' : $sUrl;
+    }
+
+    public function serviceOnContentDeleted($sContentType, $iContentId, $iContextId)
+    {
+        $aData = $this->_oDb->getContentData(['sample' => 'content', 'content_type' => $sContentType, 'content_id' => $iContentId]);
+        if(empty($aData) || !is_array($aData))
+            return;
+
+        $this->_oDb->deleteContentData([
+            'entry_id' => $iContextId,
+            'content_type' => $sContentType,
+            'content_id' => $iContentId,
+        ]);
+
+        $this->_oDb->deleteContentData2Users([
+            'data_id' => $aData['id']
+        ]);
+
+        $aNode = $this->_oDb->getContentNodes(['sample' => 'id', 'id' => $aData['node_id']]);
+        if(!empty($aNode['counters'])) {
+            $sUsage = $this->_oConfig->getUsageI2S($aData['usage']);
+            $aCounters = json_decode($aNode['counters'], true);
+
+            if(!empty($aCounters[$sUsage][$sContentType])) {
+                if((int)$aCounters[$sUsage][$sContentType] > 1)
+                    $aCounters[$sUsage][$sContentType] -= 1;
+                else
+                    unset($aCounters[$sUsage][$sContentType]);
+
+                $this->_oDb->updateContentNodes(['counters' => json_encode($aCounters)], ['id' => $aData['node_id']]);
+            }
+        }
+    }
+
+    public function serviceJoinedEntriesSummary ($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId || !($oProfile = BxDolProfile::getInstance($iProfileId)))
+            return false;
+
+        if ($iProfileId != $this->_iProfileId)
+            return false;
+
+        return $this->_oTemplate->getJoinedEntriesSummary($iProfileId);
+    }
+
+    public function serviceEntityContentStructureBlock($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oConfig->isContent())
+            return '';
+
+        if(!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if(!$iProfileId)
+            return '';
+
+        $aContentInfo = $this->_oDb->getContentInfoByProfileId($iProfileId);
+
+        $sGridKey = 'OBJECT_GRID_CNT_STRUCTURE_' . ($this->checkAllowedEdit($aContentInfo) === CHECK_ACTION_RESULT_ALLOWED ? 'MANAGE' : 'VIEW');
+        $oGrid = BxDolGrid::getObjectInstance($CNF[$sGridKey]);
+        if(!$oGrid)
+            return '';
+
+        $oGrid->setEntryId($aContentInfo[$CNF['FIELD_ID']]);
+
+        if($this->_bIsApi)
+            return ($aGrid = $oGrid->getCodeAPI()) ? [
+                bx_api_get_block('grid', $aGrid)
+            ] : [];
+
+        return $oGrid->getCode();
+    }
+    
+    public function serviceEntityContentDataBlock($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oConfig->isContent())
+            return '';
+
+        if(!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if(!$iProfileId)
+            return '';
+
+        $aContentInfo = $this->_oDb->getContentInfoByProfileId($iProfileId);
+
+        $sGridKey = 'OBJECT_GRID_CNT_DATA_' . ($this->checkAllowedEdit($aContentInfo) === CHECK_ACTION_RESULT_ALLOWED ? 'MANAGE' : 'VIEW');
+        $oGrid = BxDolGrid::getObjectInstance($CNF[$sGridKey]);
+        if(!$oGrid)
+            return '';
+
+        $oGrid->setEntryId($aContentInfo[$CNF['FIELD_ID']]);
+
+        if($this->_bIsApi)
+            return ($aGrid = $oGrid->getCodeAPI()) ? [
+                bx_api_get_block('grid', $aGrid)
+            ] : [];
+
+        return $oGrid->getCode();
+    }
+    
+    public function serviceEntityStructureL1Block($iContentId = 0)
+    {
+        if(!$this->_oConfig->isContent() || $this->_oConfig->getContentLevelMax() == 1)
+            return $this->_bIsApi ? [] : '';
+
+        $mixedResult = $this->_serviceTemplateFuncEx ('entryStructureByLevel', $iContentId, [
+            'level' => 1,
+            'selected' => ($iNodeId = bx_get('parent_id')) !== false ? (int)$iNodeId : 0,
+            'start' => ($iStart = bx_get('start')) !== false ? (int)$iStart : 0,
+            'per_page' => ($iPerPage = bx_get('per_page')) !== false ? (int)$iPerPage : $this->_oConfig->getPerPageDefault('structure_l1'),
+        ]);
+
+        return $this->_bIsApi ? [bx_api_get_block('course_structure', $mixedResult)] : $mixedResult;
+    }
+
+    public function serviceEntityStructureL2Block($iContentId = 0, $iParentId = 0)
+    {
+        if(!$this->_oConfig->isContent())
+            return $this->_bIsApi ? [] : '';
+
+        if(!$iParentId && ($_iParentId = bx_get('parent_id')) !== false)
+            $iParentId = (int)$_iParentId;
+
+        $mixedResult = $this->_serviceTemplateFuncEx ('entryStructureByParentMl' . $this->_oConfig->getContentLevelMax(), $iContentId, [
+            'parent_id' => $iParentId
+        ]);
+
+        return $this->_bIsApi ? [bx_api_get_block('module_structure', $mixedResult)] : $mixedResult;
+    }
+    
+    public function serviceEntityNodeBlock($iContentId = 0, $iNodeId = 0, $iUsage = false)
+    {
+        if(!$this->_oConfig->isContent())
+            return $this->_bIsApi ? [] : '';
+
+        if(!$iNodeId && ($_iNodeId = bx_get('node_id')) !== false)
+            $iNodeId = (int)$_iNodeId;
+
+        if($iUsage === false && ($_iUsage = bx_get('usage')) !== false)
+            $iUsage = (int)$_iUsage;
+
+        $mixedResult = $this->_serviceTemplateFuncEx('entryNode', $iContentId, [
+            'node_id' => $iNodeId,
+            'usage' => $iUsage
+        ]);
+
+        return $this->_bIsApi ? [bx_api_get_block('lesson_structure', $mixedResult)] : $mixedResult;
+    }
+
+    public function checkAllowedHide($aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if($aDataEntry[$CNF['FIELD_STATUS']] != 'active')
+            return CHECK_ACTION_RESULT_NOT_ALLOWED;
+
+        // moderator and owner always have access
+        if ($aDataEntry[$CNF['FIELD_AUTHOR']] == $this->_iProfileId || $this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return CHECK_ACTION_RESULT_NOT_ALLOWED;
+    }
+
+    public function checkAllowedUnhide($aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if($aDataEntry[$CNF['FIELD_STATUS']] == 'active')
+            return CHECK_ACTION_RESULT_NOT_ALLOWED;
+
+        // moderator and owner always have access
+        if ($aDataEntry[$CNF['FIELD_AUTHOR']] == $this->_iProfileId || $this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return CHECK_ACTION_RESULT_NOT_ALLOWED;
+    }
+
+    public function getNodeLevelByParent($aParentInfo)
+    {
+        return (is_array($aParentInfo) && !empty($aParentInfo['level']) ? (int)$aParentInfo['level'] : 0) + 1;
+    }
+    
+    public function getDataTotalByNode($mixedNode, $iUsage = 0)
+    {
+        if(!is_array($mixedNode))
+            $mixedNode = $this->_oDb->getContentNodes([
+                'sample' => 'id', 
+                'id' => (int)$mixedNode
+            ]);
+        
+        $iTotal = 0;
+        $sUsage = $this->_oConfig->getUsageI2S($iUsage);
+        if(!empty($mixedNode['counters']) && ($aCountes = json_decode($mixedNode['counters'], true)) && !empty($aCountes[$sUsage]) && is_array($aCountes[$sUsage]))
+            $iTotal = array_sum($aCountes[$sUsage]);
+
+        return $iTotal;
+    }
+
+    public function isNodeStarted($iProfileId, $mixedNode)
+    {
+        $iNodeId = is_array($mixedNode) ? $mixedNode['id'] : (int)$mixedNode;
+
+        $aChildNodes = $this->_oDb->getContentStructure([
+            'sample' => 'parent_id',
+            'parent_id' => $iNodeId
+        ]);
+
+        if(!empty($aChildNodes) && is_array($aChildNodes)) {
+            foreach($aChildNodes as $aChildNode)
+                if($this->isNodePassed($iProfileId, $aChildNode['node_id']))
+                    return true;
+                else
+                    return $this->isNodeStarted($iProfileId, $aChildNode['node_id']);
+        }
+        else {
+            $aChildData = $this->_oDb->getContentData([
+                'sample' => 'node_id',
+                'node_id' => $iNodeId
+            ]);
+
+            if(!empty($aChildData) && is_array($aChildData))
+                foreach($aChildData as $aChildData)
+                    if($this->isDataPassed($iProfileId, $aChildData['id']))
+                        return true;
+
+            return false;
+        }
+    }
+
+    public function isNodePassed($iProfileId, $mixedNode)
+    {
+        $aUserTrack = $this->_oDb->getContentStructure([
+            'sample' => 'user_passed', 
+            'profile_id' => $iProfileId, 
+            'node_id' => is_array($mixedNode) ? $mixedNode['id'] : (int)$mixedNode
+        ]);
+
+        return !empty($aUserTrack) && is_array($aUserTrack);
+    }
+    
+    public function isDataPassed($iProfileId, $mixedData)
+    {
+        $aUserTrack = $this->_oDb->getContentData([
+            'sample' => 'user_passed', 
+            'profile_id' => $iProfileId, 
+            'data_id' => is_array($mixedData) ? $mixedData['id'] : (int)$mixedData
+        ]);
+
+        return !empty($aUserTrack) && is_array($aUserTrack);
+    }
+
+    public function getEntryPass($iProfileId, $iContentId)
+    {
+        $aChildren = $this->_oDb->getContentStructure([
+            'sample' => 'entry_id', 
+            'entry_id' => $iContentId,
+            'level' => 1
+        ]);
+
+        $aPassDetails = [];
+        foreach($aChildren as $aChild)
+            $this->getNodePass($iProfileId, $iContentId, $aChild, $aPassDetails);
+
+        $iPassPercent = 0;
+        $sPassStatus = '_bx_courses_txt_status_not_started';
+        $sPassTitle = '_bx_courses_txt_pass_start';
+        $iLevelMax = $this->_oConfig->getContentLevelMax();
+        if(isset($aPassDetails[$iLevelMax]) && ($aPassLevelMax = $aPassDetails[$iLevelMax])) {
+            $iPassPercent = ($iTotal = (int)$aPassLevelMax['total']) != 0 ? (int)round(100 * $aPassLevelMax['passed']/$iTotal) : 0;
+
+            if($aPassLevelMax['passed'] != 0) {
+                if($aPassLevelMax['passed'] != $aPassLevelMax['total']) {
+                    $sPassStatus = '_bx_courses_txt_status_in_process';
+                    $sPassTitle = '_bx_courses_txt_pass_continue';
+                }
+                else {
+                    $sPassStatus = '_bx_courses_txt_status_completed';
+                    $sPassTitle = '_bx_courses_txt_pass_again';
+                }
+            }
+        }
+
+        return [
+            $iPassPercent,
+            $aPassDetails,
+            _t($sPassStatus),
+            _t($sPassTitle)
+        ];
+    }
+
+    public function getNodePass($iProfileId, $iContentId, $aNode, &$aResults)
+    {
+        $iLevel = $aNode['level'];
+
+        if(!isset($aResults[$iLevel]))
+            $aResults[$iLevel] = [
+                'passed' => 0,
+                'total' => 0
+            ];
+
+        $aResults[$iLevel]['passed'] += $this->isNodePassed($iProfileId, $aNode['node_id']);
+        $aResults[$iLevel]['total'] += 1;
+
+        if($iLevel != $this->_oConfig->getContentLevelMax() && !empty($aNode['cn_l' . ($iLevel + 1)])) {
+            $aChildren = $this->_oDb->getContentStructure([
+                'sample' => 'parent_id', 
+                'parent_id' => $aNode['node_id']
+            ]);
+            
+            foreach($aChildren as $aChild)
+                $this->getNodePass($iProfileId, $iContentId, $aChild, $aResults);
+        }
+    }
+
+    public function getNodePassByChildren($iProfileId, $iContentId, $aNode, &$aResults)
+    {
+        $aUserTrack = $this->_oDb->getContentStructure([
+            'sample' => 'user_track', 
+            'entry_id' => $iContentId, 
+            'node_id' => $aNode['node_id'], 
+            'profile_id' => $iProfileId
+        ]);
+
+        $iLevelChildren = $aNode['level'] + 1;
+        $iCountChildren = $aNode['cn_l' . $iLevelChildren];
+
+        if(!isset($aResults[$iLevelChildren]))
+            $aResults[$iLevelChildren] = [
+                'passed' => 0,
+                'total' => 0
+            ];
+
+        $aResults[$iLevelChildren]['passed'] += count($aUserTrack);
+        $aResults[$iLevelChildren]['total'] += $iCountChildren;
+
+        if($iCountChildren && $iLevelChildren != $this->_oConfig->getContentLevelMax()) {
+            $aChildren = $this->_oDb->getContentStructure([
+                'sample' => 'parent_id', 
+                'parent_id' => $aNode['node_id']
+            ]);
+            
+            foreach($aChildren as $aChild)
+                $this->getNodePassByChildren($iProfileId, $iContentId, $aChild, $aResults);
+        }
+    }
+
+    public function getNodePassByData($iProfileId, $iContentId, $aNode)
+    {
+        $iTotal = 0;
+        $iPassCount = $iPassPercent = 0;
+        $sPassStatus = $sPassTitle = '';
+        if(($iTotal = $this->getDataTotalByNode($aNode)) != 0) {
+            $aUserTrack = $this->_oDb->getContentData([
+                'sample' => 'user_track', 
+                'entry_id' => $iContentId, 
+                'node_id' => $aNode['node_id'], 
+                'profile_id' => $iProfileId
+            ]);
+
+            $iPassCount = count($aUserTrack);
+            $iPassPercent = (int)round(100 * $iPassCount/$iTotal);
+            
+            if($iPassCount == 0) {
+                $sPassStatus = '_bx_courses_txt_status_not_started';
+                $sPassTitle = '_bx_courses_txt_pass_start';
+            }
+            else {
+                if($iPassCount != $iTotal) {
+                    $sPassStatus = '_bx_courses_txt_status_in_process';
+                    $sPassTitle = '_bx_courses_txt_pass_continue';
+                }
+                else {
+                    $sPassStatus = '_bx_courses_txt_status_completed';
+                    $sPassTitle = '_bx_courses_txt_pass_again';
+                }
+            }
+        }
+
+        return [
+            $iPassPercent,
+            _t('_bx_courses_txt_n_m_steps', $iPassCount, $iTotal),
+            _t($sPassStatus ? $sPassStatus : '_undefined'),
+            _t($sPassTitle)
+        ];
+    }
+
+    public function decodeDataAPI($aData, $aParams = [])
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        $aResult = parent::decodeDataAPI($aData, $aParams);
+        $aResultAdd = [
+            $CNF['FIELD_STATUS'] => $aData[$CNF['FIELD_STATUS']]
+        ];
+
+        $iEntryId = (int)$aData[$CNF['FIELD_ID']];
+        $aLevelToNode = $this->_oConfig->getContentLevel2Node(false);        
+        if($this->isFan($iEntryId, $this->_iProfileId)) {
+            $sTxtProgress = _t('_bx_courses_txt_n_m_progress');
+
+            list($iPassPercent, $aPassDetails, $sPassStatus, $sPassTitle) = $this->getEntryPass($this->_iProfileId, $iEntryId);
+
+            $aCounters = [];
+            foreach($aPassDetails as $iLevel => $aDetails) {
+                $aCounters[] = [
+                    'level' => $iLevel,
+                    'title' => $aLevelToNode[$iLevel],
+                    'passed' => $aDetails['passed'],
+                    'total' => $aDetails['total'],
+                    'progress' => bx_replace_markers($sTxtProgress, $aDetails)
+                ];
+            }
+
+            $aResultAdd = [
+                'percent' => $iPassPercent,
+                'counters' => $aCounters,
+                'pass_status' => $sPassStatus,
+                'show_pass' => $iPassPercent > 0 && $iPassPercent < 100,
+            ];
+
+            if($aResultAdd['show_pass'])
+                $aResultAdd = array_merge($aResultAdd, [
+                    'pass_link' => $aResult['url'],
+                    'pass_title' => $sPassTitle,
+                ]);
+            
+        }
+        else {
+            $iLevelMax = $this->_oConfig->getContentLevelMax();
+            $aEntryCounters = $this->_oDb->getContentStructure(['sample' => 'entry_id_counters', 'entry_id' => $iEntryId]);
+
+            $aCounters = [];
+            if(!empty($aEntryCounters) && is_array($aEntryCounters))
+                for($i = 1; $i <= $iLevelMax; $i++) {
+                    $aCounters[] = [
+                        'level' => $i,
+                        'title' => $aLevelToNode[$i],
+                        'total' => $aEntryCounters['cn_l' . $i]
+                    ];
+                }
+
+            $aResultAdd = [
+                'counters' => $aCounters,
+            ];
+        }
+
+        return array_merge($aResult, $aResultAdd);
+    }
+
+    protected function _getOptionsContentModules()
+    {
+        $aResult = [];
+
+        $aModules = bx_srv('system', 'get_modules_by_type', ['content']);
+        foreach($aModules as $aModule)
+            $aResult[] = ['key' => $aModule['name'], 'value' => $aModule['title']];
+
+        return $aResult;
+    }
+
+    protected function _performHideCourseProfile($aDataEntry)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = $aDataEntry[$CNF['FIELD_ID']];
+        if(!$this->_oDb->updateEntriesBy([$CNF['FIELD_STATUS'] => 'hidden'], [$CNF['FIELD_ID'] => $iContentId])) 
+            return false;
+
+        $this->checkAllowedHide($aDataEntry, true);
+
+        $this->alertAfterEdit($this->_oDb->getContentInfoById($iContentId));
+
+        return $this->_bIsApi ? [
+            'title' => _t($CNF['T']['menu_item_title_unhide']),
+            'request_url' => $this->_oConfig->getName() . '/publish/&params[]=' . $iContentId, 
+        ] : true;
+    }
+
+    protected function _performUnhideCourseProfile($aDataEntry)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = $aDataEntry[$CNF['FIELD_ID']];
+        if(!$this->_oDb->updateEntriesBy([$CNF['FIELD_STATUS'] => 'active'], [$CNF['FIELD_ID'] => $iContentId])) 
+            return false;
+
+        $this->checkAllowedHide($aDataEntry, true);
+
+        $this->alertAfterEdit($this->_oDb->getContentInfoById($iContentId));
+
+        return $this->_bIsApi ? [
+            'title' => _t($CNF['T']['menu_item_title_hide']),
+            'request_url' => $this->_oConfig->getName() . '/hide/&params[]=' . $iContentId, 
+        ] : true;
+    }
+}
+
+/** @} */

+ 47 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesPageEntry.php

@@ -0,0 +1,47 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Profile create/edit/delete pages.
+ */
+class BxCoursesPageEntry extends BxBaseModGroupsPageEntry
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->MODULE = 'bx_courses';
+
+        parent::__construct($aObject, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bLoggedOwner = isset($this->_aContentInfo[$CNF['FIELD_AUTHOR']]) && $this->_aContentInfo[$CNF['FIELD_AUTHOR']] == bx_get_logged_profile_id();
+        $bLoggedModerator = $this->_oModule->checkAllowedEditAnyEntry() === CHECK_ACTION_RESULT_ALLOWED;
+        if(($oInformer = BxDolInformer::getInstance($this->_oTemplate)) && ($bLoggedOwner || $bLoggedModerator)) {
+            $aInformers = [];
+
+            if($aInformers)
+                foreach($aInformers as $aInformer)
+                    $oInformer->add($aInformer['name'], $this->_replaceMarkers($aInformer['msg']), $aInformer['type']);
+        }
+    }
+
+    public function getCode ()
+    {
+        $sResult = parent::getCode();
+        if(!empty($sResult))
+            $sResult .= $this->_oModule->_oTemplate->getJsCode('entry');
+
+        $this->_oModule->_oTemplate->addJs(['modules/base/groups/js/|entry.js', 'entry.js']);
+        return $sResult;
+    }
+}
+
+/** @} */

+ 214 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesSearchResult.php

@@ -0,0 +1,214 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCoursesSearchResult extends BxBaseModGroupsSearchResult
+{
+    function __construct($sMode = '', $aParams = false)
+    {
+        parent::__construct($sMode, $aParams); 
+
+        $this->aCurrent =  array(
+            'name' => 'bx_courses',
+            'module_name' => 'bx_courses',
+            'object_metatags' => 'bx_courses',
+            'title' => _t('_bx_courses_page_title_browse'),
+            'table' => 'sys_profiles',
+            'tableSearch' => 'bx_courses_data',
+            'ownFields' => array(),
+            'searchFields' => array(),
+            'restriction' => array(
+                'account_id' => array('value' => '', 'field' => 'account_id', 'operator' => '='),
+                'perofileStatus' => array('value' => 'active', 'field' => 'status', 'operator' => '='),
+                'perofileType' => array('value' => 'bx_courses', 'field' => 'type', 'operator' => '='),
+                'owner' => array('value' => '', 'field' => 'author', 'operator' => '=', 'table' => 'bx_courses_data'),
+                'featured' => array('value' => '', 'field' => 'featured', 'operator' => '<>', 'table' => 'bx_courses_data'),
+                'status' => array('value' => 'active', 'field' => 'status', 'operator' => '=', 'table' => 'bx_courses_data'),
+                'statusAdmin' => array('value' => 'active', 'field' => 'status_admin', 'operator' => '=', 'table' => 'bx_courses_data'),
+            ),
+            'join' => array (
+                'profile' => array(
+                    'type' => 'INNER',
+                    'table' => 'bx_courses_data',
+                    'mainField' => 'content_id',
+                    'onField' => 'id',
+                    'joinFields' => array('id', 'name', 'desc', 'picture', 'cover', 'cover_data', 'added', 'author', 'allow_view_to', 'status'),
+                ),
+                'account' => array(
+                    'type' => 'INNER',
+                    'table' => 'sys_accounts',
+                    'mainField' => 'account_id',
+                    'onField' => 'id',
+                    'joinFields' => array(),
+                ),
+            ),
+            'paginate' => array('perPage' => getParam('bx_courses_per_page_browse'), 'start' => 0),
+            'sorting' => 'last',
+            'rss' => array(
+                'title' => '',
+                'link' => '',
+                'image' => '',
+                'profile' => 0,
+                'fields' => array (
+                    'Guid' => 'link',
+                    'Link' => 'link',
+                    'Title' => 'group_name',
+                    'DateTimeUTS' => 'added',
+                    'Desc' => 'group_name',
+                    'Image' => 'picture',
+                ),
+            ),
+            'ident' => 'id'
+        );
+
+        $this->sFilterName = 'bx_courses_data_filter';
+        $this->oModule = $this->getMain();
+
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $sSearchFields = getParam($CNF['PARAM_SEARCHABLE_FIELDS']);
+        $this->aCurrent['searchFields'] = !empty($sSearchFields) ? explode(',', $sSearchFields) : '';
+
+        $oJoinedProfile = null;
+        $bProcessConditionsForPrivateContent = true;
+        switch ($sMode) {
+            case 'created_entries':
+                if(!$this->_setAuthorConditions($sMode, $aParams, $oJoinedProfile)) {
+                    $this->isError = true;
+                    break;
+                }
+                break;
+
+            case 'joined_entries':
+                $oJoinedProfile = BxDolProfile::getInstance((int)$aParams['joined_profile']);
+                if (!$oJoinedProfile) {
+                    $this->isError = true;
+                    break;
+                }
+
+                $bProcessConditionsForPrivateContent = false;
+
+                $this->aCurrent['join']['fans'] = array(
+                    'type' => 'INNER',
+                    'table' => 'bx_courses_fans',
+                    'mainField' => 'id',
+                    'onField' => 'content',
+                    'joinFields' => array('initiator'),
+                );
+
+                $this->aCurrent['restriction']['fans'] = array('value' => $oJoinedProfile->id(), 'field' => 'initiator', 'operator' => '=', 'table' => 'bx_courses_fans');
+
+                $this->sBrowseUrl = 'page.php?i=' . $CNF['URI_JOINED_ENTRIES'] . '&profile_id={profile_id}';
+                $this->aCurrent['title'] = _t('_bx_courses_page_title_joined_entries');
+                $this->aCurrent['rss']['link'] = 'modules/?r=groups/rss/' . $sMode . '/' . $oJoinedProfile->id();
+                break;
+
+            case 'connections':
+                if ($this->_setConnectionsConditions($aParams)) {
+                    $bProcessConditionsForPrivateContent = false;
+                    $oProfile = BxDolProfile::getInstance($aParams['profile']);
+                    $oProfile2 = isset($aParams['profile2']) ? BxDolProfile::getInstance($aParams['profile2']) : null;
+
+                    if (isset($aParams['type']) && $aParams['type'] == 'common' && $oProfile && $oProfile2)
+                        $this->aCurrent['title'] = _t('_bx_courses_page_title_browse_connections_mutual', $oProfile->getDisplayName(), $oProfile2->getDisplayName());
+                    elseif ((!isset($aParams['type']) || $aParams['type'] != 'common') && $oProfile)
+                        $this->aCurrent['title'] = _t('_bx_courses_page_title_browse_connections', $oProfile->getDisplayName());
+
+                    $this->aCurrent['rss']['link'] = 'modules/?r=groups/rss/' . $sMode . '/' . $aParams['object'] . '/' . $aParams['type'] . '/' . (int)$aParams['profile'] . '/' . (int)$aParams['profile2'] . '/' . (int)$aParams['mutual'];
+                }
+                break;
+
+            case 'favorite':
+                if(!$this->_setFavoriteConditions($sMode, $aParams, $oJoinedProfile)) {
+                    $this->isError = true;
+                    break;
+                }
+                break;
+
+            case 'recent':
+                $this->aCurrent['rss']['link'] = 'modules/?r=groups/rss/' . $sMode;
+                $this->aCurrent['title'] = _t('_bx_courses_page_title_browse_recent');
+                $this->aCurrent['sorting'] = 'last';
+                $this->sBrowseUrl = 'page.php?i=groups-home';
+                break;
+
+            case 'featured':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_HOME']);
+                $this->aCurrent['title'] = _t('_bx_courses_page_title_browse_featured');
+                $this->aCurrent['restriction']['featured']['value'] = '0';
+                $this->aCurrent['rss']['link'] = 'modules/?r=groups/rss/' . $sMode;
+                $this->aCurrent['sorting'] = 'featured';
+                break;
+
+            case 'recommended':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_HOME']);
+                $this->aCurrent['title'] = _t('_bx_courses_page_title_browse_recommended');
+                $this->aCurrent['restriction']['featured']['value'] = '0';
+                $this->aCurrent['rss']['link'] = 'modules/?r=groups/rss/' . $sMode;
+                $this->aCurrent['sorting'] = 'recommended';
+                $this->_setConditionsForRecommended();
+                break;    
+                
+            case 'top':
+                $this->aCurrent['rss']['link'] = 'modules/?r=groups/rss/' . $sMode;
+                $this->aCurrent['title'] = _t('_bx_courses_page_title_browse_top');
+                $this->aCurrent['sorting'] = 'top';
+                $this->sBrowseUrl = 'page.php?i=groups-top';
+                break;
+
+            case '': // search results
+                $this->sBrowseUrl = BX_DOL_SEARCH_KEYWORD_PAGE;
+                $this->aCurrent['title'] = _t('_bx_courses');
+                unset($this->aCurrent['paginate']['perPage'], $this->aCurrent['rss']);
+                break;
+
+            default:
+                $this->isError = true;
+        }
+
+        if ($bProcessConditionsForPrivateContent) {
+            $this->addConditionsForPrivateContent($CNF, $oJoinedProfile);
+
+            $this->addCustomConditions($CNF, $oJoinedProfile, $sMode, $aParams);
+        }
+
+        $this->sCenterContentUnitSelector = false;
+    }
+
+    function getAlterOrder()
+    {
+        switch ($this->aCurrent['sorting']) {
+        case 'featured':
+            return array('order' => ' ORDER BY `bx_courses_data`.`featured` DESC ');
+        case 'recommended':
+            return array('order' => ' ORDER BY RAND() ');
+        case 'none':
+            return array();
+        case 'top':
+            return array('order' => ' ORDER BY `bx_courses_data`.`views` DESC ');
+        case 'last':
+        default:                        
+            return array('order' => ' ORDER BY `bx_courses_data`.`added` DESC ');
+        }
+    }
+
+    function _getPseud ()
+    {
+        return array(
+            'id' => 'id',
+            'name' => 'name',
+            'added' => 'added',
+            'author' => 'author',
+            'picture' => 'picture',
+        );
+    }
+}
+
+/** @} */

+ 731 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/classes/BxCoursesTemplate.php

@@ -0,0 +1,731 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Courses module representation.
+ */
+class BxCoursesTemplate extends BxBaseModGroupsTemplate
+{
+    protected $_iProfileId;
+
+    public function __construct(&$oConfig, &$oDb)
+    {
+        $this->MODULE = 'bx_courses';
+
+        parent::__construct($oConfig, $oDb);
+
+        $this->_iProfileId = bx_get_logged_profile_id();
+    }
+
+    public function unit ($aData, $isCheckPrivateContent = true, $mixedTemplate = false, $aParams = [])
+    {
+        if(!empty($aParams['context']) && in_array($aParams['context'], ['favorite', 'joined_entries']))
+            $aParams['template_name'] = 'unit_personal.html';
+
+        return parent::unit($aData, $isCheckPrivateContent, $mixedTemplate, $aParams);
+    }
+
+    public function unitVars($aData, $isCheckPrivateContent = true, $mixedTemplate = false, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aVars = parent::unitVars($aData, $isCheckPrivateContent, $mixedTemplate, $aParams);
+
+        $bVarsShowProgress = $bVarsShoStats = false;
+        $aVarsShowProgress = $aVarsShowStats = [true];
+        if(!empty($aParams['context']) && in_array($aParams['context'], ['favorite', 'joined_entries'])) {
+            $aLevelToNode = $this->_oConfig->getContentLevel2Node(false);
+            $sTxtProgress = _t('_bx_courses_txt_n_m_progress');
+
+            list($iPassPercent, $aPassDetails, $sPassStatus, $sPassTitle) = $this->_oModule->getEntryPass($this->_iProfileId, $aData[$CNF['FIELD_ID']]);
+
+            $bVarsShowProgress = true;
+            $aVarsShowProgress = [
+                'progress' => $iPassPercent
+            ];
+
+            $aTmplVarsCounters = [];
+            foreach($aPassDetails as $iLevel => $aDetails) {
+                $aTmplVarsCounters[] = [
+                    'cn_title' => $aLevelToNode[$iLevel],
+                    'cn_passed' => $aDetails['passed'],
+                    'cn_total' => $aDetails['total'],
+                    'cn_progress' => bx_replace_markers($sTxtProgress, $aDetails)
+                ];
+            }
+
+            $bVarsShoStats = true;
+            $aVarsShowStats = [
+                'status' => $sPassStatus,
+                'bx_repeat:counters' => $aTmplVarsCounters,
+                'bx_if:show_pass' => [
+                    'condition' => $iPassPercent > 0 && $iPassPercent < 100,
+                    'content' => [
+                        'pass_link' => $aVars['content_url'],
+                        'pass_title' => $sPassTitle
+                    ]
+                ]
+            ];
+        }
+
+        return array_merge($aVars, [
+            'bx_if:show_pass_progress' => [
+                'condition' => $bVarsShowProgress,
+                'content' => $aVarsShowProgress
+            ],
+            'bx_if:show_pass_stats' => [
+                'condition' => $bVarsShoStats,
+                'content' => $aVarsShowStats
+            ]
+        ]);
+    }
+
+    public function getCounters($aCounters)
+    {
+        $aTmplVars = [];
+        foreach([BX_COURSES_CND_USAGE_ST, BX_COURSES_CND_USAGE_AT] as $iUsage) {
+            $sUsage = $this->_oConfig->getUsageI2S($iUsage);
+
+            $aTmplVarsCounters = [true];
+            $bTmplVarsCounters = !empty($aCounters[$sUsage]) && is_array($aCounters[$sUsage]);
+            if($bTmplVarsCounters) {
+                $aTmplVarsCounters['bx_repeat:counters_' . $sUsage] = [];
+                foreach($aCounters[$sUsage] as $sModule => $iCount) {
+                    $aCounter = [
+                        'title' => _t('_' . $sModule), 
+                        'value' => $iCount
+                    ];
+
+                    if($this->_bIsApi)
+                        $aTmplVars[$sUsage][] = $aCounter;
+                    else
+                        $aTmplVarsCounters['bx_repeat:counters_' . $sUsage][] = $aCounter;
+                }
+            }
+            
+            if($this->_bIsApi)
+                continue;
+
+            $aTmplVars['bx_if:show_' . $sUsage] = [
+                'condition' => $bTmplVarsCounters,
+                'content' => $aTmplVarsCounters
+            ];
+        }
+
+        return $this->_bIsApi ? $aTmplVars : $this->parseHtmlByName('counters.html', $aTmplVars);
+    }
+
+    public function getJoinedEntriesSummary($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+        if(!$oConnection)
+            return false;
+
+        $aEntries = $oConnection->getConnectedContent($iProfileId);
+
+        $iJoined = count($aEntries);
+        $iStarted = 0;
+        $iPassed = 0;
+
+        foreach($aEntries as $iEntryId) {
+            $aEntryInfo = $this->_oDb->getContentInfoByProfileId($iEntryId);
+
+            list($iPassPercent) = $this->_oModule->getEntryPass($iProfileId, $aEntryInfo[$CNF['FIELD_ID']]);
+            if($iPassPercent > 0 && $iPassPercent < 100)
+                $iStarted += 1;
+            else if($iPassPercent == 100)
+                $iPassed +=1;
+        }
+
+        $aTmplVars = [
+            'joined' => $iJoined,
+            'passed' => $iPassed,
+            'not_passed' => $iJoined - $iPassed,
+            'passed_percent' => $iJoined != 0 ? (int)round(100 * $iPassed/$iJoined) : 0
+        ];
+
+        return $this->_bIsApi ? $aTmplVars : $this->parseHtmlByName('entries_summary.html', $aTmplVars);
+    }
+
+    public function entryStructureByLevel($aContentInfo, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!isset($aParams['level']))
+            return '';
+
+        $iProfileId = bx_get_logged_profile_id();
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+        $bEditable = $this->_oModule->checkAllowedEdit($aContentInfo) === CHECK_ACTION_RESULT_ALLOWED;
+
+        $oPermalink = BxDolPermalinks::getInstance();
+
+        $iLevel = (int)$aParams['level'];
+        $iSelected = (int)$aParams['selected'];
+        $iStart = isset($aParams['start']) ? (int)$aParams['start'] : 0;
+        $iPerPage = isset($aParams['per_page']) ? (int)$aParams['per_page'] : 2;
+
+        $aNodes = $this->_oDb->getContentStructure([
+            'sample' => 'entry_id_full', 
+            'entry_id' => $iContentId, 
+            'level' => $iLevel, 
+            'status' => 'active',
+            'start' => $iStart, 
+            'per_page' => $iPerPage ? $iPerPage + 1 : 0
+        ]);
+
+        if(empty($aNodes) || !is_array($aNodes))
+            return $this->_bIsApi ? [
+                'items' => [], 
+                'isEditable' => $bEditable, 
+                'entry_id' => $iContentId
+            ] : '';
+
+        $iLevelMax = $this->_oConfig->getContentLevelMax();
+        $aLevelToNode = $this->_oConfig->getContentLevel2Node(false);
+
+        $sTxtProgress = _t('_bx_courses_txt_n_m_progress');
+        $sTmplKeysSelected = $this->_bIsApi ? 'selected' : 'bx_if:selected';
+        $sTmplKeysCounters = $this->_bIsApi ? 'counters' : 'bx_repeat:counters';
+        
+        $aTmplVarsNodes = [];
+        foreach($aNodes as $iKey => $aNode) {
+            $aNodeStats = [];
+            $this->_oModule->getNodePassByChildren($iProfileId, $iContentId, $aNode, $aNodeStats);           
+
+            $aTmplVarsCounters = [];
+            for($i = $iLevel + 1; $i <= $iLevelMax; $i++) {
+                if(!isset($aNodeStats[$i]))
+                    continue;
+
+                $aTmplVarsCounters[] = [
+                    'cn_title' => $aLevelToNode[$i],
+                    'cn_passed' => $aNodeStats[$i]['passed'],
+                    'cn_total' => $aNodeStats[$i]['total'],
+                    'cn_progress' => bx_replace_markers($sTxtProgress, $aNodeStats[$i])
+                ];
+            }
+
+            $bSelected = $aNode['node_id'] == $iSelected;
+            $sLink = BX_DOL_URL_ROOT . $oPermalink->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&' . $CNF['FIELD_ID'] . '=' . $iContentId, [
+                'parent_id' => $aNode['node_id']
+            ]);
+            $aNode = array_merge($aNode, [
+                $sTmplKeysSelected => $this->_bIsApi ? $bSelected : [
+                    'condition' => $bSelected,
+                    'content' => [true]
+                ],
+                'index' => $iKey + 1,
+                'link' => $this->_bIsApi ? bx_api_get_relative_url($sLink) : $sLink,
+                'percent' => isset($aNodeStats[$iLevelMax]) && ($aStats = $aNodeStats[$iLevelMax]) && $aStats['total'] ? round(100 * $aStats['passed']/$aStats['total']) : 0,
+                'status' => $this->_getNodeStatus($iProfileId, $iContentId, $aNode['node_id']),
+                $sTmplKeysCounters => $aTmplVarsCounters
+            ]);
+
+            $aTmplVarsNodes[] = $this->_bIsApi ? $aNode : [
+                'node' => $this->parseHtmlByName('node_l' . $iLevel . '.html', $aNode)
+            ];
+        }
+
+        if($this->_bIsApi)
+            return [
+                'items' => $aTmplVarsNodes, 
+                'isEditable' => $bEditable, 
+                'entry_id' => $iContentId
+            ];
+
+        $oPaginate = new BxTemplPaginate([
+            'start' => $iStart,
+            'per_page' => $iPerPage,
+            'on_change_page' => "return !loadDynamicBlockAutoPaginate(this, '{start}', '{per_page}')"
+        ]);
+        $oPaginate->setNumFromDataArray($aTmplVarsNodes);
+
+        return $this->parseHtmlByName('nodes_l' . $iLevel . '.html', [
+            'level' => $iLevel,
+            'bx_repeat:nodes' => $aTmplVarsNodes,
+            'paginate' => $oPaginate->getSimplePaginate()
+        ]);
+    }
+
+    /**
+     * For 1 level bases structure ( Max Level = 1)
+     */
+    public function entryStructureByParentMl1($aContentInfo, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+        $iParentId = isset($aParams['parent_id']) ? (int)$aParams['parent_id'] : 0;
+        $iProfileId = bx_get_logged_profile_id();
+
+        $aNodes = $this->_oDb->getContentStructure([
+            'sample' => 'entry_id_full', 
+            'entry_id' => $iContentId, 
+            'parent_id' => $iParentId,
+            'status' => 'active'
+        ]);
+
+        if((empty($aNodes) || !is_array($aNodes)) && !$this->_bIsApi)
+            return '';
+
+        $sJsObject = $this->_oConfig->getJsObject('entry');
+        $oPermalink = BxDolPermalinks::getInstance();
+        
+        $iLevelMax = $this->_oConfig->getContentLevelMax();
+
+        $sTmplKeysShowPass = $this->_bIsApi ? 'show_pass' : 'bx_if:show_pass';
+
+        $aTmplVarsNodes = [];
+        foreach($aNodes as $iKey => $aNode) {
+            list($iPassPercent, $sPassProgress, $sPassStatus, $sPassTitle) = $this->_oModule->getNodePassByData($iProfileId, $iContentId, $aNode);
+
+            $sLink = BX_DOL_URL_ROOT . $oPermalink->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY_NODE'] . '&id=' . $iContentId, [
+                'node_id' => $aNode['node_id']
+            ]);
+
+            $bShowPass = !empty($sPassTitle);
+
+            $aNode = array_merge($aNode, [
+                'level_max' => $iLevelMax,
+                'index' => $iKey + 1,
+                'link' => $sLink,
+                'pass_percent' => $iPassPercent,
+                'pass_progress' => $sPassProgress,
+                'pass_status' => $sPassStatus,
+                $sTmplKeysShowPass => $this->_bIsApi ? $bShowPass : [
+                    'condition' => $bShowPass,
+                    'content' => [
+                        'js_object' => $sJsObject,
+                        'id' => $aNode['node_id'],
+                        'pass_href' => $sLink,
+                        'pass_title' => $sPassTitle,
+                    ]
+                ]
+            ]);
+
+            if($this->_bIsApi) {
+                if(!empty($aNode['counters']) && ($aCounters = json_decode($aNode['counters'], true)))
+                    $aNode['counters'] = $this->getCounters($aCounters);
+
+                $aNode = array_merge($aNode, [
+                    'link' => bx_api_get_relative_url($aNode['link']),
+                    'pass_title' => $sPassTitle,
+                    'pass_callback' => $this->MODULE . '/pass_node&params[]=' . $aNode['node_id'],
+                ]);
+            }
+
+            $aTmplVarsNodes[] = $this->_bIsApi ? $aNode : [
+                'node' => $this->parseHtmlByName('ml' . $iLevelMax . '_node_l' . $aNode['level'] . '.html', $aNode)
+            ];
+        }
+
+        if($this->_bIsApi)           
+            return [
+                'items' => $aTmplVarsNodes, 
+                'isEditable' => $this->_oModule->checkAllowedEdit($aContentInfo) === CHECK_ACTION_RESULT_ALLOWED, 
+                'entry_id' => $iContentId, 
+                'parent_id' => $iParentId
+            ];
+
+        return $this->parseHtmlByName('ml' . $iLevelMax . '_nodes_l' . $aNode['level'] . '.html', [
+            'level_max' => $iLevelMax,
+            'level' => $aNode['level'],
+            'bx_repeat:nodes' => $aTmplVarsNodes
+        ]);
+    }
+
+    /**
+     * For 2 levels bases structure ( Max Level = 2)
+     */
+    public function entryStructureByParentMl2($aContentInfo, $aParams = [])
+    {
+        if(empty($aParams['parent_id']))
+            return $this->_bIsApi ? [] : '';
+
+        return $this->entryStructureByParentMl1($aContentInfo, $aParams);
+    }
+
+    /**
+     * For 3 levels bases structure ( Max Level = 3)
+     */
+    public function entryStructureByParentMl3($aContentInfo, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParams['parent_id']))
+            return $this->_bIsApi ? [] : '';
+
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+        $iParentId = (int)$aParams['parent_id'];
+        $iProfileId = bx_get_logged_profile_id();
+
+        $aNodes = $this->_oDb->getContentStructure([
+            'sample' => 'entry_id_full', 
+            'entry_id' => $iContentId, 
+            'parent_id' => $iParentId,
+            'status' => 'active'
+        ]);
+
+        if(empty($aNodes) || !is_array($aNodes))
+            return $this->_bIsApi ? [] : '';
+
+        $sJsObject = $this->_oConfig->getJsObject('entry');
+        $oPermalink = BxDolPermalinks::getInstance();
+
+        $iLevelMax = $this->_oConfig->getContentLevelMax();
+
+        $aInputs = [];
+        foreach($aNodes as $aNode) {
+            $aInputs['node_' . $aNode['node_id']] = [
+                'type' => 'block_header',
+                'caption' => bx_process_output($aNode['title']),
+                'collapsed' => false,
+                'attrs' => ['id' => 'node_' . $aNode['node_id'], 'class' => ''],
+            ];
+
+            $aSubNodes = $this->_oDb->getContentStructure([
+                'sample' => 'entry_id_full', 
+                'entry_id' => $iContentId, 
+                'parent_id' => (int)$aNode['node_id'], 
+                'status' => 'active'
+            ]);
+
+            if(!empty($aSubNodes) && is_array($aSubNodes)) {
+                $sTmplKeysShowPass = $this->_bIsApi ? 'show_pass' : 'bx_if:show_pass';
+                $aTmplVarsNodes = [];
+                foreach($aSubNodes as $iKey => $aSubNode) {
+                    list($iPassPercent, $sPassProgress, $sPassStatus, $sPassTitle) = $this->_oModule->getNodePassByData($iProfileId, $iContentId, $aSubNode);
+
+                    $sLink = BX_DOL_URL_ROOT . $oPermalink->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY_NODE'] . '&id=' . $iContentId, [
+                        'node_id' => $aSubNode['node_id']
+                    ]);
+
+                    $bShowPass = !empty($sPassTitle);
+                    
+                    $aSubNode = array_merge($aSubNode, [
+                        'level_max' => $iLevelMax,
+                        'index' => $iKey + 1,
+                        'link' => $this->_bIsApi ? bx_api_get_relative_url($sLink) : $sLink,
+                        'pass_percent' => $iPassPercent,
+                        'pass_progress' => $sPassProgress,
+                        'pass_status' => $sPassStatus,
+                        $sTmplKeysShowPass => $this->_bIsApi ? $bShowPass : [
+                            'condition' => $sPassTitle,
+                            'content' => [
+                                'js_object' => $sJsObject,
+                                'id' => $aSubNode['node_id'],
+                                'pass_href' => $sLink,
+                                'pass_title' => $sPassTitle,
+                            ]
+                        ]
+                    ]);
+
+                    $aTmplVarsNodes[] = $this->_bIsApi ? $aSubNode : [
+                        'node' => $this->parseHtmlByName('ml3_node_l' . $aSubNode['level'] . '.html', $aSubNode)
+                    ];
+                }
+
+                $sInput = 'node_' . $aNode['node_id'] . '_subnodes';
+                $aInputs[$sInput] = [
+                    'type' => 'custom',
+                    'name' => $sInput,
+                    'caption' => '',
+                    'content' => $this->_bIsApi ? $aTmplVarsNodes : $this->parseHtmlByName('ml3_nodes_l' . $aSubNode['level'] . '.html', [
+                        'level_max' => $iLevelMax,
+                        'level' => $aSubNode['level'],
+                        'bx_repeat:nodes' => $aTmplVarsNodes
+                    ]),
+                ];
+            }
+            else
+                $aInputs['node_' . $aNode['node_id']]['collapsed'] = true;
+        }
+
+        $oForm = new BxTemplFormView([
+            'form_attrs' => [
+                'id' => 'bx-courses-structure-by-parent-' . $iParentId,
+            ],
+            'inputs' => $aInputs,
+        ]);
+        $oForm->setShowEmptySections(true);
+
+        return $this->_bIsApi ? $oForm->getCodeAPI() : $oForm->getCode();
+    }
+
+    public function entryNode($aContentInfo, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!isset($aParams['node_id']))
+            return '';
+
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+        $iNodeId = (int)$aParams['node_id'];
+        $iUsage = isset($aParams['usage']) && $aParams['usage'] !== false ? (int)$aParams['usage'] : BX_COURSES_CND_USAGE_ST;
+        $iProfileId = bx_get_logged_profile_id();
+
+        $aNode = $this->_oDb->getContentNodes([
+            'sample' => 'id_full', 
+            'id' => $iNodeId,
+        ]);
+
+        $aTmplVars = [
+            'index' => $aNode['order'],
+            'sample' => $this->_oConfig->getContentNodeTitle($aNode['level']),
+            'title' => $aNode['title'],
+            'text' => $aNode['text'],
+            'passing' => $aNode['passing']
+        ];
+
+        if($this->_bIsApi)
+            return array_merge($aTmplVars, [
+                'steps' => $this->_entryNodeItems($iProfileId, $iContentId, $aNode, BX_COURSES_CND_USAGE_ST),
+                'attachments' => $this->_entryNodeItems($iProfileId, $iContentId, $aNode, BX_COURSES_CND_USAGE_AT) 
+            ]);
+        else
+            $aTmplVars['bx_repeat:items'] = $this->_entryNodeItems($iProfileId, $iContentId, $aNode, $iUsage);
+            
+
+        $sMiName = 'node-data-';
+        $sMiLink = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY_NODE'] . '&id=' . $iContentId, [
+            'node_id' => $iNodeId,
+            'usage' => ''
+        ]);
+
+        $oMenu = new BxTemplMenu([
+            'template' => 'menu_block_submenu_ver.html', 
+            'menu_id'=> 'node-data', 
+            'menu_items' => [
+                ['id' => $sMiName . 'st', 'name' => $sMiName . 'st', 'class' => '', 'link' => $sMiLink . '0', 'target' => '_self', 'title' => _t('_bx_courses_menu_item_title_node_data_steps')],
+                ['id' => $sMiName . 'at', 'name' => $sMiName . 'at', 'class' => '', 'link' => $sMiLink . '1', 'target' => '_self', 'title' => _t('_bx_courses_menu_item_title_node_data_attachments')]
+            ]
+        ]);
+        $oMenu->setSelected('', $sMiName . $this->_oConfig->getUsageI2S($iUsage));
+
+        return [
+            'content' => $this->parseHtmlByName('node_view.html', $aTmplVars),
+            'menu' => $oMenu
+        ];
+    }
+
+    public function entryData($aData, $sView)
+    {
+        $sJsObject = $this->_oConfig->getJsObject('entry');
+
+        $aNode = $this->_oDb->getContentNodes([
+            'sample' => 'id', 
+            'id' => $aData['node_id']
+        ]);  
+
+        $iOrder = (int)$aData['order'];
+        $aSiblings = $this->_oDb->getContentData([
+            'sample' => 'siblings', 
+            'entry_id' => $aData['entry_id'], 
+            'node_id' => $aData['node_id'], 
+            'usage' => BX_COURSES_CND_USAGE_ST, 
+            'order' => $iOrder
+        ]);
+
+        $bTmplVarsBack = false;
+        $aTmplVarsBack = ($iOrderB = $iOrder - 1) && ($bTmplVarsBack = !empty($aSiblings[$iOrderB]) && is_array($aSiblings[$iOrderB])) ? [
+            'js_object' => $sJsObject,
+            'id_back' => $aSiblings[$iOrderB]['id']
+        ] : [true];
+
+        $bTmplVarsNext = false;
+        $aTmplVarsNext = ($iOrderN = $iOrder + 1) && ($bTmplVarsNext = !empty($aSiblings[$iOrderN]) && is_array($aSiblings[$iOrderN])) ? [
+            'js_object' => $sJsObject,
+            'id_next' => $aSiblings[$iOrderN]['id']
+        ] : [true];
+
+        return ['popup' => [
+            'html' => BxTemplFunctions::getInstance()->popupBox($this->_oConfig->getHtmlIds('popup_content_data'), $aNode['title'], $this->parseHtmlByName('node_data_view.html', [
+                'js_object' => $sJsObject,
+                'view' => $sView,
+                'bx_if:show_back' => [
+                    'condition' => $bTmplVarsBack,
+                    'content' => $aTmplVarsBack
+                ],
+                'bx_if:show_next' => [
+                    'condition' => $bTmplVarsNext,
+                    'content' => $aTmplVarsNext
+                ]
+            ]), true),
+            'options' => [
+                'onHide' => 'document.location = document.location'
+            ]
+        ]];
+    }
+
+    protected function _entryNodeItems($iProfileId, $iContentId, $aNode, $iUsage)
+    {
+        $sJsObject = $this->_oConfig->getJsObject('entry');
+
+        $aDataItems = $this->_oDb->getContentData([
+            'sample' => 'entry_node_ids', 
+            'entry_id' => $iContentId,
+            'node_id' => $aNode['id'],
+            'usage' => $iUsage
+        ]);
+
+        $sTxtUndefined = _t('_undefined');
+        $sTxtPass = _t('_bx_courses_txt_pass');
+
+        $bUsageSt = $iUsage == BX_COURSES_CND_USAGE_ST;
+        $bUsageAt = $iUsage == BX_COURSES_CND_USAGE_AT;
+
+        $aResults = [];
+        if(!empty($aDataItems) && is_array($aDataItems))
+            foreach($aDataItems as $iIndex => $aDataItem) {
+                $sImageUrl = '';
+                if(($sMethod = 'get_thumb') && bx_is_srv($aDataItem['content_type'], $sMethod))
+                    $sImageUrl = bx_srv($aDataItem['content_type'], $sMethod, [$aDataItem['content_id']]);
+
+                $sVideoUrl = '';
+                if(($sMethod = 'get_video') && bx_is_srv($aDataItem['content_type'], $sMethod))
+                    $sVideoUrl = bx_srv($aDataItem['content_type'], $sMethod, [$aDataItem['content_id']]);
+
+                $sLink = '';
+                if(($sMethod = 'get_link') && bx_is_srv($aDataItem['content_type'], $sMethod))
+                    $sLink = bx_srv($aDataItem['content_type'], $sMethod, [$aDataItem['content_id']]);
+
+                $bPassed = $bUsageSt && $this->_oModule->isDataPassed($iProfileId, $aDataItem);
+
+                $aTmplVarsPass = [true];
+                $bTmplVarsPass = $bUsageSt && !$bPassed && $sLink && ((int)$aNode['passing'] == BX_COURSES_CND_PASSING_ALL || $iIndex == 0 || $this->_oModule->isDataPassed($iProfileId, $aDataItems[$iIndex - 1]));
+                if($bTmplVarsPass) {
+                    $aTmplVarsPass = [
+                        'js_object' => $sJsObject,
+                        'id' => $aDataItem['id'],
+                        'link' => $sLink,
+                        'title' => $sTxtPass
+                    ];
+                }
+
+                $sType = _t('_bx_courses_txt_data_type_' . $aDataItem['content_type']);
+
+                $sTitle = '';
+                if(($sMethod = 'get_title') && bx_is_srv($aDataItem['content_type'], $sMethod))              
+                    $sTitle = bx_srv($aDataItem['content_type'], $sMethod, [$aDataItem['content_id']]);
+                if(!$sTitle && ($sMethod = 'get_text') &&  bx_is_srv($aDataItem['content_type'], $sMethod))
+                    $sTitle = bx_srv($aDataItem['content_type'], $sMethod, [$aDataItem['content_id']]);
+                if(!$sTitle)
+                    $sTitle = $sTxtUndefined;
+
+                $bTmplVarsShowLink = $bUsageAt && $sLink;
+                $aTmplVarsShowLink = $bTmplVarsShowLink ? [
+                    'link' => $sLink,
+                    'title' => $sTitle
+                ] : false;
+
+                $bTmplVarsShowSize = $aTmplVarsShowSize = false;
+                $bTmplVarsShowDownload = $aTmplVarsShowDownload = false;
+                if($bUsageAt && ($bTmplVarsShowSize = ($sMethod = 'get_file') && bx_is_srv($aDataItem['content_type'], $sMethod))) {
+                    $aFileInfo = bx_srv($aDataItem['content_type'], $sMethod, [$aDataItem['content_id']]);
+                    if(!empty($aFileInfo) && is_array($aFileInfo)) {
+                        $aTmplVarsShowSize = [
+                            'size' => _t_format_size($aFileInfo['size'])
+                        ];
+
+                        if(($bTmplVarsShowDownload = !empty($aFileInfo['url_download'])))
+                            $aTmplVarsShowDownload = [
+                                'link' => $aFileInfo['url_download']
+                            ];
+                    }
+                }
+
+                if($this->_bIsApi) {
+                    $sText = '';
+                    if(($sMethod = 'get_text') &&  bx_is_srv($aDataItem['content_type'], $sMethod))
+                        $sText = bx_srv($aDataItem['content_type'], $sMethod, [$aDataItem['content_id']]);
+
+                    $aResults[] = array_merge([
+                        'id' => $aDataItem['id'],
+                        'type' => $sType,
+                        'title' => $sTitle,
+                        'text' => $sText,
+                        'image' => $sImageUrl,
+                        'video' => $sVideoUrl
+                    ], ($bUsageSt ? [
+                        'pass_link' => $bTmplVarsPass ? bx_api_get_relative_url($sLink) : '',
+                        'pass_title' => $sTxtPass,
+                        'passed' => $bPassed
+                    ] : [
+                        'size' => $bTmplVarsShowSize ? $aTmplVarsShowSize['size'] : '',
+                        'view_link' => $bTmplVarsShowLink ? bx_api_get_relative_url($sLink) : '',
+                        'download_link' => $bTmplVarsShowDownload ? $aTmplVarsShowDownload['link'] : '',
+                    ]));
+                }
+                else 
+                    $aResults[] = [
+                        'bx_if:show_image' => [
+                            'condition' => $sImageUrl,
+                            'content' => [
+                                'image' => $sImageUrl
+                            ]
+                        ],
+                        'bx_if:show_image_empty' => [
+                            'condition' => !$sImageUrl,
+                            'content' => [
+                                'type' => $sType
+                            ]
+                        ],
+                        'bx_if:show_video' => [
+                            'condition' => $sVideoUrl,
+                            'content' => [
+                                'video' => $sVideoUrl
+                            ]
+                        ],
+                        'type' => $sType,
+                        'bx_if:show_link' => [
+                            'condition' => $bTmplVarsShowLink,
+                            'content' => $aTmplVarsShowLink
+                        ],
+                        'bx_if:show_text' => [
+                            'condition' => !$bTmplVarsShowLink,
+                            'content' => [
+                                'title' => $sTitle,
+                            ]
+                        ],
+                        'bx_if:show_size' => [
+                            'condition' => $bTmplVarsShowSize,
+                            'content' => $aTmplVarsShowSize
+                        ],
+                        'bx_if:show_pass' => [
+                            'condition' => $bTmplVarsPass,
+                            'content' => $aTmplVarsPass
+                        ],
+                        'bx_if:show_download' => [
+                            'condition' => $bTmplVarsShowDownload,
+                            'content' => $aTmplVarsShowDownload
+                        ],
+                    ];
+            }
+
+        return $aResults;
+    }
+
+    protected function _getNodeStatus($iProfileId, $iContentId, $iNodeId)
+    {
+        if($this->_oModule->isNodePassed($iProfileId, $iNodeId))
+            return _t('_bx_courses_txt_status_completed');
+
+        if($this->_oModule->isNodeStarted($iProfileId, $iNodeId))
+            return _t('_bx_courses_txt_status_in_process');
+        
+        return _t('_bx_courses_txt_status_not_started');
+    }
+}
+
+/** @} */

+ 152 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/install/config.php

@@ -0,0 +1,152 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_courses',
+    'title' => 'Courses',
+    'note' => 'Basic course profiles functionality.',
+    'version' => '14.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/courses/',
+    'home_uri' => 'courses',
+
+    'db_prefix' => 'bx_courses_',
+    'class_prefix' => 'BxCourses',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Courses',
+
+    /**
+     * Connections.
+     */
+    'connections' => array(
+        'sys_profiles_friends' => array ('type' => 'profiles'),
+        'sys_profiles_subscriptions' => array ('type' => 'profiles'),
+    ),
+
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+    	'trigger_profile_view_submenu',
+        'trigger_group_view_submenu',
+        'trigger_group_snippet_meta',
+        'trigger_group_view_actions',
+    ),
+
+    /**
+     * Page triggers.
+     */
+    'page_triggers' => array (
+    	'trigger_page_group_view_entry', 
+    ),
+
+	/**
+     * Storage objects to automatically delete files from upon module uninstallation.
+     * Note. Don't add storage objects used in transcoder objects.
+     */
+    'storages' => array(
+    	'bx_courses_pics'
+    ),
+
+	/**
+     * Transcoders.
+     */
+    'transcoders' => array(
+    	'bx_courses_icon', 
+    	'bx_courses_thumb', 
+    	'bx_courses_avatar', 
+        'bx_courses_avatar_big', 
+    	'bx_courses_picture', 
+    	'bx_courses_cover', 
+    	'bx_courses_cover_thumb',
+        'bx_courses_gallery'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_courses',
+    	'bx_courses_cmts',
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+    	'process_esearches' => 1,
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    	'process_connections' => 1,
+        'process_deleted_profiles' => 1,
+    	'update_relations' => 1,        
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+    	'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable_success' => array(
+    	'process_menu_triggers' => 1,
+    	'process_page_triggers' => 1,
+    	'process_esearches' => 1,
+    	'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'unregister_transcoders' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+    	'register_transcoders' => 1,
+    	'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Connections Section
+     */
+    'relations' => array(
+        'bx_timeline',
+        'bx_notifications',
+    ),
+);
+
+/** @} */

+ 549 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/install/langs/en.xml

@@ -0,0 +1,549 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_courses"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_cmts"><![CDATA[Courses' Comments]]></string>
+
+    <string name="_bx_courses_option_enable_auto_approve"><![CDATA[Auto approve a course after creation]]></string>
+    <string name="_bx_courses_option_enable_content"><![CDATA[Enable possibility to add content in a course (experimental)]]></string>
+    <string name="_bx_courses_option_content_level_max"><![CDATA[Max level for a node in tree based structure (choose before start making courses)]]></string>
+    <string name="_bx_courses_option_content_modules_step"><![CDATA[Modules from which the content can be added in a course as step]]></string>
+    <string name="_bx_courses_option_content_modules_attachment"><![CDATA[Modules from which the content can be posted in a course as attachment]]></string>
+    <string name="_bx_courses_option_num_connections_quick"><![CDATA[Number of items in friends block]]></string>
+    <string name="_bx_courses_option_per_page_browse"><![CDATA[Number of items per page]]></string>
+    <string name="_bx_courses_option_num_rss"><![CDATA[Number of items in RSS feed]]></string>
+    <string name="_bx_courses_option_searchable_fields"><![CDATA[Searchable fields]]></string>
+    <string name="_bx_courses_option_members_mode"><![CDATA[Special mode for course members]]></string>
+    <string name="_bx_courses_option_members_mode_multi_roles"><![CDATA[Multi Roles]]></string>
+    <string name="_bx_courses_option_per_page_for_favorites_lists"><![CDATA[Number of favorite lists per page]]></string>
+    <string name="_bx_courses_option_internal_notifications"><![CDATA[Use internal notifications]]></string>
+
+    <string name="_bx_courses_form_profile"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_form_profile_display_add"><![CDATA[Add Course]]></string>
+    <string name="_bx_courses_form_profile_display_edit"><![CDATA[Edit Course]]></string>
+    <string name="_bx_courses_form_profile_display_edit_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_courses_form_profile_display_delete"><![CDATA[Delete Course]]></string>
+    <string name="_bx_courses_form_profile_display_view"><![CDATA[View Course]]></string>
+    <string name="_bx_courses_form_profile_display_view_full"><![CDATA[Full View Course]]></string>
+    <string name="_bx_courses_form_profile_display_invite"><![CDATA[Invite to Course]]></string>
+    <string name="_bx_courses_form_entry_input_author"><![CDATA[Author]]></string>
+    <string name="_bx_courses_form_profile_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_courses_form_profile_input_sys_join_confirm"><![CDATA[Join confirmation]]></string>
+    <string name="_bx_courses_form_profile_input_join_confirm"><![CDATA[Approval required to join]]></string>
+    <string name="_bx_courses_form_profile_input_sys_allow_view_to"><![CDATA[Visible to]]></string>
+    <string name="_bx_courses_form_profile_input_allow_view_to"><![CDATA[Visible to]]></string>
+    <string name="_bx_courses_form_profile_input_allow_view_favorite_list"><![CDATA[Visibility]]></string>
+    <string name="_bx_courses_form_profile_input_allow_view_to_desc"><![CDATA[Closed - anyone can find the course and see who's in it, only students can see posts. Secret - only students can find the course and see posts.]]></string>
+    <string name="_bx_courses_form_profile_input_sys_allow_post_to"><![CDATA[Who can post]]></string>
+    <string name="_bx_courses_form_profile_input_allow_post_to"><![CDATA[Who can post]]></string>
+    <string name="_bx_courses_form_profile_input_sys_initial_members"><![CDATA[Invite Students]]></string>
+    <string name="_bx_courses_form_profile_input_initial_members"><![CDATA[Invite Students]]></string>
+    <string name="_bx_courses_form_profile_input_sys_name"><![CDATA[Name]]></string>
+    <string name="_bx_courses_form_profile_input_name"><![CDATA[Name]]></string>
+    <string name="_bx_courses_form_profile_input_name_err"><![CDATA[This information is essential. Please, fill in this field.]]></string>
+    <string name="_bx_courses_form_profile_input_sys_desc"><![CDATA[Description]]></string>
+    <string name="_bx_courses_form_profile_input_desc"><![CDATA[Description]]></string>
+    <string name="_bx_courses_form_profile_input_sys_cat"><![CDATA[Category]]></string>
+    <string name="_bx_courses_form_profile_input_cat"><![CDATA[Category]]></string>
+    <string name="_bx_courses_form_profile_input_cat_err"><![CDATA[Category field is mandatory]]></string>
+    <string name="_bx_courses_form_profile_input_sys_picture"><![CDATA[Course Picture]]></string>
+    <string name="_bx_courses_form_profile_input_picture"><![CDATA[Upload New Course Picture]]></string>
+    <string name="_bx_courses_form_profile_input_picture_err"><![CDATA[Course picture is mandatory.]]></string>
+    <string name="_bx_courses_form_profile_input_sys_cover"><![CDATA[Cover Picture]]></string>
+    <string name="_bx_courses_form_profile_input_cover"><![CDATA[Upload New Cover Picture]]></string>
+    <string name="_bx_courses_form_profile_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string>
+    <string name="_bx_courses_form_profile_input_delete_confirm"><![CDATA[I am sure I want to delete this course]]></string>
+    <string name="_bx_courses_form_profile_input_delete_confirm_info"><![CDATA[This action can not be undone.]]></string>
+    <string name="_bx_courses_form_profile_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete course.]]></string>
+
+    <string name="_bx_courses_form_price"><![CDATA[Price]]></string>
+    <string name="_bx_courses_form_price_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_courses_form_price_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_courses_form_price_input_sys_id"><![CDATA[ID]]></string>
+    <string name="_bx_courses_form_price_input_sys_role_id"><![CDATA[Role ID]]></string>
+    <string name="_bx_courses_form_price_input_sys_name"><![CDATA[Name]]></string>
+    <string name="_bx_courses_form_price_input_name"><![CDATA[Name]]></string>
+    <string name="_bx_courses_form_price_input_inf_name"><![CDATA[Unique value which will be used in back end and API calls]]></string>
+    <string name="_bx_courses_form_price_input_err_name"><![CDATA[You need to enter a unique value here]]></string>
+    <string name="_bx_courses_form_price_input_sys_period"><![CDATA[Period]]></string>
+    <string name="_bx_courses_form_price_input_period"><![CDATA[Period]]></string>
+    <string name="_bx_courses_form_price_input_inf_period"><![CDATA[Use 0 for lifetime]]></string>
+    <string name="_bx_courses_form_price_input_sys_period_unit"><![CDATA[Period unit]]></string>
+    <string name="_bx_courses_form_price_input_period_unit"><![CDATA[Period unit]]></string>
+    <string name="_bx_courses_form_price_input_inf_period_unit"><![CDATA[Leave empty for lifetime level.]]></string>
+    <string name="_bx_courses_form_price_input_err_period_unit"><![CDATA[You need to select a period unit here.]]></string>
+    <string name="_bx_courses_form_price_input_sys_price"><![CDATA[Price]]></string>
+    <string name="_bx_courses_form_price_input_price"><![CDATA[Price]]></string>
+    <string name="_bx_courses_form_price_input_inf_price"><![CDATA[Use 0 or leave empty to create free level.]]></string>
+    <string name="_bx_courses_form_price_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_courses_form_price_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_courses_form_price_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_courses_form_price_input_do_cancel"><![CDATA[Cancel]]></string>
+
+    <string name="_bx_courses_form_content_node"><![CDATA[Content Node]]></string>
+    <string name="_bx_courses_form_content_node_display_add"><![CDATA[Add Node]]></string>
+    <string name="_bx_courses_form_content_node_display_edit"><![CDATA[Edit Node]]></string>
+    <string name="_bx_courses_form_content_node_input_sys_title"><![CDATA[Title]]></string>
+    <string name="_bx_courses_form_content_node_input_title"><![CDATA[Title]]></string>
+    <string name="_bx_courses_form_content_node_input_title_err"><![CDATA[Title is essential. Please, fill in this field.]]></string>
+    <string name="_bx_courses_form_content_node_input_sys_text"><![CDATA[Description]]></string>
+    <string name="_bx_courses_form_content_node_input_text"><![CDATA[Description]]></string>
+    <string name="_bx_courses_form_content_node_input_sys_passing"><![CDATA[Passing]]></string>
+    <string name="_bx_courses_form_content_node_input_passing"><![CDATA[Passing]]></string>
+    <string name="_bx_courses_form_content_node_input_passing_all"><![CDATA[All at once]]></string>
+    <string name="_bx_courses_form_content_node_input_passing_alternate"><![CDATA[Alternate]]></string>
+    <string name="_bx_courses_form_content_node_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_courses_form_content_node_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_courses_form_content_node_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_courses_form_content_node_input_do_cancel"><![CDATA[Cancel]]></string>
+
+    <string name="_bx_courses_acl_action_create_profile"><![CDATA[Create Course]]></string>
+    <string name="_bx_courses_acl_action_use_paid_join"><![CDATA[Use paid join]]></string>
+    <string name="_bx_courses_acl_action_delete_profile"><![CDATA[Delete Course]]></string>
+    <string name="_bx_courses_acl_action_view_profile"><![CDATA[View Course]]></string>
+    <string name="_bx_courses_acl_action_edit_any_profile"><![CDATA[Edit Any Course]]></string>
+    <string name="_bx_courses_acl_action_delete_any_profile"><![CDATA[Delete Any Course]]></string>
+    <string name="_bx_courses_acl_action_delete_invites"><![CDATA[Delete Invites]]></string>
+
+    <string name="_bx_courses_page_title_sys_joined_entries"><![CDATA[Profile Courses]]></string>
+    <string name="_bx_courses_page_title_joined_entries"><![CDATA[Profile Courses]]></string>
+    <string name="_bx_courses_page_title_sys_create_profile"><![CDATA[Create Course]]></string>
+    <string name="_bx_courses_page_title_create_profile"><![CDATA[Create Course]]></string>
+    <string name="_bx_courses_page_title_sys_edit_profile"><![CDATA[Edit Course]]></string>
+    <string name="_bx_courses_page_title_edit_profile"><![CDATA[Edit Course]]></string>
+    <string name="_bx_courses_page_title_sys_edit_profile_cover"><![CDATA[Edit Course Cover]]></string>
+    <string name="_bx_courses_page_title_edit_profile_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_courses_page_title_sys_delete_profile"><![CDATA[Delete Course]]></string>
+    <string name="_bx_courses_page_title_delete_profile"><![CDATA[Delete Course]]></string>
+    <string name="_bx_courses_page_title_sys_join_profile"><![CDATA[Join Course]]></string>
+    <string name="_bx_courses_page_title_join_profile"><![CDATA[Join Course]]></string>
+    <string name="_bx_courses_page_title_sys_view_profile_closed"><![CDATA[View Closed Course]]></string>
+    <string name="_bx_courses_page_title_sys_view_profile"><![CDATA[View Course]]></string>
+    <string name="_bx_courses_page_title_sys_view_profile_node"><![CDATA[View Lesson]]></string>
+    <string name="_bx_courses_page_title_view_profile"><![CDATA[{display_name}]]></string>
+    <string name="_bx_courses_page_title_view_profile_node"><![CDATA[Lesson]]></string>
+    <string name="_bx_courses_page_title_browse"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_page_title_browse_by_author"><![CDATA[Courses of {display_name}]]></string>
+    <string name="_bx_courses_page_title_browse_featured"><![CDATA[Featured Courses]]></string>
+    <string name="_bx_courses_page_title_browse_top"><![CDATA[Top Courses]]></string>
+    <string name="_bx_courses_page_title_browse_recent"><![CDATA[New Courses]]></string>
+    <string name="_bx_courses_page_title_browse_connections"><![CDATA[{0} connections]]></string>
+    <string name="_bx_courses_page_title_browse_connections_mutual"><![CDATA[{0} and {1} mutual connections]]></string>
+    <string name="_bx_courses_page_title_sys_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_courses_page_title_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_courses_page_title_sys_entry_fans"><![CDATA[Students]]></string>
+    <string name="_bx_courses_page_title_entry_fans"><![CDATA[Students]]></string>
+    <string name="_bx_courses_page_title_sys_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_courses_page_title_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_courses_page_block_title_entry_reports"><![CDATA[Reports]]></string>
+    <string name="_bx_courses_page_title_sys_profile_info"><![CDATA[Course Info]]></string>
+    <string name="_bx_courses_page_title_profile_info"><![CDATA[Info about {display_name}]]></string>
+    <string name="_bx_courses_page_title_sys_profile_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_courses_page_title_profile_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_courses_page_title_sys_profile_content"><![CDATA[Content]]></string>
+    <string name="_bx_courses_page_title_profile_content"><![CDATA[Content]]></string>
+    <string name="_bx_courses_page_title_sys_recent"><![CDATA[New Courses]]></string>
+    <string name="_bx_courses_page_title_recent"><![CDATA[New Courses]]></string>
+    <string name="_bx_courses_page_title_sys_manage"><![CDATA[Manage Own Courses]]></string>
+    <string name="_bx_courses_page_title_sys_manage_administration"><![CDATA[Manage All Courses]]></string>
+    <string name="_bx_courses_page_title_manage"><![CDATA[Manage Courses]]></string>
+    <string name="_bx_courses_page_title_sys_top"><![CDATA[Top Courses]]></string>
+    <string name="_bx_courses_page_title_top"><![CDATA[Top Courses]]></string>
+    <string name="_bx_courses_page_title_sys_joined"><![CDATA[Attended  Courses]]></string>
+    <string name="_bx_courses_page_title_joined"><![CDATA[Attended Courses]]></string>
+    <string name="_bx_courses_page_title_sys_invite_to_course"><![CDATA[Invite Students to the Course]]></string>
+    <string name="_bx_courses_page_title_invite_to_course"><![CDATA[Invite Students to the Course]]></string>
+    <string name="_bx_courses_page_title_sys_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_courses_page_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_courses_page_title_sys_entries_favorites"><![CDATA[Favorites Courses]]></string>
+    <string name="_bx_courses_page_title_entries_favorites"><![CDATA[Favorites Courses]]></string>
+    <string name="_bx_courses_page_block_title_sys_entries_actions"><![CDATA[Courses actions]]></string>
+    <string name="_bx_courses_page_block_title_entries_actions"><![CDATA[Courses actions]]></string>
+    <string name="_bx_courses_page_block_title_sys_favorites_entries"><![CDATA[Favorite List]]></string>
+    <string name="_bx_courses_page_block_title_favorites_entries"><![CDATA["{title}" List]]></string>
+    <string name="_bx_courses_page_block_title_favorites_entries_info"><![CDATA[Info]]></string>
+    <string name="_bx_courses_page_block_title_favorites_entries_actions"><![CDATA[Favorites List Actions]]></string>
+    <string name="_bx_courses_page_block_title_sys_entries_of_author"><![CDATA[Courses of author]]></string>
+    <string name="_bx_courses_page_block_title_entries_of_author"><![CDATA[Courses by {display_name}]]></string>
+    <string name="_bx_courses_page_block_title_favorites_of_author"><![CDATA[Favorite Courses]]></string>
+    <string name="_bx_courses_page_block_title_sys_favorites_of_author"><![CDATA[Favorite Courses]]></string>
+    <string name="_bx_courses_page_block_title_sys_joined_entries_summary"><![CDATA[Attended Courses Summary]]></string>
+    <string name="_bx_courses_page_block_title_joined_entries_summary"><![CDATA[Attended Courses Summary]]></string>
+    <string name="_bx_courses_page_block_title_sys_joined_entries"><![CDATA[Attended Courses]]></string>
+    <string name="_bx_courses_page_block_title_joined_entries"><![CDATA[Attended Courses]]></string>
+    <string name="_bx_courses_page_block_title_create_profile"><![CDATA[Create Course]]></string>
+    <string name="_bx_courses_page_block_title_edit_profile"><![CDATA[Edit Course]]></string>
+    <string name="_bx_courses_page_block_title_edit_profile_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_courses_page_block_title_delete_profile"><![CDATA[Delete Course]]></string>
+    <string name="_bx_courses_page_block_title_join_profile"><![CDATA[Join Course]]></string>
+    <string name="_bx_courses_page_block_title_profile_info_link"><![CDATA[About <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_courses_page_block_title_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_courses_page_block_title_system_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_courses_page_block_title_system_profile_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_courses_page_block_title_profile_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_courses_page_block_title_profile_pricing_link"><![CDATA[Pricing of <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_courses_page_block_title_system_profile_structure"><![CDATA[Structure]]></string>
+    <string name="_bx_courses_page_block_title_profile_structure_link"><![CDATA[Structure of <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_courses_page_block_title_system_profile_data"><![CDATA[Content]]></string>
+    <string name="_bx_courses_page_block_title_profile_data_link"><![CDATA[Content of <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_courses_page_block_title_profile_description"><![CDATA[Description]]></string>
+    <string name="_bx_courses_page_block_title_profile_structure_l1"><![CDATA[Modules]]></string>
+    <string name="_bx_courses_page_block_title_profile_structure_l2"><![CDATA[Themes]]></string>
+    <string name="_bx_courses_page_block_title_profile_friends_link"><![CDATA[Friends of <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_courses_page_block_title_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_courses_page_block_title_system_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_courses_page_block_title_admins"><![CDATA[Instructors]]></string>
+    <string name="_bx_courses_page_block_title_fans_link"><![CDATA[Students of <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_courses_page_block_title_fans"><![CDATA[Students]]></string>
+    <string name="_bx_courses_page_block_title_system_fans"><![CDATA[Students]]></string>
+    <string name="_bx_courses_page_block_title_fans_invites"><![CDATA[Invites]]></string>
+    <string name="_bx_courses_page_block_title_system_invites"><![CDATA[Invites]]></string>
+    <string name="_bx_courses_page_block_title_profile_location"><![CDATA[Location]]></string>
+    <string name="_bx_courses_page_block_title_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_courses_page_block_title_profile_comments_link"><![CDATA[Comments to <a href="{entry_link}">{title}</a>]]></string>
+    <string name="_bx_courses_page_block_title_profile_actions"><![CDATA[Actions]]></string>
+    <string name="_bx_courses_page_block_title_featured_profiles"><![CDATA[Featured Courses]]></string>
+    <string name="_bx_courses_page_block_title_latest_profiles"><![CDATA[New Courses]]></string>
+    <string name="_bx_courses_page_block_title_system_manage"><![CDATA[Manage Own Courses]]></string>
+    <string name="_bx_courses_page_block_title_system_manage_administration"><![CDATA[Manage All Courses]]></string>
+    <string name="_bx_courses_page_block_title_manage"><![CDATA[Manage Courses]]></string>
+    <string name="_bx_courses_page_block_title_top_profiles"><![CDATA[Top Courses]]></string>
+    <string name="_bx_courses_page_block_title_joined_profiles"><![CDATA[Attended Courses]]></string>
+    <string name="_bx_courses_page_block_title_entry_all_actions"><![CDATA[All Actions]]></string>
+    <string name="_bx_courses_page_block_title_entry_social_sharing"><![CDATA[Share]]></string>
+    <string name="_bx_courses_page_block_title_system_entry_node"><![CDATA[Lesson]]></string>
+    <string name="_bx_courses_page_block_title_entry_node"><![CDATA[]]></string>
+    <string name="_bx_courses_page_block_title_categories"><![CDATA[Courses Categories]]></string>
+    <string name="_bx_courses_page_block_title_invite_to_course"><![CDATA[Invite Students to the Course]]></string>
+    <string name="_bx_courses_page_block_title_search_form"><![CDATA[Search Form]]></string>
+    <string name="_bx_courses_page_block_title_search_results"><![CDATA[Search Results]]></string>
+    <string name="_bx_courses_page_block_title_search_form_cmts"><![CDATA[Comments Search Form]]></string>
+    <string name="_bx_courses_page_block_title_search_results_cmts"><![CDATA[Comments Search Results]]></string>
+    <string name="_bx_courses_page_block_title_sys_featured_entries_view_showcase"><![CDATA[Featured Courses (Showcase View)]]></string>
+    <string name="_bx_courses_page_block_title_featured_entries_view_showcase"><![CDATA[Featured Courses]]></string>
+    <string name="_bx_courses_page_block_title_sys_recommended_entries_view_showcase"><![CDATA[Recommended Courses (Showcase View)]]></string>
+    <string name="_bx_courses_page_block_title_recommended_entries_view_showcase"><![CDATA[Recommended Courses]]></string>
+    <string name="_bx_courses_page_block_title_sys_labels_tree"><![CDATA[Labels tree]]></string>
+    <string name="_bx_courses_page_block_title_labels_tree"><![CDATA[Labels tree]]></string>
+    <string name="_bx_courses_page_block_title_sys_browse_by_label"><![CDATA[Browse by label]]></string>
+    <string name="_bx_courses_page_block_title_browse_by_label"><![CDATA[Browse by label]]></string>
+    <string name="_bx_courses_page_block_title_sys_labels_breadcrumbs"><![CDATA[Labels breadcrumbs]]></string>
+    <string name="_bx_courses_page_block_title_labels_breadcrumbs"><![CDATA[Labels breadcrumbs]]></string>
+    <string name="_bx_courses_page_block_title_sys_cover_block"><![CDATA[Cover]]></string>
+    <string name="_bx_courses_page_block_title_cover_block"><![CDATA[Cover]]></string>
+
+
+    <string name="_bx_courses_menu_title_submenu"><![CDATA[Courses Submenu]]></string>
+    <string name="_bx_courses_menu_set_title_submenu"><![CDATA[Courses Submenu]]></string>
+    <string name="_bx_courses_menu_set_title_view_profile_actions"><![CDATA[Course Actions]]></string>
+    <string name="_bx_courses_menu_title_view_profile_actions"><![CDATA[Course Actions]]></string>
+    <string name="_bx_courses_menu_set_title_view_profile_actions_more"><![CDATA[Course More Actions]]></string>
+    <string name="_bx_courses_menu_title_view_profile_actions_more"><![CDATA[Course More Actions]]></string>
+    <string name="_bx_courses_menu_title_view_profile_meta"><![CDATA[Course Meta]]></string>
+    <string name="_bx_courses_menu_set_title_view_profile_meta"><![CDATA[Course Meta]]></string>
+    <string name="_bx_courses_menu_set_title_view_profile_submenu"><![CDATA[View Course Submenu]]></string>
+    <string name="_bx_courses_menu_title_view_profile_submenu"><![CDATA[View Course Submenu]]></string>
+    <string name="_bx_courses_menu_title_entries_my"><![CDATA[Profile Courses Action Menu]]></string>
+    <string name="_bx_courses_menu_set_title_entries_my"><![CDATA[Profile Courses Menu]]></string>
+    <string name="_bx_courses_menu_set_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_courses_menu_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_courses_menu_item_title_system_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_courses_menu_item_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_courses_menu_item_title_system_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_courses_menu_item_title_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_courses_menu_item_title_system_clear_reports"><![CDATA[Clear reports]]></string>
+    <string name="_bx_courses_menu_item_title_clear_reports"><![CDATA[Clear reports]]></string>
+    <string name="_bx_courses_menu_item_title_system_edit_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_courses_menu_item_title_edit_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_courses_menu_item_title_system_edit_pricing"><![CDATA[Edit Pricing]]></string>
+    <string name="_bx_courses_menu_item_title_edit_pricing"><![CDATA[Edit Pricing]]></string>
+    <string name="_bx_courses_menu_item_title_system_edit_content"><![CDATA[Edit Content]]></string>
+    <string name="_bx_courses_menu_item_title_edit_content"><![CDATA[Edit Content]]></string>
+    <string name="_bx_courses_menu_item_title_system_hide_profile"><![CDATA[Hide Course]]></string>
+    <string name="_bx_courses_menu_item_title_hide_profile"><![CDATA[Hide]]></string>
+    <string name="_bx_courses_menu_item_title_system_unhide_profile"><![CDATA[Unhide Course]]></string>
+    <string name="_bx_courses_menu_item_title_unhide_profile"><![CDATA[Publish]]></string>
+    <string name="_bx_courses_menu_item_title_system_edit_profile"><![CDATA[Edit Course]]></string>
+    <string name="_bx_courses_menu_item_title_edit_profile"><![CDATA[Edit Course]]></string>
+    <string name="_bx_courses_menu_item_title_system_delete_profile"><![CDATA[Delete Course]]></string>
+    <string name="_bx_courses_menu_item_title_delete_profile"><![CDATA[Delete Course]]></string>
+    <string name="_bx_courses_menu_item_title_system_delete_account"><![CDATA[Delete Account]]></string>
+    <string name="_bx_courses_menu_item_title_delete_account"><![CDATA[Delete Account]]></string>
+    <string name="_bx_courses_menu_item_title_system_profile_type"><![CDATA[Course]]></string>
+    <string name="_bx_courses_menu_item_title_profile_type"><![CDATA[Course]]></string>
+    <string name="_bx_courses_menu_item_title_system_create_profile"><![CDATA[Course]]></string>
+    <string name="_bx_courses_menu_item_title_create_profile"><![CDATA[Course]]></string>
+    <string name="_bx_courses_menu_item_title_system_view_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_courses_menu_item_title_view_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_courses_menu_item_title_system_view_joined_entries"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_view_joined_entries"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_system_view_fans"><![CDATA[Students]]></string>
+    <string name="_bx_courses_menu_item_title_view_fans"><![CDATA[Students]]></string>
+    <string name="_bx_courses_menu_item_title_system_view_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_courses_menu_item_title_view_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_courses_menu_item_title_system_view_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_courses_menu_item_title_view_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_courses_menu_item_title_system_view_profile_view"><![CDATA[Course]]></string>
+    <string name="_bx_courses_menu_item_title_view_profile_view"><![CDATA[Course]]></string>
+    <string name="_bx_courses_menu_item_title_system_view_profile_more_auto"><![CDATA[More]]></string>
+    <string name="_bx_courses_menu_item_title_view_profile_more_auto"><![CDATA[More]]></string>
+    <string name="_bx_courses_menu_item_title_system_invite"><![CDATA[Invite]]></string>
+    <string name="_bx_courses_menu_item_title_invite"><![CDATA[Invite]]></string>
+
+    <string name="_bx_courses_menu_item_title_system_befriend"><![CDATA[Add friend]]></string>
+    <string name="_bx_courses_menu_item_title_befriend"><![CDATA[Add friend]]></string>
+    <string name="_bx_courses_menu_item_title_befriend_sent"><![CDATA[Add friend (requested)]]></string>
+    <string name="_bx_courses_menu_item_title_befriend_confirm"><![CDATA[Accept friend request]]></string>
+    <string name="_bx_courses_menu_item_title_system_unfriend"><![CDATA[Unfriend]]></string>
+    <string name="_bx_courses_menu_item_title_unfriend"><![CDATA[Unfriend]]></string>
+    <string name="_bx_courses_menu_item_title_unfriend_cancel_request"><![CDATA[Cancel friend request]]></string>
+    <string name="_bx_courses_menu_item_title_unfriend_reject_request"><![CDATA[Reject friend request]]></string>
+
+    <string name="_bx_courses_menu_item_title_system_become_fan"><![CDATA[Attend Course]]></string>
+    <string name="_bx_courses_menu_item_title_become_fan"><![CDATA[Attend Course]]></string>
+    <string name="_bx_courses_menu_item_title_become_fan_sent"><![CDATA[Attend Course (requested)]]></string>
+    <string name="_bx_courses_menu_item_title_become_fan_confirm"><![CDATA[Accept Request]]></string>
+    <string name="_bx_courses_menu_item_title_system_leave_entry"><![CDATA[Leave Course]]></string>
+    <string name="_bx_courses_menu_item_title_leave_entry"><![CDATA[Leave Course]]></string>
+    <string name="_bx_courses_menu_item_title_leave_entry_cancel_request"><![CDATA[Cancel Request]]></string>
+    <string name="_bx_courses_menu_item_title_leave_entry_reject_request"><![CDATA[Reject Request]]></string>
+
+    <string name="_bx_courses_menu_item_title_system_pay_and_join"><![CDATA[Pay & Join]]></string>
+    <string name="_bx_courses_menu_item_title_pay_and_join"><![CDATA[Pay & Join]]></string>
+
+    <string name="_bx_courses_menu_item_title_system_subscribe"><![CDATA[Follow]]></string>
+    <string name="_bx_courses_menu_item_title_subscribe"><![CDATA[Follow]]></string>
+    <string name="_bx_courses_menu_item_title_system_unsubscribe"><![CDATA[Unfollow]]></string>
+    <string name="_bx_courses_menu_item_title_unsubscribe"><![CDATA[Unfollow]]></string>
+    <string name="_bx_courses_menu_item_title_system_more_actions"><![CDATA[More Actions]]></string>
+    <string name="_bx_courses_menu_item_title_more_actions"><![CDATA[]]></string>
+    <string name="_bx_courses_menu_item_title_system_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_courses_menu_item_title_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_courses_menu_item_title_system_manage_my_entries"><![CDATA[My courses]]></string>
+    <string name="_bx_courses_menu_item_title_manage_my_entries"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_system_followings"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_followings"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_system_entries_recent"><![CDATA[New]]></string>
+    <string name="_bx_courses_menu_item_title_entries_recent"><![CDATA[New]]></string>
+    <string name="_bx_courses_menu_item_title_system_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_courses_menu_item_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_courses_menu_item_title_system_entries_joined"><![CDATA[Attended]]></string>
+    <string name="_bx_courses_menu_item_title_entries_joined"><![CDATA[Attended]]></string>
+    <string name="_bx_courses_menu_item_title_system_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_courses_menu_item_title_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_courses_menu_item_title_system_entries_home"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_entries_home"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_system_admt_entries"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_admt_entries"><![CDATA[Courses]]></string>
+    <string name="_bx_courses_menu_item_title_manage_my"><![CDATA[My Courses]]></string>
+    <string name="_bx_courses_menu_item_title_manage_all"><![CDATA[All Courses]]></string>
+    <string name="_bx_courses_menu_item_title_system_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_courses_menu_item_title_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_courses_menu_item_title_system_sm_privacy"><![CDATA[Privacy]]></string>
+    <string name="_bx_courses_menu_item_title_sm_privacy"><![CDATA[Privacy]]></string>
+    <string name="_bx_courses_menu_item_title_system_sm_members"><![CDATA[Students]]></string>
+    <string name="_bx_courses_menu_item_title_sm_members"><![CDATA[{0} students]]></string>
+    <string name="_bx_courses_menu_item_title_system_sm_join"><![CDATA[Attend]]></string>
+    <string name="_bx_courses_menu_item_title_sm_join"><![CDATA[Attend]]></string>
+    <string name="_bx_courses_menu_item_title_sm_join_paid"><![CDATA[Pay & Attend]]></string>
+    <string name="_bx_courses_menu_item_title_sm_join_confirm"><![CDATA[Accept attend request]]></string>
+    <string name="_bx_courses_menu_item_title_sm_leave"><![CDATA[Leave]]></string>
+    <string name="_bx_courses_menu_item_title_sm_leave_reject"><![CDATA[Reject attend request]]></string>
+    <string name="_bx_courses_menu_item_title_sm_leave_cancel"><![CDATA[Cancel attend request]]></string>
+    <string name="_bx_courses_menu_item_title_system_sm_reports"><![CDATA[Reports]]></string>
+    <string name="_bx_courses_menu_item_title_system_sm_pass"><![CDATA[Pass]]></string>
+
+    <string name="_bx_courses_menu_title_content_add"><![CDATA[Add Content]]></string>
+    <string name="_bx_courses_menu_set_title_content_add"><![CDATA[Add Content]]></string>
+    <string name="_bx_courses_menu_item_title_node_data_steps"><![CDATA[Steps]]></string>
+    <string name="_bx_courses_menu_item_title_node_data_attachments"><![CDATA[Attachments]]></string>
+
+    <string name="_bx_courses_grid_filter_item_title_adm_select_one_filter1"><![CDATA[All Statuses]]></string>
+    <string name="_bx_courses_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string>
+    <string name="_bx_courses_grid_filter_item_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_courses_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string>
+    <string name="_bx_courses_grid_filter_item_title_adm_pending"><![CDATA[Pending]]></string>
+    <string name="_bx_courses_grid_filter_item_title_adm_suspended"><![CDATA[Suspended]]></string>
+    <string name="_bx_courses_grid_column_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_courses_grid_column_title_adm_name"><![CDATA[Name]]></string>
+    <string name="_bx_courses_grid_column_title_adm_added"><![CDATA[Created]]></string>
+    <string name="_bx_courses_grid_column_title_adm_account"><![CDATA[Account]]></string>
+    <string name="_bx_courses_grid_action_title_adm_audit_context"><![CDATA[Audit actions in context]]></string>
+    <string name="_bx_courses_grid_action_title_adm_audit_content"><![CDATA[Audit actions with context]]></string>
+    <string name="_bx_courses_grid_action_title_adm_clear_reports"><![CDATA[Сlear reports]]></string>
+    <string name="_bx_courses_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_courses_grid_action_title_adm_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_courses_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
+    <string name="_bx_courses_grid_action_err_delete"><![CDATA[Cannot delete the selected course(s).]]></string>
+    <string name="_bx_courses_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+
+    <string name="_bx_courses_grid_column_title_name"><![CDATA[Name]]></string>
+    <string name="_bx_courses_grid_column_title_role_id"><![CDATA[Role]]></string>
+    <string name="_bx_courses_grid_column_title_period"><![CDATA[Period]]></string>
+    <string name="_bx_courses_grid_column_title_price"><![CDATA[Price]]></string>
+    <string name="_bx_courses_grid_action_title_add"><![CDATA[Add]]></string>
+    <string name="_bx_courses_grid_action_title_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_courses_grid_action_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_courses_grid_action_title_buy"><![CDATA[Buy]]></string>
+    <string name="_bx_courses_grid_action_title_buy_title"><![CDATA[One-time payment]]></string>
+    <string name="_bx_courses_grid_action_title_subscribe"><![CDATA[Subscribe]]></string>
+    <string name="_bx_courses_grid_action_title_subscribe_title"><![CDATA[Create a subscription with recurring billing]]></string>
+    <string name="_bx_courses_grid_action_title_choose"><![CDATA[Choose]]></string>
+    <string name="_bx_courses_grid_action_title_choose_title"><![CDATA[Switch to the level for free]]></string>
+    <string name="_bx_courses_popup_title_price_add"><![CDATA[Add Pricing Plan]]></string>
+    <string name="_bx_courses_popup_title_price_edit"><![CDATA[Edit Pricing Plan]]></string>
+
+    <string name="_bx_courses_grid_column_title_cnt_active"><![CDATA[]]></string>
+    <string name="_bx_courses_grid_column_title_cnt_title"><![CDATA[Title]]></string>
+    <string name="_bx_courses_grid_column_title_cnt_cn_l2"><![CDATA[Themes]]></string>
+    <string name="_bx_courses_grid_column_title_cnt_cn_l3"><![CDATA[Lessons]]></string>
+    <string name="_bx_courses_grid_column_title_cnt_counters"><![CDATA[Info]]></string>
+    <string name="_bx_courses_grid_column_title_cnt_content_id"><![CDATA[Content]]></string>
+    <string name="_bx_courses_grid_column_title_cnt_content_type"><![CDATA[Type]]></string>
+    <string name="_bx_courses_grid_column_title_cnt_usage"><![CDATA[Usage]]></string>
+    <string name="_bx_courses_grid_column_title_cnt_added"><![CDATA[Added]]></string>
+    <string name="_bx_courses_grid_action_title_cnt_back"><![CDATA[Back]]></string>
+    <string name="_bx_courses_grid_action_title_cnt_add"><![CDATA[Add {node}]]></string>
+    <string name="_bx_courses_grid_action_title_cnt_add_step"><![CDATA[Add step]]></string>
+    <string name="_bx_courses_grid_action_title_cnt_add_attachment"><![CDATA[Add attachment]]></string>
+    <string name="_bx_courses_grid_action_title_cnt_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_courses_grid_action_title_cnt_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_courses_popup_title_cnt_add"><![CDATA[Add {node}]]></string>
+    <string name="_bx_courses_popup_title_cnt_edit"><![CDATA[Edit {node}]]></string>
+
+    <string name="_bx_courses_chart_growth"><![CDATA[Courses: Growth]]></string>
+    <string name="_bx_courses_chart_growth_speed"><![CDATA[Courses: Growth Speed]]></string>
+
+    <string name="_bx_courses_search_extended"><![CDATA[Courses Search]]></string>
+    <string name="_bx_courses_search_extended_cmts"><![CDATA[Courses Comments Search]]></string>
+
+    <string name="_bx_courses_txt_account_pending"><![CDATA[This course is pending approval, please wait until it is activated.]]></string>
+    <string name="_bx_courses_txt_account_suspended"><![CDATA[This course is suspended.]]></string>
+    <string name="_bx_courses_txt_status_hidden"><![CDATA[This course is hidden.]]></string>
+    <string name="_bx_courses_txt_N_fans"><![CDATA[{0} students]]></string>
+    <string name="_bx_courses_txt_role"><![CDATA[Role]]></string>
+    <string name="_bx_courses_txt_role_added"><![CDATA[Added]]></string>
+    <string name="_bx_courses_txt_role_expired"><![CDATA[Expires]]></string>
+    <string name="_bx_courses_txt_sample_single"><![CDATA[course]]></string>
+    <string name="_bx_courses_txt_sample_single_with_article"><![CDATA[a course]]></string>
+    <string name="_bx_courses_txt_sample_comment_single"><![CDATA[comment to course]]></string>
+    <string name="_bx_courses_txt_sample_vote_single"><![CDATA[vote to course]]></string>
+    <string name="_bx_courses_txt_sample_score_up_single"><![CDATA[score up vote to course]]></string>
+    <string name="_bx_courses_txt_sample_score_down_single"><![CDATA[score down vote to course]]></string>
+    <string name="_bx_courses_txt_set_role"><![CDATA[Set Role]]></string>
+    <string name="_bx_courses_txt_private_entry"><![CDATA[Private course]]></string>
+    <string name="_bx_courses_txt_invite_popup_text"><![CDATA[You have been invited to a course. Click the button below to accept the invitation.]]></string>
+    <string name="_bx_courses_txt_invite_popup_title"><![CDATA[Invitation to a course]]></string>
+    <string name="_bx_courses_txt_invite_popup_button_accept"><![CDATA[Accept]]></string>
+    <string name="_bx_courses_txt_invite_popup_button_decline"><![CDATA[Decline]]></string>
+    <string name="_bx_courses_txt_invite_popup_error_invitation_absent"><![CDATA[Sorry, an error has occurred. You can not join this course. The invitation is expired or deleted.]]></string>
+    <string name="_bx_courses_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Sorry, an error has occurred. You can not join this course. This invitation cannot be used by you.]]></string>
+    <string name="_bx_courses_txt_n_unit"><![CDATA[{0} ({1})]]></string>
+    <string name="_bx_courses_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_courses_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string>    
+    <string name="_bx_courses_txt_sample_l1_single"><![CDATA[module]]></string>
+    <string name="_bx_courses_txt_sample_l1_plural"><![CDATA[modules]]></string>
+    <string name="_bx_courses_txt_sample_l2_single"><![CDATA[theme]]></string>
+    <string name="_bx_courses_txt_sample_l2_plural"><![CDATA[themes]]></string>
+    <string name="_bx_courses_txt_sample_l3_single"><![CDATA[lesson]]></string>
+    <string name="_bx_courses_txt_sample_l3_plural"><![CDATA[lessons]]></string>
+    <string name="_bx_courses_txt_status_not_started"><![CDATA[not started]]></string>
+    <string name="_bx_courses_txt_status_in_process"><![CDATA[in process]]></string>
+    <string name="_bx_courses_txt_status_completed"><![CDATA[completed]]></string>
+    <string name="_bx_courses_txt_pass_start"><![CDATA[Start]]></string>
+    <string name="_bx_courses_txt_pass_continue"><![CDATA[Continue]]></string>
+    <string name="_bx_courses_txt_pass_again"><![CDATA[Pass again]]></string>
+    <string name="_bx_courses_txt_pass"><![CDATA[Pass]]></string>
+    <string name="_bx_courses_txt_passed"><![CDATA[Passed]]></string>
+    <string name="_bx_courses_txt_n_m_steps"><![CDATA[{0}/{1} steps]]></string>
+    <string name="_bx_courses_txt_n_m_progress"><![CDATA[{passed}/{total}]]></string>
+    <string name="_bx_courses_txt_st"><![CDATA[steps]]></string>
+    <string name="_bx_courses_txt_at"><![CDATA[attachments]]></string>
+    <string name="_bx_courses_txt_data_type_bx_files"><![CDATA[file]]></string>
+    <string name="_bx_courses_txt_data_type_bx_photos"><![CDATA[image]]></string>
+    <string name="_bx_courses_txt_data_type_bx_polls"><![CDATA[poll]]></string>
+    <string name="_bx_courses_txt_data_type_bx_posts"><![CDATA[theory]]></string>
+    <string name="_bx_courses_txt_data_type_bx_videos"><![CDATA[video]]></string>
+    <string name="_bx_courses_txt_data_usage_0"><![CDATA[step]]></string>
+    <string name="_bx_courses_txt_data_usage_1"><![CDATA[attachment]]></string>
+    <string name="_bx_courses_txt_back_step"><![CDATA[Back]]></string>
+    <string name="_bx_courses_txt_next_step"><![CDATA[Next]]></string>
+    <string name="_bx_courses_txt_download"><![CDATA[Download]]></string>
+    <string name="_bx_courses_txt_summary_title"><![CDATA[Study progress]]></string>
+    <string name="_bx_courses_txt_summary_joined"><![CDATA[Attended]]></string>
+    <string name="_bx_courses_txt_summary_passed"><![CDATA[Passed]]></string>
+    <string name="_bx_courses_txt_summary_not_started"><![CDATA[Not started]]></string>
+
+    <string name="_bx_courses_msg_performed"><![CDATA[The action was successfully performed.]]></string>
+    <string name="_bx_courses_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>
+    <string name="_bx_courses_err_cannot_perform"><![CDATA[An error appeared. Please report.]]></string>
+
+    <string name="_bx_courses_alert_action_fan_added"><![CDATA[member joined]]></string>
+    <string name="_bx_courses_txt_ntfs_fan_added"><![CDATA[Course <b class="bx-ntfs-content-profile">{owner_name}</b> has got a member <b class="bx-ntfs-content-entry">{subentry_sample}</b>]]></string>
+    <string name="_bx_courses_alert_action_join_invitation_notif"><![CDATA[join invitation]]></string>
+    <string name="_bx_courses_alert_action_join_request"><![CDATA[join request]]></string>
+    <string name="_bx_courses_txt_ntfs_join_invitation"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> course's representative invites <b class="bx-ntfs-content-entry">{subentry_sample}</b> to join course]]></string>
+    <string name="_bx_courses_txt_ntfs_join_request"><![CDATA[Course <b class="bx-ntfs-content-profile">{owner_name}</b> has a join request from <b class="bx-ntfs-content-entry">{subentry_sample}</b>]]></string>
+    <string name="_bx_courses_txt_ntfs_timeline_post_common"><![CDATA[New post <b class="bx-ntfs-content-entry">{subentry_sample}</b> in <b class="bx-ntfs-content-profile">{owner_name}</b> course timeline]]></string>
+
+    <string name="_bx_courses_pre_lists_cats"><![CDATA[Course categories]]></string>
+    <string name="_bx_courses_cat_General"><![CDATA[General]]></string>
+
+    <string name="_bx_courses_pre_lists_roles"><![CDATA[Course roles]]></string>
+    <string name="_bx_courses_role_regular"><![CDATA[Student]]></string>
+    <string name="_bx_courses_role_administrator"><![CDATA[Instructor]]></string>
+    <string name="_bx_courses_role_moderator"><![CDATA[Assistant]]></string>
+
+    <string name="_bx_courses_pre_lists_period_units"><![CDATA[Billing Interval]]></string>
+    <string name="_bx_courses_period_unit_day"><![CDATA[day]]></string>
+    <string name="_bx_courses_period_unit_week"><![CDATA[week]]></string>
+    <string name="_bx_courses_period_unit_month"><![CDATA[month]]></string>
+    <string name="_bx_courses_period_unit_year"><![CDATA[year]]></string>
+
+    <string name="_bx_courses_email_join_request"><![CDATA[Course join request]]></string>
+    <string name="_bx_courses_email_join_request_subject"><![CDATA[Request to join your course]]></string>
+    <string name="_bx_courses_email_join_request_body"><![CDATA[{email_header}
+<p>New request to join <a href="{EntryUrl}">{EntryTitle}</a> course from <a href="{NewMemberUrl}">{NewMemberDisplayName}</a></p>
+{email_footer}]]></string>
+
+    <string name="_bx_courses_email_join_reject"><![CDATA[Course join request was rejected]]></string>
+    <string name="_bx_courses_email_join_reject_subject"><![CDATA[Request to join a course was rejected]]></string>
+    <string name="_bx_courses_email_join_reject_body"><![CDATA[{email_header}
+<p>Your request to join <a href="{EntryUrl}">{EntryTitle}</a> course was rejected by course admin.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_courses_email_join_confirm"><![CDATA[Course join request was confirmed]]></string>
+    <string name="_bx_courses_email_join_confirm_subject"><![CDATA[Your request to join a course was confirmed]]></string>
+    <string name="_bx_courses_email_join_confirm_body"><![CDATA[{email_header}
+<p>Your request to join <a href="{EntryUrl}">{EntryTitle}</a> course was confirmed by the course admin.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_courses_email_fan_remove"><![CDATA[Profile was removed from course fans]]></string>
+    <string name="_bx_courses_email_fan_remove_subject"><![CDATA[Your profile was removed from the course]]></string>
+    <string name="_bx_courses_email_fan_remove_body"><![CDATA[{email_header}
+<p>Your profile was removed from <a href="{EntryUrl}">{EntryTitle}</a> course by the course admin.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_courses_email_fan_become_admin"><![CDATA[Course instructor status was granted]]></string>
+    <string name="_bx_courses_email_fan_become_admin_subject"><![CDATA[You are a course instructor now]]></string>
+    <string name="_bx_courses_email_fan_become_admin_body"><![CDATA[{email_header}
+<p>You are an instructor of <a href="{EntryUrl}">{EntryTitle}</a> course now.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_courses_email_admin_become_fan"><![CDATA[Course instructor status was revoked]]></string>
+    <string name="_bx_courses_email_admin_become_fan_subject"><![CDATA[Your course instructor status was revoked]]></string>
+    <string name="_bx_courses_email_admin_become_fan_body"><![CDATA[{email_header}
+<p>Your instructor status was revoked from <a href="{EntryUrl}">{EntryTitle}</a> course.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_courses_email_invitation"><![CDATA[Invitation to course]]></string>
+    <string name="_bx_courses_email_invitation_subject"><![CDATA[Invitation to "{EntryTitle}" course]]></string>
+    <string name="_bx_courses_email_invitation_body"><![CDATA[{email_header}
+<p><a href="{InviterUrl}">{InviterDisplayName}</a> invited you to <a href="{EntryUrl}">{EntryTitle} course</a>.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_courses_email_set_role"><![CDATA[Course member status was changed]]></string>
+    <string name="_bx_courses_email_set_role_subject"><![CDATA[Your course status was changed]]></string>
+    <string name="_bx_courses_email_set_role_body"><![CDATA[{email_header}
+<p>Your status in <a href="{EntryUrl}">{EntryTitle}</a> course was changed to <b>{Role}</b>.</p>
+{email_footer}]]></string>
+</resources>

+ 744 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/install/sql/enable.sql

@@ -0,0 +1,744 @@
+
+-- SETTINGS
+
+SET @iTypeOrder = (SELECT MAX(`order`) FROM `sys_options_types` WHERE `group` = 'modules');
+INSERT INTO `sys_options_types`(`group`, `name`, `caption`, `icon`, `order`) VALUES 
+('modules', 'bx_courses', '_bx_courses', 'bx_courses@modules/boonex/courses/|std-icon.svg', IF(ISNULL(@iTypeOrder), 1, @iTypeOrder + 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order`)
+VALUES (@iTypeId, 'bx_courses', '_bx_courses', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+('bx_courses_enable_auto_approve', 'on', @iCategId, '_bx_courses_option_enable_auto_approve', 'checkbox', '', '', '', 0),
+('bx_courses_enable_content', '', @iCategId, '_bx_courses_option_enable_content', 'checkbox', '', '', '', 5),
+('bx_courses_content_level_max', '3', @iCategId, '_bx_courses_option_content_level_max', 'select', '1,2,3', '', '', 6),
+('bx_courses_content_modules_st', '', @iCategId, '_bx_courses_option_content_modules_step', 'list', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:30:"get_options_content_modules_st";}', '', '', 7),
+('bx_courses_content_modules_at', '', @iCategId, '_bx_courses_option_content_modules_attachment', 'list', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:30:"get_options_content_modules_at";}', '', '', 8),
+('bx_courses_num_connections_quick', '6', @iCategId, '_bx_courses_option_num_connections_quick', 'digit', '', '', '', 10),
+('bx_courses_per_page_browse', '24', @iCategId, '_bx_courses_option_per_page_browse', 'digit', '', '', '', 11),
+('bx_courses_num_rss', '10', @iCategId, '_bx_courses_option_num_rss', 'digit', '', '', '', 12),
+('bx_courses_per_page_browse_showcase', '32', @iCategId, '_sys_option_per_page_browse_showcase', 'digit', '', '', '', 15),
+('bx_courses_per_page_browse_recommended', '10', @iCategId, '_sys_option_per_page_browse_recommended', 'digit', '', '', '', 16),
+('bx_courses_per_page_for_favorites_lists', '5', @iCategId, '_bx_courses_option_per_page_for_favorites_lists', 'digit', '', '', '', 17),
+('bx_courses_searchable_fields', 'name,desc', @iCategId, '_bx_courses_option_searchable_fields', 'list', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:21:"get_searchable_fields";}', '', '', 30),
+('bx_courses_members_mode', '', @iCategId, '_bx_courses_option_members_mode', 'select', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:24:"get_options_members_mode";}', '', '', 40), 
+('bx_courses_internal_notifications', '', @iCategId, '_bx_courses_option_internal_notifications', 'checkbox', '', '', '', 50);
+
+-- PAGES
+
+-- PAGE: create profile
+
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_create_profile', 'create-course-profile', '_bx_courses_page_title_sys_create_profile', '_bx_courses_page_title_create_profile', 'bx_courses', 5, 2147483647, 1, 'page.php?i=create-course-profile', '', '', '', 0, 1, 0, '', '');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_create_profile', 1, 'bx_courses', '_bx_courses_page_block_title_create_profile', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:13:"entity_create";}', 0, 1, 1);
+
+-- PAGE: view profile
+
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_view_profile', 'view-course-profile', '_bx_courses_page_title_sys_view_profile', '_bx_courses_page_title_view_profile', 'bx_courses', 12, 2147483647, 1, 'page.php?i=view-course-profile', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
+('bx_courses_view_profile', 1, 'bx_courses', '', '_bx_courses_page_block_title_entry_social_sharing', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:21:"entity_social_sharing";}', 0, 0, 0, 0),
+('bx_courses_view_profile', 1, 'bx_courses', '', '_bx_courses_page_block_title_entry_all_actions', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:18:"entity_all_actions";}', 0, 0, 0, 0),
+('bx_courses_view_profile', 2, 'bx_courses', '', '_bx_courses_page_block_title_profile_description', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:17:"entity_text_block";}', 0, 0, 1, 0),
+('bx_courses_view_profile', 2, 'bx_courses', '', '_bx_courses_page_block_title_profile_structure_l1', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:25:"entity_structure_l1_block";}', 0, 0, 1, 1),
+('bx_courses_view_profile', 2, 'bx_courses', '', '_bx_courses_page_block_title_profile_structure_l2', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:25:"entity_structure_l2_block";}', 0, 0, 1, 2),
+('bx_courses_view_profile', 3, 'bx_courses', '', '_bx_courses_page_block_title_profile_location', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"entity_location";}', 0, 0, 0, 0),
+('bx_courses_view_profile', 3, 'bx_courses', '', '_bx_courses_page_block_title_profile_info', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:11:"entity_info";}', 0, 0, 1, 0),
+('bx_courses_view_profile', 3, 'bx_courses', '', '_bx_courses_page_block_title_admins', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:6:"admins";}', 0, 0, 1, 1),
+('bx_courses_view_profile', 3, 'bx_courses', '', '_bx_courses_page_block_title_fans', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:19:"fans_without_admins";}', 0, 0, 1, 2),
+('bx_courses_view_profile', 3, 'bx_courses', '', '_bx_courses_page_block_title_profile_location', 3, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:13:"locations_map";s:6:"params";a:2:{i:0;s:10:"bx_courses";i:1;s:12:"{content_id}";}s:5:"class";s:20:"TemplServiceMetatags";}', 0, 0, 1, 3),
+('bx_courses_view_profile', 4, 'bx_courses', '', '_bx_courses_page_block_title_profile_comments', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"entity_comments";}', 0, 0, 0, 0),
+('bx_courses_view_profile', 2, 'bx_courses', '', '_bx_courses_page_block_title_entry_reports', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:10:\"bx_courses\";s:6:\"method\";s:14:\"entity_reports\";}', 0, 0, 1, 6);
+
+-- PAGE: view profile node 
+
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_view_profile_node', 'view-course-profile-node', '_bx_courses_page_title_sys_view_profile_node', '_bx_courses_page_title_view_profile_node', 'bx_courses', 5, 2147483647, 1, 'page.php?i=view-course-profile-node', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
+('bx_courses_view_profile_node', 1, 'bx_courses', '_bx_courses_page_block_title_system_entry_node', '_bx_courses_page_block_title_entry_node', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:17:"entity_node_block";}', 0, 0, 1, 0);
+
+-- PAGE: view closed profile 
+
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_view_profile_closed', 'view-course-profile-closed', '_bx_courses_page_title_sys_view_profile_closed', '_bx_courses_page_title_view_profile', 'bx_courses', 10, 2147483647, 1, 'page.php?i=view-course-profile', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_courses_view_profile_closed', 2, 'bx_courses', '', '_bx_courses_page_block_title_profile_info', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:11:"entity_info";}', 0, 0, 1, 0),
+('bx_courses_view_profile_closed', 3, 'bx_courses', '', '_bx_courses_page_block_title_fans', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:4:"fans";}', 0, 0, 1, 0);
+
+-- PAGE: edit profile
+
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_edit_profile', 'edit-course-profile', '_bx_courses_page_title_sys_edit_profile', '_bx_courses_page_title_edit_profile', 'bx_courses', 5, 2147483647, 1, 'page.php?i=edit-course-profile', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_edit_profile', 1, 'bx_courses', '_bx_courses_page_block_title_edit_profile', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:11:"entity_edit";}', 0, 0, 0);
+
+-- PAGE: invite members
+
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_invite', 'invite-to-course', '_bx_courses_page_title_sys_invite_to_course', '_bx_courses_page_title_invite_to_course', 'bx_courses', 5, 2147483647, 1, 'page.php?i=invite-to-course', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_invite', 1, 'bx_courses', '_bx_courses_page_block_title_invite_to_course', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:13:"entity_invite";}', 0, 0, 0);
+
+-- PAGE: delete profile
+
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_delete_profile', 'delete-course-profile', '_bx_courses_page_title_sys_delete_profile', '_bx_courses_page_title_delete_profile', 'bx_courses', 5, 2147483647, 1, 'page.php?i=delete-course-profile', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_delete_profile', 1, 'bx_courses', '_bx_courses_page_block_title_delete_profile', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:13:"entity_delete";}', 0, 0, 0);
+
+-- PAGE: join profile
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_join_profile', 'join-course-profile', '_bx_courses_page_title_sys_join_profile', '_bx_courses_page_title_join_profile', 'bx_courses', 5, 2147483647, 1, 'page.php?i=join-course-profile', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_join_profile', 1, 'bx_courses', '_bx_courses_page_block_title_join_profile', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:11:"entity_join";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 0);
+
+-- PAGE: profile info
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_profile_info', 'course-profile-info', '_bx_courses_page_title_sys_profile_info', '_bx_courses_page_title_profile_info', 'bx_courses', 5, 2147483647, 1, 'page.php?i=course-profile-info', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_courses_profile_info', 1, 'bx_courses', '_bx_courses_page_block_title_system_profile_info', '_bx_courses_page_block_title_profile_info_link', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:16:"entity_info_full";}', 0, 0, 1, 1),
+('bx_courses_profile_info', 1, 'bx_courses', '', '_bx_courses_page_block_title_profile_description', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:17:"entity_text_block";}', 0, 0, 1, 2);
+
+-- PAGE: manage profile pricing
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_profile_pricing', 'edit-course-pricing', '_bx_courses_page_title_sys_profile_pricing', '_bx_courses_page_title_profile_pricing', 'bx_courses', 5, 2147483647, 1, 'page.php?i=edit-course-pricing', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_courses_profile_pricing', 1, 'bx_courses', '_bx_courses_page_block_title_system_profile_pricing', '_bx_courses_page_block_title_profile_pricing_link', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:14:"entity_pricing";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1);
+
+-- PAGE: manage profile content
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_profile_content', 'edit-course-content', '_bx_courses_page_title_sys_profile_content', '_bx_courses_page_title_profile_content', 'bx_courses', 5, 2147483647, 1, 'page.php?i=edit-course-content', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_courses_profile_content', 1, 'bx_courses', '_bx_courses_page_block_title_system_profile_structure', '_bx_courses_page_block_title_profile_structure_link', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:30:"entity_content_structure_block";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1),
+('bx_courses_profile_content', 1, 'bx_courses', '_bx_courses_page_block_title_system_profile_data', '_bx_courses_page_block_title_profile_data_link', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:25:"entity_content_data_block";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 2);
+
+-- PAGE: course fans
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_fans', 'course-fans', '_bx_courses_page_title_sys_entry_fans', '_bx_courses_page_title_entry_fans', 'bx_courses', 5, 2147483647, 1, 'page.php?i=course-fans', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_courses_fans', 1, 'bx_courses', '_bx_courses_page_block_title_system_fans', '_bx_courses_page_block_title_fans_link', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:10:"fans_table";}', 0, 0, 1, 1),
+('bx_courses_fans', 1, 'bx_courses', '_bx_courses_page_block_title_system_invites', '_bx_courses_page_block_title_fans_invites', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:13:"invites_table";}', 0, 0, 1, 1);
+
+-- PAGE: view entry comments
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_profile_comments', '_bx_courses_page_title_sys_profile_comments', '_bx_courses_page_title_profile_comments', 'bx_courses', 5, 2147483647, 1, 'course-profile-comments', '', '', '', '', 0, 1, 0, 'BxCoursesPageEntry', 'modules/boonex/courses/classes/BxCoursesPageEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_courses_profile_comments', 1, 'bx_courses', '_bx_courses_page_block_title_profile_comments', '_bx_courses_page_block_title_profile_comments_link', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"entity_comments";}', 0, 0, 0, 1);
+
+-- PAGE: module home
+
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_home', '_bx_courses_page_title_sys_recent', '_bx_courses_page_title_recent', 'bx_courses', 5, 2147483647, 1, 'courses-home', 'page.php?i=courses-home', '', '', '', 0, 1, 0, 'BxCoursesPageBrowse', 'modules/boonex/courses/classes/BxCoursesPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_courses_home', 1, 'bx_courses', '_bx_courses_page_block_title_featured_profiles', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"browse_featured";s:6:"params";a:1:{i:0;s:7:"gallery";}}', 0, 1, 0),
+('bx_courses_home', 1, 'bx_courses', '_bx_courses_page_block_title_latest_profiles', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"browse_recent_profiles";s:6:"params";a:1:{i:0;b:1;}}', 0, 1, 1);
+
+-- PAGE: top profiles
+
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_top', '_bx_courses_page_title_sys_top', '_bx_courses_page_title_top', 'bx_courses', 5, 2147483647, 1, 'courses-top', 'page.php?i=courses-top', '', '', '', 0, 1, 0, 'BxCoursesPageBrowse', 'modules/boonex/courses/classes/BxCoursesPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_top', 1, 'bx_courses', '_bx_courses_page_block_title_top_profiles', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:19:"browse_top_profiles";s:6:"params";a:1:{i:0;b:1;}}', 0, 1, 0);
+
+-- PAGE: search for entries
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_search', '_bx_courses_page_title_sys_entries_search', '_bx_courses_page_title_entries_search', 'bx_courses', 5, 2147483647, 1, 'courses-search', 'page.php?i=courses-search', '', '', '', 0, 1, 0, 'BxCoursesPageBrowse', 'modules/boonex/courses/classes/BxCoursesPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_courses_search', 1, 'bx_courses', '_bx_courses_page_block_title_search_form', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:8:"get_form";s:6:"params";a:1:{i:0;a:1:{s:6:"object";s:10:"bx_courses";}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 1, 1),
+('bx_courses_search', 1, 'bx_courses', '_bx_courses_page_block_title_search_results', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:11:"get_results";s:6:"params";a:1:{i:0;a:2:{s:6:"object";s:10:"bx_courses";s:10:"show_empty";b:1;}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 1, 2),
+('bx_courses_search', 1, 'bx_courses', '_bx_courses_page_block_title_search_form_cmts', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:8:"get_form";s:6:"params";a:1:{i:0;a:1:{s:6:"object";s:15:"bx_courses_cmts";}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 0, 3),
+('bx_courses_search', 1, 'bx_courses', '_bx_courses_page_block_title_search_results_cmts', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:11:"get_results";s:6:"params";a:1:{i:0;a:2:{s:6:"object";s:15:"bx_courses_cmts";s:10:"show_empty";b:1;}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 0, 4);
+
+-- PAGE: joined profiles
+
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_joined_courses', '_bx_courses_page_title_sys_joined', '_bx_courses_page_title_joined', 'bx_courses', 5, 2147483647, 1, 'courses-joined', 'page.php?i=courses-joined', '', '', '', 0, 1, 0, 'BxCoursesPageBrowse', 'modules/boonex/courses/classes/BxCoursesPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_joined_courses', 1, 'bx_courses', '_bx_courses_page_block_title_joined_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:21:"browse_joined_entries";s:6:"params";a:2:{i:0;s:12:"{profile_id}";i:1;b:1;}}', 0, 1, 0);
+
+-- PAGE: module manage own
+
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_manage', '_bx_courses_page_title_sys_manage', '_bx_courses_page_title_manage', 'bx_courses', 5, 2147483647, 1, 'courses-manage', 'page.php?i=courses-manage', '', '', '', 0, 1, 0, 'BxCoursesPageBrowse', 'modules/boonex/courses/classes/BxCoursesPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_manage', 1, 'bx_courses', '_bx_courses_page_block_title_system_manage', '_bx_courses_page_block_title_manage', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:12:"manage_tools";}}', 0, 1, 0);
+
+-- PAGE: module manage all
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_administration', '_bx_courses_page_title_sys_manage_administration', '_bx_courses_page_title_manage', 'bx_courses', 5, 192, 1, 'courses-administration', 'page.php?i=courses-administration', '', '', '', 0, 1, 0, 'BxCoursesPageBrowse', 'modules/boonex/courses/classes/BxCoursesPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_administration', 1, 'bx_courses', '_bx_courses_page_block_title_system_manage_administration', '_bx_courses_page_block_title_manage', 11, 192, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:12:"manage_tools";s:6:"params";a:1:{i:0;s:14:"administration";}}', 0, 1, 0);
+
+-- PAGE: user's courses
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_joined', 'joined-courses', '_bx_courses_page_title_sys_joined_entries', '_bx_courses_page_title_joined_entries', 'bx_courses', 5, 2147483647, 1, '', '', '', '', 0, 1, 0, 'BxCoursesPageJoinedEntries', 'modules/boonex/courses/classes/BxCoursesPageJoinedEntries.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
+('bx_courses_joined', 1, 'bx_courses', '_bx_courses_page_block_title_sys_entries_actions', '_bx_courses_page_block_title_entries_actions', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:18:"my_entries_actions";}', 0, 0, 1, 1),
+('bx_courses_joined', 1, 'bx_courses', '_bx_courses_page_block_title_sys_entries_of_author', '_bx_courses_page_block_title_entries_of_author', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"browse_created_entries";}', 0, 0, 0, 2),
+('bx_courses_joined', 1, 'bx_courses', '_bx_courses_page_block_title_sys_favorites_of_author', '_bx_courses_page_block_title_favorites_of_author', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"browse_favorite";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 1, 1, 3),
+('bx_courses_joined', 1, 'bx_courses', '_bx_courses_page_block_title_sys_joined_entries_summary', '_bx_courses_page_block_title_joined_entries_summary', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"joined_entries_summary";}', 0, 0, 1, 4),
+('bx_courses_joined', 1, 'bx_courses', '_bx_courses_page_block_title_sys_joined_entries', '_bx_courses_page_block_title_joined_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:21:"browse_joined_entries";s:6:"params";a:2:{i:0;i:0;i:1;b:1;}}', 0, 0, 1, 5);
+
+-- PAGE: favorites by list
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_favorites', '_bx_courses_page_title_sys_entries_favorites', '_bx_courses_page_title_entries_favorites', 'bx_courses', 12, 2147483647, 1, 'courses-favorites', 'page.php?i=courses-favorites', '', '', '', 0, 1, 0, 'BxCoursesPageListEntry', 'modules/boonex/courses/classes/BxCoursesPageListEntry.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_courses_favorites', 2, 'bx_courses', '_bx_courses_page_block_title_sys_favorites_entries', '_bx_courses_page_block_title_favorites_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"browse_favorite";s:6:"params";a:2:{s:10:"profile_id";s:12:"{profile_id}";s:6:"params";a:3:{s:9:"unit_view";s:7:"gallery";s:13:"empty_message";b:0;s:13:"ajax_paginate";b:0;}}}', 0, 1, 1),
+('bx_courses_favorites', 3, 'bx_courses', '', '_bx_courses_page_block_title_favorites_entries_info', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:19:"favorites_list_info";}', 0, 0, 0),
+('bx_courses_favorites', 3, 'bx_courses', '', '_bx_courses_page_block_title_favorites_entries_actions', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"favorites_list_actions";}', 0, 0, 1);
+
+-- PAGE: add block to homepage
+SET @iBlockOrder = (SELECT `order` FROM `sys_pages_blocks` WHERE `object` = 'sys_home' AND `cell_id` = 1 ORDER BY `order` DESC LIMIT 1);
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
+('sys_home', 1, 'bx_courses', '', '_bx_courses_page_block_title_latest_profiles', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"browse_recent_profiles";}', 1, 0, 0, IFNULL(@iBlockOrder, 0) + 1),
+('sys_home', 1, 'bx_courses', '_bx_courses_page_block_title_sys_recommended_entries_view_showcase', '_bx_courses_page_block_title_recommended_entries_view_showcase', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:18:"browse_recommended";}', 1, 0, 0, IFNULL(@iBlockOrder, 0) + 2);
+
+-- PAGE: service blocks
+SET @iBlockOrder = (SELECT `order` FROM `sys_pages_blocks` WHERE `object` = '' AND `cell_id` = 0 ORDER BY `order` DESC LIMIT 1);
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
+('', 0, 'bx_courses', '', '_bx_courses_page_block_title_categories', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:15:"categories_list";s:6:"params";a:2:{i:0;s:15:"bx_courses_cats";i:1;a:2:{s:10:"show_empty";b:1;s:21:"show_empty_categories";b:1;}}s:5:"class";s:20:"TemplServiceCategory";}', 1, 1, 1, IFNULL(@iBlockOrder, 0) + 1),
+('', 0, 'bx_courses', '_bx_courses_page_block_title_sys_featured_entries_view_showcase', '_bx_courses_page_block_title_featured_entries_view_showcase', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"browse_featured";s:6:"params";a:3:{s:9:"unit_view";s:8:"showcase";s:13:"empty_message";b:0;s:13:"ajax_paginate";b:0;}}', 0, 1, 1, IFNULL(@iBlockOrder, 0) + 2),
+('', 0, 'bx_courses', '_bx_courses_page_block_title_sys_recommended_entries_view_showcase', '_bx_courses_page_block_title_recommended_entries_view_showcase', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:18:"browse_recommended";s:6:"params";a:3:{s:9:"unit_view";s:8:"showcase";s:13:"empty_message";b:0;s:13:"ajax_paginate";b:0;}}', 0, 1, 1, IFNULL(@iBlockOrder, 0) + 3),
+('', 0, 'bx_courses', '_bx_courses_page_block_title_sys_labels_tree', '_bx_courses_page_block_title_labels_tree', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"get_lables_tree";}', 0, 1, 1, IFNULL(@iBlockOrder, 0) + 4),
+('', 0, 'bx_courses', '_bx_courses_page_block_title_sys_browse_by_label', '_bx_courses_page_block_title_browse_by_label', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:15:"browse_by_label";}', 0, 1, 1, IFNULL(@iBlockOrder, 0) + 5),
+('', 0, 'bx_courses', '_bx_courses_page_block_title_sys_labels_breadcrumbs', '_bx_courses_page_block_title_labels_breadcrumbs', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"get_lables_breadcrumbs";}', 0, 1, 1, IFNULL(@iBlockOrder, 0) + 6),
+('', 0, 'bx_courses', '_bx_courses_page_block_title_sys_cover_block', '_bx_courses_page_block_title_cover_block', 3, 2147483647, 'service', 'a:2:{s:6:\"module\";s:10:\"bx_courses\";s:6:\"method\";s:12:\"entity_cover\";}', 0, 1, 1, IFNULL(@iBlockOrder, 0) + 7);
+
+-- MENU
+
+-- MENU: add to site menu
+
+SET @iSiteMenuOrder = (SELECT `order` FROM `sys_menu_items` WHERE `set_name` = 'sys_site' AND `active` = 1 AND `order` < 9999 ORDER BY `order` DESC LIMIT 1);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('sys_site', 'bx_courses', 'courses-home', '_bx_courses_menu_item_title_system_entries_home', '_bx_courses_menu_item_title_entries_home', 'page.php?i=courses-home', '', '', 'book-reader col-blue3-dark', 'bx_courses_submenu', 2147483647, 1, 1, IFNULL(@iSiteMenuOrder, 0) + 1);
+
+-- MENU: add to homepage menu
+
+SET @iHomepageMenuOrder = (SELECT `order` FROM `sys_menu_items` WHERE `set_name` = 'sys_homepage' AND `active` = 1 ORDER BY `order` DESC LIMIT 1);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('sys_homepage', 'bx_courses', 'courses-home', '_bx_courses_menu_item_title_system_entries_home', '_bx_courses_menu_item_title_entries_home', 'page.php?i=courses-home', '', '', 'book-reader col-blue3-dark', 'bx_courses_submenu', 2147483647, 1, 1, IFNULL(@iHomepageMenuOrder, 0) + 1);
+
+-- MENU: add to "add content" menu
+
+SET @iAddMenuOrder = (SELECT `order` FROM `sys_menu_items` WHERE `set_name` = 'sys_add_content_links' AND `active` = 1 ORDER BY `order` DESC LIMIT 1);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('sys_add_content_links', 'bx_courses', 'create-course-profile', '_bx_courses_menu_item_title_system_create_profile', '_bx_courses_menu_item_title_create_profile', 'page.php?i=create-course-profile', '', '', 'book-reader col-blue3-dark', '', 2147483647, 1, 1, IFNULL(@iAddMenuOrder, 0) + 1);
+
+-- MENU: view actions
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_view_actions', '_bx_courses_menu_title_view_profile_actions', 'bx_courses_view_actions', 'bx_courses', 9, 0, 1, 'BxCoursesMenuViewActions', 'modules/boonex/courses/classes/BxCoursesMenuViewActions.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_view_actions', 'bx_courses', '_bx_courses_menu_set_title_view_profile_actions', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `submenu_popup`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `editable`, `order`) VALUES 
+('bx_courses_view_actions', 'bx_courses', 'join-course-profile', '_bx_courses_menu_item_title_system_pay_and_join', '_bx_courses_menu_item_title_pay_and_join', 'page.php?i=join-course-profile&profile_id={profile_id}', '', '', 'sign-in-alt', '', 0, 2147483647, 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"is_paid_join_avaliable";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 1, 0, 0, 5),
+('bx_courses_view_actions', 'bx_courses', 'profile-fan-add', '_bx_courses_menu_item_title_system_become_fan', '{title_add_fan}', 'javascript:void(0)', 'bx_conn_action(this, \'bx_courses_fans\', \'add\', \'{profile_id}\')', '', 'sign-in-alt', '', 0, 2147483647, 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:22:"is_free_join_avaliable";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 1, 0, 0, 5),
+('bx_courses_view_actions', 'bx_courses', 'profile-fan-remove', '_bx_courses_menu_item_title_system_leave_entry', '{title_remove_fan}', 'javascript:void(0)', 'bx_conn_action(this, \'bx_courses_fans\', \'remove\', \'{profile_id}\')', '', 'sign-out-alt', '', 0, 2147483647, '', 1, 0, 1, 6),
+('bx_courses_view_actions', 'bx_courses', 'profile-subscribe-add', '_bx_courses_menu_item_title_system_subscribe', '_bx_courses_menu_item_title_subscribe', 'javascript:void(0)', 'bx_conn_action(this, \'sys_profiles_subscriptions\', \'add\', \'{profile_id}\')', '', 'check', '', 0, 2147483647, '', 1, 0, 1, 20),
+('bx_courses_view_actions', 'bx_courses', 'profile-subscribe-remove', '_bx_courses_menu_item_title_system_unsubscribe', '_bx_courses_menu_item_title_unsubscribe', 'javascript:void(0)', 'bx_conn_action(this, \'sys_profiles_subscriptions\', \'remove\', \'{profile_id}\')', '', 'check', '', 0, 2147483647, '', 1, 0, 1, 21),
+('bx_courses_view_actions', 'bx_courses', 'profile-set-badges', '_sys_menu_item_title_system_set_badges', '_sys_menu_item_title_set_badges', 'javascript:void(0)', 'bx_menu_popup(''sys_set_badges'', window, {}, {module: ''bx_courses'', content_id: {content_id}});', '', 'check-circle', '', 0, 192, 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:19:"is_badges_avaliable";}', 1, 0, 1, 30),
+('bx_courses_view_actions', 'bx_courses', 'profile-actions-more', '_bx_courses_menu_item_title_system_more_actions', '_bx_courses_menu_item_title_more_actions', 'javascript:void(0)', 'bx_menu_popup(''bx_courses_view_actions_more'', this, {}, {profile_id:{profile_id}});', '', 'cog', 'bx_courses_view_actions_more', 1, 2147483647, '', 1, 0, 1, 9999);
+
+-- MENU: view actions more
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_view_actions_more', '_bx_courses_menu_title_view_profile_actions_more', 'bx_courses_view_actions_more', 'bx_courses', 6, 0, 1, 'BxCoursesMenuViewActions', 'modules/boonex/courses/classes/BxCoursesMenuViewActions.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_view_actions_more', 'bx_courses', '_bx_courses_menu_set_title_view_profile_actions_more', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `order`) VALUES 
+('bx_courses_view_actions_more', 'bx_courses', 'notes', '_sys_menu_item_title_system_va_notes', '_sys_menu_item_title_va_notes', 'javascript:void(0)', 'javascript:bx_get_notes(this,  ''{module_uri}'', {content_id});', '', 'exclamation-triangle', '', 192, '', 1, 0, 20),
+('bx_courses_view_actions_more', 'bx_courses', 'audit', '_sys_menu_item_title_system_va_audit', '_sys_menu_item_title_va_audit', 'page.php?i=dashboard-audit&module=bx_courses&content_id={content_id}', '', '', 'history', '', 192, '', 1, 0, 30),
+('bx_courses_view_actions_more', 'bx_courses', 'hide-course-profile', '_bx_courses_menu_item_title_system_hide_profile', '_bx_courses_menu_item_title_hide_profile', 'javascript:void(0);', 'javascript:{js_object}.perform(this, ''hide-course-profile'', {content_id});', '', 'stop-circle', '', 2147483647, '', 1, 0, 35),
+('bx_courses_view_actions_more', 'bx_courses', 'unhide-course-profile', '_bx_courses_menu_item_title_system_unhide_profile', '_bx_courses_menu_item_title_unhide_profile', 'javascript:void(0);', 'javascript:{js_object}.perform(this, ''unhide-course-profile'', {content_id});', '', 'play-circle', '', 2147483647, '', 1, 0, 36),
+('bx_courses_view_actions_more', 'bx_courses', 'edit-course-profile', '_bx_courses_menu_item_title_system_edit_profile', '_bx_courses_menu_item_title_edit_profile', 'page.php?i=edit-course-profile&id={content_id}', '', '', 'pencil-alt', '', 2147483647, '', 1, 0, 40),
+('bx_courses_view_actions_more', 'bx_courses', 'edit-course-pricing', '_bx_courses_menu_item_title_system_edit_pricing', '_bx_courses_menu_item_title_edit_pricing', 'page.php?i=edit-course-pricing&profile_id={profile_id}', '', '', 'money-check-alt', '', 2147483647, 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:20:"is_pricing_avaliable";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 1, 0, 41),
+('bx_courses_view_actions_more', 'bx_courses', 'edit-course-content', '_bx_courses_menu_item_title_system_edit_content', '_bx_courses_menu_item_title_edit_content', 'page.php?i=edit-course-content&profile_id={profile_id}', '', '', 'folder-tree', '', 2147483647, 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:20:"is_content_avaliable";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 1, 0, 42),
+('bx_courses_view_actions_more', 'bx_courses', 'invite-to-course', '_bx_courses_menu_item_title_system_invite', '_bx_courses_menu_item_title_invite', 'page.php?i=invite-to-course&id={content_id}', '', '', 'user-friends', '', 2147483647, '', 1, 0, 45),
+('bx_courses_view_actions_more', 'bx_courses', 'delete-course-profile', '_bx_courses_menu_item_title_system_delete_profile', '_bx_courses_menu_item_title_delete_profile', 'page.php?i=delete-course-profile&id={content_id}', '', '', 'remove', '', 2147483647, '', 1, 0, 50),
+('bx_courses_view_actions_more', 'bx_courses', 'approve-course-profile', '_sys_menu_item_title_system_va_approve', '_sys_menu_item_title_va_approve', 'javascript:void(0)', 'javascript:bx_approve(this, ''{module_uri}'', {content_id});', '', 'check', '', 2147483647, '', 1, 0, 60);
+
+-- MENU: all actions menu for view entry 
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `persistent`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_view_actions_all', '_sys_menu_title_view_actions', 'bx_courses_view_actions_all', 'bx_courses', 15, 1, 0, 1, 'BxCoursesMenuViewActionsAll', 'modules/boonex/courses/classes/BxCoursesMenuViewActionsAll.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_view_actions_all', 'bx_courses', '_sys_menu_set_title_view_actions', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `submenu_popup`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `order`) VALUES 
+('bx_courses_view_actions_all', 'bx_courses', 'join-course-profile', '_bx_courses_menu_item_title_system_pay_and_join', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 5),
+('bx_courses_view_actions_all', 'bx_courses', 'profile-fan-add', '_bx_courses_menu_item_title_system_become_fan', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 10),
+('bx_courses_view_actions_all', 'bx_courses', 'profile-fan-remove', '_bx_courses_menu_item_title_system_leave_entry', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 20),
+('bx_courses_view_actions_all', 'bx_courses', 'profile-subscribe-add', '_bx_courses_menu_item_title_system_subscribe', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 30),
+('bx_courses_view_actions_all', 'bx_courses', 'profile-subscribe-remove', '_bx_courses_menu_item_title_system_unsubscribe', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 40),
+('bx_courses_view_actions_all', 'bx_courses', 'profile-set-badges', '_sys_menu_item_title_system_set_badges', '', '', '', '', '', '', '', 0, 192, 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:19:"is_badges_avaliable";}', 1, 0, 50),
+('bx_courses_view_actions_all', 'bx_courses', 'comment', '_sys_menu_item_title_system_va_comment', '', '', '', '', '', '', '', 0, 2147483647, '', 0, 0, 200),
+('bx_courses_view_actions_all', 'bx_courses', 'view', '_sys_menu_item_title_system_va_view', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 210),
+('bx_courses_view_actions_all', 'bx_courses', 'vote', '_sys_menu_item_title_system_va_vote', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 220),
+('bx_courses_view_actions_all', 'bx_courses', 'score', '_sys_menu_item_title_system_va_score', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 230),
+('bx_courses_view_actions_all', 'bx_courses', 'favorite', '_sys_menu_item_title_system_va_favorite', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 240),
+('bx_courses_view_actions_all', 'bx_courses', 'feature', '_sys_menu_item_title_system_va_feature', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 250),
+('bx_courses_view_actions_all', 'bx_courses', 'repost', '_sys_menu_item_title_system_va_repost', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 260),
+('bx_courses_view_actions_all', 'bx_courses', 'report', '_sys_menu_item_title_system_va_report', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 270),
+('bx_courses_view_actions_all', 'bx_courses', 'social-sharing', '_sys_menu_item_title_system_social_sharing', '_sys_menu_item_title_social_sharing', 'javascript:void(0)', 'oBxDolPage.share(this, \'{url_encoded}\')', '', 'share', '', '', 0, 2147483647, '', 1, 0, 300),
+('bx_courses_view_actions_all', 'bx_courses', 'more-auto', '_sys_menu_item_title_system_va_more_auto', '_sys_menu_item_title_va_more_auto', 'javascript:void(0)', '', '', 'ellipsis-v', '', '', 0, 2147483647, '', 1, 0, 9999);
+
+-- MENU: meta (counters) menu for view entry
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_view_meta', '_bx_courses_menu_title_view_profile_meta', 'bx_courses_view_meta', 'bx_courses', 15, 0, 1, 'BxCoursesMenuViewMeta', 'modules/boonex/courses/classes/BxCoursesMenuViewMeta.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_view_meta', 'bx_courses', '_bx_courses_menu_set_title_view_profile_meta', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `submenu_popup`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `order`) VALUES 
+('bx_courses_view_meta', 'bx_courses', 'members', '_sys_menu_item_title_system_sm_members', '_sys_menu_item_title_sm_members', '', '', '', '', '', 0, 2147483647, '', 1, 0, 10),
+('bx_courses_view_meta', 'bx_courses', 'subscribers', '_sys_menu_item_title_system_sm_subscribers', '_sys_menu_item_title_sm_subscribers', '', '', '', '', '', 0, 2147483647, '', 1, 0, 20),
+('bx_courses_view_meta', 'bx_courses', 'views', '_sys_menu_item_title_system_sm_views', '_sys_menu_item_title_sm_views', '', '', '', '', '', 0, 2147483647, '', 1, 0, 30),
+('bx_courses_view_meta', 'bx_courses', 'votes', '_sys_menu_item_title_system_sm_votes', '_sys_menu_item_title_sm_votes', '', '', '', '', '', 0, 2147483647, '', 1, 0, 40),
+('bx_courses_view_meta', 'bx_courses', 'comments', '_sys_menu_item_title_system_sm_comments', '_sys_menu_item_title_sm_comments', '', '', '', '', '', 0, 2147483647, '', 1, 0, 50);
+
+-- MENU: add content menu for entry
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `persistent`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_content_add', '_bx_courses_menu_title_content_add', 'bx_courses_content_add', 'bx_courses', 6, 1, 0, 1, 'BxCoursesMenuContentAdd', 'modules/boonex/courses/classes/BxCoursesMenuContentAdd.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_content_add', 'bx_courses', '_bx_courses_menu_set_title_content_add', 0);
+
+-- MENU: actions menu for my entries
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_my', '_bx_courses_menu_title_entries_my', 'bx_courses_my', 'bx_courses', 9, 0, 1, 'BxCoursesMenu', 'modules/boonex/courses/classes/BxCoursesMenu.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_my', 'bx_courses', '_bx_courses_menu_set_title_entries_my', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('bx_courses_my', 'bx_courses', 'create-course-profile', '_bx_courses_menu_item_title_system_create_profile', '_bx_courses_menu_item_title_create_profile', 'page.php?i=create-course-profile', '', '', 'plus', '', 2147483647, 1, 0, 0);
+
+-- MENU: module sub-menu
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_submenu', '_bx_courses_menu_title_submenu', 'bx_courses_submenu', 'bx_courses', 8, 0, 1, '', '');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_submenu', 'bx_courses', '_bx_courses_menu_set_title_submenu', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('bx_courses_submenu', 'bx_courses', 'courses-home', '_bx_courses_menu_item_title_system_entries_recent', '_bx_courses_menu_item_title_entries_recent', 'page.php?i=courses-home', '', '', '', '', 2147483647, 1, 1, 1),
+('bx_courses_submenu', 'bx_courses', 'courses-top', '_bx_courses_menu_item_title_system_entries_top', '_bx_courses_menu_item_title_entries_top', 'page.php?i=courses-top', '', '', '', '', 2147483647, 1, 1, 2),
+('bx_courses_submenu', 'bx_courses', 'courses-search', '_bx_courses_menu_item_title_system_entries_search', '_bx_courses_menu_item_title_entries_search', 'page.php?i=courses-search', '', '', '', '', 2147483647, 1, 1, 3),
+('bx_courses_submenu', 'bx_courses', 'courses-joined', '_bx_courses_menu_item_title_system_entries_joined', '_bx_courses_menu_item_title_entries_joined', 'page.php?i=courses-joined', '', '', '', '', 2147483647, 1, 1, 4),
+('bx_courses_submenu', 'bx_courses', 'courses-manage', '_bx_courses_menu_item_title_system_entries_manage', '_bx_courses_menu_item_title_entries_manage', 'page.php?i=courses-manage', '', '', '', '', 2147483646, 1, 1, 5);
+
+-- MENU: view submenu
+
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_view_submenu', '_bx_courses_menu_title_view_profile_submenu', 'bx_courses_view_submenu', 'bx_courses', 18, 0, 1, 'BxCoursesMenuView', 'modules/boonex/courses/classes/BxCoursesMenuView.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_view_submenu', 'bx_courses', '_bx_courses_menu_set_title_view_profile_submenu', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `submenu_popup`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('bx_courses_view_submenu', 'bx_courses', 'view-course-profile', '_bx_courses_menu_item_title_system_view_profile_view', '_bx_courses_menu_item_title_view_profile_view', 'page.php?i=view-course-profile&id={content_id}', '', '', 'book-reader col-blue3-dark', '', '', 0, 2147483647, 1, 0, 1),
+('bx_courses_view_submenu', 'bx_courses', 'course-profile-info', '_bx_courses_menu_item_title_system_view_profile_info', '_bx_courses_menu_item_title_view_profile_info', 'page.php?i=course-profile-info&id={content_id}', '', '', 'info-circle col-gray', '', '', 0, 2147483647, 1, 0, 2),
+('bx_courses_view_submenu', 'bx_courses', 'course-profile-comments', '_bx_courses_menu_item_title_system_view_profile_comments', '_bx_courses_menu_item_title_view_profile_comments', 'page.php?i=course-profile-comments&id={content_id}', '', '', '', '', '', 0, 2147483647, 0, 0, 3),
+('bx_courses_view_submenu', 'bx_courses', 'course-fans', '_bx_courses_menu_item_title_system_view_fans', '_bx_courses_menu_item_title_view_fans', 'page.php?i=course-fans&profile_id={profile_id}', '', '', 'users col-blue3', '', '', 0, 2147483647, 1, 0, 4),
+('bx_courses_view_submenu', 'bx_courses', 'more-auto', '_bx_courses_menu_item_title_system_view_profile_more_auto', '_bx_courses_menu_item_title_view_profile_more_auto', 'javascript:void(0)', '', '', 'ellipsis-v', '', '', 0, 2147483647, 1, 0, 9999);
+
+-- MENU: custom menu for snippet meta info
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_snippet_meta', '_sys_menu_title_snippet_meta', 'bx_courses_snippet_meta', 'bx_courses', 15, 0, 1, 'BxCoursesMenuSnippetMeta', 'modules/boonex/courses/classes/BxCoursesMenuSnippetMeta.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_snippet_meta', 'bx_courses', '_sys_menu_set_title_snippet_meta', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `editable`, `order`) VALUES
+('bx_courses_snippet_meta', 'bx_courses', 'privacy', '_bx_courses_menu_item_title_system_sm_privacy', '_bx_courses_menu_item_title_sm_privacy', '', '', '', '', '', 2147483647, '', 1, 0, 1, 0),
+('bx_courses_snippet_meta', 'bx_courses', 'date', '_sys_menu_item_title_system_sm_date', '_sys_menu_item_title_sm_date', '', '', '', '', '', 2147483647, '', 0, 0, 1, 1),
+('bx_courses_snippet_meta', 'bx_courses', 'category', '_sys_menu_item_title_system_sm_category', '_sys_menu_item_title_sm_category', '', '', '', '', '', 2147483647, '', 0, 0, 1, 5),
+('bx_courses_snippet_meta', 'bx_courses', 'tags', '_sys_menu_item_title_system_sm_tags', '_sys_menu_item_title_sm_tags', '', '', '', '', '', 2147483647, '', 0, 0, 1, 10),
+('bx_courses_snippet_meta', 'bx_courses', 'views', '_sys_menu_item_title_system_sm_views', '_sys_menu_item_title_sm_views', '', '', '', '', '', 2147483647, '', 0, 0, 1, 15),
+('bx_courses_snippet_meta', 'bx_courses', 'comments', '_sys_menu_item_title_system_sm_comments', '_sys_menu_item_title_sm_comments', '', '', '', '', '', 2147483647, '', 0, 0, 1, 20),
+('bx_courses_snippet_meta', 'bx_courses', 'members', '_bx_courses_menu_item_title_system_sm_members', '_bx_courses_menu_item_title_sm_members', '', '', '', '', '', 2147483647, '', 1, 0, 1, 25),
+('bx_courses_snippet_meta', 'bx_courses', 'subscribers', '_sys_menu_item_title_system_sm_subscribers', '_sys_menu_item_title_sm_subscribers', '', '', '', '', '', 2147483647, '', 0, 0, 1, 30),
+('bx_courses_snippet_meta', 'bx_courses', 'reports', '_bx_courses_menu_item_title_system_sm_reports', '', '', '', '', '', '', 2147483647, '', 0, 0, 1, 33),
+('bx_courses_snippet_meta', 'bx_courses', 'nl', '_sys_menu_item_title_system_sm_nl', '_sys_menu_item_title_sm_nl', '', '', '', '', '', 2147483647, '', 1, 0, 1, 35),
+('bx_courses_snippet_meta', 'bx_courses', 'join-paid', '_sys_menu_item_title_system_sm_join_paid', '_sys_menu_item_title_sm_join_paid', '', '', '', '', '', 2147483647, 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:33:"is_paid_join_avaliable_by_content";s:6:"params";a:1:{i:0;s:12:"{content_id}";}}', 1, 0, 1, 40),
+('bx_courses_snippet_meta', 'bx_courses', 'join', '_bx_courses_menu_item_title_system_sm_join', '_bx_courses_menu_item_title_sm_join', '', '', '', '', '', 2147483647, 'a:3:{s:6:"module";s:10:"bx_courses";s:6:"method";s:33:"is_free_join_avaliable_by_content";s:6:"params";a:1:{i:0;s:12:"{content_id}";}}', 1, 0, 1, 45),
+('bx_courses_snippet_meta', 'bx_courses', 'leave', '_sys_menu_item_title_system_sm_leave', '_sys_menu_item_title_sm_leave', '', '', '', '', '', 2147483647, '', 0, 0, 1, 50),
+('bx_courses_snippet_meta', 'bx_courses', 'subscribe', '_sys_menu_item_title_system_sm_subscribe', '_sys_menu_item_title_sm_subscribe', '', '', '', '', '', 2147483647, '', 0, 0, 1, 55),
+('bx_courses_snippet_meta', 'bx_courses', 'unsubscribe', '_sys_menu_item_title_system_sm_unsubscribe', '_sys_menu_item_title_sm_unsubscribe', '', '', '', '', '', 2147483647, '', 0, 0, 1, 60),
+('bx_courses_snippet_meta', 'bx_courses', 'pass', '_bx_courses_menu_item_title_system_sm_pass', '', '', '', '', '', '', 2147483647, '', 0, 0, 1, 65);
+
+-- MENU: profile stats
+SET @iNotifMenuOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name` = 'sys_profile_stats' AND `active` = 1 LIMIT 1);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES
+('sys_profile_stats', 'bx_courses', 'profile-stats-my-courses', '_bx_courses_menu_item_title_system_manage_my_entries', '_bx_courses_menu_item_title_manage_my_entries', 'page.php?i=joined-courses&profile_id={member_id}', '', '_self', 'book-reader col-blue3-dark', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:41:"get_menu_addon_manage_tools_profile_stats";}', '', 2147483646, 1, 0, @iNotifMenuOrder + 2);
+
+-- MENU: profile followings
+SET @iFollowingsMenuOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name`='sys_profile_followings' LIMIT 1);
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `collapsed`, `active`, `copyable`, `order`) VALUES 
+('sys_profile_followings', 'bx_courses', 'courses', '_bx_courses_menu_item_title_system_followings', '_bx_courses_menu_item_title_followings', 'javascript:void(0)', '', '_self', 'book-reader col-blue3-dark', '', '', 2147483647, 0, 1, 0, @iFollowingsMenuOrder + 1);
+
+-- MENU: manage tools submenu
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_courses_menu_manage_tools', '_bx_courses_menu_title_manage_tools', 'bx_courses_menu_manage_tools', 'bx_courses', 6, 0, 1, 'BxCoursesMenuManageTools', 'modules/boonex/courses/classes/BxCoursesMenuManageTools.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_courses_menu_manage_tools', 'bx_courses', '_bx_courses_menu_set_title_manage_tools', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('bx_courses_menu_manage_tools', 'bx_courses', 'clear-reports', '_bx_courses_menu_item_title_system_clear_reports', '_bx_courses_menu_item_title_clear_reports', 'javascript:void(0)', 'javascript:{js_object}.onClickClearReports({content_id});', '_self', 'eraser', '', 2147483647, 1, 0, 1),
+('bx_courses_menu_manage_tools', 'bx_courses', 'delete', '_bx_courses_menu_item_title_system_delete', '_bx_courses_menu_item_title_delete', 'javascript:void(0)', 'javascript:{js_object}.onClickDelete({content_id});', '_self', 'far trash-alt', '', 2147483647, 1, 0, 2),
+('bx_courses_menu_manage_tools', 'bx_courses', 'delete-with-content', '_bx_courses_menu_item_title_system_delete_with_content', '_bx_courses_menu_item_title_delete_with_content', 'javascript:void(0)', 'javascript:{js_object}.onClickDeleteWithContent({content_id});', '_self', 'far trash-alt', '', 2147483647, 0, 0, 3);
+
+-- MENU: dashboard manage tools
+SET @iManageMenuOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name`='sys_account_dashboard_manage_tools' LIMIT 1);
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('sys_account_dashboard_manage_tools', 'bx_courses', 'courses-administration', '_bx_courses_menu_item_title_system_admt_entries', '_bx_courses_menu_item_title_admt_entries', 'page.php?i=courses-administration', '', '_self', 'users', 'a:2:{s:6:"module";s:10:"bx_courses";s:6:"method";s:27:"get_menu_addon_manage_tools";}', '', 192, 1, 0, @iManageMenuOrder + 1);
+
+-- MENU: add menu item to profiles modules (trigger* menu sets are processed separately upon modules enable/disable)
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('trigger_profile_view_submenu', 'bx_courses', 'joined-courses', '_bx_courses_menu_item_title_system_view_joined_entries', '_bx_courses_menu_item_title_view_joined_entries', 'page.php?i=joined-courses&profile_id={profile_id}', '', '', 'book-reader col-blue3-dark', '', 2147483647, 1, 0, 0);
+
+-- ACL
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_courses', 'create entry', NULL, '_bx_courses_acl_action_create_profile', '', 1, 1);
+SET @iIdActionProfileCreate = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_courses', 'use paid join', NULL, '_bx_courses_acl_action_use_paid_join', '', 1, 1);
+SET @iIdActionUsePaidJoin = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_courses', 'delete entry', NULL, '_bx_courses_acl_action_delete_profile', '', 1, 1);
+SET @iIdActionProfileDelete = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_courses', 'view entry', NULL, '_bx_courses_acl_action_view_profile', '', 1, 0);
+SET @iIdActionProfileView = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_courses', 'edit any entry', NULL, '_bx_courses_acl_action_edit_any_profile', '', 1, 3);
+SET @iIdActionProfileEditAny = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_courses', 'delete any entry', NULL, '_bx_courses_acl_action_delete_any_profile', '', 1, 3);
+SET @iIdActionProfileDeleteAny = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_courses', 'delete invites', NULL, '_bx_courses_acl_action_delete_invites', '', 1, 3);
+SET @iIdActionProfileDeleteInvites = LAST_INSERT_ID();
+
+SET @iUnauthenticated = 1;
+SET @iAccount = 2;
+SET @iStandard = 3;
+SET @iUnconfirmed = 4;
+SET @iPending = 5;
+SET @iSuspended = 6;
+SET @iModerator = 7;
+SET @iAdministrator = 8;
+SET @iPremium = 9;
+
+INSERT INTO `sys_acl_matrix` (`IDLevel`, `IDAction`) VALUES
+
+-- profile create
+(@iAccount, @iIdActionProfileCreate),
+(@iStandard, @iIdActionProfileCreate),
+(@iUnconfirmed, @iIdActionProfileCreate),
+(@iPending, @iIdActionProfileCreate),
+(@iModerator, @iIdActionProfileCreate),
+(@iAdministrator, @iIdActionProfileCreate),
+(@iPremium, @iIdActionProfileCreate),
+
+-- use paid join
+(@iStandard, @iIdActionUsePaidJoin),
+(@iModerator, @iIdActionUsePaidJoin),
+(@iAdministrator, @iIdActionUsePaidJoin),
+(@iPremium, @iIdActionUsePaidJoin),
+
+-- profile delete
+(@iAccount, @iIdActionProfileDelete),
+(@iStandard, @iIdActionProfileDelete),
+(@iUnconfirmed, @iIdActionProfileDelete),
+(@iPending, @iIdActionProfileDelete),
+(@iModerator, @iIdActionProfileDelete),
+(@iAdministrator, @iIdActionProfileDelete),
+(@iPremium, @iIdActionProfileDelete),
+
+-- profile view
+(@iUnauthenticated, @iIdActionProfileView),
+(@iAccount, @iIdActionProfileView),
+(@iStandard, @iIdActionProfileView),
+(@iUnconfirmed, @iIdActionProfileView),
+(@iPending, @iIdActionProfileView),
+(@iModerator, @iIdActionProfileView),
+(@iAdministrator, @iIdActionProfileView),
+(@iPremium, @iIdActionProfileView),
+
+-- any profile edit
+(@iModerator, @iIdActionProfileEditAny),
+(@iAdministrator, @iIdActionProfileEditAny),
+
+-- any profile delete
+(@iAdministrator, @iIdActionProfileDeleteAny),
+
+-- any invites edit
+(@iModerator, @iIdActionProfileDeleteInvites),
+(@iAdministrator, @iIdActionProfileDeleteInvites);
+
+
+-- METATAGS
+INSERT INTO `sys_objects_metatags` (`object`, `module`, `table_keywords`, `table_locations`, `table_mentions`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses', 'bx_courses', 'bx_courses_meta_keywords', 'bx_courses_meta_locations', 'bx_courses_meta_mentions', '', '');
+
+-- CATEGORY
+INSERT INTO `sys_objects_category` (`object`, `module`, `search_object`, `form_object`, `list_name`, `table`, `field`, `join`, `where`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_cats', 'bx_courses',  'bx_courses', 'bx_course', 'bx_courses_cats', 'bx_courses_data', 'cat', 'INNER JOIN `sys_profiles` ON (`sys_profiles`.`content_id` = `bx_courses_data`.`id` AND `sys_profiles`.`type` = ''bx_courses'')', 'AND `sys_profiles`.`status` = ''active''', '', '');
+
+-- SEARCH
+SET @iSearchOrder = (SELECT IFNULL(MAX(`Order`), 0) FROM `sys_objects_search`);
+INSERT INTO `sys_objects_search` (`ObjectName`, `Title`, `Order`, `ClassName`, `ClassPath`) VALUES
+('bx_courses', '_bx_courses', @iSearchOrder + 1, 'BxCoursesSearchResult', 'modules/boonex/courses/classes/BxCoursesSearchResult.php');
+
+-- CONNECTIONS
+INSERT INTO `sys_objects_connection` (`object`, `table`, `profile_initiator`, `profile_content`, `type`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_fans', 'bx_courses_fans', 1, 1, 'mutual', 'BxCoursesConnectionFans', 'modules/boonex/courses/classes/BxCoursesConnectionFans.php');
+
+-- STATS
+SET @iMaxOrderStats = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_statistics`);
+INSERT INTO `sys_statistics` (`module`, `name`, `title`, `link`, `icon`, `query`, `order`) VALUES 
+('bx_courses', 'bx_courses', '_bx_courses', 'page.php?i=courses-home', 'book-reader col-blue3-dark', 'SELECT COUNT(*) FROM `bx_courses_data` AS `td` LEFT JOIN `sys_profiles` AS `tp` ON `td`.`id` = `tp`.`content_id` AND `tp`.`type`=''bx_courses'' WHERE 1 AND `tp`.`status`=''active''', @iMaxOrderStats + 1);
+
+-- CHARTS
+SET @iMaxOrderCharts = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_objects_chart`);
+INSERT INTO `sys_objects_chart` (`object`, `title`, `table`, `field_date_ts`, `field_date_dt`, `field_status`, `query`, `active`, `order`, `class_name`, `class_file`) VALUES
+('bx_courses_growth', '_bx_courses_chart_growth', 'bx_courses_data', 'added', '', '', 'SELECT {field_date_formatted} AS `period`, COUNT(*) AS {object} FROM {table} LEFT JOIN `sys_profiles` AS `tp` ON {table}.`id` = `tp`.`content_id` AND `tp`.`type`=''bx_courses'' WHERE 1 AND `tp`.`status`=''active'' {where_inteval} GROUP BY `period` ORDER BY {table}.{field_date} ASC', 1, @iMaxOrderCharts + 1, 'BxDolChartGrowth', ''),
+('bx_courses_growth_speed', '_bx_courses_chart_growth_speed', 'bx_courses_data', 'added', '', '', 'SELECT {field_date_formatted} AS `period`, COUNT(*) AS {object} FROM {table} LEFT JOIN `sys_profiles` AS `tp` ON {table}.`id` = `tp`.`content_id` AND `tp`.`type`=''bx_courses'' WHERE 1 AND `tp`.`status`=''active'' {where_inteval} GROUP BY `period` ORDER BY {table}.{field_date} ASC', 1, @iMaxOrderCharts + 2, 'BxDolChartGrowthSpeed', '');
+
+-- GRID: connections
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `responsive`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_fans', 'Sql', 'SELECT `p`.`id`, `c`.`added`, `c`.`mutual` FROM `sys_profiles` AS `p` INNER JOIN `sys_accounts` AS `a` ON (`a`.`id` = `p`.`account_id`) {join_connections}', 'sys_profiles', 'id', 'c`.`added', '', '', 10, NULL, 'start', '', 'name,email', '', 'auto', '', '', 2147483647, 0, 'BxCoursesGridConnections', 'modules/boonex/courses/classes/BxCoursesGridConnections.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `params`, `order`) VALUES
+('bx_courses_fans', 'name', '_sys_name', '50%', '', 10),
+('bx_courses_fans', 'role', '_bx_courses_txt_role', '10%', '', 15),
+('bx_courses_fans', 'role_added', '_bx_courses_txt_role_added', '10%', '', 16),
+('bx_courses_fans', 'role_expired', '_bx_courses_txt_role_expired', '10%', '', 17),
+('bx_courses_fans', 'actions', '', '20%', '', 20);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `confirm`, `order`) VALUES
+('bx_courses_fans', 'single', 'accept', '_sys_accept', '', 0, 10),
+('bx_courses_fans', 'single', 'set_role', '_bx_courses_txt_set_role', '', 0, 20),
+('bx_courses_fans', 'single', 'set_role_submit', '', '', 0, 21),
+('bx_courses_fans', 'single', 'delete', '', 'remove', 1, 40);
+
+
+-- GRID: invites
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `responsive`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_invites', 'Sql', 'SELECT `bx_courses_invites`.`id`, `bx_courses_invites`.`invited_profile_id`, `bx_courses_invites`.`added`, `bx_courses_invites`.`author_profile_id` FROM `sys_profiles` AS `p` INNER JOIN `sys_accounts` AS `a` ON (`a`.`id` = `p`.`account_id`) INNER JOIN `bx_courses_invites` ON `bx_courses_invites`.`invited_profile_id` = `p`.`id` ', 'bx_courses_invites', 'id', 'bx_courses_invites`.`added', '', '', 10, NULL, 'start', '', 'name,email', '', 'auto', '', '', 2147483647, 0, 'BxCoursesGridInvites', 'modules/boonex/courses/classes/BxCoursesGridInvites.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `params`, `order`) VALUES
+('bx_courses_invites', 'name', '_sys_name', '33%', '', 10),
+('bx_courses_invites', 'added', '_sys_added', '33%', '', 20),
+('bx_courses_invites', 'actions', '', '34%', '', 30);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `confirm`, `order`) VALUES
+('bx_courses_invites', 'single', 'delete', '', 'remove', 1, 10);
+
+
+-- GRIDS: administration
+
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_administration', 'Sql', 'SELECT `td`.*, `td`.`name` AS `name`, `ta`.`email` AS `account`, `td`.`added` AS `added_ts`, `tp`.`status` AS `status_profile` FROM `bx_courses_data` AS `td` LEFT JOIN `sys_profiles` AS `tp` ON `td`.`id`=`tp`.`content_id` AND `tp`.`type`=''bx_courses'' LEFT JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` WHERE 1 ', 'bx_courses_data', 'id', 'added', 'status_admin', '', 20, NULL, 'start', '', 'name', '', 'like', 'reports', '', 192, 'BxCoursesGridAdministration', 'modules/boonex/courses/classes/BxCoursesGridAdministration.php'),
+('bx_courses_common', 'Sql', 'SELECT `td`.*, `td`.`name` AS `name`, `ta`.`email` AS `account`, `td`.`added` AS `added_ts`, `tp`.`status` AS `status_profile` FROM `bx_courses_data` AS `td` LEFT JOIN `sys_profiles` AS `tp` ON `td`.`id`=`tp`.`content_id` AND `tp`.`type`=''bx_courses'' LEFT JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` WHERE 1 ', 'bx_courses_data', 'id', 'added', 'status', '', 20, NULL, 'start', '', 'name', '', 'like', '', '', 2147483647, 'BxCoursesGridCommon', 'modules/boonex/courses/classes/BxCoursesGridCommon.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_courses_administration', 'checkbox', '_sys_select', '2%', 0, '', '', 1),
+('bx_courses_administration', 'switcher', '_bx_courses_grid_column_title_adm_active', '8%', 0, '', '', 2),
+('bx_courses_administration', 'reports', '_sys_txt_reports_title', '5%', 0, '', '', 3),
+('bx_courses_administration', 'name', '_bx_courses_grid_column_title_adm_name', '25%', 0, '', '', 4),
+('bx_courses_administration', 'added_ts', '_bx_courses_grid_column_title_adm_added', '20%', 1, '25', '', 5),
+('bx_courses_administration', 'account', '_bx_courses_grid_column_title_adm_account', '20%', 0, '25', '', 6),
+('bx_courses_administration', 'actions', '', '20%', 0, '', '', 7),
+
+('bx_courses_common', 'checkbox', '_sys_select', '2%', 0, '', '', 1),
+('bx_courses_common', 'switcher', '_bx_courses_grid_column_title_adm_active', '8%', 0, '', '', 2),
+('bx_courses_common', 'name', '_bx_courses_grid_column_title_adm_name', '40%', 0, '', '', 3),
+('bx_courses_common', 'added_ts', '_bx_courses_grid_column_title_adm_added', '30%', 1, '25', '', 4),
+('bx_courses_common', 'actions', '', '20%', 0, '', '', 5);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `active`, `order`) VALUES
+('bx_courses_administration', 'bulk', 'clear_reports', '_bx_courses_grid_action_title_adm_clear_reports', '', 0, 1, 1, 1),
+('bx_courses_administration', 'bulk', 'delete', '_bx_courses_grid_action_title_adm_delete', '', 0, 1, 1, 2),
+('bx_courses_administration', 'bulk', 'delete_with_content', '_bx_courses_grid_action_title_adm_delete_with_content', '', 0, 1, 0, 3),
+('bx_courses_administration', 'single', 'settings', '_bx_courses_grid_action_title_adm_more_actions', 'cog', 1, 0, 1, 2),
+('bx_courses_administration', 'single', 'audit_content', '_bx_courses_grid_action_title_adm_audit_content', 'search', 1, 0, 1, 3),
+('bx_courses_administration', 'single', 'audit_context', '_bx_courses_grid_action_title_adm_audit_context', 'search-location', 1, 0, 1, 4),
+
+('bx_courses_common', 'bulk', 'delete', '_bx_courses_grid_action_title_adm_delete', '', 0, 1, 1, 1),
+('bx_courses_common', 'bulk', 'delete_with_content', '_bx_courses_grid_action_title_adm_delete_with_content', '', 0, 1, 0, 2),
+('bx_courses_common', 'single', 'settings', '_bx_courses_grid_action_title_adm_more_actions', 'cog', 1, 0, 1, 1);
+
+-- GRIDS: Pricing
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_prices_manage', 'Sql', 'SELECT * FROM `bx_courses_prices` WHERE 1 ', 'bx_courses_prices', 'id', 'order', '', '', 100, NULL, 'start', '', 'period,period_unit,price', '', 'like', '', '', 2147483647, 'BxCoursesGridPricesManage', 'modules/boonex/courses/classes/BxCoursesGridPricesManage.php'),
+('bx_courses_prices_view', 'Sql', 'SELECT * FROM `bx_courses_prices` WHERE 1 ', 'bx_courses_prices', 'id', 'order', '', '', 100, NULL, 'start', '', 'period,period_unit,price', '', 'like', '', '', 2147483647, 'BxCoursesGridPricesView', 'modules/boonex/courses/classes/BxCoursesGridPricesView.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_courses_prices_manage', 'checkbox', '_sys_select', '1%', 0, '', '', 1),
+('bx_courses_prices_manage', 'order', '', '1%', 0, '', '', 2),
+('bx_courses_prices_manage', 'name', '_bx_courses_grid_column_title_name', '38%', 0, 32, '', 3),
+('bx_courses_prices_manage', 'price', '_bx_courses_grid_column_title_price', '20%', 0, 16, '', 4),
+('bx_courses_prices_manage', 'period', '_bx_courses_grid_column_title_period', '20%', 0, 16, '', 5),
+('bx_courses_prices_manage', 'actions', '', '20%', 0, '', '', 6),
+
+('bx_courses_prices_view', 'role_id', '_bx_courses_grid_column_title_role_id', '40%', 0, 32, '', 1),
+('bx_courses_prices_view', 'price', '_bx_courses_grid_column_title_price', '20%', 0, 16, '', 2),
+('bx_courses_prices_view', 'period', '_bx_courses_grid_column_title_period', '20%', 0, 16, '', 3),
+('bx_courses_prices_view', 'actions', '', '20%', 0, '', '', 4);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_courses_prices_manage', 'independent', 'add', '_bx_courses_grid_action_title_add', '', 0, 0, 1),
+('bx_courses_prices_manage', 'single', 'edit', '_bx_courses_grid_action_title_edit', 'pencil-alt', 1, 0, 1),
+('bx_courses_prices_manage', 'single', 'delete', '_bx_courses_grid_action_title_delete', 'remove', 1, 1, 2),
+('bx_courses_prices_manage', 'bulk', 'delete', '_bx_courses_grid_action_title_delete', '', 0, 1, 1),
+
+('bx_courses_prices_view', 'single', 'buy', '_bx_courses_grid_action_title_buy', 'cart-plus', 0, 0, 1),
+('bx_courses_prices_view', 'single', 'subscribe', '_bx_courses_grid_action_title_subscribe', 'credit-card', 0, 0, 2),
+('bx_courses_prices_view', 'single', 'choose', '_bx_courses_grid_action_title_choose', 'far check-square', 0, 0, 3);
+
+
+-- GRIDS: content structure
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_cnt_structure_manage', 'Sql', 'SELECT `tcn`.*, `tcs`.`parent_id`, `tcs`.`level`, `tcs`.`cn_l2`, `tcs`.`cn_l3` FROM `bx_courses_content_nodes` AS `tcn` LEFT JOIN `bx_courses_content_structure` AS `tcs` ON `tcn`.`id`=`tcs`.`node_id` WHERE 1 ', 'bx_courses_content_nodes', 'id', 'order', 'status', '', 20, NULL, 'start', '', 'title', '', 'like', '', '', 2147483647, 'BxCoursesGridCntStructureManage', 'modules/boonex/courses/classes/BxCoursesGridCntStructureManage.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_courses_cnt_structure_manage', 'order', '', '4%', 0, 0, '', 1),
+('bx_courses_cnt_structure_manage', 'checkbox', '_sys_select', '4%', 0, 0, '', 2),
+('bx_courses_cnt_structure_manage', 'switcher', '_bx_courses_grid_column_title_cnt_active', '7%', 0, 0, '', 3),
+('bx_courses_cnt_structure_manage', 'title', '_bx_courses_grid_column_title_cnt_title', '35%', 0, 0, '', 4),
+('bx_courses_cnt_structure_manage', 'cn_l2', '_bx_courses_grid_column_title_cnt_cn_l2', '10%', 0, 0, '', 5),
+('bx_courses_cnt_structure_manage', 'cn_l3', '_bx_courses_grid_column_title_cnt_cn_l3', '10%', 0, 0, '', 6),
+('bx_courses_cnt_structure_manage', 'counters', '_bx_courses_grid_column_title_cnt_counters', '20%', 0, 0, '', 7),
+('bx_courses_cnt_structure_manage', 'added', '_bx_courses_grid_column_title_cnt_added', '10%', 0, 0, '', 8),
+('bx_courses_cnt_structure_manage', 'actions', '', '20%', 0, 0, '', 9);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_courses_cnt_structure_manage', 'independent', 'back', '_bx_courses_grid_action_title_cnt_back', 'arrow-left', 0, 0, 1),
+('bx_courses_cnt_structure_manage', 'independent', 'add', '_bx_courses_grid_action_title_cnt_add', '', 0, 0, 2),
+('bx_courses_cnt_structure_manage', 'single', 'edit', '_bx_courses_grid_action_title_cnt_edit', 'pencil-alt', 1, 0, 1),
+('bx_courses_cnt_structure_manage', 'single', 'delete', '_bx_courses_grid_action_title_cnt_delete', 'remove', 1, 1, 2),
+('bx_courses_cnt_structure_manage', 'bulk', 'delete', '_bx_courses_grid_action_title_cnt_delete', '', 0, 1, 1);
+
+-- GRIDS: content data
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_cnt_data_manage', 'Sql', 'SELECT * FROM `bx_courses_content_data` WHERE 1 ', 'bx_courses_content_data', 'id', 'order', '', '', 20, NULL, 'start', '', '', '', 'like', '', '', 2147483647, 'BxCoursesGridCntDataManage', 'modules/boonex/courses/classes/BxCoursesGridCntDataManage.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_courses_cnt_data_manage', 'order', '', '4%', 0, 0, '', 1),
+('bx_courses_cnt_data_manage', 'checkbox', '_sys_select', '4%', 0, 0, '', 2),
+('bx_courses_cnt_data_manage', 'content_id', '_bx_courses_grid_column_title_cnt_content_id', '42%', 0, 0, '', 3),
+('bx_courses_cnt_data_manage', 'content_type', '_bx_courses_grid_column_title_cnt_content_type', '10%', 0, 0, '', 4),
+('bx_courses_cnt_data_manage', 'usage', '_bx_courses_grid_column_title_cnt_usage', '10%', 0, 0, '', 5),
+('bx_courses_cnt_data_manage', 'added', '_bx_courses_grid_column_title_cnt_added', '10%', 0, 0, '', 6),
+('bx_courses_cnt_data_manage', 'actions', '', '20%', 0, '', '', 7);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_courses_cnt_data_manage', 'independent', 'back', '_bx_courses_grid_action_title_cnt_back', 'arrow-left', 0, 0, 1),
+('bx_courses_cnt_data_manage', 'independent', 'add_st', '_bx_courses_grid_action_title_cnt_add_step', '', 0, 0, 2),
+('bx_courses_cnt_data_manage', 'independent', 'add_at', '_bx_courses_grid_action_title_cnt_add_attachment', '', 0, 0, 3),
+('bx_courses_cnt_data_manage', 'single', 'edit', '_bx_courses_grid_action_title_cnt_edit', 'pencil-alt', 1, 0, 1),
+('bx_courses_cnt_data_manage', 'single', 'delete', '_bx_courses_grid_action_title_cnt_delete', 'remove', 1, 1, 2),
+('bx_courses_cnt_data_manage', 'bulk', 'delete', '_bx_courses_grid_action_title_cnt_delete', '', 0, 1, 1);
+
+
+-- ALERTS
+
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES 
+('bx_courses', 'BxCoursesAlertsResponse', 'modules/boonex/courses/classes/BxCoursesAlertsResponse.php', '');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('system', 'save_setting', @iHandler),
+('bx_timeline', 'post_common', @iHandler),
+('bx_courses_pics', 'file_deleted', @iHandler),
+('bx_courses_fans', 'connection_added', @iHandler),
+('bx_courses_fans', 'connection_removed', @iHandler),
+('profile', 'delete', @iHandler),
+('bx_courses', 'fan_added', @iHandler),
+('bx_courses', 'join_invitation', @iHandler),
+('bx_courses', 'join_request', @iHandler),
+('bx_courses', 'join_request_accepted', @iHandler),
+('bx_courses', 'timeline_view', @iHandler),
+('bx_courses', 'timeline_post', @iHandler),
+('bx_courses', 'timeline_delete', @iHandler),
+('bx_courses', 'timeline_comment', @iHandler),
+('bx_courses', 'timeline_vote', @iHandler),
+('bx_courses', 'timeline_score', @iHandler),
+('bx_courses', 'timeline_report', @iHandler),
+('bx_courses', 'timeline_repost', @iHandler),
+('bx_courses', 'timeline_pin', @iHandler),
+('bx_courses', 'timeline_promote', @iHandler);
+
+-- PRIVACY
+
+INSERT INTO `sys_objects_privacy` (`object`, `module`, `action`, `title`, `default_group`, `spaces`, `table`, `table_field_id`, `table_field_author`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_allow_view_to', 'bx_courses', 'view', '_bx_courses_form_profile_input_allow_view_to', '3', '', 'bx_courses_data', 'id', 'author', 'BxCoursesPrivacy', 'modules/boonex/courses/classes/BxCoursesPrivacy.php'),
+('bx_courses_allow_view_notification_to', 'bx_courses', 'view_event', '_bx_courses_form_profile_input_allow_view_notification_to', '3', '', 'bx_notifications_events', 'id', 'object_owner_id', 'BxCoursesPrivacyNotifications', 'modules/boonex/courses/classes/BxCoursesPrivacyNotifications.php'),
+('bx_courses_allow_post_to', 'bx_courses', 'post', '_bx_courses_form_profile_input_allow_post_to', 'p', '', 'bx_courses_data', 'id', 'author', 'BxCoursesPrivacyPost', 'modules/boonex/courses/classes/BxCoursesPrivacyPost.php'),
+('bx_courses_allow_view_favorite_list', 'bx_courses', 'view_favorite_list', '_bx_courses_form_profile_input_allow_view_favorite_list', '3', '', 'bx_courses_favorites_lists', 'id', 'author_id', 'BxCoursesPrivacy', 'modules/boonex/courses/classes/BxCoursesPrivacy.php');
+
+-- EMAIL TEMPLATES
+
+INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `Body`) VALUES
+('bx_courses', '_bx_courses_email_join_request', 'bx_courses_join_request', '_bx_courses_email_join_request_subject', '_bx_courses_email_join_request_body'),
+('bx_courses', '_bx_courses_email_join_reject', 'bx_courses_join_reject', '_bx_courses_email_join_reject_subject', '_bx_courses_email_join_reject_body'),
+('bx_courses', '_bx_courses_email_join_confirm', 'bx_courses_join_confirm', '_bx_courses_email_join_confirm_subject', '_bx_courses_email_join_confirm_body'),
+('bx_courses', '_bx_courses_email_fan_remove', 'bx_courses_fan_remove', '_bx_courses_email_fan_remove_subject', '_bx_courses_email_fan_remove_body'),
+('bx_courses', '_bx_courses_email_fan_become_admin', 'bx_courses_fan_become_admin', '_bx_courses_email_fan_become_admin_subject', '_bx_courses_email_fan_become_admin_body'),
+('bx_courses', '_bx_courses_email_admin_become_fan', 'bx_courses_admin_become_fan', '_bx_courses_email_admin_become_fan_subject', '_bx_courses_email_admin_become_fan_body'),
+('bx_courses', '_bx_courses_email_set_role', 'bx_courses_set_role', '_bx_courses_email_set_role_subject', '_bx_courses_email_set_role_body'),
+('bx_courses', '_bx_courses_email_invitation', 'bx_courses_invitation', '_bx_courses_email_invitation_subject', '_bx_courses_email_invitation_body');
+
+-- UPLOADERS
+
+INSERT INTO `sys_objects_uploader` (`object`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_cover_crop', 1, 'BxCoursesUploaderCoverCrop', 'modules/boonex/courses/classes/BxCoursesUploaderCoverCrop.php'),
+('bx_courses_picture_crop', 1, 'BxCoursesUploaderPictureCrop', 'modules/boonex/courses/classes/BxCoursesUploaderPictureCrop.php');
+
+-- CRON
+
+INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
+('bx_courses_pruning', '0 0 * * *', 'BxCoursesCronPruning', 'modules/boonex/courses/classes/BxCoursesCronPruning.php', '');

+ 588 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/install/sql/install.sql

@@ -0,0 +1,588 @@
+SET @sStorageEngine = (SELECT `value` FROM `sys_options` WHERE `name` = 'sys_storage_default');
+
+-- TABLE: PROFILES
+CREATE TABLE IF NOT EXISTS `bx_courses_data` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `author` int(10) unsigned NOT NULL,
+  `added` int(11) NOT NULL,
+  `changed` int(11) NOT NULL,
+  `picture` int(11) NOT NULL,
+  `cover` int(11) NOT NULL,
+  `cover_data` varchar(50) NOT NULL,
+  `name` varchar(255) NOT NULL,
+  `cat` int(11) NOT NULL,
+  `desc` text NOT NULL,
+  `labels` text NOT NULL,
+  `location` text NOT NULL,
+  `views` int(11) NOT NULL default '0',
+  `rate` float NOT NULL default '0',
+  `votes` int(11) NOT NULL default '0',
+  `score` int(11) NOT NULL default '0',
+  `sc_up` int(11) NOT NULL default '0',
+  `sc_down` int(11) NOT NULL default '0',
+  `favorites` int(11) NOT NULL default '0',
+  `comments` int(11) NOT NULL default '0',
+  `reports` int(11) NOT NULL default '0',
+  `featured` int(11) NOT NULL default '0',
+  `cf` int(11) NOT NULL default '1',
+  `join_confirmation` tinyint(4) NOT NULL DEFAULT '0',
+  `allow_view_to` varchar(16) NOT NULL DEFAULT '3',
+  `allow_post_to` varchar(16) NOT NULL DEFAULT '3',
+  `status` enum('active','awaiting','hidden') NOT NULL DEFAULT 'active',
+  `status_admin` enum('active','hidden','pending') NOT NULL DEFAULT 'active',
+  PRIMARY KEY (`id`),
+  FULLTEXT KEY `search_fields` (`name`, `desc`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_content_structure` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `entry_id` int(11) NOT NULL default '0',
+  `parent_id` int(11) NOT NULL DEFAULT '0',
+  `node_id` int(11) NOT NULL DEFAULT '0',
+  `level` tinyint(4) NOT NULL DEFAULT '0',
+  `order` int(11) NOT NULL DEFAULT '0',
+  `cn_l2` int(11) NOT NULL DEFAULT '0',
+  `cn_l3` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `node_id` (`node_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_content_nodes` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `entry_id` int(11) NOT NULL default '0',
+  `title` varchar(255) NOT NULL DEFAULT '',
+  `text` text NOT NULL,
+  `passing` tinyint(4) NOT NULL DEFAULT '0',
+  `counters` text NOT NULL,
+  `added` int(11) NOT NULL,
+  `status` enum('active','hidden') NOT NULL DEFAULT 'active',
+  PRIMARY KEY (`id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_content_nodes2users` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `node_id` int(11) NOT NULL default '0',
+  `profile_id` int(11) NOT NULL default '0',
+  `date` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `pass` (`node_id`, `profile_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_content_data` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `entry_id` int(11) NOT NULL default '0',
+  `node_id` int(11) NOT NULL DEFAULT '0',
+  `content_type` varchar(32) NOT NULL DEFAULT '',
+  `content_id` int(11) NOT NULL DEFAULT '0',
+  `usage` tinyint(4) NOT NULL DEFAULT '0',
+  `added` int(11) NOT NULL DEFAULT '0',
+  `order` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_content_data2users` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `data_id` int(11) NOT NULL default '0',
+  `profile_id` int(11) NOT NULL default '0',
+  `date` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `pass` (`data_id`, `profile_id`)
+);
+
+-- TABLE: STORAGES & TRANSCODERS
+CREATE TABLE IF NOT EXISTS `bx_courses_pics` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `profile_id` int(10) unsigned NOT NULL,
+  `remote_id` varchar(128) NOT NULL,
+  `path` varchar(255) NOT NULL,
+  `file_name` varchar(255) NOT NULL,
+  `mime_type` varchar(128) NOT NULL,
+  `ext` varchar(32) NOT NULL,
+  `size` bigint(20) NOT NULL,
+  `added` int(11) NOT NULL,
+  `modified` int(11) NOT NULL,
+  `private` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `remote_id` (`remote_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_pics_resized` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `profile_id` int(10) unsigned NOT NULL,
+  `remote_id` varchar(128) NOT NULL,
+  `path` varchar(255) NOT NULL,
+  `file_name` varchar(255) NOT NULL,
+  `mime_type` varchar(128) NOT NULL,
+  `ext` varchar(32) NOT NULL,
+  `size` bigint(20) NOT NULL,
+  `added` int(11) NOT NULL,
+  `modified` int(11) NOT NULL,
+  `private` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `remote_id` (`remote_id`)
+);
+
+-- TABLE: comments
+CREATE TABLE IF NOT EXISTS `bx_courses_cmts` (
+  `cmt_id` int(11) NOT NULL AUTO_INCREMENT,
+  `cmt_parent_id` int(11) NOT NULL DEFAULT '0',
+  `cmt_vparent_id` int(11) NOT NULL DEFAULT '0',
+  `cmt_object_id` int(11) NOT NULL DEFAULT '0',
+  `cmt_author_id` int(11) NOT NULL DEFAULT '0',
+  `cmt_level` int(11) NOT NULL DEFAULT '0',
+  `cmt_text` text NOT NULL,
+  `cmt_mood` tinyint(4) NOT NULL DEFAULT '0',
+  `cmt_rate` int(11) NOT NULL DEFAULT '0',
+  `cmt_rate_count` int(11) NOT NULL DEFAULT '0',
+  `cmt_time` int(11) unsigned NOT NULL DEFAULT '0',
+  `cmt_replies` int(11) NOT NULL DEFAULT '0',
+  `cmt_pinned` int(11) NOT NULL default '0',
+  `cmt_cf` int(11) NOT NULL default '1',
+  PRIMARY KEY (`cmt_id`),
+  KEY `cmt_object_id` (`cmt_object_id`,`cmt_parent_id`),
+  FULLTEXT KEY `search_fields` (`cmt_text`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_cmts_notes` (
+  `cmt_id` int(11) NOT NULL AUTO_INCREMENT,
+  `cmt_parent_id` int(11) NOT NULL DEFAULT '0',
+  `cmt_vparent_id` int(11) NOT NULL DEFAULT '0',
+  `cmt_object_id` int(11) NOT NULL DEFAULT '0',
+  `cmt_author_id` int(11) NOT NULL DEFAULT '0',
+  `cmt_level` int(11) NOT NULL DEFAULT '0',
+  `cmt_text` text NOT NULL,
+  `cmt_mood` tinyint(4) NOT NULL DEFAULT '0',
+  `cmt_rate` int(11) NOT NULL DEFAULT '0',
+  `cmt_rate_count` int(11) NOT NULL DEFAULT '0',
+  `cmt_time` int(11) unsigned NOT NULL DEFAULT '0',
+  `cmt_replies` int(11) NOT NULL DEFAULT '0',
+  `cmt_pinned` int(11) NOT NULL default '0',
+  PRIMARY KEY (`cmt_id`),
+  KEY `cmt_object_id` (`cmt_object_id`,`cmt_parent_id`),
+  FULLTEXT KEY `search_fields` (`cmt_text`)
+);
+
+-- TABLE: VIEWS
+CREATE TABLE IF NOT EXISTS `bx_courses_views_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `viewer_id` int(11) NOT NULL default '0',
+  `viewer_nip` int(11) unsigned NOT NULL default '0',
+  `date` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `id` (`object_id`,`viewer_id`,`viewer_nip`)
+);
+
+-- TABLE: VOTES
+CREATE TABLE IF NOT EXISTS `bx_courses_votes` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `count` int(11) NOT NULL default '0',
+  `sum` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `object_id` (`object_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_votes_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `author_id` int(11) NOT NULL default '0',
+  `author_nip` int(11) unsigned NOT NULL default '0',
+  `value` tinyint(4) NOT NULL default '0',
+  `date` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `vote` (`object_id`, `author_nip`)
+);
+
+-- TABLE: REPORTS
+CREATE TABLE IF NOT EXISTS `bx_courses_reports` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `count` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `object_id` (`object_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_reports_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `author_id` int(11) NOT NULL default '0',
+  `author_nip` int(11) unsigned NOT NULL default '0',
+  `type` varchar(32) NOT NULL default '',
+  `text` text NOT NULL default '',
+  `date` int(11) NOT NULL default '0',
+  `checked_by` int(11) NOT NULL default '0',
+  `status` tinyint(11) NOT NULL default '0',	
+  PRIMARY KEY (`id`),
+  KEY `report` (`object_id`, `author_nip`)
+);
+
+-- TABLE: metas
+CREATE TABLE IF NOT EXISTS `bx_courses_meta_keywords` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(10) unsigned NOT NULL,
+  `keyword` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `object_id` (`object_id`),
+  KEY `keyword` (`keyword`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_meta_locations` (
+  `object_id` int(10) unsigned NOT NULL,
+  `lat` double NOT NULL,
+  `lng` double NOT NULL,
+  `country` varchar(2) NOT NULL,
+  `state` varchar(255) NOT NULL,
+  `city` varchar(255) NOT NULL,
+  `zip` varchar(255) NOT NULL,
+  `street` varchar(255) NOT NULL,
+  `street_number` varchar(255) NOT NULL,
+  PRIMARY KEY (`object_id`),
+  KEY `country_state_city` (`country`,`state`(8),`city`(8))
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_meta_mentions` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(10) unsigned NOT NULL,
+  `profile_id` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `object_id` (`object_id`),
+  KEY `profile_id` (`profile_id`)
+);
+
+-- TABLE: fans
+CREATE TABLE IF NOT EXISTS `bx_courses_fans` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `initiator` int(11) NOT NULL,
+  `content` int(11) NOT NULL,
+  `mutual` tinyint(4) NOT NULL,
+  `added` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `initiator` (`initiator`,`content`),
+  KEY `content` (`content`)
+);
+
+-- TABLE: admins
+CREATE TABLE IF NOT EXISTS `bx_courses_admins` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `group_profile_id` int(10) unsigned NOT NULL,
+  `fan_id` int(10) unsigned NOT NULL,
+  `role` int(10) unsigned NOT NULL default '0',
+  `order` varchar(32) NOT NULL default '',
+  `added` int(11) unsigned NOT NULL default '0',
+  `expired` int(11) unsigned NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `admin` (`group_profile_id`,`fan_id`)
+);
+
+-- TABLE: favorites
+CREATE TABLE IF NOT EXISTS `bx_courses_favorites_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `author_id` int(11) NOT NULL default '0',
+  `list_id` int(11) NOT NULL default '0',
+  `date` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `id` (`object_id`,`author_id`)
+);
+
+CREATE TABLE `bx_courses_favorites_lists` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `title` varchar(255) NOT NULL,
+  `author_id` int(11) NOT NULL default '0',
+  `date` int(11) NOT NULL default '0',
+  `allow_view_favorite_list_to` varchar(16) NOT NULL DEFAULT '3',
+   PRIMARY KEY (`id`)
+);
+
+
+-- TABLE: scores
+CREATE TABLE IF NOT EXISTS `bx_courses_scores` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `count_up` int(11) NOT NULL default '0',
+  `count_down` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `object_id` (`object_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_scores_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `author_id` int(11) NOT NULL default '0',
+  `author_nip` int(11) unsigned NOT NULL default '0',
+  `type` varchar(8) NOT NULL default '',
+  `date` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `vote` (`object_id`, `author_nip`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_courses_invites` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `key` varchar(128) NOT NULL default '0',
+  `group_profile_id` int(11) NOT NULL default '0',
+  `author_profile_id` int(11) NOT NULL default '0',
+  `invited_profile_id` int(11) NOT NULL default '0',
+  `added` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`)
+);
+
+-- TABLE: Pricing
+CREATE TABLE IF NOT EXISTS `bx_courses_prices` (
+  `id` int(11) NOT NULL auto_increment,
+  `profile_id` int(11) NOT NULL default '0',
+  `role_id` int(11) unsigned NOT NULL default '0',
+  `name` varchar(128) NOT NULL default '',
+  `period` int(11) unsigned NOT NULL default '1',
+  `period_unit` varchar(32) NOT NULL default '',
+  `price` float unsigned NOT NULL default '1',
+  `order` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`),
+  UNIQUE KEY `type` (`profile_id`, `role_id`,`period`, `period_unit`)
+);
+
+-- STORAGES & TRANSCODERS
+INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `cache_control`, `levels`, `table_files`, `ext_mode`, `ext_allow`, `ext_deny`, `quota_size`, `current_size`, `quota_number`, `current_number`, `max_file_size`, `ts`) VALUES
+('bx_courses_pics', @sStorageEngine, '', 360, 2592000, 3, 'bx_courses_pics', 'allow-deny', '{image}', '', 0, 0, 0, 0, 0, 0),
+('bx_courses_pics_resized', @sStorageEngine, '', 360, 2592000, 3, 'bx_courses_pics_resized', 'allow-deny', '{image}', '', 0, 0, 0, 0, 0, 0);
+
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
+('bx_courses_icon', 'bx_courses_pics_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_courses_pics";}', 'no', '1', '2592000', '0'),
+('bx_courses_thumb', 'bx_courses_pics_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_courses_pics";}', 'no', '1', '2592000', '0'),
+('bx_courses_avatar', 'bx_courses_pics_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_courses_pics";}', 'no', '1', '2592000', '0'),
+('bx_courses_avatar_big', 'bx_courses_pics_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_courses_pics";}', 'no', '1', '2592000', '0'),
+('bx_courses_picture', 'bx_courses_pics_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_courses_pics";}', 'no', '1', '2592000', '0'),
+('bx_courses_cover', 'bx_courses_pics_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_courses_pics";}', 'no', '1', '2592000', '0'),
+('bx_courses_cover_thumb', 'bx_courses_pics_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_courses_pics";}', 'no', '1', '2592000', '0'),
+('bx_courses_gallery', 'bx_courses_pics_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_courses_pics";}', 'no', '1', '2592000', '0');
+
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
+('bx_courses_icon', 'Resize', 'a:3:{s:1:"w";s:2:"30";s:1:"h";s:2:"30";s:13:"square_resize";s:1:"1";}', '0'),
+('bx_courses_thumb', 'Resize', 'a:3:{s:1:"w";s:2:"50";s:1:"h";s:2:"50";s:13:"square_resize";s:1:"1";}', '0'),
+('bx_courses_avatar', 'Resize', 'a:3:{s:1:"w";s:3:"100";s:1:"h";s:3:"100";s:13:"square_resize";s:1:"1";}', '0'),
+('bx_courses_avatar_big', 'Resize', 'a:3:{s:1:"w";s:3:"200";s:1:"h";s:3:"200";s:13:"square_resize";s:1:"1";}', '0'),
+('bx_courses_picture', 'Resize', 'a:3:{s:1:"w";s:4:"1024";s:1:"h";s:4:"1024";s:13:"square_resize";s:1:"0";}', '0'),
+('bx_courses_cover', 'Resize', 'a:1:{s:1:"w";s:4:"1200";}', '0'),
+('bx_courses_cover_thumb', 'Resize', 'a:3:{s:1:"w";s:2:"48";s:1:"h";s:2:"48";s:13:"square_resize";s:1:"1";}', '0'),
+('bx_courses_gallery', 'Resize', 'a:1:{s:1:"w";s:3:"500";}', '0');
+
+-- FORMS
+INSERT INTO `sys_objects_form`(`object`, `module`, `title`, `action`, `form_attrs`, `table`, `key`, `uri`, `uri_title`, `submit_name`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_course', 'bx_courses', '_bx_courses_form_profile', '', 'a:1:{s:7:\"enctype\";s:19:\"multipart/form-data\";}', 'bx_courses_data', 'id', '', '', 'do_submit', '', 0, 1, 'BxCoursesFormEntry', 'modules/boonex/courses/classes/BxCoursesFormEntry.php');
+
+INSERT INTO `sys_form_displays`(`object`, `display_name`, `module`, `view_mode`, `title`) VALUES 
+('bx_course', 'bx_course_add', 'bx_courses', 0, '_bx_courses_form_profile_display_add'),
+('bx_course', 'bx_course_delete', 'bx_courses', 0, '_bx_courses_form_profile_display_delete'),
+('bx_course', 'bx_course_edit', 'bx_courses', 0, '_bx_courses_form_profile_display_edit'),
+('bx_course', 'bx_course_edit_cover', 'bx_courses', 0, '_bx_courses_form_profile_display_edit_cover'),
+('bx_course', 'bx_course_view', 'bx_courses', 1, '_bx_courses_form_profile_display_view'),
+('bx_course', 'bx_course_view_full', 'bx_courses', 1, '_bx_courses_form_profile_display_view_full'),
+('bx_course', 'bx_course_invite', 'bx_courses', 0, '_bx_courses_form_profile_display_invite');
+
+INSERT INTO `sys_form_inputs`(`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES 
+('bx_course', 'bx_courses', 'cf', '1', '#!sys_content_filter', 0, 'select', '_sys_form_entry_input_sys_cf', '_sys_form_entry_input_cf', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_course', 'bx_courses', 'allow_view_to', 3, '', 0, 'custom', '_bx_courses_form_profile_input_sys_allow_view_to', '_bx_courses_form_profile_input_allow_view_to', '_bx_courses_form_profile_input_allow_view_to_desc', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_course', 'bx_courses', 'allow_post_to', 3, '', 0, 'custom', '_bx_courses_form_profile_input_sys_allow_post_to', '_bx_courses_form_profile_input_allow_post_to', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_course', 'bx_courses', 'delete_confirm', 1, '', 0, 'checkbox', '_bx_courses_form_profile_input_sys_delete_confirm', '_bx_courses_form_profile_input_delete_confirm', '_bx_courses_form_profile_input_delete_confirm_info', 1, 0, 0, '', '', '', 'avail', '', '_bx_courses_form_profile_input_delete_confirm_error', '', '', 1, 0),
+('bx_course', 'bx_courses', 'do_submit', '_sys_form_account_input_submit', '', 0, 'submit', '_bx_courses_form_profile_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_course', 'bx_courses', 'desc', '', '', 0, 'textarea', '_bx_courses_form_profile_input_sys_desc', '_bx_courses_form_profile_input_desc', '', 0, 0, 2, '', '', '', '', '', '', 'XssHtml', '', 1, 1),
+('bx_course', 'bx_courses', 'cat', '', '#!bx_courses_cats', 0, 'select', '_bx_courses_form_profile_input_sys_cat', '_bx_courses_form_profile_input_cat', '', 1, 0, 0, '', '', '', 'avail', '', '_bx_courses_form_profile_input_cat_err', 'Xss', '', 1, 1),
+('bx_course', 'bx_courses', 'name', '', '', 0, 'text', '_bx_courses_form_profile_input_sys_name', '_bx_courses_form_profile_input_name', '', 1, 0, 0, '', '', '', 'avail', '', '_bx_courses_form_profile_input_name_err', 'Xss', '', 1, 0),
+('bx_course', 'bx_courses', 'initial_members', '', '', 0, 'custom', '_bx_courses_form_profile_input_sys_initial_members', '_bx_courses_form_profile_input_initial_members', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 1),
+('bx_course', 'bx_courses', 'join_confirmation', 1, '', 0, 'switcher', '_bx_courses_form_profile_input_sys_join_confirm', '_bx_courses_form_profile_input_join_confirm', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_course', 'bx_courses', 'cover', 'a:1:{i:0;s:21:"bx_courses_cover_crop";}', 'a:1:{s:21:"bx_courses_cover_crop";s:24:"_sys_uploader_crop_title";}', 0, 'files', '_bx_courses_form_profile_input_sys_cover', '_bx_courses_form_profile_input_cover', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_course', 'bx_courses', 'picture', 'a:1:{i:0;s:23:"bx_courses_picture_crop";}', 'a:1:{s:23:"bx_courses_picture_crop";s:24:"_sys_uploader_crop_title";}', 0, 'files', '_bx_courses_form_profile_input_sys_picture', '_bx_courses_form_profile_input_picture', '', 0, 0, 0, '', '', '', '', '', '_bx_courses_form_profile_input_picture_err', '', '', 1, 0),
+('bx_course', 'bx_courses', 'location', '', '', 0, 'location', '_sys_form_input_sys_location', '_sys_form_input_location', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_course', 'bx_courses', 'labels', '', '', 0, 'custom', '_sys_form_input_sys_labels', '_sys_form_input_labels', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0);
+
+INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES 
+('bx_course_add', 'initial_members', 2147483647, 1, 1),
+('bx_course_add', 'name', 2147483647, 1, 2),
+('bx_course_add', 'cat', 2147483647, 1, 3),
+('bx_course_add', 'desc', 2147483647, 1, 4),
+('bx_course_add', 'location', 2147483647, 1, 5),
+('bx_course_add', 'join_confirmation', 2147483647, 1, 6),
+('bx_course_add', 'allow_view_to', 2147483647, 1, 7),
+('bx_course_add', 'allow_post_to', 2147483647, 1, 8),
+('bx_course_add', 'cf', 2147483647, 1, 9),
+('bx_course_add', 'do_submit', 2147483647, 1, 10),
+
+('bx_course_invite', 'initial_members', 2147483647, 1, 1),
+('bx_course_invite', 'do_submit', 2147483647, 1, 2),
+
+('bx_course_delete', 'delete_confirm', 2147483647, 1, 1),
+('bx_course_delete', 'do_submit', 2147483647, 1, 2),
+
+('bx_course_edit', 'name', 2147483647, 1, 1),
+('bx_course_edit', 'cat', 2147483647, 1, 2),
+('bx_course_edit', 'desc', 2147483647, 1, 3),
+('bx_course_edit', 'location', 2147483647, 1, 4),
+('bx_course_edit', 'join_confirmation', 2147483647, 1, 5),
+('bx_course_edit', 'allow_view_to', 2147483647, 1, 6),
+('bx_course_edit', 'allow_post_to', 2147483647, 1, 7),
+('bx_course_edit', 'cf', 2147483647, 1, 8),
+('bx_course_edit', 'do_submit', 2147483647, 1, 9),
+
+('bx_course_edit_cover', 'cover', 2147483647, 1, 1),
+('bx_course_edit_cover', 'do_submit', 2147483647, 1, 2),
+
+('bx_course_view', 'name', 2147483647, 1, 1),
+('bx_course_view', 'cat', 2147483647, 1, 2),
+
+('bx_course_view_full', 'name', 2147483647, 1, 1),
+('bx_course_view_full', 'cat', 2147483647, 1, 2),
+('bx_course_view_full', 'desc', 2147483647, 1, 3);
+
+-- FORMS: Price
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_price', 'bx_courses', '_bx_courses_form_price', '', '', 'do_submit', 'bx_courses_prices', 'id', '', '', '', 0, 1, 'BxCoursesFormPrice', 'modules/boonex/courses/classes/BxCoursesFormPrice.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_courses_price_add', 'bx_courses', 'bx_courses_price', '_bx_courses_form_price_display_add', 0),
+('bx_courses_price_edit', 'bx_courses', 'bx_courses_price', '_bx_courses_form_price_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_courses_price', 'bx_courses', 'id', '', '', 0, 'hidden', '_bx_courses_form_price_input_sys_id', '', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_courses_price', 'bx_courses', 'role_id', '', '', 0, 'hidden', '_bx_courses_form_price_input_sys_role_id', '', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_courses_price', 'bx_courses', 'name', '', '', 0, 'text', '_bx_courses_form_price_input_sys_name', '_bx_courses_form_price_input_name', '_bx_courses_form_price_input_inf_name', 1, 0, 0, '', '', '', 'Avail', '', '_bx_courses_form_price_input_err_name', 'Xss', '', 1, 0),
+('bx_courses_price', 'bx_courses', 'period', '', '', 0, 'text', '_bx_courses_form_price_input_sys_period', '_bx_courses_form_price_input_period', '_bx_courses_form_price_input_inf_period', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_courses_price', 'bx_courses', 'period_unit', '', '#!bx_courses_period_units', 0, 'select', '_bx_courses_form_price_input_sys_period_unit', '_bx_courses_form_price_input_period_unit', '_bx_courses_form_price_input_inf_period_unit', 1, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_courses_price', 'bx_courses', 'price', '', '', 0, 'price', '_bx_courses_form_price_input_sys_price', '_bx_courses_form_price_input_price', '_bx_courses_form_price_input_inf_price', 1, 0, 0, '', '', '', '', '', '', 'Float', '', 1, 0),
+('bx_courses_price', 'bx_courses', 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_courses_price', 'bx_courses', 'do_submit', '_bx_courses_form_price_input_do_submit', '', 0, 'submit', '_bx_courses_form_price_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_courses_price', 'bx_courses', 'do_cancel', '_bx_courses_form_price_input_do_cancel', '', 0, 'button', '_bx_courses_form_price_input_sys_do_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 1, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_courses_price_add', 'id', 2147483647, 0, 1),
+('bx_courses_price_add', 'role_id', 2147483647, 1, 2),
+('bx_courses_price_add', 'name', 2147483647, 1, 3),
+('bx_courses_price_add', 'price', 2147483647, 1, 4),
+('bx_courses_price_add', 'period', 2147483647, 1, 5),
+('bx_courses_price_add', 'period_unit', 2147483647, 1, 6),
+('bx_courses_price_add', 'controls', 2147483647, 1, 7),
+('bx_courses_price_add', 'do_submit', 2147483647, 1, 8),
+('bx_courses_price_add', 'do_cancel', 2147483647, 1, 9),
+
+('bx_courses_price_edit', 'id', 2147483647, 1, 1),
+('bx_courses_price_edit', 'role_id', 2147483647, 1, 2),
+('bx_courses_price_edit', 'name', 2147483647, 1, 3),
+('bx_courses_price_edit', 'price', 2147483647, 1, 4),
+('bx_courses_price_edit', 'period', 2147483647, 1, 5),
+('bx_courses_price_edit', 'period_unit', 2147483647, 1, 6),
+('bx_courses_price_edit', 'controls', 2147483647, 1, 7),
+('bx_courses_price_edit', 'do_submit', 2147483647, 1, 8),
+('bx_courses_price_edit', 'do_cancel', 2147483647, 1, 9);
+
+-- FORMS: Content Nodes
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_courses_content_node', 'bx_courses', '_bx_courses_form_content_node', '', '', 'do_submit', 'bx_courses_content_nodes', 'id', '', '', '', 0, 1, 'BxCoursesFormContentNode', 'modules/boonex/courses/classes/BxCoursesFormContentNode.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_courses_content_node_add', 'bx_courses', 'bx_courses_content_node', '_bx_courses_form_content_node_display_add', 0),
+('bx_courses_content_node_edit', 'bx_courses', 'bx_courses_content_node', '_bx_courses_form_content_node_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_courses_content_node', 'bx_courses', 'title', '', '', 0, 'text', '_bx_courses_form_content_node_input_sys_title', '_bx_courses_form_content_node_input_title', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_courses_form_content_node_input_title_err', 'Xss', '', 1, 0),
+('bx_courses_content_node', 'bx_courses', 'text', '', '', 0, 'textarea', '_bx_courses_form_content_node_input_sys_text', '_bx_courses_form_content_node_input_text', '', 0, 0, 2, '', '', '', '', '', '', 'XssHtml', '', 1, 0),
+('bx_courses_content_node', 'bx_courses', 'passing', '', '', 0, 'select', '_bx_courses_form_content_node_input_sys_passing', '_bx_courses_form_content_node_input_passing', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_courses_content_node', 'bx_courses', 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_courses_content_node', 'bx_courses', 'do_submit', '_bx_courses_form_content_node_input_do_submit', '', 0, 'submit', '_bx_courses_form_content_node_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_courses_content_node', 'bx_courses', 'do_cancel', '_bx_courses_form_content_node_input_do_cancel', '', 0, 'button', '_bx_courses_form_content_node_input_sys_do_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 1, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_courses_content_node_add', 'title', 2147483647, 1, 1),
+('bx_courses_content_node_add', 'text', 2147483647, 1, 2),
+('bx_courses_content_node_add', 'passing', 2147483647, 1, 3),
+('bx_courses_content_node_add', 'controls', 2147483647, 1, 4),
+('bx_courses_content_node_add', 'do_submit', 2147483647, 1, 5),
+('bx_courses_content_node_add', 'do_cancel', 2147483647, 1, 6),
+
+('bx_courses_content_node_edit', 'title', 2147483647, 1, 1),
+('bx_courses_content_node_edit', 'text', 2147483647, 1, 2),
+('bx_courses_content_node_edit', 'passing', 2147483647, 1, 3),
+('bx_courses_content_node_edit', 'controls', 2147483647, 1, 4),
+('bx_courses_content_node_edit', 'do_submit', 2147483647, 1, 5),
+('bx_courses_content_node_edit', 'do_cancel', 2147483647, 1, 6);
+
+-- PRE-VALUES
+INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
+('bx_courses_cats', '_bx_courses_pre_lists_cats', 'bx_courses', '0');
+
+INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALUES
+('bx_courses_cats', '', 0, '_sys_please_select', ''),
+('bx_courses_cats', '1', 1, '_bx_courses_cat_General', '');
+
+INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
+('bx_courses_roles', '_bx_courses_pre_lists_roles', 'bx_courses', '1');
+
+INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALUES
+('bx_courses_roles', '0', 1, '_bx_courses_role_regular', ''),
+('bx_courses_roles', '1', 2, '_bx_courses_role_administrator', ''),
+('bx_courses_roles', '2', 3, '_bx_courses_role_moderator', '');
+
+INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
+('bx_courses_period_units', '_bx_courses_pre_lists_period_units', 'bx_courses', '0');
+
+INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALUES
+('bx_courses_period_units', '', 0, '_sys_please_select', ''),
+('bx_courses_period_units', 'day', 1, '_bx_courses_period_unit_day', ''),
+('bx_courses_period_units', 'week', 2, '_bx_courses_period_unit_week', ''),
+('bx_courses_period_units', 'month', 3, '_bx_courses_period_unit_month', ''),
+('bx_courses_period_units', 'year', 4, '_bx_courses_period_unit_year', '');
+
+-- COMMENTS
+INSERT INTO `sys_objects_cmts` (`Name`, `Module`, `Table`, `CharsPostMin`, `CharsPostMax`, `CharsDisplayMax`, `Html`, `PerView`, `PerViewReplies`, `BrowseType`, `IsBrowseSwitch`, `PostFormPosition`, `NumberOfLevels`, `IsDisplaySwitch`, `IsRatable`, `ViewingThreshold`, `IsOn`, `RootStylePrefix`, `BaseUrl`, `ObjectVote`, `TriggerTable`, `TriggerFieldId`, `TriggerFieldAuthor`, `TriggerFieldTitle`, `TriggerFieldComments`, `ClassName`, `ClassFile`) VALUES
+('bx_courses', 'bx_courses', 'bx_courses_cmts', 1, 5000, 1000, 3, 5, 3, 'tail', 1, 'bottom', 1, 1, 1, -3, 1, 'cmt', 'page.php?i=view-course-profile&id={object_id}', '', 'bx_courses_data', 'id', 'author', 'name', 'comments', 'BxCoursesCmts', 'modules/boonex/courses/classes/BxCoursesCmts.php'),
+('bx_courses_notes', 'bx_courses', 'bx_courses_cmts_notes', 1, 5000, 1000, 0, 5, 3, 'tail', 1, 'bottom', 1, 1, 1, -3, 1, 'cmt', 'page.php?i=view-course-profile&id={object_id}', '', 'bx_courses_data', 'id', 'author', 'name', '', 'BxTemplCmtsNotes', '');
+
+-- VIEWS
+INSERT INTO `sys_objects_view` (`name`, `module`, `table_track`, `period`, `is_on`, `trigger_table`, `trigger_field_id`, `trigger_field_author`, `trigger_field_count`, `class_name`, `class_file`) VALUES 
+('bx_courses', 'bx_courses', 'bx_courses_views_track', '86400', '1', 'bx_courses_data', 'id', 'author', 'views', '', '');
+
+-- VOTES
+INSERT INTO `sys_objects_vote` (`Name`, `Module`, `TableMain`, `TableTrack`, `PostTimeout`, `MinValue`, `MaxValue`, `IsUndo`, `IsOn`, `TriggerTable`, `TriggerFieldId`, `TriggerFieldAuthor`, `TriggerFieldRate`, `TriggerFieldRateCount`, `ClassName`, `ClassFile`) VALUES 
+('bx_courses', 'bx_courses', 'bx_courses_votes', 'bx_courses_votes_track', '604800', '1', '1', '0', '1', 'bx_courses_data', 'id', 'author', 'rate', 'votes', '', '');
+
+-- SCORES
+INSERT INTO `sys_objects_score` (`name`, `module`, `table_main`, `table_track`, `post_timeout`, `is_on`, `trigger_table`, `trigger_field_id`, `trigger_field_author`, `trigger_field_score`, `trigger_field_cup`, `trigger_field_cdown`, `class_name`, `class_file`) VALUES 
+('bx_courses', 'bx_courses', 'bx_courses_scores', 'bx_courses_scores_track', '604800', '0', 'bx_courses_data', 'id', 'author', 'score', 'sc_up', 'sc_down', '', '');
+
+-- REPORTS
+INSERT INTO `sys_objects_report` (`name`, `module`, `table_main`, `table_track`, `is_on`, `base_url`, `object_comment`, `trigger_table`, `trigger_field_id`, `trigger_field_author`, `trigger_field_count`, `class_name`, `class_file`) VALUES 
+('bx_courses', 'bx_courses', 'bx_courses_reports', 'bx_courses_reports_track', '1', 'page.php?i=view-course-profile&id={object_id}', 'bx_courses_notes', 'bx_courses_data', 'id', 'author', 'reports', '', '');
+
+-- FAFORITES
+INSERT INTO `sys_objects_favorite` (`name`, `table_track`, `table_lists`, `is_on`, `is_undo`, `is_public`, `base_url`, `trigger_table`, `trigger_field_id`, `trigger_field_author`, `trigger_field_count`, `class_name`, `class_file`) VALUES 
+('bx_courses', 'bx_courses_favorites_track', 'bx_courses_favorites_lists', '1', '1', '1', 'page.php?i=view-course-profile&id={object_id}', 'bx_courses_data', 'id', 'author', 'favorites', '', '');
+
+-- FEATURED
+INSERT INTO `sys_objects_feature` (`name`, `module`, `is_on`, `is_undo`, `base_url`, `trigger_table`, `trigger_field_id`, `trigger_field_author`, `trigger_field_flag`, `class_name`, `class_file`) VALUES 
+('bx_courses', 'bx_courses', '1', '1', 'page.php?i=view-course-profile&id={object_id}', 'bx_courses_data', 'id', 'author', 'featured', '', '');
+
+-- CONTENT INFO
+INSERT INTO `sys_objects_content_info` (`name`, `title`, `alert_unit`, `alert_action_add`, `alert_action_update`, `alert_action_delete`, `class_name`, `class_file`) VALUES
+('bx_courses', '_bx_courses', 'bx_courses', 'added', 'edited', 'deleted', '', ''),
+('bx_courses_cmts', '_bx_courses_cmts', 'bx_courses', 'commentPost', 'commentUpdated', 'commentRemoved', 'BxDolContentInfoCmts', '');
+
+INSERT INTO `sys_content_info_grids` (`object`, `grid_object`, `grid_field_id`, `condition`, `selection`) VALUES
+('bx_courses', 'bx_courses_administration', 'td`.`id', '', ''),
+('bx_courses', 'bx_courses_common', 'td`.`id', '', '');
+
+-- SEARCH EXTENDED
+INSERT INTO `sys_objects_search_extended` (`object`, `object_content_info`, `module`, `title`, `active`, `class_name`, `class_file`) VALUES
+('bx_courses', 'bx_courses', 'bx_courses', '_bx_courses_search_extended', 1, '', ''),
+('bx_courses_cmts', 'bx_courses_cmts', 'bx_courses', '_bx_courses_search_extended_cmts', 1, 'BxTemplSearchExtendedCmts', '');
+
+-- STUDIO PAGE & WIDGET
+INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
+(3, 'bx_courses', '_bx_courses', '_bx_courses', 'bx_courses@modules/boonex/courses/|std-icon.svg');
+SET @iPageId = LAST_INSERT_ID();
+
+SET @iParentPageId = (SELECT `id` FROM `sys_std_pages` WHERE `name` = 'home');
+SET @iParentPageOrder = (SELECT MAX(`order`) FROM `sys_std_pages_widgets` WHERE `page_id` = @iParentPageId);
+INSERT INTO `sys_std_widgets` (`page_id`, `module`, `type`, `url`, `click`, `icon`, `caption`, `cnt_notices`, `cnt_actions`) VALUES
+(@iPageId, 'bx_courses', 'content', '{url_studio}module.php?name=bx_courses', '', 'bx_courses@modules/boonex/courses/|std-icon.svg', '_bx_courses', '', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:11:"get_actions";s:6:"params";a:0:{}s:5:"class";s:18:"TemplStudioModules";}');
+INSERT INTO `sys_std_pages_widgets` (`page_id`, `widget_id`, `order`) VALUES
+(@iParentPageId, LAST_INSERT_ID(), IF(ISNULL(@iParentPageOrder), 1, @iParentPageOrder + 1));
+

+ 86 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/js/entry.js

@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Courses Courses
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+function BxCoursesEntry(oOptions) {
+    BxBaseModGroupsEntry.call(this, oOptions);
+
+    this._sObjName = oOptions.sObjName == undefined ? 'oBxCoursesEntry' : oOptions.sObjName;
+}
+
+BxCoursesEntry.prototype = Object.create(BxBaseModGroupsEntry.prototype);
+BxCoursesEntry.prototype.constructor = BxCoursesEntry;
+
+BxCoursesEntry.prototype.perform = function(oLink, sAction, iId) {
+    var oDate = new Date();
+    var oData = {
+    	action: sAction,
+    	id: iId,
+        _t:oDate.getTime()
+    };
+
+    $(oLink).parents('.bx-popup-applied:first:visible').dolPopupHide({});
+
+    $.post(
+        this._sActionsUrl + 'perform/',
+        oData,
+        function(oData) {
+            processJsonData(oData);
+        },
+        'json'
+    );
+};
+
+BxCoursesEntry.prototype.passNode = function(oElement, iNodeId) {
+    var $this = this;
+    var oParams = this._getDefaultData();
+    oParams['node_id'] = iNodeId;
+
+    if(oElement)
+        this.loadingInButton(oElement, true);
+
+    jQuery.get (
+        this._sActionsUrl + 'pass_node',
+        oParams,
+        function(oData) {
+            if(oElement)
+                $this.loadingInButton(oElement, false);
+
+            processJsonData(oData);
+        },
+        'json'
+    );
+
+    return false;
+};
+
+BxCoursesEntry.prototype.passData = function(oElement, iDataId) {
+    var $this = this;
+    var oParams = this._getDefaultData();
+    oParams['data_id'] = iDataId;
+
+    if(oElement)
+        this.loadingInButton(oElement, true);
+
+    jQuery.get (
+        this._sActionsUrl + 'pass_data',
+        oParams,
+        function(oData) {
+            if(oElement)
+                $this.loadingInButton(oElement, false);
+
+            processJsonData(oData);
+        },
+        'json'
+    );
+
+    return false;
+};
+
+/** @} */

+ 20 - 0
modules/boonex/courses/updates/14.0.3_14.0.4/source/template/entries_summary.html

@@ -0,0 +1,20 @@
+<div class="bx-courses-entries-summary flex flex-row items-stretch">
+    <div class="bx-courses-es-percent flex-1 md:flex-none">
+        <div class="bx-courses-percent flex justify-center items-center w-16 h-16 border-2 rounded-full">
+            <span>__passed_percent__%</span>
+        </div>
+    </div>
+    <div class="bx-courses-es-title flex-1 hidden md:flex items-center pl-4 text-xl font-bold"><bx_text:_bx_courses_txt_summary_title /></div>
+    <div class="bx-courses-es-joined flex flex-col items-center w-24 border-r">
+        <div class="bx-courses-esi-title text-xs"><bx_text:_bx_courses_txt_summary_joined /></div>
+        <div class="bx-courses-esi-value flex-1 flex items-center text-2xl font-bold">__joined__</div>
+    </div>
+    <div class="bx-courses-es-passed flex flex-col items-center w-24 border-r">
+        <div class="bx-courses-esi-title text-xs"><bx_text:_bx_courses_txt_summary_passed /></div>
+        <div class="bx-courses-esi-value flex-1 flex items-center text-2xl font-bold">__passed__</div>
+    </div>
+    <div class="bx-courses-es-not-passed flex flex-col items-center w-24">
+        <div class="bx-courses-esi-title text-xs"><bx_text:_bx_courses_txt_summary_not_started /></div>
+        <div class="bx-courses-esi-value flex-1 flex items-center text-2xl font-bold">__not_passed__</div>
+    </div>
+</div>

+ 51 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Developer',
+    'version_from' => '14.0.3',
+    'version_to' => '14.0.4',
+    'vendor' => 'BoonEx',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/developer/updates/update_14.0.3_14.0.4/',
+    'home_uri' => 'developer_update_1403_1404',
+
+    'module_dir' => 'boonex/developer/',
+    'module_uri' => 'developer',
+
+    'db_prefix' => 'bx_dev_',
+    'class_prefix' => 'BxDev',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'BoonEx Developer',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxDevUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 11 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/install/langs/en.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string_add name="_bx_dev_nav_txt_sys_menus_config_api"><![CDATA[Config (for App)]]></string_add>
+	<string_add name="_bx_dev_nav_txt_menus_config_api"><![CDATA[Config (for App in JSON format)]]></string_add>
+	<string_add name="_bx_dev_nav_txt_sys_items_config_api"><![CDATA[Config (for App)]]></string_add>
+	<string_add name="_bx_dev_nav_txt_items_config_api"><![CDATA[Config (for App in JSON format)]]></string_add>
+	<string_add name="_bx_dev_bp_txt_sys_page_config_api"><![CDATA[Config (for App)]]></string_add>
+	<string_add name="_bx_dev_bp_txt_page_config_api"><![CDATA[Config (for App in JSON format)]]></string_add>
+	<string_add name="_bx_dev_bp_txt_sys_block_config_api"><![CDATA[Config (for App)]]></string_add>
+	<string_add name="_bx_dev_bp_txt_block_config_api"><![CDATA[Config (for App in JSON format)]]></string_add>
+</resources>

+ 37 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/install/sql/install.sql

@@ -0,0 +1,37 @@
+SET @sName = 'bx_developer';
+
+
+-- FORMS
+DELETE FROM `sys_form_inputs` WHERE `object`='bx_developer_nav_menu' AND `name`='config_api';
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_nav_menu', @sName, 'config_api', '', '', 0, 'textarea', '_bx_dev_nav_txt_sys_menus_config_api', '_bx_dev_nav_txt_menus_config_api', '', 0, 0, 0, '', '', '', '', '', '', 'XssHtml', '', 0, 0);
+
+DELETE FROM `sys_form_display_inputs` WHERE `display_name` IN ('bx_developer_nav_menu_add', 'bx_developer_nav_menu_edit') AND `input_name`='config_api';
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_nav_menu_add', 'config_api', 2147483647, 1, 5),
+('bx_developer_nav_menu_edit', 'config_api', 2147483647, 1, 6);
+
+DELETE FROM `sys_form_inputs` WHERE `object`='bx_developer_nav_item' AND `name`='config_api';
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_nav_item', @sName, 'config_api', '', '', 0, 'textarea', '_bx_dev_nav_txt_sys_items_config_api', '_bx_dev_nav_txt_items_config_api', '', 0, 0, 0, '', '', '', '', '', '', 'XssHtml', '', 0, 0);
+
+DELETE FROM `sys_form_display_inputs` WHERE `display_name` IN ('bx_developer_nav_item_add', 'bx_developer_nav_item_edit') AND `input_name`='config_api';
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_nav_item_add', 'config_api', 2147483647, 1, 17),
+('bx_developer_nav_item_edit', 'config_api', 2147483647, 1, 18);
+
+DELETE FROM `sys_form_inputs` WHERE `object`='bx_developer_bp_page' AND `name`='config_api';
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_bp_page', @sName, 'config_api', '', '', 0, 'textarea', '_bx_dev_bp_txt_sys_page_config_api', '_bx_dev_bp_txt_page_config_api', '', 0, 0, 0, '', '', '', '', '', '', 'XssHtml', '', 0, 0);
+
+DELETE FROM `sys_form_display_inputs` WHERE `display_name`='bx_developer_bp_page_add' AND `input_name`='config_api';
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_bp_page_add', 'config_api', 2147483647, 1, 7);
+
+DELETE FROM `sys_form_inputs` WHERE `object`='bx_developer_bp_block' AND `name`='config_api';
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_bp_block', @sName, 'config_api', '', '', 0, 'textarea', '_bx_dev_bp_txt_sys_block_config_api', '_bx_dev_bp_txt_block_config_api', '', 0, 0, 0, '', '', '', '', '', '', 'XssHtml', '', 0, 0);
+
+DELETE FROM `sys_form_display_inputs` WHERE `display_name`='bx_developer_bp_block_edit' AND `input_name`='config_api';
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_bp_block_edit', 'config_api', 2147483647, 1, 13);

+ 517 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevBuilderPage.php

@@ -0,0 +1,517 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxDevBuilderPage extends BxTemplStudioBuilderPage
+{
+    protected $sActionPageExport = 'page_export';
+
+    protected $oModule;
+    protected $aParams;
+
+    function __construct($aParams)
+    {
+        parent::__construct(isset($aParams['type']) ? $aParams['type'] : '',  isset($aParams['page']) ? $aParams['page'] : '');
+
+        $this->bPageMenuTitle = false;
+
+        $this->aParams = $aParams;
+        $this->sBaseUrl = $this->aParams['url'];
+        $this->sTypeUrl = $this->sBaseUrl . '&bp_type=%s';
+        $this->sPageUrl = $this->sTypeUrl . '&bp_page=%s';
+
+        $this->oModule = BxDolModule::getInstance('bx_developer');
+
+        $this->oModule->_oTemplate->addStudioCss(array('builder_page.css'));
+    }
+
+    function getPageCode($sPage = '', $bWrap = true)
+    {
+        return $this->oModule->_oTemplate->getJsCode('main') . parent::getPageCode($sPage, $bWrap);
+    }
+
+    function getBlockPanelTop($aBlock)
+    {
+         return BxBaseStudioWidget::getBlockPanelTop(
+            array('panel_top' => $this->oModule->_oTemplate->parseHtmlByName('bp_block_panel_top.html', $this->_getTmplVarsBlockPanelTop()))
+        );
+    }
+
+    protected function actionPageCreate()
+    {
+    	$sFormObject = $this->oModule->_oConfig->getObject('form_bp_page');
+        $sFormDisplay = $this->oModule->_oConfig->getObject('form_display_bp_page_add');
+
+        $oForm = BxDolForm::getObjectInstance($sFormObject, $sFormDisplay, $this->oModule->_oTemplate);
+        $oForm->aFormAttrs['action'] = sprintf($this->sPageUrl, $this->sType, $this->sPage) . '&bp_action=' . $this->sActionPageCreate;
+        $oForm->aInputs['module']['values'] = array_merge(array('' => _t('_bx_dev_bp_txt_select_module')), BxDolStudioUtils::getModules());
+
+        $oForm->aInputs['layout_id']['values'] = array(
+            array('key' => '', 'value' => _t('_bx_dev_bp_txt_page_layout_id_select'))
+        );
+
+        $aLayouts = array();
+        $this->oDb->getLayouts(array('type' => 'all'), $aLayouts, false);
+        foreach($aLayouts as $aLayout)
+            $oForm->aInputs['layout_id']['values'][] = array('key' => $aLayout['id'], 'value' => _t($aLayout['title']));
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $sObject = $oForm->getCleanValue('object');
+            $sModule = $oForm->getCleanValue('module');
+
+            if(($iId = (int)$oForm->insert()) != 0)
+                return array('eval' => $this->getPageJsObject() . '.onCreatePage(\'' . $sModule . '\', \'' . $sObject . '\')');
+            else
+                return array('msg' => _t('_bx_dev_bp_err_page_create'));
+        }
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox($this->aHtmlIds['add_popup_id'], _t('_bx_dev_bp_txt_page_create_popup'), $this->oModule->_oTemplate->parseHtmlByName('bp_add_page.html', array(
+            'form_id' => $oForm->aFormAttrs['id'],
+            'form' => $oForm->getCode(true)
+        )));
+
+        return array('popup' => $sContent);
+    }
+
+    protected function actionPageExport()
+    {
+        $sContentInsert = $sContentDelete = "";
+
+        $aPage = $this->oDb->getPages(array('type' => 'by_object', 'value' => $this->sPage));
+        if(empty($aPage) || !is_array($aPage))
+            return array();
+
+        $sContentInsert .= ($this->oModule->_oDb->getQueryInsert('sys_objects_page', array($aPage), "Dumping data for '" . $aPage['object'] . "' page"));
+        $sContentDelete .= ($this->oModule->_oDb->getQueryDelete('sys_objects_page', 'object', array($aPage), "Deleting data for '" . $aPage['object'] . "' page"));
+
+        $aBlocks = array();
+        $this->oDb->getBlocks(array('type' => 'by_object', 'value' => $aPage['object']), $aBlocks, false);
+        $sContentInsert .= $this->oModule->_oDb->getQueryInsert('sys_pages_blocks', $aBlocks, false, array('id', 'name'));
+        $sContentDelete .= $this->oModule->_oDb->getQueryDelete('sys_pages_blocks', 'object', array($aPage), false);
+
+        $aForm = array(
+            'form_attrs' => array(),
+            'inputs' => array (
+                'insert' => array(
+                    'type' => 'textarea',
+                    'name' => 'insert',
+                    'caption' => _t('_bx_dev_bp_txt_page_export_insert'),
+                    'value' => $sContentInsert,
+                ),
+                'delete' => array(
+                    'type' => 'textarea',
+                    'name' => 'delete',
+                    'caption' => _t('_bx_dev_bp_txt_page_export_delete'),
+                    'value' => $sContentDelete,
+                ),
+                'done' => array (
+                    'type' => 'button',
+                    'name' => 'done',
+                    'value' => _t('_bx_dev_bp_btn_page_done'),
+                    'attrs' => array(
+                        'onclick' => "$('.bx-popup-applied:visible').dolPopupHide()",
+                    ),
+                )
+            )
+        );
+        $oForm = new BxTemplStudioFormView($aForm);
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-dev-bp-page-export-popup', _t('_bx_dev_bp_txt_page_export_popup', _t($aPage['title'])), $this->oModule->_oTemplate->parseHtmlByName('bp_export.html', array(
+            'content' => $oForm->getCode()
+        )));
+
+        return array('popup' => $sContent);
+    }
+
+    protected function actionBlockEdit()
+    {
+    	$sFormObject = $this->oModule->_oConfig->getObject('form_bp_block');
+        $sFormDisplay = $this->oModule->_oConfig->getObject('form_display_bp_block_edit');
+        
+        $iId = (int)bx_get('id');
+        if(!$iId)
+            return array();
+
+        $aBlock = array();
+        $this->oDb->getBlocks(array('type' => 'by_id', 'value' => $iId), $aBlock, false);
+        if(empty($aBlock) || !is_array($aBlock))
+            return array('msg' => _t('_bx_dev_bp_err_block_not_found'));
+
+        $oForm = BxDolForm::getObjectInstance($sFormObject, $sFormDisplay, $this->oModule->_oTemplate);
+
+        $this->onLoadBlock($oForm, $aBlock);
+
+        $oForm->initChecker($aBlock);
+        if($oForm->isSubmittedAndValid()) {
+            $this->onSaveBlock($oForm, $aBlock);
+
+            if($oForm->update($iId) !== false)
+                return array('eval' => $this->getPageJsObject() . '.onEditBlock(oData)');
+            else
+                return array('msg' => _t('_bx_dev_bp_err_block_edit'));
+        }
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox($this->aHtmlIds['edit_block_popup_id'], _t('_bx_dev_bp_txt_block_edit_popup', _t($aBlock['title'])), $this->oModule->_oTemplate->parseHtmlByName('bp_add_block.html', array(
+            'form_id' => $oForm->aFormAttrs['id'],
+            'form' => $oForm->getCode(true)
+        )));
+
+        return array('popup' => $sContent);
+    }
+
+    protected function actionBlockDelete()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $iId = (int)bx_get('id');
+
+        $aBlock = array();
+        $this->oDb->getBlocks(array('type' => 'by_id', 'value' => $iId), $aBlock, false);
+        if(empty($aBlock) || !is_array($aBlock))
+            return array('msg' => _t('_bx_dev_bp_err_block_not_found'));
+
+        if(!$this->oDb->deleteBlocks(array('type' => 'by_id', 'value' => $iId)))
+            return array('msg' => _t('_bx_dev_bp_err_block_delete'));
+
+        $this->onBlockDelete($aBlock);
+        return array('eval' => $sJsObject . '.onDeleteBlock(' . $iId . ', oData)');
+    }
+
+    protected function getSettingsOptions($aPage = array(), $bCreate = true, $bInputsOnly = false)
+    {
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-settings-seo',
+            ),
+            'params' => array (
+                'remove_form' => '1',
+                'csrf' => array(
+                    'disable' => true
+                )
+            ),
+            'inputs' => array(
+                'object'  => array(
+                    'type' => 'text',
+                    'name' => 'object',
+                    'caption' => _t('_bx_dev_bp_txt_page_object'),
+                    'info' => '',
+                    'value' => $this->aPageRebuild['object'],
+                    'required' => '',
+                    'attrs' => array(
+                        'disabled' => 'disabled'
+                    ),
+                )
+            )
+        );
+
+        $aForm['inputs'] += parent::getSettingsOptions($aPage, $bCreate, true);
+
+        $aForm['inputs']['title_system']['type'] = 'text';
+        $aForm['inputs']['title_system']['caption'] = _t('_bx_dev_bp_txt_page_title_system');
+        $aForm['inputs']['title']['type'] = 'text';
+
+        $aUri = array(
+            'uri'  => array(
+                'type' => 'text',
+                'name' => 'uri',
+                'caption' => _t('_bx_dev_bp_txt_page_uri'),
+                'info' => '',
+                'value' => $this->aPageRebuild['uri'],
+                'required' => '',
+                'db' => array (
+                    'pass' => 'Xss',
+                ),
+            )
+        );
+        $aForm['inputs'] = bx_array_insert_before($aUri, $aForm['inputs'], 'url');
+
+        $aForm['inputs']['url']['caption'] = _t('_bx_dev_bp_txt_page_url');
+        $aForm['inputs']['url']['value'] = isset($aPage['url']) ? $aPage['url'] : '';
+        $aForm['inputs']['url']['db'] = array (
+            'pass' => 'Xss',
+        );
+        unset($aForm['inputs']['url']['attrs']['disabled']);
+
+        $aConfigApi = array(
+            'config_api'  => array(
+                'type' => 'textarea',
+                'name' => 'config_api',
+                'caption' => _t('_bx_dev_bp_txt_page_config_api'),
+                'info' => '',
+                'value' => $this->aPageRebuild['config_api'],
+                'required' => '',
+                'db' => array (
+                    'pass' => 'XssHtml',
+                ),
+            )
+        );
+        $aForm['inputs'] = bx_array_insert_before($aConfigApi, $aForm['inputs'], 'sticky_columns');
+
+        $aForm['inputs']['deletable'] = array(
+            'type' => 'switcher',
+            'name' => 'deletable',
+            'caption' => _t('_bx_dev_bp_txt_page_deletable'),
+            'info' => '',
+            'value' => '1',
+            'checked' => (int)$this->aPageRebuild['deletable'] == 1,
+            'required' => '',
+            'db' => array (
+                'pass' => 'Int',
+            )
+        );
+        $aForm['inputs']['override_class_name'] = array(
+            'type' => 'text',
+            'name' => 'override_class_name',
+            'caption' => _t('_bx_dev_bp_txt_page_override_class_name'),
+            'info' => '',
+            'value' => $this->aPageRebuild['override_class_name'],
+            'required' => '0',
+            'db' => array (
+                'pass' => 'Xss',
+            )
+        );
+        $aForm['inputs']['override_class_file'] = array(
+            'type' => 'text',
+            'name' => 'override_class_file',
+            'caption' => _t('_bx_dev_bp_txt_page_override_class_file'),
+            'info' => '',
+            'value' => $this->aPageRebuild['override_class_file'],
+            'required' => '0',
+            'db' => array (
+                'pass' => 'Xss',
+            )
+        );
+
+        if($bInputsOnly)
+            return $aForm['inputs'];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        return $oForm->getCode();
+    }
+
+    protected function getSettingsCache($aPage = array(), $bCreate = true, $bInputsOnly = false)
+    {
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-bp-settings-cache',
+            ),
+            'params' => array (
+                'remove_form' => '1',
+                'csrf' => array(
+                    'disable' => true
+                )
+            ),
+            'inputs' => parent::getSettingsCache($aPage, $bCreate, true)
+        );
+
+        unset($aForm['inputs']['cache_lifetime']['attrs']);
+
+        $aForm['inputs']['cache_editable'] = array(
+            'type' => 'switcher',
+            'name' => 'cache_editable',
+            'caption' => _t('_bx_dev_bp_txt_page_cache_editable'),
+            'info' => '',
+            'value' => '1',
+            'checked' => (int)$this->aPageRebuild['cache_editable'] == 1,
+            'required' => '',
+            'db' => array (
+                'pass' => 'Int',
+            )
+        );
+
+        if($bInputsOnly)
+            return $aForm['inputs'];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        return $oForm->getCode();
+    }
+
+    protected function getBlockModule($aBlock)
+    {
+        return $aBlock['module'] != BX_DOL_STUDIO_BP_SKELETONS ? $aBlock['module'] : $this->aPageRebuild['module'];
+    }
+
+    protected function getBlockContent($aBlock)
+    {
+        $aFields = array();
+
+        switch($aBlock['type']) {
+            case BX_DOL_STUDIO_BP_BLOCK_SERVICE:
+                $aFields = array(
+                    'content' => array(
+                        'type' => 'textarea',
+                        'name' => 'content',
+                        'caption' => _t('_bx_dev_bp_txt_block_content_service'),
+                        'info' => '',
+                        'value' => $aBlock['content'],
+                        'required' => '0',
+                        'db' => array (
+                            'pass' => 'Xss',
+                        ),
+                    )
+                );
+                break;
+
+            case BX_DOL_STUDIO_BP_BLOCK_LANG:
+                $aFields = array(
+                    'content' => array(
+                        'type' => 'text',
+                        'name' => 'content',
+                        'caption' => _t('_bx_dev_bp_txt_block_content_lang'),
+                        'info' => _t('_bx_dev_bp_dsc_block_content_lang'),
+                        'value' => $aBlock['content'],
+                        'required' => '0',
+                        'db' => array (
+                            'pass' => 'Xss',
+                        ),
+                    ),
+                );
+                break;
+
+            default:
+                $aFields = parent::getBlockContent($aBlock);
+                break;
+        }
+
+        return $aFields;
+    }
+
+    protected function getBlocks($sModule)
+    {
+        $aBlocks = parent::getBlocks($sModule);
+
+        if($sModule == BX_DOL_STUDIO_BP_SKELETONS) {
+            $aBlock = array();
+            $this->oDb->getBlocks(array('type' => 'skeleton_by_type', 'value' => 'service'), $aBlock, false);
+            if(!empty($aBlock) && is_array($aBlock))
+                $aBlocks[] = $aBlock;
+        }
+
+        return $aBlocks;
+    }
+
+    protected function onLoadBlock(&$oForm, &$aBlock)
+    {
+        $oForm->aFormAttrs['action'] = sprintf($this->sPageUrl, $this->sType, $this->sPage) . '&bp_action=' . $this->sActionBlockEdit;
+        $oForm->aInputs['module']['values'] = array_merge(array('' => _t('_bx_dev_bp_txt_select_module')), BxDolStudioUtils::getModules());
+
+        $aDBoxes = array();
+        $this->oDb->getDesignBoxes(array('type' => 'ordered'), $aDBoxes, false);
+        foreach($aDBoxes as $aDBox)
+            $oForm->aInputs['designbox_id']['values'][] = array('key' => $this->sSelectKeyPrefix . $aDBox['id'], 'value' => _t($aDBox['title']));
+
+        $oForm->aInputs['class']['value'] = $aBlock['class'];
+
+        $aContentPlaceholders = $this->oDb->getContentPlaceholders();
+        $oForm->aInputs['async']['values'] = array(
+            array('key' => 0, 'value' => _t('_sys_no_async'))
+        );
+        foreach ($aContentPlaceholders as $iKey => $sTitle)
+            $oForm->aInputs['async']['values'][] = array('key' => $iKey, 'value' => _t($sTitle));
+
+        $oForm->aInputs['hidden_on']['values'] = array(
+            BX_DB_HIDDEN_PHONE => _t('_bx_dev_bp_txt_block_hidden_on_phone'),
+            BX_DB_HIDDEN_TABLET => _t('_bx_dev_bp_txt_block_hidden_on_tablet'),
+            BX_DB_HIDDEN_DESKTOP => _t('_bx_dev_bp_txt_block_hidden_on_desktop'),
+            BX_DB_HIDDEN_MOBILE => _t('_bx_dev_bp_txt_block_hidden_on_mobile')
+        );
+
+        $aSubmenus = $this->oDb->getBlockSubmenus();
+        $oForm->aInputs['submenu']['values'] = array(
+            array('key' => '', 'value' => _t('_sys_no_menu'))
+        );
+        foreach ($aSubmenus as $sObject => $sTitle)
+            $oForm->aInputs['submenu']['values'][] = array('key' => $sObject, 'value' => _t($sTitle));
+
+        $oForm->aInputs['visible_for']['value'] = $aBlock['visible_for_levels'] == BX_DOL_INT_MAX ? BX_DOL_STUDIO_VISIBLE_ALL : BX_DOL_STUDIO_VISIBLE_SELECTED;
+        $oForm->aInputs['visible_for']['values'] = array(
+            array('key' => BX_DOL_STUDIO_VISIBLE_ALL, 'value' => _t('_bx_dev_bp_txt_block_visible_for_all')),
+            array('key' => BX_DOL_STUDIO_VISIBLE_SELECTED, 'value' => _t('_bx_dev_bp_txt_block_visible_for_selected')),
+        );
+        $oForm->aInputs['visible_for']['attrs']['onchange'] = $this->getPageJsObject() . '.onChangeVisibleFor(this)';
+
+        $oForm->aInputs['visible_for_levels']['tr_attrs']['style'] = $aBlock['visible_for_levels'] == BX_DOL_INT_MAX ? 'display:none' : '';
+        BxDolStudioUtils::getVisibilityValues($aBlock['visible_for_levels'], $oForm->aInputs['visible_for_levels']['values'], $oForm->aInputs['visible_for_levels']['value']);
+        $aBlock['visible_for_levels'] = $oForm->aInputs['visible_for_levels']['value'];
+
+        $oForm->aInputs = $this->addInArray($oForm->aInputs, 'visible_for_levels', $this->getBlockContent($aBlock));
+
+        $oForm->aInputs['controls'][0]['value'] = _t('_bx_dev_bp_btn_block_save');
+        $oForm->aInputs['controls'][2]['attrs']['onclick'] = $this->getPageJsObject() . ".deleteBlock(" . $aBlock['id'] . ")";
+
+        if($aBlock['type'] == BX_DOL_STUDIO_BP_BLOCK_SERVICE)
+            $aBlock['content'] = BxDevFunctions::unserializeString($aBlock['content']);
+
+        $aBlock['designbox_id'] = $this->sSelectKeyPrefix . $aBlock['designbox_id'];
+    }
+
+    protected function onSavePage(&$oForm, &$aPage) 
+    {
+        if(($sConfigApi = $oForm->getCleanValue('config_api')) && !$this->oModule->isValidJson($sConfigApi))
+            BxDolForm::setSubmittedValue('config_api', '', $oForm->aFormAttrs['method']);
+    }
+
+    protected function onSaveBlock(&$oForm, &$aBlock)
+    {
+        parent::onSaveBlock($oForm, $aBlock);
+
+        if($aBlock['type'] == BX_DOL_STUDIO_BP_BLOCK_SERVICE && isset($oForm->aInputs['content']))
+            $this->onSaveBlockService($oForm, $aBlock);
+
+        if(($sConfigApi = $oForm->getCleanValue('config_api')) && !$this->oModule->isValidJson($sConfigApi))
+            BxDolForm::setSubmittedValue('config_api', '', $oForm->aFormAttrs['method']);
+    }
+
+    protected function onSaveBlockLang(&$oForm, &$aBlock)
+    {
+        $sValue = $oForm->getCleanValue('content');
+        BxDolForm::setSubmittedValue('content', $sValue, $oForm->aFormAttrs['method']);
+    }
+
+    protected function onSaveBlockService(&$oForm, &$aBlock)
+    {
+        $sValue = $oForm->getCleanValue('content');
+        $sValue = BxDevFunctions::serializeString($sValue);
+        BxDolForm::setSubmittedValue('content', $sValue, $oForm->aFormAttrs['method']);
+    }
+
+    protected function _getTmplVarsBlockPanelTop()
+    {
+        $aResult = parent::_getTmplVarsBlockPanelTop();
+
+        $aResult['bx_if:show_actions_global'] = [
+            'condition' => $this->sPage == '' || empty($this->aPageRebuild),
+            'content' => [
+                'js_object' => $this->oModule->_oConfig->getJsObject('main'),
+                'action_page_import' => 'import',
+                'action_page_export' => 'export'
+            ]
+        ];
+
+        return $aResult;
+    }
+
+    protected function _getTmplVarsBlockPanelTopActions()
+    {
+        $sJsObject = $this->getPageJsObject();
+
+        $oPermalinks = BxDolPermalinks::getInstance();
+
+        return array(
+            'js_object' => $sJsObject,
+            'url_view' => bx_absolute_url($oPermalinks->permalink($this->aPageRebuild['url'])),
+            'action_page_export' => $this->sActionPageExport,
+            'action_page_edit' => $this->sActionPageEdit,
+            'action_block_create' => $this->sActionBlockCreate,
+        );
+    }
+}
+
+/** @} */

+ 51 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevForms.php

@@ -0,0 +1,51 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxDevForms extends BxTemplStudioForms
+{
+    protected $oModule;
+    protected $aParams;
+    protected $aGridObjects;
+
+    function __construct($aParams)
+    {
+        parent::__construct(isset($aParams['page']) ? $aParams['page'] : '');
+        
+        $this->bPageMenuTitle = false;
+
+        $this->aParams = $aParams;
+        $this->sSubpageUrl = $this->aParams['url'] . '&form_page=';
+
+        unset(
+            $this->aMenuItems[BX_DOL_STUDIO_FORM_TYPE_LABELS],
+            $this->aMenuItems[BX_DOL_STUDIO_FORM_TYPE_CATEGORIES],
+            $this->aMenuItems[BX_DOL_STUDIO_FORM_TYPE_GROUPS_ROLES]
+        );
+
+        $this->oModule = BxDolModule::getInstance('bx_developer');
+
+        $this->aGridObjects = array(
+            'forms' => $this->oModule->_oConfig->getObject('grid_forms'),
+            'displays' => $this->oModule->_oConfig->getObject('grid_forms_displays'),
+            'fields' => $this->oModule->_oConfig->getObject('grid_forms_fields'),
+
+            'pre_lists' => $this->oModule->_oConfig->getObject('grid_forms_pre_lists'),
+            'pre_values' => $this->oModule->_oConfig->getObject('grid_forms_pre_values'),
+
+            'search_forms' => $this->oModule->_oConfig->getObject('grid_search_forms'),
+            'search_fields' => $this->oModule->_oConfig->getObject('grid_search_forms_fields'),
+        );
+
+        $this->oModule->_oTemplate->addStudioCss(array('forms.css'));
+    }
+}
+
+/** @} */

+ 515 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevModule.php

@@ -0,0 +1,515 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once('BxDevFunctions.php');
+
+define('BX_DEV_TOOLS_SETTINGS', 'settings');
+define('BX_DEV_TOOLS_FORMS', 'forms');
+define('BX_DEV_TOOLS_PAGES', 'pages');
+define('BX_DEV_TOOLS_NAVIGATION', 'navigation');
+define('BX_DEV_TOOLS_POLYGLOT', 'polyglot');
+define('BX_DEV_TOOLS_PERMISSIONS', 'permissions');
+
+class BxDevModule extends BxDolModule
+{
+    public $aTools;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->aTools = array(
+            array('name' => BX_DEV_TOOLS_SETTINGS, 'title' => '_adm_page_cpt_settings', 'icon' => 'bx-dev-mi-settings.svg'),
+            array('name' => BX_DEV_TOOLS_POLYGLOT, 'title' => '_adm_page_cpt_polyglot', 'icon' => 'bx-dev-mi-polyglot.svg'),
+            array('name' => BX_DEV_TOOLS_FORMS, 'title' => '_adm_page_cpt_builder_forms', 'icon' => 'bx-dev-mi-forms.svg'),
+            array('name' => BX_DEV_TOOLS_NAVIGATION, 'title' => '_adm_page_cpt_builder_menus', 'icon' => 'bx-dev-mi-navigation.svg'),
+            array('name' => BX_DEV_TOOLS_PAGES, 'title' => '_adm_page_cpt_builder_pages', 'icon' => 'bx-dev-mi-pages.svg'),
+            //array('name' => BX_DEV_TOOLS_PERMISSIONS, 'title' => '_adm_page_cpt_builder_permissions', 'icon' => 'bx-dev-mi-permissions.png'),
+        );
+    }
+
+    function getToolsList()
+    {
+        return $this->aTools;
+    }
+
+    public function actionResetHash($sType, $sModule = '')
+    {
+    	$oDb = bx_instance('BxDolStudioInstallerQuery');
+
+    	$sResult = '';
+    	switch ($sType) {
+    		case 'system':
+                    $oHasher = bx_instance('BxDolInstallerHasher');
+
+                    $oDb->deleteModuleTrackFiles(BX_SYSTEM_MODULE_ID);
+
+                    $sResult = _t('_bx_dev_hash_' . ($oHasher->hashSystemFiles() ? 'msg' : 'err') . '_reset_hash_system');    			
+                    break;
+
+    		case 'modules':
+                    $oModuleQuery = BxDolModuleQuery::getInstance();
+                    if(!empty($sModule)) {
+                        $aNames = strpos($sModule, ',') !== false ? explode(',', $sModule) : array($sModule);
+
+                        $aModule = array();
+                        foreach($aNames as $aName)
+                            $aModules[] = $oModuleQuery->getModuleByName($aName);
+                    }
+                    else
+                        $aModules = $oModuleQuery->getModules();
+
+                    $aTmplVarsModules = array();
+                    if(!empty($aModules) && is_array($aModules))
+                        foreach($aModules as $aModule) {
+                            if($aModule['name'] == 'system')
+                                continue;
+
+                            $aConfig = BxDolInstallerUtils::getModuleConfig($aModule);
+                            $sPathInstaller = BX_DIRECTORY_PATH_MODULES . $aModule['path'] . 'install/installer.php';
+                            if(empty($aConfig) || !file_exists($sPathInstaller))
+                                continue;
+
+                            require_once($sPathInstaller);
+
+                            $sClassName = $aConfig['class_prefix'] . 'Installer';
+                            $oInstaller = new $sClassName($aConfig);
+
+                            $oDb->deleteModuleTrackFiles($aModule['id']);
+
+                            $aFiles = array();
+                            $oInstaller->hashFiles(BX_DIRECTORY_PATH_ROOT . 'modules/' . $aModule['path'], $aFiles);
+                            foreach($aFiles as $aFile)
+                                $oDb->insertModuleTrack($aModule['id'], $aFile);
+
+                            $aTmplVarsModules[] = array(
+                                'module' => $aModule['title'],
+                                'files' => count($aFiles)
+                            );
+                        }
+
+                        $sResult = $this->_oTemplate->parseHtmlByName('hash_modules.html', array(
+                            'bx_repeat:modules' => $aTmplVarsModules
+                        ));
+                        break;
+    	}
+
+    	echo $sResult;
+    	exit;
+    }
+    
+    public function actionExport()
+    {
+        $sType = bx_process_input(bx_get('type'));
+        if(empty($sType))
+            return echoJson([]);
+
+        echoJson([
+            'url' => BX_DOL_URL_ROOT . bx_append_url_params($this->_oConfig->getBaseUri() . 'download', [
+                'type' => $sType,
+            ]),
+            'eval' => $this->_oConfig->getJsObject('main') . '.onExport(oData);'
+        ]);
+    }
+
+    public function actionDownload()
+    {
+        $sType = bx_process_input(bx_get('type'));
+        if(empty($sType))
+            return;
+
+        $sMethod = '_eiExport' . bx_gen_method_name($sType);
+        if(!method_exists($this, $sMethod))
+            return;
+
+        $mixedFull = ($mixedFull = bx_get('full')) !== false ? (int)$mixedFull != 0 : false;
+
+        list($aMeta, $aData) = $this->$sMethod($mixedFull);
+
+        $iNow = time();
+    	$sContent = json_encode([
+            'meta' => array_merge([
+                'version' => bx_get_ver(),
+                'type' => $sType,
+                'full' => $mixedFull,
+                'date' => date('D, d M Y H:i:s', $iNow),
+            ], $aMeta),
+            'data' => $aData
+    	]);
+
+    	header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
+        header("Content-type: application/json");
+        header("Content-Length: " . strlen($sContent));
+        header("Content-Disposition: attachment; filename=\"" . $sType . ($mixedFull ? "_f" : "") . "_". date('d_m_Y', $iNow) . ".json\"");
+
+        echo $sContent;
+        exit;
+    }
+    
+    public function actionImport()
+    {
+        $sType = bx_process_input(bx_get('type'));
+        if(empty($sType))
+            return echoJson([]);
+
+        $aResult = $this->getPopupCodeImport($sType, [
+            'form_name' => 'bx-dev-' . $sType . '-import-full',
+            'form_action' => BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'import?type=' . $sType
+        ]);
+
+        if(!isset($aResult['code']) || (int)$aResult['code'] != 0)
+            return echoJson($aResult);
+        
+        $sMethod = '_eiImport' . bx_gen_method_name($sType);
+        if(!method_exists($this, $sMethod))
+            return echoJson([]);
+
+        echoJson($this->$sMethod($aResult['content'], $aResult['disable'] != 0));
+    }
+
+    public function getPopupCodeImport($sType, $aParams)
+    {
+    	$sJsObject = $this->_oConfig->getJsObject('main');
+
+    	$sForm = $aParams['form_name'];
+    	$aForm = [
+            'form_attrs' => [
+                'id' => $sForm,
+                'name' => $sForm,
+                'action' => $aParams['form_action'],
+                'method' => 'post',
+                'enctype' => 'multipart/form-data'
+            ],
+            'params' => [
+                'db' => [
+                    'submit_name' => 'save'
+                ],
+            ],
+            'inputs' => [
+            	'file' => [
+                    'type' => 'file',
+                    'name' => 'file',
+                    'caption' => '',
+                    'value' => '',
+                ],
+                'disable' => [
+                    'type' => 'switcher',
+                    'name' => 'disable',
+                    'caption' => _t('_bx_dev_txt_disable_before_import'),
+                    'info' => _t('_bx_dev_txt_disable_before_import_note'),
+                    'value' => '1',
+                    'checked' => 0,
+                    'db' => [
+                        'pass' => 'Int',
+                    ],
+                ],
+                'controls' => [
+                    'type' => 'input_set', [
+                        'type' => 'submit',
+                        'name' => 'save',
+                        'value' => _t('_bx_dev_btn_import_full'),
+                    ], [
+                        'type' => 'button',
+                        'name' => 'cancel',
+                        'value' => _t('_bx_dev_btn_cancel'),
+                        'attrs' => [
+                            'class' => 'bx-def-margin-sec-left-auto',
+                            'onclick' => '$(".bx-popup-applied:visible").dolPopupHide()'
+                        ]
+                    ]
+                ]
+            ]
+        ];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $sError = _t('_bx_dev_err_cannot_perform');
+            $sErrorWf = _t('_bx_dev_err_import_wrong_format');
+
+            if(empty($_FILES['file']) || empty($_FILES['file']['tmp_name']))
+                return ['code' => 1, 'msg' => $sError];
+
+            $aFile = $_FILES['file'];
+            $sFile = $aFile['tmp_name'];
+            $rHandle = @fopen($sFile, "r");
+            if(!$rHandle)
+                return ['code' => 2, 'message' => $sError];
+
+            $sContents = fread($rHandle, filesize($sFile));
+            fclose($rHandle);
+
+            $aContent = json_decode($sContents, true);
+            if(!is_array($aContent) || empty($aContent['meta']) || empty($aContent['data']))
+                return ['code' => 3, 'message' => $sErrorWf];
+
+            if(empty($aContent['meta']['type']) || strcmp($aContent['meta']['type'], $sType) != 0)
+                return ['code' => 4, 'message' => $sErrorWf];
+
+            return ['code' => 0, 'content' => $aContent, 'disable' => $oForm->getCleanValue('disable')];
+        }
+
+        return [
+            'popup' => BxTemplStudioFunctions::getInstance()->popupBox('bx-dev-import-popup', _t('_bx_dev_txt_import_full_popup'), $this->_oTemplate->parseHtmlByName('import_popup.html', [
+                'js_object' => $sJsObject,
+                'form_id' => $sForm,
+                'form' => $oForm->getCode(true),
+            ]))
+        ];
+    }
+
+    public function isValidJson($s)
+    {
+        //--- Validation is disabled for now.         
+        //--- return json_decode($s, true) !== null;
+
+        return true;
+    }
+
+    /**
+     * 
+     * Methods for Export/Import feature.
+     * 
+     */
+    protected function _eiExportForms($bModeFull = false)
+    {
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->getObject('grid_forms'));
+        if(!$oGrid)
+            return [];
+
+        $aForms = [];
+        $oGrid->oDb->getForms(['type' => 'export'], $aForms, false);
+        if(empty($aForms) || !is_array($aForms))
+            return [];
+
+        $aMeta = [
+            'masks' => [
+                'form' => ['object', 'active'],
+                'display' => ['display_name'],
+                'input' => ['object', 'name', 'privacy', 'checker_func', 'checker_params', 'checker_error', 'db_pass', 'db_params'],
+                'display_input' => ['display_name', 'input_name', 'visible_for_levels', 'active', 'order']
+            ]
+        ];
+
+        if($bModeFull) {
+            $aMeta['masks'] = [
+                'form' => ['object', 'module', 'title', 'action', 'form_attrs', 'submit_name', 'table', 'key', 'uri', 'uri_title', 'params', 'deletable', 'active', 'override_class_name', 'override_class_file'],
+                'display' => ['display_name', 'module', 'object', 'title', 'view_mode'],
+                'input' => ['object', 'module', 'name', 'value', 'values', 'checked', 'type', 'caption_system', 'caption', 'info', 'help', 'icon', 'required', 'unique', 'collapsed', 'html', 'privacy', 'rateable', 'attrs', 'attrs_tr', 'attrs_wrapper', 'checker_func', 'checker_params', 'checker_error', 'db_pass', 'db_params', 'editable', 'deletable'],
+                'display_input' => ['display_name', 'input_name', 'visible_for_levels', 'active', 'order']
+            ];
+        }
+
+        $aMfForm = $aMfDisplay = $aMfInput = $aMfDisplayInput = false;
+        foreach($aMeta['masks'] as $sMask => $aMask)
+            ${'aMf' . bx_gen_method_name($sMask)} = array_flip($aMask);
+
+        $aData = [];
+        foreach($aForms as $aForm) {
+            $sObject = $aForm['object'];
+
+            $aDisplays = [];
+            $oGrid->oDb->getDisplays(['type' => 'export_by_object', 'value' => $sObject], $aDisplays, false);
+            
+            $aListDisplays = [];
+            $aDataDisplays = [];
+            if(!empty($aDisplays) && is_array($aDisplays))
+                foreach($aDisplays as $aDisplay) {
+                    $aListDisplays[] = $aDisplay['display_name'];
+                    $aDataDisplays[] = $aMfDisplay !== false ? array_intersect_key($aDisplay, $aMfDisplay) : $aDisplay;
+                }
+
+            $aInputs = [];
+            $oGrid->oDb->getInputs(['type' => 'dump_inputs', 'value' => $sObject], $aInputs, false);
+
+            $aDataInputs = [];
+            if(!empty($aInputs) && is_array($aInputs))
+                foreach($aInputs as $aInput)
+                    $aDataInputs[] = $aMfInput !== false ? array_intersect_key($aInput, $aMfInput) : $aInput;
+
+            $aDisplayInputs = [];
+            $oGrid->oDb->getInputs(['type' => 'dump_display_inputs', 'displays' => $aListDisplays], $aDisplayInputs, false);
+
+            $aDataDisplayInputs = [];
+            if(!empty($aDisplayInputs) && is_array($aDisplayInputs))
+                foreach($aDisplayInputs as $aDisplayInput)
+                    $aDataDisplayInputs[] = $aMfDisplayInput !== false ? array_intersect_key($aDisplayInput, $aMfDisplayInput) : $aDisplayInput;
+
+            $aData[] = [
+                'form' => $aMfForm !== false ? array_intersect_key($aForm, $aMfForm) : $aForm,
+                'displays' => $aDataDisplays,
+                'inputs' => $aDataInputs,
+                'display_inputs' => $aDataDisplayInputs
+            ];
+        }
+
+        return [$aMeta, $aData];
+    }
+
+    protected function _eiExportMenus($bModeFull = false)
+    {
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->getObject('grid_nav_menus'));
+        if(!$oGrid)
+            return [];
+
+        $aMenus = [];
+        $oGrid->oDb->getMenus(['type' => 'export'], $aMenus, false);
+        if(empty($aMenus) || !is_array($aMenus))
+            return [];
+
+        $aMeta = [
+            'masks' => [
+                'menu' => ['object', 'active'],
+                'set' => ['set_name'],
+                'item' => ['set_name', 'name', 'visible_for_levels', 'visibility_custom', 'hidden_on', 'hidden_on_cxt', 'hidden_on_pt', 'hidden_on_col', 'active', 'active_api', 'order']
+            ]
+        ];
+
+        if($bModeFull) {
+            $aMeta['masks'] = [
+                'menu' => ['object', 'title', 'set_name', 'module', 'template_id', 'deletable', 'active', 'override_class_name', 'override_class_file'],
+                'set' => ['set_name', 'module', 'title', 'deletable'],
+                'item' => ['parent_id', 'set_name', 'module', 'name', 'title_system', 'title', 'link', 'onclick', 'target', 'icon', 'addon', 'addon_cache', 'markers', 'submenu_object', 'submenu_popup', 'visible_for_levels', 'visibility_custom', 'hidden_on', 'hidden_on_cxt', 'hidden_on_pt', 'hidden_on_col', 'primary', 'collapsed', 'active', 'active_api', 'copyable', 'editable', 'order']
+            ];
+        }
+
+        $aMfMenu = $aMfSet = $aMfItem = false;
+        foreach($aMeta['masks'] as $sMask => $aMask)
+            ${'aMf' . bx_gen_method_name($sMask)} = array_flip($aMask);
+
+        $aData = [];
+        foreach($aMenus as $aMenu) {
+            $aSet = [];
+            $oGrid->oDb->getSets(['type' => 'export_by_name', 'value' => $aMenu['set_name']], $aSet, false);
+
+            $aDataItems = [];
+            if(!empty($aSet) && is_array($aSet)) {
+                $aItems = [];
+                $oGrid->oDb->getItems(['type' => 'export_by_set_name', 'value' => $aSet['set_name']], $aItems, false);
+
+                if(!empty($aItems) && is_array($aItems))
+                    foreach($aItems as $aItem)
+                        $aDataItems[] = $aMfItem !== false ? array_intersect_key($aItem, $aMfItem) : $aItem;
+            }
+
+            $aData[] = [
+                'menu' => $aMfMenu !== false ? array_intersect_key($aMenu, $aMfMenu) : $aMenu,
+                'set' => !empty($aSet) && $aMfSet !== false ? array_intersect_key($aSet, $aMfSet) : $aSet,
+                'items' => $aDataItems,
+            ];
+        }
+
+        return [$aMeta, $aData];
+    }
+
+    protected function _eiExportPages($bModeFull = false)
+    {
+        bx_import('BuilderPage', $this->_aModule);
+        $oBuilderPage = new BxDevBuilderPage(['type' => '', 'page' => '', 'url' => '']);
+        if(!$oBuilderPage)
+            return [];
+
+        $oBpDb = $oBuilderPage->getDb();
+
+        $aPages = $oBpDb->getPages(['type' => 'export']);
+        if(empty($aPages) || !is_array($aPages))
+            return [];
+
+        $aMeta = [
+            'masks' => [
+                'page' => ['object', 'visible_for_levels'],
+                'block' => ['object', 'cell_id', 'module', 'title_system', 'title', 'visible_for_levels', 'hidden_on', 'active', 'active_api', 'order']
+            ]
+        ];
+
+        if($bModeFull) {
+            $aMeta['masks'] = [
+                'page' => ['author', 'added', 'object', 'uri', 'title_system', 'title', 'module', 'cover', 'cover_image', 'cover_title', 'type_id', 'layout_id', 'sticky_columns', 'submenu', 'visible_for_levels', 'visible_for_levels_editable', 'url', 'content_info', 'meta_description', 'meta_keywords', 'meta_robots', 'cache_lifetime', 'cache_editable', 'inj_head', 'inj_footer', 'deletable', 'override_class_name', 'override_class_file'],
+                'block' => ['object', 'cell_id', 'module', 'title_system', 'title', 'designbox_id', 'class', 'submenu', 'tabs', 'async', 'visible_for_levels', 'hidden_on', 'type', 'content', 'content_empty', 'text', 'text_updated', 'help', 'cache_lifetime', 'deletable', 'copyable', 'active', 'active_api', 'order']
+            ];
+        }
+
+        $aMfPage = $aMfBlock = false;
+        foreach($aMeta['masks'] as $sMask => $aMask)
+            ${'aMf' . bx_gen_method_name($sMask)} = array_flip($aMask);
+
+        $aData = [];
+        foreach($aPages as $aPage) {
+            $aBlocks = [];
+            $oBpDb->getBlocks(['type' => 'export_by_object', 'value' => $aPage['object']], $aBlocks, false);
+
+            $aDataBlocks = [];
+            if(!empty($aBlocks) && is_array($aBlocks))
+                foreach($aBlocks as $aBlock)
+                    $aDataBlocks[] = $aMfBlock !== false ? array_intersect_key($aBlock, $aMfBlock) : $aBlock;
+
+            $aData[] = [
+                'page' => $aMfPage !== false ? array_intersect_key($aPage, $aMfPage) : $aPage,
+                'blocks' => $aDataBlocks,
+            ];
+        }
+
+        return [$aMeta, $aData];
+    }
+
+    protected function _eiImportPages($aContent, $bDisable = false)
+    {
+        bx_import('BuilderPage', $this->_aModule);
+        $oBuilderPage = new BxDevBuilderPage(['type' => '', 'page' => '', 'url' => '']);
+        if(!$oBuilderPage)
+            return [];
+
+        $oBpDb = $oBuilderPage->getDb();
+
+        $bModeFull = isset($aContent['meta']['full']) && (bool)$aContent['meta']['full'] === true;
+
+        $aMfMenu = $aMfSet = $aMfItem = false;
+        foreach($aContent['meta']['masks'] as $sMask => $aMask)
+            ${'aMf' . bx_gen_method_name($sMask)} = array_flip($aMask);
+
+        if($bDisable)
+            $oBpDb->updateBlocks(['active' => 0, 'active_api' => 0]);
+
+        $iData = 0;
+        foreach($aContent['data'] as $aData) {
+            $iData += 1;
+
+            $sObject = $aData['page']['object'];
+            if($bModeFull && !$oBpDb->isPageExists($sObject))
+                $oBpDb->addPage($aData['page']);
+            else
+                $oBpDb->updatePageByObject($sObject, $aData['page']);
+
+            foreach($aData['blocks'] as $aBlock) {
+                $aFields = [
+                    'object' => $aBlock['object'],
+                    'module' => $aBlock['module'], 
+                    'title_system' => $aBlock['title_system'], 
+                    'title' => $aBlock['title']
+                ];
+
+                if($bModeFull && !$oBpDb->isBlockExists($aFields))
+                    $oBpDb->addBlock($aItem);
+                else
+                    $oBpDb->updateBlockByFields($aFields, $aBlock);
+            }
+        }
+
+        BxDolCacheUtilities::getInstance()->clear('db');
+
+        return [
+            'msg' => _t('_bx_dev_msg_imported', $iData), 
+            'eval' => $this->_oConfig->getJsObject('main') . '.onImport(oData);'
+        ];
+    }
+}
+/** @} */

+ 39 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevNavigation.php

@@ -0,0 +1,39 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxDevNavigation extends BxTemplStudioNavigation
+{
+    protected $oModule;
+    protected $aParams;
+    protected $aGridObjects;
+
+    function __construct($aParams)
+    {
+        parent::__construct(isset($aParams['page']) ? $aParams['page'] : '');
+
+        $this->bPageMenuTitle = false;
+
+        $this->aParams = $aParams;
+        $this->sSubpageUrl = $this->aParams['url'] . '&nav_page=';
+
+        $this->oModule = BxDolModule::getInstance('bx_developer');
+
+        $this->aGridObjects = array(
+	        'menus' => $this->oModule->_oConfig->getObject('grid_nav_menus'),
+	        'sets' => $this->oModule->_oConfig->getObject('grid_nav_sets'),
+	        'items' => $this->oModule->_oConfig->getObject('grid_nav_items')
+	    );
+
+        $this->oModule->_oTemplate->addStudioCss(array('navigation.css'));
+    }
+}
+
+/** @} */

+ 182 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevNavigationItems.php

@@ -0,0 +1,182 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxDevNavigationItems extends BxTemplStudioNavigationItems
+{
+    protected $oModule;
+
+    function __construct($aOptions, $oTemplate = false)
+    {
+        parent::__construct($aOptions, $oTemplate);
+
+        $this->oModule = BxDolModule::getInstance('bx_developer');
+        $this->sUrlPage = BX_DOL_URL_STUDIO . 'module.php?name=' . $this->oModule->_oConfig->getName() . '&page=navigation&nav_page=items';
+        $this->sUrlViewItems = $this->sUrlPage . '&nav_module=%s&nav_set=%s';
+
+        $sModule = bx_get('nav_module');
+        if(!empty($sModule)) {
+            $this->sModule = bx_process_input($sModule);
+            $this->_aQueryAppend['module'] = $this->sModule;
+        }
+
+        $sSet = bx_get('nav_set');
+        if(!empty($sSet)) {
+            $this->sSet = bx_process_input($sSet);
+            $this->_aQueryAppend['set'] = $this->sSet;
+        }
+    }
+
+    public function performActionAdd()
+    {
+        $sAction = 'add';
+        $sFormObject = $this->oModule->_oConfig->getObject('form_nav_item');
+        $sFormDisplay = $this->oModule->_oConfig->getObject('form_display_nav_item_add');
+
+        $oForm = BxDolForm::getObjectInstance($sFormObject, $sFormDisplay, $this->oModule->_oTemplate);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction . '&set=' . $this->sSet;
+        $this->fillInSelects($oForm->aInputs);
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $this->onSave($oForm);
+
+            if(($iId = (int)$oForm->insert()) != 0)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+            else
+                $aRes = array('msg' => _t('_bx_dev_nav_err_items_create'));
+
+            echoJson($aRes);
+        } else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-dev-nav-item-create-popup', _t('_bx_dev_nav_txt_items_create_popup'), $this->_oTemplate->parseHtmlByName('nav_add_item.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    public function performActionEdit($bUpdateGrid = false)
+    {
+        $sAction = 'edit';
+        $sFormObject = $this->oModule->_oConfig->getObject('form_nav_item');
+        $sFormDisplay = $this->oModule->_oConfig->getObject('form_display_nav_item_edit');
+
+        $aItem = $this->_getItem('getItems');
+        if($aItem === false) {
+            echoJson(array());
+            exit;
+        }
+
+        $this->_prepareServiceEdit('addon', $aItem);
+        $this->_prepareServiceEdit('visibility_custom', $aItem);
+
+        $oForm = BxDolForm::getObjectInstance($sFormObject, $sFormDisplay, $this->oModule->_oTemplate);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction . '&set=' . $this->sSet;
+        $oForm->aInputs['controls'][0]['value'] = _t('_bx_dev_nav_btn_items_save');
+        $this->fillInSelects($oForm->aInputs);
+
+        $oForm->initChecker($aItem);
+        if($oForm->isSubmittedAndValid()) {
+            $this->_prepareServiceSave('addon', $oForm);
+            $this->_prepareServiceSave('visibility_custom', $oForm);
+
+            $this->onSave($oForm, $aItem);
+
+            if($oForm->update($aItem['id']) !== false)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $aItem['id']);
+            else
+                $aRes = array('msg' => _t('_bx_dev_nav_err_items_edit'));
+
+            echoJson($aRes);
+        } else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-dev-nav-item-edit-popup', _t('_bx_dev_nav_txt_items_edit_popup', _t($aItem['title_system'])), $this->oModule->_oTemplate->parseHtmlByName('nav_add_item.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    protected function _prepareServiceEdit($sField, &$aItem)
+    {
+        if(empty($aItem[$sField]))
+            return;
+
+        $aItem[$sField] = BxDevFunctions::unserializeString($aItem[$sField]);
+    }
+
+    protected function _prepareServiceSave($sField, &$oForm)
+    {
+        $sValue = $oForm->getCleanValue($sField);
+        $sValue = BxDevFunctions::serializeString($sValue);
+        BxDolForm::setSubmittedValue($sField, $sValue, $oForm->aFormAttrs['method']);
+    }
+
+    protected function _getActionDelete ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _isEditable(&$aRow)
+    {
+    	return true;
+    }
+
+    protected function _isDeletable(&$aRow)
+    {
+    	return true;
+    }
+
+    protected function onSave(&$oForm, &$aItem) 
+    {
+        if(($sConfigApi = $oForm->getCleanValue('config_api')) && !$this->oModule->isValidJson($sConfigApi))
+            BxDolForm::setSubmittedValue('config_api', '', $oForm->aFormAttrs['method']);
+    }
+
+    private function fillInSelects(&$aInputs)
+    {
+        $aInputs['module']['values'] = array_merge(array('' => _t('_bx_dev_nav_txt_select_module')), BxDolStudioUtils::getModules());
+        $aInputs['module']['value'] = $this->sModule;
+
+        $aInputs['set_name']['value'] = $this->sSet;
+
+        $aInputs['hidden_on']['values'] = array(
+            BX_DB_HIDDEN_PHONE => _t('_bx_dev_nav_txt_sys_items_hidden_on_phone'),
+            BX_DB_HIDDEN_TABLET => _t('_bx_dev_nav_txt_sys_items_hidden_on_tablet'),
+            BX_DB_HIDDEN_DESKTOP => _t('_bx_dev_nav_txt_sys_items_hidden_on_desktop'),
+            BX_DB_HIDDEN_MOBILE => _t('_bx_dev_nav_txt_sys_items_hidden_on_mobile')
+        );
+
+        $aInputs['hidden_on_pt']['values'] = [];
+        $aPageTypes = BxDolPageQuery::getPageTypes();
+        foreach($aPageTypes as $aPageType) {
+            $iPageType = (int)$aPageType['id'];
+            if($iPageType == 1)
+                continue;
+
+            $aInputs['hidden_on_pt']['values'][$iPageType - 1] = _t($aPageType['title']);
+        }
+
+        $aInputs['hidden_on_col']['values'] = [
+            1 => _t('_adm_nav_txt_block_hidden_on_col_thin'),
+            2 => _t('_adm_nav_txt_block_hidden_on_col_half'),
+            3 => _t('_adm_nav_txt_block_hidden_on_col_wide'),
+            4 => _t('_adm_nav_txt_block_hidden_on_col_full')
+        ];
+    }
+}
+/** @} */

+ 257 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevNavigationMenus.php

@@ -0,0 +1,257 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxDevNavigationMenus extends BxTemplStudioNavigationMenus
+{
+    protected $oModule;
+
+    function __construct($aOptions, $oTemplate = false)
+    {
+        parent::__construct($aOptions, $oTemplate);
+
+        $this->_aOptions['actions_single']['export']['attr']['title'] = _t('_bx_dev_nav_btn_menus_gl_export');
+
+        $this->oModule = BxDolModule::getInstance('bx_developer');
+        $this->sUrlViewItems = BX_DOL_URL_STUDIO . 'module.php?name=' . $this->oModule->_oConfig->getName() . '&page=navigation&nav_page=items&nav_module=%s&nav_set=%s';
+    }
+
+    public function getCode($isDisplayHeader = true)
+    {
+        return $this->oModule->_oTemplate->getJsCode('main') . parent::getCode($isDisplayHeader);
+    }
+
+    public function performActionAdd()
+    {
+        $sAction = 'add';
+        $sFormObject = $this->oModule->_oConfig->getObject('form_nav_menu');
+        $sFormDisplay = $this->oModule->_oConfig->getObject('form_display_nav_menu_add');
+
+        $oForm = BxDolForm::getObjectInstance($sFormObject, $sFormDisplay, $this->oModule->_oTemplate);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction;
+        $this->fillInSelects($oForm->aInputs);
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $sObject = uriGenerate($oForm->getCleanValue('object'), 'sys_objects_menu', 'object', ['empty' => 'object']);
+            BxDolForm::setSubmittedValue('object', $sObject, $oForm->aFormAttrs['method']);
+
+            $this->onSave($oForm);
+
+            if(($iId = (int)$oForm->insert()) != 0)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+            else
+                $aRes = array('msg' => _t('_bx_dev_nav_err_menus_create'));
+
+            echoJson($aRes);
+        } else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-dev-nav-menu-create-popup', _t('_bx_dev_nav_txt_menus_create_popup'), $this->oModule->_oTemplate->parseHtmlByName('nav_add_menu.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    public function performActionEdit()
+    {
+        $sAction = 'edit';
+        $sFormObject = $this->oModule->_oConfig->getObject('form_nav_menu');
+        $sFormDisplay = $this->oModule->_oConfig->getObject('form_display_nav_menu_edit');
+
+        $aMenu = $this->_getItem('getMenus');
+        if($aMenu === false) {
+            echoJson(array());
+            exit;
+        }
+
+        $oForm = BxDolForm::getObjectInstance($sFormObject, $sFormDisplay, $this->oModule->_oTemplate);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction;
+        $oForm->aInputs['controls'][0]['value'] = _t('_bx_dev_nav_btn_menus_save');
+        $this->fillInSelects($oForm->aInputs);
+
+        $oForm->initChecker($aMenu);
+        if($oForm->isSubmittedAndValid()) {
+            $this->onSave($oForm, $aMenu);
+
+            if($oForm->update($aMenu['id']) !== false)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $aMenu['id']);
+            else
+                $aRes = array('msg' => _t('_bx_dev_nav_err_menus_edit'));
+
+            echoJson($aRes);
+        } else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-dev-nav-menu-edit-popup', _t('_bx_dev_nav_txt_menus_edit_popup', _t($aMenu['title'])), $this->oModule->_oTemplate->parseHtmlByName('nav_add_menu.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    public function performActionExport()
+    {
+        $sContentInsert = $sContentDelete = "";
+
+        $aMenu = $this->_getItem('getMenus');
+        if($aMenu === false) {
+            echoJson(array());
+            exit;
+        }
+
+        $sContentInsert .= $this->oModule->_oDb->getQueryInsert('sys_objects_menu', array($aMenu), "Dumping data for '" . $aMenu['object'] . "' menu");
+        $sContentDelete .= $this->oModule->_oDb->getQueryDelete('sys_objects_menu', 'object', array($aMenu), "Deleting data for '" . $aMenu['object'] . "' menu");
+
+        $aSet = array();
+        $this->oDb->getSets(array('type' => 'dump_by_name', 'value' => $aMenu['set_name']), $aSet, false);
+        $sContentInsert .= $this->oModule->_oDb->getQueryInsert('sys_menu_sets', array($aSet), false, array('name'));
+        $sContentDelete .= $this->oModule->_oDb->getQueryDelete('sys_menu_sets', 'set_name', array($aSet), false);
+
+        $aItems = array();
+        $this->oDb->getItems(array('type' => 'by_set_name', 'value' => $aMenu['set_name']), $aItems, false);
+        $sContentInsert .= $this->oModule->_oDb->getQueryInsert('sys_menu_items', $aItems);
+        $sContentDelete .= $this->oModule->_oDb->getQueryDelete('sys_menu_items', 'set_name', array($aSet), false);
+
+        $aFormStructure = array(
+            'form_attrs' => array(),
+            'inputs' => array (
+                'insert' => array(
+                    'type' => 'textarea',
+                    'name' => 'insert',
+                    'caption' => _t('_bx_dev_nav_txt_menus_export_insert'),
+                    'value' => $sContentInsert,
+                ),
+                'delete' => array(
+                    'type' => 'textarea',
+                    'name' => 'delete',
+                    'caption' => _t('_bx_dev_nav_txt_menus_export_delete'),
+                    'value' => $sContentDelete,
+                ),
+                'done' => array (
+                    'type' => 'button',
+                    'name' => 'done',
+                    'value' => _t('_bx_dev_nav_btn_menus_done'),
+                    'attrs' => array(
+                        'onclick' => "$('.bx-popup-applied:visible').dolPopupHide()",
+                    ),
+                )
+            )
+        );
+        $oForm = new BxTemplStudioFormView($aFormStructure);
+
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox('bx-dev-nav-menu-export-popup', _t('_bx_dev_nav_txt_menus_export_popup', _t($aMenu['title'])), $this->oModule->_oTemplate->parseHtmlByName('nav_export.html', array(
+            'content' => $oForm->getCode()
+        )));
+
+        echoJson(array('popup' => $sContent));
+    }
+
+    public function performActionExportFull()
+    {
+        echoJson([
+            'url' => BX_DOL_URL_ROOT . bx_append_url_params($this->oModule->_oConfig->getBaseUri() . 'download', [
+                'type' => 'menus',
+            ]),
+            'eval' => $this->oModule->_oConfig->getJsObject('main') . '.onExport(oData);'
+        ]);
+    }
+    
+    public function performActionImportFull()
+    {
+        $sAction = 'import_full';
+
+        $aResult = $this->oModule->getPopupCodeImport('menus', [
+            'form_name' => 'bx-dev-menus-import-full',
+            'form_action' => BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction
+        ]);
+        
+        if(!isset($aResult['code']) || (int)$aResult['code'] != 0)
+            return echoJson($aResult);
+
+        $aContent = $aResult['content'];
+        $bModeFull = isset($aContent['meta']['full']) && (bool)$aContent['meta']['full'] === true;
+
+        $aMfMenu = $aMfSet = $aMfItem = false;
+        foreach($aContent['meta']['masks'] as $sMask => $aMask)
+            ${'aMf' . bx_gen_method_name($sMask)} = array_flip($aMask);
+
+        if($aResult['disable'] != 0) {
+            $this->oDb->updateMenus(['active' => 0]);
+            $this->oDb->updateItems(['active' => 0, 'active_api' => 0]);
+        }
+            
+        $iData = 0;
+        foreach($aContent['data'] as $aData) {
+            $iData += 1;
+
+            $sObject = $aData['menu']['object'];
+            if($bModeFull && !$this->oDb->isMenuExists($sObject))
+                $this->oDb->addMenu($aData['menu']);
+            else
+                $this->oDb->updateMenuByObject($sObject, $aData['menu']);
+
+            $aSet = $aData['set'];
+            if(empty($aSet) || !is_array($aSet))
+                continue;
+
+            $sSetName = $aSet['set_name'];
+            if($bModeFull && !$this->oDb->isSetExists($sSetName))
+                $this->oDb->addSet($aSet);
+
+            foreach($aData['items'] as $aItem)
+                if($bModeFull && !$this->oDb->isItemExists($sSetName,  $aItem['name']))
+                    $this->oDb->addItem($aItem);
+                else
+                    $this->oDb->updateItemBySetAndName($sSetName, $aItem['name'], $aItem);
+        }
+
+        BxDolCacheUtilities::getInstance()->clear('db');
+
+        echoJson([
+            'msg' => _t('_bx_dev_msg_imported', $iData), 
+            'eval' => $this->oModule->_oConfig->getJsObject('main') . '.onImport(oData);'
+        ]);
+    }
+
+    protected function onSave(&$oForm, &$aMenu) 
+    {
+        if(($sConfigApi = $oForm->getCleanValue('config_api')) && !$this->oModule->isValidJson($sConfigApi))
+            BxDolForm::setSubmittedValue('config_api', '', $oForm->aFormAttrs['method']);
+    }
+
+    private function fillInSelects(&$aInputs)
+    {
+        $aInputs['module']['values'] = array_merge(array('' => _t('_bx_dev_nav_txt_select_module')), BxDolStudioUtils::getModules());
+        $aInputs['set_name']['values'] = array(
+            array('key' => '', 'value' => _t('_bx_dev_nav_txt_menus_set_name_select'))
+        );
+
+        $aSets = array();
+        $this->oDb->getSets(array('type' => 'all'), $aSets, false);
+        foreach($aSets as $sSet)
+            $aInputs['set_name']['values'][] = array('key' => $sSet['name'], 'value' => _t($sSet['title']));
+
+        $aInputs['template_id']['values'] = array(
+            array('key' => '', 'value' => _t('_bx_dev_nav_txt_menus_template_id_select'))
+        );
+
+        $aTemplates = array();
+        $this->oDb->getTemplates(array('type' => 'all'), $aTemplates, false);
+        foreach($aTemplates as $aTemplate)
+            $aInputs['template_id']['values'][] = array('key' => $aTemplate['id'], 'value' => _t($aTemplate['title']));
+    }
+}
+/** @} */

+ 77 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevPolyglot.php

@@ -0,0 +1,77 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxDevPolyglot extends BxTemplStudioPolyglot
+{
+    protected $oModule;
+    protected $aParams;
+    protected $aMenuItems;
+    protected $aGridObjects;
+
+    function __construct($aParams)
+    {
+        parent::__construct(isset($aParams['page']) ? $aParams['page'] : '');
+
+        $this->bPageMenuTitle = false;
+
+        $this->aParams = $aParams;
+        $this->sSubpageUrl = $this->aParams['url'] . '&pgt_page=';
+
+        $this->oModule = BxDolModule::getInstance('bx_developer');
+
+        $this->aMenuItems = false;
+
+        $this->aGridObjects = [
+            'manage' => $this->oModule->_oConfig->getObject('grid_pgt_manage'),
+        ];
+    }
+
+    function getPageCss()
+    {
+        $this->oModule->_oTemplate->addStudioCss(array('polyglot.css'));
+        return parent::getPageCss();
+    }
+
+    function getPageJs()
+    {
+        $this->oModule->_oTemplate->addStudioJs(array('polyglot.js'));
+        return parent::getPageJs();
+    }
+
+    function getPageJsClass()
+    {
+        return $this->oModule->_oConfig->getJsClass('polyglot');
+    }
+
+    function getPageJsObject()
+    {
+        return $this->oModule->_oConfig->getJsObject('polyglot');
+    }
+
+    function getPageJsCode($aOptions = array(), $bWrap = true)
+    {
+        $aOptions = array_merge($aOptions, array(
+            'sActionUrl' => BX_DOL_URL_ROOT . $this->oModule->_oConfig->getBaseUri(),
+            'sPolyglotUrl' => BX_DOL_URL_STUDIO . 'polyglot.php',
+            'sObjName' => $this->getPageJsObject(),
+            'sAnimationEffect' => $this->oModule->_oConfig->getAnimationEffect(),
+            'iAnimationSpeed' => $this->oModule->_oConfig->getAnimationSpeed()
+        ));
+
+        return parent::getPageJsCode($aOptions, $bWrap);
+    }
+
+    protected function getManager()
+    {
+        return $this->getGrid($this->aGridObjects['manage']);
+    }
+}
+/** @} */

+ 172 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/classes/BxDevStudioPage.php

@@ -0,0 +1,172 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxDevStudioPage extends BxTemplStudioModule
+{
+    protected $oModule;
+    protected $sUrl;
+
+    function __construct($sModule, $mixedPageName, $sPage = "")
+    {
+        parent::__construct($sModule, $mixedPageName, $sPage);
+
+        $this->aPageCodeNoWrap = array('polyglot', 'forms', 'navigation', 'pages');
+
+        $this->oModule = BxDolModule::getInstance($this->sModule);
+
+        $this->sUrl = BX_DOL_URL_STUDIO . 'module.php?name=%s&page=%s';
+    }
+
+    function getPageMenu($aMenu = [], $aMarkers = [])
+    {
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sActions = $this->getPageCaptionActions();
+        $oTemplate->addInjection('injection_header', 'text', BxTemplStudioFunctions::getInstance()->transBox('bx-std-pmenu-popup-actions', $sActions, true));
+
+        $this->aMenuItems = [];
+        foreach($this->oModule->aTools as $aTool)
+            $this->aMenuItems[] = [
+                'name' => $aTool['name'],
+                'icon' => $aTool['icon'],
+                'link' => sprintf($this->sUrl, $this->sModule, $aTool['name']),
+                'title' => $aTool['title'],
+                'selected' => $aTool['name'] == $this->sPage
+            ];
+
+        $oMenu = new BxTemplStudioMenu(['template' => 'menu_main_dev.html', 'menu_items' => $this->aMenuItems], $this->oModule->_oTemplate);
+
+        return $this->oModule->_oTemplate->parseHtmlByName('page_menu.html', [
+            'actions_onclick' => BX_DOL_STUDIO_PAGE_JS_OBJECT . ".togglePopup('actions', this)",
+            'actions_icon' => $oTemplate->getIconUrl('mi-cog.svg'),
+            'menu' => $oMenu->getCode()
+        ]);
+    }
+
+    protected function getSettings()
+    {
+        $oOptions = new BxTemplStudioOptions('system', 'hidden');
+        $oOptions->enableManage(true);
+
+        if(($mixedResult = $oOptions->checkAction()) !== false) {
+            echoJson($mixedResult);
+            exit;
+        }
+
+        $this->aPageCss = array_merge($this->aPageCss, $oOptions->getCss());
+        $this->aPageJs = array_merge($this->aPageJs, $oOptions->getJs());
+        return $this->oModule->_oTemplate->displayPageSettings($this->sPage, $oOptions);
+    }
+
+    protected function getForms()
+    {
+        $sPage = bx_get('form_page');
+        $sPage = $sPage !== false ? bx_process_input($sPage) : '';
+
+        bx_import('Forms', $this->aModule);
+        $oContent = new BxDevForms(array(
+            'page' => $sPage,
+            'url' => sprintf($this->sUrl, $this->sModule, BX_DEV_TOOLS_FORMS),
+        ));
+
+        if(($mixedResult = $oContent->checkAction()) !== false) {
+            echoJson($mixedResult);
+            exit;
+        }
+
+        return $this->oModule->_oTemplate->displayPageContent($this->sPage, $oContent);
+    }
+
+    protected function getNavigation()
+    {
+        $sPage = bx_get('nav_page');
+        $sPage = $sPage !== false ? bx_process_input($sPage) : '';
+
+        bx_import('Navigation', $this->aModule);
+        $oContent = new BxDevNavigation(array(
+            'page' => $sPage,
+            'url' => sprintf($this->sUrl, $this->sModule, BX_DEV_TOOLS_NAVIGATION),
+        ));
+
+        if(($mixedResult = $oContent->checkAction()) !== false) {
+            echoJson($mixedResult);
+            exit;
+        }
+
+        return $this->oModule->_oTemplate->displayPageContent($this->sPage, $oContent);
+    }
+
+    protected function getPages()
+    {
+        $sType = bx_get('bp_type');
+        $sType = $sType !== false ? bx_process_input($sType) : '';
+
+        $sPage = bx_get('bp_page');
+        $sPage = $sPage !== false ? bx_process_input($sPage) : '';
+
+        bx_import('BuilderPage', $this->aModule);
+        $oContent = new BxDevBuilderPage(array(
+            'type' => $sType,
+            'page' => $sPage,
+            'url' => sprintf($this->sUrl, $this->sModule, BX_DEV_TOOLS_PAGES),
+        ));
+
+        if(($mixedResult = $oContent->checkAction()) !== false) {
+            echoJson($mixedResult);
+            exit;
+        }
+
+        return $this->oModule->_oTemplate->displayPageContent($this->sPage, $oContent);
+    }
+
+    protected function getPolyglot()
+    {
+        $sType = bx_get('pgt_type');
+        $sType = $sType !== false ? bx_process_input($sType) : '';
+
+        $sPage = bx_get('pgt_page');
+        $sPage = $sPage !== false ? bx_process_input($sPage) : 'manager';
+
+        bx_import('Polyglot', $this->aModule);
+        $oContent = new BxDevPolyglot(array(
+            'page' => $sPage,
+            'url' => sprintf($this->sUrl, $this->sModule, BX_DEV_TOOLS_POLYGLOT),
+        ));
+
+        if(($mixedResult = $oContent->checkAction()) !== false) {
+            echoJson($mixedResult);
+            exit;
+        }
+
+        return $this->oModule->_oTemplate->displayPageContent($this->sPage, $oContent);
+    }
+
+    protected function getPermissions()
+    {
+        $sPage = bx_get('prm_page');
+        $sPage = $sPage !== false ? bx_process_input($sPage) : '';
+
+        bx_import('Permissions', $this->aModule);
+        $oContent = new BxDevPermissions(array(
+            'page' => $sPage,
+            'url' => sprintf($this->sUrl, $this->sModule, BX_DEV_TOOLS_PERMISSIONS),
+        ));
+
+        if(($mixedResult = $oContent->checkAction()) !== false) {
+            echoJson($mixedResult);
+            exit;
+        }
+
+        return $this->oModule->_oTemplate->displayPageContent($this->sPage, $oContent);
+    }
+}
+
+/** @} */

+ 69 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/install/config.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_developer',
+    'title' => 'Developer',
+    'note' => 'Developer tools...',
+    'version' => '14.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/developer/',
+    'home_uri' => 'developer',
+
+    'db_prefix' => 'bx_dev_',
+    'class_prefix' => 'BxDev',
+
+	/**
+     * Category for language keys.
+     */
+    'language_category' => 'BoonEx Developer',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+    ),
+    'disable' => array(
+        'execute_sql' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+);
+
+/** @} */

+ 551 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/install/langs/en.xml

@@ -0,0 +1,551 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_developer"><![CDATA[Developer]]></string>
+
+    <string name="_bx_dev_adm_wgt_cpt"><![CDATA[Developer]]></string>
+    <string name="_bx_dev_adm_page_cpt"><![CDATA[Developer]]></string>
+    
+    <!-- Forms Builder -->
+    <string name="_bx_dev_frm_txt_forms_gl_title"><![CDATA[System Name]]></string>
+    <string name="_bx_dev_frm_txt_forms_gl_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_forms_gl_displays"><![CDATA[Displays]]></string>
+    <string name="_bx_dev_frm_btn_forms_gl_export"><![CDATA[Export Form]]></string>
+    <string name="_bx_dev_frm_txt_displays_gl_title"><![CDATA[System Name]]></string>
+    <string name="_bx_dev_frm_txt_displays_gl_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_displays_gl_form"><![CDATA[Form]]></string>
+    <string name="_bx_dev_frm_txt_displays_gl_fields"><![CDATA[Fields]]></string>
+    <string name="_bx_dev_frm_txt_fields_gl_type"><![CDATA[Type]]></string>
+    <string name="_bx_dev_frm_txt_fields_gl_caption_system"><![CDATA[System Name]]></string>
+    <string name="_bx_dev_frm_txt_fields_gl_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_fields_gl_visible"><![CDATA[Visible For]]></string>
+    <string name="_bx_dev_frm_txt_pre_lists_gl_title"><![CDATA[System Name]]></string>
+    <string name="_bx_dev_frm_txt_pre_lists_gl_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_pre_lists_gl_values"><![CDATA[Values]]></string>
+    <string name="_bx_dev_frm_txt_pre_lists_gl_use_for_sets"><![CDATA[Use For Sets]]></string>
+    <string name="_bx_dev_frm_txt_pre_values_gl_lkey"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_gl_title"><![CDATA[System Name]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_gl_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_gl_fields"><![CDATA[Fields]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_fields_gl_type"><![CDATA[Type]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_fields_gl_caption"><![CDATA[Caption]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_fields_gl_search_type"><![CDATA[Search Type]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_fields_gl_search_operator"><![CDATA[Search Operator]]></string>
+    <string name="_bx_dev_frm_btn_forms_gl_create"><![CDATA[Add New Form]]></string>
+    <string name="_bx_dev_frm_btn_displays_gl_create"><![CDATA[Add New Display]]></string>
+    <string name="_bx_dev_frm_btn_fields_create"><![CDATA[Add New Field]]></string>
+    <string name="_bx_dev_frm_btn_pre_lists_create"><![CDATA[Add New List]]></string>
+    <string name="_bx_dev_frm_btn_pre_values_create"><![CDATA[Add New Item]]></string>
+    <string name="_bx_dev_frm_btn_search_forms_create"><![CDATA[Add New Search]]></string>
+    <string name="_bx_dev_frm_btn_search_forms_edit"><![CDATA[Edit search form]]></string>
+    <string name="_bx_dev_frm_btn_search_forms_fields_edit"><![CDATA[Edit search field]]></string>
+    <string name="_bx_dev_frm_btn_search_forms_fields_reset"><![CDATA[Reset]]></string>
+    <string name="_bx_dev_frm_txt_select_module"><![CDATA[Select Module...]]></string>
+    <string name="_bx_dev_frm_txt_select_object"><![CDATA[Select Object...]]></string>
+    <string name="_bx_dev_frm_txt_select_object_content_info"><![CDATA[Select Content Info Object...]]></string>
+    <string name="_bx_dev_frm_txt_forms_create_popup"><![CDATA[Add New Form]]></string>
+    <string name="_bx_dev_frm_txt_forms_edit_popup"><![CDATA[Edit "{0}" Form]]></string>
+    <string name="_bx_dev_frm_txt_forms_export_popup"><![CDATA[Export "{0}" Form]]></string>
+    <string name="_bx_dev_frm_err_forms_create"><![CDATA[Cannot add form]]></string>
+    <string name="_bx_dev_frm_err_forms_edit"><![CDATA[Cannot update form]]></string>
+    <string name="_bx_dev_frm_txt_forms_form"><![CDATA[Forms Builder Form]]></string>
+    <string name="_bx_dev_frm_txt_forms_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_txt_forms_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_id"><![CDATA[ID]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_frm_txt_forms_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_frm_err_forms_object"><![CDATA[You need to enter a unique value here]]></string>    
+    <string name="_bx_dev_frm_txt_sys_forms_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_forms_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_err_forms_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_txt_forms_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_err_forms_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_action"><![CDATA[Action]]></string>
+    <string name="_bx_dev_frm_txt_forms_action"><![CDATA[Action]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_form_attrs"><![CDATA[Form Attrs]]></string>
+    <string name="_bx_dev_frm_txt_forms_form_attrs"><![CDATA[Form Attrs]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_submit_name"><![CDATA[Submit Name]]></string>
+    <string name="_bx_dev_frm_txt_forms_submit_name"><![CDATA[Submit Name]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_table"><![CDATA[Table]]></string>
+    <string name="_bx_dev_frm_txt_forms_table"><![CDATA[Table]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_key"><![CDATA[Key Field]]></string>
+    <string name="_bx_dev_frm_txt_forms_key"><![CDATA[Key Field]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_uri"><![CDATA[URI Field]]></string>
+    <string name="_bx_dev_frm_txt_forms_uri"><![CDATA[URI Field]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_uri_title"><![CDATA[URI Source Field]]></string>
+    <string name="_bx_dev_frm_txt_forms_uri_title"><![CDATA[URI Source Field]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_params"><![CDATA[Params]]></string>
+    <string name="_bx_dev_frm_txt_forms_params"><![CDATA[Params]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_frm_txt_forms_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_override_class_name"><![CDATA[Class Name]]></string>
+    <string name="_bx_dev_frm_txt_forms_override_class_name"><![CDATA[Class Name]]></string>
+    <string name="_bx_dev_frm_txt_sys_forms_override_class_file"><![CDATA[Class File]]></string>
+    <string name="_bx_dev_frm_txt_forms_override_class_file"><![CDATA[Class File]]></string>
+    <string name="_bx_dev_frm_txt_forms_export_insert"><![CDATA[Adding to database]]></string>
+    <string name="_bx_dev_frm_txt_forms_export_delete"><![CDATA[Removing from database]]></string>
+    <string name="_bx_dev_frm_btn_sys_forms_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_forms_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_forms_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_frm_btn_sys_forms_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_btn_forms_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_btn_forms_done"><![CDATA[Done]]></string>
+    <string name="_bx_dev_frm_txt_displays_create_popup"><![CDATA[Add New Display]]></string>
+    <string name="_bx_dev_frm_txt_displays_edit_popup"><![CDATA[Edit "{0}" Display]]></string>
+    <string name="_bx_dev_frm_err_displays_create"><![CDATA[Cannot add display]]></string>
+    <string name="_bx_dev_frm_err_displays_edit"><![CDATA[Cannot update display]]></string>
+    <string name="_bx_dev_frm_txt_displays_diplay"><![CDATA[Forms Builder Display]]></string>
+    <string name="_bx_dev_frm_txt_displays_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_txt_displays_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_frm_txt_sys_displays_id"><![CDATA[ID]]></string>
+    <string name="_bx_dev_frm_txt_sys_displays_display_name"><![CDATA[Name]]></string>
+    <string name="_bx_dev_frm_txt_displays_display_name"><![CDATA[Name]]></string>
+    <string name="_bx_dev_frm_err_displays_display_name"><![CDATA[You need to enter a unique (in the form) value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_displays_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_displays_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_err_displays_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_frm_txt_sys_displays_object"><![CDATA[Form Name]]></string>
+    <string name="_bx_dev_frm_txt_displays_object"><![CDATA[Form Name]]></string>
+    <string name="_bx_dev_frm_err_displays_object"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_displays_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_txt_displays_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_err_displays_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_displays_view_mode"><![CDATA[View Mode]]></string>
+    <string name="_bx_dev_frm_txt_displays_view_mode"><![CDATA[View Mode]]></string>
+    <string name="_bx_dev_frm_btn_sys_displays_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_displays_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_displays_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_frm_btn_sys_displays_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_btn_displays_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_txt_field_title_editable"><![CDATA[Editable]]></string>
+    <string name="_bx_dev_frm_txt_field_info_editable"><![CDATA[Make the field Editable in Studio Forms Builder]]></string>
+    <string name="_bx_dev_frm_txt_field_title_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_frm_txt_field_info_deletable"><![CDATA[Make the field Deletable in Studio Forms Builder]]></string>
+    <string name="_bx_dev_frm_txt_prelists_create_popup"><![CDATA[Add Data List]]></string>
+    <string name="_bx_dev_frm_err_prelists_create"><![CDATA[Cannot add data list]]></string>
+    <string name="_bx_dev_frm_txt_prelists_edit_popup"><![CDATA[Edit "{0}" Data List]]></string>
+    <string name="_bx_dev_frm_err_prelists_edit"><![CDATA[Cannot update data list]]></string>
+    <string name="_bx_dev_frm_txt_prelists_export_popup"><![CDATA[Export "{0}" Data List]]></string>
+    <string name="_bx_dev_frm_txt_prelists_prelist"><![CDATA[Forms Builder Data List]]></string>
+    <string name="_bx_dev_frm_txt_prelists_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_txt_prelists_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_frm_txt_sys_prelists_id"><![CDATA[id]]></string>
+    <string name="_bx_dev_frm_txt_sys_prelists_key"><![CDATA[Key]]></string>
+    <string name="_bx_dev_frm_txt_prelists_key"><![CDATA[Key]]></string>
+    <string name="_bx_dev_frm_err_prelists_key"><![CDATA[You need to enter a unique value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_prelists_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_prelists_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_err_prelists_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_frm_txt_sys_prelists_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_txt_prelists_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_err_prelists_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_prelists_use_for_sets"><![CDATA[Use For Sets]]></string>
+    <string name="_bx_dev_frm_txt_prelists_use_for_sets"><![CDATA[Use For Sets]]></string>
+    <string name="_bx_dev_frm_btn_sys_prelists_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_prelists_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_prelists_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_frm_btn_sys_prelists_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_btn_prelists_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_btn_prelists_done"><![CDATA[Done]]></string>
+    <string name="_bx_dev_frm_txt_prelists_export_insert"><![CDATA[Adding to database]]></string>
+    <string name="_bx_dev_frm_txt_prelists_export_delete"><![CDATA[Removing from database]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_create_popup"><![CDATA[Add Data Value]]></string>
+    <string name="_bx_dev_frm_err_prevalues_create"><![CDATA[Cannot add data value]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_edit_popup"><![CDATA[Edit "{0}" Data Value]]></string>
+    <string name="_bx_dev_frm_err_prevalues_edit"><![CDATA[Cannot update data value]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_prevalue"><![CDATA[Forms Builder Data Value]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_display_edit"><![CDATA[Edd]]></string>
+    <string name="_bx_dev_frm_txt_sys_prevalues_id"><![CDATA[ID]]></string>
+    <string name="_bx_dev_frm_txt_sys_prevalues_key"><![CDATA[List]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_key"><![CDATA[List]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_key_select"><![CDATA[Select List...]]></string>
+    <string name="_bx_dev_frm_err_prevalues_key"><![CDATA[You need to select a list]]></string>
+    <string name="_bx_dev_frm_txt_sys_prevalues_value"><![CDATA[Value]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_value"><![CDATA[Value]]></string>
+    <string name="_bx_dev_frm_err_prevalues_value"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_prevalues_lkey"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_lkey"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_err_prevalues_lkey"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_prevalues_lkey2"><![CDATA[Title Additional]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_lkey2"><![CDATA[Title Additional]]></string>
+    <string name="_bx_dev_frm_txt_sys_prevalues_data"><![CDATA[Data]]></string>
+    <string name="_bx_dev_frm_txt_prevalues_data"><![CDATA[Data]]></string>
+    <string name="_bx_dev_frm_btn_sys_prevalues_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_prevalues_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_prevalues_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_frm_btn_sys_prevalues_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_btn_prevalues_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_err_prevalues_create_forbidden"><![CDATA[This list is already used. It's impossible to add non-numeric, or less than 1, or more than {0} values in it.]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_create_popup"><![CDATA[Add New Search Form]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_edit_popup"><![CDATA[Edit "{0}" Search Form]]></string>
+    <string name="_bx_dev_frm_err_search_forms_create"><![CDATA[Cannot add search form]]></string>
+    <string name="_bx_dev_frm_err_search_forms_edit"><![CDATA[Cannot update search form]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_form"><![CDATA[Forms Builder Search Form]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_forms_id"><![CDATA[ID]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_forms_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_frm_err_forms_object"><![CDATA[You need to enter a unique value here]]></string>    
+    <string name="_bx_dev_frm_txt_sys_search_forms_object_content_info"><![CDATA[Content Info object]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_object_content_info"><![CDATA[Content Info object]]></string>
+    <string name="_bx_dev_frm_err_forms_object_content_info"><![CDATA[You need to select the name of Content Info object here]]></string>    
+    <string name="_bx_dev_frm_txt_sys_search_forms_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_frm_err_forms_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_forms_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_frm_err_forms_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_forms_class_name"><![CDATA[Class Name]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_class_name"><![CDATA[Class Name]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_forms_class_file"><![CDATA[Class File]]></string>
+    <string name="_bx_dev_frm_txt_search_forms_class_file"><![CDATA[Class File]]></string>
+    <string name="_bx_dev_frm_btn_sys_search_forms_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_search_forms_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_frm_btn_search_forms_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_frm_btn_sys_search_forms_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_btn_search_forms_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_edit_popup"><![CDATA[Edit "{0}" Search Field]]></string>
+    <string name="_bx_dev_frm_err_search_fields_edit"><![CDATA[Cannot update search field]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_form"><![CDATA[Forms Builder Search Field]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_id"><![CDATA[ID]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_object"><![CDATA[Search Form]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_object"><![CDATA[Search Form]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_name"><![CDATA[Name]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_name"><![CDATA[Name]]></string>
+    <string name="_bx_dev_frm_err_search_fields_name"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_type"><![CDATA[Type]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_type"><![CDATA[Type]]></string>
+    <string name="_bx_dev_frm_err_search_fields_type"><![CDATA[You need to select a type]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_caption"><![CDATA[Caption]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_caption"><![CDATA[Caption]]></string>
+    <string name="_bx_dev_frm_err_search_fields_caption"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_values"><![CDATA[Values]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_values"><![CDATA[Values]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_search_type"><![CDATA[Search Type]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_search_type"><![CDATA[Search Type]]></string>
+    <string name="_bx_dev_frm_err_search_fields_search_type"><![CDATA[You need to select a search type]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_search_value"><![CDATA[Default Value]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_search_value"><![CDATA[Default Value]]></string>
+    <string name="_bx_dev_frm_txt_sys_search_fields_search_operator"><![CDATA[Operator]]></string>
+    <string name="_bx_dev_frm_txt_search_fields_search_operator"><![CDATA[Operator]]></string>
+    <string name="_bx_dev_frm_err_search_fields_search_operator"><![CDATA[You need to select an operator]]></string>
+    <string name="_bx_dev_frm_btn_search_fields_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_frm_btn_sys_search_fields_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_frm_btn_search_fields_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_frm_btn_sys_search_fields_cancel"><![CDATA[Cancel]]></string>
+    
+    <!-- Navigation Builder -->
+    <string name="_bx_dev_nav_txt_menus_gl_title"><![CDATA[System Name]]></string>
+    <string name="_bx_dev_nav_txt_menus_gl_set_title"><![CDATA[Set]]></string>
+    <string name="_bx_dev_nav_txt_menus_gl_items"><![CDATA[Items]]></string>
+    <string name="_bx_dev_nav_txt_menus_gl_template_title"><![CDATA[Style]]></string>
+    <string name="_bx_dev_nav_txt_menus_gl_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_txt_sets_gl_title"><![CDATA[System Name]]></string>
+    <string name="_bx_dev_nav_txt_sets_gl_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_txt_sets_gl_items"><![CDATA[Items]]></string>
+    <string name="_bx_dev_nav_txt_items_gl_icon"><![CDATA[Icon]]></string>
+    <string name="_bx_dev_nav_txt_items_gl_title_system"><![CDATA[System Name]]></string>
+    <string name="_bx_dev_nav_txt_items_gl_link"><![CDATA[URL]]></string>
+    <string name="_bx_dev_nav_txt_items_gl_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_txt_items_gl_visible"><![CDATA[Visibile For]]></string>
+    <string name="_bx_dev_nav_btn_menus_gl_create"><![CDATA[Add New Menu]]></string>
+    <string name="_bx_dev_nav_btn_menus_gl_export"><![CDATA[Export Menu]]></string>
+    <string name="_bx_dev_nav_btn_sets_gl_create"><![CDATA[Add New Set]]></string>
+    <string name="_bx_dev_nav_btn_items_gl_create"><![CDATA[Add New Item]]></string>
+    <string name="_bx_dev_nav_btn_items_gl_import"><![CDATA[Import Items]]></string>
+    <string name="_bx_dev_nav_btn_items_gl_visible"><![CDATA[Visibile For]]></string>
+    <string name="_bx_dev_nav_txt_select_module"><![CDATA[Select Module...]]></string>
+    <string name="_bx_dev_nav_txt_menus_create_popup"><![CDATA[Add New Menu]]></string>
+    <string name="_bx_dev_nav_err_menus_create"><![CDATA[Cannot add menu]]></string>
+    <string name="_bx_dev_nav_txt_menus_edit_popup"><![CDATA[Edit "{0}" Menu]]></string>
+    <string name="_bx_dev_nav_err_menus_edit"><![CDATA[Cannot update menu]]></string>
+    <string name="_bx_dev_nav_txt_menus_export_popup"><![CDATA[Export "{0}" Menu]]></string>
+    <string name="_bx_dev_nav_txt_menus_menu"><![CDATA[Navigation Builder Menu]]></string>
+    <string name="_bx_dev_nav_txt_menus_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_txt_menus_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_id"><![CDATA[ID]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_nav_txt_menus_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_nav_err_menus_object"><![CDATA[You need to enter a unique value here]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_txt_menus_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_err_menus_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_nav_txt_menus_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_nav_err_menus_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_set_name"><![CDATA[Set]]></string>
+    <string name="_bx_dev_nav_txt_menus_set_name"><![CDATA[Set]]></string>
+    <string name="_bx_dev_nav_txt_menus_set_name_select"><![CDATA[Select Set...]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_template_id"><![CDATA[Style]]></string>
+    <string name="_bx_dev_nav_txt_menus_template_id"><![CDATA[Style]]></string>
+    <string name="_bx_dev_nav_txt_menus_template_id_select"><![CDATA[Select Style...]]></string>
+    <string name="_bx_dev_nav_err_menus_template_id"><![CDATA[You need to select a style]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_config_api"><![CDATA[Config (for App)]]></string>
+    <string name="_bx_dev_nav_txt_menus_config_api"><![CDATA[Config (for App in JSON format)]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_nav_txt_menus_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_override_class_name"><![CDATA[Class Name]]></string>
+    <string name="_bx_dev_nav_txt_menus_override_class_name"><![CDATA[Class Name]]></string>
+    <string name="_bx_dev_nav_txt_sys_menus_override_class_file"><![CDATA[Class File]]></string>
+    <string name="_bx_dev_nav_txt_menus_override_class_file"><![CDATA[Class File]]></string>
+    <string name="_bx_dev_nav_txt_menus_export_insert"><![CDATA[Adding to database]]></string>
+    <string name="_bx_dev_nav_txt_menus_export_delete"><![CDATA[Removing from database]]></string>
+    <string name="_bx_dev_nav_btn_sys_menus_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_btn_menus_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_btn_menus_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_nav_btn_sys_menus_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_nav_btn_menus_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_nav_btn_menus_done"><![CDATA[Done]]></string>
+    <string name="_bx_dev_nav_txt_sets_create_popup"><![CDATA[Add New Set]]></string>
+    <string name="_bx_dev_nav_err_sets_create"><![CDATA[Cannot add set]]></string>
+    <string name="_bx_dev_nav_txt_sets_edit_popup"><![CDATA[Edit "{0}" Set]]></string>
+    <string name="_bx_dev_nav_err_sets_edit"><![CDATA[Cannot update set]]></string>
+    <string name="_bx_dev_nav_txt_sets_set"><![CDATA[Navigation Builder Sets]]></string>
+    <string name="_bx_dev_nav_txt_sets_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_txt_sets_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_nav_txt_sys_sets_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_txt_sets_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_err_sets_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_nav_txt_sys_sets_set_name"><![CDATA[Set]]></string>
+    <string name="_bx_dev_nav_txt_sets_set_name"><![CDATA[Set]]></string>
+    <string name="_bx_dev_nav_err_sets_set_name"><![CDATA[You need to enter a set name]]></string>
+    <string name="_bx_dev_nav_txt_sys_sets_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_nav_txt_sets_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_nav_err_sets_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_nav_txt_sys_sets_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_nav_txt_sets_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_nav_btn_sys_sets_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_btn_sets_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_btn_sys_sets_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_nav_btn_sets_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_nav_txt_items_create_popup"><![CDATA[Add New Item]]></string>
+    <string name="_bx_dev_nav_err_items_create"><![CDATA[Cannot add item]]></string>
+    <string name="_bx_dev_nav_txt_items_edit_popup"><![CDATA[Edit "{0}" Item]]></string>
+    <string name="_bx_dev_nav_err_items_edit"><![CDATA[Cannot update item]]></string>
+    <string name="_bx_dev_nav_txt_items_item"><![CDATA[Navigation Builder Items]]></string>
+    <string name="_bx_dev_nav_txt_items_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_txt_items_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_id"><![CDATA[ID]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_txt_items_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_nav_err_items_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_set_name"><![CDATA[Set]]></string>
+    <string name="_bx_dev_nav_txt_items_set_name"><![CDATA[Set]]></string>
+    <string name="_bx_dev_nav_err_items_set_name"><![CDATA[You need to select a set]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_name"><![CDATA[Name]]></string>
+    <string name="_bx_dev_nav_txt_items_name"><![CDATA[Name]]></string>
+    <string name="_bx_dev_nav_err_items_name"><![CDATA[You need to enter a unique value here]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_title_system"><![CDATA[System Title]]></string>
+    <string name="_bx_dev_nav_txt_items_title_system"><![CDATA[System Title]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_nav_txt_items_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_nav_err_items_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_link"><![CDATA[Link]]></string>
+    <string name="_bx_dev_nav_txt_items_link"><![CDATA[Link]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_onclick"><![CDATA[OnClick]]></string>
+    <string name="_bx_dev_nav_txt_items_onclick"><![CDATA[OnClick]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_target"><![CDATA[Target]]></string>
+    <string name="_bx_dev_nav_txt_items_target"><![CDATA[Target]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_icon"><![CDATA[Icon]]></string>
+    <string name="_bx_dev_nav_txt_items_icon"><![CDATA[Icon]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_addon"><![CDATA[Addon]]></string>
+    <string name="_bx_dev_nav_txt_items_addon"><![CDATA[Addon]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_submenu_object"><![CDATA[Submenu Object]]></string>
+    <string name="_bx_dev_nav_txt_items_submenu_object"><![CDATA[Submenu Object]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_submenu_popup"><![CDATA[Submenu Popup]]></string>
+    <string name="_bx_dev_nav_txt_items_submenu_popup"><![CDATA[Submenu Popup]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_visibility_custom"><![CDATA[Custom Visibility]]></string>
+    <string name="_bx_dev_nav_txt_items_visibility_custom"><![CDATA[Custom Visibility]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_hidden_on"><![CDATA[Hidden on]]></string>
+    <string name="_bx_dev_nav_txt_items_hidden_on"><![CDATA[Hidden on]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_hidden_on_phone"><![CDATA[phone]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_hidden_on_tablet"><![CDATA[tablet]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_hidden_on_desktop"><![CDATA[desktop]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_hidden_on_mobile"><![CDATA[mobile app]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_hidden_on_pt"><![CDATA[Hidden for page type]]></string>
+    <string name="_bx_dev_nav_txt_items_hidden_on_pt"><![CDATA[Hidden for page type]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_hidden_on_col"><![CDATA[Hidden for column]]></string>
+    <string name="_bx_dev_nav_txt_items_hidden_on_col"><![CDATA[Hidden for column]]></string>
+    <string name="_bx_dev_nav_txt_items_hidden_on_col_thin"><![CDATA[Thin]]></string>
+    <string name="_bx_dev_nav_txt_items_hidden_on_col_half"><![CDATA[Half]]></string>
+    <string name="_bx_dev_nav_txt_items_hidden_on_col_wide"><![CDATA[Wide]]></string>
+    <string name="_bx_dev_nav_txt_items_hidden_on_col_full"><![CDATA[Full]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_hidden_on_cxt"><![CDATA[Hidden in contexts]]></string>
+    <string name="_bx_dev_nav_txt_items_hidden_on_cxt"><![CDATA[Hidden in contexts]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_config_api"><![CDATA[Config (for App)]]></string>
+    <string name="_bx_dev_nav_txt_items_config_api"><![CDATA[Config (for App in JSON format)]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_primary"><![CDATA[Primary]]></string>
+    <string name="_bx_dev_nav_txt_items_primary"><![CDATA[Primary]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_collapsed"><![CDATA[Collapsed]]></string>
+    <string name="_bx_dev_nav_txt_items_collapsed"><![CDATA[Collapsed]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_copyable"><![CDATA[Copyable]]></string>
+    <string name="_bx_dev_nav_txt_items_copyable"><![CDATA[Copyable]]></string>
+    <string name="_bx_dev_nav_txt_sys_items_active_api"><![CDATA[Active (for App)]]></string>
+    <string name="_bx_dev_nav_txt_items_active_api"><![CDATA[Active (for App)]]></string>
+    <string name="_bx_dev_nav_btn_sys_items_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_btn_items_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_nav_btn_items_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_nav_btn_sys_items_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_nav_btn_items_cancel"><![CDATA[Cancel]]></string>
+
+    <!-- Page Builder -->
+    <string name="_bx_dev_bp_txt_select_module"><![CDATA[Select Module...]]></string>
+    <string name="_bx_dev_bp_txt_page_create_popup"><![CDATA[Add New Page]]></string>
+    <string name="_bx_dev_bp_err_page_create"><![CDATA[Cannot add page]]></string>
+    <string name="_bx_dev_bp_txt_page_form"><![CDATA[Page in Page Builder]]></string>
+    <string name="_bx_dev_bp_txt_page_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_bp_txt_page_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_bp_err_page_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_bp_txt_page_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_bp_err_page_object"><![CDATA[You need to enter a unique value here]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_title_system"><![CDATA[System Title]]></string>
+    <string name="_bx_dev_bp_txt_page_title_system"><![CDATA[System Title]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_bp_txt_page_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_bp_err_page_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_uri"><![CDATA[URI]]></string>
+    <string name="_bx_dev_bp_txt_page_uri"><![CDATA[URI]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_url"><![CDATA[URL]]></string>
+    <string name="_bx_dev_bp_txt_page_url"><![CDATA[URL]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_layout_id"><![CDATA[Layout]]></string>
+    <string name="_bx_dev_bp_txt_page_layout_id"><![CDATA[Layout]]></string>
+    <string name="_bx_dev_bp_txt_page_layout_id_select"><![CDATA[Select Layout...]]></string>
+    <string name="_bx_dev_bp_err_page_layout_id"><![CDATA[You need to select a layout]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_config_api"><![CDATA[Config (for App)]]></string>
+    <string name="_bx_dev_bp_txt_page_config_api"><![CDATA[Config (for App in JSON format)]]></string>
+    <string name="_bx_dev_bp_txt_page_cache_editable"><![CDATA[Editable]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_bp_txt_page_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_override_class_name"><![CDATA[Class Name]]></string>
+    <string name="_bx_dev_bp_txt_page_override_class_name"><![CDATA[Class Name]]></string>
+    <string name="_bx_dev_bp_txt_sys_page_override_class_file"><![CDATA[Class File]]></string>
+    <string name="_bx_dev_bp_txt_page_override_class_file"><![CDATA[Class File]]></string>
+    <string name="_bx_dev_bp_btn_sys_page_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_bp_btn_page_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_bp_btn_sys_page_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_bp_btn_page_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_bp_btn_page_done"><![CDATA[Done]]></string>
+    <string name="_bx_dev_bp_btn_page_export"><![CDATA[Export Page]]></string>
+    <string name="_bx_dev_bp_txt_page_export_popup"><![CDATA[Export "{0}" Page]]></string>
+    <string name="_bx_dev_bp_txt_page_export_insert"><![CDATA[Adding to database]]></string>
+    <string name="_bx_dev_bp_txt_page_export_delete"><![CDATA[Removing from database]]></string>
+    <string name="_bx_dev_bp_txt_block_edit_popup"><![CDATA[Edit "{0}" Block]]></string>
+    <string name="_bx_dev_bp_err_block_edit"><![CDATA[Cannot update page]]></string>
+    <string name="_bx_dev_bp_err_block_not_found"><![CDATA[Block was not found]]></string>
+    <string name="_bx_dev_bp_err_block_delete"><![CDATA[Cannot delete block]]></string>
+    <string name="_bx_dev_bp_txt_block_form"><![CDATA[Block in Page Builder]]></string>
+    <string name="_bx_dev_bp_txt_block_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_id"><![CDATA[ID]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_bp_txt_block_object"><![CDATA[Name]]></string>
+    <string name="_bx_dev_bp_err_block_object"><![CDATA[You need to enter a unique value here]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_bp_txt_block_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_bp_err_block_module"><![CDATA[You need to select a module]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_title_system"><![CDATA[Title System]]></string>
+    <string name="_bx_dev_bp_txt_block_title_system"><![CDATA[Title System]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_bp_txt_block_title"><![CDATA[Title]]></string>
+    <string name="_bx_dev_bp_err_block_title"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_designbox_id"><![CDATA[Layout]]></string>
+    <string name="_bx_dev_bp_txt_block_designbox_id"><![CDATA[Layout]]></string>
+    <string name="_bx_dev_bp_err_block_designbox_id"><![CDATA[You need to select a layout]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_class"><![CDATA[CSS class]]></string>
+    <string name="_bx_dev_bp_txt_block_class"><![CDATA[CSS class]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_config_api"><![CDATA[Config (for App)]]></string>
+    <string name="_bx_dev_bp_txt_block_config_api"><![CDATA[Config (for App in JSON format)]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_async"><![CDATA[Asynchronous Mode]]></string>
+    <string name="_bx_dev_bp_txt_block_async"><![CDATA[Asynchronous Mode]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_submenu"><![CDATA[Block submenu]]></string>
+    <string name="_bx_dev_bp_txt_block_submenu"><![CDATA[Block submenu]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_tabs"><![CDATA[Use 'tabs']]></string>
+    <string name="_bx_dev_bp_txt_block_tabs"><![CDATA[Use 'tabs' based block submenu]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_hidden_on"><![CDATA[Hidden on]]></string>
+    <string name="_bx_dev_bp_txt_block_hidden_on"><![CDATA[Hidden on]]></string>
+    <string name="_bx_dev_bp_txt_block_hidden_on_phone"><![CDATA[phone]]></string>
+    <string name="_bx_dev_bp_txt_block_hidden_on_tablet"><![CDATA[tablet]]></string>
+    <string name="_bx_dev_bp_txt_block_hidden_on_desktop"><![CDATA[desktop]]></string>
+    <string name="_bx_dev_bp_txt_block_hidden_on_mobile"><![CDATA[mobile]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_visible_for"><![CDATA[Visible For]]></string>
+    <string name="_bx_dev_bp_txt_block_visible_for"><![CDATA[Visible For]]></string>
+    <string name="_bx_dev_bp_txt_block_visible_for_all"><![CDATA[Anyone]]></string>
+    <string name="_bx_dev_bp_txt_block_visible_for_selected"><![CDATA[Selected levels]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_visible_for_levels"><![CDATA[Select Levels]]></string>
+    <string name="_bx_dev_bp_txt_block_visible_for_levels"><![CDATA[Select Levels]]></string>
+    <string name="_bx_dev_bp_txt_block_content_service"><![CDATA[Code]]></string>
+    <string name="_bx_dev_bp_txt_block_content_lang"><![CDATA[Content]]></string>
+    <string name="_bx_dev_bp_dsc_block_content_lang"><![CDATA[Enter language key]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_bp_txt_block_deletable"><![CDATA[Deletable]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_copyable"><![CDATA[Copyable]]></string>
+    <string name="_bx_dev_bp_txt_block_copyable"><![CDATA[Copyable]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_active"><![CDATA[Active]]></string>
+    <string name="_bx_dev_bp_txt_block_active"><![CDATA[Active]]></string>
+    <string name="_bx_dev_bp_txt_sys_block_active_api"><![CDATA[Active (for App)]]></string>
+    <string name="_bx_dev_bp_txt_block_active_api"><![CDATA[Active (for App)]]></string>
+    <string name="_bx_dev_bp_btn_sys_block_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_bp_btn_block_add"><![CDATA[Add]]></string>
+    <string name="_bx_dev_bp_btn_block_save"><![CDATA[Save]]></string>
+    <string name="_bx_dev_bp_btn_sys_block_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_bp_btn_block_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_bp_btn_sys_block_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_dev_bp_btn_block_delete"><![CDATA[Delete]]></string>
+
+    <!-- Polyglot -->
+    <string name="_bx_dev_pgt_txt_manage_gl_title"><![CDATA[Module]]></string>
+    <string name="_bx_dev_pgt_btn_manage_gl_add_keys"><![CDATA[Add Keys]]></string>
+    <string name="_bx_dev_pgt_btn_manage_gl_recompile"><![CDATA[Recompile All]]></string>
+    <string name="_bx_dev_pgt_btn_manage_gl_restore"><![CDATA[Restore All]]></string>
+    <string name="_bx_dev_pgt_txt_manage_recompile_title_sing"><![CDATA[Recompile %s language for %s module.]]></string>
+    <string name="_bx_dev_pgt_txt_manage_restore_title_sing"><![CDATA[Restore %s language for %s module.]]></string>
+    <string name="_bx_dev_pgt_txt_manage_recompile_title_plur"><![CDATA[Recompile %s language for all modules.]]></string>
+    <string name="_bx_dev_pgt_txt_manage_restore_title_plur"><![CDATA[Restore %s language for all modules.]]></string>
+    <string name="_bx_dev_pgt_txt_manage_modules_all"><![CDATA[All]]></string>
+    <string name="_bx_dev_pgt_txt_keys_add_popup"><![CDATA[Add Keys]]></string>
+
+    <string name="_bx_dev_pgt_txt_keys_form"><![CDATA[Keys]]></string>
+    <string name="_bx_dev_pgt_txt_keys_display_add"><![CDATA[Add Keys]]></string>
+    <string name="_bx_dev_pgt_txt_sys_keys_language"><![CDATA[Language]]></string>
+    <string name="_bx_dev_pgt_txt_keys_language"><![CDATA[Language]]></string>
+    <string name="_bx_dev_pgt_inf_keys_language"><![CDATA[Leave empty to add in all available languages.]]></string>
+    <string name="_bx_dev_pgt_txt_sys_keys_category"><![CDATA[Category]]></string>
+    <string name="_bx_dev_pgt_txt_keys_category"><![CDATA[Category]]></string>
+    <string name="_bx_dev_pgt_err_keys_category"><![CDATA[You need to select a category]]></string>
+    <string name="_bx_dev_pgt_txt_sys_keys_content"><![CDATA[Keys]]></string>
+    <string name="_bx_dev_pgt_txt_keys_content"><![CDATA[Keys]]></string>
+    <string name="_bx_dev_pgt_err_keys_content"><![CDATA[You need to enter a value here]]></string>
+    <string name="_bx_dev_pgt_inf_keys_content"><![CDATA[Use XML format, like the following:<br />&lt;string name="_key_first"&gt;&lt;![CDATA[First Value]]&gt;&lt;/string&gt;<br />&lt;string name="_key_second"&gt;&lt;![CDATA[Second Value]]&gt;&lt;/string&gt;]]></string>
+    <string name="_bx_dev_pgt_txt_sys_keys_replace"><![CDATA[Replace]]></string>
+    <string name="_bx_dev_pgt_txt_keys_replace"><![CDATA[Force replace if exists]]></string>
+    <string name="_bx_dev_pgt_btn_sys_keys_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_dev_pgt_btn_keys_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_dev_pgt_btn_sys_keys_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_pgt_btn_keys_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_pgt_msg_keys_added"><![CDATA[{0} keys were added]]></string>
+
+    <!-- Hash -->
+    <string name="_bx_dev_hash_msg_reset_hash_system"><![CDATA[System hash was successfully reset.]]></string>
+    <string name="_bx_dev_hash_err_reset_hash_system"><![CDATA[Cannot reset system hash.]]></string>
+    <string name="_bx_dev_hash_msg_reset_hash_modules"><![CDATA[Hash was successfully reset for the following modules:]]></string>
+    <string name="_bx_dev_hash_txt_reset_hash_module"><![CDATA[Module]]></string>
+    <string name="_bx_dev_hash_txt_reset_hash_files"><![CDATA[Files Hashed]]></string>
+
+    <!-- Export/Import -->
+    <string name="_bx_dev_btn_basic"><![CDATA[Basic]]></string>
+    <string name="_bx_dev_btn_full"><![CDATA[Full]]></string>
+    <string name="_bx_dev_btn_import_full"><![CDATA[Import]]></string>
+    <string name="_bx_dev_btn_export_full"><![CDATA[Export]]></string>
+    <string name="_bx_dev_btn_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_dev_txt_disable_before_import"><![CDATA[Deactivate before import]]></string>
+    <string name="_bx_dev_txt_disable_before_import_note"><![CDATA[Existed menu items, blocks, etc will be deactivated before importing new ones. This allows custom (different) elements to be hidden.]]></string>
+    <string name="_bx_dev_txt_import_full_popup"><![CDATA[Import]]></string>
+    <string name="_bx_dev_txt_export_type"><![CDATA[What type of export do you need? <br />(<b>Basic</b> - visibility related data only, <b>Full</b> - all data)]]></string>
+    <string name="_bx_dev_err_cannot_perform"><![CDATA[Cannot perform the action.]]></string>
+    <string name="_bx_dev_msg_imported"><![CDATA[Import finished. {0} items were processed.]]></string>
+    <string name="_bx_dev_err_import_wrong_format"><![CDATA[Wrong file format.]]></string>
+</resources>

+ 741 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/install/sql/install.sql

@@ -0,0 +1,741 @@
+SET @sName = 'bx_developer';
+
+
+--
+-- Studio page and widget.
+--
+INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
+(3, @sName, '_bx_dev_adm_page_cpt', '_bx_dev_adm_page_cpt', 'bx_developer@modules/boonex/developer/|std-icon.svg');
+SET @iPageId = LAST_INSERT_ID();
+
+SET @iParentPageId = (SELECT `id` FROM `sys_std_pages` WHERE `name`='home');
+SET @iParentPageOrder = (SELECT MAX(`order`) FROM `sys_std_pages_widgets` WHERE `page_id`=@iParentPageId);
+INSERT INTO `sys_std_widgets`(`page_id`, `module`, `type`, `url`, `click`, `icon`, `caption`, `cnt_notices`, `cnt_actions`) VALUES
+(@iPageId, @sName, 'extensions', CONCAT('{url_studio}module.php?name=', @sName), '', 'bx_developer@modules/boonex/developer/|std-icon.svg', '_bx_dev_adm_wgt_cpt', '', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:11:"get_actions";s:6:"params";a:0:{}s:5:"class";s:18:"TemplStudioModules";}');
+INSERT INTO `sys_std_pages_widgets`(`page_id`, `widget_id`, `order`) VALUES
+(@iParentPageId, LAST_INSERT_ID(), IF(NOT ISNULL(@iParentPageOrder), @iParentPageOrder + 1, 1));
+
+
+--
+-- Forms Builder -> Grid descriptors
+--
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_forms', 'Sql', 'SELECT * FROM `sys_objects_form` WHERE 1 ', 'sys_objects_form', 'id', 'module,title', 'active', '', 100, NULL, 'start', '', 'module', 'title', 'like', 'module', 'title', 2147483647, 'BxDevFormsForms', 'modules/boonex/developer/classes/BxDevFormsForms.php'),
+('bx_developer_forms_displays', 'Sql', 'SELECT `td`.`id` AS `id`, `td`.`object` AS `object`, `td`.`display_name` AS `display_name`, `td`.`title` AS `display_title`, `td`.`module` AS `module`, `tf`.`title` AS `form_title` FROM `sys_form_displays` AS `td` LEFT JOIN `sys_objects_form` AS `tf` ON `td`.`object`=`tf`.`object` WHERE 1 ', 'sys_form_displays', 'id', 'module,object,display_title', '', '', 100, NULL, 'start', '', 'td`.`module', 'td`.`title', 'like', 'module', 'display_title,form_title', 2147483647, 'BxDevFormsDisplays', 'modules/boonex/developer/classes/BxDevFormsDisplays.php'),
+('bx_developer_forms_fields', 'Sql', 'SELECT `tdi`.`id` AS `id`, `ti`.`caption_system` AS `caption_system`, `ti`.`caption` AS `caption`, `ti`.`type` AS `type`, `ti`.`module` AS `module`, `tdi`.`visible_for_levels` AS `visible_for_levels`, `tdi`.`active` AS `active`, `ti`.`editable` AS `editable`, `ti`.`deletable` AS `deletable`, `tdi`.`order` AS `order` FROM `sys_form_display_inputs` AS `tdi` LEFT JOIN `sys_form_inputs` AS `ti` ON `tdi`.`input_name`=`ti`.`name` AND `ti`.`object`=? WHERE 1 AND `tdi`.`display_name`=?', 'sys_form_display_inputs', 'id', 'order', 'active', '', 100, NULL, 'start', '', 'ti`.`type', 'ti`.`caption_system', 'like', '', '', 2147483647, 'BxDevFormsFields', 'modules/boonex/developer/classes/BxDevFormsFields.php'),
+
+('bx_developer_forms_pre_lists', 'Sql', 'SELECT * FROM `sys_form_pre_lists` WHERE 1 ', 'sys_form_pre_lists', 'id', '', '', '', 100, NULL, 'start', '', 'module,key', 'title', 'auto', 'module', 'title', 2147483647, 'BxDevFormsPreLists', 'modules/boonex/developer/classes/BxDevFormsPreLists.php'),
+('bx_developer_forms_pre_values', 'Sql', 'SELECT * FROM `sys_form_pre_values` WHERE 1 ', 'sys_form_pre_values', 'id', 'Order', '', '', 1000, NULL, 'start', '', 'Key,Value', 'LKey,LKey2', 'auto', '', '', 2147483647, 'BxDevFormsPreValues', 'modules/boonex/developer/classes/BxDevFormsPreValues.php'),
+
+('bx_developer_search_forms', 'Sql', 'SELECT * FROM `sys_objects_search_extended` WHERE 1 ', 'sys_objects_search_extended', 'id', 'module,title', 'active', '', 100, NULL, 'start', '', 'module', 'title', 'like', 'module', 'title', 2147483647, 'BxDevFormsSearchForms', 'modules/boonex/developer/classes/BxDevFormsSearchForms.php'),
+('bx_developer_search_forms_fields', 'Sql', 'SELECT * FROM `sys_search_extended_fields` WHERE 1 AND `object`=?', 'sys_search_extended_fields', 'id', 'order', 'active', '', 100, NULL, 'start', '', 'type', 'caption', 'like', '', '', 2147483647, 'BxDevFormsSearchFields', 'modules/boonex/developer/classes/BxDevFormsSearchFields.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_developer_forms', 'switcher', '', '10%', 0, '', '', 1),
+('bx_developer_forms', 'title', '_bx_dev_frm_txt_forms_gl_title', '40%', 1, '38', '', 2),
+('bx_developer_forms', 'module', '_bx_dev_frm_txt_forms_gl_module', '15%', 0, '13', '', 3),
+('bx_developer_forms', 'displays', '_bx_dev_frm_txt_forms_gl_displays', '15%', 0, '13', '', 4),
+('bx_developer_forms', 'actions', '', '20%', 0, '', '', 5),
+
+('bx_developer_forms_displays', 'display_title', '_bx_dev_frm_txt_displays_gl_title', '30%', 1, '48', '', 1),
+('bx_developer_forms_displays', 'module', '_bx_dev_frm_txt_displays_gl_module', '13%', 0, '11', '', 2),
+('bx_developer_forms_displays', 'form_title', '_bx_dev_frm_txt_displays_gl_form', '24%', 1, '22', '', 3),
+('bx_developer_forms_displays', 'fields', '_bx_dev_frm_txt_displays_gl_fields', '13%', 0, '11', '', 4),
+('bx_developer_forms_displays', 'actions', '', '20%', 0, '', '', 5),
+
+('bx_developer_forms_fields', 'order', '', '1%', 0, '', '', 1),
+('bx_developer_forms_fields', 'switcher', '', '9%', 0, '', '', 2),
+('bx_developer_forms_fields', 'type', '_bx_dev_frm_txt_fields_gl_type', '5%', 0, '', '', 3),
+('bx_developer_forms_fields', 'caption_system', '_bx_dev_frm_txt_fields_gl_caption_system', '40%', 1, '38', '', 4),
+('bx_developer_forms_fields', 'module', '_bx_dev_frm_txt_fields_gl_module', '15%', 0, '13', '', 5),
+('bx_developer_forms_fields', 'visible_for_levels', '_bx_dev_frm_txt_fields_gl_visible', '10%', 0, '10', '', 6),
+('bx_developer_forms_fields', 'actions', '', '20%', 0, '', '', 7),
+
+('bx_developer_forms_pre_lists', 'title', '_bx_dev_frm_txt_pre_lists_gl_title', '45%', 1, '50', '', 1),
+('bx_developer_forms_pre_lists', 'values', '_bx_dev_frm_txt_pre_lists_gl_values', '12%', 0, '10', '', 2),
+('bx_developer_forms_pre_lists', 'module', '_bx_dev_frm_txt_pre_lists_gl_module', '13%', 0, '11', '', 3),
+('bx_developer_forms_pre_lists', 'use_for_sets', '_bx_dev_frm_txt_pre_lists_gl_use_for_sets', '10%', 0, '8', '', 4),
+('bx_developer_forms_pre_lists', 'actions', '', '20%', 0, '', '', 5),
+
+('bx_developer_forms_pre_values', 'order', '', '1%', 0, '', '', 1),
+('bx_developer_forms_pre_values', 'LKey', '_bx_dev_frm_txt_pre_values_gl_lkey', '79%', 1, '75', '', 2),
+('bx_developer_forms_pre_values', 'actions', '', '20%', 0, '', '', 3),
+
+('bx_developer_search_forms', 'switcher', '', '10%', 0, '', '', 1),
+('bx_developer_search_forms', 'title', '_bx_dev_frm_txt_search_forms_gl_title', '40%', 1, '38', '', 2),
+('bx_developer_search_forms', 'module', '_bx_dev_frm_txt_search_forms_gl_module', '15%', 0, '13', '', 3),
+('bx_developer_search_forms', 'fields', '_bx_dev_frm_txt_search_forms_gl_fields', '15%', 0, '13', '', 4),
+('bx_developer_search_forms', 'actions', '', '20%', 0, '', '', 5),
+
+('bx_developer_search_forms_fields', 'order', '', '1%', 0, '', '', 1),
+('bx_developer_search_forms_fields', 'switcher', '', '9%', 0, '', '', 2),
+('bx_developer_search_forms_fields', 'type', '_bx_dev_frm_txt_search_forms_fields_gl_type', '10%', 0, '', '', 3),
+('bx_developer_search_forms_fields', 'caption', '_bx_dev_frm_txt_search_forms_fields_gl_caption', '40%', 1, '38', '', 4),
+('bx_developer_search_forms_fields', 'search_type', '_bx_dev_frm_txt_search_forms_fields_gl_search_type', '10%', 0, '', '', 5),
+('bx_developer_search_forms_fields', 'search_operator', '_bx_dev_frm_txt_search_forms_fields_gl_search_operator', '10%', 0, '', '', 6),
+('bx_developer_search_forms_fields', 'actions', '', '20%', 0, '', '', 7);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `confirm`, `order`) VALUES
+('bx_developer_forms', 'independent', 'add', '_bx_dev_frm_btn_forms_gl_create', '', 0, 1),
+('bx_developer_forms', 'independent', 'import_full', '_bx_dev_btn_import_full', '', 0, 2),
+('bx_developer_forms', 'independent', 'export_full', '_bx_dev_btn_export_full', '', 0, 3),
+('bx_developer_forms', 'single', 'export', '', 'download', 0, 1),
+('bx_developer_forms', 'single', 'edit', '', 'pencil-alt', 0, 2),
+
+('bx_developer_forms_displays', 'independent', 'add', '_bx_dev_frm_btn_displays_gl_create', '', 0, 1),
+('bx_developer_forms_displays', 'single', 'edit', '', 'pencil-alt', 0, 1),
+
+('bx_developer_forms_fields', 'single', 'edit', '', 'pencil-alt', 0, 1),
+('bx_developer_forms_fields', 'single', 'delete', '', 'remove', 1, 2),
+('bx_developer_forms_fields', 'single', 'show_to', '_bx_dev_frm_btn_fields_visible', '', 0, 3),
+('bx_developer_forms_fields', 'independent', 'add', '_bx_dev_frm_btn_fields_create', '', 0, 1),
+
+('bx_developer_forms_pre_lists', 'independent', 'add', '_bx_dev_frm_btn_pre_lists_create', '', 0, 1),
+('bx_developer_forms_pre_lists', 'single', 'export', '', 'download', 0, 1),
+('bx_developer_forms_pre_lists', 'single', 'edit', '', 'pencil-alt', 0, 2),
+('bx_developer_forms_pre_lists', 'single', 'delete', '', 'remove', 1, 3),
+
+('bx_developer_forms_pre_values', 'independent', 'add', '_bx_dev_frm_btn_pre_values_create', '', 0, 1),
+('bx_developer_forms_pre_values', 'single', 'edit', '', 'pencil-alt', 0, 1),
+('bx_developer_forms_pre_values', 'single', 'delete', '', 'remove', 1, 2),
+
+('bx_developer_search_forms', 'independent', 'add', '_bx_dev_frm_btn_search_forms_create', '', 0, 1),
+('bx_developer_search_forms', 'single', 'edit', '', 'pencil-alt', 0, 1),
+
+('bx_developer_search_forms_fields', 'single', 'edit', '', 'pencil-alt', 0, 1),
+('bx_developer_search_forms_fields', 'independent', 'reset', '_bx_dev_frm_btn_search_forms_fields_reset', '', 0, 1);
+
+
+--
+-- Forms Builder -> Forms.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_forms_form', @sName, '_bx_dev_frm_txt_forms_form', '', '', 'do_submit', 'sys_objects_form', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_forms_form_add', @sName, 'bx_developer_forms_form', '_bx_dev_frm_txt_forms_display_add', 0),
+('bx_developer_forms_form_edit', @sName, 'bx_developer_forms_form', '_bx_dev_frm_txt_forms_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_forms_form', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_frm_txt_sys_forms_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_form', @sName, 'object', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_object', '_bx_dev_frm_txt_forms_object', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_forms_object', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'module', '', '', 0, 'select', '_bx_dev_frm_txt_sys_forms_module', '_bx_dev_frm_txt_forms_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_forms_module', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'title', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_title', '_bx_dev_frm_txt_forms_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_forms_title', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'action', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_action', '_bx_dev_frm_txt_forms_action', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'form_attrs', '', '', 0, 'textarea', '_bx_dev_frm_txt_sys_forms_form_attrs', '_bx_dev_frm_txt_forms_form_attrs', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'submit_name', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_submit_name', '_bx_dev_frm_txt_forms_submit_name', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'table', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_table', '_bx_dev_frm_txt_forms_table', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'key', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_key', '_bx_dev_frm_txt_forms_key', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'uri', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_uri', '_bx_dev_frm_txt_forms_uri', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'uri_title', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_uri_title', '_bx_dev_frm_txt_forms_uri_title', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'params', '', '', 0, 'textarea', '_bx_dev_frm_txt_sys_forms_params', '_bx_dev_frm_txt_forms_params', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'deletable', '1', '', 0, 'switcher', '_bx_dev_frm_txt_sys_forms_deletable', '_bx_dev_frm_txt_forms_deletable', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_form', @sName, 'override_class_name', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_override_class_name', '_bx_dev_frm_txt_forms_override_class_name', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'override_class_file', '', '', 0, 'text', '_bx_dev_frm_txt_sys_forms_override_class_file', '_bx_dev_frm_txt_forms_override_class_file', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_form', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_form', @sName, 'do_submit', '_bx_dev_frm_btn_forms_add', '', 0, 'submit', '_bx_dev_frm_btn_sys_forms_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_form', @sName, 'cancel', '_bx_dev_frm_btn_forms_cancel', '', 0, 'button', '_bx_dev_frm_btn_sys_forms_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_forms_form_add', 'object', 2147483647, 1, 1),
+('bx_developer_forms_form_add', 'module', 2147483647, 1, 2),
+('bx_developer_forms_form_add', 'title', 2147483647, 1, 3),
+('bx_developer_forms_form_add', 'action', 2147483647, 1, 4),
+('bx_developer_forms_form_add', 'form_attrs', 2147483647, 1, 5),
+('bx_developer_forms_form_add', 'submit_name', 2147483647, 1, 6),
+('bx_developer_forms_form_add', 'table', 2147483647, 1, 7),
+('bx_developer_forms_form_add', 'key', 2147483647, 1, 8),
+('bx_developer_forms_form_add', 'uri', 2147483647, 1, 9),
+('bx_developer_forms_form_add', 'uri_title', 2147483647, 1, 10),
+('bx_developer_forms_form_add', 'params', 2147483647, 1, 11),
+('bx_developer_forms_form_add', 'deletable', 2147483647, 1, 12),
+('bx_developer_forms_form_add', 'override_class_name', 2147483647, 1, 13),
+('bx_developer_forms_form_add', 'override_class_file', 2147483647, 1, 14),
+('bx_developer_forms_form_add', 'controls', 2147483647, 1, 15),
+('bx_developer_forms_form_add', 'do_submit', 2147483647, 1, 16),
+('bx_developer_forms_form_add', 'cancel', 2147483647, 1, 17),
+('bx_developer_forms_form_edit', 'id', 2147483647, 1, 1),
+('bx_developer_forms_form_edit', 'object', 2147483647, 1, 2),
+('bx_developer_forms_form_edit', 'module', 2147483647, 1, 3),
+('bx_developer_forms_form_edit', 'title', 2147483647, 1, 4),
+('bx_developer_forms_form_edit', 'action', 2147483647, 1, 5),
+('bx_developer_forms_form_edit', 'form_attrs', 2147483647, 1, 6),
+('bx_developer_forms_form_edit', 'submit_name', 2147483647, 1, 7),
+('bx_developer_forms_form_edit', 'table', 2147483647, 1, 8),
+('bx_developer_forms_form_edit', 'key', 2147483647, 1, 9),
+('bx_developer_forms_form_edit', 'uri', 2147483647, 1, 10),
+('bx_developer_forms_form_edit', 'uri_title', 2147483647, 1, 11),
+('bx_developer_forms_form_edit', 'params', 2147483647, 1, 12),
+('bx_developer_forms_form_edit', 'deletable', 2147483647, 1, 13),
+('bx_developer_forms_form_edit', 'override_class_name', 2147483647, 1, 14),
+('bx_developer_forms_form_edit', 'override_class_file', 2147483647, 1, 15),
+('bx_developer_forms_form_edit', 'controls', 2147483647, 1, 16),
+('bx_developer_forms_form_edit', 'do_submit', 2147483647, 1, 17),
+('bx_developer_forms_form_edit', 'cancel', 2147483647, 1, 18);
+
+--
+-- Forms Builder -> Displays.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_forms_display', @sName, '_bx_dev_frm_txt_displays_diplay', '', '', 'do_submit', 'sys_form_displays', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_forms_display_add', @sName, 'bx_developer_forms_display', '_bx_dev_frm_txt_displays_display_add', 0),
+('bx_developer_forms_display_edit', @sName, 'bx_developer_forms_display', '_bx_dev_frm_txt_displays_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_forms_display', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_frm_txt_sys_displays_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_display', @sName, 'display_name', '', '', 0, 'text', '_bx_dev_frm_txt_sys_displays_display_name', '_bx_dev_frm_txt_displays_display_name', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_displays_display_name', 'Xss', '', 0, 0),
+('bx_developer_forms_display', @sName, 'module', '', '', 0, 'select', '_bx_dev_frm_txt_sys_displays_module', '_bx_dev_frm_txt_displays_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_displays_module', 'Xss', '', 0, 0),
+('bx_developer_forms_display', @sName, 'object', '', '', 0, 'select', '_bx_dev_frm_txt_sys_displays_object', '_bx_dev_frm_txt_displays_object', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_displays_object', 'Xss', '', 0, 0),
+('bx_developer_forms_display', @sName, 'title', '', '', 0, 'text', '_bx_dev_frm_txt_sys_displays_title', '_bx_dev_frm_txt_displays_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_displays_title', 'Xss', '', 0, 0),
+('bx_developer_forms_display', @sName, 'view_mode', '1', '', 0, 'switcher', '_bx_dev_frm_txt_sys_displays_view_mode', '_bx_dev_frm_txt_displays_view_mode', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_display', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_display', @sName, 'do_submit', '_bx_dev_frm_btn_displays_add', '', 0, 'submit', '_bx_dev_frm_btn_sys_displays_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_display', @sName, 'cancel', '_bx_dev_frm_btn_displays_cancel', '', 0, 'button', '_bx_dev_frm_btn_sys_displays_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_forms_display_add', 'display_name', 2147483647, 1, 1),
+('bx_developer_forms_display_add', 'module', 2147483647, 1, 2),
+('bx_developer_forms_display_add', 'object', 2147483647, 1, 3),
+('bx_developer_forms_display_add', 'title', 2147483647, 1, 4),
+('bx_developer_forms_display_add', 'view_mode', 2147483647, 1, 5),
+('bx_developer_forms_display_add', 'controls', 2147483647, 1, 6),
+('bx_developer_forms_display_add', 'do_submit', 2147483647, 1, 7),
+('bx_developer_forms_display_add', 'cancel', 2147483647, 1, 8),
+('bx_developer_forms_display_edit', 'id', 2147483647, 1, 1),
+('bx_developer_forms_display_edit', 'display_name', 2147483647, 1, 1),
+('bx_developer_forms_display_edit', 'module', 2147483647, 1, 2),
+('bx_developer_forms_display_edit', 'object', 2147483647, 1, 3),
+('bx_developer_forms_display_edit', 'title', 2147483647, 1, 4),
+('bx_developer_forms_display_edit', 'view_mode', 2147483647, 1, 5),
+('bx_developer_forms_display_edit', 'controls', 2147483647, 1, 6),
+('bx_developer_forms_display_edit', 'do_submit', 2147483647, 1, 7),
+('bx_developer_forms_display_edit', 'cancel', 2147483647, 1, 8);
+
+--
+-- Forms Builder -> Pre Lists.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_forms_prelist', @sName, '_bx_dev_frm_txt_prelists_prelist', '', '', 'do_submit', 'sys_form_pre_lists', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_forms_prelist_add', @sName, 'bx_developer_forms_prelist', '_bx_dev_frm_txt_prelists_display_add', 0),
+('bx_developer_forms_prelist_edit', @sName, 'bx_developer_forms_prelist', '_bx_dev_frm_txt_prelists_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_forms_prelist', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_frm_txt_sys_prelists_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_prelist', @sName, 'key', '', '', 0, 'text', '_bx_dev_frm_txt_sys_prelists_key', '_bx_dev_frm_txt_prelists_key', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_prelists_key', 'Xss', '', 0, 0),
+('bx_developer_forms_prelist', @sName, 'module', '', '', 0, 'select', '_bx_dev_frm_txt_sys_prelists_module', '_bx_dev_frm_txt_prelists_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_prelists_module', 'Xss', '', 0, 0),
+('bx_developer_forms_prelist', @sName, 'title', '', '', 0, 'text', '_bx_dev_frm_txt_sys_prelists_title', '_bx_dev_frm_txt_prelists_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_prelists_title', 'Xss', '', 0, 0),
+('bx_developer_forms_prelist', @sName, 'use_for_sets', '1', '', 1, 'switcher', '_bx_dev_frm_txt_sys_prelists_use_for_sets', '_bx_dev_frm_txt_prelists_use_for_sets', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_prelist', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_prelist', @sName, 'do_submit', '_bx_dev_frm_btn_prelists_add', '', 0, 'submit', '_bx_dev_frm_btn_sys_prelists_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_prelist', @sName, 'cancel', '_bx_dev_frm_btn_prelists_cancel', '', 0, 'button', '_bx_dev_frm_btn_sys_prelists_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_forms_prelist_add', 'key', 2147483647, 1, 1),
+('bx_developer_forms_prelist_add', 'module', 2147483647, 1, 2),
+('bx_developer_forms_prelist_add', 'title', 2147483647, 1, 3),
+('bx_developer_forms_prelist_add', 'use_for_sets', 2147483647, 1, 4),
+('bx_developer_forms_prelist_add', 'controls', 2147483647, 1, 5),
+('bx_developer_forms_prelist_add', 'do_submit', 2147483647, 1, 6),
+('bx_developer_forms_prelist_add', 'cancel', 2147483647, 1, 7),
+('bx_developer_forms_prelist_edit', 'id', 2147483647, 1, 1),
+('bx_developer_forms_prelist_edit', 'module', 2147483647, 1, 2),
+('bx_developer_forms_prelist_edit', 'title', 2147483647, 1, 3),
+('bx_developer_forms_prelist_edit', 'use_for_sets', 2147483647, 1, 4),
+('bx_developer_forms_prelist_edit', 'controls', 2147483647, 1, 5),
+('bx_developer_forms_prelist_edit', 'do_submit', 2147483647, 1, 6),
+('bx_developer_forms_prelist_edit', 'cancel', 2147483647, 1, 7);
+
+--
+-- Forms Builder -> Pre Values.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_forms_prevalue', @sName, '_bx_dev_frm_txt_prevalues_prevalue', '', '', 'do_submit', 'sys_form_pre_values', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_forms_prevalue_add', @sName, 'bx_developer_forms_prevalue', '_bx_dev_frm_txt_prevalues_display_add', 0),
+('bx_developer_forms_prevalue_edit', @sName, 'bx_developer_forms_prevalue', '_bx_dev_frm_txt_prevalues_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_forms_prevalue', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_frm_txt_sys_prevalues_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_prevalue', @sName, 'Key', '', '', 0, 'select', '_bx_dev_frm_txt_sys_prevalues_key', '_bx_dev_frm_txt_prevalues_key', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_prevalues_key', 'Xss', '', 0, 0),
+('bx_developer_forms_prevalue', @sName, 'Value', '', '', 0, 'text', '_bx_dev_frm_txt_sys_prevalues_value', '_bx_dev_frm_txt_prevalues_value', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:1;s:3:"max";i:255;}', '_bx_dev_frm_err_prevalues_value', 'Xss', '', 0, 0),
+('bx_developer_forms_prevalue', @sName, 'LKey', '', '', 0, 'text', '_bx_dev_frm_txt_sys_prevalues_lkey', '_bx_dev_frm_txt_prevalues_lkey', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:1;s:3:"max";i:100;}', '_bx_dev_frm_err_prevalues_lkey', 'Xss', '', 0, 0),
+('bx_developer_forms_prevalue', @sName, 'LKey2', '', '', 0, 'text', '_bx_dev_frm_txt_sys_prevalues_lkey2', '_bx_dev_frm_txt_prevalues_lkey2', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_prevalue', @sName, 'Data', '', '', 0, 'textarea', '_bx_dev_frm_txt_sys_prevalues_data', '_bx_dev_frm_txt_prevalues_data', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_prevalue', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_prevalue', @sName, 'do_submit', '_bx_dev_frm_btn_prevalues_add', '', 0, 'submit', '_bx_dev_frm_btn_sys_prevalues_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_prevalue', @sName, 'cancel', '_bx_dev_frm_btn_prevalues_cancel', '', 0, 'button', '_bx_dev_frm_btn_sys_prevalues_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_forms_prevalue_add', 'Key', 2147483647, 1, 1),
+('bx_developer_forms_prevalue_add', 'Value', 2147483647, 1, 2),
+('bx_developer_forms_prevalue_add', 'LKey', 2147483647, 1, 3),
+('bx_developer_forms_prevalue_add', 'LKey2', 2147483647, 1, 4),
+('bx_developer_forms_prevalue_add', 'Data', 2147483647, 1, 5),
+('bx_developer_forms_prevalue_add', 'controls', 2147483647, 1, 6),
+('bx_developer_forms_prevalue_add', 'do_submit', 2147483647, 1, 7),
+('bx_developer_forms_prevalue_add', 'cancel', 2147483647, 1, 8),
+
+('bx_developer_forms_prevalue_edit', 'id', 2147483647, 1, 1),
+('bx_developer_forms_prevalue_edit', 'Key', 2147483647, 1, 2),
+('bx_developer_forms_prevalue_edit', 'Value', 2147483647, 1, 3),
+('bx_developer_forms_prevalue_edit', 'LKey', 2147483647, 1, 4),
+('bx_developer_forms_prevalue_edit', 'LKey2', 2147483647, 1, 5),
+('bx_developer_forms_prevalue_edit', 'Data', 2147483647, 1, 6),
+('bx_developer_forms_prevalue_edit', 'controls', 2147483647, 1, 7),
+('bx_developer_forms_prevalue_edit', 'do_submit', 2147483647, 1, 8),
+('bx_developer_forms_prevalue_edit', 'cancel', 2147483647, 1, 9);
+
+--
+-- Forms Builder -> Search Forms.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_forms_search_form', @sName, '_bx_dev_frm_txt_search_forms_form', '', '', 'do_submit', 'sys_objects_search_extended', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_forms_search_form_add', @sName, 'bx_developer_forms_search_form', '_bx_dev_frm_txt_search_forms_display_add', 0),
+('bx_developer_forms_search_form_edit', @sName, 'bx_developer_forms_search_form', '_bx_dev_frm_txt_search_forms_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_forms_search_form', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_frm_txt_sys_search_forms_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'object', '', '', 0, 'text', '_bx_dev_frm_txt_sys_search_forms_object', '_bx_dev_frm_txt_search_forms_object', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_forms_object', 'Xss', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'object_content_info', '', '', 0, 'select', '_bx_dev_frm_txt_sys_search_forms_object_content_info', '_bx_dev_frm_txt_search_forms_object_content_info', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_forms_object_content_info', 'Xss', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'module', '', '', 0, 'select', '_bx_dev_frm_txt_sys_search_forms_module', '_bx_dev_frm_txt_search_forms_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_forms_module', 'Xss', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'title', '', '', 0, 'text', '_bx_dev_frm_txt_sys_search_forms_title', '_bx_dev_frm_txt_search_forms_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_forms_title', 'Xss', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'class_name', '', '', 0, 'text', '_bx_dev_frm_txt_sys_search_forms_class_name', '_bx_dev_frm_txt_search_forms_class_name', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'class_file', '', '', 0, 'text', '_bx_dev_frm_txt_sys_search_forms_class_file', '_bx_dev_frm_txt_search_forms_class_file', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'do_submit', '_bx_dev_frm_btn_search_forms_add', '', 0, 'submit', '_bx_dev_frm_btn_sys_search_forms_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_search_form', @sName, 'cancel', '_bx_dev_frm_btn_search_forms_cancel', '', 0, 'button', '_bx_dev_frm_btn_sys_search_forms_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_forms_search_form_add', 'object', 2147483647, 1, 1),
+('bx_developer_forms_search_form_add', 'object_content_info', 2147483647, 1, 2),
+('bx_developer_forms_search_form_add', 'module', 2147483647, 1, 3),
+('bx_developer_forms_search_form_add', 'title', 2147483647, 1, 4),
+('bx_developer_forms_search_form_add', 'class_name', 2147483647, 1, 5),
+('bx_developer_forms_search_form_add', 'class_file', 2147483647, 1, 6),
+('bx_developer_forms_search_form_add', 'controls', 2147483647, 1, 7),
+('bx_developer_forms_search_form_add', 'do_submit', 2147483647, 1, 8),
+('bx_developer_forms_search_form_add', 'cancel', 2147483647, 1, 9),
+
+('bx_developer_forms_search_form_edit', 'id', 2147483647, 1, 1),
+('bx_developer_forms_search_form_edit', 'object', 2147483647, 1, 2),
+('bx_developer_forms_search_form_edit', 'object_content_info', 2147483647, 1, 3),
+('bx_developer_forms_search_form_edit', 'module', 2147483647, 1, 4),
+('bx_developer_forms_search_form_edit', 'title', 2147483647, 1, 5),
+('bx_developer_forms_search_form_edit', 'class_name', 2147483647, 1, 6),
+('bx_developer_forms_search_form_edit', 'class_file', 2147483647, 1, 7),
+('bx_developer_forms_search_form_edit', 'controls', 2147483647, 1, 8),
+('bx_developer_forms_search_form_edit', 'do_submit', 2147483647, 1, 9),
+('bx_developer_forms_search_form_edit', 'cancel', 2147483647, 1, 10);
+
+--
+-- Forms Builder -> Search Fields.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_forms_search_fields', @sName, '_bx_dev_frm_txt_search_fields_form', '', '', 'do_submit', 'sys_search_extended_fields', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_forms_search_fields_edit', @sName, 'bx_developer_forms_search_fields', '_bx_dev_frm_txt_search_fields_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_forms_search_fields', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_frm_txt_sys_search_fields_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'object', '', '', 0, 'select', '_bx_dev_frm_txt_sys_search_fields_object', '_bx_dev_frm_txt_search_fields_object', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_forms_object', 'Xss', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'name', '', '', 0, 'text', '_bx_dev_frm_txt_sys_search_fields_name', '_bx_dev_frm_txt_search_fields_name', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_search_fields_name', 'Xss', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'type', '', '', 0, 'select', '_bx_dev_frm_txt_sys_search_fields_type', '_bx_dev_frm_txt_search_fields_type', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_search_fields_type', 'Xss', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'caption', '', '', 0, 'text', '_bx_dev_frm_txt_sys_search_fields_caption', '_bx_dev_frm_txt_search_fields_caption', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_frm_err_search_fields_caption', 'Xss', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'values', '', '', 0, 'text', '_bx_dev_frm_txt_sys_search_fields_values', '_bx_dev_frm_txt_search_fields_values', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'search_type', '', '', 0, 'select', '_bx_dev_frm_txt_sys_search_fields_search_type', '_bx_dev_frm_txt_search_fields_search_type', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_search_fields_search_type', 'Xss', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'search_value', '', '', 0, 'text', '_bx_dev_frm_txt_sys_search_fields_search_value', '_bx_dev_frm_txt_search_fields_search_value', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'search_operator', '', '', 0, 'select', '_bx_dev_frm_txt_sys_search_fields_search_operator', '_bx_dev_frm_txt_search_fields_search_operator', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_frm_err_search_fields_search_operator', 'Xss', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'do_submit', '_bx_dev_frm_btn_search_fields_save', '', 0, 'submit', '_bx_dev_frm_btn_sys_search_fields_save', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_forms_search_fields', @sName, 'cancel', '_bx_dev_frm_btn_search_fields_cancel', '', 0, 'button', '_bx_dev_frm_btn_sys_search_fields_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_forms_search_fields_edit', 'id', 2147483647, 1, 1),
+('bx_developer_forms_search_fields_edit', 'object', 2147483647, 1, 2),
+('bx_developer_forms_search_fields_edit', 'name', 2147483647, 1, 3),
+('bx_developer_forms_search_fields_edit', 'type', 2147483647, 1, 4),
+('bx_developer_forms_search_fields_edit', 'caption', 2147483647, 1, 5),
+('bx_developer_forms_search_fields_edit', 'values', 2147483647, 1, 6),
+('bx_developer_forms_search_fields_edit', 'search_type', 2147483647, 1, 7),
+('bx_developer_forms_search_fields_edit', 'search_value', 2147483647, 1, 8),
+('bx_developer_forms_search_fields_edit', 'search_operator', 2147483647, 1, 9),
+('bx_developer_forms_search_fields_edit', 'controls', 2147483647, 1, 10),
+('bx_developer_forms_search_fields_edit', 'do_submit', 2147483647, 1, 11),
+('bx_developer_forms_search_fields_edit', 'cancel', 2147483647, 1, 12);
+
+--
+-- Navigation Builder -> Grid descriptors
+--
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_nav_menus', 'Sql', 'SELECT `tm`.*, `tms`.`title` AS `set_title`, `tmt`.`title` AS `template_title` FROM `sys_objects_menu` AS `tm` LEFT JOIN `sys_menu_sets` AS `tms` ON `tm`.`set_name`=`tms`.`set_name` LEFT JOIN `sys_menu_templates` AS `tmt` ON `tm`.`template_id`=`tmt`.`id` WHERE 1 ', 'sys_objects_menu', 'id', '', 'active', '', 100, NULL, 'start', '', '', 'tm`.`title,tms`.`title,tmt`.`title', 'auto', '', '', 2147483647, 'BxDevNavigationMenus', 'modules/boonex/developer/classes/BxDevNavigationMenus.php'),
+('bx_developer_nav_sets', 'Sql', 'SELECT * FROM `sys_menu_sets` WHERE 1 ', 'sys_menu_sets', 'set_name', '', '', '', 100, NULL, 'start', '', '', 'title', 'auto', '', '', 2147483647, 'BxDevNavigationSets', 'modules/boonex/developer/classes/BxDevNavigationSets.php'),
+('bx_developer_nav_items', 'Sql', 'SELECT * FROM `sys_menu_items` WHERE 1 ', 'sys_menu_items', 'id', 'order', 'active', '', 100, NULL, 'start_it', '', 'link', 'title_system', 'like', '', '', 2147483647, 'BxDevNavigationItems', 'modules/boonex/developer/classes/BxDevNavigationItems.php');
+
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_developer_nav_menus', 'switcher', '', '10%', 0, '', '', 1),
+('bx_developer_nav_menus', 'title', '_bx_dev_nav_txt_menus_gl_title', '17%', 0, '15', '', 2),
+('bx_developer_nav_menus', 'set_title', '_bx_dev_nav_txt_menus_gl_set_title', '17%', 1, '15', '', 3),
+('bx_developer_nav_menus', 'items', '_bx_dev_nav_txt_menus_gl_items', '10%', 0, '8', '', 4),
+('bx_developer_nav_menus', 'template_title', '_bx_dev_nav_txt_menus_gl_template_title', '17%', 1, '15', '', 5),
+('bx_developer_nav_menus', 'module', '_bx_dev_nav_txt_menus_gl_module', '12%', 0, '10', '', 6),
+('bx_developer_nav_menus', 'actions', '', '17%', 0, '', '', 7),
+('bx_developer_nav_sets', 'title', '_bx_dev_nav_txt_sets_gl_title', '50%', 1, '48', '', 1),
+('bx_developer_nav_sets', 'module', '_bx_dev_nav_txt_sets_gl_module', '15%', 0, '13', '', 2),
+('bx_developer_nav_sets', 'items', '_bx_dev_nav_txt_sets_gl_items', '15%', 0, '13', '', 3),
+('bx_developer_nav_sets', 'actions', '', '20%', 0, '', '', 4),
+('bx_developer_nav_items', 'order', '', '1%', 0, '', '', 1),
+('bx_developer_nav_items', 'switcher', '', '9%', 0, '', '', 2),
+('bx_developer_nav_items', 'icon', '_bx_dev_nav_txt_items_gl_icon', '5%', 0, '', '', 3),
+('bx_developer_nav_items', 'title_system', '_bx_dev_nav_txt_items_gl_title_system', '23%', 1, '23', '', 4),
+('bx_developer_nav_items', 'link', '_bx_dev_nav_txt_items_gl_link', '23%', 0, '23', '', 5),
+('bx_developer_nav_items', 'module', '_bx_dev_nav_txt_items_gl_module', '12%', 0, '12', '', 6),
+('bx_developer_nav_items', 'visible_for_levels', '_bx_dev_nav_txt_items_gl_visible', '10%', 0, '10', '', 7),
+('bx_developer_nav_items', 'actions', '', '17%', 0, '', '', 8);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `confirm`, `order`) VALUES
+('bx_developer_nav_menus', 'independent', 'add', '_bx_dev_nav_btn_menus_gl_create', '', 0, 1),
+('bx_developer_nav_menus', 'independent', 'import_full', '_bx_dev_btn_import_full', '', 0, 2),
+('bx_developer_nav_menus', 'independent', 'export_full', '_bx_dev_btn_export_full', '', 0, 3),
+('bx_developer_nav_menus', 'single', 'export', '', 'download', 0, 1),
+('bx_developer_nav_menus', 'single', 'edit', '', 'pencil-alt', 0, 2),
+('bx_developer_nav_menus', 'single', 'delete', '', 'remove', 1, 3),
+
+('bx_developer_nav_sets', 'independent', 'add', '_bx_dev_nav_btn_sets_gl_create', '', 0, 1),
+('bx_developer_nav_sets', 'single', 'edit', '', 'pencil-alt', 0, 1),
+('bx_developer_nav_sets', 'single', 'delete', '', 'remove', 1, 2),
+
+('bx_developer_nav_items', 'independent', 'import', '_bx_dev_nav_btn_items_gl_import', '', 0, 1),
+('bx_developer_nav_items', 'independent', 'add', '_bx_dev_nav_btn_items_gl_create', '', 0, 2),
+('bx_developer_nav_items', 'single', 'edit', '', 'pencil-alt', 0, 1),
+('bx_developer_nav_items', 'single', 'delete', '', 'remove', 1, 2),
+('bx_developer_nav_items', 'single', 'show_to', '_bx_dev_nav_btn_items_gl_visible', '', 0, 3);
+
+--
+-- Navigation Builder -> Menus.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_nav_menu', @sName, '_bx_dev_nav_txt_menus_menu', '', '', 'do_submit', 'sys_objects_menu', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_nav_menu_add', @sName, 'bx_developer_nav_menu', '_bx_dev_nav_txt_menus_display_add', 0),
+('bx_developer_nav_menu_edit', @sName, 'bx_developer_nav_menu', '_bx_dev_nav_txt_menus_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_nav_menu', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_nav_txt_sys_menus_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'object', '', '', 0, 'text', '_bx_dev_nav_txt_sys_menus_object', '_bx_dev_nav_txt_menus_object', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_nav_err_menus_object', 'Xss', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'module', '', '', 0, 'select', '_bx_dev_nav_txt_sys_menus_module', '_bx_dev_nav_txt_menus_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_nav_err_menus_module', 'Xss', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'title', '', '', 0, 'text', '_bx_dev_nav_txt_sys_menus_title', '_bx_dev_nav_txt_menus_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_nav_err_menus_title', 'Xss', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'set_name', '', '', 0, 'select', '_bx_dev_nav_txt_sys_menus_set_name', '_bx_dev_nav_txt_menus_set_name', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'template_id', '', '', 0, 'select', '_bx_dev_nav_txt_sys_menus_template_id', '_bx_dev_nav_txt_menus_template_id', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_nav_err_menus_template_id', 'Int', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'config_api', '', '', 0, 'textarea', '_bx_dev_nav_txt_sys_menus_config_api', '_bx_dev_nav_txt_menus_config_api', '', 0, 0, 0, '', '', '', '', '', '', 'XssHtml', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'deletable', '1', '', 0, 'switcher', '_bx_dev_nav_txt_sys_menus_deletable', '_bx_dev_nav_txt_menus_deletable', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'override_class_name', '', '', 0, 'text', '_bx_dev_nav_txt_sys_menus_override_class_name', '_bx_dev_nav_txt_menus_override_class_name', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'override_class_file', '', '', 0, 'text', '_bx_dev_nav_txt_sys_menus_override_class_file', '_bx_dev_nav_txt_menus_override_class_file', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'do_submit', '_bx_dev_nav_btn_menus_add', '', 0, 'submit', '_bx_dev_nav_btn_sys_menus_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_nav_menu', @sName, 'cancel', '_bx_dev_nav_btn_menus_cancel', '', 0, 'button', '_bx_dev_nav_btn_sys_menus_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_nav_menu_add', 'object', 2147483647, 1, 1),
+('bx_developer_nav_menu_add', 'module', 2147483647, 1, 2),
+('bx_developer_nav_menu_add', 'title', 2147483647, 1, 3),
+('bx_developer_nav_menu_add', 'set_name', 2147483647, 1, 4),
+('bx_developer_nav_menu_add', 'template_id', 2147483647, 1, 5),
+('bx_developer_nav_menu_add', 'config_api', 2147483647, 1, 6),
+('bx_developer_nav_menu_add', 'deletable', 2147483647, 1, 7),
+('bx_developer_nav_menu_add', 'override_class_name', 2147483647, 1, 8),
+('bx_developer_nav_menu_add', 'override_class_file', 2147483647, 1, 9),
+('bx_developer_nav_menu_add', 'controls', 2147483647, 1, 10),
+('bx_developer_nav_menu_add', 'do_submit', 2147483647, 1, 11),
+('bx_developer_nav_menu_add', 'cancel', 2147483647, 1, 12),
+
+('bx_developer_nav_menu_edit', 'id', 2147483647, 1, 1),
+('bx_developer_nav_menu_edit', 'object', 2147483647, 1, 2),
+('bx_developer_nav_menu_edit', 'module', 2147483647, 1, 3),
+('bx_developer_nav_menu_edit', 'title', 2147483647, 1, 4),
+('bx_developer_nav_menu_edit', 'set_name', 2147483647, 1, 5),
+('bx_developer_nav_menu_edit', 'template_id', 2147483647, 1, 6),
+('bx_developer_nav_menu_edit', 'config_api', 2147483647, 1, 7),
+('bx_developer_nav_menu_edit', 'deletable', 2147483647, 1, 8),
+('bx_developer_nav_menu_edit', 'override_class_name', 2147483647, 1, 9),
+('bx_developer_nav_menu_edit', 'override_class_file', 2147483647, 1, 10),
+('bx_developer_nav_menu_edit', 'controls', 2147483647, 1, 11),
+('bx_developer_nav_menu_edit', 'do_submit', 2147483647, 1, 12),
+('bx_developer_nav_menu_edit', 'cancel', 2147483647, 1, 13);
+
+--
+-- Navigation Builder -> Sets.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_nav_set', @sName, '_bx_dev_nav_txt_sets_set', '', '', 'do_submit', 'sys_menu_sets', 'set_name', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_nav_set_add', @sName, 'bx_developer_nav_set', '_bx_dev_nav_txt_sets_display_add', 0),
+('bx_developer_nav_set_edit', @sName, 'bx_developer_nav_set', '_bx_dev_nav_txt_sets_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_nav_set', @sName, 'module', '', '', 0, 'select', '_bx_dev_nav_txt_sys_sets_module', '_bx_dev_nav_txt_sets_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_nav_err_sets_module', 'Xss', '', 0, 0),
+('bx_developer_nav_set', @sName, 'set_name', '', '', 0, 'text', '_bx_dev_nav_txt_sys_sets_set_name', '_bx_dev_nav_txt_sets_set_name', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_nav_err_sets_set_name', 'Xss', '', 0, 0),
+('bx_developer_nav_set', @sName, 'title', '', '', 0, 'text', '_bx_dev_nav_txt_sys_sets_title', '_bx_dev_nav_txt_sets_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_nav_err_sets_title', 'Xss', '', 0, 0),
+('bx_developer_nav_set', @sName, 'deletable', '1', '', 0, 'switcher', '_bx_dev_nav_txt_sys_sets_deletable', '_bx_dev_nav_txt_sets_deletable', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_set', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_nav_set', @sName, 'do_submit', '_bx_dev_nav_btn_sets_add', '', 0, 'submit', '_bx_dev_nav_btn_sys_sets_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_nav_set', @sName, 'cancel', '_bx_dev_nav_btn_sets_cancel', '', 0, 'button', '_bx_dev_nav_btn_sys_sets_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_nav_set_add', 'module', 2147483647, 1, 1),
+('bx_developer_nav_set_add', 'set_name', 2147483647, 1, 2),
+('bx_developer_nav_set_add', 'title', 2147483647, 1, 3),
+('bx_developer_nav_set_add', 'deletable', 2147483647, 1, 4),
+('bx_developer_nav_set_add', 'controls', 2147483647, 1, 5),
+('bx_developer_nav_set_add', 'do_submit', 2147483647, 1, 6),
+('bx_developer_nav_set_add', 'cancel', 2147483647, 1, 7),
+('bx_developer_nav_set_edit', 'module', 2147483647, 1, 1),
+('bx_developer_nav_set_edit', 'set_name', 2147483647, 0, 2),
+('bx_developer_nav_set_edit', 'title', 2147483647, 1, 3),
+('bx_developer_nav_set_edit', 'deletable', 2147483647, 1, 4),
+('bx_developer_nav_set_edit', 'controls', 2147483647, 1, 5),
+('bx_developer_nav_set_edit', 'do_submit', 2147483647, 1, 6),
+('bx_developer_nav_set_edit', 'cancel', 2147483647, 1, 7);
+
+--
+-- Navigation Builder -> Items.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_nav_item', @sName, '_bx_dev_nav_txt_items_item', '', '', 'do_submit', 'sys_menu_items', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_nav_item_add', @sName, 'bx_developer_nav_item', '_bx_dev_nav_txt_items_display_add', 0),
+('bx_developer_nav_item_edit', @sName, 'bx_developer_nav_item', '_bx_dev_nav_txt_items_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_nav_item', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_nav_txt_sys_items_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_item', @sName, 'module', '', '', 0, 'select', '_bx_dev_nav_txt_sys_items_module', '_bx_dev_nav_txt_items_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_nav_err_items_module', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'set_name', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_set_name', '_bx_dev_nav_txt_items_set_name', '', 0, 0, 0, 'a:1:{s:8:"readonly";s:8:"readonly";}', '', '', 'Avail', '', '_bx_dev_nav_err_items_set_name', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'name', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_name', '_bx_dev_nav_txt_items_name', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_nav_err_items_name', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'title_system', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_title_system', '_bx_dev_nav_txt_items_title_system', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'title', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_title', '_bx_dev_nav_txt_items_title', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_nav_err_items_title', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'link', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_link', '_bx_dev_nav_txt_items_link', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'onclick', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_onclick', '_bx_dev_nav_txt_items_onclick', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'target', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_target', '_bx_dev_nav_txt_items_target', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'icon', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_icon', '_bx_dev_nav_txt_items_icon', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'addon', '', '', 0, 'textarea', '_bx_dev_nav_txt_sys_items_addon', '_bx_dev_nav_txt_items_addon', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'submenu_object', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_submenu_object', '_bx_dev_nav_txt_items_submenu_object', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'submenu_popup', '1', '', 0, 'switcher', '_bx_dev_nav_txt_sys_items_submenu_popup', '_bx_dev_nav_txt_items_submenu_popup', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_item', @sName, 'visibility_custom', '', '', 0, 'textarea', '_bx_dev_nav_txt_sys_items_visibility_custom', '_bx_dev_nav_txt_items_visibility_custom', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'hidden_on', '', '', 0, 'select_multiple', '_bx_dev_nav_txt_sys_items_hidden_on', '_bx_dev_nav_txt_items_hidden_on', '', 0, 0, 0, '', '', '', '', '', '', 'Set', '', 0, 0),
+('bx_developer_nav_item', @sName, 'hidden_on_pt', '', '', 0, 'select_multiple', '_bx_dev_nav_txt_sys_items_hidden_on_pt', '_bx_dev_nav_txt_items_hidden_on_pt', '', 0, 0, 0, '', '', '', '', '', '', 'Set', '', 0, 0),
+('bx_developer_nav_item', @sName, 'hidden_on_col', '', '', 0, 'select_multiple', '_bx_dev_nav_txt_sys_items_hidden_on_col', '_bx_dev_nav_txt_items_hidden_on_col', '', 0, 0, 0, '', '', '', '', '', '', 'Set', '', 0, 0),
+('bx_developer_nav_item', @sName, 'hidden_on_cxt', '', '', 0, 'text', '_bx_dev_nav_txt_sys_items_hidden_on_cxt', '_bx_dev_nav_txt_items_hidden_on_cxt', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_nav_item', @sName, 'config_api', '', '', 0, 'textarea', '_bx_dev_nav_txt_sys_items_config_api', '_bx_dev_nav_txt_items_config_api', '', 0, 0, 0, '', '', '', '', '', '', 'XssHtml', '', 0, 0),
+('bx_developer_nav_item', @sName, 'primary', '1', '', 0, 'switcher', '_bx_dev_nav_txt_sys_items_primary', '_bx_dev_nav_txt_items_primary', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_item', @sName, 'collapsed', '1', '', 0, 'switcher', '_bx_dev_nav_txt_sys_items_collapsed', '_bx_dev_nav_txt_items_collapsed', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_item', @sName, 'copyable', '1', '', 0, 'switcher', '_bx_dev_nav_txt_sys_items_copyable', '_bx_dev_nav_txt_items_copyable', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_item', @sName, 'active_api', '1', '', 0, 'switcher', '_bx_dev_nav_txt_sys_items_active_api', '_bx_dev_nav_txt_items_active_api', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_nav_item', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_nav_item', @sName, 'do_submit', '_bx_dev_nav_btn_items_add', '', 0, 'submit', '_bx_dev_nav_btn_sys_items_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_nav_item', @sName, 'cancel', '_bx_dev_nav_btn_items_cancel', '', 0, 'button', '_bx_dev_nav_btn_sys_items_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_nav_item_add', 'module', 2147483647, 1, 1),
+('bx_developer_nav_item_add', 'set_name', 2147483647, 1, 2),
+('bx_developer_nav_item_add', 'name', 2147483647, 1, 3),
+('bx_developer_nav_item_add', 'title_system', 2147483647, 1, 4),
+('bx_developer_nav_item_add', 'title', 2147483647, 1, 5),
+('bx_developer_nav_item_add', 'link', 2147483647, 1, 6),
+('bx_developer_nav_item_add', 'onclick', 2147483647, 1, 7),
+('bx_developer_nav_item_add', 'target', 2147483647, 1, 8),
+('bx_developer_nav_item_add', 'icon', 2147483647, 1, 9),
+('bx_developer_nav_item_add', 'addon', 2147483647, 1, 10),
+('bx_developer_nav_item_add', 'submenu_object', 2147483647, 1, 11),
+('bx_developer_nav_item_add', 'submenu_popup', 2147483647, 1, 12),
+('bx_developer_nav_item_add', 'visibility_custom', 2147483647, 1, 13),
+('bx_developer_nav_item_add', 'hidden_on', 2147483647, 1, 14),
+('bx_developer_nav_item_add', 'hidden_on_pt', 2147483647, 1, 15),
+('bx_developer_nav_item_add', 'hidden_on_col', 2147483647, 1, 16),
+('bx_developer_nav_item_add', 'hidden_on_cxt', 2147483647, 1, 17),
+('bx_developer_nav_item_add', 'config_api', 2147483647, 1, 18),
+('bx_developer_nav_item_add', 'primary', 2147483647, 1, 19),
+('bx_developer_nav_item_add', 'collapsed', 2147483647, 1, 20),
+('bx_developer_nav_item_add', 'copyable', 2147483647, 1, 21),
+('bx_developer_nav_item_add', 'active_api', 2147483647, 1, 22),
+('bx_developer_nav_item_add', 'controls', 2147483647, 1, 23),
+('bx_developer_nav_item_add', 'do_submit', 2147483647, 1, 24),
+('bx_developer_nav_item_add', 'cancel', 2147483647, 1, 25),
+
+('bx_developer_nav_item_edit', 'id', 2147483647, 1, 1),
+('bx_developer_nav_item_edit', 'module', 2147483647, 1, 2),
+('bx_developer_nav_item_edit', 'set_name', 2147483647, 1, 3),
+('bx_developer_nav_item_edit', 'name', 2147483647, 1, 4),
+('bx_developer_nav_item_edit', 'title_system', 2147483647, 1, 5),
+('bx_developer_nav_item_edit', 'title', 2147483647, 1, 6),
+('bx_developer_nav_item_edit', 'link', 2147483647, 1, 7),
+('bx_developer_nav_item_edit', 'onclick', 2147483647, 1, 8),
+('bx_developer_nav_item_edit', 'target', 2147483647, 1, 9),
+('bx_developer_nav_item_edit', 'icon', 2147483647, 1, 10),
+('bx_developer_nav_item_edit', 'addon', 2147483647, 1, 11),
+('bx_developer_nav_item_edit', 'submenu_object', 2147483647, 1, 12),
+('bx_developer_nav_item_edit', 'submenu_popup', 2147483647, 1, 13),
+('bx_developer_nav_item_edit', 'visibility_custom', 2147483647, 1, 14),
+('bx_developer_nav_item_edit', 'hidden_on', 2147483647, 1, 15),
+('bx_developer_nav_item_edit', 'hidden_on_pt', 2147483647, 1, 16),
+('bx_developer_nav_item_edit', 'hidden_on_col', 2147483647, 1, 17),
+('bx_developer_nav_item_edit', 'hidden_on_cxt', 2147483647, 1, 18),
+('bx_developer_nav_item_edit', 'config_api', 2147483647, 1, 19),
+('bx_developer_nav_item_edit', 'primary', 2147483647, 1, 20),
+('bx_developer_nav_item_edit', 'collapsed', 2147483647, 1, 21),
+('bx_developer_nav_item_edit', 'copyable', 2147483647, 1, 22),
+('bx_developer_nav_item_edit', 'active_api', 2147483647, 1, 23),
+('bx_developer_nav_item_edit', 'controls', 2147483647, 1, 24),
+('bx_developer_nav_item_edit', 'do_submit', 2147483647, 1, 25),
+('bx_developer_nav_item_edit', 'cancel', 2147483647, 1, 26);
+
+--
+-- Page Builder -> Page.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_bp_page', @sName, '_bx_dev_bp_txt_page_form', '', '', 'do_submit', 'sys_objects_page', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_bp_page_add', @sName, 'bx_developer_bp_page', '_bx_dev_bp_txt_page_display_add', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_bp_page', @sName, 'object', '', '', 0, 'text', '_bx_dev_bp_txt_sys_page_object', '_bx_dev_bp_txt_page_object', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_bp_err_page_object', 'Xss', '', 0, 0),
+('bx_developer_bp_page', @sName, 'module', '', '', 0, 'select', '_bx_dev_bp_txt_sys_page_module', '_bx_dev_bp_txt_page_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_bp_err_page_module', 'Xss', '', 0, 0),
+('bx_developer_bp_page', @sName, 'title_system', '', '', 0, 'text', '_bx_dev_bp_txt_sys_page_title_system', '_bx_dev_bp_txt_page_title_system', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_page', @sName, 'title', '', '', 0, 'text', '_bx_dev_bp_txt_sys_page_title', '_bx_dev_bp_txt_page_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_bp_err_page_title', 'Xss', '', 0, 0),
+('bx_developer_bp_page', @sName, 'uri', '', '', 0, 'text', '_bx_dev_bp_txt_sys_page_uri', '_bx_dev_bp_txt_page_uri', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_page', @sName, 'url', '', '', 0, 'text', '_bx_dev_bp_txt_sys_page_url', '_bx_dev_bp_txt_page_url', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_page', @sName, 'layout_id', '', '', 0, 'select', '_bx_dev_bp_txt_sys_page_layout_id', '_bx_dev_bp_txt_page_layout_id', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_bp_err_page_layout_id', 'Int', '', 0, 0),
+('bx_developer_bp_page', @sName, 'config_api', '', '', 0, 'textarea', '_bx_dev_bp_txt_sys_page_config_api', '_bx_dev_bp_txt_page_config_api', '', 0, 0, 0, '', '', '', '', '', '', 'XssHtml', '', 0, 0),
+('bx_developer_bp_page', @sName, 'deletable', '1', '', 0, 'switcher', '_bx_dev_bp_txt_sys_page_deletable', '_bx_dev_bp_txt_page_deletable', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_page', @sName, 'override_class_name', '', '', 0, 'text', '_bx_dev_bp_txt_sys_page_override_class_name', '_bx_dev_bp_txt_page_override_class_name', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_page', @sName, 'override_class_file', '', '', 0, 'text', '_bx_dev_bp_txt_sys_page_override_class_file', '_bx_dev_bp_txt_page_override_class_file', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_page', @sName, 'controls', '', 'do_submit,cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_bp_page', @sName, 'do_submit', '_bx_dev_bp_btn_page_add', '', 0, 'submit', '_bx_dev_bp_btn_sys_page_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_bp_page', @sName, 'cancel', '_bx_dev_bp_btn_page_cancel', '', 0, 'button', '_bx_dev_bp_btn_sys_page_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_bp_page_add', 'object', 2147483647, 1, 1),
+('bx_developer_bp_page_add', 'module', 2147483647, 1, 2),
+('bx_developer_bp_page_add', 'title_system', 2147483647, 1, 3),
+('bx_developer_bp_page_add', 'title', 2147483647, 1, 4),
+('bx_developer_bp_page_add', 'uri', 2147483647, 1, 5),
+('bx_developer_bp_page_add', 'url', 2147483647, 1, 6),
+('bx_developer_bp_page_add', 'layout_id', 2147483647, 1, 7),
+('bx_developer_bp_page_add', 'config_api', 2147483647, 1, 8),
+('bx_developer_bp_page_add', 'deletable', 2147483647, 1, 9),
+('bx_developer_bp_page_add', 'override_class_name', 2147483647, 1, 10),
+('bx_developer_bp_page_add', 'override_class_file', 2147483647, 1, 11),
+('bx_developer_bp_page_add', 'controls', 2147483647, 1, 12),
+('bx_developer_bp_page_add', 'do_submit', 2147483647, 1, 13),
+('bx_developer_bp_page_add', 'cancel', 2147483647, 1, 14);
+
+--
+-- Page Builder -> Block.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_bp_block', @sName, '_bx_dev_bp_txt_block_form', '', '', 'do_submit', 'sys_pages_blocks', 'id', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_bp_block_edit', @sName, 'bx_developer_bp_block', '_bx_dev_bp_txt_block_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_bp_block', @sName, 'id', '0', '', 0, 'hidden', '_bx_dev_bp_txt_sys_block_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_block', @sName, 'object', '', '', 0, 'text', '_bx_dev_bp_txt_sys_block_object', '_bx_dev_bp_txt_block_object', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:1;s:3:"max";i:64;}', '_bx_dev_bp_err_block_object', 'Xss', '', 0, 0),
+('bx_developer_bp_block', @sName, 'module', '', '', 0, 'select', '_bx_dev_bp_txt_sys_block_module', '_bx_dev_bp_txt_block_module', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_bp_err_block_module', 'Xss', '', 0, 0),
+('bx_developer_bp_block', @sName, 'title_system', '', '', 0, 'text', '_bx_dev_bp_txt_sys_block_title_system', '_bx_dev_bp_txt_block_title_system', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_block', @sName, 'title', '', '', 0, 'text', '_bx_dev_bp_txt_sys_block_title', '_bx_dev_bp_txt_block_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:6;s:3:"max";i:100;}', '_bx_dev_bp_err_block_title', 'Xss', '', 0, 0),
+('bx_developer_bp_block', @sName, 'designbox_id', '', '', 0, 'select', '_bx_dev_bp_txt_sys_block_designbox_id', '_bx_dev_bp_txt_block_designbox_id', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_bp_err_block_designbox_id', 'Xss', '', 0, 0),
+('bx_developer_bp_block', @sName, 'class', '', '', 0, 'text', '_bx_dev_bp_txt_sys_block_class', '_bx_dev_bp_txt_block_class', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_block', @sName, 'config_api', '', '', 0, 'textarea', '_bx_dev_bp_txt_sys_block_config_api', '_bx_dev_bp_txt_block_config_api', '', 0, 0, 0, '', '', '', '', '', '', 'XssHtml', '', 0, 0),
+
+('bx_developer_bp_block', @sName, 'async', '', '', 0, 'select', '_bx_dev_bp_txt_sys_block_async', '_bx_dev_bp_txt_block_async', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_block', @sName, 'submenu', '', '', 0, 'select', '_bx_dev_bp_txt_sys_block_submenu', '_bx_dev_bp_txt_block_submenu', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_block', @sName, 'tabs', '1', '', 0, 'switcher', '_bx_dev_bp_txt_sys_block_tabs', '_bx_dev_bp_txt_block_tabs', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_block', @sName, 'hidden_on', '', '', 0, 'select_multiple', '_bx_dev_bp_txt_sys_block_hidden_on', '_bx_dev_bp_txt_block_hidden_on', '', 0, 0, 0, '', '', '', '', '', '', 'Set', '', 0, 0),
+
+('bx_developer_bp_block', @sName, 'visible_for', '', '', 0, 'select', '_bx_dev_bp_txt_sys_block_visible_for', '_bx_dev_bp_txt_block_visible_for', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_developer_bp_block', @sName, 'visible_for_levels', '', '', 0, 'checkbox_set', '_bx_dev_bp_txt_sys_block_visible_for_levels', '_bx_dev_bp_txt_block_visible_for_levels', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_block', @sName, 'deletable', '1', '', 0, 'switcher', '_bx_dev_bp_txt_sys_block_deletable', '_bx_dev_bp_txt_block_deletable', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_block', @sName, 'copyable', '1', '', 0, 'switcher', '_bx_dev_bp_txt_sys_block_copyable', '_bx_dev_bp_txt_block_copyable', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_block', @sName, 'active', '1', '', 0, 'switcher', '_bx_dev_bp_txt_sys_block_active', '_bx_dev_bp_txt_block_active', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_block', @sName, 'active_api', '1', '', 0, 'switcher', '_bx_dev_bp_txt_sys_block_active_api', '_bx_dev_bp_txt_block_active_api', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_bp_block', @sName, 'controls', '', 'do_submit,cancel,delete', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_bp_block', @sName, 'do_submit', '_bx_dev_bp_btn_block_add', '', 0, 'submit', '_bx_dev_bp_btn_sys_block_add', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_bp_block', @sName, 'cancel', '_bx_dev_bp_btn_block_cancel', '', 0, 'button', '_bx_dev_bp_btn_sys_block_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_bp_block', @sName, 'delete', '_bx_dev_bp_btn_block_delete', '', 0, 'button', '_bx_dev_bp_btn_sys_block_delete', '', '', 0, 0, 0, 'a:1:{s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_bp_block_edit', 'id', 2147483647, 1, 1),
+('bx_developer_bp_block_edit', 'object', 2147483647, 1, 2),
+('bx_developer_bp_block_edit', 'module', 2147483647, 1, 3),
+('bx_developer_bp_block_edit', 'title_system', 2147483647, 1, 4),
+('bx_developer_bp_block_edit', 'title', 2147483647, 1, 5),
+('bx_developer_bp_block_edit', 'designbox_id', 2147483647, 1, 6),
+('bx_developer_bp_block_edit', 'async', 2147483647, 1, 7),
+('bx_developer_bp_block_edit', 'submenu', 2147483647, 1, 8),
+('bx_developer_bp_block_edit', 'tabs', 2147483647, 1, 9),
+('bx_developer_bp_block_edit', 'hidden_on', 2147483647, 1, 10),
+('bx_developer_bp_block_edit', 'visible_for', 2147483647, 1, 11),
+('bx_developer_bp_block_edit', 'visible_for_levels', 2147483647, 1, 12),
+('bx_developer_bp_block_edit', 'class', 2147483647, 1, 13),
+('bx_developer_bp_block_edit', 'config_api', 2147483647, 1, 14),
+('bx_developer_bp_block_edit', 'deletable', 2147483647, 1, 15),
+('bx_developer_bp_block_edit', 'copyable', 2147483647, 1, 16),
+('bx_developer_bp_block_edit', 'active', 2147483647, 1, 17),
+('bx_developer_bp_block_edit', 'active_api', 2147483647, 1, 18),
+('bx_developer_bp_block_edit', 'controls', 2147483647, 1, 19),
+('bx_developer_bp_block_edit', 'do_submit', 2147483647, 1, 20),
+('bx_developer_bp_block_edit', 'cancel', 2147483647, 1, 21),
+('bx_developer_bp_block_edit', 'delete', 2147483647, 1, 22);
+
+--
+-- Polyglot -> Grid descriptors
+--
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_pgt_manage', 'Sql', 'SELECT * FROM `sys_modules` WHERE 1 ', 'sys_modules', 'id', 'name', '', '', 100, NULL, 'start', '', '', '', '', '', '', 2147483647, 'BxDevPolyglotManage', 'modules/boonex/developer/classes/BxDevPolyglotManage.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_developer_pgt_manage', 'title', '_bx_dev_pgt_txt_manage_gl_title', '100%', 0, '', '', 1);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `confirm`, `order`) VALUES
+('bx_developer_pgt_manage', 'independent', 'add_keys', '_bx_dev_pgt_btn_manage_gl_add_keys', '', 0, 0),
+('bx_developer_pgt_manage', 'independent', 'recompile', '_bx_dev_pgt_btn_manage_gl_recompile', '', 0, 1),
+('bx_developer_pgt_manage', 'independent', 'restore', '_bx_dev_pgt_btn_manage_gl_restore', '', 0, 2);
+
+--
+-- Polyglot -> Manage.
+--
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_developer_pgt_keys', @sName, '_bx_dev_pgt_txt_keys_form', '', '', 'do_submit', 'sys_localization_keys', 'ID', '', '', '', 0, 1, 'BxDevFormView', 'modules/boonex/developer/classes/BxDevFormView.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_developer_pgt_keys_add', @sName, 'bx_developer_pgt_keys', '_bx_dev_pgt_txt_keys_display_add', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_developer_pgt_keys', @sName, 'language', '', '', 0, 'select', '_bx_dev_pgt_txt_sys_keys_language', '_bx_dev_pgt_txt_keys_language', '_bx_dev_pgt_inf_keys_language', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_pgt_keys', @sName, 'category', '', '', 0, 'select', '_bx_dev_pgt_txt_sys_keys_category', '_bx_dev_pgt_txt_keys_category', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_pgt_err_keys_category', 'Int', '', 0, 0),
+('bx_developer_pgt_keys', @sName, 'content', '', '', 0, 'textarea', '_bx_dev_pgt_txt_sys_keys_content', '_bx_dev_pgt_txt_keys_content', '_bx_dev_pgt_inf_keys_content', 1, 0, 0, '', '', '', 'Avail', '', '_bx_dev_pgt_err_keys_content', 'Xss', '', 0, 0),
+('bx_developer_pgt_keys', @sName, 'replace', '1', '', 0, 'switcher', '_bx_dev_pgt_txt_sys_keys_replace', '_bx_dev_pgt_txt_keys_replace', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_developer_pgt_keys', @sName, 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_pgt_keys', @sName, 'do_submit', '_bx_dev_pgt_btn_keys_submit', '', 0, 'submit', '_bx_dev_pgt_btn_sys_keys_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_developer_pgt_keys', @sName, 'do_cancel', '_bx_dev_pgt_btn_keys_cancel', '', 0, 'button', '_bx_dev_pgt_btn_sys_keys_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_developer_pgt_keys_add', 'language', 2147483647, 1, 1),
+('bx_developer_pgt_keys_add', 'category', 2147483647, 1, 2),
+('bx_developer_pgt_keys_add', 'content', 2147483647, 1, 3),
+('bx_developer_pgt_keys_add', 'replace', 2147483647, 1, 4),
+('bx_developer_pgt_keys_add', 'controls', 2147483647, 1, 5),
+('bx_developer_pgt_keys_add', 'do_submit', 2147483647, 1, 6),
+('bx_developer_pgt_keys_add', 'do_cancel', 2147483647, 1, 7);

+ 138 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/template/css/main.css

@@ -0,0 +1,138 @@
+/*--- Overwrite ---*/
+body.bx-dev-page-body-columns div.bx-std-page-column.bx-std-page-content .bx-std-page-column-cnt {
+    padding: 4.0rem 0 0 0;
+}
+body.bx-dev-page-body-single div.bx-std-page-menu,
+body.bx-dev-page-body-columns div.bx-std-page-menu {
+    -webkit-flex-basis: 67px;
+    flex-basis: 67px;
+}
+body.bx-dev-page-body-single div.bx-std-page-menu div.bx-std-page-column-cnt,
+body.bx-dev-page-body-columns div.bx-std-page-menu div.bx-std-page-column-cnt {
+    padding-left: 0;
+    padding-right: 0;
+}
+body.bx-dev-page-body-columns div.bx-std-page-menu div.bx-std-page-column-cnt {
+    -webkit-flex-direction: column;
+    flex-direction: column;
+}
+body.bx-dev-page-body-single div.bx-std-page-column.bx-std-page-menu .bx-std-pcm-actions,
+body.bx-dev-page-body-columns div.bx-std-page-column.bx-std-page-menu .bx-std-pcm-actions {
+    width: 2rem;
+    height: 2rem;
+
+    line-height: 2rem;
+}
+body.bx-dev-page-body-columns div.bx-std-page-column.bx-std-page-content {
+    overflow-x: visible;
+}
+body.bx-dev-page-body-columns div.bx-std-page-column-cnt {
+    padding: 0px;
+}
+
+
+/*--- General ---*/
+div.bx-dev-cb {
+    clear: both;
+    font-size: 0px;
+}
+
+
+/*--- Left Menu ---*/
+div.bx-dev-menu-main {
+    position: relative;
+    width: 65px;
+    border-right: 2px solid transparent;
+}
+
+div.bx-dev-mm-cnt {
+    padding: 8px 16px 16px 16px;
+}
+
+div.bx-dev-menu-main li {
+    width:32px;
+    height:32px;
+    margin: 0px 0px 16px 0px;
+    border-radius: 10px 10px 10px 10px;
+}
+
+
+div.bx-dev-menu-main li:hover,
+div.bx-dev-menu-main li.bx-def-color-bg-active {
+    opacity:1;
+    -moz-box-shadow: 0 0 9px 0px #ffffff;
+    -webkit-box-shadow: 0 0 9px 0px #ffffff;
+    box-shadow: 0 0 9px 0px #ffffff;
+}
+
+div.bx-dev-menu-main div.bx-dev-mm-item {
+    position: relative;
+    width: 32px;
+    height: 32px;
+} 
+
+div.bx-dev-menu-main div.bx-dev-mm-item img {
+    width: 32px;
+    height: 32px;
+}
+
+div.bx-dev-menu-main div.bx-dev-mm-item i.sys-icon {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width:32px;
+    height: 32px;
+
+    color:white;
+    font-size:20px;
+    text-align:center;
+    text-shadow:none;
+    line-height: 32px;
+}
+
+/*--- Page Content ---*/
+.bx-dev-page-body-columns div.bx-std-page-column-cnt,
+div.bx-dev-page-columns-cnt {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: stretch;
+    align-items: stretch;
+    width: 100%;
+}
+
+div.bx-dev-page-columns-cnt {
+    height: calc(100vh - 5rem);
+}
+
+div.bx-dev-page-column.bx-dev-page-menu {
+    position: relative;
+    -webkit-flex: 0 0 20rem;
+    flex: 0 0 20rem;
+    width: 20rem;
+    
+    overflow-y: auto;
+}
+div.bx-dev-page-column.bx-dev-page-menu::-webkit-scrollbar {
+  display: none;
+}
+
+div.bx-dev-page-column.bx-dev-page-menu div.bx-dev-page-column-cnt {
+    position: -webkit-sticky;
+    position: sticky;
+    top: 0;
+}
+
+div.bx-dev-page-column.bx-dev-page-content {
+    position: relative;
+    -webkit-flex: 2 1 auto;
+    flex: 2 1 auto;
+
+    height: 100%;
+
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+div.bx-dev-page-column.bx-dev-page-content .bx-dev-page-column-cnt {
+    padding: 1.0rem;
+}

+ 8 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/template/page_content.html

@@ -0,0 +1,8 @@
+<div class="bx-dev-page-columns-cnt">
+    <div class="bx-dev-page-column bx-dev-page-menu">
+        <div class="bx-dev-page-column-cnt p-4" id="bx-dev-pc-menu">__page_menu_code__</div>
+    </div>
+    <div class="bx-dev-page-column bx-dev-page-content">
+        <div class="bx-dev-page-column-cnt" id="bx-dev-pc-content">__page_main_code__</div>
+    </div>
+</div>

+ 8 - 0
modules/boonex/developer/updates/14.0.3_14.0.4/source/template/page_menu.html

@@ -0,0 +1,8 @@
+<div class="bx-std-pcm-title" id="bx-std-pc-title">
+    <div class="bx-std-pcm-title-cnt mx-4 pb-6 border-b border-gray-100/20">
+        <a class="bx-std-pcm-actions flex justify-center items-center w-10 h-10 bg-gray-200 hover:bg-gray-100 border border-black/10 rounded-lg text-gray-600" href="javascript:void(0)" onclick="__actions_onclick__" title="<bx_text_attribute:_adm_txt_show_actions />">
+            <img src="__actions_icon__" />
+        </a>
+    </div>
+</div>
+<div class="bx-std-pcm-menu" id="bx-std-pc-menu">__menu__</div>

+ 51 - 0
modules/boonex/events/updates/14.0.6_14.0.7/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Events',
+    'version_from' => '14.0.6',
+    'version_to' => '14.0.7',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/events/updates/update_14.0.6_14.0.7/',
+    'home_uri' => 'events_update_1406_1407',
+
+    'module_dir' => 'boonex/events/',
+    'module_uri' => 'events',
+
+    'db_prefix' => 'bx_events_',
+    'class_prefix' => 'BxEvents',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Events',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/events/updates/14.0.6_14.0.7/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxEventsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 4 - 0
modules/boonex/events/updates/14.0.6_14.0.7/install/langs/en.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string_add name="_bx_events_alert_action_info_reminder_follow_context"><![CDATA[New reminders were posted in a space you follow.]]></string_add>
+</resources>

+ 93 - 0
modules/boonex/events/updates/14.0.6_14.0.7/source/classes/BxEventsMenuViewActionsAll.php

@@ -0,0 +1,93 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Events Events
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * View entry all actions menu
+ */
+class BxEventsMenuViewActionsAll extends BxBaseModGroupsMenuViewActionsAll
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_events';
+
+        parent::__construct($aObject, $oTemplate);
+    }
+
+    protected function _getMenuAttrs ($aMenuItem)
+    {
+        $s = parent::_getMenuAttrs ($aMenuItem);
+        if ('ical-export' == $aMenuItem['name']) {
+            $CNF = $this->_oModule->_oConfig->CNF;
+            $s .= ' download="' . title2uri($this->_aContentInfo[$CNF['FIELD_TITLE']]) . '.ics"';
+        }
+        return $s;
+    }
+
+    protected function _getMenuItemJoinEventProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemEditEventCover($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemEditEventProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemEventQuestionnaire($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemEventSessions($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemEventPricing($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemInviteToEvent($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemDeleteEventProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemApproveEventProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
+    protected function _getMenuItemProfileCheckIn($aItem)
+    {
+        $a = $this->_getMenuItemByNameActions($aItem);
+        if (bx_is_api()){
+
+          //  print_r($this );
+            $a['display_type'] = 'callback';
+            $a['data'] = ['request_url' => $this->_oModule->_aModule['name'] . '/check_in/&params[]=' . $this->_aMarkers['content_id'], 'on_callback' => 'hide'];
+            return $a;
+        }
+        return $a;
+    }
+}
+
+/** @} */

+ 935 - 0
modules/boonex/events/updates/14.0.6_14.0.7/source/classes/BxEventsModule.php

@@ -0,0 +1,935 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Events Events
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+use Spatie\CalendarLinks\Link;
+
+/**
+ * Events profiles module.
+ */
+class BxEventsModule extends BxBaseModGroupsModule implements iBxDolCalendarService 
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->_aSearchableNamesExcept = array_merge($this->_aSearchableNamesExcept, [
+            $CNF['FIELD_TIMEZONE'],
+            $CNF['FIELD_JOIN_CONFIRMATION'],
+            $CNF['FIELD_REMINDER']
+        ]);
+
+        $this->_aBrowsingFiltersKeys = array_merge($this->_aBrowsingFiltersKeys, [
+            'by_location_country', 'by_location_state', 'by_location_city',
+            'by_date', 'date_start', 'date_end', 'timezone'
+        ]);
+    }
+
+    public function actionCheckIn()
+    {
+    	$iId = (int)bx_get('id');
+    	if(empty($iId) || empty($this->_iProfileId))
+            return echoJson(['code' => 1]);
+
+        if(!$this->serviceCheckIn($iId, $this->_iProfileId))
+            return echoJson(['code' => 2]);
+
+        return echoJson(['code' => 0, 'reload' => 1]);
+    }
+
+    public function actionCalendarSync($iContentId = 0)
+    {
+        $aContentInfo = (int)$iContentId ? $this->_oDb->getContentInfoById((int)$iContentId) : null;
+        if (!$aContentInfo) {
+            $this->_oTemplate->displayPageNotFound();
+            exit;
+        }
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== $this->checkAllowedView($aContentInfo)) {
+            $this->_oTemplate->displayAccessDenied();
+            exit;
+        }
+
+        $CNF = $this->_oConfig->CNF;
+
+        if (!isset($aContentInfo['date_start']) || !$aContentInfo['date_start'] || !isset($aContentInfo['date_end']) || !$aContentInfo['date_end'] || !$aContentInfo[$CNF['FIELD_TIMEZONE']] || empty($CNF['OBJECT_METATAGS'])) {
+            $this->_oTemplate->displayPageNotFound();
+            exit;
+        }
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $sLocation = $oMetatags && $oMetatags->locationsIsEnabled() ? $oMetatags->locationsString($aContentInfo[$CNF['FIELD_ID']], false) : '';
+
+        $oDateStart = new DateTime('@' . $aContentInfo['date_start']);
+        $oDateEnd = new DateTime('@' . ($aContentInfo['date_end'] > $aContentInfo['repeat_stop'] ? $aContentInfo['date_end'] : $aContentInfo['repeat_stop']));
+
+        $oTz = new DateTimeZone($aContentInfo[$CNF['FIELD_TIMEZONE']]);
+        $oDateStart->setTimezone($oTz);
+        $oDateEnd->setTimezone($oTz);
+
+        $oICalLink = $oDateStart && $oDateEnd ? Link::create(
+            $aContentInfo[$CNF['FIELD_TITLE']],
+            $oDateStart,
+            $oDateEnd
+        ) : null;
+
+        if ($sLocation)
+            $oICalLink->address($sLocation);
+
+        $s = $oICalLink->ics();
+
+        if (!preg_match('/^data:([a-zA-Z0-9\/]+);charset=[a-zA-Z0-9]+;base64,(.*)$/', $s, $m)) {
+            $this->_oTemplate->displayErrorOccured();
+            exit;
+        }
+
+        header('Content-type: ' . $m[1] . '; charset=utf-8');
+        header('Content-Disposition: inline; filename="event.ics"');
+        echo base64_decode($m[2]);
+    }
+
+    public function actionCalendarData()
+    {
+        $aParams = [
+            'type' => ($sType = bx_get('type')) !== false ? $sType : 'browse',
+            'start' => bx_get('start'),
+            'end' => bx_get('end')
+        ];
+        if(($iEventId = bx_get('event')) !== false)
+            $aParams['event_id'] = (int)$iEventId;
+        if(($iProfileId = bx_get('profile_id')) !== false)
+            $aParams['profile_id'] = (int)$iProfileId;
+        if(($iContextId = bx_get('context_id')) !== false)
+            $aParams['context_id'] = (int)$iContextId;
+    
+        $aResult = $this->serviceCalendarData($aParams);
+        if(isset($aResult['data']))
+            $aResult = $aResult['data'];
+
+        header('Content-Type: application/json; charset=utf-8');
+        echo json_encode($aResult);
+    }
+
+    public function serviceCheckIn($iId, $iProfileId = 0)
+    {
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+        if(!$iProfileId)
+            return false;
+        
+        $aDataEntry = $this->_oDb->getContentInfoById($iId);
+        if(empty($aDataEntry) || !is_array($aDataEntry))
+            return false;
+
+        if($this->checkAllowedCheckIn($aDataEntry) !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        if(!$this->_oDb->checkIn($iProfileId, $iId))
+            return false;
+
+         /**
+         * @hooks
+         * @hookdef hook-bx_events-check_in 'bx_events', 'check_in' - hook in on somebody check-in some event 
+         * - $unit_name - equals `bx_events`
+         * - $action - equals `check_in` 
+         * - $object_id - event_id
+         * - $sender_id - current user's profile_id
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `event_profile_id` - [int] profile_id from checked event
+         * @hook @ref hook-bx_events-check_in
+         */
+        bx_alert($this->getName(), 'check_in', $iId, $iProfileId, [
+            'event_profile_id' => $aDataEntry['profile_id'],
+        ]);
+
+        return true;
+    }
+
+    public function serviceCalendarData($aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if($this->_bIsApi && is_string($aParams)) {
+            $aParams = json_decode($aParams, true);
+            if(!empty($aParams['params']))
+                $aParams = $aParams['params'];
+            
+             if (isset($aParams['profile_id']) && $aParams['profile_id'] == '{profile_id}')
+                $aParams['profile_id'] =  bx_get_logged_profile_id();
+        }
+
+        $sModule = $this->getName();
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+
+        $iNow = time();
+
+        if(empty($aParams['start']))
+            $sStart = date('d.m.Y', $iNow);
+        else if(is_numeric($aParams['start']))
+            $sStart = date('d.m.Y', $aParams['start']);
+        else
+            $sStart = $aParams['start'];
+
+        if(empty($aParams['end']))
+            $sEnd = date('d.m.Y', $iNow + 3600 * 24 * 30);
+        else if(is_numeric($aParams['end']))
+            $sEnd = date('d.m.Y', $aParams['end']);
+        else
+            $sEnd = $aParams['end']; 
+
+        $iContentId = 0;
+        $iContextId = 0;
+        $aSQLPart = [];
+        switch($aParams['type']) {
+            case 'event':
+                $iContentId = !empty($aParams['event_id']) ? (int)$aParams['event_id'] : 0;
+                $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+                if(CHECK_ACTION_RESULT_ALLOWED !== $this->checkAllowedView($aContentInfo))
+                    $this->_oTemplate->displayAccessDenied();
+                break;
+
+            case 'author':
+                $iProfileId = !empty($aParams['profile_id']) ? (int)$aParams['profile_id'] : 0;
+
+                $aSQLPart = [
+                    'where' => $this->_oDb->prepareAsString("AND `{$CNF['TABLE_ENTRIES']}`.`{$CNF['FIELD_AUTHOR']}`=?", $iProfileId)
+                ];
+                break;
+
+            case 'joined':
+                $iProfileId = !empty($aParams['profile_id']) ? (int)$aParams['profile_id'] : 0;
+
+                $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+                if(!$oConnection)
+                    return [];
+
+                $aEventsJoined = $oConnection->getConnectedContent($iProfileId, true);
+                if(empty($aEventsJoined) || !is_array($aEventsJoined))
+                    return [];
+
+                $aSQLPart = [
+                    'join' => "INNER JOIN `sys_profiles` ON `{$CNF['TABLE_ENTRIES']}`.`{$CNF['FIELD_ID']}`=`sys_profiles`.`content_id` AND `sys_profiles`.`type`='{$sModule}'",
+                    'where' => "AND `sys_profiles`.`id` IN (" . $this->_oDb->implode_escape($aEventsJoined) . ")"
+                ];
+                break;
+                
+            case 'followed':
+                $iProfileId = !empty($aParams['profile_id']) ? (int)$aParams['profile_id'] : 0;
+
+                $oConnection = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+                if(!$oConnection)
+                    return [];
+
+                $aEventsFollowed = $oConnection->getConnectedContent($iProfileId, false);
+                if(empty($aEventsFollowed) || !is_array($aEventsFollowed))
+                    return [];
+
+                $aSQLPart = [
+                    'join' => "INNER JOIN `sys_profiles` ON `{$CNF['TABLE_ENTRIES']}`.`{$CNF['FIELD_ID']}`=`sys_profiles`.`content_id` AND `sys_profiles`.`type`='{$sModule}'",
+                    'where' => "AND `sys_profiles`.`id` IN (" . $this->_oDb->implode_escape($aEventsFollowed) . ")"
+                ];
+                break;
+
+            case 'context':
+                $iContextId = !empty($aParams['context_id']) ? (int)$aParams['context_id'] : 0;
+                if(!$this->serviceIsEnableForContext($iContextId))
+                    return [];
+
+                $aSQLPart = $oPrivacy->getContentByGroupAsSQLPart(-$iContextId);
+                break;
+            
+            default:
+                $aSQLPart = $oPrivacy->getContentPublicAsSQLPart(0, $oPrivacy->getPartiallyVisiblePrivacyGroups());
+        }
+
+        // get entries
+        $aEntries = $this->_oDb->getEntriesByDate($sStart, $sEnd, $iContentId, $aSQLPart);
+        
+        foreach($aEntries as &$aEntry){
+            $aEntry['cover'] = $this->serviceGetCover($aEntry['id']);
+        }
+        
+        /**
+         * @hooks
+         * @hookdef hook-bx_events-calendar_data 'bx_events', 'calendar_data' - hook in get data for calendar
+         * - $unit_name - equals `bx_events`
+         * - $action - equals `calendar_data` 
+         * - $object_id - not used 
+         * - $sender_id - not used 
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `params` - [array] array of paramaretes in calendar 
+         *      - `event` - [int] event id
+         *      - `context_id` - [int] profile_id for context of current event
+         *      - `start` - [string] start date in format date('d.m.Y')
+         *      - `end` - [string] end date in format date('d.m.Y')
+         *      - `sql_part` - [array] by ref, sql for data selection, can be overridden in hook processing
+         *      - `data` - [array] by ref, data for calendar, can be overridden in hook processing
+         * @hook @ref hook-bx_events-calendar_data
+         */
+        bx_alert($this->getName(), 'calendar_data', 0, false, array(
+            'params' => $aParams,
+            'event' => $iContentId,
+            'context_id' => $iContextId,
+            'start' => $sStart,
+            'end' =>  $sEnd,
+            'sql_part' => &$aSQLPart,
+            'data' => &$aEntries,
+        ));
+
+        return [
+            'params' => ['start' => strtotime($sStart), 'end' => strtotime($sEnd)],
+            'data' => $aEntries
+        ];
+    }
+
+    public function serviceIsIcalExportAvaliable($iContentId)
+    {
+        return true;
+        $aContentInfo = $iContentId ? $this->_oDb->getContentInfoById($iContentId) : false;
+        if ($aContentInfo)
+            return false;
+        
+        return $aContentInfo['date_start'] && $aContentInfo['date_end'];
+    }
+    
+    public function decodeDataAPI($aData, $aParams = [])
+    {
+        $CNF = $this->_oConfig->CNF;
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+
+        $aResult = parent::decodeDataAPI($aData, $aParams);
+        $aResult = array_merge($aResult, [
+            'date_start' => $aData[$CNF['FIELD_DATE_START']],
+            'date_end' => $aData[$CNF['FIELD_DATE_END']],
+            'description' => $aData[$CNF['FIELD_TEXT']],
+            'location_data' => $oMetatags->locationGet($aData[$CNF['FIELD_ID']]),
+            'threshold' => $aData['threshold'],
+            'location' => $oMetatags->locationsString($aData[$CNF['FIELD_ID']], false)
+        ]);
+        return $aResult;
+    }
+
+    public function serviceGetCalendarEntries($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $oConn = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+        $aData = $oConn->getConnectedContent($iProfileId);
+        $aData2 = array(0);
+        foreach($aData as $iProfileId2){
+            $oProfile = BxDolProfile::getInstance($iProfileId2);
+            array_push($aData2, $oProfile->getContentId());
+        }
+        $aSQLPart['where'] = " AND `bx_events_data`.`id` IN(" . implode(',', $aData2) . ")";
+        return $this->_oDb->getEntriesByDate(bx_get('start'), bx_get('end'), null, $aSQLPart);
+	}
+
+    public function serviceGetSafeServices()
+    {
+        return array_merge(parent::serviceGetSafeServices(), [
+            'CheckIn' => '',
+            'BrowsePastProfiles' => '',
+            'Calendar' => '',
+            'CalendarData' => ''
+        ]);
+    }
+
+    public function serviceEntitySessions($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if(!$iProfileId)
+            return '';
+
+        $aProfileInfo = BxDolProfileQuery::getInstance()->getInfoById($iProfileId);
+        if(empty($aProfileInfo) || !is_array($aProfileInfo))
+            return '';
+        
+        $aContentInfo = $this->_oDb->getContentInfoById($aProfileInfo['content_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return '';
+
+        if($this->checkAllowedEdit($aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+            return MsgBox(_t('_Access denied'));
+
+        $oGrid = BxDolGrid::getObjectInstance($CNF['OBJECT_GRID_SESSIONS_MANAGE']);
+        if(!$oGrid)
+            return '';
+
+        if($this->_bIsApi){
+            return [
+                bx_api_get_block('grid', $oGrid->getCodeAPI())
+            ];
+            
+        }
+
+        
+        return $oGrid->getCode();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_events Events
+     * @subsection bx_events-browse Browse
+     * @subsubsection bx_events-browse_upcoming_profiles browse_upcoming_profiles
+     * 
+     * @code bx_srv('bx_events', 'browse_upcoming_profiles', [...]); @endcode
+     * 
+     * Browse upcoming events
+     * 
+     * @param $bDisplayEmptyMsg show "empty" message or not if nothing found
+     * @param $bAjaxPaginate use AJAX paginate or not
+     *
+     * @see BxEventsModule::serviceBrowseUpcomingProfiles
+     */
+    /** 
+     * @ref bx_events-browse_upcoming_profiles "browse_upcoming_profiles"
+     */
+    public function serviceBrowseUpcomingProfiles ($aParams = false)
+    {
+        $bDisplayEmptyMsg = false;
+        if(isset($aParams['empty_message'])) {
+            $bDisplayEmptyMsg = (bool)$aParams['empty_message'];
+            unset($aParams['empty_message']);
+        }
+
+        $bAjaxPaginate = true;
+        if(isset($aParams['ajax_paginate'])) {
+            $bAjaxPaginate = (bool)$aParams['ajax_paginate'];
+            unset($aParams['ajax_paginate']);
+        }
+
+        return $this->_serviceBrowse ('upcoming', $aParams, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_events Events
+     * @subsection bx_events-browse Browse
+     * @subsubsection bx_events-browse_upcoming_connected_profiles browse_upcoming_connected_profiles
+     * 
+     * @code bx_srv('bx_events', 'browse_upcoming_connected_profiles', [...]); @endcode
+     * 
+     * Browse upcoming connected (followed) events
+     * 
+     * @param $bDisplayEmptyMsg show "empty" message or not if nothing found
+     * @param $bAjaxPaginate use AJAX paginate or not
+     *
+     * @see BxEventsModule::serviceBrowseUpcomingConnectedProfiles
+     */
+    /** 
+     * @ref bx_events-browse_upcoming_connected_profiles "browse_upcoming_connected_profiles"
+     */
+    public function serviceBrowseUpcomingConnectedProfiles ($aParams = false)
+    {
+        if(!is_array($aParams) || empty($aParams['object']) || empty($aParams['profile']))
+            return '';
+
+        $bDisplayEmptyMsg = false;
+        if(isset($aParams['empty_message'])) {
+            $bDisplayEmptyMsg = (bool)$aParams['empty_message'];
+            unset($aParams['empty_message']);
+        }
+
+        $bAjaxPaginate = true;
+        if(isset($aParams['ajax_paginate'])) {
+            $bAjaxPaginate = (bool)$aParams['ajax_paginate'];
+            unset($aParams['ajax_paginate']);
+        }
+
+        $aDefaults = array(
+            'type' => 'content',
+            'mutual' => false,
+            'profile2' => 0
+        );
+
+        return $this->_serviceBrowse ('upcoming_connected', array_merge($aDefaults, $aParams), BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    public function serviceBrowseRecentProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = true, $sUnitView = false)
+    {
+        $sMode = 'recent';
+
+        $sFilterSelector = '#' . $this->_oConfig->getHtmlIds('popup_bfilters_recent');
+        $aFilterParams = ['mode' => $sMode];
+
+        $aParams = [
+            'filters' => [
+                'values' => [],
+                'onclick' => $this->_oConfig->getJsObject('main') . ".changeBrowsingFilters(this, '" . $sFilterSelector . "', " . json_encode($aFilterParams) . ")"
+            ]
+        ];
+        if ($sUnitView) {
+            $aParams['unit_view'] = $sUnitView;
+        }
+        return $this->_serviceBrowse ($sMode, $aParams, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_events Events
+     * @subsection bx_events-browse Browse
+     * @subsubsection bx_events-browse_past_profiles browse_past_profiles
+     * 
+     * @code bx_srv('bx_events', 'browse_past_profiles', [...]); @endcode
+     * 
+     * Browse past events
+     * 
+     * @param $bDisplayEmptyMsg show "empty" message or not if nothing found
+     * @param $bAjaxPaginate use AJAX paginate or not
+     *
+     * @see BxEventsModule::serviceBrowsePastProfiles
+     */
+    /** 
+     * @ref bx_events-browse_past_profiles "browse_past_profiles"
+     */
+    public function serviceBrowsePastProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('past', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_events Events
+     * @subsection bx_events-browse Browse
+     * @subsubsection bx_events-browse_past_profiles browse_past_profiles_by_params
+     * 
+     * @code bx_srv('bx_events', 'browse_past_profiles_by_params', [...]); @endcode
+     * 
+     * Browse past events
+     * 
+     * @param $aParams array with all parameters
+     *
+     * @see BxEventsModule::serviceBrowsePastProfilesByParams
+     */
+    /** 
+     * @ref bx_events-browse_past_profiles_by_params "browse_past_profiles_by_params"
+     */
+    public function serviceBrowsePastProfilesByParams ($aParams)
+    {
+        $bDisplayEmptyMsg = false;
+        if(isset($aParams['empty_message'])) {
+            $bDisplayEmptyMsg = (bool)$aParams['empty_message'];
+            unset($aParams['empty_message']);
+        }
+
+        $bAjaxPaginate = true;
+        if(isset($aParams['ajax_paginate'])) {
+            $bAjaxPaginate = (bool)$aParams['ajax_paginate'];
+            unset($aParams['ajax_paginate']);
+        }
+        return $this->_serviceBrowse ('past', $aParams, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_events Events
+     * @subsection bx_events-page_blocks Page Blocks
+     * @subsubsection bx_events-calendar calendar
+     * 
+     * @code bx_srv('bx_events', 'calendar', [...]); @endcode
+     * 
+     * Shows event or events calendar
+     * 
+     * @param $aData additional data to point which events to show, leave empty to show all events, specify event's ID in 'event' array key to show calendar for one event only, specify context's ID in 'context_id' array key to show calendar for one context events only. If only one event is specified then it will show calendar only if it's repeating event.
+     * @param $sTemplate template to use to show calendar, or leave empty for default template, possible options: calendar.html, calendar_compact.html
+     * @return HTML string with calendar to display on the site, all necessary CSS and JS files are automatically added to the HEAD section of the site HTML. On error empty string is returned.
+     *
+     * @see BxEventsModule::serviceCalendar
+     */
+    /** 
+     * @ref bx_events-calendar "calendar"
+     */
+    public function serviceCalendar($aData = [], $sTemplate = 'calendar.html')
+    {
+        if(isset($aData['event']) && !isset($aData['type']))
+            $aData['type'] = 'event';
+
+        if(isset($aData['profile_id']) && !isset($aData['type']))
+            $aData['type'] = 'joined';
+
+        if(isset($aData['context_id'])) {
+            if(!$this->serviceIsEnableForContext($aData['context_id']))
+                return '';
+
+            $aData['type'] = 'context';
+        }
+
+        if(!isset($aData['type']))
+            $aData['type'] = 'browse';
+
+        $oCalendar = new BxTemplCalendar([
+            'eventSources' => [
+                bx_append_url_params(BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'calendar_data', $aData),
+            ],
+        ], $this->_oTemplate);
+
+        if (bx_is_api())
+            return [bx_api_get_block('calendar', ['request_url' => $this->getName() . '/calendar_data&params[]=' . json_encode(['params' => $aData])])];
+        
+        $this->_oTemplate->addCss(['main.css']);
+        return $oCalendar->display($sTemplate);
+    }
+
+    public function serviceSessions($iContentId = 0)
+    {
+        if(!$iContentId)
+            $iContentId = (int)bx_get('id');
+        if(!$iContentId)
+            return '';
+        
+        return $this->_oTemplate->getBlockSessions($iContentId);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_events Events
+     * @subsection bx_events-other Other
+     * @subsubsection bx_events-process_reminders process_reminders
+     * 
+     * @code bx_srv('bx_events', 'process_reminders'); @endcode
+     * 
+     * Send remiders to event's participants.
+     *
+     * It must be processed once every hour for the proper processing.
+     * @return nothing
+     *
+     * @see BxEventsModule::serviceProcessReminders
+     */
+    /** 
+     * @ref bx_events-process_reminders "process_reminders"
+     */
+    public function serviceProcessReminders()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iNow = time();
+        $bUseIn = $this->_oConfig->isInternalNotifications();
+        $sModule = $this->getName();
+
+        // get all events for today and tomorrow, since the max reminder is 24 hours
+        $aEntries = $this->_oDb->getEntriesByDate('@' . time(), '@' . (time() + 3600 * getParam('bx_events_reminder_interval')));
+
+        foreach($aEntries as $aEntry) {
+            $aReminders = $bUseIn ? [$aEntry['reminder']] : [24, 1];
+            if(!$aReminders)
+                continue;
+
+            $oEventProfile = BxDolProfile::getInstanceByContentAndType($aEntry[$CNF['FIELD_ID']], $sModule);
+            if(!$oEventProfile)
+                continue;
+
+            $iEventProfileId = $oEventProfile->id();
+            foreach($aReminders as $iReminder) {
+                $iTimestamp = $aEntry['start_utc'] - (3600 * $iReminder);
+                if($iNow <= ($iTimestamp - 3600) || $iNow >= $iTimestamp)
+                    continue;
+
+                if($bUseIn)
+                    $this->sendReminders($aEntry);
+
+                /**
+                 * @hooks
+                 * @hookdef hook-bx_events-reminder 'bx_events', 'reminder' - hook on enent's reminder
+                 * - $unit_name - equals `bx_events`
+                 * - $action - equals `reminder` 
+                 * - $object_id - event_id
+                 * - $sender_id - event profile_id
+                 * - $extra_params - array of additional params with the following array keys:
+                 *      - `object_author_id` - [int] event profile_id 
+                 *      - `reminder` - [int] reminder id
+                 * @hook @ref hook-bx_events-reminder
+                 */
+                bx_alert($sModule, 'reminder', $aEntry[$CNF['FIELD_ID']], $iEventProfileId, array(
+                    'object_author_id' => $iEventProfileId,
+                    'reminder' => $iReminder
+                ));
+            }
+        }
+    }
+
+    public function serviceGetNotificationsData()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResults = parent::serviceGetNotificationsData();
+        
+        $sModule = $this->_aModule['name'];
+        $aSettingsTypes = ['follow_context'];
+
+        $aResults['handlers'] = array_merge($aResults['handlers'], [
+            ['group' => $sModule . '_reminder', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'reminder', 'module_name' => $sModule, 'module_method' => 'get_notifications_reminder', 'module_class' => 'Module', 'module_event_privacy' => $CNF['OBJECT_PRIVACY_VIEW_NOTIFICATION_EVENT']],
+        ]);
+
+        $aResults['settings'] = array_merge($aResults['settings'], [
+            ['group' => 'reminder', 'unit' => $sModule, 'action' => 'reminder', 'types' => $aSettingsTypes],
+        ]);
+
+        $aResults['alerts'] = array_merge($aResults['alerts'], [
+            ['unit' => $sModule, 'action' => 'reminder'],
+        ]);
+
+        return $aResults;
+    }
+
+    public function serviceGetNotificationsInsertData($oAlert, $aHandler, $aDataItems)
+    {
+        if($oAlert->sAction != 'reminder' || empty($aDataItems) || !is_array($aDataItems) || empty($oAlert->aExtras['reminder']))
+            return $aDataItems;
+
+        foreach($aDataItems as $iIndex => $aDataItem) {
+            $aContent = [];
+            if(!empty($aDataItem['content']))
+                $aContent = unserialize($aDataItem['content']);
+
+            $aContent['reminder'] = (int)$oAlert->aExtras['reminder'];
+
+            $aDataItems[$iIndex]['content'] = serialize($aContent);
+        }
+
+        return $aDataItems;
+    }
+
+    public function serviceGetNotificationsReminder($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!isset($aEvent['content']['reminder']))
+            return [];
+
+        $iContentId = (int)$aEvent['object_id'];
+        $oEventProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oEventProfile)
+            return [];
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return [];
+
+        return [
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => bx_absolute_url(str_replace(BX_DOL_URL_ROOT, '', $oEventProfile->getUrl()), '{bx_url_root}'),
+            'entry_caption' => $oEventProfile->getDisplayName(),
+            'entry_author' => $oEventProfile->id(),
+            'lang_key' => '_bx_events_txt_ntfs_reminder_' . $aEvent['content']['reminder']
+        ];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_events Events
+     * @subsection bx_events-internal Internal
+     * @subsubsection bx_events-get_timeline_post get_timeline_post
+     *
+     * @code bx_srv('bx_events', 'get_timeline_post', [...]); @endcode
+     * 
+     * Get Timeline post. It's needed for Timeline module.
+     * 
+     * @param $aEvent timeline event array from Timeline module
+     * @return array in special format which is needed specifically for Timeline module to display the data.
+     *
+     * @see BxEventsModule::serviceGetTimelinePost
+     */
+    /** 
+     * @ref bx_events-get_timeline_post "get_timeline_post"
+     */
+    public function serviceGetTimelinePost($aEvent, $aBrowseParams = array())
+    {
+        $a = parent::serviceGetTimelinePost($aEvent, $aBrowseParams);
+        if($a === false)
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return $a;
+
+        $CNF = $this->_oConfig->CNF;
+
+        $oDateStart = date_create('@' . $aContentInfo['date_start']);
+        if ($oDateStart){
+            $oDateStart->setTimezone(new DateTimeZone($aContentInfo['timezone'] ? $aContentInfo['timezone'] : 'UTC'));
+            $a['content']['date_start'] = $aContentInfo['date_start'];
+        }
+        
+        $oDateEnd = date_create('@' . $aContentInfo['date_end']);
+        if ($oDateEnd){
+            $oDateEnd->setTimezone(new DateTimeZone($aContentInfo['timezone'] ? $aContentInfo['timezone'] : 'UTC'));
+            $a['content']['date_end'] = $aContentInfo['date_end'];
+        }
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $sLocationString = $oMetatags ? $oMetatags->locationsString($aContentInfo[$CNF['FIELD_ID']], false) : false;
+
+        if(!$this->_bIsApi)
+            $a['content']['raw'] = $this->_oTemplate->parseHtmlByName('timeline_post.html', [
+                'title' => $a['content']['title'],
+                'title_attr' => bx_html_attribute($a['content']['title']),
+                'url' => $a['content']['url'],
+                'bx_if:date' => [
+                    'condition' => $oDateStart,
+                    'content' => [
+                        'date' => $oDateStart ? bx_time_js($aContentInfo['date_start'], BX_FORMAT_DATE_TIME, true) : '',
+                        'date_c' => $oDateStart->format('c'),
+                ]],
+                'bx_if:location' => array(
+                    'condition' => !!$sLocationString,
+                    'content' => array('location' => $sLocationString),
+                ),
+            ]);
+
+        return $a;
+    }
+    
+    public function actionIntervals()
+    {
+        $sAction = bx_get('a');
+        $sMethodName = 'subaction' . ucfirst($sAction);
+        if (!method_exists($this, $sMethodName)) {
+            $this->_oTemplate->pageNotFound();
+            return;
+        }
+        $this->$sMethodName();
+    }
+
+    public function subactionRestore($iContentId = 0)
+    {
+        if (!$iContentId)
+            $iContentId = (int)bx_get('c');
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+
+        if (
+            ($aContentInfo && CHECK_ACTION_RESULT_ALLOWED !== $this->checkAllowedEdit ($aContentInfo))
+            ||
+            (!$aContentInfo && CHECK_ACTION_RESULT_ALLOWED !== $this->checkAllowedAdd ())
+        ) {
+            $this->_oTemplate->displayAccessDenied();
+            exit;
+        }
+
+        $a = $this->_oDb->getIntervals($iContentId);
+        foreach ($a as $iIntervalId => $r) {
+            $a[$iIntervalId]['file_id'] = $r['interval_id'];
+            $a[$iIntervalId]['repeat_stop'] = bx_process_output ($r['repeat_stop'], BX_DATA_DATE_TS);
+        }
+
+        if ('json' == bx_get('f')) {
+            header('Content-type: text/html; charset=utf-8');
+            echo json_encode($a);
+        }
+    }
+
+    public function subactionDelete()
+    {
+        header('Content-type: text/html; charset=utf-8');
+
+        $iIntervalId = (int)bx_get('id');
+
+        if (!($aContentInfo = $this->_oDb->getContentInfoByIntervalId($iIntervalId))) {
+            echo _t('_sys_request_page_not_found_cpt');
+        }
+        elseif (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedEdit ($aContentInfo))) {
+            echo $sMsg;
+        } 
+        elseif (!$this->_oDb->deleteIntervalById($iIntervalId)) {
+            echo _t('_sys_txt_error_occured');
+        } 
+        else {
+            echo 'ok';
+        }
+    }
+
+    public function checkAllowedCheckIn($aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sError = _t('_sys_txt_access_denied');
+        if(!$this->_iProfileId || !$this->isFan($aDataEntry[$CNF['FIELD_ID']], $this->_iProfileId))
+            return $sError;
+
+        if(!$this->isOngoing($aDataEntry))
+            return $sError;
+
+        if($this->_oDb->isCheckedIn($this->_iProfileId, $aDataEntry[$CNF['FIELD_ID']]))
+            return $sError;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkAllowedIcalExport ($aDataEntry, $isPerformAction = false)
+    {
+        return $aDataEntry['date_start'] && $aDataEntry['date_end'] ? CHECK_ACTION_RESULT_ALLOWED : _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * Send reminder to event's paritcipants
+     * @param $aContentInfo content info
+     * @return number of sent reminders
+     */ 
+    protected function sendReminders($aContentInfo)
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+        if (!$oConnection)
+            return false;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aContentInfo[$CNF['FIELD_ID']], $this->getName());
+        if (!$oGroupProfile)
+            return false;
+
+        $aParticipants = $oConnection->getConnectedInitiators($oGroupProfile->id(), true);
+        if (!$aParticipants)
+            return true;
+
+        $oDateBegin = new DateTime();
+        $oDateBegin->setTimestamp($aContentInfo['start_utc']);
+        $oDateBegin->setTimezone(new DateTimeZone($aContentInfo['timezone'] ? $aContentInfo['timezone'] : 'UTC'));
+        $sEntryBegin = $oDateBegin->format('r');
+        $sEntryBeginShort = date(getParam($CNF['PARAM_FORMAT_DATE']), $oDateBegin->getTimestamp());
+        $oDateBegin->setTimezone(new DateTimeZone('UTC'));
+        $sEntryBeginUTC = $oDateBegin->format('c');
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $sLocationString = $oMetatags->locationsString($aContentInfo[$CNF['FIELD_ID']], false);
+
+        $iCounter = 0;
+        foreach ($aParticipants as $iProfileId) {
+
+            $b = sendMailTemplate($CNF['EMAIL_REMINDER'], 0, $iProfileId, array(
+                'EntryUrl' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']])),
+                'EntryTitle' => $aContentInfo['title'],
+                'EntryBegin' => $sEntryBegin,
+                'EntryBeginShort' => $sEntryBeginShort,
+                'EntryBeginUTC' => $sEntryBeginUTC,
+                'EntryLocation' => $sLocationString ? _t('_bx_events_email_reminder_location', $sLocationString) : '',
+            ), BX_EMAIL_NOTIFY);
+
+            $iCounter += $b ? 1 : 0;
+        }
+        return $iCounter;
+    }
+    
+    public function isOngoing($aDataEntry)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aDataEntry[$CNF['FIELD_DATE_START']]) || empty($aDataEntry[$CNF['FIELD_DATE_END']]))
+            return false;
+
+        $iTime = time();
+        return $iTime >= $aDataEntry[$CNF['FIELD_DATE_START']] && $iTime <= $aDataEntry[$CNF['FIELD_DATE_END']];
+    }
+}
+
+/** @} */

+ 152 - 0
modules/boonex/events/updates/14.0.6_14.0.7/source/install/config.php

@@ -0,0 +1,152 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Events Events
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_events',
+    'title' => 'Events',
+    'note' => 'Events functionality.',
+    'version' => '14.0.7',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/events/',
+    'home_uri' => 'events',
+
+    'db_prefix' => 'bx_events_',
+    'class_prefix' => 'BxEvents',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Events',
+
+    /**
+     * Connections.
+     */
+    'connections' => array(
+    	'sys_profiles_friends' => array ('type' => 'profiles'),
+		'sys_profiles_subscriptions' => array ('type' => 'profiles'),
+    ),
+
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+        'trigger_profile_view_submenu',
+        'trigger_group_view_submenu',
+        'trigger_group_snippet_meta',
+        'trigger_group_view_actions',
+    ),
+
+    /**
+     * Page triggers.
+     */
+    'page_triggers' => array (
+    	'trigger_page_group_view_entry', 
+    ),
+
+	/**
+     * Storage objects to automatically delete files from upon module uninstallation.
+     * Note. Don't add storage objects used in transcoder objects.
+     */
+    'storages' => array(
+    	'bx_events_pics'
+    ),
+
+	/**
+     * Transcoders.
+     */
+    'transcoders' => array(
+    	'bx_events_icon', 
+    	'bx_events_thumb', 
+    	'bx_events_avatar', 
+        'bx_events_avatar_big', 
+    	'bx_events_picture', 
+    	'bx_events_cover', 
+    	'bx_events_cover_thumb',
+    	'bx_events_gallery'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_events',
+    	'bx_events_cmts',
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+    	'process_esearches' => 1,
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    	'process_connections' => 1,
+        'process_deleted_profiles' => 1,
+    	'update_relations' => 1,        
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+    	'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable_success' => array(
+    	'process_menu_triggers' => 1,
+    	'process_page_triggers' => 1,
+    	'process_esearches' => 1,
+    	'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'unregister_transcoders' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+    	'register_transcoders' => 1,
+    	'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Connections Section
+     */
+    'relations' => array(
+        'bx_timeline',
+        'bx_notifications',
+    ),
+);
+
+/** @} */

+ 694 - 0
modules/boonex/events/updates/14.0.6_14.0.7/source/install/langs/en.xml

@@ -0,0 +1,694 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_events"><![CDATA[Events]]></string>
+    <string name="_bx_events_cmts"><![CDATA[Events' Comments]]></string>
+
+    <string name="_bx_events_option_enable_auto_approve"><![CDATA[Auto approve an event after creation]]></string>
+    <string name="_bx_events_option_num_connections_quick"><![CDATA[Number of items in friends block]]></string>
+    <string name="_bx_events_option_per_page_browse"><![CDATA[Number of items per page]]></string>
+    <string name="_bx_events_option_num_rss"><![CDATA[Number of items in RSS feed]]></string>
+    <string name="_bx_events_option_time_format"><![CDATA[Time format]]></string>
+    <string name="_bx_events_option_short_date_format"><![CDATA[Short date format]]></string>
+    <string name="_bx_events_option_datetime_format"><![CDATA[Date and time format]]></string>
+    <string name="_bx_events_option_summary_chars"><![CDATA[Number of characters in auto-cropped event summary]]></string>
+    <string name="_bx_events_option_searchable_fields"><![CDATA[Searchable fields]]></string>
+    <string name="_bx_events_option_members_mode"><![CDATA[Special mode for event members]]></string>
+    <string name="_bx_events_option_members_mode_multi_roles"><![CDATA[Multi Roles]]></string>
+    <string name="_bx_events_option_reminder_interval"><![CDATA[Reminder Interval]]></string>
+    <string name="_bx_events_option_public_subscribed_me"><![CDATA[Public list of interested members]]></string>
+    <string name="_bx_events_option_per_page_for_favorites_lists"><![CDATA[Number of favorite lists per page]]></string>
+    <string name="_bx_events_option_enable_subscribe_wo_join"><![CDATA[Allow to mark as Interested without joining (marking as Going)]]></string>
+    <string name="_bx_events_option_internal_notifications"><![CDATA[Use internal notifications]]></string>
+    <string name="_bx_events_option_enable_subscribe_for_past_events"><![CDATA[Enable join for past events]]></string>
+    <string name="_bx_events_option_rm_from_timeline_after"><![CDATA[Delete past events from Timeline after N hours (0 - don't delete)]]></string>
+
+    <string name="_bx_events_form_profile"><![CDATA[Events]]></string>
+    <string name="_bx_events_form_profile_display_add"><![CDATA[Add Event]]></string>
+    <string name="_bx_events_form_profile_display_edit"><![CDATA[Edit Event]]></string>
+    <string name="_bx_events_form_profile_display_edit_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_events_form_profile_display_delete"><![CDATA[Delete Event]]></string>
+    <string name="_bx_events_form_profile_display_view"><![CDATA[View Event]]></string>
+    <string name="_bx_events_form_profile_display_view_full"><![CDATA[Full View Event]]></string>
+    <string name="_bx_events_form_profile_display_invite"><![CDATA[Invite to Event]]></string>
+    <string name="_bx_events_form_profile_input_author"><![CDATA[Author]]></string>
+    <string name="_bx_events_form_profile_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_events_form_profile_input_sys_join_confirm"><![CDATA[Join confirmation]]></string>
+    <string name="_bx_events_form_profile_input_join_confirm"><![CDATA[Approval required to join]]></string>
+    <string name="_bx_events_form_profile_input_sys_allow_view_to"><![CDATA[Visible to]]></string>
+    <string name="_bx_events_form_profile_input_allow_view_to"><![CDATA[Visible to]]></string>
+    <string name="_bx_events_form_profile_input_allow_view_favorite_list"><![CDATA[Visibility]]></string>
+    <string name="_bx_events_form_profile_input_allow_view_to_desc"><![CDATA[Closed - anyone can find the event and see who's in it, only members can see posts. Secret - only members can find the event and see posts.]]></string>
+    <string name="_bx_events_form_profile_input_sys_allow_post_to"><![CDATA[Who can post]]></string>
+    <string name="_bx_events_form_profile_input_allow_post_to"><![CDATA[Who can post]]></string>
+    <string name="_bx_events_form_profile_input_sys_initial_members"><![CDATA[Invite Members]]></string>
+    <string name="_bx_events_form_profile_input_initial_members"><![CDATA[Invite Members]]></string>
+    <string name="_bx_events_form_profile_input_sys_event_name"><![CDATA[Event Name]]></string>
+    <string name="_bx_events_form_profile_input_event_name"><![CDATA[Event Name]]></string>
+    <string name="_bx_events_form_profile_input_event_name_err"><![CDATA[Field must contain 1 - 80 characters.]]></string>
+    <string name="_bx_events_form_profile_input_sys_event_desc"><![CDATA[Description]]></string>
+    <string name="_bx_events_form_profile_input_event_desc"><![CDATA[Description]]></string>
+    <string name="_bx_events_form_profile_input_sys_event_cat"><![CDATA[Category]]></string>
+    <string name="_bx_events_form_profile_input_event_cat"><![CDATA[Category]]></string>
+    <string name="_bx_events_form_profile_input_event_cat_err"><![CDATA[Category field is mandatory]]></string>
+    <string name="_bx_events_form_profile_input_sys_hashtag"><![CDATA[Main hashtag]]></string>
+    <string name="_bx_events_form_profile_input_hashtag"><![CDATA[Main hashtag]]></string>
+    <string name="_bx_events_form_profile_input_sys_picture"><![CDATA[Event Picture]]></string>
+    <string name="_bx_events_form_profile_input_picture"><![CDATA[Upload New Event Picture]]></string>
+    <string name="_bx_events_form_profile_input_picture_err"><![CDATA[Event picture is mandatory.]]></string>
+    <string name="_bx_events_form_profile_input_sys_cover"><![CDATA[Cover Picture]]></string>
+    <string name="_bx_events_form_profile_input_cover"><![CDATA[Upload New Cover Picture]]></string>
+    <string name="_bx_events_form_profile_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string>
+    <string name="_bx_events_form_profile_input_delete_confirm"><![CDATA[I am sure I want to delete this event]]></string>
+    <string name="_bx_events_form_profile_input_delete_confirm_info"><![CDATA[This action can not be undone.]]></string>
+    <string name="_bx_events_form_profile_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete event.]]></string>
+    <string name="_bx_events_form_profile_input_sys_reoccurring"><![CDATA[Reoccurring]]></string>
+    <string name="_bx_events_form_profile_input_reoccurring"><![CDATA[Reoccurring]]></string>
+    <string name="_bx_events_form_profile_input_reoccurring_add_interval"><![CDATA[Add repeating interval]]></string>
+    <string name="_bx_events_form_profile_input_sys_date_added"><![CDATA[Created]]></string>
+    <string name="_bx_events_form_profile_input_date_added"><![CDATA[Created]]></string>
+    <string name="_bx_events_form_profile_input_sys_date_changed"><![CDATA[Updated]]></string>
+    <string name="_bx_events_form_profile_input_date_changed"><![CDATA[Updated]]></string>
+    <string name="_bx_events_form_profile_input_sys_date_published"><![CDATA[Publishing time]]></string>
+    <string name="_bx_events_form_profile_input_date_published"><![CDATA[Publishing time]]></string>
+    <string name="_bx_events_form_profile_input_date_published_info"><![CDATA[Leave empty to publish immediately.]]></string>
+    <string name="_bx_events_form_profile_input_sys_date_start"><![CDATA[Date start]]></string>
+    <string name="_bx_events_form_profile_input_date_start"><![CDATA[Date start]]></string>
+    <string name="_bx_events_form_profile_input_date_start_err"><![CDATA[Start date is mandatory]]></string>
+    <string name="_bx_events_form_profile_input_sys_date_end"><![CDATA[Date end]]></string>
+    <string name="_bx_events_form_profile_input_date_end"><![CDATA[Date end]]></string>    
+    <string name="_bx_events_form_profile_input_date_end_err"><![CDATA[End date is mandatory]]></string>
+    <string name="_bx_events_form_profile_input_date_end_invalid_err"><![CDATA[End date must be greater than start date]]></string>
+    <string name="_bx_events_form_profile_input_sys_timezone"><![CDATA[Timezone]]></string>
+    <string name="_bx_events_form_profile_input_timezone"><![CDATA[Timezone]]></string>
+    <string name="_bx_events_form_profile_input_sys_time"><![CDATA[Time]]></string>
+    <string name="_bx_events_form_profile_input_time"><![CDATA[Time]]></string>
+    <string name="_bx_events_form_profile_input_sys_reminder"><![CDATA[Reminder (part of Internal Notifications)]]></string>
+    <string name="_bx_events_form_profile_input_reminder"><![CDATA[Reminder]]></string>
+    <string name="_bx_events_form_profile_input_sys_threshold"><![CDATA[Threshold radius]]></string>
+    <string name="_bx_events_form_profile_input_threshold"><![CDATA[Threshold radius (in meters)]]></string>
+    <string name="_bx_events_form_input_repeat_year"><![CDATA[Repeat year]]></string>
+    <string name="_bx_events_form_input_repeat_month"><![CDATA[Repeat month]]></string>
+    <string name="_bx_events_form_input_repeat_week_of_month"><![CDATA[Repeat week of month]]></string>
+    <string name="_bx_events_form_input_repeat_day_of_month"><![CDATA[Repeat day of month]]></string>
+    <string name="_bx_events_form_input_repeat_day_of_week"><![CDATA[Repeat day of week]]></string>
+    <string name="_bx_events_form_input_stop_repeating"><![CDATA[Stop repeating]]></string>
+
+    <string name="_bx_events_form_question"><![CDATA[Question]]></string>
+    <string name="_bx_events_form_question_display_add"><![CDATA[Add Question]]></string>
+    <string name="_bx_events_form_question_display_edit"><![CDATA[Edit Question]]></string>
+    <string name="_bx_events_form_question_input_sys_action"><![CDATA[Action]]></string>
+    <string name="_bx_events_form_question_input_sys_question"><![CDATA[Question]]></string>
+    <string name="_bx_events_form_question_input_question"><![CDATA[Question]]></string>
+    <string name="_bx_events_form_question_input_question_err"><![CDATA[Question is mandatory.]]></string>
+    <string name="_bx_events_form_question_input_sys_answer"><![CDATA[Answer]]></string>
+    <string name="_bx_events_form_question_input_sys_controls"><![CDATA[Controls]]></string>
+    <string name="_bx_events_form_question_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_events_form_question_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_events_form_question_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_events_form_question_input_do_cancel"><![CDATA[Cancel]]></string>
+
+    <string name="_bx_events_form_questionnaire_input_question_err"><![CDATA[Please, answer the question.]]></string>
+
+    <string name="_bx_events_form_session"><![CDATA[Session]]></string>
+    <string name="_bx_events_form_session_display_add"><![CDATA[Add Session]]></string>
+    <string name="_bx_events_form_session_display_edit"><![CDATA[Edit Session]]></string>
+    <string name="_bx_events_form_session_input_sys_title"><![CDATA[Title]]></string>
+    <string name="_bx_events_form_session_input_title"><![CDATA[Title]]></string>
+    <string name="_bx_events_form_session_input_title_err"><![CDATA[Session title is mandatory.]]></string>
+    <string name="_bx_events_form_session_input_sys_description"><![CDATA[Description]]></string>
+    <string name="_bx_events_form_session_input_description"><![CDATA[Description]]></string>
+    <string name="_bx_events_form_session_input_sys_date_start"><![CDATA[Date start]]></string>
+    <string name="_bx_events_form_session_input_date_start"><![CDATA[Date start]]></string>
+    <string name="_bx_events_form_session_input_date_start_err"><![CDATA[Start date is mandatory]]></string>
+    <string name="_bx_events_form_session_input_sys_date_end"><![CDATA[Date end]]></string>
+    <string name="_bx_events_form_session_input_date_end"><![CDATA[Date end]]></string>
+    <string name="_bx_events_form_session_input_date_end_err"><![CDATA[End date is mandatory]]></string>
+    <string name="_bx_events_form_session_input_sys_controls"><![CDATA[Controls]]></string>
+    <string name="_bx_events_form_session_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_events_form_session_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_events_form_session_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_events_form_session_input_do_cancel"><![CDATA[Cancel]]></string>
+
+    <string name="_bx_events_form_price"><![CDATA[Price]]></string>
+    <string name="_bx_events_form_price_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_events_form_price_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_events_form_price_input_sys_id"><![CDATA[ID]]></string>
+    <string name="_bx_events_form_price_input_sys_role_id"><![CDATA[Role ID]]></string>
+    <string name="_bx_events_form_price_input_sys_name"><![CDATA[Name]]></string>
+    <string name="_bx_events_form_price_input_name"><![CDATA[Name]]></string>
+    <string name="_bx_events_form_price_input_inf_name"><![CDATA[Unique value which will be used in back end and API calls]]></string>
+    <string name="_bx_events_form_price_input_err_name"><![CDATA[You need to enter a unique value here]]></string>
+    <string name="_bx_events_form_price_input_sys_period"><![CDATA[Period]]></string>
+    <string name="_bx_events_form_price_input_period"><![CDATA[Period]]></string>
+    <string name="_bx_events_form_price_input_inf_period"><![CDATA[Use 0 for lifetime]]></string>
+    <string name="_bx_events_form_price_input_sys_period_unit"><![CDATA[Period unit]]></string>
+    <string name="_bx_events_form_price_input_period_unit"><![CDATA[Period unit]]></string>
+    <string name="_bx_events_form_price_input_inf_period_unit"><![CDATA[Leave empty for lifetime level.]]></string>
+    <string name="_bx_events_form_price_input_err_period_unit"><![CDATA[You need to select a period unit here.]]></string>
+    <string name="_bx_events_form_price_input_sys_price"><![CDATA[Price]]></string>
+    <string name="_bx_events_form_price_input_price"><![CDATA[Price]]></string>
+    <string name="_bx_events_form_price_input_inf_price"><![CDATA[Use 0 or leave empty to create free level.]]></string>
+    <string name="_bx_events_form_price_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_events_form_price_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_events_form_price_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_events_form_price_input_do_cancel"><![CDATA[Cancel]]></string>
+
+    <string name="_bx_events_form_filters_input_by_location"><![CDATA[By location]]></string>
+    <string name="_bx_events_form_filters_input_by_date"><![CDATA[By date]]></string>
+    <string name="_bx_events_form_filters_input_by_date_all"><![CDATA[Any date]]></string>
+    <string name="_bx_events_form_filters_input_by_date_today"><![CDATA[Today]]></string>
+    <string name="_bx_events_form_filters_input_by_date_tomorrow"><![CDATA[Tomorrow]]></string>
+    <string name="_bx_events_form_filters_input_by_date_weekend"><![CDATA[This weekend]]></string>
+    <string name="_bx_events_form_filters_input_by_date_this_week"><![CDATA[This week]]></string>
+    <string name="_bx_events_form_filters_input_by_date_next_week"><![CDATA[Next week]]></string>
+    <string name="_bx_events_form_filters_input_by_date_this_month"><![CDATA[This month]]></string>
+    <string name="_bx_events_form_filters_input_by_date_date_range"><![CDATA[Custom date range]]></string>
+    <string name="_bx_events_form_filters_input_do_apply"><![CDATA[Apply]]></string>
+
+
+    <string name="_bx_events_acl_action_create_profile"><![CDATA[Create Event]]></string>
+    <string name="_bx_events_acl_action_use_paid_join"><![CDATA[Use paid join]]></string>
+    <string name="_bx_events_acl_action_delete_profile"><![CDATA[Delete Event]]></string>
+    <string name="_bx_events_acl_action_view_profile"><![CDATA[View Event]]></string>
+    <string name="_bx_events_acl_action_edit_any_profile"><![CDATA[Edit Any Event]]></string>
+    <string name="_bx_events_acl_action_delete_any_profile"><![CDATA[Delete Any Event]]></string>
+    <string name="_bx_events_acl_action_delete_invites"><![CDATA[Delete Invites]]></string>
+
+    <string name="_bx_events_page_title_sys_joined"><![CDATA[Profile Events]]></string>
+    <string name="_bx_events_page_title_joined"><![CDATA[Profile Events]]></string>
+    <string name="_bx_events_page_title_sys_joined_entries"><![CDATA[Profile Events]]></string>
+    <string name="_bx_events_page_title_followed"><![CDATA[Events I follow]]></string>
+    <string name="_bx_events_page_title_sys_followed"><![CDATA[Events I follow]]></string>
+    <string name="_bx_events_page_title_joined_entries"><![CDATA[Profile Events]]></string>
+    <string name="_bx_events_page_title_sys_entries_in_context"><![CDATA[Events in context]]></string>
+    <string name="_bx_events_page_title_entries_in_context"><![CDATA[Events in {display_name}]]></string>
+    <string name="_bx_events_page_title_sys_create_profile"><![CDATA[Create Event]]></string>
+    <string name="_bx_events_page_title_create_profile"><![CDATA[Create Event]]></string>
+    <string name="_bx_events_page_title_sys_edit_profile"><![CDATA[Edit Event]]></string>
+    <string name="_bx_events_page_title_edit_profile"><![CDATA[Edit Event]]></string>
+    <string name="_bx_events_page_title_sys_edit_profile_cover"><![CDATA[Edit Event Cover]]></string>
+    <string name="_bx_events_page_title_edit_profile_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_events_page_title_sys_questionnaire"><![CDATA[Questionnaire]]></string>
+    <string name="_bx_events_page_title_questionnaire"><![CDATA[Questionnaire]]></string>
+    <string name="_bx_events_page_title_manage_profile"><![CDATA[Manage]]></string>
+    <string name="_bx_events_page_title_sys_manage_profile"><![CDATA[Manage Event]]></string>
+    <string name="_bx_events_page_title_sys_delete_profile"><![CDATA[Delete Event]]></string>
+    <string name="_bx_events_page_title_delete_profile"><![CDATA[Delete Event]]></string>
+    <string name="_bx_events_page_title_sys_join_profile"><![CDATA[Join Event]]></string>
+    <string name="_bx_events_page_title_join_profile"><![CDATA[Join Event]]></string>
+    <string name="_bx_events_page_title_sys_view_profile_closed"><![CDATA[View Closed Event]]></string>
+    <string name="_bx_events_page_title_sys_view_profile"><![CDATA[View Event]]></string>
+    <string name="_bx_events_page_title_view_profile"><![CDATA[{display_name}]]></string>
+    <string name="_bx_events_page_title_browse"><![CDATA[Events]]></string>
+    <string name="_bx_events_page_title_browse_by_author"><![CDATA[Events of {display_name}]]></string>
+    <string name="_bx_events_page_title_browse_by_context"><![CDATA[Events in {display_name}]]></string>
+    <string name="_bx_events_page_title_browse_featured"><![CDATA[Featured Events]]></string>
+    <string name="_bx_events_page_title_browse_top"><![CDATA[Top Events]]></string>
+    <string name="_bx_events_page_title_browse_upcoming"><![CDATA[Upcoming Events]]></string>
+    <string name="_bx_events_page_title_browse_past"><![CDATA[Past Events]]></string>
+    <string name="_bx_events_page_title_browse_recent"><![CDATA[New Events]]></string>
+    <string name="_bx_events_page_title_browse_connections"><![CDATA[{0} connections]]></string>
+    <string name="_bx_events_page_title_browse_connections_mutual"><![CDATA[{0} and {1} mutual connections]]></string>
+    <string name="_bx_events_page_title_sys_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_events_page_title_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_events_page_title_sys_group_fans"><![CDATA[Event Guests]]></string>
+    <string name="_bx_events_page_title_group_fans"><![CDATA[Going]]></string>
+    <string name="_bx_events_page_title_sys_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_events_page_title_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_events_page_title_sys_profile_info"><![CDATA[Event Info]]></string>
+    <string name="_bx_events_page_title_profile_info"><![CDATA[Info about {display_name}]]></string>
+    <string name="_bx_events_page_title_sys_profile_sessions"><![CDATA[Sessions]]></string>
+    <string name="_bx_events_page_title_profile_sessions"><![CDATA[Sessions]]></string>
+    <string name="_bx_events_page_title_sys_profile_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_events_page_title_profile_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_events_page_title_sys_recent"><![CDATA[New Events]]></string>
+    <string name="_bx_events_page_title_recent"><![CDATA[New Events]]></string>
+    <string name="_bx_events_page_title_sys_manage"><![CDATA[Manage Own Events]]></string>
+    <string name="_bx_events_page_title_sys_manage_administration"><![CDATA[Manage All Events]]></string>
+    <string name="_bx_events_page_title_manage"><![CDATA[Manage Events]]></string>
+    <string name="_bx_events_page_title_sys_top"><![CDATA[Top Events]]></string>
+    <string name="_bx_events_page_title_top"><![CDATA[Top Events]]></string>
+    <string name="_bx_events_page_title_sys_upcoming"><![CDATA[Upcoming Events]]></string>
+    <string name="_bx_events_page_title_upcoming"><![CDATA[Upcoming Events]]></string>
+    <string name="_bx_events_page_title_sys_past"><![CDATA[Past Events]]></string>
+    <string name="_bx_events_page_title_past"><![CDATA[Past Events]]></string>
+    <string name="_bx_events_page_title_sys_calendar"><![CDATA[Events Calendar]]></string>
+    <string name="_bx_events_page_title_calendar"><![CDATA[Events Calendar]]></string>
+    <string name="_bx_events_page_title_sys_invite_to_group"><![CDATA[Invite Members to the Event]]></string>
+    <string name="_bx_events_page_title_invite_to_group"><![CDATA[Invite Members to the Event]]></string>
+    <string name="_bx_events_page_title_sys_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_events_page_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_events_page_title_sys_entries_favorites"><![CDATA[Favorites Events]]></string>
+    <string name="_bx_events_page_title_entries_favorites"><![CDATA[Favorites Events]]></string>
+
+    <string name="_bx_events_page_block_title_sys_entries_actions"><![CDATA[Events actions]]></string>
+    <string name="_bx_events_page_block_title_entries_actions"><![CDATA[Events actions]]></string>
+    <string name="_bx_events_page_block_title_entries_in_context"><![CDATA[Events in <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_events_page_block_title_sys_entries_in_context"><![CDATA[Events in context]]></string>
+    <string name="_bx_events_page_block_title_calendar_in_context"><![CDATA[Events Calendar in <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_events_page_block_title_sys_calendar_in_context"><![CDATA[Events Calendar]]></string>
+    <string name="_bx_events_page_block_title_calendar_for_context"><![CDATA[Events Calendar]]></string>
+    <string name="_bx_events_page_block_title_browse_for_context"><![CDATA[Events in context]]></string>
+    <string name="_bx_events_page_block_title_sys_favorites_entries"><![CDATA[Favorite List]]></string>
+    <string name="_bx_events_page_block_title_favorites_entries"><![CDATA["{title}" List]]></string>
+    <string name="_bx_events_page_block_title_favorites_entries_info"><![CDATA[Info]]></string>
+    <string name="_bx_events_page_block_title_favorites_entries_actions"><![CDATA[Favorites List Actions]]></string>
+    <string name="_bx_events_page_block_title_sys_entries_of_author"><![CDATA[Events of author]]></string>
+    <string name="_bx_events_page_block_title_entries_of_author"><![CDATA[Events by {display_name}]]></string>
+    <string name="_bx_events_page_block_title_favorites_of_author"><![CDATA[Favorite Events]]></string>
+    <string name="_bx_events_page_block_title_sys_favorites_of_author"><![CDATA[Favorite Events]]></string>
+    <string name="_bx_events_page_block_title_sys_joined_entries"><![CDATA[Joined Events]]></string>
+    <string name="_bx_events_page_block_title_joined_entries"><![CDATA[Joined Events]]></string>
+    <string name="_bx_events_page_block_title_sys_joined_calendar"><![CDATA[Joined Events Calendar]]></string>
+    <string name="_bx_events_page_block_title_joined_calendar"><![CDATA[Joined Events Calendar]]></string>
+    <string name="_bx_events_page_block_title_sys_followed_entries"><![CDATA[Following]]></string>
+    <string name="_bx_events_page_block_title_followed_entries"><![CDATA[Following]]></string>
+    <string name="_bx_events_page_block_title_create_profile"><![CDATA[Create Event]]></string>
+    <string name="_bx_events_page_block_title_edit_profile"><![CDATA[Edit Event]]></string>
+    <string name="_bx_events_page_block_title_edit_profile_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_events_page_block_title_edit_questionnaire"><![CDATA[Questionnaire]]></string>
+    <string name="_bx_events_page_block_title_delete_profile"><![CDATA[Delete Event]]></string>
+    <string name="_bx_events_page_block_title_join_profile"><![CDATA[Join Event]]></string>
+    <string name="_bx_events_page_block_title_profile_info_link"><![CDATA[About <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_events_page_block_title_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_events_page_block_title_system_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_events_page_block_title_profile_sessions"><![CDATA[Sessions]]></string>
+    <string name="_bx_events_page_block_title_profile_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_events_page_block_title_profile_description"><![CDATA[Description]]></string>
+    <string name="_bx_events_page_block_title_profile_friends_link"><![CDATA[Friends of <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_events_page_block_title_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_events_page_block_title_system_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_events_page_block_title_admins"><![CDATA[Administrators]]></string>
+    <string name="_bx_events_page_block_title_fans_link"><![CDATA[Going to <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_events_page_block_title_fans"><![CDATA[Going]]></string>
+    <string name="_bx_events_page_block_title_system_fans"><![CDATA[Going]]></string>
+    <string name="_bx_events_page_block_title_system_fans_manage"><![CDATA[Manage fans]]></string>
+    <string name="_bx_events_page_block_title_invites"><![CDATA[Invites]]></string>
+    <string name="_bx_events_page_block_title_system_invites_manage"><![CDATA[Manage invites]]></string>
+    <string name="_bx_events_page_block_title_subscribers_link"><![CDATA[Interested in <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_events_page_block_title_subscribers"><![CDATA[Interested]]></string>
+    <string name="_bx_events_page_block_title_system_subscribers_manage"><![CDATA[Manage interested]]></string>
+    <string name="_bx_events_page_block_title_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_events_page_block_title_entry_reports"><![CDATA[Reports]]></string>
+    <string name="_bx_events_page_block_title_profile_comments_link"><![CDATA[Comments to <a href="{entry_link}">{title}</a>]]></string>
+    <string name="_bx_events_page_block_title_profile_actions"><![CDATA[Actions]]></string>
+    <string name="_bx_events_page_block_title_featured_profiles"><![CDATA[Featured Events]]></string>
+    <string name="_bx_events_page_block_title_latest_profiles"><![CDATA[New Events]]></string>
+    <string name="_bx_events_page_block_title_system_manage"><![CDATA[Manage Own Events]]></string>
+    <string name="_bx_events_page_block_title_system_manage_administration"><![CDATA[Manage All Events]]></string>
+    <string name="_bx_events_page_block_title_manage"><![CDATA[Manage Events]]></string>
+    <string name="_bx_events_page_block_title_top_profiles"><![CDATA[Top Events]]></string>
+    <string name="_bx_events_page_block_title_joined_profiles"><![CDATA[Joined Events]]></string>
+    <string name="_bx_events_page_block_title_followed_profiles"><![CDATA[Events I follow]]></string>
+    <string name="_bx_events_page_block_title_upcoming_profiles"><![CDATA[Upcoming Events]]></string>
+    <string name="_bx_events_page_block_title_past_profiles"><![CDATA[Past Events]]></string>
+    <string name="_bx_events_page_block_title_entry_all_actions"><![CDATA[All Actions]]></string>
+    <string name="_bx_events_page_block_title_entry_social_sharing"><![CDATA[Share]]></string>
+    <string name="_bx_events_page_block_title_categories"><![CDATA[Event categories]]></string>
+    <string name="_bx_events_page_block_title_calendar"><![CDATA[Events calendar]]></string>
+    <string name="_bx_events_page_block_title_profile_calendar"><![CDATA[Calendar]]></string>
+    <string name="_bx_events_page_block_title_profile_sessions"><![CDATA[Sessions]]></string>
+    <string name="_bx_events_page_block_title_sys_entry_context"><![CDATA[Posted in]]></string>
+    <string name="_bx_events_page_block_title_entry_context"><![CDATA[Posted in]]></string>
+    <string name="_bx_events_page_block_title_entry_location"><![CDATA[Location]]></string>
+    <string name="_bx_events_page_block_title_invite_to_group"><![CDATA[Invite Members to the Event]]></string>
+    <string name="_bx_events_page_block_title_search_form"><![CDATA[Search Form]]></string>
+    <string name="_bx_events_page_block_title_search_results"><![CDATA[Search Results]]></string>
+    <string name="_bx_events_page_block_title_search_form_cmts"><![CDATA[Comments Search Form]]></string>
+    <string name="_bx_events_page_block_title_search_results_cmts"><![CDATA[Comments Search Results]]></string>
+    <string name="_bx_events_page_block_title_sys_featured_entries_view_showcase"><![CDATA[Featured Events (Showcase View)]]></string>
+    <string name="_bx_events_page_block_title_featured_entries_view_showcase"><![CDATA[Featured Events]]></string>
+    <string name="_bx_events_page_block_title_sys_recommended_entries_view_showcase"><![CDATA[Recommended Events (Showcase View)]]></string>
+    <string name="_bx_events_page_block_title_recommended_entries_view_showcase"><![CDATA[Recommended Events]]></string>
+    <string name="_bx_events_page_block_title_sys_cover_block"><![CDATA[Cover]]></string>
+    <string name="_bx_events_page_block_title_cover_block"><![CDATA[Cover]]></string>
+    
+    <string name="_bx_events_menu_title_submenu"><![CDATA[Events Submenu]]></string>
+    <string name="_bx_events_menu_set_title_submenu"><![CDATA[Events Submenu]]></string>
+    <string name="_bx_events_menu_set_title_view_profile_actions"><![CDATA[Event Actions]]></string>
+    <string name="_bx_events_menu_title_view_profile_actions"><![CDATA[Event Actions]]></string>
+    <string name="_bx_events_menu_set_title_view_profile_actions_more"><![CDATA[Event More Actions]]></string>
+    <string name="_bx_events_menu_title_view_profile_actions_more"><![CDATA[Event More Actions]]></string>
+    <string name="_bx_events_menu_title_view_profile_meta"><![CDATA[Event Meta]]></string>
+    <string name="_bx_events_menu_set_title_view_profile_meta"><![CDATA[Event Meta]]></string>
+    <string name="_bx_events_menu_set_title_view_profile_submenu"><![CDATA[View Event Submenu]]></string>
+    <string name="_bx_events_menu_title_view_profile_submenu"><![CDATA[View Event Submenu]]></string>
+    <string name="_bx_events_menu_title_entries_my"><![CDATA[Profile Events Action Menu]]></string>
+    <string name="_bx_events_menu_set_title_entries_my"><![CDATA[Profile Events Menu]]></string>
+    <string name="_bx_events_menu_set_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_events_menu_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_events_menu_item_title_system_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_events_menu_item_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_events_menu_item_title_system_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_events_menu_item_title_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_events_menu_item_title_system_clear_reports"><![CDATA[Clear reports]]></string>
+    <string name="_bx_events_menu_item_title_clear_reports"><![CDATA[Clear reports]]></string>
+    <string name="_bx_events_menu_item_title_system_edit_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_events_menu_item_title_edit_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_events_menu_item_title_system_edit_questionnaire"><![CDATA[Questionnaire]]></string>
+    <string name="_bx_events_menu_item_title_edit_questionnaire"><![CDATA[Questionnaire]]></string>
+    <string name="_bx_events_menu_item_title_system_edit_sessions"><![CDATA[Sessions]]></string>
+    <string name="_bx_events_menu_item_title_edit_sessions"><![CDATA[Sessions]]></string>
+    <string name="_bx_events_menu_item_title_system_edit_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_events_menu_item_title_edit_pricing"><![CDATA[Pricing]]></string>
+    <string name="_bx_events_menu_item_title_system_edit_profile"><![CDATA[Edit Event]]></string>
+    <string name="_bx_events_menu_item_title_edit_profile"><![CDATA[Edit Event]]></string>
+    <string name="_bx_events_menu_item_title_system_delete_profile"><![CDATA[Delete Event]]></string>
+    <string name="_bx_events_menu_item_title_delete_profile"><![CDATA[Delete Event]]></string>
+    <string name="_bx_events_menu_item_title_system_delete_account"><![CDATA[Delete Account]]></string>
+    <string name="_bx_events_menu_item_title_delete_account"><![CDATA[Delete Account]]></string>
+    <string name="_bx_events_menu_item_title_system_profile_type"><![CDATA[Event]]></string>
+    <string name="_bx_events_menu_item_title_profile_type"><![CDATA[Event]]></string>
+    <string name="_bx_events_menu_item_title_system_create_profile"><![CDATA[Event]]></string>
+    <string name="_bx_events_menu_item_title_create_profile"><![CDATA[Event]]></string>
+    <string name="_bx_events_menu_item_title_system_view_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_events_menu_item_title_view_profile_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_events_menu_item_title_system_view_joined_groups"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_view_joined_groups"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_system_view_fans"><![CDATA[Guests]]></string>
+    <string name="_bx_events_menu_item_title_view_fans"><![CDATA[Going]]></string>
+    <string name="_bx_events_menu_item_title_system_view_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_events_menu_item_title_view_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_events_menu_item_title_system_view_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_events_menu_item_title_view_profile_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_events_menu_item_title_system_view_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_events_menu_item_title_view_profile_info"><![CDATA[Info]]></string>
+    <string name="_bx_events_menu_item_title_system_view_profile_view"><![CDATA[Event]]></string>
+    <string name="_bx_events_menu_item_title_view_profile_view"><![CDATA[Event]]></string>
+    <string name="_bx_events_menu_item_title_system_view_profile_more_auto"><![CDATA[More]]></string>
+    <string name="_bx_events_menu_item_title_view_profile_more_auto"><![CDATA[More]]></string>
+    <string name="_bx_events_menu_item_title_system_invite"><![CDATA[Invite]]></string>
+    <string name="_bx_events_menu_item_title_invite"><![CDATA[Invite]]></string>
+
+    <string name="_bx_events_menu_item_title_system_befriend"><![CDATA[Add friend]]></string>
+    <string name="_bx_events_menu_item_title_befriend"><![CDATA[Add friend]]></string>
+    <string name="_bx_events_menu_item_title_befriend_sent"><![CDATA[Add friend (requested)]]></string>
+    <string name="_bx_events_menu_item_title_befriend_confirm"><![CDATA[Accept friend request]]></string>
+    <string name="_bx_events_menu_item_title_system_unfriend"><![CDATA[Unfriend]]></string>
+    <string name="_bx_events_menu_item_title_unfriend"><![CDATA[Unfriend]]></string>
+    <string name="_bx_events_menu_item_title_unfriend_cancel_request"><![CDATA[Cancel friend request]]></string>
+    <string name="_bx_events_menu_item_title_unfriend_reject_request"><![CDATA[Reject friend request]]></string>
+
+    <string name="_bx_events_menu_item_title_system_become_fan"><![CDATA[Join Event]]></string>
+    <string name="_bx_events_menu_item_title_become_fan"><![CDATA[Going]]></string>
+    <string name="_bx_events_menu_item_title_become_fan_sent"><![CDATA[Requested]]></string>
+    <string name="_bx_events_menu_item_title_become_fan_confirm"><![CDATA[Confirm]]></string>
+    <string name="_bx_events_menu_item_title_system_leave_group"><![CDATA[Leave Event]]></string>
+    <string name="_bx_events_menu_item_title_leave_group"><![CDATA[Not going]]></string>
+    <string name="_bx_events_menu_item_title_leave_group_cancel_request"><![CDATA[Cancel]]></string>
+    <string name="_bx_events_menu_item_title_leave_group_reject_request"><![CDATA[Decline]]></string>
+    <string name="_bx_events_menu_item_title_sm_members"><![CDATA[{0} going]]></string>
+    <string name="_bx_events_menu_item_title_sm_subscribers"><![CDATA[{0} interested]]></string>
+
+    <string name="_bx_events_menu_item_title_system_pay_and_join"><![CDATA[Pay & Join]]></string>
+    <string name="_bx_events_menu_item_title_pay_and_join"><![CDATA[Pay & Join]]></string>
+    <string name="_bx_events_menu_item_title_system_subscribe"><![CDATA[Follow]]></string>
+    <string name="_bx_events_menu_item_title_subscribe"><![CDATA[Interested]]></string>
+    <string name="_bx_events_menu_item_title_system_check_in"><![CDATA[Check In]]></string>
+    <string name="_bx_events_menu_item_title_check_in"><![CDATA[Check In]]></string>
+    <string name="_bx_events_menu_item_title_system_view_entries_in_context"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_view_entries_in_context"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_system_unsubscribe"><![CDATA[Unfollow]]></string>
+    <string name="_bx_events_menu_item_title_unsubscribe"><![CDATA[Not interested]]></string>
+    <string name="_bx_events_menu_item_title_system_more_actions"><![CDATA[More Actions]]></string>
+    <string name="_bx_events_menu_item_title_more_actions"><![CDATA[]]></string>
+    <string name="_bx_events_menu_item_title_system_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_events_menu_item_title_friends"><![CDATA[Friends]]></string>
+    <string name="_bx_events_menu_item_title_system_manage_my_groups"><![CDATA[My events]]></string>
+    <string name="_bx_events_menu_item_title_manage_my_groups"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_system_followings"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_followings"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_system_entries_recent"><![CDATA[New]]></string>
+    <string name="_bx_events_menu_item_title_entries_recent"><![CDATA[New]]></string>
+    <string name="_bx_events_menu_item_title_system_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_events_menu_item_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_events_menu_item_title_system_entries_joined"><![CDATA[Joined]]></string>
+    <string name="_bx_events_menu_item_title_entries_followed"><![CDATA[Following]]></string>
+    <string name="_bx_events_menu_item_title_system_entries_followed"><![CDATA[Following]]></string>
+    <string name="_bx_events_menu_item_title_entries_joined"><![CDATA[Joined]]></string>
+    <string name="_bx_events_menu_item_title_system_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_events_menu_item_title_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_events_menu_item_title_system_entries_home"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_entries_home"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_system_admt_groups"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_admt_groups"><![CDATA[Events]]></string>
+    <string name="_bx_events_menu_item_title_manage_my"><![CDATA[My Events]]></string>
+    <string name="_bx_events_menu_item_title_manage_all"><![CDATA[All Events]]></string>
+    <string name="_bx_events_menu_item_title_system_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_events_menu_item_title_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_events_menu_item_title_system_entries_upcoming"><![CDATA[Upcoming]]></string>
+    <string name="_bx_events_menu_item_title_entries_upcoming"><![CDATA[Upcoming]]></string>        
+    <string name="_bx_events_menu_item_title_system_entries_past"><![CDATA[Past]]></string>
+    <string name="_bx_events_menu_item_title_entries_past"><![CDATA[Past]]></string>
+    <string name="_bx_events_menu_item_title_system_calendar"><![CDATA[Calendar]]></string>
+    <string name="_bx_events_menu_item_title_calendar"><![CDATA[Calendar]]></string>
+
+    <string name="_bx_events_menu_item_title_system_sm_date_start"><![CDATA[Date start]]></string>
+    <string name="_bx_events_menu_item_title_sm_date_start"><![CDATA[Date start]]></string>
+    <string name="_bx_events_menu_item_title_system_sm_date_end"><![CDATA[Date end]]></string>
+    <string name="_bx_events_menu_item_title_sm_date_end"><![CDATA[Date end]]></string>
+    <string name="_bx_events_menu_item_title_ical_export"><![CDATA[Sync to Calendar]]></string>
+
+    <string name="_bx_events_grid_filter_item_title_adm_select_one_filter1"><![CDATA[All Statuses]]></string>
+    <string name="_bx_events_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string>
+    <string name="_bx_events_grid_filter_item_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_events_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string>
+    <string name="_bx_events_grid_filter_item_title_adm_pending"><![CDATA[Pending]]></string>
+    <string name="_bx_events_grid_filter_item_title_adm_suspended"><![CDATA[Suspended]]></string>
+    <string name="_bx_events_grid_column_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_events_grid_column_title_adm_name"><![CDATA[Name]]></string>
+    <string name="_bx_events_grid_column_title_adm_added"><![CDATA[Created]]></string>
+    <string name="_bx_events_grid_column_title_adm_account"><![CDATA[Account]]></string>
+    <string name="_bx_events_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_events_grid_action_title_adm_delete_with_content"><![CDATA[Delete With Content]]></string>
+    <string name="_bx_events_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
+    <string name="_bx_events_grid_action_title_adm_audit_content"><![CDATA[Audit actions with content]]></string>
+    <string name="_bx_events_grid_action_title_adm_audit_context"><![CDATA[Audit actions in context]]></string>
+    <string name="_bx_events_grid_action_title_adm_clear_reports"><![CDATA[Сlear reports]]></string>
+    <string name="_bx_events_grid_action_err_delete"><![CDATA[Cannot delete the selected event(s).]]></string>
+    <string name="_bx_events_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+
+    <string name="_bx_events_grid_column_title_qn_question"><![CDATA[Question]]></string>
+    <string name="_bx_events_grid_action_title_qn_add"><![CDATA[Add]]></string>
+    <string name="_bx_events_grid_action_title_qn_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_events_grid_action_title_qn_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_events_popup_title_qn_add"><![CDATA[Add Question]]></string>
+    <string name="_bx_events_popup_title_qn_edit"><![CDATA[Edit Question]]></string>
+    <string name="_bx_events_popup_title_questionnaire"><![CDATA[Questionnaire]]></string>
+
+    <string name="_bx_events_grid_column_title_sn_title"><![CDATA[Title]]></string>
+    <string name="_bx_events_grid_column_title_sn_description"><![CDATA[Description]]></string>
+    <string name="_bx_events_grid_column_title_sn_date_start"><![CDATA[Date start]]></string>
+    <string name="_bx_events_grid_column_title_sn_date_end"><![CDATA[Date end]]></string>
+    <string name="_bx_events_grid_action_title_sn_add"><![CDATA[Add]]></string>
+    <string name="_bx_events_grid_action_title_sn_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_events_grid_action_title_sn_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_events_popup_title_sn_add"><![CDATA[Add Session]]></string>
+    <string name="_bx_events_popup_title_sn_edit"><![CDATA[Edit Session]]></string>
+
+    <string name="_bx_events_grid_column_title_name"><![CDATA[Name]]></string>
+    <string name="_bx_events_grid_column_title_role_id"><![CDATA[Role]]></string>
+    <string name="_bx_events_grid_column_title_period"><![CDATA[Period]]></string>
+    <string name="_bx_events_grid_column_title_price"><![CDATA[Price]]></string>
+    <string name="_bx_events_grid_action_title_add"><![CDATA[Add]]></string>
+    <string name="_bx_events_grid_action_title_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_events_grid_action_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_events_grid_action_title_buy"><![CDATA[Buy]]></string>
+    <string name="_bx_events_grid_action_title_buy_title"><![CDATA[One-time payment]]></string>
+    <string name="_bx_events_grid_action_title_subscribe"><![CDATA[Subscribe]]></string>
+    <string name="_bx_events_grid_action_title_subscribe_title"><![CDATA[Create a subscription with recurring billing]]></string>
+    <string name="_bx_events_grid_action_title_choose"><![CDATA[Choose]]></string>
+    <string name="_bx_events_grid_action_title_choose_title"><![CDATA[Switch to the level for free]]></string>
+    <string name="_bx_events_popup_title_price_add"><![CDATA[Add Pricing Plan]]></string>
+    <string name="_bx_events_popup_title_price_edit"><![CDATA[Edit Pricing Plan]]></string>
+
+    <string name="_bx_events_chart_growth"><![CDATA[Events: Growth]]></string>
+    <string name="_bx_events_chart_growth_speed"><![CDATA[Events: Growth Speed]]></string>
+
+    <string name="_bx_events_search_extended"><![CDATA[Events Search]]></string>
+    <string name="_bx_events_search_extended_cmts"><![CDATA[Events Comments Search]]></string>
+
+    <string name="_bx_events_txt_all_entries_in"><![CDATA[<a href="{0}">All Events in {1} ({2})</a>]]></string>
+    <string name="_bx_events_txt_account_pending"><![CDATA[This event is pending approval, please wait until it is activated.]]></string>
+    <string name="_bx_events_txt_account_suspended"><![CDATA[This event is suspended.]]></string>
+    <string name="_bx_events_txt_scheduled_awaiting"><![CDATA[Event publishing date is {date_publish_uf}]]></string>
+    <string name="_bx_events_txt_status_hidden"><![CDATA[This event was hidden.]]></string>
+    <string name="_bx_events_txt_N_fans"><![CDATA[{0} going]]></string>
+    <string name="_bx_events_txt_role"><![CDATA[Role]]></string>
+    <string name="_bx_events_txt_role_added"><![CDATA[Added]]></string>
+    <string name="_bx_events_txt_role_expired"><![CDATA[Expires]]></string>
+    <string name="_bx_events_txt_sample_single"><![CDATA[event]]></string>
+    <string name="_bx_events_txt_sample_single_with_article"><![CDATA[an event]]></string>
+    <string name="_bx_events_txt_sample_comment_single"><![CDATA[comment to event]]></string>
+    <string name="_bx_events_txt_sample_vote_single"><![CDATA[vote to event]]></string>
+    <string name="_bx_events_txt_sample_score_up_single"><![CDATA[score up vote to event]]></string>
+    <string name="_bx_events_txt_sample_score_down_single"><![CDATA[score down vote to event]]></string>
+    <string name="_bx_events_txt_set_role"><![CDATA[Set Role]]></string>
+    <string name="_bx_events_txt_view_answers"><![CDATA[View questionnaire answers]]></string>
+    <string name="_bx_events_txt_private_group"><![CDATA[Private event]]></string>
+    <string name="_bx_events_txt_from_time_to_time"><![CDATA[{0} - {1}]]></string>
+    <string name="_bx_events_txt_invite_popup_text"><![CDATA[You have been invited to a event. Click the button below to accept the invitation.]]></string>
+    <string name="_bx_events_txt_invite_popup_title"><![CDATA[Invitation to a event]]></string>
+    <string name="_bx_events_txt_invite_popup_button_accept"><![CDATA[Accept]]></string>
+    <string name="_bx_events_txt_invite_popup_button_decline"><![CDATA[Decline]]></string>
+    <string name="_bx_events_txt_invite_popup_error_invitation_absent"><![CDATA[Sorry, an error has occurred. You can not join this event. The invitation is expired or deleted.]]></string>
+    <string name="_bx_events_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Sorry, an error has occurred. You can not join this event. This invitation cannot be used by you.]]></string>
+    <string name="_bx_events_txt_n_unit"><![CDATA[{0} ({1})]]></string>
+    <string name="_bx_events_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_events_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string>    
+
+    <string name="_bx_events_msg_performed"><![CDATA[The action was successfully performed.]]></string>
+    <string name="_bx_events_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>
+    <string name="_bx_events_err_cannot_perform"><![CDATA[An error appeared. Please report.]]></string>
+
+    <string name="_bx_events_alert_action_fan_added"><![CDATA[member joined]]></string>
+    <string name="_bx_events_txt_ntfs_fan_added"><![CDATA[Event <b class="bx-ntfs-content-profile">{owner_name}</b> has got a member <b class="bx-ntfs-content-entry">{subentry_sample}</b>]]></string>
+    <string name="_bx_events_alert_action_join_invitation_notif"><![CDATA[join invitation]]></string>
+    <string name="_bx_events_alert_action_join_request"><![CDATA[join request]]></string>
+    <string name="_bx_events_alert_action_reminder"><![CDATA[reminder]]></string>
+    <string name="_bx_events_alert_action_reminder_follow_context"><![CDATA[New reminders]]></string>
+    <string name="_bx_events_alert_action_info_reminder_follow_context"><![CDATA[New reminders were posted in a space you follow.]]></string>
+    <string name="_bx_events_txt_ntfs_join_invitation"><![CDATA[<b class="bx-ntfs-content-profile">{owner_name}</b> event's representative invites <b class="bx-ntfs-content-entry">{subentry_sample}</b> to join event]]></string>
+    <string name="_bx_events_txt_ntfs_join_request"><![CDATA[Event <b class="bx-ntfs-content-profile">{owner_name}</b> has a join request from <b class="bx-ntfs-content-entry">{subentry_sample}</b>]]></string>
+    <string name="_bx_events_txt_ntfs_reminder_1"><![CDATA[Event <b class="bx-ntfs-content-profile">{owner_name}</b> starts in an hour]]></string>
+    <string name="_bx_events_txt_ntfs_reminder_24"><![CDATA[Event <b class="bx-ntfs-content-profile">{owner_name}</b> starts in 24 hours]]></string>
+    <string name="_bx_events_txt_ntfs_timeline_post_common"><![CDATA[New post <b class="bx-ntfs-content-entry">{subentry_sample}</b> in <b class="bx-ntfs-content-profile">{owner_name}</b> event timeline]]></string>
+
+    <string name="_bx_events_privacy_group_closed"><![CDATA[Closed]]></string>
+    <string name="_bx_events_privacy_group_secret"><![CDATA[Secret]]></string>
+
+    <string name="_bx_events_pre_lists_reminder"><![CDATA[Event reminder]]></string>
+    <string name="_bx_events_reminder_none"><![CDATA[Reminder is off]]></string>
+    <string name="_bx_events_reminder_1h"><![CDATA[1 hour]]></string>
+    <string name="_bx_events_reminder_2h"><![CDATA[2 hours]]></string>
+    <string name="_bx_events_reminder_3h"><![CDATA[3 hours]]></string>
+    <string name="_bx_events_reminder_6h"><![CDATA[6 hours]]></string>
+    <string name="_bx_events_reminder_12h"><![CDATA[12 hours]]></string>
+    <string name="_bx_events_reminder_24h"><![CDATA[24 hours]]></string>
+
+    <string name="_bx_events_pre_lists_cats"><![CDATA[Event categories]]></string>
+    <string name="_bx_events_cat_Conference"><![CDATA[Conference]]></string>
+    <string name="_bx_events_cat_Festival"><![CDATA[Festival]]></string>
+    <string name="_bx_events_cat_Fundraiser"><![CDATA[Fundraiser]]></string>
+    <string name="_bx_events_cat_Lecture"><![CDATA[Lecture]]></string>
+    <string name="_bx_events_cat_Market"><![CDATA[Market]]></string>
+    <string name="_bx_events_cat_Meal"><![CDATA[Meal]]></string>
+    <string name="_bx_events_cat_Social_Mixer"><![CDATA[Social Mixer]]></string>
+    <string name="_bx_events_cat_Tour"><![CDATA[Tour]]></string>
+    <string name="_bx_events_cat_Volunteering"><![CDATA[Volunteering]]></string>
+    <string name="_bx_events_cat_Workshop"><![CDATA[Workshop]]></string>
+    <string name="_bx_events_cat_Other"><![CDATA[Other]]></string>
+
+    <string name="_bx_events_pre_lists_roles"><![CDATA[Event roles]]></string>
+    <string name="_bx_events_role_regular"><![CDATA[Regular]]></string>
+    <string name="_bx_events_role_administrator"><![CDATA[Administrator]]></string>
+    <string name="_bx_events_role_moderator"><![CDATA[Moderator]]></string>
+
+    <string name="_bx_events_pre_lists_period_units"><![CDATA[Billing Interval]]></string>
+    <string name="_bx_events_period_unit_day"><![CDATA[day]]></string>
+    <string name="_bx_events_period_unit_week"><![CDATA[week]]></string>
+    <string name="_bx_events_period_unit_month"><![CDATA[month]]></string>
+    <string name="_bx_events_period_unit_year"><![CDATA[year]]></string>
+
+    <string name="_bx_events_pre_lists_repeat_year"><![CDATA[Repeat year]]></string>
+    <string name="_bx_events_cat_repeat_year_every_year"><![CDATA[Every year]]></string>
+    <string name="_bx_events_cat_repeat_year_every_2_years"><![CDATA[Every 2 years]]></string>
+    <string name="_bx_events_cat_repeat_year_every_3_years"><![CDATA[Every 3 years]]></string>
+    <string name="_bx_events_cat_repeat_year_every_4_years"><![CDATA[Every 4 years]]></string>
+
+    <string name="_bx_events_pre_lists_repeat_month"><![CDATA[Repeat month]]></string>
+    <string name="_bx_events_cat_repeat_month_every_month"><![CDATA[Every month]]></string>
+    <string name="_bx_events_cat_repeat_month_jan"><![CDATA[Every January]]></string>
+    <string name="_bx_events_cat_repeat_month_feb"><![CDATA[Every February]]></string>
+    <string name="_bx_events_cat_repeat_month_mar"><![CDATA[Every March]]></string>
+    <string name="_bx_events_cat_repeat_month_apr"><![CDATA[Every April]]></string>
+    <string name="_bx_events_cat_repeat_month_may"><![CDATA[Every May]]></string>
+    <string name="_bx_events_cat_repeat_month_jun"><![CDATA[Every June]]></string>
+    <string name="_bx_events_cat_repeat_month_jul"><![CDATA[Every July]]></string>
+    <string name="_bx_events_cat_repeat_month_aug"><![CDATA[Every August]]></string>
+    <string name="_bx_events_cat_repeat_month_sep"><![CDATA[Every September]]></string>
+    <string name="_bx_events_cat_repeat_month_oct"><![CDATA[Every October]]></string>
+    <string name="_bx_events_cat_repeat_month_nov"><![CDATA[Every November]]></string>
+    <string name="_bx_events_cat_repeat_month_dec"><![CDATA[Every December]]></string>
+
+    <string name="_bx_events_pre_lists_repeat_week_of_month"><![CDATA[Repeat week of the month]]></string>
+    <string name="_bx_events_cat_repeat_week_of_month_every_week"><![CDATA[Every week]]></string>
+    <string name="_bx_events_cat_repeat_week_of_month_first_week_of_month"><![CDATA[Every 1st week of the month]]></string>
+    <string name="_bx_events_cat_repeat_week_of_month_second_week_of_month"><![CDATA[Every 2nd week of the month]]></string>
+    <string name="_bx_events_cat_repeat_week_of_month_third_week_of_month"><![CDATA[Every 3rd week of the month]]></string>
+    <string name="_bx_events_cat_repeat_week_of_month_fourth_week_of_month"><![CDATA[Every 4th week of the month]]></string>
+    <string name="_bx_events_cat_repeat_week_of_month_fifth_week_of_month"><![CDATA[Every 5th week of the month]]></string>
+    <string name="_bx_events_cat_repeat_week_of_month_sixth_week_of_month"><![CDATA[Every 6th week of the month]]></string>
+
+    <string name="_bx_events_pre_lists_repeat_day_of_month"><![CDATA[Repeat day of the month]]></string>
+    <string name="_bx_events_cat_repeat_day_of_month_every_day"><![CDATA[Every day]]></string>
+
+    <string name="_bx_events_pre_lists_repeat_day_of_week"><![CDATA[Repeat day of the week]]></string>
+    <string name="_bx_events_cat_repeat_day_of_week_every_day"><![CDATA[Every day of the week]]></string>
+    <string name="_bx_events_cat_repeat_day_of_week_mon"><![CDATA[Every Monday]]></string>
+    <string name="_bx_events_cat_repeat_day_of_week_tue"><![CDATA[Every Tuesday]]></string>
+    <string name="_bx_events_cat_repeat_day_of_week_wed"><![CDATA[Every Wednesday]]></string>
+    <string name="_bx_events_cat_repeat_day_of_week_thu"><![CDATA[Every Thursday]]></string>
+    <string name="_bx_events_cat_repeat_day_of_week_fri"><![CDATA[Every Friday]]></string>
+    <string name="_bx_events_cat_repeat_day_of_week_sat"><![CDATA[Every Saturday]]></string>
+    <string name="_bx_events_cat_repeat_day_of_week_sun"><![CDATA[Every Sunday]]></string>
+
+    <string name="_bx_events_email_join_request"><![CDATA[Event join request]]></string>
+    <string name="_bx_events_email_join_request_subject"><![CDATA[Request to join your event]]></string>
+    <string name="_bx_events_email_join_request_body"><![CDATA[{email_header}
+<p>New request to join <a href="{EntryUrl}">{EntryTitle}</a> event from <a href="{NewMemberUrl}">{NewMemberDisplayName}</a></p>
+{email_footer}]]></string>
+
+    <string name="_bx_events_email_join_reject"><![CDATA[Event join request was rejected]]></string>
+    <string name="_bx_events_email_join_reject_subject"><![CDATA[Request to join an event was rejected]]></string>
+    <string name="_bx_events_email_join_reject_body"><![CDATA[{email_header}
+<p>Your request to join <a href="{EntryUrl}">{EntryTitle}</a> event was rejected by event admin.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_events_email_join_confirm"><![CDATA[Event join request was confirmed]]></string>
+    <string name="_bx_events_email_join_confirm_subject"><![CDATA[Your request to join an event was confirmed]]></string>
+    <string name="_bx_events_email_join_confirm_body"><![CDATA[{email_header}
+<p>Your request to join <a href="{EntryUrl}">{EntryTitle}</a> event was confirmed by the event admin.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_events_email_fan_remove"><![CDATA[Profile was removed from event participants]]></string>
+    <string name="_bx_events_email_fan_remove_subject"><![CDATA[Your profile was removed from the event]]></string>
+    <string name="_bx_events_email_fan_remove_body"><![CDATA[{email_header}
+<p>Your profile was removed from <a href="{EntryUrl}">{EntryTitle}</a> event by the event admin.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_events_email_fan_become_admin"><![CDATA[Event admin status was granted]]></string>
+    <string name="_bx_events_email_fan_become_admin_subject"><![CDATA[You are an event admin now]]></string>
+    <string name="_bx_events_email_fan_become_admin_body"><![CDATA[{email_header}
+<p>You are an admin of <a href="{EntryUrl}">{EntryTitle}</a> event now.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_events_email_admin_become_fan"><![CDATA[Event admin status was revoked]]></string>
+    <string name="_bx_events_email_admin_become_fan_subject"><![CDATA[Your event admin status was revoked]]></string>
+    <string name="_bx_events_email_admin_become_fan_body"><![CDATA[{email_header}
+<p>Your admin status was revoked from <a href="{EntryUrl}">{EntryTitle}</a> event.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_events_email_invitation"><![CDATA[Invitation to event]]></string>
+    <string name="_bx_events_email_invitation_subject"><![CDATA[Invitation to "{EntryTitle}" event]]></string>
+    <string name="_bx_events_email_invitation_body"><![CDATA[{email_header}
+<p><a href="{InviterUrl}">{InviterDisplayName}</a> invited you to <a href="{EntryUrl}">{EntryTitle} event</a>.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_events_email_set_role"><![CDATA[Event member status was changed]]></string>
+    <string name="_bx_events_email_set_role_subject"><![CDATA[Your event status was changed]]></string>
+    <string name="_bx_events_email_set_role_body"><![CDATA[{email_header}
+<p>Your status in <a href="{EntryUrl}">{EntryTitle}</a> event was changed to <b>{Role}</b>.</p>
+{email_footer}]]></string>
+
+    <string name="_bx_events_email_reminder"><![CDATA[Reminder for the event]]></string>
+    <string name="_bx_events_email_reminder_subject"><![CDATA["{EntryTitle}" event - {EntryBeginShort}]]></string>
+    <string name="_bx_events_email_reminder_body"><![CDATA[{email_header}
+<p class="vevent">
+    <a href="{EntryUrl}" class="summary">{EntryTitle}</a> event reminder:</a><br />
+    Date: <abbr class="dtstart" title="{EntryBeginUTC}">{EntryBegin}</abbr><br />
+    {EntryLocation}
+</p>
+{email_footer}]]></string>
+    <string name="_bx_events_email_reminder_location"><![CDATA[Location: <span class="location">{0}</span>]]></string>
+</resources>

+ 51 - 0
modules/boonex/files/updates/14.0.3_14.0.4/install/config.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Files',
+    'version_from' => '14.0.3',
+    'version_to' => '14.0.4',
+    'vendor' => 'BoonEx',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/files/updates/update_14.0.3_14.0.4/',
+    'home_uri' => 'files_update_1403_1404',
+
+    'module_dir' => 'boonex/files/',
+    'module_uri' => 'files',
+
+    'db_prefix' => 'bx_files_',
+    'class_prefix' => 'BxFiles',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Files',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/files/updates/14.0.3_14.0.4/install/installer.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxFilesUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 653 - 0
modules/boonex/files/updates/14.0.3_14.0.4/source/classes/BxFilesModule.php

@@ -0,0 +1,653 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Files Files
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Files module
+ */
+class BxFilesModule extends BxBaseModFilesModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    public function serviceMyEntriesActions ($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        if (!$this->serviceIsAllowedAddContentToContext($iProfileId))
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_MY_ENTRIES']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    public function checkAllowedSetThumb ($iContentId = 0)
+    {
+        return _t('_sys_txt_access_denied');
+    }
+
+    public function actionDownload($sToken = '', $iContentId = 0, $sBulk = '')
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        $aFilesToDownload = [$iContentId];
+        if (!empty($sBulk)) {
+            $aFilesToDownload = json_decode($sBulk);
+        }
+
+        if (!is_array($aFilesToDownload)) {
+            $this->_oTemplate->displayPageNotFound();
+            return;
+        }
+
+        $iContentId = reset($aFilesToDownload);
+
+        $aData = $this->_oDb->getContentInfoById((int)$iContentId);
+        if (!$aData) {
+            $this->_oTemplate->displayPageNotFound();
+            return;
+        }
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== $this->checkAllowedView($aData)) {
+            $this->_oTemplate->displayAccessDenied();
+            return;
+        }
+
+        if ($aData['type'] == 'folder' || $aFilesToDownload && count($aFilesToDownload) > 1) {
+            bx_import('BulkDownloader', $this->_aModule);
+            $oDownloader = new BxFilesBulkDownloader($this);
+            $aResult = $oDownloader->createDownloadingJob($aFilesToDownload, $aData[$CNF['FIELD_TITLE']]);
+
+            switch ($aResult['status']) {
+                case BX_FILES_DOWNLOADER_STATUS_TOO_LARGE:
+                    $this->_oTemplate->displayMsg(_t('_bx_files_txt_max_size_reached', _t_format_size(intval(getParam('bx_files_max_bulk_download_size')) * 1024 * 1024)));
+                    break;
+                case BX_FILES_DOWNLOADER_STATUS_DOWNLOADING:
+                    $this->_oTemplate->addJsTranslation('_bx_files_txt_status_downloading_ready_msg');
+                    $this->_oTemplate->displayMsg(['title' => _t('_bx_files_txt_status_downloading'), 'content' => $this->_oTemplate->parseHtmlByName('bulk_downloader_process.html', [
+                        'timeout' => BX_FILES_DOWNLOADER_TIMEOUT_REQUESTS,
+                        'action_url' => BX_DOL_URL_ROOT.$this->_oConfig->getBaseUri().'process_download/'.$aResult['job'],
+                    ])]);
+                    break;
+                case BX_FILES_DOWNLOADER_STATUS_READY:
+                    bx_smart_readfile($aResult['file'], $aResult['filename'], 'application/zip');
+                    break;
+                case BX_FILES_DOWNLOADER_STATUS_EMPTY:
+                default:
+                    $this->_oTemplate->displayPageNotFound();
+                    break;
+            }
+            exit;
+        }
+
+        $aFile = $this->getContentFile($aData);
+        if (!$aFile) {
+            $this->_oTemplate->displayPageNotFound();
+            return;
+        }
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        if (!$oStorage) {
+            $this->_oTemplate->displayErrorOccured();
+            return;
+        }
+
+        if (!$oStorage->download($aFile['remote_id'], $sToken, true)) {
+            $this->_oTemplate->displayErrorOccured();
+            return;
+        }
+        
+        exit;   
+    }
+
+    public function actionProcessDownload($iDownloadingJobId) {
+        if (!$iDownloadingJobId) die;
+
+        $aJob = $this->_oDb->getDownloadingJob($iDownloadingJobId);
+        if (!$aJob || $aJob['owner'] != bx_get_logged_profile_id()) die;
+
+        if ($aJob['result']) {
+            bx_smart_readfile($aJob['result'], $aJob['name'], 'application/zip');
+            exit;
+        }
+
+        bx_import('BulkDownloader', $this->_aModule);
+        $oDownloader = new BxFilesBulkDownloader($this);
+
+        $mRes = $oDownloader->processDownloading($aJob['files']);
+        if ($mRes === BX_FILES_DOWNLOADER_STATUS_DOWNLOADING) {
+            $this->_oDb->updateDownloadingJob($iDownloadingJobId, $aJob['files'], '');
+            echoJson([
+                'status' => 'downloading',
+            ]);
+        } else {
+            $this->_oDb->updateDownloadingJob($iDownloadingJobId, $aJob['files'], $mRes);
+            echoJson([
+                'status' => 'ready',
+            ]);
+        }
+    }
+    
+    public function getContentFile($aData) 
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        if (!isset($aData[$CNF['FIELD_AUTHOR']]) || !isset($aData[$CNF['FIELD_ID']]))
+            return false;
+
+        if (isset($aData['type']) && $aData['type'] == 'folder') {
+            return [
+                'id' => $aData[$CNF['FIELD_ID']],
+                'profile_id' => $aData[$CNF['FIELD_AUTHOR']],
+                'remote_id' => 0,
+                'path' => '',
+                'file_name' => $aData[$CNF['FIELD_TITLE']],
+                'mime_type' => '',
+                'ext' => '',
+                'size' => 0,
+                'added' => $aData[$CNF['FIELD_ADDED']],
+                'modified' => 0,
+                'private' => false,
+                'is_image' => false,
+            ];
+        }
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        if (!$oStorage)
+            return false;
+
+        if (isset($aData[$CNF['FIELD_FILE_ID']]) && $aData[$CNF['FIELD_FILE_ID']])
+            return $this->_addFileFields($oStorage->getFile($aData[$CNF['FIELD_FILE_ID']]));
+        
+        $aGhostFiles = $oStorage->getGhosts ($aData[$CNF['FIELD_AUTHOR']], $aData[$CNF['FIELD_ID']]);
+        if (!$aGhostFiles)
+            return false;
+
+        return $this->_addFileFields(array_pop($aGhostFiles));
+    }
+    
+    public function getDownloadLink($mixedData) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!is_array($mixedData))
+            $mixedData = $this->_oDb->getContentInfoById((int)$mixedData);
+        if(empty($mixedData) || !is_array($mixedData))
+            return false;
+        
+        $aFile = $this->getContentFile($mixedData);
+        if($aFile === false)
+            return false;
+
+        $sFileExt = !empty($aFile['ext']) ? $aFile['ext'] : '';
+
+        $aMarkers = array_merge([
+            'content_id' => $mixedData[$CNF['FIELD_ID']]
+        ], ($aFile['private'] && ($oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE'])) ? [
+            'file_download_token' => $oStorage->genToken($aFile['id']),
+            'file_ext' => $sFileExt
+        ] : [
+            'file_download_token' => '0',
+            'file_ext' => $sFileExt,
+        ]));
+
+        return bx_replace_markers(BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($CNF['URL_DOWNLOAD']), $aMarkers);
+    }
+
+    protected function _addFileFields($aFile) 
+    {
+        if (is_array($aFile))
+            $aFile['is_image'] = in_array($aFile['ext'], array('jpg', 'jpeg', 'png', 'gif', /* when ImageMagick is used - 'tif', 'tiff', 'bmp', 'ico', 'psd' */));
+        return $aFile;
+    }
+
+    public function serviceEntityFilePreview($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryFilePreview', $iContentId);
+    }
+
+    public function serviceProcessFilesData($iNumberOfFilesToProcessAtOnce = 3)
+    {
+        $CNF = $this->_oConfig->CNF;
+        
+        if (!defined('BX_SYSTEM_JAVA') || !constant('BX_SYSTEM_JAVA'))
+            return;
+        
+        $a = $this->_oDb->getNotProcessedFiles($iNumberOfFilesToProcessAtOnce);
+        if (!$a)
+            return;
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        if (!$oStorage)
+            return false;
+        
+        foreach ($a as $aContentInfo) {
+            $aFile = $this->getContentFile($aContentInfo);
+            if (!$aFile) {
+                $this->_oDb->updateFileData ($aContentInfo[$CNF['FIELD_ID']], '');
+                continue;
+            }
+
+            $sFileUrl = $oStorage->getFileUrlById($aFile['id']);
+            $sFilePath = BX_DIRECTORY_PATH_TMP . $aFile['remote_id'] . '.' . $aFile['ext'];
+            @file_put_contents($sFilePath, file_get_contents($sFileUrl));
+            if (!file_exists($sFilePath)) {
+                $this->_oDb->updateFileData ($aContentInfo[$CNF['FIELD_ID']], '');
+                continue;
+            }
+            
+            $sCommand = '"' . constant('BX_SYSTEM_JAVA') . '" -Djava.awt.headless=true -jar "' . $this->_oConfig->getHomePath() . 'data/tika-app.jar" --encoding=UTF-8 --text "' . $sFilePath . '"';
+            $sData = `$sCommand`;
+            @unlink($sFilePath);
+
+            $this->_oDb->updateFileData ($aContentInfo[$CNF['FIELD_ID']], $sData);
+        }
+    }
+
+    public function servicePruneDownloadingJobs()
+    {
+        $aFiles = $this->_oDb->deleteOldDownloadingJobs();
+        if ($aFiles) foreach ($aFiles as $sFile) @unlink($sFile);
+    }
+
+    protected function _getFilesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aFile = $this->getContentFile($aContentInfo);
+        if(!$aFile || $aFile['is_image'])
+            return [];
+
+        $sIconSrc = '';
+        if(($oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE'])) !== false)
+            $sIconSrc = $this->_oTemplate->getIconUrl($oStorage->getIconNameByFileName($aFile['file_name']));
+
+        return [[
+            'id' => $aFile['id'],
+            'src' => $sIconSrc,
+            'src_medium' => $sIconSrc,
+            'src_orig' => '',
+            'url' => $sUrl
+        ]];
+    }
+
+    protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $aFile = $this->getContentFile($aContentInfo);
+        if (!$aFile)
+            return [];
+
+        if (!$aFile['is_image'])
+            return [];
+
+        $sPhotoThumb = '';
+        if ($oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($this->_oConfig->CNF['OBJECT_IMAGES_TRANSCODER_GALLERY']))
+            $sPhotoThumb = $oImagesTranscoder->getFileUrl($aFile['id']);
+
+        return [[
+            'id' => $aFile['id'], 
+            'url' => $sUrl, 
+            'src' => $sPhotoThumb
+        ]];
+    }
+
+    public function actionBookmark($iContentId) {
+        if (!$this->isLogged()) return;
+
+        $aFilesToBookmark = [];
+        if ($iContentId) $aFilesToBookmark = [$iContentId];
+        elseif (isset($_POST['bulk'])) $aFilesToBookmark = $_POST['bulk'];
+
+        if ($aFilesToBookmark)
+            foreach ($aFilesToBookmark as $iContentId) {
+                if ($iContentId <= 0) continue;
+
+                $aData = $this->_oDb->getContentInfoById((int)$iContentId);
+                if (!$aData) continue;
+
+                $this->_oDb->bookmarkFile((int)$iContentId, bx_get_logged_profile_id());
+            }
+    }
+
+    public function actionEntryPreview($iContentId) {
+        $aData = $this->_oDb->getContentInfoById((int)$iContentId);
+
+        $sJsCss = '';
+
+        $sPreviewCode = $this->_serviceTemplateFunc ('entryFilePreview', $iContentId);
+
+        // various init scripts could be there, which fail to work properly
+        // because of a fact that it is inside a popup which is at the moment is hidden
+        // so we need a way to postpone that initialization
+        $aScripts = [];
+        $sPreviewCode = preg_replace_callback('/<script>(.*)<\/script>/s', function($aMatch) use (&$aScripts) {
+            $aScripts[] = ['code' => $aMatch[1]];
+            return '';
+        }, $sPreviewCode);
+
+        // if there is a script then most likely it is a text + codemirror initialization, so include it here.
+        if ($aScripts) {
+            $sJsCss .= $this->_oTemplate->addJs('codemirror/codemirror-ext.min.js', true);
+            $sJsCss .= $this->_oTemplate->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'codemirror/|codemirror.css', true);
+        }
+
+        $sJsCss .= $this->_oTemplate->addCss('file_handler.css', true);
+
+        $sPopupCode = $this->_oTemplate->parseHtmlByName('entry-popup.html', [
+            'content' => $sPreviewCode,
+            'bx_repeat:init_scripts' => $aScripts,
+        ]);
+
+        echoJson([
+            'popup' => [
+                'html' => PopupBox('bx-files-popup', bx_process_output($aData['title']), $sJsCss.$sPopupCode),
+                'options' => [
+                    'onShow' => 'bx_files_popup_init()',
+                ],
+            ],
+        ]);
+    }
+
+    public function actionEntryInfo($iContentId) {
+        $aData = $this->_oDb->getContentInfoById((int)$iContentId);
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== $this->checkAllowedView($aData)) {
+            echoJson([
+                'popup' => [
+                    'html' => PopupBox('bx-files-popup', _t('_Access denied'), _t('_Access denied')),
+                ],
+            ]);
+            return;
+        }
+
+        echoJson([
+            'popup' => [
+                'html' => PopupBox('bx-files-popup', bx_process_output($aData['title']), $this->_oTemplate->entryInfoPopup($aData)),
+            ],
+        ]);
+    }
+
+    public function actionEntryEditTitle($iContentId) {
+        $iContentId = intval($iContentId);
+        $aData = $this->_oDb->getContentInfoById($iContentId);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        if (!$aData) {
+            echoJson(['message' => _t('_bx_files_txt_error')]);
+            exit;
+        }
+
+        if ($this->checkAllowedEdit($aData, false) !== CHECK_ACTION_RESULT_ALLOWED) {
+            echoJson(['message' => _t('_bx_files_txt_permission_denied')]);
+            exit;
+        }
+
+        setlocale(LC_ALL,'C.UTF-8'); //otherwise pathinfo returns empty string on a non-latin characters filenames
+
+        if (isset($_POST['title'])) {
+            if ($sNewTitle = bx_get('title')) {
+                $sExt = pathinfo($aData[$CNF['FIELD_TITLE']], PATHINFO_EXTENSION);
+                if ($sExt) $sExt = '.'.$sExt;
+
+                $this->_oDb->updateEntryTitle($iContentId, $sNewTitle.$sExt);
+                $aData = $this->_oDb->getContentInfoById($iContentId);
+                echo bx_process_output($aData[$CNF['FIELD_TITLE']]);
+            }
+            exit;
+        }
+
+
+        $sActionUrl = BX_DOL_URL_ROOT.$this->_oConfig->getBaseUri();
+        echoJson([
+            'eval' => "bx_prompt('"._t('_bx_files_form_entry_input_title')."', '".bx_js_string(pathinfo($aData[$CNF['FIELD_TITLE']], PATHINFO_FILENAME))."', function(val) {                
+                $.post('{$sActionUrl}entry_edit_title/{$iContentId}', {title: val.getValue()}, function(sTitle) {
+                    if (sTitle)
+                        $('.bx-file-entry[file_id=".$aData[$CNF['FIELD_ID']]."] .bx-files-filename a').html(sTitle);
+                });                
+            })",
+        ]);
+    }
+
+    public function actionEntryDelete($iContentId) {
+        $aFilesToDelete = [];
+        if ($iContentId) $aFilesToDelete = [$iContentId];
+        elseif (isset($_POST['bulk'])) $aFilesToDelete = $_POST['bulk'];
+
+        if ($aFilesToDelete)
+            foreach ($aFilesToDelete as $iContentId) {
+                $iContentId = intval($iContentId);
+                if ($iContentId <= 0) continue;
+
+                $aData = $this->_oDb->getContentInfoById($iContentId);
+
+                $CNF = &$this->_oConfig->CNF;
+
+                if (!$aData) {
+                    echoJson(['message' => _t('_bx_files_txt_error')]);
+                    exit;
+                }
+
+                if ($this->checkAllowedDelete($aData, false) !== CHECK_ACTION_RESULT_ALLOWED) {
+                    continue;
+                }
+
+                $this->serviceDeleteEntity($iContentId);
+            }
+
+        if (!isset($_POST['bulk'])) {
+            echoJson([
+                'eval' => "
+                    $('.bx-file-entry[file_id={$iContentId}]').fadeOut();
+                    $(window).trigger('files_browser.update');
+                ",
+            ]);
+        } else {
+            echoJson([]);
+        }
+    }
+
+    public function actionClearGhosts() {
+        if (!isLogged()) die;
+
+        $oStorage = BxDolStorage::getObjectInstance($this->_oConfig->CNF['OBJECT_STORAGE']);
+        if ($oStorage) {
+            $aFiles = $oStorage->getGhosts(bx_get_logged_profile_id(), 0);
+
+            if ($aFiles) foreach ($aFiles as $aFile) {
+                $oStorage->deleteFile($aFile['id']);
+            }
+        }
+    }
+
+    public function actionUploadCompleted() {
+        $iContext = intval(bx_get('context'));
+        $iFolder = intval(bx_get('folder'));
+
+        if (!isLogged()) {
+            echoJson(['message' => _t('_Access denied')]);
+            exit;
+        }
+
+        if ($this->checkAllowedAdd() != CHECK_ACTION_RESULT_ALLOWED || $iContext && !$this->serviceIsAllowedAddContentToContext($iContext)) {
+            echoJson(['message' => _t('_Access denied')]);
+            exit;
+        }
+
+        $oStorage = BxDolStorage::getObjectInstance($this->_oConfig->CNF['OBJECT_STORAGE']);
+        if ($oStorage) {
+            $iAuthorProfile = bx_get_logged_profile_id();
+            $aFiles = $oStorage->getGhosts($iAuthorProfile, 0);
+
+            if ($aFiles) {
+                $aFilesIDs = [];
+                foreach($aFiles as $aFile) {
+                    $aFilesIDs[] = $aFile['id'];
+                    bx_set('title-'.$aFile['id'], $aFile['file_name'], 'post');
+                }
+                bx_set('attachments', $aFilesIDs, 'post');
+                bx_set('profile_id', $iAuthorProfile, 'post');
+                bx_set('allow_view_to', !$iContext ? BX_DOL_PG_ALL : -$iContext, 'post');
+                bx_set('cf', 1, 'post');
+
+                $oForm = BxDolForm::getObjectInstance($this->_oConfig->CNF['OBJECT_FORM_ENTRY_UPLOAD'], $this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_UPLOAD']);
+                $aFiles = $oForm->insert();
+
+                if ($iFolder != 0 && !empty($aFiles))
+                    $this->_oDb->moveFilesToFolder($aFiles, $iFolder);
+
+                if (!empty($aFiles))
+                foreach ($aFiles as $iContentId) {
+                    $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+                    $this->alertAfterAdd($aContentInfo);
+                }
+            }
+        }
+
+        echoJson(['success' => true]);
+    }
+
+    public function actionCreateFolder() {
+        $iContext = intval(bx_get('context'));
+        $iFolder = intval(bx_get('current_folder'));
+        $sName = trim(bx_get('name'));
+
+        if (!isLogged()) {
+            echoJson(['message' => _t('_Access denied')]);
+            exit;
+        }
+
+        if ($this->checkAllowedAdd() != CHECK_ACTION_RESULT_ALLOWED || $iContext && !$this->serviceIsAllowedAddContentToContext($iContext)) {
+            echoJson(['message' => _t('_Access denied')]);
+            exit;
+        }
+
+        if (!empty($sName)) {
+            $iAuthor = bx_get_logged_profile_id();
+            // in case it is posted to context make the context profile as an author of an entry
+            // to leave folder in case creator is removed
+            // and to delete folder when context profile is being deleted
+            $this->_oDb->createFolder($iFolder, $iContext ? $iContext : $iAuthor, !$iContext || $iAuthor == $iContext ? BX_DOL_PG_ALL : -$iContext, $sName);
+        }
+
+        echoJson([]);
+        exit;
+    }
+
+    public function actionMoveFiles() {
+        $iContentId = intval(bx_get('file'));
+
+        $aFilesToMove = [];
+        if ($iContentId) $aFilesToMove = [$iContentId];
+        elseif (isset($_POST['bulk'])) $aFilesToMove = $_POST['bulk'];
+
+        $iContentId = reset($aFilesToMove);
+
+        $aData = $this->_oDb->getContentInfoById($iContentId);
+
+        if (!$aData) {
+            echoJson(['message' => _t('_bx_files_txt_error')]);
+            exit;
+        }
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContext = $aData[$CNF['FIELD_ALLOW_VIEW_TO']] < 0 ? -$aData[$CNF['FIELD_ALLOW_VIEW_TO']] : $aData[$CNF['FIELD_AUTHOR']];
+
+        if (!isAdmin() && ($this->checkAllowedAdd() != CHECK_ACTION_RESULT_ALLOWED || !$this->serviceIsAllowedAddContentToContext($iContext))) {
+            echoJson(['message' => _t('_Access denied')]);
+            exit;
+        }
+
+        if (isset($_POST['move_to'])) {
+            $iMoveTo = intval(bx_get('move_to'));
+            if ($iMoveTo) {
+                $aDataMoveTo = $this->_oDb->getContentInfoById($iMoveTo);
+                if (!$aDataMoveTo) {
+                    echoJson(['message' => _t('_bx_files_txt_error')]);
+                    exit;
+                }
+
+                $iContextMoveTo = $aDataMoveTo[$CNF['FIELD_ALLOW_VIEW_TO']] < 0 ? -$aDataMoveTo[$CNF['FIELD_ALLOW_VIEW_TO']] : $aDataMoveTo[$CNF['FIELD_AUTHOR']];
+
+                if (!isAdmin() && ($this->checkAllowedAdd() != CHECK_ACTION_RESULT_ALLOWED || !$this->serviceIsAllowedAddContentToContext($iContextMoveTo))) {
+                    echoJson(['message' => _t('_Access denied')]);
+                    exit;
+                }
+            }
+
+            $this->_oDb->moveFilesToFolder($aFilesToMove, $iMoveTo);
+
+            echoJson([
+                'eval' => "                        
+                    $(window).trigger('files_browser.update');
+                ",
+            ]);
+            exit;
+        }
+
+        $aFolders = $this->_oDb->getFoldersInContext($iContext);
+        if (!$aFolders) {
+            echoJson(['message' => _t('_bx_files_txt_move_to_nowhere')]);
+            exit;
+        }
+
+        echoJson([
+            'popup' => [
+                'html' => PopupBox('bx-files-popup', _t('_bx_files_txt_move_to'), $this->_oTemplate->getJsTree($aFilesToMove, $aFolders)),
+                'options' => [],
+            ],
+        ]);
+    }
+
+    public function serviceDeleteEntitiesByAuthor ($iProfileId) {
+        $this->_oDb->deleteProfileBookmarks($iProfileId);
+        return parent::serviceDeleteEntitiesByAuthor ($iProfileId);
+    }
+
+    public function serviceDeleteEntity ($iContentId, $sFuncDelete = 'deleteData') {
+        $aData = $this->_oDb->getContentInfoById($iContentId);
+        if ($aData['type'] == 'folder') {
+            $aNestedFiles = $this->_oDb->getFolderFiles($aData[$this->_oConfig->CNF['FIELD_ID']]);
+            if ($aNestedFiles)
+                foreach ($aNestedFiles as $iFile) {
+                    $this->serviceDeleteEntity($iFile);
+                }
+        }
+        parent::serviceDeleteEntity($iContentId, $sFuncDelete);
+    }
+
+    public function serviceBrowseContext ($iProfileId = 0, $aParams = array()) {
+        if(!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId)
+            return;
+        // do not show files in context block for persons profiles
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if ($oProfile && $oProfile->getModule() == 'bx_persons') return;
+
+        return parent::serviceBrowseContext($iProfileId, $aParams);
+    }
+
+    public function serviceGetFile ($iContentId, $aParams = []) 
+    {
+        $aResult = parent::serviceGetFile ($iContentId, $aParams);
+        if(!$aResult)
+            return $aResult;
+
+        $aResult['url_download'] = $this->getDownloadLink($iContentId);
+        return $aResult;
+    }
+}
+
+/** @} */

+ 142 - 0
modules/boonex/files/updates/14.0.3_14.0.4/source/install/config.php

@@ -0,0 +1,142 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Files Files
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_files',
+    'title' => 'Files',
+    'note' => 'Basic blogging module.',
+    'version' => '14.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '14.0.0-RC3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/files/',
+    'home_uri' => 'files',
+
+    'db_prefix' => 'bx_files_',
+    'class_prefix' => 'BxFiles',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Files',
+
+    /**
+     * List of page triggers.
+     */
+    'page_triggers' => array (
+    	'trigger_page_profile_view_entry',
+    ),
+    
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+    	'trigger_profile_view_submenu',
+    	'trigger_group_view_submenu',
+    ),
+
+	/**
+     * Storage objects to automatically delete files from upon module uninstallation.
+     * Note. Don't add storage objects used in transcoder objects.
+     */
+    'storages' => array(
+    	'bx_files_files'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+        'bx_files_preview',
+        'bx_files_gallery',
+
+        'bx_files_sounds_mp3',
+
+        'bx_files_videos_poster',
+        'bx_files_videos_poster_preview',
+        'bx_files_videos_mp4',
+        'bx_files_videos_mp4_hd'
+    ),
+
+	/**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_files',
+    	'bx_files_cmts',
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+    	'process_esearches' => 1,
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    	'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+    	'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable_success' => array(
+        'process_menu_triggers' => 1,
+        'process_page_triggers' => 1,
+    	'process_esearches' => 1,
+    	'register_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+    	'update_relations' => 1,
+    	'unregister_transcoders' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+    	'register_transcoders' => 1,
+    	'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Relations Section
+     */
+    'relations' => array(
+    	'bx_timeline',
+    	'bx_notifications',
+    ),
+
+);
+
+/** @} */

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác