AntonLV 1 месяц назад
Родитель
Сommit
bf423d8bfc
100 измененных файлов с 15747 добавлено и 0 удалено
  1. 51 0
      modules/boonex/accounts/updates/15.0.1_15.0.2/install/config.php
  2. 13 0
      modules/boonex/accounts/updates/15.0.1_15.0.2/install/installer.php
  3. 52 0
      modules/boonex/accounts/updates/15.0.1_15.0.2/source/classes/BxAccntDb.php
  4. 71 0
      modules/boonex/accounts/updates/15.0.1_15.0.2/source/install/config.php
  5. 27 0
      modules/boonex/accounts/updates/15.0.1_15.0.2/source/template/images/icons/std-icon.svg
  6. 51 0
      modules/boonex/acl/updates/15.0.3_15.0.4/install/config.php
  7. 13 0
      modules/boonex/acl/updates/15.0.3_15.0.4/install/installer.php
  8. 17 0
      modules/boonex/acl/updates/15.0.3_15.0.4/install/sql/install.sql
  9. 262 0
      modules/boonex/acl/updates/15.0.3_15.0.4/source/classes/BxAclGridView.php
  10. 70 0
      modules/boonex/acl/updates/15.0.3_15.0.4/source/install/config.php
  11. 160 0
      modules/boonex/acl/updates/15.0.3_15.0.4/source/install/sql/install.sql
  12. 51 0
      modules/boonex/ads/updates/15.0.4_15.0.5/install/config.php
  13. 13 0
      modules/boonex/ads/updates/15.0.4_15.0.5/install/installer.php
  14. 453 0
      modules/boonex/ads/updates/15.0.4_15.0.5/source/classes/BxAdsFormEntry.php
  15. 2789 0
      modules/boonex/ads/updates/15.0.4_15.0.5/source/classes/BxAdsModule.php
  16. 151 0
      modules/boonex/ads/updates/15.0.4_15.0.5/source/install/config.php
  17. 3 0
      modules/boonex/ads/updates/15.0.4_15.0.5/source/template/images/icons/std-icon.svg
  18. 51 0
      modules/boonex/albums/updates/15.0.3_15.0.4/install/config.php
  19. 13 0
      modules/boonex/albums/updates/15.0.3_15.0.4/install/installer.php
  20. 21 0
      modules/boonex/albums/updates/15.0.3_15.0.4/install/langs/en.xml
  21. 24 0
      modules/boonex/albums/updates/15.0.3_15.0.4/install/sql/enable.sql
  22. 15 0
      modules/boonex/albums/updates/15.0.3_15.0.4/install/sql/install.sql
  23. 258 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsConfig.php
  24. 76 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsFormMedia.php
  25. 220 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsMenuViewActionsMedia.php
  26. 1258 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsModule.php
  27. 463 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsTemplate.php
  28. 143 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/install/config.php
  29. 325 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/install/langs/en.xml
  30. 109 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  31. 585 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/install/sql/enable.sql
  32. 616 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/install/sql/install.sql
  33. 109 0
      modules/boonex/albums/updates/15.0.3_15.0.4/source/js/main.js
  34. 51 0
      modules/boonex/antispam/updates/15.0.0_15.0.1/install/config.php
  35. 13 0
      modules/boonex/antispam/updates/15.0.0_15.0.1/install/installer.php
  36. 6 0
      modules/boonex/antispam/updates/15.0.0_15.0.1/install/sql/enable.sql
  37. 635 0
      modules/boonex/antispam/updates/15.0.0_15.0.1/source/classes/BxAntispamModule.php
  38. 68 0
      modules/boonex/antispam/updates/15.0.0_15.0.1/source/install/config.php
  39. 262 0
      modules/boonex/antispam/updates/15.0.0_15.0.1/source/install/sql/enable.sql
  40. 27 0
      modules/boonex/antispam/updates/15.0.0_15.0.1/source/template/images/icons/std-icon.svg
  41. 51 0
      modules/boonex/classes/updates/15.0.2_15.0.3/install/config.php
  42. 13 0
      modules/boonex/classes/updates/15.0.2_15.0.3/install/installer.php
  43. 33 0
      modules/boonex/classes/updates/15.0.2_15.0.3/source/classes/BxClssFormsEntryHelper.php
  44. 152 0
      modules/boonex/classes/updates/15.0.2_15.0.3/source/install/config.php
  45. 58 0
      modules/boonex/courses/updates/15.0.1_15.0.2/install/config.php
  46. 13 0
      modules/boonex/courses/updates/15.0.1_15.0.2/install/installer.php
  47. 152 0
      modules/boonex/courses/updates/15.0.1_15.0.2/source/install/config.php
  48. 56 0
      modules/boonex/courses/updates/15.0.1_15.0.2/source/template/unit_personal.html
  49. 51 0
      modules/boonex/credits/updates/15.0.2_15.0.3/install/config.php
  50. 13 0
      modules/boonex/credits/updates/15.0.2_15.0.3/install/installer.php
  51. 341 0
      modules/boonex/credits/updates/15.0.2_15.0.3/source/classes/BxCreditsConfig.php
  52. 1517 0
      modules/boonex/credits/updates/15.0.2_15.0.3/source/classes/BxCreditsModule.php
  53. 216 0
      modules/boonex/credits/updates/15.0.2_15.0.3/source/classes/BxCreditsTemplate.php
  54. 77 0
      modules/boonex/credits/updates/15.0.2_15.0.3/source/install/config.php
  55. 25 0
      modules/boonex/credits/updates/15.0.2_15.0.3/source/install/sql/enable-app.sql
  56. 51 0
      modules/boonex/datafox/updates/12.0.0_15.0.0/install/config.php
  57. 13 0
      modules/boonex/datafox/updates/12.0.0_15.0.0/install/installer.php
  58. 2 0
      modules/boonex/datafox/updates/12.0.0_15.0.0/install/sql/enable.sql
  59. 68 0
      modules/boonex/datafox/updates/12.0.0_15.0.0/source/install/config.php
  60. 30 0
      modules/boonex/datafox/updates/12.0.0_15.0.0/source/install/sql/enable.sql
  61. 3 0
      modules/boonex/datafox/updates/12.0.0_15.0.0/source/template/images/icons/std-icon.svg
  62. 51 0
      modules/boonex/facebook_connect/updates/15.0.0_15.0.1/install/config.php
  63. 13 0
      modules/boonex/facebook_connect/updates/15.0.0_15.0.1/install/installer.php
  64. 2 0
      modules/boonex/facebook_connect/updates/15.0.0_15.0.1/install/sql/enable.sql
  65. 68 0
      modules/boonex/facebook_connect/updates/15.0.0_15.0.1/source/install/config.php
  66. 51 0
      modules/boonex/facebook_connect/updates/15.0.0_15.0.1/source/install/sql/enable.sql
  67. 6 0
      modules/boonex/facebook_connect/updates/15.0.0_15.0.1/source/template/images/icons/std-icon.svg
  68. 51 0
      modules/boonex/files/updates/15.0.4_15.0.5/install/config.php
  69. 13 0
      modules/boonex/files/updates/15.0.4_15.0.5/install/installer.php
  70. 4 0
      modules/boonex/files/updates/15.0.4_15.0.5/install/langs/en.xml
  71. 36 0
      modules/boonex/files/updates/15.0.4_15.0.5/source/classes/BxFilesFormsEntryHelper.php
  72. 669 0
      modules/boonex/files/updates/15.0.4_15.0.5/source/classes/BxFilesModule.php
  73. 142 0
      modules/boonex/files/updates/15.0.4_15.0.5/source/install/config.php
  74. 310 0
      modules/boonex/files/updates/15.0.4_15.0.5/source/install/langs/en.xml
  75. 51 0
      modules/boonex/forum/updates/15.0.4_15.0.5/install/config.php
  76. 13 0
      modules/boonex/forum/updates/15.0.4_15.0.5/install/installer.php
  77. 274 0
      modules/boonex/forum/updates/15.0.4_15.0.5/source/classes/BxForumConfig.php
  78. 661 0
      modules/boonex/forum/updates/15.0.4_15.0.5/source/classes/BxForumGrid.php
  79. 26 0
      modules/boonex/forum/updates/15.0.4_15.0.5/source/classes/BxForumMenuCategories.php
  80. 151 0
      modules/boonex/forum/updates/15.0.4_15.0.5/source/install/config.php
  81. 51 0
      modules/boonex/google_connect/updates/15.0.0_15.0.1/install/config.php
  82. 13 0
      modules/boonex/google_connect/updates/15.0.0_15.0.1/install/installer.php
  83. 2 0
      modules/boonex/google_connect/updates/15.0.0_15.0.1/install/sql/enable.sql
  84. 68 0
      modules/boonex/google_connect/updates/15.0.0_15.0.1/source/install/config.php
  85. 42 0
      modules/boonex/google_connect/updates/15.0.0_15.0.1/source/install/sql/enable.sql
  86. 3 0
      modules/boonex/google_connect/updates/15.0.0_15.0.1/source/template/images/icons/std-icon.svg
  87. 51 0
      modules/boonex/intercom/updates/15.0.0_15.0.1/install/config.php
  88. 13 0
      modules/boonex/intercom/updates/15.0.0_15.0.1/install/installer.php
  89. 2 0
      modules/boonex/intercom/updates/15.0.0_15.0.1/install/sql/enable.sql
  90. 68 0
      modules/boonex/intercom/updates/15.0.0_15.0.1/source/install/config.php
  91. 40 0
      modules/boonex/intercom/updates/15.0.0_15.0.1/source/install/sql/enable.sql
  92. 16 0
      modules/boonex/intercom/updates/15.0.0_15.0.1/source/template/images/icons/std-icon.svg
  93. 51 0
      modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/install/config.php
  94. 13 0
      modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/install/installer.php
  95. 2 0
      modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/install/sql/enable.sql
  96. 68 0
      modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/source/install/config.php
  97. 42 0
      modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/source/install/sql/enable.sql
  98. 16 0
      modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/source/template/images/icons/std-icon.svg
  99. 51 0
      modules/boonex/mailchimp/updates/15.0.0_15.0.1/install/config.php
  100. 13 0
      modules/boonex/mailchimp/updates/15.0.0_15.0.1/install/installer.php

+ 51 - 0
modules/boonex/accounts/updates/15.0.1_15.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' => 'Accounts Manager',
+    'version_from' => '15.0.1',
+    'version_to' => '15.0.2',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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_15.0.1_15.0.2/',
+    'home_uri' => 'accounts_update_1501_1502',
+
+    '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' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Accounts',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/accounts/updates/15.0.1_15.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 BxAccntUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 52 - 0
modules/boonex/accounts/updates/15.0.1_15.0.2/source/classes/BxAccntDb.php

@@ -0,0 +1,52 @@
+<?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 BxAccntDb extends BxBaseModGeneralDb
+{
+    function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+    }
+
+    public function getAccountFields()
+    {
+        return $this->getFields('sys_accounts');
+    }
+
+    public function getAccountIds()
+    {
+    	return $this->getColumn("SELECT `id` FROM `sys_accounts` WHERE `name` != 'Robot'", "id");
+    }
+
+    public function updateAccount($aSet, $aWhere)
+    {
+    	return (int)$this->query("UPDATE `sys_accounts` SET " . $this->arrayToSQL($aSet) . " WHERE " . $this->arrayToSQL($aWhere, ' AND ')) > 0;
+    }
+    
+    public function getEntriesNumByParams ($aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        $sSql = "SELECT COUNT(*) FROM `sys_accounts` WHERE 1";
+        
+        foreach($aParams as $aValue){
+            if (!$this->isValidOperator($aValue['operator'])) {
+                throw new Exception('Invalid operator in getEntriesNumByParams method');
+            }
+            $sSql .= ' AND ' . (isset($aValue['table'])? '`' . $aValue['table'] .'`.' : '') . '`' . $aValue['key'] ."` " . $aValue['operator'] . " '" . $aValue['value'] . "'";
+        }
+        
+        $sQuery = $this->prepare($sSql);
+        return $this->getOne($sQuery);
+    }
+}
+
+/** @} */

+ 71 - 0
modules/boonex/accounts/updates/15.0.1_15.0.2/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' => '15.0.2',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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(),
+
+);
+
+/** @} */

+ 27 - 0
modules/boonex/accounts/updates/15.0.1_15.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_830_8279)"/>
+<path d="M51 27.5C51 33.5751 46.0751 38.5 40 38.5C33.9249 38.5 29 33.5751 29 27.5C29 21.4249 33.9249 16.5 40 16.5C46.0751 16.5 51 21.4249 51 27.5Z" fill="black" fill-opacity="0.1"/>
+<path d="M36 53.5C36 59.5751 31.0751 64.5 25 64.5C18.9249 64.5 14 59.5751 14 53.5C14 47.4249 18.9249 42.5 25 42.5C31.0751 42.5 36 47.4249 36 53.5Z" fill="black" fill-opacity="0.1"/>
+<path d="M66 53.5C66 59.5751 61.0751 64.5 55 64.5C48.9249 64.5 44 59.5751 44 53.5C44 47.4249 48.9249 42.5 55 42.5C61.0751 42.5 66 47.4249 66 53.5Z" fill="black" fill-opacity="0.1"/>
+<circle cx="40" cy="26.5" r="11" fill="url(#paint1_linear_830_8279)"/>
+<circle cx="25" cy="52.5" r="11" fill="url(#paint2_linear_830_8279)"/>
+<circle cx="55" cy="52.5" r="11" fill="url(#paint3_linear_830_8279)"/>
+<defs>
+<linearGradient id="paint0_linear_830_8279" x1="40" y1="0" x2="40" y2="80" gradientUnits="userSpaceOnUse">
+<stop stop-color="#EA580C"/>
+<stop offset="1" stop-color="#7C2D12"/>
+</linearGradient>
+<linearGradient id="paint1_linear_830_8279" x1="40" y1="15.5" x2="40" y2="37.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FDE68A"/>
+<stop offset="1" stop-color="#D97706"/>
+</linearGradient>
+<linearGradient id="paint2_linear_830_8279" x1="25" y1="41.5" x2="25" y2="63.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F9FAFB"/>
+<stop offset="1" stop-color="#9CA3AF"/>
+</linearGradient>
+<linearGradient id="paint3_linear_830_8279" x1="55" y1="41.5" x2="55" y2="63.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F9FAFB"/>
+<stop offset="1" stop-color="#9CA3AF"/>
+</linearGradient>
+</defs>
+</svg>

+ 51 - 0
modules/boonex/acl/updates/15.0.3_15.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' => 'Paid Levels',
+    'version_from' => '15.0.3',
+    'version_to' => '15.0.4',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/acl/updates/update_15.0.3_15.0.4/',
+    'home_uri' => 'acl_update_1503_1504',
+
+    'module_dir' => 'boonex/acl/',
+    'module_uri' => 'acl',
+
+    'db_prefix' => 'bx_acl_',
+    'class_prefix' => 'BxAcl',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Paid Levels',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/acl/updates/15.0.3_15.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 BxAclUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 17 - 0
modules/boonex/acl/updates/15.0.3_15.0.4/install/sql/install.sql

@@ -0,0 +1,17 @@
+SET @sName = 'bx_acl';
+
+
+-- GRIDS
+UPDATE `sys_objects_grid` SET `source`='SELECT `tlp`.*, `tl`.`ID` AS `level_id`, `tl`.`Name` AS `level_name`, `tl`.`Icon` AS `level_icon` FROM `bx_acl_level_prices` AS `tlp` LEFT JOIN `sys_acl_levels` AS `tl` ON `tlp`.`level_id`=`tl`.`ID` WHERE `tlp`.`active`<>''0'' && `tl`.`Active`=''yes'' ' WHERE `object`='bx_acl_view';
+
+DELETE FROM `sys_grid_fields` WHERE `object`='bx_acl_view';
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_acl_view', 'level_icon', '_bx_acl_grid_column_level_icon', '5%', 0, 0, '', 1),
+('bx_acl_view', 'level_name', '_bx_acl_grid_column_level_name', '15%', 1, 16, '', 2),
+('bx_acl_view', 'caption', '_bx_acl_grid_column_caption', '10%', 1, 16, '', 3),
+('bx_acl_view', 'description', '_bx_acl_grid_column_description', '10%', 1, 8, '', 4),
+('bx_acl_view', 'details', '_bx_acl_grid_column_details', '10%', 1, 8, '', 5),
+('bx_acl_view', 'price', '_bx_acl_grid_column_price', '10%', 0, 16, '', 6),
+('bx_acl_view', 'period', '_bx_acl_grid_column_period', '10%', 0, 16, '', 7),
+('bx_acl_view', 'trial', '_bx_acl_grid_column_trial', '10%', 0, 16, '', 8),
+('bx_acl_view', 'actions', '', '20%', 0, '', '', 9);

+ 262 - 0
modules/boonex/acl/updates/15.0.3_15.0.4/source/classes/BxAclGridView.php

@@ -0,0 +1,262 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    PaidLevels Paid Levels
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+require_once('BxAclGridLevels.php');
+
+class BxAclGridView extends BxAclGridLevels
+{
+    /**
+     * Array with check_sum => JS_code pairs of all JS codes 
+     * which should be added to the page.
+     */
+    protected $_aJsCodes;
+
+    protected $_iLogged;
+    protected $_iOwner;
+    protected $_oPayment;
+    protected $_bTypeSingle;
+    protected $_bTypeRecurring;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_aJsCodes = array();
+
+        $this->_iLogged = bx_get_logged_profile_id();
+        $this->_iOwner = $this->_oModule->_oConfig->getOwner();
+        $this->_oPayment = BxDolPayments::getInstance();
+        $this->_bTypeSingle = $this->_oPayment->isAcceptingPayments($this->_iOwner, BX_PAYMENT_TYPE_SINGLE);
+        $this->_bTypeRecurring = !$this->_oPayment->isCreditsOnly() && $this->_oPayment->isAcceptingPayments($this->_iOwner, BX_PAYMENT_TYPE_RECURRING);
+    }
+
+    public function performActionChoose()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!$this->_iLogged)
+            return $this->_bIsApi ? [] : echoJson([]);
+
+        $aIds = $this->_getIds();
+        if($aIds === false)
+            return $this->_bIsApi ? [] : echoJson([]);
+
+        $aItem = $this->_oModule->_oDb->getPrices(['type' => 'by_id', 'value' => $aIds[0]]);
+        if(!is_array($aItem) || empty($aItem) || (float)$aItem['price'] != 0)
+            return $this->_bIsApi ? [] : echoJson(array());
+
+        $aResult = [];
+        $iUserId = bx_get_logged_profile_id();
+        if(BxDolAcl::getInstance()->setMembership($iUserId, $aItem['level_id'], ['period' => $aItem['period'], 'period_unit' => $aItem['period_unit']], true))
+            $aResult = ['grid' => $this->getCode(false), 'blink' => $aItem['id'], 'msg' => _t('_bx_acl_msg_performed')];
+        else
+            $aResult = ['msg' => _t('_bx_acl_err_cannot_perform')];
+
+        return $this->_bIsApi ? [] : echoJson($aResult);
+    }
+
+    public function performActionBuy()
+    {
+        if(!$this->_iLogged)
+            return $this->_bIsApi ? [] : echoJson([]);
+
+        if(!$this->_bIsApi)
+            return echoJson([]);
+        
+        $aIds = $this->_getIds();
+        if($aIds === false || !is_array($aIds))
+            return [];
+
+        if(($sDescriptor = array_shift($aIds))) {
+            $aDescriptor = $this->_oPayment->getActiveObject()->_oConfig->descriptorS2A($sDescriptor);
+            if($aDescriptor && is_array($aDescriptor) && (int)$aDescriptor[0] == $this->_iOwner) {
+                $aResult = $this->_oPayment->addToCart($this->_iOwner, $this->MODULE, (int)$aDescriptor[2], 1, true);
+                if(isset($aResult['code']) && (int)$aResult['code'] != 0)
+                    return [bx_api_get_msg($aResult['message'])];
+            }
+        }
+
+        return [];
+    }
+
+    public function performActionSubscribe()
+    {
+        if(!$this->_iLogged)
+            return $this->_bIsApi ? [] : echoJson([]);
+
+        if(!$this->_bIsApi)
+            return echoJson([]);
+        
+        $aIds = $this->_getIds();
+        if($aIds === false)
+            return [];
+
+        //TODO: Payment Provider selector should be realized.
+        $aResult = $this->_oPayment->subscribeWithAddons($this->_iOwner, 'stripe_v3', $this->MODULE, array_shift($aIds), 1, true);
+        if(isset($aResult['code']) && (int)$aResult['code'] != 0)
+            return [bx_api_get_msg($aResult['message'])];
+
+        return [];
+    }
+
+    public function getCode($isDisplayHeader = true)
+    {
+    	return parent::getCode($isDisplayHeader) . $this->getJsCode();
+    }
+
+    public function getJsCode()
+    {
+        if(empty($this->_aJsCodes) || !is_array($this->_aJsCodes))
+            return '';
+
+        return implode('', $this->_aJsCodes);
+    }
+
+    public function decodeDataAPI($aData)
+    {
+        $aResults = parent::decodeDataAPI($aData);
+        foreach($aResults as $iKey => $aResult)
+            if(($iMemLevelId = $aData[$iKey]['level_id'] ?? false) !== false)
+                 $aResults[$iKey]['level_id'] = $iMemLevelId;
+
+        return $aResults;
+    }
+    protected function _getCellLevelIcon($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = $this->_oModule->_oTemplate->displayLevelIcon($mixedValue);
+    	return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellLevelName($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(!$this->_bIsApi)
+            $mixedValue = $this->_oModule->_oTemplate->parseLink('javascript:void(0)', $mixedValue, [
+                'onclick' => $this->_oModule->_oConfig->getJsObject('main') . '.viewActions(this, ' . $aRow['level_id'] . ')'
+            ]);
+
+    	return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionChoose ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if((float)$aRow['price'] != 0 || BxDolAcl::getInstance()->isMemberLevelInSet(array($aRow['level_id'])))
+            return $this->_bIsApi ? [] : '';
+
+        if($this->_bIsApi)
+            return array_merge($a, ['name' => $sKey, 'type' => 'callback', 'on_callback' => 'hide']);
+
+        if(!$this->_iLogged)
+            $a['attr'] = array_merge($a['attr'], [
+                'onclick' => "window.open('" . $this->_getLoginUrl() . "','_self');"
+            ]);
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getActionBuy ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if((float)$aRow['price'] == 0 || !$this->_bTypeSingle || ($this->_bTypeRecurring && getParam($CNF['PARAM_RECURRING_PRIORITIZE']) == 'on' && !$this->_isLifetime($aRow)))
+            return $this->_bIsApi ? [] : '';
+
+        if($this->_bIsApi)
+            return array_merge($a, [
+                'name' => $sKey, 
+                'type' => 'callback', 
+                'on_callback' => 'redirect',
+                'redirect_url' => bx_api_get_relative_url($this->_oPayment->getCartUrl($this->_iOwner)),
+                'payment_type' => BX_PAYMENT_TYPE_SINGLE,
+                'seller_id' => $this->_iOwner,
+                'items' => [$this->_oPayment->getCartItemDescriptor($this->_iOwner, $this->_oModule->_oConfig->getId(), $aRow['id'], 1)],
+            ]);
+        
+        if($this->_iLogged) {
+            $aJs = $this->_oPayment->getAddToCartJs($this->_iOwner, $this->MODULE, $aRow['id'], 1, true);
+            if(!empty($aJs) && is_array($aJs)) {
+                list($sJsCode, $sJsMethod) = $aJs;
+
+                $sJsCodeCheckSum = md5($sJsCode);
+                if(!isset($this->_aJsCodes[$sJsCodeCheckSum]))
+                    $this->_aJsCodes[$sJsCodeCheckSum] = $sJsCode;
+
+                $a['attr'] = array(
+                    'title' => bx_html_attribute(_t('_bx_acl_grid_action_buy_title')),
+                    'onclick' => $sJsMethod
+                );
+            }
+        }
+        else
+            $a['attr'] = array_merge($a['attr'], [
+                'onclick' => "window.open('" . $this->_getLoginUrl() . "','_self');"
+            ]);
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getActionSubscribe ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if((float)$aRow['price'] == 0 || !$this->_bTypeRecurring || ($this->_bTypeSingle && $this->_isLifetime($aRow)))
+            return $this->_bIsApi ? [] : '';
+
+        if($this->_bIsApi)
+            return array_merge($a, [
+                'name' => $sKey, 
+                'type' => 'object', 
+                'object_name' => 'stripe_v3',
+                'payment_type' => BX_PAYMENT_TYPE_RECURRING,
+                'seller_id' => $this->_iOwner,
+                'items' => [$this->_oPayment->getCartItemDescriptor($this->_iOwner, $this->_oModule->_oConfig->getId(), $aRow['id'], 1)],
+            ]);
+
+        if($this->_iLogged) {
+            $aJs = $this->_oPayment->getSubscribeJs($this->_iOwner, '', $this->MODULE, $aRow['id'], 1);
+            if(!empty($aJs) && is_array($aJs)) {
+                list($sJsCode, $sJsMethod) = $aJs;
+
+                $sJsCodeCheckSum = md5($sJsCode);
+                if(!isset($this->_aJsCodes[$sJsCodeCheckSum]))
+                    $this->_aJsCodes[$sJsCodeCheckSum] = $sJsCode;
+
+                $a['attr'] = array(
+                    'title' => bx_html_attribute(_t('_bx_acl_grid_action_subscribe_title')),
+                    'onclick' => $sJsMethod
+                );
+            }
+        }
+        else
+            $a['attr'] = array_merge($a['attr'], [
+                'onclick' => "window.open('" . $this->_getLoginUrl() . "','_self');"
+            ]);
+
+    	return parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if($this->_iLogged && ($oLogged = BxDolProfile::getInstance($this->_iLogged)) !== false)
+            $this->_aOptions['source'] .= "AND `tl`.`UnavailableTo` NOT LIKE " . $this->_oModule->_oDb->escape('%|' . $oLogged->getModule() . '|%') . " ";
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);;
+    }
+
+    protected function _isLifetime($aRow)
+    {
+        return empty($aRow['period']) && empty($aRow['period_unit']);
+    }
+
+    protected function _getLoginUrl()
+    {
+        return bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=login'));
+    }
+}
+
+/** @} */

+ 70 - 0
modules/boonex/acl/updates/15.0.3_15.0.4/source/install/config.php

@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    PaidLevels Paid Levels
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_acl',
+    'title' => 'Paid Levels',
+    'note' => 'Paid Levels module.',
+    'version' => '15.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/acl/',
+    'home_uri' => 'acl',
+
+    'db_prefix' => 'bx_acl_',
+    'class_prefix' => 'BxAcl',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Paid Levels',
+
+    /**
+     * 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(),
+);
+
+/** @} */

+ 160 - 0
modules/boonex/acl/updates/15.0.3_15.0.4/source/install/sql/install.sql

@@ -0,0 +1,160 @@
+SET @sName = 'bx_acl';
+
+
+-- TABLES
+CREATE TABLE `bx_acl_level_prices` (
+  `id` int(11) NOT NULL auto_increment,
+  `level_id` int(11) unsigned NOT NULL default '0',
+  `name` varchar(128) NOT NULL default '',
+  `caption` varchar(128) NOT NULL default '',
+  `description` text NOT NULL,
+  `details` text NOT NULL,
+  `period` int(11) unsigned NOT NULL default '1',
+  `period_unit` varchar(32) NOT NULL default '',
+  `trial` int(11) unsigned NOT NULL default '0',
+  `price` float unsigned NOT NULL default '1',
+  `immediate` tinyint(4) NOT NULL default '1',
+  `added` int(11) NOT NULL default '0',
+  `active` tinyint(4) NOT NULL default '1',
+  `order` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+);
+
+INSERT INTO `bx_acl_level_prices` (`level_id`, `name`, `caption`, `description`, `details`, `period`, `period_unit`, `trial`, `price`, `immediate`, `added`, `active`, `order`) VALUES
+(3, 'standard', '_bx_acl_txt_level_caption_standard', '_bx_acl_txt_level_description_standard', '_bx_acl_txt_level_details_standard', 0, '', 0, 0, 1, UNIX_TIMESTAMP(), 0, 1);
+
+-- TABLE: licenses
+CREATE TABLE IF NOT EXISTS `bx_acl_licenses` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `profile_id` int(11) unsigned NOT NULL default '0',
+  `price_id` int(11) unsigned NOT NULL default '0',
+  `type` varchar(16) NOT NULL default 'single',
+  `order` varchar(32) NOT NULL default '',
+  `license` varchar(32) NOT NULL default '',
+  `added` int(11) unsigned NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `price_id` (`price_id`, `profile_id`),
+  KEY `license` (`license`)
+);
+
+
+-- 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_acl_price', @sName, '_bx_acl_form_price', '', '', 'do_submit', 'bx_acl_level_prices', 'id', '', '', '', 0, 1, 'BxAclFormPrice', 'modules/boonex/acl/classes/BxAclFormPrice.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_acl_price_add', @sName, 'bx_acl_price', '_bx_acl_form_price_display_add', 0),
+('bx_acl_price_edit', @sName, 'bx_acl_price', '_bx_acl_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_acl_price', @sName, 'id', '', '', 0, 'hidden', '_bx_acl_form_price_input_sys_id', '', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_acl_price', @sName, 'level_id', '', '', 0, 'select', '_bx_acl_form_price_input_sys_level_id', '_bx_acl_form_price_input_level_id', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_acl_form_price_input_err_level_id', 'Int', '', 1, 0),
+('bx_acl_price', @sName, 'name', '', '', 0, 'text', '_bx_acl_form_price_input_sys_name', '_bx_acl_form_price_input_name', '_bx_acl_form_price_input_inf_name', 1, 0, 0, '', '', '', 'Avail', '', '_bx_acl_form_price_input_err_name', 'Xss', '', 1, 0),
+('bx_acl_price', @sName, 'caption', '0', '', 0, 'text_translatable', '_bx_acl_form_price_input_sys_caption', '_bx_acl_form_price_input_caption', '_bx_acl_form_price_input_inf_caption', 1, 0, 0, '', '', '', 'AvailTranslatable', 'a:1:{i:0;s:7:"caption";}', '_bx_acl_form_price_input_err_caption', 'Xss', '', 0, 0),
+('bx_acl_price', @sName, 'description', '0', '', 0, 'textarea_translatable', '_bx_acl_form_price_input_sys_description', '_bx_acl_form_price_input_description', '', 0, 0, 3, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_acl_price', @sName, 'details', '0', '', 0, 'textarea_translatable', '_bx_acl_form_price_input_sys_details', '_bx_acl_form_price_input_details', '', 0, 0, 3, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_acl_price', @sName, 'period', '', '', 0, 'text', '_bx_acl_form_price_input_sys_period', '_bx_acl_form_price_input_period', '_bx_acl_form_price_input_inf_period', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_acl_price', @sName, 'period_unit', '', '#!bx_acl_period_units', 0, 'select', '_bx_acl_form_price_input_sys_period_unit', '_bx_acl_form_price_input_period_unit', '_bx_acl_form_price_input_inf_period_unit', 1, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_acl_price', @sName, 'trial', '', '', 0, 'text', '_bx_acl_form_price_input_sys_trial', '_bx_acl_form_price_input_trial', '_bx_acl_form_price_input_inf_trial', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_acl_price', @sName, 'price', '', '', 0, 'price', '_bx_acl_form_price_input_sys_price', '_bx_acl_form_price_input_price', '_bx_acl_form_price_input_inf_price', 1, 0, 0, '', '', '', '', '', '', 'Float', '', 1, 0),
+('bx_acl_price', @sName, 'immediate', 1, '', 1, 'switcher', '_bx_acl_form_price_input_sys_immediate', '_bx_acl_form_price_input_immediate', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_acl_price', @sName, 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_acl_price', @sName, 'do_submit', '_bx_acl_form_price_input_do_submit', '', 0, 'submit', '_bx_acl_form_price_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_acl_price', @sName, 'do_cancel', '_bx_acl_form_price_input_do_cancel', '', 0, 'button', '_bx_acl_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_acl_price_add', 'id', 2147483647, 0, 1),
+('bx_acl_price_add', 'level_id', 2147483647, 1, 2),
+('bx_acl_price_add', 'name', 2147483647, 1, 3),
+('bx_acl_price_add', 'caption', 2147483647, 1, 4),
+('bx_acl_price_add', 'description', 2147483647, 1, 5),
+('bx_acl_price_add', 'details', 2147483647, 1, 6),
+('bx_acl_price_add', 'price', 2147483647, 1, 7),
+('bx_acl_price_add', 'period', 2147483647, 1, 8),
+('bx_acl_price_add', 'period_unit', 2147483647, 1, 9),
+('bx_acl_price_add', 'trial', 2147483647, 1, 10),
+('bx_acl_price_add', 'immediate', 2147483647, 1, 11),
+('bx_acl_price_add', 'controls', 2147483647, 1, 12),
+('bx_acl_price_add', 'do_submit', 2147483647, 1, 13),
+('bx_acl_price_add', 'do_cancel', 2147483647, 1, 14),
+
+('bx_acl_price_edit', 'id', 2147483647, 1, 1),
+('bx_acl_price_edit', 'level_id', 2147483647, 1, 2),
+('bx_acl_price_edit', 'name', 2147483647, 1, 3),
+('bx_acl_price_edit', 'caption', 2147483647, 1, 4),
+('bx_acl_price_edit', 'description', 2147483647, 1, 5),
+('bx_acl_price_edit', 'details', 2147483647, 1, 6),
+('bx_acl_price_edit', 'price', 2147483647, 1, 7),
+('bx_acl_price_edit', 'period', 2147483647, 1, 8),
+('bx_acl_price_edit', 'period_unit', 2147483647, 1, 9),
+('bx_acl_price_edit', 'trial', 2147483647, 1, 10),
+('bx_acl_price_edit', 'immediate', 2147483647, 1, 11),
+('bx_acl_price_edit', 'controls', 2147483647, 1, 12),
+('bx_acl_price_edit', 'do_submit', 2147483647, 1, 13),
+('bx_acl_price_edit', 'do_cancel', 2147483647, 1, 14);
+
+
+-- PRE-VALUES
+INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
+('bx_acl_period_units', '_bx_acl_pre_lists_period_units', 'bx_acl', '0');
+
+INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALUES
+('bx_acl_period_units', '', 0, '_sys_please_select', ''),
+('bx_acl_period_units', 'day', 1, '_bx_acl_pre_values_day', ''),
+('bx_acl_period_units', 'week', 2, '_bx_acl_pre_values_week', ''),
+('bx_acl_period_units', 'month', 3, '_bx_acl_pre_values_month', ''),
+('bx_acl_period_units', 'year', 4, '_bx_acl_pre_values_year', '');
+
+
+-- GRIDS
+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_acl_administration', 'Sql', 'SELECT * FROM `bx_acl_level_prices` WHERE 1 ', 'bx_acl_level_prices', 'id', 'order', 'active', '', 100, NULL, 'start', '', 'period,period_unit,price', 'caption', 'like', '', '', 192, 'BxAclGridAdministration', 'modules/boonex/acl/classes/BxAclGridAdministration.php'),
+('bx_acl_view', 'Sql', 'SELECT `tlp`.*, `tl`.`ID` AS `level_id`, `tl`.`Name` AS `level_name`, `tl`.`Icon` AS `level_icon` FROM `bx_acl_level_prices` AS `tlp` LEFT JOIN `sys_acl_levels` AS `tl` ON `tlp`.`level_id`=`tl`.`ID` WHERE `tlp`.`active`<>''0'' && `tl`.`Active`=''yes'' ', 'bx_acl_level_prices', 'id', 'order', '', '', 100, NULL, 'start', '', 'period,period_unit,price', 'tl`.`Name', 'like', '', '', 2147483647, 'BxAclGridView', 'modules/boonex/acl/classes/BxAclGridView.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_acl_administration', 'checkbox', '_sys_select', '2%', 0, '', '', 1),
+('bx_acl_administration', 'order', '', '2%', 0, '', '', 2),
+('bx_acl_administration', 'switcher', '', '6%', 0, '', '', 3),
+('bx_acl_administration', 'level_id', '_bx_acl_grid_column_level_id', '10%', 0, 16, '', 4),
+('bx_acl_administration', 'name', '_bx_acl_grid_column_name', '10%', 0, 16, '', 5),
+('bx_acl_administration', 'caption', '_bx_acl_grid_column_caption', '10%', 1, 16, '', 6),
+('bx_acl_administration', 'description', '_bx_acl_grid_column_description', '8%', 1, 8, '', 7),
+('bx_acl_administration', 'details', '_bx_acl_grid_column_details', '8%', 1, 8, '', 8),
+('bx_acl_administration', 'price', '_bx_acl_grid_column_price', '8%', 0, 16, '', 9),
+('bx_acl_administration', 'period', '_bx_acl_grid_column_period', '8%', 0, 16, '', 10),
+('bx_acl_administration', 'trial', '_bx_acl_grid_column_trial', '8%', 0, 16, '', 11),
+('bx_acl_administration', 'actions', '', '20%', 0, '', '', 12),
+
+('bx_acl_view', 'level_icon', '_bx_acl_grid_column_level_icon', '5%', 0, 0, '', 1),
+('bx_acl_view', 'level_name', '_bx_acl_grid_column_level_name', '15%', 1, 16, '', 2),
+('bx_acl_view', 'caption', '_bx_acl_grid_column_caption', '10%', 1, 16, '', 3),
+('bx_acl_view', 'description', '_bx_acl_grid_column_description', '10%', 1, 8, '', 4),
+('bx_acl_view', 'details', '_bx_acl_grid_column_details', '10%', 1, 8, '', 5),
+('bx_acl_view', 'price', '_bx_acl_grid_column_price', '10%', 0, 16, '', 6),
+('bx_acl_view', 'period', '_bx_acl_grid_column_period', '10%', 0, 16, '', 7),
+('bx_acl_view', 'trial', '_bx_acl_grid_column_trial', '10%', 0, 16, '', 8),
+('bx_acl_view', 'actions', '', '20%', 0, '', '', 9);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_acl_administration', 'independent', 'add', '_bx_acl_grid_action_add', '', 0, 0, 1),
+('bx_acl_administration', 'single', 'edit', '_bx_acl_grid_action_edit', 'pencil-alt', 1, 0, 1),
+('bx_acl_administration', 'single', 'delete', '_bx_acl_grid_action_delete', 'remove', 1, 1, 2),
+('bx_acl_administration', 'bulk', 'delete', '_bx_acl_grid_action_delete', '', 0, 1, 1),
+
+('bx_acl_view', 'single', 'buy', '_bx_acl_grid_action_buy', 'cart-plus', 0, 0, 1),
+('bx_acl_view', 'single', 'subscribe', '_bx_acl_grid_action_subscribe', 'credit-card', 0, 0, 2),
+('bx_acl_view', 'single', 'choose', '_bx_acl_grid_action_choose', 'far check-square', 0, 0, 3);
+
+
+-- STUDIO PAGE & WIDGET
+INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
+(3, @sName, '_bx_acl', '_bx_acl', 'bx_acl@modules/boonex/acl/|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, 'users', '{url_studio}module.php?name=bx_acl', '', 'bx_acl@modules/boonex/acl/|std-icon.svg', '_bx_acl', '', '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));

+ 51 - 0
modules/boonex/ads/updates/15.0.4_15.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' => '15.0.4',
+    'version_to' => '15.0.5',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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_15.0.4_15.0.5/',
+    'home_uri' => 'ads_update_1504_1505',
+
+    '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' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Ads',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/ads/updates/15.0.4_15.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);
+    }
+}

+ 453 - 0
modules/boonex/ads/updates/15.0.4_15.0.5/source/classes/BxAdsFormEntry.php

@@ -0,0 +1,453 @@
+<?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
+ *
+ * @{
+ */
+
+/**
+ * Create/Edit entry form
+ */
+class BxAdsFormEntry extends BxBaseModTextFormEntry
+{
+    protected $_aContentInfo;
+
+    protected $_bSources;
+    protected $_iCategory;
+    protected $_sGhostTemplateCover = 'form_ghost_template_cover.html';
+    protected $_bDisplayEditBudget;
+	
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        $this->MODULE = 'bx_ads';
+        parent::__construct($aInfo, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sJsObject = $this->_oModule->_oConfig->getJsObject('form');
+
+        $this->_bSources = $this->_oModule->_oConfig->isSources();
+        $this->_bDisplayEditBudget = $this->aParams['display'] == $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT_BUDGET'];
+
+        $this->_initCategoryFields((int)BxDolSession::getInstance()->getValue($this->aParams['display'] . '_category'));
+
+        if(!$this->_bSources) {
+            if(isset($this->aInputs[$CNF['FIELD_SOURCE_TYPE']]))
+                unset($this->aInputs[$CNF['FIELD_SOURCE_TYPE']]);
+
+            if(isset($this->aInputs[$CNF['FIELD_SOURCE']]))
+                unset($this->aInputs[$CNF['FIELD_SOURCE']]);
+        }
+
+        if(isset($this->aInputs[$CNF['FIELD_SOURCE_TYPE']], $this->aInputs[$CNF['FIELD_SOURCE']])) {
+            $aMask = ["javascript:%s.loadFromSource(this, '%s', '%s');", $sJsObject, $CNF['FIELD_SOURCE_TYPE'], $CNF['FIELD_SOURCE']];
+            $sOnBlur = call_user_func_array('sprintf', $aMask);
+
+            $this->aInputs[$CNF['FIELD_SOURCE']]['attrs']['onblur'] = $sOnBlur;
+        }
+
+        if(isset($this->aInputs[$CNF['FIELD_TITLE']], $this->aInputs[$CNF['FIELD_NAME']])) {
+            $aMask = array('mask' => "javascript:%s.checkName(this, '%s', '%s');", $sJsObject, $CNF['FIELD_TITLE'], $CNF['FIELD_NAME']);
+            if($this->aParams['display'] == $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'] && bx_get('id') !== false) {
+                $aMask['mask'] = "javascript:%s.checkName(this, '%s', '%s', %d);";
+                $aMask[] = (int)bx_get('id');
+            }
+
+            $sOnBlur = call_user_func_array('sprintf', array_values($aMask)); 
+        	$this->aInputs[$CNF['FIELD_TITLE']]['attrs']['onblur'] = $sOnBlur;
+        	$this->aInputs[$CNF['FIELD_NAME']]['attrs']['onblur'] = $sOnBlur;
+        }
+
+        if(isset($this->aInputs[$CNF['FIELD_AUCTION']]) && !$this->_oModule->_oConfig->isAuction())
+            unset($this->aInputs[$CNF['FIELD_AUCTION']]);
+
+        if(!$this->_oModule->_oConfig->isPromotion()) {
+            if(isset($this->aInputs[$CNF['FIELD_BUDGET_TOTAL']]))
+                unset($this->aInputs[$CNF['FIELD_BUDGET_TOTAL']]);
+
+            if(isset($this->aInputs[$CNF['FIELD_BUDGET_DAILY']]))
+                unset($this->aInputs[$CNF['FIELD_BUDGET_DAILY']]);
+        }
+
+        //--- Segmentation
+        if(isset($this->aInputs[$CNF['FIELD_SEG_AGE']])) {
+            if(!isset($this->aInputs[$CNF['FIELD_SEG_AGE']]['attrs']))
+                $this->aInputs[$CNF['FIELD_SEG_AGE']]['attrs'] = [];
+
+            $this->aInputs[$CNF['FIELD_SEG_AGE']]['attrs'] = array_merge($this->aInputs[$CNF['FIELD_SEG_AGE']]['attrs'], [
+                'min' => 0, 
+                'max' => 100
+            ]);
+            $this->aInputs[$CNF['FIELD_SEG_AGE']]['value'] = '18-99';
+        }
+
+        if(isset($this->aInputs[$CNF['FIELD_SEG_COUNTRY']]))
+            $this->aInputs[$CNF['FIELD_SEG_COUNTRY']]['values'] = array_merge(['' => _t('_bx_ads_form_entry_input_seg_country_all')], $this->aInputs[$CNF['FIELD_SEG_COUNTRY']]['values']);
+
+        $this->addMarkers([
+            'js_object' => $sJsObject
+        ]);
+    }
+
+    function getCode($bDynamicMode = false)
+    {
+        $sJs = $this->_oModule->_oTemplate->addJs(array('form.js'), $bDynamicMode);
+
+        $sCode = '';
+        if($bDynamicMode)
+            $sCode .= $sJs;
+
+        $sCode .= $this->_oModule->_oTemplate->getJsCode('form', [
+            'sObjNameForm' => $this->getJsObjectName()
+        ]);
+        $sCode .= parent::getCode($bDynamicMode);
+
+        return $sCode;
+    }
+
+    function initChecker ($aValues = [], $aSpecificValues = [])
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if($aValues && !empty($aValues['id']))
+            $this->_aContentInfo = $this->_oModule->_oDb->getContentInfoById($aValues['id']);
+        $bContentInfo = !empty($this->_aContentInfo) && is_array($this->_aContentInfo);
+
+        $iContentAuthorId = bx_get_logged_profile_id();
+        if($bContentInfo) {
+            $iContentAuthorId = (int)$this->_aContentInfo[$CNF['FIELD_AUTHOR']];
+
+            $this->_initCategoryFields($this->_aContentInfo[$CNF['FIELD_CATEGORY']]);
+        }
+
+        if($this->_bSources) {
+            if(isset($this->aInputs[$CNF['FIELD_SOURCE_TYPE']]) && !empty($iContentAuthorId)) {
+                $aSource = $this->_oModule->serviceGetSource($iContentAuthorId);
+                if(!empty($aSource) && is_array($aSource))
+                    $this->aInputs[$CNF['FIELD_SOURCE_TYPE']]['value'] = $aSource['name'];
+            }
+
+            if(isset($this->aInputs[$CNF['FIELD_SOURCE']]) && $bContentInfo) {
+                if(!isset($this->aInputs[$CNF['FIELD_SOURCE']]['attrs']))
+                    $this->aInputs[$CNF['FIELD_SOURCE']]['attrs'] = [];
+
+                $this->aInputs[$CNF['FIELD_SOURCE']]['attrs']['disabled'] = 'disabled';
+            }
+        }
+
+        if(isset($CNF['FIELD_COVER']) && isset($this->aInputs[$CNF['FIELD_COVER']])) {
+            if($bContentInfo)
+                $this->aInputs[$CNF['FIELD_COVER']]['content_id'] = $this->_aContentInfo[$CNF['FIELD_ID']];
+
+            $this->aInputs[$CNF['FIELD_COVER']]['ghost_template'] = $this->_oModule->_oTemplate->parseHtmlByName($this->_sGhostTemplateCover, $this->_getCoverGhostTmplVars($this->_aContentInfo));
+        }
+
+        if(isset($this->aInputs[$CNF['FIELD_SEG_AGE']]) && $bContentInfo)
+            $this->aInputs[$CNF['FIELD_SEG_AGE']]['value'] = implode('-', [$this->_aContentInfo[$CNF['FIELD_SEG_AGE_MIN']], $this->_aContentInfo[$CNF['FIELD_SEG_AGE_MAX']]]);
+
+        parent::initChecker ($aValues, $aSpecificValues);
+
+        $sKeyBt = $CNF['FIELD_BUDGET_TOTAL'];
+        if($this->isSubmitted() && $this->_bDisplayEditBudget && isset($this->aInputs[$sKeyBt], $aValues[$sKeyBt])) {
+            if((float)$this->getCleanValue($sKeyBt) < (float)$aValues[$sKeyBt]) {
+                $this->aInputs[$CNF['FIELD_BUDGET_TOTAL']]['error'] = _t('_bx_ads_form_entry_input_budget_total_err_decreased');
+                $this->_isValid = false;
+            }
+        }
+    }
+
+    public function insert ($aValsToAdd = [], $isIgnore = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        
+        $bPromotion = $this->_oModule->_oConfig->isPromotion();
+
+        $aValsToAdd[$CNF['FIELD_SINGLE']] = $this->_getSingleField();
+
+        if($bPromotion && isset($this->aInputs[$CNF['FIELD_BUDGET_TOTAL']]) && $this->getCleanValue($CNF['FIELD_BUDGET_TOTAL']) > 0)
+            $aValsToAdd[$CNF['FIELD_STATUS_ADMIN']] = BX_ADS_STATUS_ADMIN_UNPAID;
+
+        if(isset($this->aInputs[$CNF['FIELD_SEG_AGE']]) && ($sSegAge = $this->getCleanValue($CNF['FIELD_SEG_AGE'])) != '') {
+            $aSegAge = explode('-', $sSegAge);
+            if(is_array($aSegAge) && count($aSegAge) == 2)
+                list($aValsToAdd[$CNF['FIELD_SEG_AGE_MIN']], $aValsToAdd[$CNF['FIELD_SEG_AGE_MAX']]) = $aSegAge;
+        }
+
+        $iContentId = parent::insert($aValsToAdd, $isIgnore);
+        if(!empty($iContentId)) {
+            $this->processFiles($CNF['FIELD_COVER'], $iContentId, true);
+            
+            $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+            if(!empty($aContentInfo[$CNF['FIELD_PRICE']]))
+                $this->_oModule->_oDb->insertCommodity($iContentId, BX_ADS_COMMODITY_TYPE_PRODUCT, $aContentInfo[$CNF['FIELD_PRICE']]);
+
+            if($bPromotion && (float)$aContentInfo[$CNF['FIELD_BUDGET_TOTAL']] > 0)
+                $this->_oModule->_oDb->insertCommodity($iContentId, BX_ADS_COMMODITY_TYPE_PROMOTION, $aContentInfo[$CNF['FIELD_BUDGET_TOTAL']]);
+        }
+
+        return $iContentId;
+    }
+
+    public function update ($iContentId, $aValsToAdd = [], &$aTrackTextFieldsChanges = null)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bPromotion = $this->_oModule->_oConfig->isPromotion();
+
+        if(!$this->_bDisplayEditBudget)
+            $aValsToAdd[$CNF['FIELD_SINGLE']] = $this->_getSingleField();
+
+        if(isset($this->aInputs[$CNF['FIELD_SEG_AGE']]) && ($sSegAge = $this->getCleanValue($CNF['FIELD_SEG_AGE'])) != '') {
+            $aSegAge = explode('-', $sSegAge);
+            if(is_array($aSegAge) && count($aSegAge) == 2)
+                list($aValsToAdd[$CNF['FIELD_SEG_AGE_MIN']], $aValsToAdd[$CNF['FIELD_SEG_AGE_MAX']]) = $aSegAge;
+        }
+
+        $aTrackTextFieldsChanges = [];
+        $iResult = parent::update($iContentId, $aValsToAdd, $aTrackTextFieldsChanges);
+        if($bPromotion && in_array($CNF['FIELD_BUDGET_TOTAL'], $aTrackTextFieldsChanges['changed_fields'])) {
+            $fBudgetTotalDiff = (float)$this->getCleanValue($CNF['FIELD_BUDGET_TOTAL']) - (float)$aTrackTextFieldsChanges['data'][$CNF['FIELD_BUDGET_TOTAL']];
+            if($fBudgetTotalDiff > 0) {
+                $this->_oModule->_oDb->insertCommodity($iContentId, BX_ADS_COMMODITY_TYPE_PROMOTION, $fBudgetTotalDiff);
+
+                $this->_oModule->_oDb->updateEntriesBy([$CNF['FIELD_STATUS_ADMIN'] => BX_ADS_STATUS_ADMIN_UNPAID], [$CNF['FIELD_ID'] => $iContentId]);
+            }
+        }
+
+        $this->processFiles($CNF['FIELD_COVER'], $iContentId, false);
+
+        return $iResult;
+    }
+
+    public function delete ($iContentId, $aContentInfo = [])
+    {
+        $this->_oModule->_oDb->deleteOffer(['content_id' => $iContentId]);
+        $this->_oModule->_oDb->deleteCommodity(['entry_id' => $iContentId]);
+        $this->_oModule->_oDb->deleteLicense(['entry_id' => $iContentId]);
+
+        return parent::delete($iContentId, $aContentInfo);
+    }
+
+    protected function genCustomInputSource (&$aInput)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sUrl = BX_DOL_URL_ROOT . 'modules/?r=' . $this->_oModule->_oConfig->getUri() . '/get_source';
+        if(isset($this->aInputs[$CNF['FIELD_SOURCE_TYPE']]) && !empty($this->aInputs[$CNF['FIELD_SOURCE_TYPE']]['value']))
+            $sUrl = bx_append_url_params($sUrl, ['source_type' => $this->aInputs[$CNF['FIELD_SOURCE_TYPE']]['value']]);
+
+        $aInput = array_merge($aInput, [
+            'ajax_get_suggestions' => $sUrl, 
+            'placeholder' => _t('_bx_ads_form_entry_input_source_paceholder'),
+            'custom' => ['only_once' => true]
+        ]);
+        
+         if (bx_is_api()){
+             $aInput['ajax_get_suggestions'] = 'bx_ads/get_source_data&params[]=';
+             $aInput['type'] = 'suggestion';
+             $aInput['custom']['callback'] = 'bx_ads/load_entry_from_source&params[]=shopify_admin&params[]=';
+             return $aInput;
+        }
+
+        return $this->genCustomInputUsernamesSuggestions($aInput);
+    }
+    
+    protected function genCustomInputSourceValue($aInput)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aValue = is_array($aInput['value']) ? $aInput['value'] : [$aInput['value']];
+
+        $iAuthorId = $this->_getAuthorId();
+        $sSourceType = $this->_getSourceType();
+
+        $oSource = $this->_oModule->getObjectSource($sSourceType, $iAuthorId);
+        if(!$oSource)
+            return [];
+
+        $aResults = [];
+        foreach($aValue as $sValue) {
+            $aEntry = $oSource->getEntry($sValue);
+            if(empty($aEntry) || !is_array($aEntry))
+                continue;
+
+            $aResults[] = [
+                'item_unit' => '',
+                'item_name' => $aEntry[$CNF['FIELD_TITLE']],
+                'name' => $aInput['name'] . (isset($aInput['custom']['only_once']) && $aInput['custom']['only_once'] == 1 ? '' : '[]'),
+                'value' => $sValue
+            ];
+        }
+
+        return $aResults;
+    }
+
+    public function genInputPrice(&$aInput)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!isset($aInput['value_currency'])) {
+            $iAuthorId = $this->_getAuthorId();
+            $aInput['value_currency'] = BxDolPayments::getInstance()->getCurrencyCode($iAuthorId);
+        }
+
+        return parent::genInputPrice($aInput);
+    }
+
+    protected function genCustomInputCategoryView(&$aInput)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!empty($this->_iCategory)) {
+            $aCategory = $this->_oModule->_oDb->getCategories(array('type' => 'id', 'id' => $this->_iCategory));
+            if(!empty($aCategory) && is_array($aCategory))
+                $aInput['value'] = bx_process_output(_t($aCategory['title']));
+        }
+
+        return $this->genInputStandard($aInput);
+    }
+
+    protected function genCustomViewRowValueCategoryView(&$aInput)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(empty($aInput['value']))
+            return '';
+
+        $aCategory = $this->_oModule->_oDb->getCategories(['type' => 'id', 'id' => $aInput['value']]);
+        if(empty($aCategory) || !is_array($aCategory))
+            return '';
+
+        if($this->_bIsApi)
+            $aInput['value'] = bx_process_output(_t($aCategory['title']));
+
+        $sLink = bx_absolute_url(BxDolPermalinks::getInstance()->permalink($CNF['URL_CATEGORIES'], [$CNF['GET_PARAM_CATEGORY'] => $aCategory['id']]));
+        return $this->_oModule->_oTemplate->parseLink($sLink, bx_process_output(_t($aCategory['title'])));
+    }
+
+    protected function genCustomViewRowValueQuantity(&$aInput)
+    {
+        return (int)$aInput['value'] > 0 ? (int)$aInput['value'] : 0;
+    }
+
+    protected function _getCoverGhostTmplVars($aContentInfo = array())
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	return array (
+            'name' => $this->aInputs[$CNF['FIELD_COVER']]['name'],
+            'content_id' => $this->aInputs[$CNF['FIELD_COVER']]['content_id'],
+            'editor_id' => isset($CNF['FIELD_TEXT_ID']) ? $CNF['FIELD_TEXT_ID'] : '',
+            'thumb_id' => isset($CNF['FIELD_THUMB']) && isset($aContentInfo[$CNF['FIELD_THUMB']]) ? $aContentInfo[$CNF['FIELD_THUMB']] : 0,
+            'name_thumb' => isset($CNF['FIELD_THUMB']) ? $CNF['FIELD_THUMB'] : ''
+        );
+    }
+
+    protected function _getPhotoGhostTmplVars($aContentInfo = [])
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	return [
+            'name' => $this->aInputs[$CNF['FIELD_PHOTO']]['name'],
+            'content_id' => (int)$this->aInputs[$CNF['FIELD_PHOTO']]['content_id'],
+            'editor_id' => isset($CNF['FIELD_TEXT_ID']) ? $CNF['FIELD_TEXT_ID'] : '',
+            'bx_if:set_thumb' => [
+                'condition' => false,
+                'content' => []
+            ],
+    	];
+    }
+
+    protected function _getSingleField()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!isset($this->aInputs[$CNF['FIELD_QUANTITY']])) 
+            return;
+
+        $iSingle = 1;
+        $iQuantity = (int)$this->getCleanValue($CNF['FIELD_QUANTITY']);
+        if($iQuantity > 1)
+            $iSingle = 0;
+
+        return $iSingle;
+    }
+
+    protected function _initCategoryFields($iCategory = 0)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_iCategory = (int)$iCategory;
+        if(empty($this->_iCategory) && bx_get('category') !== false)
+            $this->_iCategory = (int)bx_get('category');
+
+        if(isset($CNF['FIELD_CATEGORY']) && isset($this->aInputs[$CNF['FIELD_CATEGORY']]) && $this->_iCategory != 0)
+            $this->aInputs[$CNF['FIELD_CATEGORY']]['value'] = $this->_iCategory;
+
+        if(isset($CNF['FIELD_CATEGORY_VIEW']) && isset($this->aInputs[$CNF['FIELD_CATEGORY_VIEW']]) && $this->_iCategory != 0)
+            $this->aInputs[$CNF['FIELD_CATEGORY_VIEW']]['value'] = $this->_iCategory;
+
+        if(isset($CNF['FIELD_CATEGORY_SELECT']) && isset($this->aInputs[$CNF['FIELD_CATEGORY_SELECT']]))
+            $this->aInputs[$CNF['FIELD_CATEGORY_SELECT']]['values'] = $this->_oModule->serviceGetCategoryOptions(0, true);
+    }
+
+    protected function _isChangeUserForAdmins($sDisplay)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!$this->_bAllowChangeUserForAdmins)
+            return false;
+
+        if(strpos($sDisplay, '_add') !== false)
+            return $sDisplay != $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'];
+
+        if(strpos($sDisplay, '_edit') !== false)
+            return true;
+
+        return false;
+    }
+    
+    protected function _getAuthorId()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!empty($this->_aContentInfo) && is_array($this->_aContentInfo))
+            return (int)$this->_aContentInfo[$CNF['FIELD_AUTHOR']];
+
+        if(!empty($this->_iContentId)) {
+            $this->_aContentInfo = $this->_oModule->_oDb->getContentInfoById($this->_iContentId);
+            if(!empty($this->_aContentInfo) && is_array($this->_aContentInfo))
+                return (int)$this->_aContentInfo[$CNF['FIELD_AUTHOR']];
+        }
+
+        return bx_get_logged_profile_id();
+    }
+
+    protected function _getSourceType()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!empty($this->_aContentInfo) && is_array($this->_aContentInfo))
+            return $this->_aContentInfo[$CNF['FIELD_SOURCE_TYPE']];
+        
+        if(!empty($this->_iContentId)) {
+            $this->_aContentInfo = $this->_oModule->_oDb->getContentInfoById($this->_iContentId);
+            if(!empty($this->_aContentInfo) && is_array($this->_aContentInfo))
+                return $this->_aContentInfo[$CNF['FIELD_SOURCE_TYPE']];
+        }
+        
+        $iAuthorId = $this->_getAuthorId();
+        return $this->_oModule->serviceGetSource($iAuthorId);
+    }
+
+    protected function _genInputButtonAttrs(&$aInput)
+    {
+        return $this->_replaceMarkers(parent::_genInputButtonAttrs($aInput));
+    }
+}
+
+/** @} */

+ 2789 - 0
modules/boonex/ads/updates/15.0.4_15.0.5/source/classes/BxAdsModule.php

@@ -0,0 +1,2789 @@
+<?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,
+                'visibility_auto' => false,
+                '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()
+    {
+        $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        
+        $aResult = $this->serviceInterested($iContentId);
+
+        echoJson($aResult);
+    }
+
+    public function serviceInterested($iContentId, $iViewer = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return [];
+
+        $iViewer = $iViewer ?: bx_get_logged_profile_id();
+        $oViewer = BxDolProfile::getInstance($iViewer);
+        if(!$oViewer)
+            return [];
+
+        $iContentAuthor = (int)$aContentInfo[$CNF['FIELD_AUTHOR']];
+        if($iContentAuthor == $iViewer)
+            return ['msg' => _t('_bx_ads_txt_err_your_own')];
+
+        if($this->_oDb->isInterested($iContentId, $iViewer))
+            return ['msg' => _t('_bx_ads_txt_err_duplicate')];
+
+        $iInterestId = $this->_oDb->insertInterested(array('entry_id' => $iContentId, 'profile_id' => $iViewer));
+        if(!$iInterestId)
+            return ['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 ['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(), [
+            'Interested' => '',
+            '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($this->_bIsApi && ($iCategory = bx_get('category_select')) !== false)
+            $iCategory = (int)$iCategory;
+
+        if(($sDisplay = $this->getCategoryDisplay('add', $iCategory)) !== false) {
+            if(empty($sParams) || !is_array($sParams))
+                $sParams = [];
+
+            $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();
+        return array_merge($this->_aModule, [
+            'url_browse_order_common' => bx_absolute_url(bx_append_url_params($oPermalink->permalink($CNF['URL_LICENSES_COMMON']), ['filter' => '{order}'], false)),
+            'url_browse_order_administration' => bx_absolute_url(bx_append_url_params($oPermalink->permalink($CNF['URL_LICENSES_ADMINISTRATION']), ['filter' => '{order}'], false))
+        ]);
+    }
+
+    /**
+     * @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,
+
+            'recipient_id_ref' => &$iClientId,
+            'email_template_ref' => &$sEmailTemplate,
+            'email_params_ref' => &$aEmailParams,
+            'cancel_ref' => &$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 || (!$this->isEntryAuthor($mixedContent, $iProfileId) && !$this->_isModerator() && !$this->_isAdministrator()))
+            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/15.0.4_15.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' => '15.0.5',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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'
+    ),
+
+);
+
+/** @} */

Разница между файлами не показана из-за своего большого размера
+ 3 - 0
modules/boonex/ads/updates/15.0.4_15.0.5/source/template/images/icons/std-icon.svg


+ 51 - 0
modules/boonex/albums/updates/15.0.3_15.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' => 'Albums',
+    'version_from' => '15.0.3',
+    'version_to' => '15.0.4',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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_15.0.3_15.0.4/',
+    'home_uri' => 'albums_update_1503_1504',
+
+    'module_dir' => 'boonex/albums/',
+    'module_uri' => 'albums',
+
+    'db_prefix' => 'bx_albums_',
+    'class_prefix' => 'BxAlbums',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Albums',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/albums/updates/15.0.3_15.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 BxAlbumsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 21 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/install/langs/en.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string_add name="_bx_albums_form_media_display_delete"><![CDATA[Delete Media]]></string_add>
+	<string_add name="_bx_albums_form_media_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string_add>
+	<string_add name="_bx_albums_form_media_input_delete_confirm"><![CDATA[I am sure I want to delete this media]]></string_add>
+	<string_add name="_bx_albums_form_media_input_delete_confirm_info"><![CDATA[This action can not be undone]]></string_add>
+	<string_add name="_bx_albums_form_media_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete the media]]></string_add>
+
+	<string_add name="_bx_albums_page_title_sys_edit_media"><![CDATA[Media - Edit]]></string_add>
+	<string_add name="_bx_albums_page_title_edit_media"><![CDATA[Edit Media]]></string_add>
+	<string_add name="_bx_albums_page_block_title_edit_media"><![CDATA[Edit Media]]></string_add>
+	<string_add name="_bx_albums_page_title_sys_move_media"><![CDATA[Media - Move]]></string_add>
+	<string_add name="_bx_albums_page_title_move_media"><![CDATA[Move Media]]></string_add>
+	<string_add name="_bx_albums_page_block_title_move_media"><![CDATA[Move Media]]></string_add>
+	<string_add name="_bx_albums_page_title_sys_delete_media"><![CDATA[Media - Delete]]></string_add>
+	<string_add name="_bx_albums_page_title_delete_media"><![CDATA[Delete Media]]></string_add>
+	<string_add name="_bx_albums_page_block_title_delete_media"><![CDATA[Delete Media]]></string_add>
+
+	<string_upd name="_bx_albums_page_title_sys_view_media"><![CDATA[Media - View]]></string_upd>
+	<string_upd name="_bx_albums_page_block_title_entry_view_media"><![CDATA[View Media]]></string_upd>
+</resources>

+ 24 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/install/sql/enable.sql

@@ -0,0 +1,24 @@
+-- PAGES
+DELETE FROM `sys_objects_page` WHERE `object`='bx_albums_edit_media';
+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_albums_edit_media', '_bx_albums_page_title_sys_edit_media', '_bx_albums_page_title_edit_media', 'bx_albums', 5, 2147483647, 1, 'edit-media', '', '', '', '', 0, 1, 0, 'BxAlbumsPageMedia', 'modules/boonex/albums/classes/BxAlbumsPageMedia.php');
+
+DELETE FROM `sys_pages_blocks` WHERE `object`='bx_albums_edit_media';
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_edit_media', 1, 'bx_albums', '_bx_albums_page_block_title_edit_media', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:10:"media_edit";}', 0, 0, 0);
+
+DELETE FROM `sys_objects_page` WHERE `object`='bx_albums_move_media';
+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_albums_move_media', '_bx_albums_page_title_sys_move_media', '_bx_albums_page_title_move_media', 'bx_albums', 5, 2147483647, 1, 'move-media', '', '', '', '', 0, 1, 0, 'BxAlbumsPageMedia', 'modules/boonex/albums/classes/BxAlbumsPageMedia.php');
+
+DELETE FROM `sys_pages_blocks` WHERE `object`='bx_albums_move_media';
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_move_media', 1, 'bx_albums', '_bx_albums_page_block_title_move_media', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:10:"media_move";}', 0, 0, 0);
+
+DELETE FROM `sys_objects_page` WHERE `object`='bx_albums_delete_media';
+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_albums_delete_media', '_bx_albums_page_title_sys_delete_media', '_bx_albums_page_title_delete_media', 'bx_albums', 5, 2147483647, 1, 'delete-media', '', '', '', '', 0, 1, 0, 'BxAlbumsPageMedia', 'modules/boonex/albums/classes/BxAlbumsPageMedia.php');
+
+DELETE FROM `sys_pages_blocks` WHERE `object`='bx_albums_delete_media';
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_delete_media', 1, 'bx_albums', '_bx_albums_page_block_title_delete_media', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:12:"media_delete";}', 0, 0, 0);

+ 15 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/install/sql/install.sql

@@ -0,0 +1,15 @@
+-- FORMS
+DELETE FROM `sys_form_displays` WHERE `object`='bx_albums_media' AND `display_name`='bx_albums_media_delete';
+INSERT INTO `sys_form_displays`(`object`, `display_name`, `module`, `view_mode`, `title`) VALUES 
+('bx_albums_media', 'bx_albums_media_delete', 'bx_albums', 0, '_bx_albums_form_media_display_delete');
+
+DELETE FROM `sys_form_inputs` WHERE `object`='bx_albums_media' AND `name`='delete_confirm';
+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_albums_media', 'bx_albums', 'delete_confirm', 1, '', 0, 'checkbox', '_bx_albums_form_media_input_sys_delete_confirm', '_bx_albums_form_media_input_delete_confirm', '_bx_albums_form_media_input_delete_confirm_info', 1, 0, 0, '', '', '', 'Avail', '', '_bx_albums_form_media_input_delete_confirm_error', '', '', 1, 0);
+
+DELETE FROM `sys_form_display_inputs` WHERE `display_name`='bx_albums_media_delete';
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_albums_media_delete', 'delete_confirm', 2147483647, 1, 1),
+('bx_albums_media_delete', 'controls', 2147483647, 1, 2),
+('bx_albums_media_delete', 'do_submit', 2147483647, 1, 3),
+('bx_albums_media_delete', 'do_cancel', 2147483647, 1, 4);

+ 258 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsConfig.php

@@ -0,0 +1,258 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    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',
+
+            // 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',
+            'URI_EDIT_MEDIA' => 'edit-media',
+            'URI_MOVE_MEDIA' => 'move-media',
+            'URI_DELETE_MEDIA' => 'delete-media',
+
+            '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_FORM_MEDIA_DISPLAY_DELETE' => 'bx_albums_media_delete',
+            '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',
+
+            'BADGES_AVALIABLE' => true,
+
+            // 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'],
+        	
+        );
+    }
+
+    public function getAlbumUrl($iId)
+    {
+        $sUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->CNF['URI_VIEW_ENTRY'] . '&id=' . $iId);
+
+        return bx_absolute_url($sUrl);
+    }
+
+    public function getMediaUrl($iId, $sContext = '')
+    {
+        $sUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->CNF['URI_VIEW_MEDIA'] . '&id=' . $iId);
+        if($sContext)
+            $sUrl = bx_append_url_params($sUrl, [
+                'context' => $sContext
+            ]);
+
+        return bx_absolute_url($sUrl);
+    }
+}
+
+/** @} */

+ 76 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsFormMedia.php

@@ -0,0 +1,76 @@
+<?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
+ *
+ * @{
+ */
+
+/**
+ * Create/Edit entry form
+ */
+class BxAlbumsFormMedia extends BxTemplFormView
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_iMediaId;
+    protected $_aMediaInfo;
+
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_albums';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct($aInfo, $oTemplate);
+    }
+
+    public function initChecker($aValues = array (), $aSpecificValues = array())
+    {
+        if(!empty($this->_aMediaInfo) && is_array($this->_aMediaInfo))
+            $aValues = $this->_aMediaInfo;
+
+        parent::initChecker($aValues, $aSpecificValues);
+    }
+
+    public function initForm($aAction, $iMediaId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_iMediaId = $iMediaId;
+        $this->_aMediaInfo = $this->_oModule->_oDb->getMediaInfoById($iMediaId);
+
+        if(bx_is_dynamic_request())        
+            $this->aFormAttrs['action'] = $this->_oModule->_oConfig->getBaseUri() . $aAction . '_media/' . $iMediaId;
+        else
+            $this->aFormAttrs['action'] = BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_' . strtoupper($aAction) . '_MEDIA'] . '&id=' . $iMediaId);
+        $this->aFormAttrs['action'] = bx_absolute_url($this->aFormAttrs['action']);
+
+        if(isset($this->aInputs['content_id'])) {
+            $aAlbums = $this->_oModule->_oDb->getEntriesBy(array('type' => 'author', 'author' => $this->_aMediaInfo['author']));
+            foreach($aAlbums as $aAlbum)
+                $this->aInputs['content_id']['values'][] = ['key' => $aAlbum[$CNF['FIELD_ID']], 'value' => $aAlbum[$CNF['FIELD_TITLE']]];
+        }
+    }
+    
+    public function update($val, $aValsToAdd = array(), &$aTrackTextFieldsChanges = null)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sField = 'content_id';
+        if(isset($this->aInputs[$sField])) {
+            $iContentId = $this->getCleanValue($sField);
+
+            $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+            if(!$oStorage->updateGhostsContentId($this->_aMediaInfo['file_id'], $this->_aMediaInfo['author'], $iContentId))
+                return false;
+        }
+
+        return parent::update($val, $aValsToAdd, $aTrackTextFieldsChanges);
+    }
+}
+
+/** @} */

+ 220 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsMenuViewActionsMedia.php

@@ -0,0 +1,220 @@
+<?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
+ *
+ * @{
+ */
+
+require_once('BxAlbumsMenuViewActions.php');
+
+/**
+ * View media actions menu
+ */
+class BxAlbumsMenuViewActionsMedia extends BxAlbumsMenuViewActions
+{
+    protected $_iMediaId;
+    protected $_aMediaInfo;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+    }
+
+    public function setContentId($iMediaId)
+    {
+        $this->_iMediaId = (int)$iMediaId;
+        $this->_aMediaInfo = $this->_oModule->_oDb->getMediaInfoById($this->_iMediaId);
+        if($this->_aMediaInfo) {
+            $this->_sJsObjectMoreAuto .= $this->_iMediaId;
+            $this->_aHtmlIds['main'] .= '-' . $this->_iMediaId;
+
+            $this->addMarkers(array('media_id' => (int)$this->_iMediaId));
+
+            $this->_iContentId = (int)$this->_aMediaInfo['content_id'];
+            $this->_aContentInfo = $this->_oModule->_oDb->getContentInfoById($this->_iContentId);
+            if($this->_aContentInfo)
+                $this->addMarkers(array('content_id' => (int)$this->_iContentId));
+        }
+    }
+
+    protected function _isContentPublic($iMediaId, $aPublicGroups = [])
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(empty($CNF['FIELD_ALLOW_VIEW_TO'])) 
+            return true;
+
+        $aContentInfo = array();
+        if($iMediaId != $this->_iMediaId) {
+            $aMediaInfo = $this->_oModule->_oDb->getMediaInfoById($iMediaId);
+            if(!empty($aMediaInfo['content_id']))
+                $aContentInfo = (int)$aMediaInfo['content_id'] == $this->_iContentId ? $this->_aContentInfo : $this->_oModule->_oDb->getContentInfoById((int)$aMediaInfo['content_id']);
+        }
+        else 
+            $aContentInfo = $this->_aContentInfo;
+
+        if(!isset($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            return true;
+
+        if(empty($aPublicGroups))
+            $aPublicGroups = [BX_DOL_PG_ALL, BX_DOL_PG_MEMBERS];
+
+        return in_array($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']], $aPublicGroups);
+    }
+
+    protected function _getMenuItemAddImagesToAlbum($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return $this->_getMenuItemByNameActions($aItem, array(
+            'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA']
+        ));
+    }
+
+    protected function _getMenuItemEditAlbum($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return $this->_getMenuItemByNameActions($aItem, array(
+            'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA']
+        ));
+    }
+
+    protected function _getMenuItemEditImage($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aResult = $this->_getMenuItemByNameActions($aItem, [
+            'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA']
+        ]);
+
+        if($this->_bIsApi) {
+            $aResult = array_merge($aResult, [
+                'link' => BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_EDIT_MEDIA'] . '&id=' . $this->_iMediaId),
+            ]);
+
+            unset($aResult['display_type'], $aResult['data']);
+        }
+
+        return $aResult;
+    }
+
+    protected function _getMenuItemDeleteImage($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aResult = $this->_getMenuItemByNameActions($aItem, [
+            'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA']
+        ]);
+
+        if($this->_bIsApi) {
+            $aResult = array_merge($aResult, [
+                'link' => BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_DELETE_MEDIA'] . '&id=' . $this->_iMediaId),
+            ]);
+
+            unset($aResult['display_type'], $aResult['data']);
+        }
+
+        return $aResult;
+    }
+
+    protected function _getMenuItemMoveImage($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aResult = $this->_getMenuItemByNameActions($aItem, [
+            'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA']
+        ]);
+
+        if($this->_bIsApi) {
+            $aResult = array_merge($aResult, [
+                'link' => BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_MOVE_MEDIA'] . '&id=' . $this->_iMediaId),
+            ]);
+
+            unset($aResult['display_type'], $aResult['data']);
+        }
+
+        return $aResult;
+    }
+
+    protected function _getMenuItemView($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemView($aItem, array(
+            'object' => $CNF['OBJECT_VIEWS_MEDIA'],
+            'id' => $this->_iMediaId
+        ));
+    }
+
+    protected function _getMenuItemComment($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemComment($aItem, array(
+            'object' => $CNF['OBJECT_COMMENTS_MEDIA'],
+            'id' => $this->_iMediaId
+        ));
+    }
+
+    protected function _getMenuItemVote($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemVote($aItem, array(
+            'object' => $CNF['OBJECT_VOTES_MEDIA'],
+            'id' => $this->_iMediaId
+        ));
+    }
+
+    protected function _getMenuItemScore($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemScore($aItem, array(
+            'object' => $CNF['OBJECT_SCORES_MEDIA'],
+            'id' => $this->_iMediaId
+        ));
+    }
+
+    protected function _getMenuItemFavorite($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemFavorite($aItem, array(
+            'object' => $CNF['OBJECT_FAVORITES_MEDIA'],
+            'id' => $this->_iMediaId
+        ));
+    }
+
+    protected function _getMenuItemFeature($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemFeature($aItem, array(
+            'object' => $CNF['OBJECT_FEATURED_MEDIA'],
+            'id' => $this->_iMediaId
+        ));
+    }
+
+    protected function _getMenuItemRepost($aItem, $aParams = array())
+    {
+        return '';
+    }
+
+    protected function _getMenuItemReport($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemReport($aItem, array(
+            'object' => $CNF['OBJECT_REPORTS_MEDIA'],
+            'id' => $this->_iMediaId
+        ));
+    }
+}
+
+/** @} */

+ 1258 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsModule.php

@@ -0,0 +1,1258 @@
+<?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 = ['popular', 'public', 'author'];
+
+    public function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    public function serviceGetSafeServices()
+    {
+        return array_merge(parent::serviceGetSafeServices(), [
+            'EntityAddFiles' => '',
+            'MediaEdit' => '',
+            'MediaMove' => '',
+            'MediaDelete' => '',
+            '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 attachments
+     */
+    public function serviceEntityAttachments ($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+        
+        return $this->_serviceBrowse('album', [
+            'unit_view' => 'gallery', 
+            'album_id' => $aContentInfo[$CNF['FIELD_ID']], 
+            'author' => $aContentInfo[$CNF['FIELD_AUTHOR']]
+        ], BX_DB_PADDING_DEF, true, true, 'SearchResultMedia');
+    }
+    
+    /**
+     * 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->_serviceDeleteFileAssociations($iFileId, $aMediaInfo) === false)
+            return false;
+
+        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;
+    }
+
+    public function serviceMediaEdit($iMediaId = 0, $sDisplay = false)
+    {
+        return $this->_serviceMediaAction('edit', $iMediaId, $sDisplay);
+    }
+
+    public function serviceEditMediaForm($iMediaId, $sDisplay = false)
+    {
+        $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 $this->_bIsApi ? [bx_api_get_msg($sMsg)] : ['msg' => $sMsg];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_MEDIA'], $sDisplay ?: $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 = ['reload' => 1];
+            }
+            else
+                $aRes = ($sMsg = _t('_bx_albums_txt_err_cannot_perform_action')) && $this->_bIsApi ? [bx_api_get_msg($sMsg)] : ['msg' => $sMsg];
+
+            return $aRes;
+        }
+
+        if($this->_bIsApi) 
+            return [bx_api_get_block('form', $oForm->getCodeAPI(), [
+                'ext' => [
+                    'name' => $this->_aModule['name'], 
+                    'request' => ['url' => '/api.php?r=' . $this->_aModule['name'] . '/media_edit/&params[]=' . $iMediaId, 'immutable' => true]
+                ]
+            ])];
+
+        return $oForm;
+    }
+
+    public function serviceMediaMove($iMediaId = 0, $sDisplay = false)
+    {
+        return $this->_serviceMediaAction('move', $iMediaId, $sDisplay);
+    }
+
+    public function serviceMoveMediaForm($iMediaId, $sDisplay = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iMediaId = (int)$iMediaId;
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_MEDIA'], $sDisplay ?: $CNF['OBJECT_FORM_MEDIA_DISPLAY_MOVE']);
+        $oForm->initForm('move', $iMediaId);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            if($oForm->update($iMediaId) !== false)
+                $aRes = ['reload' => 1];
+            else
+                $aRes = ($sMsg = _t('_bx_albums_txt_err_cannot_perform_action')) && $this->_bIsApi ? [bx_api_get_msg($sMsg)] : ['msg' => $sMsg];
+
+            return $aRes;
+        }
+
+        if($this->_bIsApi) 
+            return [bx_api_get_block('form', $oForm->getCodeAPI(), [
+                'ext' => [
+                    'name' => $this->_aModule['name'], 
+                    'request' => ['url' => '/api.php?r=' . $this->_aModule['name'] . '/media_move/&params[]=' . $iMediaId, 'immutable' => true]
+                ]
+            ])];
+
+        return $oForm;
+    }
+
+    public function serviceMediaDelete($iMediaId = 0, $sDisplay = false)
+    {
+        return $this->_serviceMediaAction('delete', $iMediaId, $sDisplay);
+    }
+
+    public function serviceDeleteMediaForm($iMediaId, $sDisplay = false)
+    {
+        $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 $this->_bIsApi ? [bx_api_get_msg($sMsg)] : ['msg' => $sMsg];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_MEDIA'], $sDisplay ?: $CNF['OBJECT_FORM_MEDIA_DISPLAY_DELETE']);
+        $oForm->initForm('delete', $iMediaId);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $aRes = ($sMsg = _t('_bx_albums_txt_err_cannot_perform_action')) && $this->_bIsApi ? [bx_api_get_msg($sMsg)] : ['msg' => $sMsg];
+
+            if($oForm->delete($iMediaId) !== false) {
+                if(($sUploader = reset($CNF['OBJECT_UPLOADERS'])) && ($oUploader = BxDolUploader::getObjectInstance($sUploader, $CNF['OBJECT_STORAGE'], '')) !== false) {
+                    $oUploader->deleteGhost($aMediaInfo['file_id'], bx_get_logged_profile_id());
+
+                    $aRes = ['redirect' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aMediaInfo['content_id']))];
+                }
+            }               
+
+            return $aRes;
+        }
+
+        if($this->_bIsApi) 
+            return [bx_api_get_block('form', $oForm->getCodeAPI(), [
+                'ext' => [
+                    'name' => $this->_aModule['name'], 
+                    'request' => ['url' => '/api.php?r=' . $this->_aModule['name'] . '/media_delete/&params[]=' . $iMediaId, 'immutable' => true]
+                ]
+            ])];
+
+        return $oForm;
+    }
+
+    /**
+     * @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 = [])
+    {
+        $mixedContent = $this->_getMedia($mixedContent);
+        if($mixedContent === false)
+            return false;
+
+        list($iMediaId, $aMediaInfo) = $mixedContent; 
+
+        $CNF = &$this->_oConfig->CNF;
+
+        return parent::serviceEntityAllActions ([$iMediaId, $aMediaInfo], array_merge([
+            '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 = [])
+    {
+        if(!$iMediaId)
+            $iMediaId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if(!$iMediaId)
+            return false;
+
+        if(!$mixedContext && ($_mixedContext = bx_get('context')) !== false) {
+            $mixedContext = bx_process_input($_mixedContext);
+            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(($sK = 'autoplay') && !isset($aParams[$sK]) && ($iAutoplay = bx_get($sK)) !== false)
+            $aParams[$sK] = (int)$iAutoplay;
+
+        $mixedResult = $this->_oTemplate->entryMediaView ($iMediaId, $aParams);
+
+        return $this->_bIsApi ? [bx_api_get_block('entity_text', $mixedResult)] : $mixedResult;
+    }
+
+    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 [];
+
+        $oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS_MEDIA'], $iMediaId);
+        if(!$oComment || !$oComment->isEnabled())
+            return [];
+
+        $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');
+
+        $iCommentId = (int)$aEvent['subobject_id'];
+
+        return [
+            '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' => $oComment->getItemUrl($iCommentId, '{bx_url_root}'),
+            'subentry_url_api' => $oComment->getItemUrlApi($iCommentId, '{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)
+    {
+        $mixedResult = $this->serviceEditMediaForm($iMediaId);
+        if($mixedResult instanceof BxDolForm)
+            $mixedResult = ['popup' => [
+                'html' => BxTemplStudioFunctions::getInstance()->transBox('bx-albums-edit-media-popup', $this->_oTemplate->parseHtmlByName('media-edit.html', [
+                    'form_id' => $mixedResult->aFormAttrs['id'],
+                    'form' => $mixedResult->getCode(true)
+                ])), 
+                'options' => ['closeOnOuterClick' => false]
+            ]];
+
+        echoJson($mixedResult);
+    }
+
+    public function actionMoveMedia($iMediaId)
+    {
+        $mixedResult = $this->serviceMoveMediaForm($iMediaId);
+        if($mixedResult instanceof BxDolForm)
+            $mixedResult = ['popup' => [
+                'html' => BxTemplStudioFunctions::getInstance()->transBox('bx-albums-move-media-popup', $this->_oTemplate->parseHtmlByName('media-edit.html', [
+                    'form_id' => $mixedResult->aFormAttrs['id'],
+                    'form' => $mixedResult->getCode(true)
+                ])), 
+                'options' => ['closeOnOuterClick' => false]
+            ]];
+
+        echoJson($mixedResult);
+    }
+
+    public function actionDeleteMedia($iMediaId)
+    {
+        $mixedResult = $this->serviceDeleteMediaForm($iMediaId);
+        if($mixedResult instanceof BxDolForm)
+            $mixedResult = ['popup' => [
+                'html' => BxTemplStudioFunctions::getInstance()->transBox('bx-albums-delete-media-popup', $this->_oTemplate->parseHtmlByName('media-edit.html', [
+                    'form_id' => $mixedResult->aFormAttrs['id'],
+                    'form' => $mixedResult->getCode(true)
+                ])), 
+                'options' => ['closeOnOuterClick' => false]
+            ]];
+
+        echoJson($mixedResult);
+    }
+
+    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;
+    }
+
+    public function decodeDataAPI($aData, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = parent::decodeDataAPI($aData, $aParams);
+
+        $aImage = [];
+        if(($sStorage = $CNF['OBJECT_STORAGE']) && ($oStorage = BxDolStorage::getObjectInstance($sStorage)) !== false) {
+            $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_TRANSCODER_BROWSE']);
+
+            $aItems = $this->_oDb->getMediaListByContentId($aData[$CNF['FIELD_ID']], 1);
+            foreach($aItems as $aItem) {
+                list($iWidth, $iHeight) = $aData['data'] ? explode('x', $aData['data']) : [0, 0];
+
+                $aImage = [
+                    'storage' => $sStorage,
+                    'src' => $oTranscoder ? $oTranscoder->getFileUrl($aItem['file_id']) : $oStorage->getFileUrlById($aItem['file_id']),
+                    'width' => $iWidth,
+                    'height' => $iHeight
+                ];
+            }
+        }
+
+        return array_merge($aResult, [
+            'image' => $aImage
+        ]);
+    }
+
+    public function getDataMediaAPI($aData, $aParams = [])
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sModule = $this->getName();
+
+        $iId = (int)$aData[$CNF['FIELD_ID']];
+        $sUrl = bx_api_get_relative_url($this->_oConfig->getMediaUrl($iId));
+
+        $aImage = $aVideo = [];
+        if(($sStorage = $CNF['OBJECT_STORAGE']) && ($oStorage = BxDolStorage::getObjectInstance($sStorage)) !== false) {
+            $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']);
+            $aTranscodersVideo = [
+                'poster' => BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['poster_preview']),
+                'mp4' => BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4']),
+                'mp4_hd' => BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4_hd']),
+            ];
+
+            $aFileInfo = $oStorage->getFile($aData['file_id']);
+
+            $bImage = strncmp('image/', $aFileInfo['mime_type'], 6) === 0 && $oTranscoder->isMimeTypeSupported($aFileInfo['mime_type']);
+            if($bImage) {
+                list($iWidth, $iHeight) = $aData['data'] ? explode('x', $aData['data']) : [0, 0];
+
+                $aImage = [
+                    'storage' => $sStorage,
+                    'src' => $oTranscoder ? $oTranscoder->getFileUrl($aFileInfo['id']) : $oStorage->getFileUrlById($aFileInfo['id']),
+                    'width' => $iWidth,
+                    'height' => $iHeight
+                ];
+            }
+
+            $bVideo = !$bImage && strncmp('video/', $aFileInfo['mime_type'], 6) === 0 && $aTranscodersVideo && $aTranscodersVideo['poster']->isMimeTypeSupported($aFileInfo['mime_type']);
+            if($bVideo) {
+                //TODO: process Media's Video here.
+            }
+        }
+
+        $aResult = [
+            'id' => $iId,
+            'module' => $sModule,
+            'module_title' => _t($CNF['T']['txt_sample_single']),
+            'added' => $aData[$CNF['FIELD_ADDED']],
+            'author' => $aData[$CNF['FIELD_AUTHOR']],
+            'author_data' => !empty($aData[$CNF['FIELD_AUTHOR']]) ? BxDolProfile::getData($aData[$CNF['FIELD_AUTHOR']]) : '',
+            'title' => $aData[$CNF['FIELD_TITLE']],
+            'url' => $sUrl,
+            'image' => $aImage,
+            'video' => $aVideo
+        ];
+
+        if(isset($aParams['extended']) && $aParams['extended'] === true)
+            $aResult['text'] = $aData[$CNF['FIELD_TEXT']];
+
+        if(!empty($CNF['OBJECT_MENU_SNIPPET_META']) && ($oMetaMenu = BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SNIPPET_META'], $this->_oTemplate)) !== false) {
+            $oMetaMenu->setContentId($iId);
+
+            $aResult['meta'] = $oMetaMenu->getCodeAPI();
+        }
+
+        return $aResult;
+    }
+
+    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;
+    }
+
+    protected function _serviceMediaAction ($sAction, $iMediaId = 0, $sDisplay = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iMediaId = $this->_getMedia($iMediaId, false);
+        if($iMediaId === false)
+            return false;
+
+        $sUrlViewMedia = 'page.php?i=' . $CNF['URI_VIEW_MEDIA'] . '&id=' . $iMediaId;
+        
+        $mixedResult = $this->{'service' . ucfirst($sAction) . 'MediaForm'}($iMediaId, $sDisplay);
+        if($this->_bIsApi) {
+            if(($sRedirect = $mixedResult['redirect'] ?? false))
+                $mixedResult = [bx_api_get_block('redirect', ['uri' => bx_api_get_relative_url($sRedirect)])];
+            else if(($mixedResult['reload'] ?? false))
+                $mixedResult = [bx_api_get_block('redirect', ['uri' => bx_api_get_relative_url(BxDolPermalinks::getInstance()->permalink($sUrlViewMedia))])];
+
+            return $mixedResult;
+        }
+
+        if($mixedResult instanceof BxDolForm)
+            return $mixedResult->getCode();
+
+        if(is_array($mixedResult)) {
+            if(($sMsg = $mixedResult['msg'] ?? false))
+                return MsgBox($sMsg);
+
+            if(($sRedirect = $mixedResult['redirect'] ?? false)) {
+                header('Location: ' . $sRedirect);
+                exit;
+            }
+
+            if(($mixedResult['reload'] ?? false)) {
+                header('Location: ' . bx_absolute_url(BxDolPermalinks::getInstance()->permalink($sUrlViewMedia)));
+                exit;
+            }
+        }
+
+        return false;
+    }
+
+    protected function _getMedia($mixedContent = 0, $sFuncGetMedia = true)
+    {
+        if($mixedContent && is_array($mixedContent))
+            return $mixedContent;
+
+        if($sFuncGetMedia === true)
+            $sFuncGetMedia = 'getMediaInfoById';
+
+        return $this->_getContent($mixedContent, $sFuncGetMedia);
+    }
+}
+
+/** @} */

+ 463 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/classes/BxAlbumsTemplate.php

@@ -0,0 +1,463 @@
+<?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
+ *
+ * @{
+ */
+
+/*
+ * Module representation.
+ */
+class BxAlbumsTemplate extends BxBaseModTextTemplate
+{
+    /**
+     * Constructor
+     */
+    function __construct(&$oConfig, &$oDb)
+    {
+        $this->MODULE = 'bx_albums';
+        parent::__construct($oConfig, $oDb);
+
+        $this->addCss(array(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flickity/|flickity.css'));
+        $this->addJs(array('flickity/flickity.pkgd.min.js', 'main.js'));
+    }
+
+    function unit ($aData, $isCheckPrivateContent = true, $sTemplateName = 'unit.html', $aParams = array())
+    {
+        if ('unit_media.html' == $sTemplateName || 'unit_media_live_search.html' == $sTemplateName || 'unit_showcase.html' == $sTemplateName || 'unit_media_gallery.html' == $sTemplateName)
+            return $this->unitMedia($aData, $isCheckPrivateContent, $sTemplateName, $aParams);
+
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        if ($s = $this->checkPrivacy ($aData, $isCheckPrivateContent, $oModule))
+            return $s;
+
+        // get entry url        
+        $sUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aData[$CNF['FIELD_ID']]));
+
+        $oProfile = BxDolProfile::getInstance($aData[$CNF['FIELD_AUTHOR']]);
+        if (!$oProfile) 
+            $oProfile = BxDolProfileUndefined::getInstance();
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_TRANSCODER_BROWSE']);
+        $oTranscoderPoster = $CNF['OBJECT_VIDEOS_TRANSCODERS'] ? BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['poster']) : false;
+
+        $aBrowseUnits = array ();
+        $aMediaList = $oModule->_oDb->getMediaListByContentId($aData[$CNF['FIELD_ID']], getParam('bx_albums_album_browsing_unit'));
+        foreach ($aMediaList as $k => $a) {
+            $aFileInfo = $oStorage->getFile($a['file_id']);
+
+            $bVideo = $oTranscoderPoster && strncmp('video/', $aFileInfo['mime_type'], 6) === 0 && $oTranscoderPoster->isMimeTypeSupported($aFileInfo['mime_type']);            
+            $iVideoDuration = $bVideo ? $oModule->getMediaDuration($aFileInfo) : 0;
+
+            $aBrowseUnits[] = array (
+                'img_url' => $oTranscoder->getFileUrl($a['file_id']),
+                'url' => $this->getViewMediaUrl($CNF, $a['id']),
+                'title_attr' => bx_html_attribute($a['title']),
+                'bx_if:show_play' => array(
+                    'condition' => $bVideo,
+                    'content' => array()
+                ),
+                'bx_if:show_duration' => array(
+                    'condition' => $iVideoDuration > 0,
+                    'content' => array(
+                        'duration' => _t_format_duration($iVideoDuration)
+                    )
+                )
+
+            );
+        }
+
+        // get summary
+        $sLinkMore = ' <a title="' . bx_html_attribute(_t('_sys_read_more', $aData[$CNF['FIELD_TITLE']])) . '" href="' . $sUrl . '"><i class="sys-icon ellipsis-h"></i></a>';
+        $sSummary = strmaxtextlen($aData[$CNF['FIELD_TEXT']], (int)getParam($CNF['PARAM_CHARS_SUMMARY']), $sLinkMore);
+        $sSummaryPlain = BxTemplFunctions::getInstance()->getStringWithLimitedLength(strip_tags($sSummary), (int)getParam($CNF['PARAM_CHARS_SUMMARY_PLAIN']));
+
+        $sMeta = '';
+        if(!empty($CNF['OBJECT_MENU_SNIPPET_META'])) {
+            $oMenuMeta = BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SNIPPET_META'], $this);
+            if($oMenuMeta) {
+                $oMenuMeta->setContentId($aData[$CNF['FIELD_ID']]);
+                $aTmplVarsMeta = array(
+                    'meta' => $oMenuMeta->getCode()
+                );
+            }
+        }
+
+        $bBadgesSingle = isset($aParams['badges_single']) ? $aParams['badges_single'] : false;
+        $bBadgesCompact = isset($aParams['badges_compact']) ? $aParams['badges_compact'] : false;
+
+        // generate html
+        $aVars = array (
+            'id' => $aData[$CNF['FIELD_ID']],
+            'content_url' => $sUrl,
+            'title' => bx_process_output($aData[$CNF['FIELD_TITLE']]),
+            'badges' => $oModule->serviceGetBadges($aData[$CNF['FIELD_ID']], $bBadgesSingle, $bBadgesCompact),
+            'summary' => $sSummary,
+            'author' => $oProfile->getDisplayName(),
+            'author_url' => $oProfile->getUrl(),
+            'entry_posting_date' => bx_time_js($aData[$CNF['FIELD_ADDED']], BX_FORMAT_DATE),
+            'module_name' => _t($CNF['T']['txt_sample_single']),
+            'ts' => $aData[$CNF['FIELD_ADDED']],
+        	'bx_if:meta' => array(
+                'condition' => !empty($aTmplVarsMeta),
+                'content' => $aTmplVarsMeta
+            ),
+            'bx_repeat:browse' => $aBrowseUnits,
+            'bx_if:thumb' => array (
+                'condition' => $aBrowseUnits,
+                'content' => array (
+                    'title' => bx_process_output($aData[$CNF['FIELD_TITLE']]),
+                    'summary_attr' => '',
+                    'content_url' => $sUrl,
+                    'thumb_url' => $aBrowseUnits ? $aBrowseUnits[0]['img_url'] : '',
+                    'gallery_url' => '',
+                    'strecher' => '',
+                ),
+            ),
+            'bx_if:no_thumb' => array (
+                'condition' => !$aBrowseUnits,
+                'content' => array (
+                    'content_url' => $sUrl,
+                    'summary_plain' => '',
+                    'strecher' => '',
+                ),
+            ),
+
+        );
+
+        return $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    function unitMedia ($aData, $isCheckPrivateContent = true, $sTemplateName = 'unit.html', $aParams = array())
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        if (!($aMediaInfo = $oModule->_oDb->getMediaInfoById($aData['id'])))
+            return '';
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        if (!($aFile = $oStorage->getFile($aMediaInfo['file_id'])))
+            return '';
+
+        $aVars = $this->mediaVars($aMediaInfo, $CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW'], $CNF['OBJECT_VIDEOS_TRANSCODERS']['poster_preview'], $aParams);
+
+        $aVarsTmp = $aVars['bx_if:image']['condition'] ? $aVars['bx_if:image']['content'] : $aVars['bx_if:video']['content'];
+
+        $aVars = array_merge($aVars, array (
+            'class' => $this->_getUnitClass($aData, $sTemplateName),
+            'module_name' => _t($CNF['T']['txt_media_single']),
+            'content_url' => $aVars['url'],
+            'ts' => $aFile['added'],
+            'actions' => $oModule->serviceMediaAllActions(array($aData['id'], $aMediaInfo), array(
+                'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA_UNIT']
+            )),
+            'bx_if:thumb' => array (
+                'condition' => isset($aData['file_id']) && $aData['file_id'],
+                'content' => array (
+                    'title' => bx_process_output($aData['title']),
+                    'summary_attr' => '',
+                    'content_url' => $aVarsTmp['url'],
+                    'thumb_url' => $aVarsTmp['url_img'],
+                    'gallery_url' => '',
+                    'strecher' => '',
+                ),
+            ),
+            'bx_if:no_thumb' => array (
+                'condition' => false,
+                'content' => array (),
+            ),
+        ));
+
+        return $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    public function entryText ($aData, $sTemplateName = 'entry-text.html')
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aVars = $this->getTmplVarsText($aData);
+        if(empty($aVars[$CNF['FIELD_TITLE']]) && empty($aVars[$CNF['FIELD_TEXT']]))
+            return false;
+
+        return $this->_bIsApi ? $aVars : $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    function entryMediaView ($iMediaId, $aParams = [])
+    {
+        $oModule = parent::getModule();
+        $CNF = &$this->_oConfig->CNF;
+
+        $aMediaInfo = [];
+        if(!($aMediaInfo = $oModule->_oDb->getMediaInfoById($iMediaId)))
+            return $this->_bIsApi ? [] : '';
+
+        $aAlbumInfo = [];
+        if(!($aAlbumInfo = $oModule->_oDb->getContentInfoById($aMediaInfo['content_id'])))
+            return $this->_bIsApi ? [] : '';        
+
+        $iProfileId = $aAlbumInfo[$CNF['FIELD_AUTHOR']];
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile) 
+            $oProfile = BxDolProfileUndefined::getInstance();
+
+        $mixedContext = isset($aParams['context']) ? $aParams['context'] : false;
+        $sAlbumUrl = $this->_oConfig->getAlbumUrl($aAlbumInfo[$CNF['FIELD_ID']]);
+        $sAlbumTitle = bx_process_output($aAlbumInfo[$CNF['FIELD_TITLE']]);
+
+        $aVars = $this->mediaVars($aMediaInfo, $CNF['OBJECT_IMAGES_TRANSCODER_BIG'], $CNF['OBJECT_VIDEOS_TRANSCODERS']['poster'], $aParams);
+        if($this->_bIsApi)
+            return array_merge($aVars, [
+                'text' => $sAlbumTitle,
+                'entry_text' => $sAlbumTitle,
+            ]);
+
+        $aVars = array_merge($aVars, [
+            'album' => _t('_bx_albums_txt_media_album_link', $sAlbumUrl, $sAlbumTitle, $oProfile->getUrl(), $oProfile->getDisplayName()),
+            'js_code' => $this->getJsCode('main')
+        ]);
+
+        $aNextPrev = [
+            'next' => $this->getNextPrevMedia($aMediaInfo, true, $mixedContext),
+            'prev' => $this->getNextPrevMedia($aMediaInfo, false, $mixedContext),
+        ];
+
+        foreach ($aNextPrev as $k => $a) {
+            $aVars['bx_if:' . $k] = [
+                'condition' => $a,
+                'content' => $a,
+            ];
+            $aVars['bx_if:' . $k . '-na'] = [
+                'condition' => !$a,
+                'content' => [],
+            ];
+        }
+
+        $aMediaItems = $oModule->_oDb->getMediaListByContentId($aMediaInfo['content_id'], false);
+        $aMediaItemsFormatted = [];
+        foreach ($aMediaItems as $i => $r) {
+            $a = $this->mediaVars($r, $CNF['OBJECT_IMAGES_TRANSCODER_BIG'], $CNF['OBJECT_VIDEOS_TRANSCODERS']['poster'], $aParams);
+            $aMedia = $a['bx_if:image']['condition'] ? $a['bx_if:image']['content'] : $a['bx_if:video']['content'];
+            $aMediaItemsFormatted[$i] = [
+                'index' => $i,
+                'alt' => $aMedia['title'],
+                'url' => $aMedia['url'],
+                'media-id' => $r['id'],
+
+            ];
+            if ($a['bx_if:video']['condition']) {
+                $aMediaItemsFormatted[$i]['html'] = '<div class="pswp__video">' . $a['bx_if:video']['content']['video'] . '</div>';
+            } else {
+                $aMediaItemsFormatted[$i]['src'] = $aMedia['url_img'];
+                $aMediaItemsFormatted[$i]['width'] = $a['bx_if:image']['content']['w'];
+                $aMediaItemsFormatted[$i]['height'] = $a['bx_if:image']['content']['h'];
+            }
+        }
+        $aVars['media_items'] = json_encode($aMediaItemsFormatted);
+
+        return $this->parseHtmlByName('media-view.html', $aVars);
+    }
+
+    protected function mediaVars ($aMediaInfo, $sImageTranscoder = false, $sVideoPosterTranscoder = false, $aParams = array())
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        $sStorage = $CNF['OBJECT_STORAGE'];
+        $oStorage = BxDolStorage::getObjectInstance($sStorage);
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($sImageTranscoder);
+        $aTranscodersVideo = [
+            'poster' => BxDolTranscoderImage::getObjectInstance($sVideoPosterTranscoder),
+            'mp4' => BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4']),
+            'mp4_hd' => BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4_hd']),
+        ];
+
+        $aFileInfo = $oStorage->getFile ($aMediaInfo['file_id']);
+        if (!$aFileInfo)
+            return '';
+
+        $isImage = 0 === strncmp('image/', $aFileInfo['mime_type'], 6) && $oTranscoder->isMimeTypeSupported($aFileInfo['mime_type']); // preview for images, transcoder object for preview must be defined
+        $isVideo = $aTranscodersVideo && (0 === strncmp('video/', $aFileInfo['mime_type'], 6)) && $aTranscodersVideo['poster']->isMimeTypeSupported($aFileInfo['mime_type']); // preview for videos, transcoder object for video must be defined
+
+        $sMediaTitle = $this->getMediaTitle($aMediaInfo);
+        $sMediaTitleAttr = bx_html_attribute($sMediaTitle);
+        $mixedContext = isset($aParams['context']) ? $aParams['context'] : '';
+        $sUrl = $this->getViewMediaUrl($CNF, $aMediaInfo['id'], $mixedContext);
+
+        $aTmplVarsImage = [];
+        if($isImage) {
+            $sSrc = $oTranscoder ? $oTranscoder->getFileUrl($aFileInfo['id']) : $oStorage->getFileUrlById($aFileInfo['id']);
+
+            list($iWidth, $iHeight) = $aMediaInfo['data'] ? explode('x', $aMediaInfo['data']) : [0, 0];
+
+            $aTmplVarsImage = $this->_bIsApi ? [
+                'storage' => $sStorage,
+                'src' => $sSrc,
+                'width' => $iWidth,
+                'height' => $iHeight
+            ] : [
+                'title_attr' => $sMediaTitleAttr,
+                'title' => $sMediaTitle,
+                'url' => $sUrl,
+                'url_img' => $sSrc,
+                'media_id' => $aMediaInfo['id'],
+                'w' => $iWidth,
+                'h' => $iHeight,
+                'context' => $mixedContext,
+            ];
+        }
+
+        $aTmplVarsVideo = [];
+        if($isVideo) {
+            $mixedAttrs = false;
+            if(!empty($aParams['autoplay']))
+                $mixedAttrs = ['autoplay' => 'autoplay'];
+
+            $iVideoDuration = $oModule->getMediaDuration($aFileInfo);
+            $sVideoDuration = _t_format_duration($iVideoDuration);
+
+            $sVideoUrl = $oStorage->getFileUrlById($aMediaInfo['file_id']);
+            $aVideoFile = $oStorage->getFile($aMediaInfo['file_id']);
+
+            $sVideoUrlHd = '';
+            if(!empty($aVideoFile['dimensions']) && $aTranscodersVideo['mp4_hd']->isProcessHD($aVideoFile['dimensions']))
+                $sVideoUrlHd = $aTranscodersVideo['mp4_hd']->getFileUrl($aMediaInfo['file_id']);
+
+            $aTmplVarsVideo = $this->_bIsApi ? [
+                //TODO: process Media's Video here.
+            ] : [
+                'title_attr' => $sMediaTitleAttr,
+                'title' => $sMediaTitle,
+                'url' => bx_append_url_params($sUrl, ['autoplay' => 1]),
+                'url_img' => $isVideo ? $aTranscodersVideo['poster']->getFileUrl($aMediaInfo['file_id']) : '',
+                'video' => $isVideo && $aTranscodersVideo ? BxTemplFunctions::getInstance()->videoPlayer(
+                    $aTranscodersVideo['poster']->getFileUrl($aMediaInfo['file_id']), 
+                    $aTranscodersVideo['mp4']->getFileUrl($aMediaInfo['file_id']), 
+                    $sVideoUrlHd, $mixedAttrs, 'max-height:' . $CNF['OBJECT_VIDEO_TRANSCODER_HEIGHT']
+                ) : '',
+                'duration' => $sVideoDuration,
+                'bx_if:show_duration' => [
+                    'condition' => $iVideoDuration > 0,
+                    'content' => [
+                        'duration' => $sVideoDuration
+                    ]
+                ]
+            ];
+        }
+
+        $aVars = $this->_bIsApi ? [
+            'id' => $aMediaInfo['id'],
+            'title' => $sMediaTitle,
+            'text' => '',
+            'image' => $aTmplVarsImage,
+            'entry_title' => $sMediaTitle,
+            'entry_text' => '',
+        ] : [
+            'bx_if:image' => [
+                'condition' => $isImage,
+                'content' => $aTmplVarsImage,
+            ],
+            'bx_if:video' => [
+                'condition' => $isVideo,
+                'content' => $aTmplVarsVideo,
+            ],
+            'title_attr' => $sMediaTitleAttr,
+            'title' => $sMediaTitle,
+            'url' => $sUrl,
+        ];
+
+        return $aVars;
+    }
+
+    public function getViewMediaUrl($CNF, $iMediaId, $sContext = '')
+    {
+        return $this->_oConfig->getMediaUrl($iMediaId, $sContext);
+    }
+
+    public function getNextPrevMedia($aMediaInfo, $isNext, $sContext, $aParamsSearchResult = array())
+    {
+        if (!$sContext) {
+            $sContext = 'album';
+            $aParamsSearchResult = array('album_id' => $aMediaInfo['content_id']);
+        }
+
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        bx_import('SearchResultMedia', $oModule->_aModule);
+        $sClass = $oModule->_aModule['class_prefix'] . 'SearchResultMedia';
+        $o = new $sClass($sContext, $aParamsSearchResult);
+        if ($o->isError)
+            return false;
+
+        $o->setUnitParams(array('context' => $sContext));
+
+        $a = $o->getNextPrevItem($aMediaInfo, $isNext);
+    
+        if ($a) {
+
+            $aVars = $this->mediaVars($a, $CNF['OBJECT_IMAGES_TRANSCODER_BIG'], $CNF['OBJECT_VIDEOS_TRANSCODERS']['poster'], array('context' => $sContext));
+            $aMedia = $aVars['bx_if:image']['condition'] ? $aVars['bx_if:image']['content'] : $aVars['bx_if:video']['content'];
+
+            $a['url'] = $aMedia['url'];
+            $a['url_img'] = $aMedia['url_img'];
+            $a['title_attr'] = $aMedia['title_attr'];
+            $a['title_js_string'] = bx_js_string($aMedia['title']);
+            $a['w'] = $aVars['bx_if:image']['condition'] ? $aVars['bx_if:image']['content']['w'] : 0;
+            $a['h'] = $aVars['bx_if:image']['condition'] ? $aVars['bx_if:image']['content']['h'] : 0;
+            $a['html'] = $aVars['bx_if:video']['condition'] ? '<div class="pswp__video">' . $aVars['bx_if:video']['content']['video'] . '</div>': 0;
+        }
+
+        return $a;
+    }
+
+    function mediaAuthor ($aMediaInfo, $iProfileId = false, $sFuncAuthorDesc = '', $sTemplateName = '') 
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $oModule = $this->getModule();
+
+        $aAlbumInfo = $oModule->_oDb->getContentInfoById($aMediaInfo['content_id']);
+        if(!$aAlbumInfo)
+            return $this->_bIsApi ? [] : '';
+
+        if($this->_bIsApi)
+            return [bx_api_get_block('entity_author', [
+                'author_data' => BxDolProfile::getData($iProfileId),
+                'entry_date' => $aMediaInfo[$CNF['FIELD_ADDED']] ?? '',
+                'entry_context' => '',
+                'menu_manage' => $oModule->getEntryAllActions([$aMediaInfo['id'], $aMediaInfo], [
+                    'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA_ALL']
+                ])
+            ])];
+
+        return $this->entryAuthor($aMediaInfo, $aAlbumInfo[$CNF['FIELD_AUTHOR']]);
+    }
+
+    function getMediaTitle ($aMediaInfo)
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        if(!empty($aMediaInfo['title'])) {
+            $sText = bx_process_output($aMediaInfo['title']);
+            if(!empty($CNF['OBJECT_METATAGS_MEDIA'])) {
+                $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS_MEDIA']);
+                if($oMetatags && $oMetatags->keywordsIsEnabled())
+                    $sText = $oMetatags->keywordsParse($aMediaInfo['id'], $sText);
+            }
+        }
+        else 
+            $sText = _t('_bx_albums_txt_media_title_empty');
+
+        return $sText;
+    }
+}
+
+/** @} */

+ 143 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/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' => '15.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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'
+    ),
+
+);
+
+/** @} */

+ 325 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/install/langs/en.xml

@@ -0,0 +1,325 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+
+    <string name="_bx_albums"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_cmts"><![CDATA[Albums' Comments]]></string>
+    <string name="_bx_albums_reactions"><![CDATA[Albums' Reactions]]></string>
+    <string name="_bx_albums_media"><![CDATA[Images]]></string>
+    <string name="_bx_albums_media_cmts"><![CDATA[Images' Comments]]></string>
+
+    <string name="_bx_albums_option_enable_auto_approve"><![CDATA[Auto approve an album after creation]]></string>
+    <string name="_bx_albums_option_summary_chars"><![CDATA[Number of characters in auto-cropped album summary]]></string>
+    <string name="_bx_albums_option_plain_summary_chars"><![CDATA[Number of characters in plain album description when album hasn't thumb]]></string>
+    <string name="_bx_albums_option_per_page_browse"><![CDATA[Number of items per page]]></string>
+    <string name="_bx_albums_option_per_page_profile"><![CDATA[Number of items on profile page]]></string>
+    <string name="_bx_albums_option_album_browsing_unit"><![CDATA[Number of items in album card]]></string>
+    <string name="_bx_albums_option_rss_num"><![CDATA[Number of items in RSS feed]]></string>
+    <string name="_bx_albums_option_searchable_fields"><![CDATA[Searchable fields for keyword search]]></string>
+    <string name="_bx_albums_option_per_page_for_favorites_lists"><![CDATA[Number of favorite lists per page]]></string>
+
+    <string name="_bx_albums_form_entry"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_form_entry_display_add"><![CDATA[Add Album]]></string>
+    <string name="_bx_albums_form_entry_display_edit"><![CDATA[Edit Album]]></string>
+    <string name="_bx_albums_form_entry_display_add_images"><![CDATA[Add Images To Album]]></string>
+    <string name="_bx_albums_form_entry_display_delete"><![CDATA[Delete Album]]></string>
+    <string name="_bx_albums_form_entry_display_view"><![CDATA[View Album]]></string>
+    <string name="_bx_albums_form_entry_input_author"><![CDATA[Author]]></string>
+    <string name="_bx_albums_form_entry_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_albums_form_entry_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_albums_form_entry_input_sys_title"><![CDATA[Caption]]></string>
+    <string name="_bx_albums_form_entry_input_title"><![CDATA[Caption]]></string>
+    <string name="_bx_albums_form_entry_input_title_err"><![CDATA[Caption is essential min 3 max 160.]]></string>
+    <string name="_bx_albums_form_entry_input_sys_text"><![CDATA[Album Description]]></string>
+    <string name="_bx_albums_form_entry_input_text"><![CDATA[Album Description]]></string>
+    <string name="_bx_albums_form_entry_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string>
+    <string name="_bx_albums_form_entry_input_delete_confirm"><![CDATA[I am sure I want to delete this album]]></string>
+    <string name="_bx_albums_form_entry_input_delete_confirm_info"><![CDATA[This action can not be undone]]></string>
+    <string name="_bx_albums_form_entry_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete the album]]></string>
+    <string name="_bx_albums_form_entry_input_sys_pictures"><![CDATA[Pictures]]></string>
+    <string name="_bx_albums_form_entry_input_pictures"><![CDATA[Pictures]]></string>
+    <string name="_bx_albums_form_entry_input_sys_allow_view_to"><![CDATA[Visibility]]></string>
+    <string name="_bx_albums_form_entry_input_allow_view_to"><![CDATA[Visibility]]></string>
+    <string name="_bx_albums_form_entry_input_allow_view_favorite_list"><![CDATA[Visibility]]></string>
+    <string name="_bx_albums_form_entry_file_title"><![CDATA[Caption]]></string>
+    <string name="_bx_albums_form_entry_input_sys_date_added"><![CDATA[Created]]></string>
+    <string name="_bx_albums_form_entry_input_date_added"><![CDATA[Created]]></string>
+    <string name="_bx_albums_form_entry_input_sys_date_changed"><![CDATA[Updated]]></string>
+    <string name="_bx_albums_form_entry_input_date_changed"><![CDATA[Updated]]></string>
+
+    <string name="_bx_albums_form_media"><![CDATA[Medias]]></string>
+    <string name="_bx_albums_form_media_display_edit"><![CDATA[Edit Media]]></string>
+    <string name="_bx_albums_form_media_display_move"><![CDATA[Move Media]]></string>
+    <string name="_bx_albums_form_media_display_delete"><![CDATA[Delete Media]]></string>
+    <string name="_bx_albums_form_media_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string>
+    <string name="_bx_albums_form_media_input_delete_confirm"><![CDATA[I am sure I want to delete this media]]></string>
+    <string name="_bx_albums_form_media_input_delete_confirm_info"><![CDATA[This action can not be undone]]></string>
+    <string name="_bx_albums_form_media_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete the media]]></string>
+    <string name="_bx_albums_form_media_input_sys_title"><![CDATA[Caption]]></string>
+    <string name="_bx_albums_form_media_input_title"><![CDATA[Caption]]></string>
+    <string name="_bx_albums_form_media_input_sys_content_id"><![CDATA[Album]]></string>
+    <string name="_bx_albums_form_media_input_content_id"><![CDATA[Album]]></string>
+    <string name="_bx_albums_form_media_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_albums_form_media_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_albums_form_media_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_albums_form_media_input_do_cancel"><![CDATA[Cancel]]></string>
+
+    <string name="_bx_albums_acl_action_create_entry"><![CDATA[Create Album]]></string>
+    <string name="_bx_albums_acl_action_delete_entry"><![CDATA[Delete Album]]></string>
+    <string name="_bx_albums_acl_action_view_entry"><![CDATA[View Album]]></string>
+    <string name="_bx_albums_acl_action_set_thumb"><![CDATA[Set Album Cover]]></string>
+    <string name="_bx_albums_acl_action_edit_any_entry"><![CDATA[Edit Any Album]]></string>
+    <string name="_bx_albums_acl_action_delete_any_entry"><![CDATA[Delete Any Album]]></string>
+
+    <string name="_bx_albums_page_title_sys_create_entry"><![CDATA[New Album]]></string>
+    <string name="_bx_albums_page_title_create_entry"><![CDATA[New Album]]></string>
+    <string name="_bx_albums_page_block_title_create_entry"><![CDATA[New Album]]></string>
+    <string name="_bx_albums_page_title_sys_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_albums_page_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_albums_page_title_sys_add_images"><![CDATA[Add/Edit Pictures]]></string>
+    <string name="_bx_albums_page_title_add_images"><![CDATA[Add/Edit Pictures]]></string>
+    <string name="_bx_albums_page_block_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_albums_page_block_title_add_images"><![CDATA[Add/Edit Pictures]]></string>
+    <string name="_bx_albums_page_title_sys_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_albums_page_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_albums_page_block_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_albums_page_title_sys_view_entry"><![CDATA[View Album]]></string>
+    <string name="_bx_albums_page_title_view_entry"><![CDATA[{title}]]></string>
+    <string name="_bx_albums_page_title_sys_view_media"><![CDATA[Media - View]]></string>
+    <string name="_bx_albums_page_title_view_media"><![CDATA[{title}]]></string>
+    <string name="_bx_albums_page_block_title_entry_view_media"><![CDATA[View Media]]></string>
+    <string name="_bx_albums_page_block_title_entry_view_media_exif"><![CDATA[EXIF]]></string>
+    <string name="_bx_albums_page_block_title_entry_attachments"><![CDATA[Attachments]]></string>
+    <string name="_bx_albums_page_block_title_entry_text"><![CDATA[Album Description]]></string>
+    <string name="_bx_albums_page_title_sys_edit_media"><![CDATA[Media - Edit]]></string>
+    <string name="_bx_albums_page_title_edit_media"><![CDATA[Edit Media]]></string>
+    <string name="_bx_albums_page_block_title_edit_media"><![CDATA[Edit Media]]></string>
+    <string name="_bx_albums_page_title_sys_move_media"><![CDATA[Media - Move]]></string>
+    <string name="_bx_albums_page_title_move_media"><![CDATA[Move Media]]></string>
+    <string name="_bx_albums_page_block_title_move_media"><![CDATA[Move Media]]></string>
+    <string name="_bx_albums_page_title_sys_delete_media"><![CDATA[Media - Delete]]></string>
+    <string name="_bx_albums_page_title_delete_media"><![CDATA[Delete Media]]></string>
+    <string name="_bx_albums_page_block_title_delete_media"><![CDATA[Delete Media]]></string>
+    <string name="_bx_albums_page_title_browse"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_page_title_search_results"><![CDATA[Albums matching "{0}"]]></string>
+    <string name="_bx_albums_page_title_browse_by_author"><![CDATA[{display_name}'s Albums]]></string>
+    <string name="_bx_albums_page_title_browse_by_context"><![CDATA[Albums in {display_name}]]></string>
+    <string name="_bx_albums_page_title_browse_featured"><![CDATA[Featured Albums]]></string>
+    <string name="_bx_albums_page_title_browse_featured_media"><![CDATA[Featured Media]]></string>
+    <string name="_bx_albums_page_title_browse_recent"><![CDATA[Latest Albums]]></string>
+    <string name="_bx_albums_page_title_browse_popular"><![CDATA[Popular Albums]]></string>
+    <string name="_bx_albums_page_title_browse_popular_media"><![CDATA[Popular Images]]></string>
+    <string name="_bx_albums_page_title_browse_top"><![CDATA[Top Albums]]></string>
+    <string name="_bx_albums_page_title_browse_top_media"><![CDATA[Top Images]]></string>
+    <string name="_bx_albums_page_title_browse_recent_media"><![CDATA[Recent Images]]></string>
+    <string name="_bx_albums_page_title_browse_media_in_album"><![CDATA[Images]]></string>
+    <string name="_bx_albums_page_title_browse_updated"><![CDATA[Updated Albums]]></string>
+    <string name="_bx_albums_page_title_sys_home"><![CDATA[Albums Home]]></string>
+    <string name="_bx_albums_page_title_home"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_page_block_title_featured_entries_view_extended"><![CDATA[Featured Albums]]></string>
+    <string name="_bx_albums_page_block_title_featured_entries_view_gallery_media"><![CDATA[Featured Media]]></string>
+    <string name="_bx_albums_page_block_title_recent_entries"><![CDATA[Latest Albums]]></string>
+    <string name="_bx_albums_page_block_title_entry_all_actions"><![CDATA[All Actions]]></string>
+    <string name="_bx_albums_page_block_title_entry_social_sharing"><![CDATA[Share]]></string>
+    <string name="_bx_albums_page_block_title_entry_author"><![CDATA[Author]]></string>
+    <string name="_bx_albums_page_block_title_entry_info"><![CDATA[Info]]></string>
+    <string name="_bx_albums_page_block_title_entry_actions"><![CDATA[Actions]]></string>
+    <string name="_bx_albums_page_block_title_entry_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_albums_page_block_title_entry_reports"><![CDATA[Reports]]></string>
+    <string name="_bx_albums_page_block_title_entry_comments_link"><![CDATA[Comments to <a href="{entry_link}">{title}</a>]]></string>
+    <string name="_bx_albums_page_block_title_entry_location"><![CDATA[Location]]></string>
+    <string name="_bx_albums_page_title_sys_entries_popular"><![CDATA[Popular]]></string>
+    <string name="_bx_albums_page_title_entries_popular"><![CDATA[Popular]]></string>
+    <string name="_bx_albums_page_title_sys_entries_popular_media"><![CDATA[Popular Images]]></string>
+    <string name="_bx_albums_page_title_entries_popular_media"><![CDATA[Popular Images]]></string>
+    <string name="_bx_albums_page_block_title_popular_entries"><![CDATA[Popular]]></string>
+    <string name="_bx_albums_page_block_title_popular_media"><![CDATA[Popular Images]]></string>
+    <string name="_bx_albums_page_title_sys_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_albums_page_title_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_albums_page_title_sys_entries_top_media"><![CDATA[Top Images]]></string>
+    <string name="_bx_albums_page_title_entries_top_media"><![CDATA[Top Images]]></string>
+    <string name="_bx_albums_page_block_title_top_entries"><![CDATA[Top]]></string>
+    <string name="_bx_albums_page_block_title_top_media"><![CDATA[Top Images]]></string>
+    <string name="_bx_albums_page_block_title_recent_media"><![CDATA[Recent Images]]></string>
+    <string name="_bx_albums_page_block_title_updated_entries"><![CDATA[Updated Albums]]></string>
+    <string name="_bx_albums_page_block_title_sys_favorites_entries"><![CDATA[Favorite List]]></string>
+    <string name="_bx_albums_page_block_title_favorites_entries"><![CDATA["{title}" List]]></string>
+    <string name="_bx_albums_page_block_title_favorites_entries_info"><![CDATA[Info]]></string>
+    <string name="_bx_albums_page_block_title_favorites_entries_actions"><![CDATA[Favorites List Actions]]></string>
+    <string name="_bx_albums_page_title_sys_view_entry_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_albums_page_title_view_entry_comments"><![CDATA[Comments to {title}]]></string>
+    <string name="_bx_albums_page_title_sys_entries_of_author"><![CDATA[Albums of author]]></string>
+    <string name="_bx_albums_page_title_entries_of_author"><![CDATA[{display_name}'s albums]]></string>
+    <string name="_bx_albums_page_title_sys_entries_in_context"><![CDATA[Albums in context]]></string>
+    <string name="_bx_albums_page_title_entries_in_context"><![CDATA[Albums in {display_name}]]></string>
+    <string name="_bx_albums_page_block_title_favorites_of_author"><![CDATA[Favorite albums by <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_albums_page_block_title_sys_favorites_of_author"><![CDATA[Favorite albums of author]]></string>
+    <string name="_bx_albums_page_block_title_favorites_of_author_media"><![CDATA[Favorite media by <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_albums_page_block_title_sys_favorites_of_author_media"><![CDATA[Favorite media of author]]></string>
+    <string name="_bx_albums_page_block_title_entries_of_author"><![CDATA[Albums by <a href="{profile_link}">{display_name}</a>]]></string>   
+    <string name="_bx_albums_page_block_title_sys_entries_of_author"><![CDATA[Albums of author]]></string>
+    <string name="_bx_albums_page_block_title_entries_in_context"><![CDATA[Albums in <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_albums_page_block_title_sys_entries_in_context"><![CDATA[Albums in context]]></string>
+    <string name="_bx_albums_page_block_title_entries_actions"><![CDATA[Albums actions]]></string>
+    <string name="_bx_albums_page_block_title_browse_cmts"><![CDATA[Albums' Comments]]></string>
+    <string name="_bx_albums_page_block_title_browse_cmts_media"><![CDATA[Images Comments]]></string>
+	<string name="_bx_albums_page_title_sys_manage"><![CDATA[Manage Own Albums]]></string>
+	<string name="_bx_albums_page_title_sys_manage_administration"><![CDATA[Manage All Albums]]></string>
+	<string name="_bx_albums_page_title_manage"><![CDATA[Manage]]></string>
+	<string name="_bx_albums_page_block_title_system_manage"><![CDATA[Manage Own Albums]]></string>
+	<string name="_bx_albums_page_block_title_system_manage_administration"><![CDATA[Manage All Albums]]></string>  
+	<string name="_bx_albums_page_block_title_manage"><![CDATA[Manage]]></string>
+	<string name="_bx_albums_page_block_title_popular_keywords_albums"><![CDATA[Popular Albums Hashtags]]></string>
+	<string name="_bx_albums_page_block_title_popular_keywords_media"><![CDATA[Popular Media Hashtags]]></string>
+	<string name="_bx_albums_page_block_title_popular_keywords_media_camera"><![CDATA[Popular Cameras]]></string>
+	<string name="_bx_albums_page_block_title_sys_my_entries"><![CDATA[User Albums]]></string>
+        <string name="_bx_albums_page_block_title_my_entries"><![CDATA[{display_name} Albums]]></string>
+  	<string name="_bx_albums_page_title_sys_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_albums_page_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_albums_page_title_sys_entries_favorites"><![CDATA[Favorites Media]]></string>
+    <string name="_bx_albums_page_title_entries_favorites"><![CDATA[Favorites Media]]></string>
+    <string name="_bx_albums_page_block_title_search_form"><![CDATA[Search Form]]></string>
+    <string name="_bx_albums_page_block_title_search_results"><![CDATA[Search Results]]></string>
+    <string name="_bx_albums_page_block_title_search_form_media"><![CDATA[Images Search Form]]></string>
+    <string name="_bx_albums_page_block_title_search_results_media"><![CDATA[Images Search Results]]></string>
+    <string name="_bx_albums_page_block_title_search_form_cmts"><![CDATA[Comments Search Form]]></string>
+    <string name="_bx_albums_page_block_title_search_results_cmts"><![CDATA[Comments Search Results]]></string>
+    <string name="_bx_albums_page_block_title_search_form_media_cmts"><![CDATA[Images Comments Search Form]]></string>
+    <string name="_bx_albums_page_block_title_search_results_media_cmts"><![CDATA[Images Comments Search Results]]></string>
+    <string name="_bx_albums_page_block_title_sys_recent_media_view_showcase"><![CDATA[Recent Images (Showcase View)]]></string>
+    <string name="_bx_albums_page_block_title_recent_media_view_showcase"><![CDATA[Recent Images]]></string>
+    <string name="_bx_albums_page_block_title_sys_popular_media_view_showcase"><![CDATA[Top Images (Showcase View)]]></string>
+    <string name="_bx_albums_page_block_title_popular_media_view_showcase"><![CDATA[Top Images]]></string>
+    <string name="_bx_albums_page_block_title_sys_featured_media_view_showcase"><![CDATA[Featured Images (Showcase View)]]></string>
+    <string name="_bx_albums_page_block_title_featured_media_view_showcase"><![CDATA[Featured Images]]></string>
+	<string name="_bx_albums_page_block_title_sys_entry_context"><![CDATA[Posted in]]></string>
+  	<string name="_bx_albums_page_block_title_entry_context"><![CDATA[Posted in]]></string>
+
+    <string name="_bx_albums_menu_set_title_view_entry"><![CDATA[Album Actions]]></string>
+    <string name="_bx_albums_menu_title_view_entry"><![CDATA[Album Actions]]></string>
+    <string name="_bx_albums_menu_set_title_view_media"><![CDATA[Media Actions]]></string>
+    <string name="_bx_albums_menu_title_view_media"><![CDATA[Media Actions]]></string>
+    <string name="_bx_albums_menu_set_title_view_actions_media"><![CDATA[Media All Actions]]></string>
+    <string name="_bx_albums_menu_title_view_actions_media"><![CDATA[Media All Actions]]></string>
+    <string name="_bx_albums_menu_set_title_view_actions_media_unit"><![CDATA[Media Card Actions]]></string>
+    <string name="_bx_albums_menu_title_view_actions_media_unit"><![CDATA[Media Card Actions]]></string>
+    <string name="_bx_albums_menu_title_submenu"><![CDATA[Albums Submenu]]></string>
+    <string name="_bx_albums_menu_set_title_submenu"><![CDATA[Albums Submenu]]></string>
+    <string name="_bx_albums_menu_item_title_system_create_entry"><![CDATA[Album]]></string>
+    <string name="_bx_albums_menu_item_title_create_entry"><![CDATA[Album]]></string>
+    <string name="_bx_albums_menu_item_title_system_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_albums_menu_item_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_albums_menu_item_title_system_edit_album"><![CDATA[Edit Album]]></string>
+    <string name="_bx_albums_menu_item_title_edit_album"><![CDATA[Edit Album]]></string>
+    <string name="_bx_albums_menu_item_title_system_add_images"><![CDATA[Add/Edit Pictures]]></string>
+    <string name="_bx_albums_menu_item_title_add_images"><![CDATA[Add/Edit Pictures]]></string>
+    <string name="_bx_albums_menu_item_title_system_edit_image"><![CDATA[Edit Image]]></string>
+    <string name="_bx_albums_menu_item_title_edit_image"><![CDATA[Edit Image]]></string>
+    <string name="_bx_albums_menu_item_title_system_delete_image"><![CDATA[Delete Image]]></string>
+    <string name="_bx_albums_menu_item_title_delete_image"><![CDATA[Delete Image]]></string>
+    <string name="_bx_albums_menu_item_title_system_move_image"><![CDATA[Move Image To]]></string>
+    <string name="_bx_albums_menu_item_title_move_image"><![CDATA[Move Image To]]></string>
+    <string name="_bx_albums_menu_item_title_system_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_albums_menu_item_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_albums_menu_item_title_system_view_entry"><![CDATA[View Album]]></string>
+    <string name="_bx_albums_menu_item_title_system_entries_home"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_item_title_entries_home"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_item_title_system_entries_recent"><![CDATA[Latest]]></string>
+    <string name="_bx_albums_menu_item_title_entries_recent"><![CDATA[Latest]]></string>
+    <string name="_bx_albums_menu_item_title_system_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_albums_menu_item_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_albums_menu_item_title_system_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_albums_menu_item_title_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_albums_menu_item_title_system_entries_popular"><![CDATA[Popular]]></string>
+    <string name="_bx_albums_menu_item_title_entries_popular"><![CDATA[Popular]]></string>
+    <string name="_bx_albums_menu_item_title_system_entries_popular_media"><![CDATA[Popular Images]]></string>
+    <string name="_bx_albums_menu_item_title_entries_popular_media"><![CDATA[Popular Images]]></string>
+    <string name="_bx_albums_menu_item_title_system_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_albums_menu_item_title_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_albums_menu_item_title_system_entries_top_media"><![CDATA[Top Images]]></string>
+    <string name="_bx_albums_menu_item_title_entries_top_media"><![CDATA[Top Images]]></string>    
+    <string name="_bx_albums_menu_title_view_entry_submenu"><![CDATA[View Album Submenu]]></string>
+    <string name="_bx_albums_menu_set_title_view_entry_submenu"><![CDATA[View Album Submenu]]></string>
+    <string name="_bx_albums_menu_item_title_view_entry_submenu_entry"><![CDATA[Album]]></string>
+    <string name="_bx_albums_menu_item_title_view_entry_submenu_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_albums_menu_item_title_system_view_entry_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_albums_menu_item_title_system_view_entries_author"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_item_title_view_entries_author"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_item_title_system_view_entries_in_context"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_item_title_view_entries_in_context"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_title_entries_my"><![CDATA[My Albums Action Menu]]></string>
+    <string name="_bx_albums_menu_set_title_entries_my"><![CDATA[My Albums Menu]]></string>
+    <string name="_bx_albums_menu_item_title_system_manage_my_albums"><![CDATA[My albums]]></string>
+    <string name="_bx_albums_menu_item_title_manage_my_albums"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_set_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_albums_menu_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_albums_menu_item_title_system_admt_albums"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_item_title_admt_albums"><![CDATA[Albums]]></string>
+    <string name="_bx_albums_menu_item_title_manage_my"><![CDATA[My Albums]]></string>
+    <string name="_bx_albums_menu_item_title_manage_all"><![CDATA[All Albums]]></string>
+    <string name="_bx_albums_menu_item_title_system_sm_items"><![CDATA[Items]]></string>
+    <string name="_bx_albums_menu_item_title_sm_items"><![CDATA[{0} items]]></string>
+
+    <string name="_bx_albums_grid_filter_item_title_adm_select_one_filter1"><![CDATA[All Statuses]]></string>
+    <string name="_bx_albums_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string>
+    <string name="_bx_albums_grid_filter_item_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_albums_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string>
+    <string name="_bx_albums_grid_filter_item_title_adm_pending"><![CDATA[Pending]]></string>
+    <string name="_bx_albums_grid_column_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_albums_grid_column_title_adm_title"><![CDATA[Caption]]></string>
+    <string name="_bx_albums_grid_column_title_adm_added"><![CDATA[Added]]></string>
+    <string name="_bx_albums_grid_column_title_adm_author"><![CDATA[Author]]></string>
+    <string name="_bx_albums_grid_action_title_adm_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_albums_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_albums_grid_action_title_adm_clear_reports"><![CDATA[Сlear reports]]></string>
+    <string name="_bx_albums_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
+    <string name="_bx_albums_grid_action_title_adm_audit_content"><![CDATA[Audit actions]]></string>
+    <string name="_bx_albums_grid_column_title_adm_status_admin"><![CDATA[Moderation]]></string>
+    <string name="_bx_albums_grid_action_err_delete"><![CDATA[Cannot delete the selected album(s).]]></string>
+    <string name="_bx_albums_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+
+    <string name="_bx_albums_chart_growth"><![CDATA[Albums: Growth]]></string>
+    <string name="_bx_albums_chart_growth_speed"><![CDATA[Albums: Growth Speed]]></string>
+    <string name="_bx_albums_chart_growth_media"><![CDATA[Images: Growth]]></string>
+    <string name="_bx_albums_chart_growth_speed_media"><![CDATA[Images: Growth Speed]]></string>
+
+    <string name="_bx_albums_search_extended"><![CDATA[Albums Search]]></string>
+    <string name="_bx_albums_search_extended_media"><![CDATA[Images Search]]></string>
+    <string name="_bx_albums_search_extended_cmts"><![CDATA[Albums Comments Search]]></string>
+    <string name="_bx_albums_search_extended_media_cmts"><![CDATA[Images Comments Search]]></string>
+
+    <string name="_bx_albums_txt_all_entries_by"><![CDATA[{0} albums]]></string>
+    <string name="_bx_albums_txt_all_entries_in"><![CDATA[<a href="{0}">All Albums in {1} ({2})</a>]]></string>
+    <string name="_bx_albums_txt_sample_single"><![CDATA[album]]></string>
+    <string name="_bx_albums_txt_sample_single_with_article"><![CDATA[an album]]></string>
+    <string name="_bx_albums_txt_sample_comment_single"><![CDATA[comment to album]]></string>
+    <string name="_bx_albums_txt_sample_vote_single"><![CDATA[vote to album]]></string>
+    <string name="_bx_albums_txt_sample_reaction_single"><![CDATA['{0}' reaction to album]]></string>
+    <string name="_bx_albums_txt_sample_score_up_single"><![CDATA[score up vote to album]]></string>
+    <string name="_bx_albums_txt_sample_score_down_single"><![CDATA[score down vote to album]]></string>
+    <string name="_bx_albums_txt_sample_action_changed"><![CDATA[changed {0}]]></string>
+    <string name="_bx_albums_txt_media_single"><![CDATA[image]]></string>
+    <string name="_bx_albums_txt_media_comment_single"><![CDATA[image's comment]]></string>
+    <string name="_bx_albums_txt_media_vote_single"><![CDATA[image's vote]]></string>
+    <string name="_bx_albums_txt_media_score_up_single"><![CDATA[image's score up vote]]></string>
+    <string name="_bx_albums_txt_media_score_down_single"><![CDATA[image's score down vote]]></string>
+    <string name="_bx_albums_txt_media_title_alt"><![CDATA[{0}'s media]]></string>
+    <string name="_bx_albums_txt_media_title_empty"><![CDATA[No Caption]]></string>
+    <string name="_bx_albums_txt_media_album_link"><![CDATA[<a href="{0}">{1}</a> album by <a href="{2}">{3}</a>]]></string>
+    <string name="_bx_albums_txt_media_album_camera"><![CDATA[Camera]]></string>
+    <string name="_bx_albums_txt_media_album_focal_length"><![CDATA[Focal length]]></string>
+    <string name="_bx_albums_txt_media_album_focal_length_value"><![CDATA[{0}mm]]></string>
+    <string name="_bx_albums_txt_media_album_aperture"><![CDATA[Aperture]]></string>
+    <string name="_bx_albums_txt_media_album_shutter_speed"><![CDATA[Exposure time]]></string>
+    <string name="_bx_albums_txt_media_album_shutter_speed_value"><![CDATA[{0}s]]></string>
+    <string name="_bx_albums_txt_media_album_iso"><![CDATA[ISO]]></string>
+    <string name="_bx_albums_txt_processing_awaiting"><![CDATA[Scheduled for publishing as soon as video conversion is complete.]]></string>
+    <string name="_bx_albums_txt_processing_failed"><![CDATA[Album isn't published because video conversion failed, edit video to fix the issue.]]></string>
+
+    <string name="_bx_albums_txt_msg_status_hidden"><![CDATA[This album was hidden.]]></string>
+    <string name="_bx_albums_txt_msg_status_pending"><![CDATA[This album is pending approval.]]></string>
+    <string name="_bx_albums_txt_err_cannot_perform_action"><![CDATA[Cannot perform the action. Please report.]]></string>
+
+    <string name="_bx_albums_alert_action_medias_added"><![CDATA[add images]]></string>
+    <string name="_bx_albums_alert_action_medias_added_follow_member"><![CDATA[New media]]></string>
+    <string name="_bx_albums_alert_action_medias_added_follow_context"><![CDATA[New media]]></string>
+</resources>

+ 109 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql

@@ -0,0 +1,109 @@
+-- PAGES: 
+
+-- PAGES: config_api
+UPDATE `sys_pages_blocks` SET `config_api`='{"content_type":"browse_simple"}' WHERE `object`='bx_albums_view_entry' AND `title`='_bx_albums_page_block_title_entry_attachments';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_create_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_create_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_add_images' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_add_images';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_edit_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_edit_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_delete_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_delete_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_text';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_attachments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_comments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_entry' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_view_media';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_comments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_view_media_exif';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_featured_entries_view_gallery_media';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_view_entry_comments' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_entry_comments' AND `title`='_bx_albums_page_block_title_entry_comments_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_popular' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_popular_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_popular_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_popular_media';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_top' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_top_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_top_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_top_media';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_author' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_author' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_sys_favorites_of_author' AND `title`='_bx_albums_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_author' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_sys_favorites_of_author_media' AND `title`='_bx_albums_page_block_title_favorites_of_author_media';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_author' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_sys_entries_of_author' AND `title`='_bx_albums_page_block_title_entries_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_author' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_sys_entries_in_context' AND `title`='_bx_albums_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_favorites' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_sys_favorites_entries' AND `title`='_bx_albums_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_favorites' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_favorites' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_context' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_sys_entries_in_context' AND `title`='_bx_albums_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_home' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_updated_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_search' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_search' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_manage' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_system_manage' AND `title`='_bx_albums_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_administration' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_system_manage_administration' AND `title`='_bx_albums_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_sys_my_entries' AND `title`='_bx_albums_page_block_title_my_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_albums' AND `title_system`='_bx_albums_page_block_title_sys_my_entries' AND `title`='_bx_albums_page_block_title_my_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_edit_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_edit_media';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_move_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_move_media';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_albums_delete_media' AND `module`='bx_albums' AND `title_system`='' AND `title`='_bx_albums_page_block_title_delete_media';
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_site' AND `module`='bx_albums' AND `name`='albums-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_content_links' AND `module`='bx_albums' AND `name`='create-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_albums' AND `name`='profile-stats-my-albums';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view' AND `module`='bx_albums' AND `name`='add-images-to-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view' AND `module`='bx_albums' AND `name`='edit-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view' AND `module`='bx_albums' AND `name`='delete-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view' AND `module`='bx_albums' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='add-images-to-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='edit-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='delete-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='view';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='reaction';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='score';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='favorite';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='feature';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='repost';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='notes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='audit';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='social-sharing';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions' AND `module`='bx_albums' AND `name`='more-auto';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_media' AND `module`='bx_albums' AND `name`='add-images-to-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_media' AND `module`='bx_albums' AND `name`='edit-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_media' AND `module`='bx_albums' AND `name`='edit-image';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_media' AND `module`='bx_albums' AND `name`='delete-image';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_media' AND `module`='bx_albums' AND `name`='move-image';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='edit-image';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='delete-image';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='move-image';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='view';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='vote';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='score';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='favorite';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='feature';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='social-sharing';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media' AND `module`='bx_albums' AND `name`='more-auto';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media_unit' AND `module`='bx_albums' AND `name`='comment';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media_unit' AND `module`='bx_albums' AND `name`='view';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media_unit' AND `module`='bx_albums' AND `name`='vote';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media_unit' AND `module`='bx_albums' AND `name`='score';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media_unit' AND `module`='bx_albums' AND `name`='favorite';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media_unit' AND `module`='bx_albums' AND `name`='feature';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media_unit' AND `module`='bx_albums' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_actions_media_unit' AND `module`='bx_albums' AND `name`='more-auto';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_my' AND `module`='bx_albums' AND `name`='create-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_submenu' AND `module`='bx_albums' AND `name`='albums-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_submenu' AND `module`='bx_albums' AND `name`='albums-popular';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_submenu' AND `module`='bx_albums' AND `name`='albums-top';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_submenu' AND `module`='bx_albums' AND `name`='albums-popular-media';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_submenu' AND `module`='bx_albums' AND `name`='albums-top-media';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_submenu' AND `module`='bx_albums' AND `name`='albums-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_submenu' AND `module`='bx_albums' AND `name`='albums-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_submenu' AND `module`='bx_albums' AND `name`='view-album';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_view_submenu' AND `module`='bx_albums' AND `name`='view-album-comments';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_snippet_meta' AND `module`='bx_albums' AND `name`='date';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_albums_snippet_meta' AND `module`='bx_albums' AND `name`='author';

+ 585 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/install/sql/enable.sql

@@ -0,0 +1,585 @@
+
+-- 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_albums', '_bx_albums', 'bx_albums@modules/boonex/albums/|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_albums', '_bx_albums', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_albums_enable_auto_approve', 'on', @iCategId, '_bx_albums_option_enable_auto_approve', 'checkbox', '', '', '', 0),
+('bx_albums_summary_chars', '700', @iCategId, '_bx_albums_option_summary_chars', 'digit', '', '', '', 1),
+('bx_albums_plain_summary_chars', '200', @iCategId, '_bx_albums_option_plain_summary_chars', 'digit', '', '', '', 2),
+('bx_albums_per_page_browse', '12', @iCategId, '_bx_albums_option_per_page_browse', 'digit', '', '', '', 10),
+('bx_albums_per_page_profile', '6', @iCategId, '_bx_albums_option_per_page_profile', 'digit', '', '', '', 12),
+('bx_albums_per_page_browse_showcase', '32', @iCategId, '_sys_option_per_page_browse_showcase', 'digit', '', '', '', 15),
+('bx_albums_per_page_for_favorites_lists', '5', @iCategId, '_bx_albums_option_per_page_for_favorites_lists', 'digit', '', '', '', 17),
+('bx_albums_album_browsing_unit', '10', @iCategId, '_bx_albums_option_album_browsing_unit', 'digit', '', '', '', 20),
+('bx_albums_rss_num', '10', @iCategId, '_bx_albums_option_rss_num', 'digit', '', '', '', 20),
+('bx_albums_searchable_fields', 'title,text', @iCategId, '_bx_albums_option_searchable_fields', 'list', '', '', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:21:"get_searchable_fields";}', 30);
+
+-- PAGE: create entry
+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_albums_create_entry', '_bx_albums_page_title_sys_create_entry', '_bx_albums_page_title_create_entry', 'bx_albums', 5, 2147483647, 1, 'create-album', 'page.php?i=create-album', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowse', 'modules/boonex/albums/classes/BxAlbumsPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_create_entry', 1, 'bx_albums', '_bx_albums_page_block_title_create_entry', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:13:"entity_create";}', 0, 1, 1);
+
+-- PAGE: add images
+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_albums_add_images', '_bx_albums_page_title_sys_add_images', '_bx_albums_page_title_add_images', 'bx_albums', 5, 2147483647, 1, 'add-images', '', '', '', '', 0, 1, 0, 'BxAlbumsPageEntry', 'modules/boonex/albums/classes/BxAlbumsPageEntry.php');
+
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_add_images', 1, 'bx_albums', '_bx_albums_page_block_title_add_images', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:16:"entity_add_files";}', 0, 0, 0);
+
+-- PAGE: edit entry
+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_albums_edit_entry', '_bx_albums_page_title_sys_edit_entry', '_bx_albums_page_title_edit_entry', 'bx_albums', 5, 2147483647, 1, 'edit-album', '', '', '', '', 0, 1, 0, 'BxAlbumsPageEntry', 'modules/boonex/albums/classes/BxAlbumsPageEntry.php');
+
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_edit_entry', 1, 'bx_albums', '_bx_albums_page_block_title_edit_entry', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:11:"entity_edit";}', 0, 0, 0);
+
+-- PAGE: delete entry
+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_albums_delete_entry', '_bx_albums_page_title_sys_delete_entry', '_bx_albums_page_title_delete_entry', 'bx_albums', 5, 2147483647, 1, 'delete-album', '', '', '', '', 0, 1, 0, 'BxAlbumsPageEntry', 'modules/boonex/albums/classes/BxAlbumsPageEntry.php');
+
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_delete_entry', 1, 'bx_albums', '_bx_albums_page_block_title_delete_entry', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:13:"entity_delete";}', 0, 0, 0);
+
+-- PAGE: view entry
+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_albums_view_entry', '_bx_albums_page_title_sys_view_entry', '_bx_albums_page_title_view_entry', 'bx_albums', 12, 2147483647, 1, 'view-album', '', '', '', '', 0, 1, 0, 'BxAlbumsPageEntry', 'modules/boonex/albums/classes/BxAlbumsPageEntry.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_albums_view_entry', 2, 'bx_albums','' , '_bx_albums_page_block_title_entry_text', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:17:\"entity_text_block\";}', 0, 0, 1, 1),
+('bx_albums_view_entry', 2, 'bx_albums','' , '_bx_albums_page_block_title_entry_attachments', 0, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:18:\"entity_attachments\";}', 0, 0, 1, 3),
+('bx_albums_view_entry', 4, 'bx_albums','' , '_bx_albums_page_block_title_entry_social_sharing', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:21:\"entity_social_sharing\";}', 0, 0, 0, 0),
+('bx_albums_view_entry', 2, 'bx_albums','' , '_bx_albums_page_block_title_entry_comments', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:15:\"entity_comments\";}', 0, 0, 1, 4),
+('bx_albums_view_entry', 3, 'bx_albums','' , '_bx_albums_page_block_title_entry_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:9:\"bx_albums\";i:1;s:4:\"{id}\";}s:5:\"class\";s:20:\"TemplServiceMetatags\";}', 0, 0, 1, 4),
+('bx_albums_view_entry', 3, 'bx_albums','' , '_bx_albums_page_block_title_entry_author', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:13:\"entity_author\";}', 0, 0, 1, 2),
+('bx_albums_view_entry', 3, 'bx_albums', '_bx_albums_page_block_title_sys_entry_context', '_bx_albums_page_block_title_entry_context', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:14:\"entity_context\";}', 0, 0, 1, 1),
+('bx_albums_view_entry', 3, 'bx_albums','' , '_bx_albums_page_block_title_entry_info', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:11:\"entity_info\";}', 0, 0, 1, 3),
+('bx_albums_view_entry', 3, 'bx_albums', '', '_bx_albums_page_block_title_entry_actions', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:14:\"entity_actions\";}', 0, 0, 0, 0),
+('bx_albums_view_entry', 2, 'bx_albums','' , '_bx_albums_page_block_title_entry_all_actions', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:18:\"entity_all_actions\";}', 0, 0, 1, 2),
+('bx_albums_view_entry', 2, 'bx_albums', '', '_bx_albums_page_block_title_entry_reports', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:14:\"entity_reports\";}', 0, 0, 1, 6);
+
+-- PAGE: view media
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `content_info`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_albums_view_media', 'view-album-media', '_bx_albums_page_title_sys_view_media', '_bx_albums_page_title_view_media', 'bx_albums', 12, 2147483647, 1, '', 'bx_albums_media', '', '', '', 0, 1, 0, 'BxAlbumsPageMedia', 'modules/boonex/albums/classes/BxAlbumsPageMedia.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_albums_view_media', 2, 'bx_albums', '', '_bx_albums_page_block_title_entry_view_media', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:10:\"media_view\";}', 0, 0, 1, 1),
+('bx_albums_view_media', 3, 'bx_albums', '', '_bx_albums_page_block_title_entry_author', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:12:\"media_author\";}', 0, 0, 1, 1),
+('bx_albums_view_media', 3, 'bx_albums', '', '_bx_albums_page_block_title_entry_social_sharing', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:20:\"media_social_sharing\";}', 0, 0, 0, 0),
+('bx_albums_view_media', 2, 'bx_albums', '', '_bx_albums_page_block_title_entry_comments', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:14:\"media_comments\";}', 0, 0, 1, 3),
+('bx_albums_view_media', 3, 'bx_albums', '', '_bx_albums_page_block_title_entry_view_media_exif', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:10:\"media_exif\";}', 0, 0, 1, 2),
+('bx_albums_view_media', 3, 'bx_albums', '', '_bx_albums_page_block_title_entry_actions', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:13:\"media_actions\";}', 0, 0, 0, 0),
+('bx_albums_view_media', 2, 'bx_albums', '', '_bx_albums_page_block_title_entry_all_actions', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:17:\"media_all_actions\";}', 0, 0, 1, 2),
+('bx_albums_view_media', 3, 'bx_albums', '', '_bx_albums_page_block_title_featured_entries_view_gallery_media', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:21:"browse_featured_media";s:6:"params";a:1:{i:0;s:7:"gallery";}}', 0, 1, 1, 3);
+
+-- PAGE: edit media
+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_albums_edit_media', '_bx_albums_page_title_sys_edit_media', '_bx_albums_page_title_edit_media', 'bx_albums', 5, 2147483647, 1, 'edit-media', '', '', '', '', 0, 1, 0, 'BxAlbumsPageMedia', 'modules/boonex/albums/classes/BxAlbumsPageMedia.php');
+
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_edit_media', 1, 'bx_albums', '_bx_albums_page_block_title_edit_media', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:10:"media_edit";}', 0, 0, 0);
+
+-- PAGE: move media
+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_albums_move_media', '_bx_albums_page_title_sys_move_media', '_bx_albums_page_title_move_media', 'bx_albums', 5, 2147483647, 1, 'move-media', '', '', '', '', 0, 1, 0, 'BxAlbumsPageMedia', 'modules/boonex/albums/classes/BxAlbumsPageMedia.php');
+
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_move_media', 1, 'bx_albums', '_bx_albums_page_block_title_move_media', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:10:"media_move";}', 0, 0, 0);
+
+-- PAGE: delete media
+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_albums_delete_media', '_bx_albums_page_title_sys_delete_media', '_bx_albums_page_title_delete_media', 'bx_albums', 5, 2147483647, 1, 'delete-media', '', '', '', '', 0, 1, 0, 'BxAlbumsPageMedia', 'modules/boonex/albums/classes/BxAlbumsPageMedia.php');
+
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('bx_albums_delete_media', 1, 'bx_albums', '_bx_albums_page_block_title_delete_media', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:12:"media_delete";}', 0, 0, 0);
+
+-- 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_albums_view_entry_comments', '_bx_albums_page_title_sys_view_entry_comments', '_bx_albums_page_title_view_entry_comments', 'bx_albums', 5, 2147483647, 1, 'view-album-comments', '', '', '', '', 0, 1, 0, 'BxAlbumsPageEntry', 'modules/boonex/albums/classes/BxAlbumsPageEntry.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_albums_view_entry_comments', 1, 'bx_albums', '_bx_albums_page_block_title_entry_comments', '_bx_albums_page_block_title_entry_comments_link', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:15:\"entity_comments\";}', 0, 0, 1);
+
+-- PAGE: popular albums
+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_albums_popular', '_bx_albums_page_title_sys_entries_popular', '_bx_albums_page_title_entries_popular', 'bx_albums', 5, 2147483647, 1, 'albums-popular', 'page.php?i=albums-popular', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowse', 'modules/boonex/albums/classes/BxAlbumsPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_albums_popular', 1, 'bx_albums', '_bx_albums_page_block_title_popular_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:14:"browse_popular";s:6:"params";a:3:{s:9:"unit_view";s:7:"gallery";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:0;}}', 0, 1, 1);
+
+-- PAGE: popular media
+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_albums_popular_media', '_bx_albums_page_title_sys_entries_popular_media', '_bx_albums_page_title_entries_popular_media', 'bx_albums', 5, 2147483647, 1, 'albums-popular-media', 'page.php?i=albums-popular-media', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowseMedia', 'modules/boonex/albums/classes/BxAlbumsPageBrowseMedia.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_albums_popular_media', 1, 'bx_albums', '_bx_albums_page_block_title_popular_media', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:20:"browse_popular_media";s:6:"params";a:3:{s:9:"unit_view";s:7:"gallery";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:0;}}', 0, 1, 1);
+
+-- PAGE: top albums
+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_albums_top', '_bx_albums_page_title_sys_entries_top', '_bx_albums_page_title_entries_top', 'bx_albums', 5, 2147483647, 1, 'albums-top', 'page.php?i=albums-top', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowse', 'modules/boonex/albums/classes/BxAlbumsPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_albums_top', 1, 'bx_albums', '_bx_albums_page_block_title_top_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:10:"browse_top";s:6:"params";a:3:{s:9:"unit_view";s:7:"gallery";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:0;}}', 0, 1, 1);
+
+-- PAGE: top media
+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_albums_top_media', '_bx_albums_page_title_sys_entries_top_media', '_bx_albums_page_title_entries_top_media', 'bx_albums', 5, 2147483647, 1, 'albums-top-media', 'page.php?i=albums-top-media', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowseMedia', 'modules/boonex/albums/classes/BxAlbumsPageBrowseMedia.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_albums_top_media', 1, 'bx_albums', '_bx_albums_page_block_title_top_media', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:16:"browse_top_media";s:6:"params";a:3:{s:9:"unit_view";s:7:"gallery";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:0;}}', 0, 1, 1);
+
+-- PAGE: entries of author
+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_albums_author', 'albums-author', '_bx_albums_page_title_sys_entries_of_author', '_bx_albums_page_title_entries_of_author', 'bx_albums', 5, 2147483647, 1, '', '', '', '', 0, 1, 0, 'BxAlbumsPageAuthor', 'modules/boonex/albums/classes/BxAlbumsPageAuthor.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_albums_author', 1, 'bx_albums', '', '_bx_albums_page_block_title_entries_actions', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:18:\"my_entries_actions\";}', 0, 0, 1, 1),
+('bx_albums_author', 1, 'bx_albums', '_bx_albums_page_block_title_sys_favorites_of_author', '_bx_albums_page_block_title_favorites_of_author', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:21:"browse_favorite_lists";s:6:"params";a:2:{s:10:"profile_id";s:12:"{profile_id}";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, 2),
+('bx_albums_author', 1, 'bx_albums', '_bx_albums_page_block_title_sys_favorites_of_author_media', '_bx_albums_page_block_title_favorites_of_author_media', 11, 2147483647, 'service', 'a:3:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:21:\"browse_favorite_media\";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 1, 1, 3),
+('bx_albums_author', 1, 'bx_albums', '_bx_albums_page_block_title_sys_entries_of_author', '_bx_albums_page_block_title_entries_of_author', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:13:\"browse_author\";}', 0, 0, 1, 4),
+('bx_albums_author', 1, 'bx_albums', '_bx_albums_page_block_title_sys_entries_in_context', '_bx_albums_page_block_title_entries_in_context', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:14:"browse_context";s:6:"params";a:2:{s:10:"profile_id";s:12:"{profile_id}";i:0;a:1:{s:13:"empty_message";b:0;}}}', 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_albums_favorites', '_bx_albums_page_title_sys_entries_favorites', '_bx_albums_page_title_entries_favorites', 'bx_albums', 12, 2147483647, 1, 'albums-favorites', 'page.php?i=albums-favorites', '', '', '', 0, 1, 0, 'BxAlbumsPageListEntry', 'modules/boonex/albums/classes/BxAlbumsPageListEntry.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_albums_favorites', 2, 'bx_albums', '_bx_albums_page_block_title_sys_favorites_entries', '_bx_albums_page_block_title_favorites_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";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_albums_favorites', 3, 'bx_albums', '', '_bx_albums_page_block_title_favorites_entries_info', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:19:"favorites_list_info";}', 0, 0, 0),
+('bx_albums_favorites', 3, 'bx_albums', '', '_bx_albums_page_block_title_favorites_entries_actions', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:22:"favorites_list_actions";}', 0, 0, 1);
+
+
+
+-- PAGE: entries in context
+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_albums_context', 'albums-context', '_bx_albums_page_title_sys_entries_in_context', '_bx_albums_page_title_entries_in_context', 'bx_albums', 5, 2147483647, 1, '', '', '', '', 0, 1, 0, 'BxAlbumsPageAuthor', 'modules/boonex/albums/classes/BxAlbumsPageAuthor.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_albums_context', 1, 'bx_albums', '_bx_albums_page_block_title_sys_entries_in_context', '_bx_albums_page_block_title_entries_in_context', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:14:\"browse_context\";}', 0, 0, 1, 1);
+
+-- PAGE: module home
+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_albums_home', 'albums-home', '_bx_albums_page_title_sys_home', '_bx_albums_page_title_home', 'bx_albums', 2, 2147483647, 1, 'page.php?i=albums-home', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowse', 'modules/boonex/albums/classes/BxAlbumsPageBrowse.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_albums_home', 1, 'bx_albums', '', '_bx_albums_page_block_title_featured_entries_view_extended', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:15:"browse_featured";s:6:"params";a:1:{i:0;s:8:"extended";}}', 0, 1, 1, 0),
+('bx_albums_home', 1, 'bx_albums', '', '_bx_albums_page_block_title_featured_entries_view_gallery_media', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:21:"browse_featured_media";s:6:"params";a:1:{i:0;s:7:"gallery";}}', 0, 1, 1, 1),
+('bx_albums_home', 1, 'bx_albums', '', '_bx_albums_page_block_title_updated_entries', 11, 2147483647, 'service', 'a:3:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:14:\"browse_updated\";s:6:\"params\";a:3:{s:9:\"unit_view\";s:8:\"extended\";s:13:\"empty_message\";b:1;s:13:\"ajax_paginate\";b:0;}}', 0, 1, 1, 2),
+('bx_albums_home', 2, 'bx_albums', '', '_bx_albums_page_block_title_popular_keywords_albums', 11, 2147483647, 'service', 'a:4:{s:6:\"module\";s:6:\"system\";s:6:\"method\";s:14:\"keywords_cloud\";s:6:\"params\";a:3:{i:0;s:9:\"bx_albums\";i:1;s:9:\"bx_albums\";i:2;a:1:{s:10:\"show_empty\";b:1;}}s:5:\"class\";s:20:\"TemplServiceMetatags\";}', 1, 0, 1, 0),
+('bx_albums_home', 2, 'bx_albums', '', '_bx_albums_page_block_title_popular_keywords_media', 11, 2147483647, 'service', 'a:4:{s:6:\"module\";s:6:\"system\";s:6:\"method\";s:14:\"keywords_cloud\";s:6:\"params\";a:2:{i:0;s:15:\"bx_albums_media\";i:1;s:15:\"bx_albums_media\";}s:5:\"class\";s:20:\"TemplServiceMetatags\";}', 1, 0, 1, 1),
+('bx_albums_home', 2, 'bx_albums', '', '_bx_albums_page_block_title_popular_keywords_media_camera', 11, 2147483647, 'service', 'a:4:{s:6:\"module\";s:6:\"system\";s:6:\"method\";s:14:\"keywords_cloud\";s:6:\"params\";a:2:{i:0;s:22:\"bx_albums_media_camera\";i:1;s:22:\"bx_albums_media_camera\";}s:5:\"class\";s:20:\"TemplServiceMetatags\";}', 1, 0, 1, 2);
+
+-- 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_albums_search', '_bx_albums_page_title_sys_entries_search', '_bx_albums_page_title_entries_search', 'bx_albums', 11, 2147483647, 1, 'albums-search', 'page.php?i=albums-search', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowse', 'modules/boonex/albums/classes/BxAlbumsPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_albums_search', 2, 'bx_albums', '_bx_albums_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:9:"bx_albums";}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 1, 1),
+('bx_albums_search', 1, 'bx_albums', '_bx_albums_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:9:"bx_albums";s:10:"show_empty";b:0;}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 1, 1),
+('bx_albums_search', 3, 'bx_albums', '_bx_albums_page_block_title_search_form_media', 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_albums_media";}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 1, 1),
+('bx_albums_search', 1, 'bx_albums', '_bx_albums_page_block_title_search_results_media', 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_albums_media";s:10:"show_empty";b:0;}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 1, 2),
+('bx_albums_search', 2, 'bx_albums', '_bx_albums_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:14:"bx_albums_cmts";}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 0, 2),
+('bx_albums_search', 1, 'bx_albums', '_bx_albums_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:14:"bx_albums_cmts";s:10:"show_empty";b:0;}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 0, 3),
+('bx_albums_search', 3, 'bx_albums', '_bx_albums_page_block_title_search_form_media_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:20:"bx_albums_media_cmts";}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 0, 2),
+('bx_albums_search', 1, 'bx_albums', '_bx_albums_page_block_title_search_results_media_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:20:"bx_albums_media_cmts";s:10:"show_empty";b:0;}}s:5:"class";s:27:"TemplSearchExtendedServices";}', 0, 1, 0, 4);
+
+-- 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_albums_manage', '_bx_albums_page_title_sys_manage', '_bx_albums_page_title_manage', 'bx_albums', 5, 2147483647, 1, 'albums-manage', 'page.php?i=albums-manage', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowse', 'modules/boonex/albums/classes/BxAlbumsPageBrowse.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_albums_manage', 1, 'bx_albums', '_bx_albums_page_block_title_system_manage', '_bx_albums_page_block_title_manage', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_albums\";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_albums_administration', '_bx_albums_page_title_sys_manage_administration', '_bx_albums_page_title_manage', 'bx_albums', 5, 192, 1, 'albums-administration', 'page.php?i=albums-administration', '', '', '', 0, 1, 0, 'BxAlbumsPageBrowse', 'modules/boonex/albums/classes/BxAlbumsPageBrowse.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_albums_administration', 1, 'bx_albums', '_bx_albums_page_block_title_system_manage_administration', '_bx_albums_page_block_title_manage', 11, 192, 'service', 'a:3:{s:6:\"module\";s:9:\"bx_albums\";s:6:\"method\";s:12:\"manage_tools\";s:6:\"params\";a:1:{i:0;s:14:\"administration\";}}', 0, 1, 0);
+
+-- 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`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('sys_home', 1, 'bx_albums', '_bx_albums_page_block_title_recent_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:13:"browse_public";s:6:"params";a:2:{i:0;b:0;i:1;b:0;}}', 1, 0, 0, IFNULL(@iBlockOrder, 0) + 1),
+('sys_home', 1, 'bx_albums', '_bx_albums_page_block_title_updated_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:14:"browse_updated";s:6:"params";a:2:{i:0;b:0;i:1;b:0;}}', 1, 0, 0, IFNULL(@iBlockOrder, 0) + 2);
+
+-- PAGES: add page block to profiles modules (trigger* page objects are processed separately upon modules enable/disable)
+SET @iPBCellProfile = 3;
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('trigger_page_profile_view_entry', @iPBCellProfile, 'bx_albums', '_bx_albums_page_block_title_sys_my_entries', '_bx_albums_page_block_title_my_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:13:"browse_author";s:6:"params";a:2:{i:0;s:12:"{profile_id}";i:1;a:2:{s:8:"per_page";s:26:"bx_albums_per_page_profile";s:13:"empty_message";b:0;}}}', 0, 0, 0);
+
+-- 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_albums', '', '_bx_albums_page_block_title_recent_media', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:19:"browse_recent_media";s:6:"params";a:3:{s:9:"unit_view";s:7:"gallery";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:1;}}', 0, 1, 1, @iBlockOrder + 1),
+('', 0, 'bx_albums', '', '_bx_albums_page_block_title_popular_keywords_albums', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:14:"keywords_cloud";s:6:"params";a:2:{i:0;s:9:"bx_albums";i:1;s:9:"bx_albums";}s:5:"class";s:20:"TemplServiceMetatags";}', 0, 1, 1, @iBlockOrder + 2),
+('', 0, 'bx_albums', '', '_bx_albums_page_block_title_popular_keywords_media', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:14:"keywords_cloud";s:6:"params";a:2:{i:0;s:15:"bx_albums_media";i:1;s:15:"bx_albums_media";}s:5:"class";s:20:"TemplServiceMetatags";}', 0, 1, 1, @iBlockOrder + 3),
+('', 0, 'bx_albums', '', '_bx_albums_page_block_title_popular_keywords_media_camera', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:14:"keywords_cloud";s:6:"params";a:2:{i:0;s:22:"bx_albums_media_camera";i:1;s:22:"bx_albums_media_camera";}s:5:"class";s:20:"TemplServiceMetatags";}', 0, 1, 1, @iBlockOrder + 4),
+('', 0, 'bx_albums', '', '_bx_albums_page_block_title_recent_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:13:"browse_public";s:6:"params";a:3:{s:9:"unit_view";s:8:"extended";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:0;}}', 0, 1, 1, @iBlockOrder + 5),
+('', 0, 'bx_albums', '_bx_albums_page_block_title_sys_recent_media_view_showcase', '_bx_albums_page_block_title_recent_media_view_showcase', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:19:"browse_recent_media";s:6:"params";a:3:{s:9:"unit_view";s:8:"showcase";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:0;}}', 0, 1, 1, @iBlockOrder + 6),
+('', 0, 'bx_albums', '_bx_albums_page_block_title_sys_popular_media_view_showcase', '_bx_albums_page_block_title_popular_media_view_showcase', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:20:"browse_popular_media";s:6:"params";a:3:{s:9:"unit_view";s:8:"showcase";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:0;}}', 0, 1, 1,@iBlockOrder + 7),
+('', 0, 'bx_albums', '_bx_albums_page_block_title_sys_featured_media_view_showcase', '_bx_albums_page_block_title_featured_media_view_showcase', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_albums";s:6:"method";s:21:"browse_featured_media";s:6:"params";a:3:{s:9:"unit_view";s:8:"showcase";s:13:"empty_message";b:1;s:13:"ajax_paginate";b:0;}}', 0, 1, 1,@iBlockOrder + 8);
+
+-- 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_albums', 'albums-home', '_bx_albums_menu_item_title_system_entries_home', '_bx_albums_menu_item_title_entries_home', 'page.php?i=albums-home', '', '', 'far image', 'bx_albums_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_albums', 'albums-home', '_bx_albums_menu_item_title_system_entries_home', '_bx_albums_menu_item_title_entries_home', 'page.php?i=albums-home', '', '', 'far image', 'bx_albums_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_albums', 'create-album', '_bx_albums_menu_item_title_system_create_entry', '_bx_albums_menu_item_title_create_entry', 'page.php?i=create-album', '', '', 'far image', '', 2147483647, 1, 1, IFNULL(@iAddMenuOrder, 0) + 1);
+
+-- MENU: actions 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_albums_view', '_bx_albums_menu_title_view_entry', 'bx_albums_view', 'bx_albums', 9, 0, 1, 'BxAlbumsMenuView', 'modules/boonex/albums/classes/BxAlbumsMenuView.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_view', 'bx_albums', '_bx_albums_menu_set_title_view_entry', 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_albums_view', 'bx_albums', 'add-images-to-album', '_bx_albums_menu_item_title_system_add_images', '_bx_albums_menu_item_title_add_images', 'page.php?i=add-images&id={content_id}', '', '', 'plus', '', 2147483647, 1, 0, 10),
+('bx_albums_view', 'bx_albums', 'edit-album', '_bx_albums_menu_item_title_system_edit_entry', '_bx_albums_menu_item_title_edit_entry', 'page.php?i=edit-album&id={content_id}', '', '', 'pencil-alt', '', 2147483647, 1, 0, 20),
+('bx_albums_view', 'bx_albums', 'delete-album', '_bx_albums_menu_item_title_system_delete_entry', '_bx_albums_menu_item_title_delete_entry', 'page.php?i=delete-album&id={content_id}', '', '', 'remove', '', 2147483647, 1, 0, 30),
+('bx_albums_view', 'bx_albums', 'approve', '_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, 40);
+
+-- MENU: all actions 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_albums_view_actions', '_sys_menu_title_view_actions', 'bx_albums_view_actions', 'bx_albums', 15, 0, 1, 'BxAlbumsMenuViewActions', 'modules/boonex/albums/classes/BxAlbumsMenuViewActions.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_view_actions', 'bx_albums', '_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_albums_view_actions', 'bx_albums', 'add-images-to-album', '_bx_albums_menu_item_title_system_add_images', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 10),
+('bx_albums_view_actions', 'bx_albums', 'edit-album', '_bx_albums_menu_item_title_system_edit_entry', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 20),
+('bx_albums_view_actions', 'bx_albums', 'delete-album', '_bx_albums_menu_item_title_system_delete_entry', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 30),
+('bx_albums_view_actions', 'bx_albums', 'approve', '_sys_menu_item_title_system_va_approve', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 40),
+('bx_albums_view_actions', 'bx_albums', '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_albums'', content_id: {content_id}});', '', 'check-circle', '', '', 0, 2147483647, 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:19:"is_badges_avaliable";}', 1, 0, 50),
+('bx_albums_view_actions', 'bx_albums', 'comment', '_sys_menu_item_title_system_va_comment', '', '', '', '', '', '', '', 0, 2147483647, '', 0, 0, 200),
+('bx_albums_view_actions', 'bx_albums', 'view', '_sys_menu_item_title_system_va_view', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 210),
+('bx_albums_view_actions', 'bx_albums', 'vote', '_sys_menu_item_title_system_va_vote', '', '', '', '', '', '', '', 0, 2147483647, '', 0, 0, 220),
+('bx_albums_view_actions', 'bx_albums', 'reaction', '_sys_menu_item_title_system_va_reaction', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 225),
+('bx_albums_view_actions', 'bx_albums', 'score', '_sys_menu_item_title_system_va_score', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 230),
+('bx_albums_view_actions', 'bx_albums', 'favorite', '_sys_menu_item_title_system_va_favorite', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 240),
+('bx_albums_view_actions', 'bx_albums', 'feature', '_sys_menu_item_title_system_va_feature', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 250),
+('bx_albums_view_actions', 'bx_albums', 'repost', '_sys_menu_item_title_system_va_repost', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 260),
+('bx_albums_view_actions', 'bx_albums', 'report', '_sys_menu_item_title_system_va_report', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 270),
+('bx_albums_view_actions', 'bx_albums', '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', '', '', 0, 2147483647, '', 1, 0, 280),
+('bx_albums_view_actions', 'bx_albums', 'audit', '_sys_menu_item_title_system_va_audit', '_sys_menu_item_title_va_audit', 'page.php?i=dashboard-audit&module=bx_albums&content_id={content_id}', '', '', 'history', '', '', 0, 192, '', 1, 0, 290),
+('bx_albums_view_actions', 'bx_albums', '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_albums_view_actions', 'bx_albums', '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: actions menu for view media
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_albums_view_media', '_bx_albums_menu_title_view_media', 'bx_albums_view_media', 'bx_albums', 9, 0, 1, 'BxAlbumsMenuView', 'modules/boonex/albums/classes/BxAlbumsMenuView.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_view_media', 'bx_albums', '_bx_albums_menu_set_title_view_media', 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_albums_view_media', 'bx_albums', 'add-images-to-album', '_bx_albums_menu_item_title_system_add_images', '_bx_albums_menu_item_title_add_images', 'page.php?i=add-images&id={content_id}', '', '', 'plus', '', 2147483647, 1, 0, 10),
+('bx_albums_view_media', 'bx_albums', 'edit-album', '_bx_albums_menu_item_title_system_edit_album', '_bx_albums_menu_item_title_edit_album', 'page.php?i=edit-album&id={content_id}', '', '', 'pencil-alt', '', 2147483647, 1, 0, 20),
+('bx_albums_view_media', 'bx_albums', 'edit-image', '_bx_albums_menu_item_title_system_edit_image', '_bx_albums_menu_item_title_edit_image', 'javascript:void(0)', 'javascript:{js_object}.editMedia(this, {media_id});', '', 'pencil-alt', '', 2147483647, 1, 0, 30),
+('bx_albums_view_media', 'bx_albums', 'delete-image', '_bx_albums_menu_item_title_system_delete_image', '_bx_albums_menu_item_title_delete_image', 'javascript:void(0)', 'javascript:{js_object}.deleteMedia(this, {media_id});', '', 'remove', '', 2147483647, 1, 0, 40),
+('bx_albums_view_media', 'bx_albums', 'move-image', '_bx_albums_menu_item_title_system_move_image', '_bx_albums_menu_item_title_move_image', 'javascript:void(0)', 'javascript:{js_object}.moveMedia(this, {media_id});', '', 'exchange-alt', '', 2147483647, 1, 0, 50);
+
+
+-- MENU: all actions menu for view media page 
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_albums_view_actions_media', '_bx_albums_menu_title_view_actions_media', 'bx_albums_view_actions_media', 'bx_albums', 15, 0, 1, 'BxAlbumsMenuViewActionsMedia', 'modules/boonex/albums/classes/BxAlbumsMenuViewActionsMedia.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_view_actions_media', 'bx_albums', '_bx_albums_menu_set_title_view_actions_media', 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_albums_view_actions_media', 'bx_albums', 'add-images-to-album', '_bx_albums_menu_item_title_system_add_images', '', '', '', '', '', '', '', 0, 2147483647, 0, 0, 10),
+('bx_albums_view_actions_media', 'bx_albums', 'edit-album', '_bx_albums_menu_item_title_system_edit_album', '', '', '', '', '', '', '', 0, 2147483647, 0, 0, 20),
+('bx_albums_view_actions_media', 'bx_albums', 'edit-image', '_bx_albums_menu_item_title_system_edit_image', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 30),
+('bx_albums_view_actions_media', 'bx_albums', 'delete-image', '_bx_albums_menu_item_title_system_delete_image', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 40),
+('bx_albums_view_actions_media', 'bx_albums', 'move-image', '_bx_albums_menu_item_title_system_move_image', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 50),
+('bx_albums_view_actions_media', 'bx_albums', 'comment', '_sys_menu_item_title_system_va_comment', '', '', '', '', '', '', '', 0, 2147483647, 0, 0, 200),
+('bx_albums_view_actions_media', 'bx_albums', 'view', '_sys_menu_item_title_system_va_view', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 210),
+('bx_albums_view_actions_media', 'bx_albums', 'vote', '_sys_menu_item_title_system_va_vote', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 220),
+('bx_albums_view_actions_media', 'bx_albums', 'score', '_sys_menu_item_title_system_va_score', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 230),
+('bx_albums_view_actions_media', 'bx_albums', 'favorite', '_sys_menu_item_title_system_va_favorite', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 240),
+('bx_albums_view_actions_media', 'bx_albums', 'feature', '_sys_menu_item_title_system_va_feature', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 250),
+('bx_albums_view_actions_media', 'bx_albums', 'report', '_sys_menu_item_title_system_va_report', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 260),
+('bx_albums_view_actions_media', 'bx_albums', '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_albums_view_actions_media', 'bx_albums', '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: actions menu for media unit
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_albums_view_actions_media_unit', '_bx_albums_menu_title_view_actions_media_unit', 'bx_albums_view_actions_media_unit', 'bx_albums', 15, 0, 1, 'BxAlbumsMenuViewActionsMedia', 'modules/boonex/albums/classes/BxAlbumsMenuViewActionsMedia.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_view_actions_media_unit', 'bx_albums', '_bx_albums_menu_set_title_view_actions_media_unit', 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_albums_view_actions_media_unit', 'bx_albums', 'comment', '_sys_menu_item_title_system_va_comment', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 200),
+('bx_albums_view_actions_media_unit', 'bx_albums', 'view', '_sys_menu_item_title_system_va_view', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 210),
+('bx_albums_view_actions_media_unit', 'bx_albums', 'vote', '_sys_menu_item_title_system_va_vote', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 220),
+('bx_albums_view_actions_media_unit', 'bx_albums', 'score', '_sys_menu_item_title_system_va_score', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 230),
+('bx_albums_view_actions_media_unit', 'bx_albums', 'favorite', '_sys_menu_item_title_system_va_favorite', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 240),
+('bx_albums_view_actions_media_unit', 'bx_albums', 'feature', '_sys_menu_item_title_system_va_feature', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 250),
+('bx_albums_view_actions_media_unit', 'bx_albums', 'report', '_sys_menu_item_title_system_va_report', '', '', '', '', '', '', '', 0, 2147483647, 1, 0, 260),
+('bx_albums_view_actions_media_unit', 'bx_albums', '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: 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_albums_my', '_bx_albums_menu_title_entries_my', 'bx_albums_my', 'bx_albums', 9, 0, 1, 'BxAlbumsMenu', 'modules/boonex/albums/classes/BxAlbumsMenu.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_my', 'bx_albums', '_bx_albums_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_albums_my', 'bx_albums', 'create-album', '_bx_albums_menu_item_title_system_create_entry', '_bx_albums_menu_item_title_create_entry', 'page.php?i=create-album', '', '', '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_albums_submenu', '_bx_albums_menu_title_submenu', 'bx_albums_submenu', 'bx_albums', 8, 0, 1, '', '');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_submenu', 'bx_albums', '_bx_albums_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_albums_submenu', 'bx_albums', 'albums-home', '_bx_albums_menu_item_title_system_entries_recent', '_bx_albums_menu_item_title_entries_recent', 'page.php?i=albums-home', '', '', '', '', 2147483647, 1, 1, 1),
+('bx_albums_submenu', 'bx_albums', 'albums-popular', '_bx_albums_menu_item_title_system_entries_popular', '_bx_albums_menu_item_title_entries_popular', 'page.php?i=albums-popular', '', '', '', '', 2147483647, 1, 1, 2),
+('bx_albums_submenu', 'bx_albums', 'albums-top', '_bx_albums_menu_item_title_system_entries_top', '_bx_albums_menu_item_title_entries_top', 'page.php?i=albums-top', '', '', '', '', 2147483647, 1, 1, 3),
+('bx_albums_submenu', 'bx_albums', 'albums-popular-media', '_bx_albums_menu_item_title_system_entries_popular_media', '_bx_albums_menu_item_title_entries_popular_media', 'page.php?i=albums-popular-media', '', '', '', '', 2147483647, 1, 1, 4),
+('bx_albums_submenu', 'bx_albums', 'albums-top-media', '_bx_albums_menu_item_title_system_entries_top_media', '_bx_albums_menu_item_title_entries_top_media', 'page.php?i=albums-top-media', '', '', '', '', 2147483647, 1, 1, 5),
+('bx_albums_submenu', 'bx_albums', 'albums-search', '_bx_albums_menu_item_title_system_entries_search', '_bx_albums_menu_item_title_entries_search', 'page.php?i=albums-search', '', '', '', '', 2147483647, 1, 1, 6),
+('bx_albums_submenu', 'bx_albums', 'albums-manage', '_bx_albums_menu_item_title_system_entries_manage', '_bx_albums_menu_item_title_entries_manage', 'page.php?i=albums-manage', '', '', '', '', 2147483646, 1, 1, 7);
+
+-- MENU: sub-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_albums_view_submenu', '_bx_albums_menu_title_view_entry_submenu', 'bx_albums_view_submenu', 'bx_albums', 8, 0, 1, 'BxAlbumsMenuView', 'modules/boonex/albums/classes/BxAlbumsMenuView.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_view_submenu', 'bx_albums', '_bx_albums_menu_set_title_view_entry_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_albums_view_submenu', 'bx_albums', 'view-album', '_bx_albums_menu_item_title_system_view_entry', '_bx_albums_menu_item_title_view_entry_submenu_entry', 'page.php?i=view-album&id={content_id}', '', '', '', '', 2147483647, 1, 0, 1),
+('bx_albums_view_submenu', 'bx_albums', 'view-album-comments', '_bx_albums_menu_item_title_system_view_entry_comments', '_bx_albums_menu_item_title_view_entry_submenu_comments', 'page.php?i=view-album-comments&id={content_id}', '', '', '', '', 2147483647, 1, 0, 2);
+
+-- 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_albums_snippet_meta', '_sys_menu_title_snippet_meta', 'bx_albums_snippet_meta', 'bx_albums', 15, 0, 1, 'BxAlbumsMenuSnippetMeta', 'modules/boonex/albums/classes/BxAlbumsMenuSnippetMeta.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_snippet_meta', 'bx_albums', '_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`, `active`, `copyable`, `editable`, `order`) VALUES 
+('bx_albums_snippet_meta', 'bx_albums', 'date', '_sys_menu_item_title_system_sm_date', '_sys_menu_item_title_sm_date', '', '', '', '', '', 2147483647, 1, 0, 1, 1),
+('bx_albums_snippet_meta', 'bx_albums', 'author', '_sys_menu_item_title_system_sm_author', '_sys_menu_item_title_sm_author', '', '', '', '', '', 2147483647, 1, 0, 1, 2),
+('bx_albums_snippet_meta', 'bx_albums', 'tags', '_sys_menu_item_title_system_sm_tags', '_sys_menu_item_title_sm_tags', '', '', '', '', '', 2147483647, 0, 0, 1, 3),
+('bx_albums_snippet_meta', 'bx_albums', 'views', '_sys_menu_item_title_system_sm_views', '_sys_menu_item_title_sm_views', '', '', '', '', '', 2147483647, 0, 0, 1, 4),
+('bx_albums_snippet_meta', 'bx_albums', 'comments', '_sys_menu_item_title_system_sm_comments', '_sys_menu_item_title_sm_comments', '', '', '', '', '', 2147483647, 0, 0, 1, 5),
+('bx_albums_snippet_meta', 'bx_albums', 'items', '_bx_albums_menu_item_title_system_sm_items', '_bx_albums_menu_item_title_sm_items', '', '', '', '', '', 2147483647, 0, 0, 1, 6);
+
+-- 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_albums', 'profile-stats-my-albums', '_bx_albums_menu_item_title_system_manage_my_albums', '_bx_albums_menu_item_title_manage_my_albums', 'page.php?i=albums-author&profile_id={member_id}', '', '_self', 'far image', 'a:2:{s:6:"module";s:9:"bx_albums";s:6:"method";s:41:"get_menu_addon_manage_tools_profile_stats";}', '', 2147483646, 1, 0, @iNotifMenuOrder + 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_albums_menu_manage_tools', '_bx_albums_menu_title_manage_tools', 'bx_albums_menu_manage_tools', 'bx_albums', 6, 0, 1, 'BxAlbumsMenuManageTools', 'modules/boonex/albums/classes/BxAlbumsMenuManageTools.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_albums_menu_manage_tools', 'bx_albums', '_bx_albums_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_albums_menu_manage_tools', 'bx_albums', 'delete-with-content', '_bx_albums_menu_item_title_system_delete_with_content', '_bx_albums_menu_item_title_delete_with_content', 'javascript:void(0)', 'javascript:{js_object}.onClickDeleteWithContent({content_id});', '_self', 'far trash-alt', '', 128, 1, 0, 0);
+
+-- 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_albums', 'albums-administration', '_bx_albums_menu_item_title_system_admt_albums', '_bx_albums_menu_item_title_admt_albums', 'page.php?i=albums-administration', '', '_self', 'far image', 'a:2:{s:6:"module";s:9:"bx_albums";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_albums', 'albums-author', '_bx_albums_menu_item_title_system_view_entries_author', '_bx_albums_menu_item_title_view_entries_author', 'page.php?i=albums-author&profile_id={profile_id}', '', '', 'far image', '', 2147483647, 1, 0, 0),
+('trigger_group_view_submenu', 'bx_albums', 'albums-context', '_bx_albums_menu_item_title_system_view_entries_in_context', '_bx_albums_menu_item_title_view_entries_in_context', 'page.php?i=albums-context&profile_id={profile_id}', '', '', 'far image', '', 2147483647, 1, 0, 0);
+
+
+-- PRIVACY 
+INSERT INTO `sys_objects_privacy` (`object`, `module`, `action`, `title`, `default_group`, `table`, `table_field_id`, `table_field_author`, `override_class_name`, `override_class_file`) VALUES
+('bx_albums_allow_view_to', 'bx_albums', 'view', '_bx_albums_form_entry_input_allow_view_to', '3', 'bx_albums_albums', 'id', 'author', '', ''),
+('bx_albums_allow_view_favorite_list', 'bx_albums', 'view_favorite_list', '_bx_albums_form_entry_input_allow_view_favorite_list', '3', 'bx_albums_favorites_lists', 'id', 'author_id', '', '');
+
+
+-- ACL
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_albums', 'create entry', NULL, '_bx_albums_acl_action_create_entry', '', 1, 3);
+SET @iIdActionEntryCreate = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_albums', 'delete entry', NULL, '_bx_albums_acl_action_delete_entry', '', 1, 3);
+SET @iIdActionEntryDelete = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_albums', 'view entry', NULL, '_bx_albums_acl_action_view_entry', '', 1, 0);
+SET @iIdActionEntryView = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_albums', 'set thumb', NULL, '_bx_albums_acl_action_set_thumb', '', 1, 3);
+SET @iIdActionSetThumb = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_albums', 'edit any entry', NULL, '_bx_albums_acl_action_edit_any_entry', '', 1, 3);
+SET @iIdActionEntryEditAny = LAST_INSERT_ID();
+
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_albums', 'delete any entry', NULL, '_bx_albums_acl_action_delete_any_entry', '', 1, 3);
+SET @iIdActionEntryDeleteAny = 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
+
+-- entry create
+(@iStandard, @iIdActionEntryCreate),
+(@iModerator, @iIdActionEntryCreate),
+(@iAdministrator, @iIdActionEntryCreate),
+(@iPremium, @iIdActionEntryCreate),
+
+-- entry delete
+(@iStandard, @iIdActionEntryDelete),
+(@iModerator, @iIdActionEntryDelete),
+(@iAdministrator, @iIdActionEntryDelete),
+(@iPremium, @iIdActionEntryDelete),
+
+-- entry view
+(@iUnauthenticated, @iIdActionEntryView),
+(@iAccount, @iIdActionEntryView),
+(@iStandard, @iIdActionEntryView),
+(@iUnconfirmed, @iIdActionEntryView),
+(@iPending, @iIdActionEntryView),
+(@iModerator, @iIdActionEntryView),
+(@iAdministrator, @iIdActionEntryView),
+(@iPremium, @iIdActionEntryView),
+
+-- set entry thumb
+(@iStandard, @iIdActionSetThumb),
+(@iModerator, @iIdActionSetThumb),
+(@iAdministrator, @iIdActionSetThumb),
+(@iPremium, @iIdActionSetThumb),
+
+-- edit any entry
+(@iModerator, @iIdActionEntryEditAny),
+(@iAdministrator, @iIdActionEntryEditAny),
+
+-- delete any entry
+(@iAdministrator, @iIdActionEntryDeleteAny);
+
+
+-- SEARCH
+SET @iSearchOrder = (SELECT IFNULL(MAX(`Order`), 0) FROM `sys_objects_search`);
+INSERT INTO `sys_objects_search` (`ObjectName`, `Title`, `Order`, `GlobalSearch`, `ClassName`, `ClassPath`) VALUES
+('bx_albums', '_bx_albums', @iSearchOrder + 1, 1, 'BxAlbumsSearchResult', 'modules/boonex/albums/classes/BxAlbumsSearchResult.php'),
+('bx_albums_cmts', '_bx_albums_cmts', @iSearchOrder + 2, 1, 'BxAlbumsCmtsSearchResult', 'modules/boonex/albums/classes/BxAlbumsCmtsSearchResult.php'),
+('bx_albums_media', '_bx_albums_media', @iSearchOrder + 3, 1, 'BxAlbumsSearchResultMedia', 'modules/boonex/albums/classes/BxAlbumsSearchResultMedia.php'),
+('bx_albums_media_cmts', '_bx_albums_media_cmts', @iSearchOrder + 4, 1, 'BxAlbumsCmtsSearchResultMedia', 'modules/boonex/albums/classes/BxAlbumsCmtsSearchResultMedia.php'),
+('bx_albums_media_camera', '_bx_albums_media', @iSearchOrder + 5, 0, 'BxAlbumsSearchResultMediaCamera', 'modules/boonex/albums/classes/BxAlbumsSearchResultMediaCamera.php');
+
+
+-- METATAGS
+INSERT INTO `sys_objects_metatags` (`object`, `module`, `table_keywords`, `table_locations`, `table_mentions`, `override_class_name`, `override_class_file`) VALUES
+('bx_albums', 'bx_albums', 'bx_albums_meta_keywords', 'bx_albums_meta_locations', 'bx_albums_meta_mentions', '', ''),
+('bx_albums_media', 'bx_albums', 'bx_albums_meta_keywords_media', '', '', '', ''),
+('bx_albums_media_camera', 'bx_albums', 'bx_albums_meta_keywords_media_camera', '', '', '', '');
+
+
+-- STATS
+SET @iMaxOrderStats = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_statistics`);
+INSERT INTO `sys_statistics` (`module`, `name`, `title`, `link`, `icon`, `query`, `order`) VALUES 
+('bx_albums', 'bx_albums', '_bx_albums', 'page.php?i=albums-home', 'far image', 'SELECT COUNT(*) FROM `bx_albums_albums` WHERE 1 AND `status` = ''active'' AND `status_admin` = ''active''', @iMaxOrderStats + 1),
+('bx_albums', 'bx_albums_media', '_bx_albums_media', '', 'far image', 'SELECT COUNT(*) FROM `bx_albums_files` WHERE 1', @iMaxOrderStats + 2);
+
+
+-- 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_albums_growth', '_bx_albums_chart_growth', 'bx_albums_albums', 'added', '', 'status,status_admin', '', 1, @iMaxOrderCharts + 1, 'BxDolChartGrowth', ''),
+('bx_albums_growth_speed', '_bx_albums_chart_growth_speed', 'bx_albums_albums', 'added', '', 'status,status_admin', '', 1, @iMaxOrderCharts + 2, 'BxDolChartGrowthSpeed', ''),
+('bx_albums_growth_media', '_bx_albums_chart_growth_media', 'bx_albums_files', 'added', '', '', '', 1, @iMaxOrderCharts + 3, 'BxDolChartGrowth', ''),
+('bx_albums_growth_speed_media', '_bx_albums_chart_growth_speed_media', 'bx_albums_files', 'added', '', '', '', 1, @iMaxOrderCharts + 4, 'BxDolChartGrowthSpeed', '');
+
+
+-- GRIDS: moderation tools
+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_albums_administration', 'Sql', 'SELECT * FROM `bx_albums_albums` WHERE 1 ', 'bx_albums_albums', 'id', 'added', 'status_admin', '', 20, NULL, 'start', '', 'title,text', '', 'like', 'reports', '', 192, 'BxAlbumsGridAdministration', 'modules/boonex/albums/classes/BxAlbumsGridAdministration.php'),
+('bx_albums_common', 'Sql', 'SELECT * FROM `bx_albums_albums` WHERE 1 ', 'bx_albums_albums', 'id', 'added', 'status', '', 20, NULL, 'start', '', 'title,text', '', 'like', '', '', 2147483647, 'BxAlbumsGridCommon', 'modules/boonex/albums/classes/BxAlbumsGridCommon.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_albums_administration', 'checkbox', '_sys_select', '2%', 0, '', '', 1),
+('bx_albums_administration', 'switcher', '_bx_albums_grid_column_title_adm_active', '8%', 0, '', '', 2),
+('bx_albums_administration', 'reports', '_sys_txt_reports_title', '5%', 0, '', '', 3),
+('bx_albums_administration', 'title', '_bx_albums_grid_column_title_adm_title', '25%', 0, '', '', 4),
+('bx_albums_administration', 'added', '_bx_albums_grid_column_title_adm_added', '20%', 1, '25', '', 5),
+('bx_albums_administration', 'author', '_bx_albums_grid_column_title_adm_author', '20%', 0, '25', '', 6),
+('bx_albums_administration', 'actions', '', '20%', 0, '', '', 7),
+
+('bx_albums_common', 'checkbox', '_sys_select', '2%', 0, '', '', 1),
+('bx_albums_common', 'switcher', '', '8%', 0, '', '', 2),
+('bx_albums_common', 'title', '_bx_albums_grid_column_title_adm_title', '40%', 0, '', '', 3),
+('bx_albums_common', 'added', '_bx_albums_grid_column_title_adm_added', '15%', 1, '25', '', 4),
+('bx_albums_common', 'status_admin', '_bx_albums_grid_column_title_adm_status_admin', '15%', 0, '16', '', 5),
+('bx_albums_common', 'actions', '', '20%', 0, '', '', 6);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_albums_administration', 'bulk', 'delete', '_bx_albums_grid_action_title_adm_delete', '', 0, 1, 1),
+('bx_albums_administration', 'bulk', 'clear_reports', '_bx_albums_grid_action_title_adm_clear_reports', '', 0, 1, 2),
+('bx_albums_administration', 'single', 'edit', '_bx_albums_grid_action_title_adm_edit', 'pencil-alt', 1, 0, 1),
+('bx_albums_administration', 'single', 'delete', '_bx_albums_grid_action_title_adm_delete', 'remove', 1, 1, 2),
+('bx_albums_administration', 'single', 'settings', '_bx_albums_grid_action_title_adm_more_actions', 'cog', 1, 0, 3),
+('bx_albums_administration', 'single', 'audit_content', '_bx_albums_grid_action_title_adm_audit_content', 'search', 1, 0, 4),
+('bx_albums_administration', 'single', 'clear_reports', '_bx_albums_grid_action_title_adm_clear_reports', 'eraser', 1, 0, 5),
+('bx_albums_common', 'bulk', 'delete', '_bx_albums_grid_action_title_adm_delete', '', 0, 1, 1),
+('bx_albums_common', 'single', 'edit', '_bx_albums_grid_action_title_adm_edit', 'pencil-alt', 1, 0, 1),
+('bx_albums_common', 'single', 'delete', '_bx_albums_grid_action_title_adm_delete', 'remove', 1, 1, 2),
+('bx_albums_common', 'single', 'settings', '_bx_albums_grid_action_title_adm_more_actions', 'cog', 1, 0, 3);
+
+
+-- UPLOADERS
+INSERT INTO `sys_objects_uploader` (`object`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_albums_html5', 1, 'BxAlbumsUploaderHTML5', 'modules/boonex/albums/classes/BxAlbumsUploaderHTML5.php'),
+('bx_albums_record_video', 1, 'BxAlbumsUploaderRecordVideo', 'modules/boonex/albums/classes/BxAlbumsUploaderRecordVideo.php'),
+('bx_albums_crop', 1, 'BxAlbumsUploaderCrop', 'modules/boonex/albums/classes/BxAlbumsUploaderCrop.php');
+
+
+-- ALERTS
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES 
+('bx_albums', 'BxAlbumsAlertsResponse', 'modules/boonex/albums/classes/BxAlbumsAlertsResponse.php', '');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('system', 'save_setting', @iHandler),
+('profile', 'delete', @iHandler),
+
+('bx_albums_files', 'file_deleted', @iHandler),
+('bx_albums_video_mp4', 'transcoded', @iHandler);

+ 616 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/install/sql/install.sql

@@ -0,0 +1,616 @@
+-- TABLE: entries
+CREATE TABLE IF NOT EXISTS `bx_albums_albums` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `author` int(10) unsigned NOT NULL,
+  `added` int(11) NOT NULL,
+  `changed` int(11) NOT NULL,
+  `thumb` int(11) NOT NULL,
+  `title` varchar(255) NOT NULL,
+  `text` 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',
+  `rrate` float NOT NULL default '0',
+  `rvotes` 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',
+  `allow_view_to` varchar(16) NOT NULL DEFAULT '3',
+  `status` enum('active','awaiting','failed','hidden') NOT NULL DEFAULT 'active',
+  `status_admin` enum('active','hidden','pending') NOT NULL DEFAULT 'active',
+  PRIMARY KEY (`id`),
+  FULLTEXT KEY `title_text` (`title`,`text`)
+);
+
+-- TABLE: storages & transcoders
+CREATE TABLE IF NOT EXISTS `bx_albums_files` (
+  `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,
+  `dimensions` varchar(12) NOT NULL,
+  `duration` int(11) 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_albums_photos_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`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_albums_files2albums` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `content_id` int(10) unsigned NOT NULL,
+  `file_id` int(11) NOT NULL,
+  `author` int(10) unsigned NOT NULL,
+  `title` varchar(255) NOT NULL,
+  `views` int(11) NOT NULL,
+  `rate` float NOT NULL,
+  `votes` int(11) NOT NULL,
+  `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,
+  `reports` int(11) NOT NULL default '0',
+  `featured` int(11) NOT NULL default '0',
+  `cf` int(11) NOT NULL default '1',
+  `data` text NOT NULL,
+  `exif` text NOT NULL,
+  `order` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `file_content` (`file_id`,`content_id`),
+  KEY `content_id` (`content_id`),
+  FULLTEXT KEY `search_fields` (`title`)
+);
+
+-- TABLE: comments
+CREATE TABLE IF NOT EXISTS `bx_albums_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_albums_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`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_albums_cmts_media` (
+  `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`)
+);
+
+-- TABLE: votes
+CREATE TABLE IF NOT EXISTS `bx_albums_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_albums_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`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_albums_votes_media` (
+  `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_albums_votes_media_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`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_albums_reactions` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `reaction` varchar(32) NOT NULL default '',
+  `count` int(11) NOT NULL default '0',
+  `sum` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `reaction` (`object_id`, `reaction`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_albums_reactions_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',
+  `reaction` varchar(32) NOT NULL default '',
+  `value` tinyint(4) NOT NULL default '0',
+  `date` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `vote` (`object_id`, `author_nip`)
+);
+
+-- TABLE: views
+CREATE TABLE `bx_albums_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`)
+);
+
+CREATE TABLE `bx_albums_views_media_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: metas
+CREATE TABLE `bx_albums_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 `bx_albums_meta_keywords_media` (
+  `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 `bx_albums_meta_keywords_media_camera` (
+  `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 `bx_albums_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 `bx_albums_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: reports
+CREATE TABLE IF NOT EXISTS `bx_albums_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_albums_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`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_albums_reports_media` (
+  `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_albums_reports_media_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: favorites
+CREATE TABLE `bx_albums_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_albums_favorites_media_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `author_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_albums_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_albums_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_albums_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_albums_scores_media` (
+  `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_albums_scores_media_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`)
+);
+
+
+-- STORAGES & TRANSCODERS
+SET @sStorageEngine = (SELECT `value` FROM `sys_options` WHERE `name` = 'sys_storage_default');
+
+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_albums_files', @sStorageEngine, '', 360, 2592000, 3, 'bx_albums_files', 'allow-deny', '{imagevideo}', '', 0, 0, 0, 0, 0, 0),
+('bx_albums_photos_resized', @sStorageEngine, '', 360, 2592000, 3, 'bx_albums_photos_resized', 'allow-deny', '{imagevideo}', '', 0, 0, 0, 0, 0, 0);
+
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`, `override_class_name`, `override_class_file`) VALUES
+('bx_albums_preview', 'bx_albums_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_albums_files";}', 'no', '1', '2592000', '0', '', ''),
+('bx_albums_browse', 'bx_albums_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_albums_files";}', 'no', '1', '2592000', '0', '', ''),
+('bx_albums_big', 'bx_albums_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_albums_files";}', 'no', '1', '2592000', '0', '', ''),
+('bx_albums_video_poster_browse', 'bx_albums_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_albums_files";}', 'no', 0, 0, 0, 'BxDolTranscoderVideo', ''),
+('bx_albums_video_poster_preview', 'bx_albums_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_albums_files";}', 'no', 0, 0, 0, 'BxDolTranscoderVideo', ''),
+('bx_albums_video_poster_big', 'bx_albums_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_albums_files";}', 'no', 0, 0, 0, 'BxDolTranscoderVideo', ''),
+('bx_albums_video_mp4', 'bx_albums_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_albums_files";}', 'no', 0, 0, 0, 'BxDolTranscoderVideo', ''),
+('bx_albums_video_mp4_hd', 'bx_albums_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_albums_files";}', 'no', 0, 0, 0, 'BxDolTranscoderVideo', ''),
+('bx_albums_proxy_preview', 'bx_albums_photos_resized', 'Proxy', 'a:4:{s:6:"object";s:15:"bx_albums_files";s:5:"image";s:17:"bx_albums_preview";s:12:"video_poster";s:30:"bx_albums_video_poster_preview";s:5:"video";a:4:{i:0;s:19:"bx_albums_video_mp4";i:1;s:22:"bx_albums_video_mp4_hd";i:2;s:29:"bx_albums_video_poster_browse";i:3;s:26:"bx_albums_video_poster_big";}}', 'no', 0, 0, 0, 'BxDolTranscoderProxy', ''),
+('bx_albums_proxy_browse', 'bx_albums_photos_resized', 'Proxy', 'a:4:{s:6:"object";s:15:"bx_albums_files";s:5:"image";s:16:"bx_albums_browse";s:12:"video_poster";s:29:"bx_albums_video_poster_browse";s:5:"video";a:2:{i:0;s:19:"bx_albums_video_mp4";i:1;s:22:"bx_albums_video_mp4_hd";}}', 'no', 0, 0, 0, 'BxDolTranscoderProxy', ''),
+('bx_albums_proxy_cover', 'bx_albums_photos_resized', 'Proxy', 'a:4:{s:6:"object";s:15:"bx_albums_files";s:5:"image";s:13:"bx_albums_big";s:12:"video_poster";s:26:"bx_albums_video_poster_big";s:5:"video";a:2:{i:0;s:19:"bx_albums_video_mp4";i:1;s:22:"bx_albums_video_mp4_hd";}}', 'no', 0, 0, 0, 'BxDolTranscoderProxy', '');
+
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
+('bx_albums_preview', 'Resize', 'a:3:{s:1:"w";s:3:"300";s:1:"h";s:3:"300";s:13:"square_resize";s:1:"1";}', '0'),
+('bx_albums_browse', 'Resize', 'a:1:{s:1:"h";s:3:"180";}', '0'),
+('bx_albums_big', 'Resize', 'a:2:{s:1:"w";s:4:"1280";s:1:"h";s:4:"1280";}', '0'),
+('bx_albums_video_poster_browse', 'Poster', 'a:2:{s:1:"h";s:3:"180";s:10:"force_type";s:3:"jpg";}', 0),
+('bx_albums_video_poster_preview', 'Poster', 'a:2:{s:1:"h";s:3:"480";s:10:"force_type";s:3:"jpg";}', 0),
+('bx_albums_video_poster_preview', 'Resize', 'a:3:{s:1:"w";s:3:"300";s:1:"h";s:3:"300";s:13:"square_resize";s:1:"1";}', 10),
+('bx_albums_video_poster_big', 'Poster', 'a:2:{s:1:"h";s:3:"480";s:10:"force_type";s:3:"jpg";}', 0),
+('bx_albums_video_mp4', 'Mp4', 'a:2:{s:1:"h";s:3:"480";s:10:"force_type";s:3:"mp4";}', 0),
+('bx_albums_video_mp4_hd', 'Mp4', 'a:3:{s:1:"h";s:3:"720";s:13:"video_bitrate";s:4:"1536";s:10:"force_type";s:3:"mp4";}', 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_albums', 'bx_albums', '_bx_albums_form_entry', '', 'a:1:{s:7:\"enctype\";s:19:\"multipart/form-data\";}', 'bx_albums_albums', 'id', '', '', 'do_submit', '', 0, 1, 'BxAlbumsFormEntry', 'modules/boonex/albums/classes/BxAlbumsFormEntry.php');
+
+INSERT INTO `sys_form_displays`(`object`, `display_name`, `module`, `view_mode`, `title`) VALUES 
+('bx_albums', 'bx_albums_entry_add', 'bx_albums', 0, '_bx_albums_form_entry_display_add'),
+('bx_albums', 'bx_albums_entry_edit', 'bx_albums', 0, '_bx_albums_form_entry_display_edit'),
+('bx_albums', 'bx_albums_entry_add_images', 'bx_albums', 0, '_bx_albums_form_entry_display_add_images'),
+('bx_albums', 'bx_albums_entry_delete', 'bx_albums', 0, '_bx_albums_form_entry_display_delete'),
+('bx_albums', 'bx_albums_entry_view', 'bx_albums', 1, '_bx_albums_form_entry_display_view');
+
+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_albums', 'bx_albums', 'delete_confirm', 1, '', 0, 'checkbox', '_bx_albums_form_entry_input_sys_delete_confirm', '_bx_albums_form_entry_input_delete_confirm', '_bx_albums_form_entry_input_delete_confirm_info', 1, 0, 0, '', '', '', 'Avail', '', '_bx_albums_form_entry_input_delete_confirm_error', '', '', 1, 0),
+('bx_albums', 'bx_albums', 'do_submit', '_bx_albums_form_entry_input_do_submit', '', 0, 'submit', '_bx_albums_form_entry_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_albums', 'bx_albums', 'do_cancel', '_sys_form_input_cancel', '', 0, 'button', '_sys_form_input_sys_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:41:"window.open(''{edit_cancel_url}'', ''_self'')";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0),
+('bx_albums', 'bx_albums', 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '_sys_form_input_sys_controls', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_albums', 'bx_albums', 'title', '', '', 0, 'text', '_bx_albums_form_entry_input_sys_title', '_bx_albums_form_entry_input_title', '', 1, 0, 0, '', '', '', 'Length', 'a:2:{s:3:"min";i:3;s:3:"max";i:160;}', '_bx_albums_form_entry_input_title_err', 'Xss', '', 1, 0),
+('bx_albums', 'bx_albums', 'text', '', '', 0, 'textarea', '_bx_albums_form_entry_input_sys_text', '_bx_albums_form_entry_input_text', '', 0, 0, 2, '', '', '', '', '', '', 'XssHtml', '', 1, 0),
+('bx_albums', 'bx_albums', 'pictures', 'a:2:{i:0;s:15:"bx_albums_html5";i:1;s:22:"bx_albums_record_video";}', 'a:3:{s:15:"bx_albums_html5";s:25:"_sys_uploader_html5_title";s:22:"bx_albums_record_video";s:32:"_sys_uploader_record_video_title";s:14:"bx_albums_crop";s:24:"_sys_uploader_crop_title";}', 0, 'files', '_bx_albums_form_entry_input_sys_pictures', '_bx_albums_form_entry_input_pictures', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_albums', 'bx_albums', '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_albums', 'bx_albums', 'allow_view_to', '', '', 0, 'custom', '_bx_albums_form_entry_input_sys_allow_view_to', '_bx_albums_form_entry_input_allow_view_to', '', 1, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_albums', 'bx_albums', 'location', '', '', 0, 'location', '_sys_form_input_sys_location', '_sys_form_input_location', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_albums', 'bx_albums', 'added', '', '', 0, 'datetime', '_bx_albums_form_entry_input_sys_date_added', '_bx_albums_form_entry_input_date_added', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_albums', 'bx_albums', 'changed', '', '', 0, 'datetime', '_bx_albums_form_entry_input_sys_date_changed', '_bx_albums_form_entry_input_date_changed', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_albums', 'bx_albums', '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_albums_entry_add', 'delete_confirm', 2147483647, 0, 1),
+('bx_albums_entry_add', 'title', 2147483647, 1, 2),
+('bx_albums_entry_add', 'text', 2147483647, 1, 3),
+('bx_albums_entry_add', 'pictures', 2147483647, 1, 4),
+('bx_albums_entry_add', 'allow_view_to', 2147483647, 1, 5),
+('bx_albums_entry_add', 'cf', 2147483647, 1, 6),
+('bx_albums_entry_add', 'location', 2147483647, 1, 7),
+('bx_albums_entry_add', 'do_submit', 2147483647, 1, 8),
+
+('bx_albums_entry_edit', 'delete_confirm', 2147483647, 0, 1),
+('bx_albums_entry_edit', 'title', 2147483647, 1, 2),
+('bx_albums_entry_edit', 'text', 2147483647, 1, 3),
+('bx_albums_entry_edit', 'pictures', 2147483647, 1, 4),
+('bx_albums_entry_edit', 'allow_view_to', 2147483647, 1, 5),
+('bx_albums_entry_edit', 'cf', 2147483647, 1, 6),
+('bx_albums_entry_edit', 'location', 2147483647, 1, 7),
+('bx_albums_entry_edit', 'controls', 2147483647, 1, 8),
+('bx_albums_entry_edit', 'do_submit', 2147483647, 1, 9),
+('bx_albums_entry_edit', 'do_cancel', 2147483647, 1, 10),
+
+('bx_albums_entry_add_images', 'delete_confirm', 2147483647, 0, 1),
+('bx_albums_entry_add_images', 'title', 2147483647, 0, 2),
+('bx_albums_entry_add_images', 'text', 2147483647, 0, 3),
+('bx_albums_entry_add_images', 'allow_view_to', 2147483647, 0, 4),
+('bx_albums_entry_add_images', 'location', 2147483647, 0, 5),
+('bx_albums_entry_add_images', 'pictures', 2147483647, 1, 6),
+('bx_albums_entry_add_images', 'do_submit', 2147483647, 1, 7),
+
+('bx_albums_entry_view', 'delete_confirm', 2147483647, 0, 0),
+('bx_albums_entry_view', 'allow_view_to', 2147483647, 0, 0),
+('bx_albums_entry_view', 'do_submit', 2147483647, 0, 0),
+('bx_albums_entry_view', 'text', 2147483647, 0, 0),
+('bx_albums_entry_view', 'title', 2147483647, 0, 0),
+('bx_albums_entry_view', 'pictures', 2147483647, 0, 0),
+('bx_albums_entry_view', 'added', 2147483647, 1, 1),
+('bx_albums_entry_view', 'changed', 2147483647, 1, 2),
+
+('bx_albums_entry_delete', 'delete_confirm', 2147483647, 1, 1),
+('bx_albums_entry_delete', 'do_submit', 2147483647, 1, 2),
+('bx_albums_entry_delete', 'allow_view_to', 2147483647, 0, 0),
+('bx_albums_entry_delete', 'pictures', 2147483647, 0, 0),
+('bx_albums_entry_delete', 'text', 2147483647, 0, 0),
+('bx_albums_entry_delete', 'title', 2147483647, 0, 0);
+
+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_albums_media', 'bx_albums', '_bx_albums_form_media', '', 'a:1:{s:7:\"enctype\";s:19:\"multipart/form-data\";}', 'bx_albums_files2albums', 'id', '', '', 'do_submit', '', 0, 1, 'BxAlbumsFormMedia', 'modules/boonex/albums/classes/BxAlbumsFormMedia.php');
+
+INSERT INTO `sys_form_displays`(`object`, `display_name`, `module`, `view_mode`, `title`) VALUES 
+('bx_albums_media', 'bx_albums_media_edit', 'bx_albums', 0, '_bx_albums_form_media_display_edit'),
+('bx_albums_media', 'bx_albums_media_move', 'bx_albums', 0, '_bx_albums_form_media_display_move'),
+('bx_albums_media', 'bx_albums_media_delete', 'bx_albums', 0, '_bx_albums_form_media_display_delete');
+
+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_albums_media', 'bx_albums', 'delete_confirm', 1, '', 0, 'checkbox', '_bx_albums_form_media_input_sys_delete_confirm', '_bx_albums_form_media_input_delete_confirm', '_bx_albums_form_media_input_delete_confirm_info', 1, 0, 0, '', '', '', 'Avail', '', '_bx_albums_form_media_input_delete_confirm_error', '', '', 1, 0),
+('bx_albums_media', 'bx_albums', 'title', '', '', 0, 'text', '_bx_albums_form_media_input_sys_title', '_bx_albums_form_media_input_title', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_albums_media', 'bx_albums', 'content_id', '', '', 0, 'select', '_bx_albums_form_media_input_sys_content_id', '_bx_albums_form_media_input_content_id', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_albums_media', 'bx_albums', 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_albums_media', 'bx_albums', 'do_submit', '_bx_albums_form_media_input_do_submit', '', 0, 'submit', '_bx_albums_form_media_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_albums_media', 'bx_albums', 'do_cancel', '_bx_albums_form_media_input_do_cancel', '', 0, 'button', '_bx_albums_form_media_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";}', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_albums_media_edit', 'title', 2147483647, 1, 1),
+('bx_albums_media_edit', 'controls', 2147483647, 1, 2),
+('bx_albums_media_edit', 'do_submit', 2147483647, 1, 3),
+('bx_albums_media_edit', 'do_cancel', 2147483647, 1, 4),
+
+('bx_albums_media_move', 'content_id', 2147483647, 1, 1),
+('bx_albums_media_move', 'controls', 2147483647, 1, 2),
+('bx_albums_media_move', 'do_submit', 2147483647, 1, 3),
+('bx_albums_media_move', 'do_cancel', 2147483647, 1, 4),
+
+('bx_albums_media_delete', 'delete_confirm', 2147483647, 1, 1),
+('bx_albums_media_delete', 'controls', 2147483647, 1, 2),
+('bx_albums_media_delete', 'do_submit', 2147483647, 1, 3),
+('bx_albums_media_delete', 'do_cancel', 2147483647, 1, 4);
+
+
+-- 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_albums', 'bx_albums', 'bx_albums_cmts', 1, 5000, 1000, 3, 5, 3, 'tail', 1, 'bottom', 1, 1, 1, -3, 1, 'cmt', 'page.php?i=view-album&id={object_id}', '', 'bx_albums_albums', 'id', 'author', 'title', 'comments', '', ''),
+('bx_albums_notes', 'bx_albums', 'bx_albums_cmts_notes', 1, 5000, 1000, 0, 5, 3, 'tail', 1, 'bottom', 1, 1, 1, -3, 1, 'cmt', 'page.php?i=view-post&id={object_id}', '', 'bx_albums_albums', 'id', 'author', 'title', '', 'BxTemplCmtsNotes', ''),
+('bx_albums_media', 'bx_albums', 'bx_albums_cmts_media', 1, 5000, 1000, 3, 5, 3, 'tail', 1, 'bottom', 1, 1, 1, -3, 1, 'cmt', 'page.php?i=view-album-media&id={object_id}', '', 'bx_albums_files2albums', 'id', 'author', 'title', 'comments', 'BxAlbumsCmtsMedia', 'modules/boonex/albums/classes/BxAlbumsCmtsMedia.php');
+
+
+-- VOTES
+INSERT INTO `sys_objects_vote` (`Name`, `Module`, `TableMain`, `TableTrack`, `PostTimeout`, `MinValue`, `MaxValue`, `IsUndo`, `IsOn`, `TriggerTable`, `TriggerFieldId`, `TriggerFieldAuthor`, `TriggerFieldRate`, `TriggerFieldRateCount`, `ClassName`, `ClassFile`) VALUES 
+('bx_albums', 'bx_albums', 'bx_albums_votes', 'bx_albums_votes_track', '604800', '1', '1', '0', '1', 'bx_albums_albums', 'id', 'author', 'rate', 'votes', '', ''),
+('bx_albums_media', 'bx_albums', 'bx_albums_votes_media', 'bx_albums_votes_media_track', '604800', '1', '1', '0', '1', 'bx_albums_files2albums', 'id', 'author', 'rate', 'votes', 'BxAlbumsVoteLikesMedia', 'modules/boonex/albums/classes/BxAlbumsVoteLikesMedia.php'),
+
+('bx_albums_reactions', 'bx_albums', 'bx_albums_reactions', 'bx_albums_reactions_track', '604800', '1', '1', '1', '1', 'bx_albums_albums', 'id', 'author', 'rrate', 'rvotes', 'BxTemplVoteReactions', '');
+
+
+-- 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_albums', 'bx_albums', 'bx_albums_scores', 'bx_albums_scores_track', '604800', '0', 'bx_albums_albums', 'id', 'author', 'score', 'sc_up', 'sc_down', '', ''),
+('bx_albums_media', 'bx_albums', 'bx_albums_scores_media', 'bx_albums_scores_media_track', '604800', '0', 'bx_albums_files2albums', '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_albums', 'bx_albums', 'bx_albums_reports', 'bx_albums_reports_track', '1', 'page.php?i=view-album&id={object_id}', 'bx_albums_notes', 'bx_albums_albums', 'id', 'author', 'reports', '', ''),
+('bx_albums_media', 'bx_albums', 'bx_albums_reports_media', 'bx_albums_reports_media_track', '1', 'page.php?i=view-album-media&id={object_id}', '', 'bx_albums_files2albums', 'id', 'author', 'reports', '', '');
+
+
+-- 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_albums', 'bx_albums', 'bx_albums_views_track', '86400', '1', 'bx_albums_albums', 'id', '', 'views', '', ''),
+('bx_albums_media', 'bx_albums', 'bx_albums_views_media_track', '86400', '1', 'bx_albums_files2albums', 'id', 'author', 'views', '', '');
+
+
+-- 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_albums', 'bx_albums_favorites_track', 'bx_albums_favorites_lists', '1', '1', '1', 'page.php?i=view-album&id={object_id}', 'bx_albums_albums', 'id', 'author', 'favorites', '', ''),
+('bx_albums_media', 'bx_albums_favorites_media_track', '', '1', '1', '1', 'page.php?i=view-album-media&id={object_id}', 'bx_albums_files2albums', '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_albums', 'bx_albums', '1', '1', 'page.php?i=view-album&id={object_id}', 'bx_albums_albums', 'id', 'author', 'featured', '', ''),
+('bx_albums_media', 'bx_albums', '1', '1', 'page.php?i=view-album-media&id={object_id}', 'bx_albums_files2albums', 'id', '', '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_albums', '_bx_albums', 'bx_albums', 'added', 'edited', 'deleted', '', ''),
+('bx_albums_media', '_bx_albums_media', 'bx_albums', 'media_added', '', 'media_deleted', 'BxAlbumsContentInfoMedia', 'modules/boonex/albums/classes/BxAlbumsContentInfoMedia.php'),
+('bx_albums_cmts', '_bx_albums_cmts', 'bx_albums', 'commentPost', 'commentUpdated', 'commentRemoved', 'BxDolContentInfoCmts', ''),
+('bx_albums_media_cmts', '_bx_albums_media_cmts', 'bx_albums_media', 'commentPost', 'commentUpdated', 'commentRemoved', 'BxDolContentInfoCmts', '');
+
+INSERT INTO `sys_content_info_grids` (`object`, `grid_object`, `grid_field_id`, `condition`, `selection`) VALUES
+('bx_albums', 'bx_albums_administration', 'id', '', ''),
+('bx_albums', 'bx_albums_common', 'id', '', '');
+
+
+-- SEARCH EXTENDED
+INSERT INTO `sys_objects_search_extended` (`object`, `object_content_info`, `module`, `title`, `active`, `class_name`, `class_file`) VALUES
+('bx_albums', 'bx_albums', 'bx_albums', '_bx_albums_search_extended', 1, '', ''),
+('bx_albums_media', 'bx_albums_media', 'bx_albums', '_bx_albums_search_extended_media', 1, '', ''),
+('bx_albums_cmts', 'bx_albums_cmts', 'bx_albums', '_bx_albums_search_extended_cmts', 1, 'BxTemplSearchExtendedCmts', ''),
+('bx_albums_media_cmts', 'bx_albums_media_cmts', 'bx_albums', '_bx_albums_search_extended_media_cmts', 1, 'BxTemplSearchExtendedCmts', '');
+
+
+-- STUDIO: page & widget
+INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
+(3, 'bx_albums', '_bx_albums', '_bx_albums', 'bx_albums@modules/boonex/albums/|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_albums', 'content', '{url_studio}module.php?name=bx_albums', '', 'bx_albums@modules/boonex/albums/|std-icon.svg', '_bx_albums', '', '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));
+

+ 109 - 0
modules/boonex/albums/updates/15.0.3_15.0.4/source/js/main.js

@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Albums Albums
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+function BxAlbumsMain(oOptions) {
+    this._sActionsUrl = oOptions.sActionUrl;
+    this._sObjName = oOptions.sObjName == undefined ? 'oBxAlbumsMain' : oOptions.sObjName;
+
+    this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
+    this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+
+    this._aHtmlIds = oOptions.aHtmlIds == undefined ? {} : oOptions.aHtmlIds;
+    this._oRequestParams = oOptions.oRequestParams == undefined ? {} : oOptions.oRequestParams;
+
+    var $this = this;
+    $(document).ready(function() {
+    	$this.init();
+    });
+}
+
+BxAlbumsMain.prototype.init = function() {
+    $('.bx-albums-unit-images').flickity({
+        cellSelector: '.bx-albums-unit-image',
+        cellAlign: 'left',
+        imagesLoaded: true,
+        wrapAround: true,
+        pageDots: false
+    });
+};
+
+BxAlbumsMain.prototype.editMedia = function(oButton, iId) {
+    var $this = this;
+    var oDate = new Date();
+
+    this.loadingInButton(oButton, true);
+
+    $.get(
+        this._sActionsUrl + 'edit_media/' + iId, 
+        {
+            _t: oDate.getTime()
+        },
+        function(oData) {
+            $this.loadingInButton(oButton, false);
+
+            processJsonData(oData);
+        },
+        'json'
+    );
+};
+
+BxAlbumsMain.prototype.onEditMedia = function(oData) {
+    if(oData && oData.content != undefined)
+        $('#' + this._oHtmlIds['subentry_votes'] + oData.content.subentry_id).html(oData.content.votes_formated);
+};
+
+BxAlbumsMain.prototype.deleteMedia = function(oButton, iId) {
+    var $this = this;
+    var oDate = new Date();
+
+    this.loadingInButton(oButton, true);
+
+    $.get(
+        this._sActionsUrl + 'delete_media/' + iId, 
+        {
+            _t: oDate.getTime()
+        },
+        function(oData) {
+            $this.loadingInButton(oButton, false);
+
+            processJsonData(oData);
+        },
+        'json'
+    );
+};
+
+BxAlbumsMain.prototype.moveMedia = function(oButton, iId) {
+    var $this = this;
+    var oDate = new Date();
+
+    this.loadingInButton(oButton, true);
+
+    $.get(
+        this._sActionsUrl + 'move_media/' + iId, 
+        {
+            _t: oDate.getTime()
+        },
+        function(oData) {
+            $this.loadingInButton(oButton, false);
+
+            processJsonData(oData);
+        },
+        'json'
+    );
+};
+
+BxAlbumsMain.prototype.loadingInButton = function(e, bShow) {
+    if($(e).length)
+        bx_loading_btn($(e), bShow);
+    else
+        bx_loading($('body'), bShow);
+};
+
+/** @} */

+ 51 - 0
modules/boonex/antispam/updates/15.0.0_15.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' => 'Antispam',
+    'version_from' => '15.0.0',
+    'version_to' => '15.0.1',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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_15.0.0_15.0.1/',
+    'home_uri' => 'antispam_update_1500_1501',
+
+    'module_dir' => 'boonex/antispam/',
+    'module_uri' => 'antispam',
+
+    'db_prefix' => 'bx_antispam_',
+    'class_prefix' => 'BxAntispam',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Antispam',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/antispam/updates/15.0.0_15.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 BxAntispamUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 6 - 0
modules/boonex/antispam/updates/15.0.0_15.0.1/install/sql/enable.sql

@@ -0,0 +1,6 @@
+-- OPTIONS
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_antispam_akismet_api_key';
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_antispam_stopforumspam_api_key';
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_antispam_toxicity_filter_api_key';
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_antispam_lasso_moderation_api_key';
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_antispam_lasso_moderation_webhook_secret';

+ 635 - 0
modules/boonex/antispam/updates/15.0.0_15.0.1/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->isSubmitted() || !$oForm->isValid()) 
+            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'));
+        $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/15.0.0_15.0.1/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' => '15.0.1',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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(),
+);
+
+/** @} */

+ 262 - 0
modules/boonex/antispam/updates/15.0.0_15.0.1/source/install/sql/enable.sql

@@ -0,0 +1,262 @@
+
+-- 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_antispam', '_bx_antispam_adm_stg_cpt_type', 'bx_antispam@modules/boonex/antispam/|std-icon.svg', IF(NOT ISNULL(@iTypeOrder), @iTypeOrder + 1, 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_antispam_general', '_bx_antispam_adm_stg_cpt_category_general', 1);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_block', '_bx_antispam_option_block', '', 'checkbox', '', '', '', 10),
+(@iCategoryId, 'bx_antispam_report', '_bx_antispam_option_report', 'on', 'checkbox', '', '', '', 20);
+
+
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_antispam_ip_table', '_bx_antispam_adm_stg_cpt_category_ip_table', 2);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_ip_list_type', '_bx_antispam_option_ip_list_type', '0', 'select', 'a:3:{s:6:"module";s:11:"bx_antispam";s:6:"method";s:13:"config_values";s:6:"params";a:1:{i:0;s:8:"ip_table";}}', '', '', 10);
+
+
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_antispam_dnsbl', '_bx_antispam_adm_stg_cpt_category_dnsbl', 3);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_dnsbl_enable', '_bx_antispam_option_dnsbl_enable', '', 'checkbox', '', '', '', 10),
+(@iCategoryId, 'bx_antispam_dnsbl_behaviour_login', '_bx_antispam_option_dnsbl_behaviour_login', 'block', 'select', 'a:3:{s:6:"module";s:11:"bx_antispam";s:6:"method";s:13:"config_values";s:6:"params";a:1:{i:0;s:11:"dnsbl_login";}}', '', '', 20),
+(@iCategoryId, 'bx_antispam_dnsbl_behaviour_join', '_bx_antispam_option_dnsbl_behaviour_join', 'approval', 'select', 'a:3:{s:6:"module";s:11:"bx_antispam";s:6:"method";s:13:"config_values";s:6:"params";a:1:{i:0;s:10:"dnsbl_join";}}', '', '', 30),
+(@iCategoryId, 'bx_antispam_uridnsbl_enable', '_bx_antispam_option_uridnsbl_enable', '', 'checkbox', '', '', '', 40);
+
+
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_antispam_akismet', '_bx_antispam_adm_stg_cpt_category_akismet', 4);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_akismet_enable', '_bx_antispam_option_akismet_enable', '', 'checkbox', '', '', '', 10),
+(@iCategoryId, 'bx_antispam_akismet_api_key', '_bx_antispam_option_akismet_api_key', '', 'secret', '', '', '', 20);
+
+
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_antispam_stopforumspam', '_bx_antispam_adm_stg_cpt_category_stopforumspam', 5);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_stopforumspam_enable', '_bx_antispam_option_stopforumspam_enable', 'on', 'checkbox', '', '', '', 10),
+(@iCategoryId, 'bx_antispam_stopforumspam_api_key', '_bx_antispam_option_stopforumspam_api_key', '', 'secret', '', '', '', 20);
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_antispam_disposable_email_domains', '_bx_antispam_adm_stg_cpt_category_disposable_email_domains', 6);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_disposable_email_domains_mode', '_bx_antispam_option_disposable_email_domains_mode', 'blacklist', 'select', 'a:3:{s:6:"module";s:11:"bx_antispam";s:6:"method";s:13:"config_values";s:6:"params";a:1:{i:0;s:29:"disposable_email_domains_mode";}}', '', '', 10),
+(@iCategoryId, 'bx_antispam_disposable_email_domains_behaviour_join', '_bx_antispam_option_disposable_email_domains_behaviour_join', 'block', 'select', 'a:3:{s:6:"module";s:11:"bx_antispam";s:6:"method";s:13:"config_values";s:6:"params";a:1:{i:0;s:29:"disposable_email_domains_join";}}', '', '', 20);
+
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_antispam_profanity_filter', '_bx_antispam_adm_stg_cpt_category_profanity_filter', 7);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_profanity_filter_enable', '_bx_antispam_option_profanity_filter_enable', '', 'checkbox', '', '', '', 10),
+(@iCategoryId, 'bx_antispam_profanity_filter_dicts', '_bx_antispam_option_profanity_filter_dicts', '', 'list', 'a:2:{s:6:"module";s:11:"bx_antispam";s:6:"method";s:26:"get_profanity_filter_dicts";}', '', '', 15),
+(@iCategoryId, 'bx_antispam_profanity_filter_bad_words_list', '_bx_antispam_option_profanity_filter_bad_words_list', '', 'text', '', '', '', 20),
+(@iCategoryId, 'bx_antispam_profanity_filter_char_replace', '_bx_antispam_option_profanity_filter_char_replace', '*', 'digit', '', '', '', 30),
+(@iCategoryId, 'bx_antispam_profanity_filter_white_words_list', '_bx_antispam_option_profanity_filter_white_words_list', '', 'text', '', '', '', 40),
+(@iCategoryId, 'bx_antispam_profanity_filter_full_words_only', '_bx_antispam_option_profanity_filter_full_words_only', 'on', 'checkbox', '', '', '', 50);
+
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )
+VALUES (@iTypeId, 'bx_antispam_toxicity_filter', '_bx_antispam_adm_stg_cpt_category_toxicity_filter', 8);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_toxicity_filter_enable', '_bx_antispam_option_toxicity_filter_enable', '', 'checkbox', '', '', '', 10),
+(@iCategoryId, 'bx_antispam_toxicity_filter_api_key', '_bx_antispam_option_toxicity_filter_api_key', '', 'secret', '', '', '', 20),
+(@iCategoryId, 'bx_antispam_toxicity_filter_action', '_bx_antispam_option_toxicity_filter_action', 'none', 'select', 'a:2:{s:6:"module";s:11:"bx_antispam";s:6:"method";s:27:"get_toxicity_filter_actions";}', '', '', 30),
+(@iCategoryId, 'bx_antispam_toxicity_filter_report', '_bx_antispam_option_toxicity_report', '', 'checkbox', 'on', '', '', 40),
+(@iCategoryId, 'bx_antispam_toxicity_filter_threshold', '_bx_antispam_option_toxicity_filter_threshold', '60', 'digit', '', '', '', 50);
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )
+VALUES (@iTypeId, 'bx_antispam_lasso_moderation', '_bx_antispam_adm_stg_cpt_category_lasso_moderation', 9);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `extra`, `check`, `check_error`, `order`) VALUES
+(@iCategoryId, 'bx_antispam_lasso_moderation_enable', '_bx_antispam_option_lasso_moderation_enable', '', 'checkbox', '', '', '', 10),
+(@iCategoryId, 'bx_antispam_lasso_moderation_api_key', '_bx_antispam_option_lasso_moderation_api_key', '', 'secret', '', '', '', 20),
+(@iCategoryId, 'bx_antispam_lasso_moderation_webhook_secret', '_bx_antispam_option_lasso_moderation_webhook_secret', '', 'secret', '', '', '', 30),
+(@iCategoryId, 'bx_antispam_lasso_moderation_webhook_url', '_bx_antispam_option_lasso_moderation_webhook_url', '{site_url}m/antispam/content_checked', 'value', '', '', '', 40),
+(@iCategoryId, 'bx_antispam_lasso_moderation_action', '_bx_antispam_option_lasso_moderation_action', 'none', 'select', 'a:2:{s:6:"module";s:11:"bx_antispam";s:6:"method";s:28:"get_lasso_moderation_actions";}', '', '', 50),
+(@iCategoryId, 'bx_antispam_lasso_moderation_report', '_bx_antispam_option_lasso_moderation_report', '', 'checkbox', 'on', '', '', 60),
+(@iCategoryId, 'bx_antispam_lasso_moderation_threshold_toxicity', '_bx_antispam_option_lasso_moderation_threshold_toxicity', '50', 'digit', '', '', '', 70),
+(@iCategoryId, 'bx_antispam_lasso_moderation_threshold_threat', '_bx_antispam_option_lasso_moderation_threshold_threat', '50', 'digit', '', '', '', 72),
+(@iCategoryId, 'bx_antispam_lasso_moderation_threshold_identity_attack', '_bx_antispam_option_lasso_moderation_threshold_identity_attack', '50', 'digit', '', '', '', 74),
+(@iCategoryId, 'bx_antispam_lasso_moderation_threshold_profanity', '_bx_antispam_option_lasso_moderation_threshold_profanity', '50', 'digit', '', '', '', 76);
+
+-- page: DNSBL list
+
+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_antispam_dnsbl', 'antispam-dnsbl-list', '_bx_antispam_page_title_sys_dnsbl_list', '_bx_antispam_page_title_dnsbl_list', 'bx_antispam', 5, 128, 1, 'page.php?i=antispam-dnsbl-list', '', '', '', 0, 1, 0, 'BxAntispamPage', 'modules/boonex/antispam/classes/BxAntispamPage.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_antispam_dnsbl', 1, 'bx_antispam', '_bx_antispam_page_block_title_dnsbl_list', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:11:\"bx_antispam\";s:6:\"method\";s:10:\"dnsbl_list\";}', 0, 1, 1, 1);
+
+-- page: ip table
+
+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_antispam_ip_table', 'antispam-ip-table', '_bx_antispam_page_title_sys_ip_table', '_bx_antispam_page_title_ip_table', 'bx_antispam', 5, 128, 1, 'page.php?i=antispam-ip-table', '', '', '', 0, 1, 0, 'BxAntispamPage', 'modules/boonex/antispam/classes/BxAntispamPage.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_antispam_ip_table', 1, 'bx_antispam', '_bx_antispam_page_block_title_ip_table', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:11:\"bx_antispam\";s:6:\"method\";s:8:\"ip_table\";}', 0, 1, 1, 1);
+
+-- page: block log
+
+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_antispam_block_log', 'antispam-block-log', '_bx_antispam_page_title_sys_block_log', '_bx_antispam_page_title_block_log', 'bx_antispam', 5, 192, 1, 'page.php?i=antispam-block-log', '', '', '', 0, 1, 0, 'BxAntispamPage', 'modules/boonex/antispam/classes/BxAntispamPage.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_antispam_block_log', 1, 'bx_antispam', '_bx_antispam_page_block_title_block_log', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:11:\"bx_antispam\";s:6:\"method\";s:9:\"block_log\";}', 0, 1, 1, 1);
+
+-- grid: DNSBL
+
+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_antispam_grid_dnsbl', 'Sql', 'SELECT `id`, `chain`, `zonedomain`, `postvresp`, `url`, `recheck`, `comment`, `added`, `active` FROM `bx_antispam_dnsbl_rules`', 'bx_antispam_dnsbl_rules', 'id', 'added', 'active', '', 16, NULL, 'start', '', 'chain,zonedomain,url,recheck', 'comment', 'auto', 'chain,zonedomain', 'comment', 128, 'BxAntispamGridDNSBL', 'modules/boonex/antispam/classes/BxAntispamGridDNSBL.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_antispam_grid_dnsbl', 'switcher', '_sys_active', '10%', 0, 0, '', 1),
+('bx_antispam_grid_dnsbl', 'chain', '_bx_antispam_field_chain', '15%', 0, 0, '', 2),
+('bx_antispam_grid_dnsbl', 'zonedomain', '_bx_antispam_field_zonedomain', '15%', 0, 0, '', 3),
+('bx_antispam_grid_dnsbl', 'comment', '_bx_antispam_field_note', '55%', 0, 0, '', 4),
+('bx_antispam_grid_dnsbl', 'actions', '_sys_actions', '5%', 0, 0, '', 5);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `confirm`, `order`) VALUES
+('bx_antispam_grid_dnsbl', 'independent', 'log', '_bx_antispam_grid_action_log', '', 0, 1),
+('bx_antispam_grid_dnsbl', 'independent', 'recheck', '_bx_antispam_grid_action_recheck', '', 0, 2),
+('bx_antispam_grid_dnsbl', 'independent', 'add', '_bx_antispam_grid_action_add', '', 0, 3),
+('bx_antispam_grid_dnsbl', 'independent', 'help', '_bx_antispam_grid_action_help', '', 0, 4),
+('bx_antispam_grid_dnsbl', 'single', 'delete', '', 'remove',  1, 1);
+
+-- grid: ip table
+
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_mode`, `sorting_fields`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_antispam_grid_ip_table', 'Sql', 'SELECT `ID`, `From`, `To`, `Type`, `LastDT`, `Desc` FROM `bx_antispam_ip_table`', 'bx_antispam_ip_table', 'ID', '', '', 10, NULL, 'start', '', 'From,To,Desc', 'auto', 'From,To,Type,LastDT,Desc', 128, 'BxAntispamGridIpTable', 'modules/boonex/antispam/classes/BxAntispamGridIpTable.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `params`, `order`) VALUES
+('bx_antispam_grid_ip_table', 'checkbox', '_sys_select', '2%', '', 1),
+('bx_antispam_grid_ip_table', 'From', '_bx_antispam_field_ip_from', '15%', '', 2),
+('bx_antispam_grid_ip_table', 'To', '_bx_antispam_field_ip_to', '15%', '', 3),
+('bx_antispam_grid_ip_table', 'Type', '_bx_antispam_field_action', '15%', '', 4),
+('bx_antispam_grid_ip_table', 'LastDT', '_bx_antispam_field_expiration', '15%', '', 5),
+('bx_antispam_grid_ip_table', 'Desc', '_bx_antispam_field_note', '26%', '', 6),
+('bx_antispam_grid_ip_table', 'actions', '_sys_actions', '12%', '', 7);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `confirm`, `order`) VALUES
+('bx_antispam_grid_ip_table', 'bulk', 'delete', '_bx_antispam_grid_action_delete', '', 1, 1),
+('bx_antispam_grid_ip_table', 'single', 'edit', '', 'pencil-alt', 0, 1),
+('bx_antispam_grid_ip_table', 'single', 'delete', '', 'remove',  1, 2),
+('bx_antispam_grid_ip_table', 'independent', 'add', '_bx_antispam_grid_action_add', '', 0, 1);
+
+-- grid: block log
+
+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_antispam_grid_block_log', 'Sql', 'SELECT `id`, `ip`, `profile_id`, `type`, `extra`, `added` FROM `bx_antispam_block_log`', 'bx_antispam_block_log', 'id', 'id', '', '', 8, NULL, 'start', '', 'type,extra', '', 'auto', 'ip,profile_id,type,extra,added', '', 192, 'BxAntispamGridBlockLog', 'modules/boonex/antispam/classes/BxAntispamGridBlockLog.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_antispam_grid_block_log', 'ip', '_bx_antispam_field_ip', '20%', 0, 0, '', 1),
+('bx_antispam_grid_block_log', 'profile_id', '_bx_antispam_field_profile_id', '20%', 0, 0, '', 2),
+('bx_antispam_grid_block_log', 'type', '_bx_antispam_field_type', '15%', 0, 0, '', 3),
+('bx_antispam_grid_block_log', 'extra', '_bx_antispam_field_note', '15%', 0, 8, '', 4),
+('bx_antispam_grid_block_log', 'added', '_bx_antispam_field_when', '30%', 0, 0, '', 5);
+
+-- data list: ip table actions
+
+INSERT INTO `sys_form_pre_lists`(`module`, `key`, `title`, `use_for_sets`) VALUES 
+('bx_antispam', 'bx_antispam_ip_table_actions', '_bx_antispam_data_list_ip_table_actions', 0);
+
+INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `LKey`, `LKey2`, `Order`) VALUES 
+('bx_antispam_ip_table_actions', 'allow', '_bx_antispam_ip_allow', '', 0),
+('bx_antispam_ip_table_actions', 'deny', '_bx_antispam_ip_deny', '', 0);
+
+-- form: IP table add/edit
+
+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_antispam_ip_table_form', 'bx_antispam', '_bx_antispam_form_ip_table', 'grid.php?o={grid_object}&a={grid_action}', '', 'bx_antispam_ip_table', 'ID', '', '', 'bx_antispam_ip_table_submit', '', 0, 1, '', '');
+
+INSERT INTO `sys_form_displays`(`object`, `display_name`, `module`, `view_mode`, `title`) VALUES 
+('bx_antispam_ip_table_form', 'bx_antispam_ip_table_form_add', 'bx_antispam', 0, '_bx_antispam_form_ip_table_add'),
+('bx_antispam_ip_table_form', 'bx_antispam_ip_table_form_edit', 'bx_antispam', 0, '_bx_antispam_form_ip_table_edit');
+
+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_antispam_ip_table_form', 'bx_antispam', 'ID', '', '', 0, 'hidden', '_bx_antispam_field_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_antispam_ip_table_form', 'bx_antispam', 'From', '', '', 0, 'text', '_bx_antispam_field_ip_from', '_bx_antispam_field_ip_from', '', 1, 0, 0, '', '', '', 'preg', 'a:1:{s:4:\"preg\";s:20:\"#\\d+\\.\\d+\\.\\d+\\.\\d+#\";}', '_bx_antispam_field_ip_err_msg', 'Xss', '', 1, 0),
+('bx_antispam_ip_table_form', 'bx_antispam', 'To', '', '', 0, 'text', '_bx_antispam_field_ip_to', '_bx_antispam_field_ip_to', '', 1, 0, 0, '', '', '', 'preg', 'a:1:{s:4:\"preg\";s:20:\"#\\d+\\.\\d+\\.\\d+\\.\\d+#\";}', '_bx_antispam_field_ip_err_msg', 'Xss', '', 1, 0),
+('bx_antispam_ip_table_form', 'bx_antispam', 'Type', 'allow', '#!bx_antispam_ip_table_actions', 0, 'select', '_bx_antispam_field_action', '_bx_antispam_field_action', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_antispam_ip_table_form', 'bx_antispam', 'LastDT', '', '', 0, 'datetime', '_bx_antispam_field_expiration', '_bx_antispam_field_expiration', '', 1, 0, 0, '', '', '', 'date_time', '', '_bx_antispam_field_expiration_err_msg', 'DateTimeTs', '', 1, 0),
+('bx_antispam_ip_table_form', 'bx_antispam', 'Desc', '', '', 0, 'text', '_bx_antispam_field_note', '_bx_antispam_field_note', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_antispam_ip_table_form', 'bx_antispam', 'bx_antispam_ip_table_submit', '_sys_submit', '', 0, 'submit', '_sys_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_antispam_ip_table_form', 'bx_antispam', 'close', '_sys_close', '', 0, 'reset', '_sys_close', '', '', 0, 0, 0, 'a:2:{s:7:\"onclick\";s:46:\"$(\'.bx-popup-applied:visible\').dolPopupHide();\";s:5:\"class\";s:22:\"bx-def-margin-sec-left\";}', '', '', '', '', '', '', '', 1, 0),
+('bx_antispam_ip_table_form', 'bx_antispam', 'buttons', '', 'bx_antispam_ip_table_submit,close', 0, 'input_set', '_bx_antispam_form_buttons', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0);
+
+INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES 
+('bx_antispam_ip_table_form_add', 'ID', 2147483647, 0, 0),
+('bx_antispam_ip_table_form_add', 'From', 2147483647, 1, 1),
+('bx_antispam_ip_table_form_add', 'To', 2147483647, 1, 2),
+('bx_antispam_ip_table_form_add', 'Type', 2147483647, 1, 3),
+('bx_antispam_ip_table_form_add', 'LastDT', 2147483647, 1, 4),
+('bx_antispam_ip_table_form_add', 'Desc', 2147483647, 1, 5),
+('bx_antispam_ip_table_form_add', 'bx_antispam_ip_table_submit', 2147483647, 1, 6),
+('bx_antispam_ip_table_form_add', 'close', 2147483647, 1, 7),
+('bx_antispam_ip_table_form_add', 'buttons', 2147483647, 1, 8),
+('bx_antispam_ip_table_form_edit', 'ID', 2147483647, 1, 0),
+('bx_antispam_ip_table_form_edit', 'From', 2147483647, 1, 1),
+('bx_antispam_ip_table_form_edit', 'To', 2147483647, 1, 2),
+('bx_antispam_ip_table_form_edit', 'Type', 2147483647, 1, 3),
+('bx_antispam_ip_table_form_edit', 'LastDT', 2147483647, 1, 4),
+('bx_antispam_ip_table_form_edit', 'Desc', 2147483647, 1, 5),
+('bx_antispam_ip_table_form_edit', 'bx_antispam_ip_table_submit', 2147483647, 1, 6),
+('bx_antispam_ip_table_form_edit', 'close', 2147483647, 1, 7),
+('bx_antispam_ip_table_form_edit', 'buttons', 2147483647, 1, 8);
+
+-- alerts
+
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES 
+('bx_antispam', 'BxAntispamAlertsResponse', 'modules/boonex/antispam/classes/BxAntispamAlertsResponse.php', '');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('account', 'check_login', @iHandler),
+('account', 'check_join', @iHandler),
+('system', 'check_spam', @iHandler),
+('system', 'check_spam_url', @iHandler),
+('system', 'form_check', @iHandler),
+('system', 'form_submitted', @iHandler),
+('comment', 'added', @iHandler),
+('comment', 'edited', @iHandler);
+
+-- email templates
+
+INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `Body`) VALUES
+('bx_antispam', '_bx_antispam_spam_report_name', 'bx_antispam_spam_report', '_bx_antispam_spam_report_subject', '_bx_antispam_spam_report_body'),
+('bx_antispam', '_bx_antispam_toxicity_blocked_report_name', 'bx_antispam_toxicity_blocked_report', '_bx_antispam_toxicity_blocked_report_subject', '_bx_antispam_toxicity_blocked_report_body'),
+('bx_antispam', '_bx_antispam_toxicity_posted_report_name', 'bx_antispam_toxicity_posted_report', '_bx_antispam_toxicity_posted_report_subject', '_bx_antispam_toxicity_posted_report_body'),
+('bx_antispam', '_bx_antispam_et_lasso_moderation_report_name', 'bx_antispam_lasso_moderation_report', '_bx_antispam_et_lasso_moderation_report_subject', '_bx_antispam_et_lasso_moderation_report_body');
+
+-- cron
+
+INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
+('bx_antispam_pruning', '0 0 * * *', 'BxAntispamCronPruning', 'modules/boonex/antispam/classes/BxAntispamCronPruning.php', ''),
+('bx_antispam_update_disposable_email_domains', '0 0 */14 * *', 'BxAntispamCronUpdateDisposableEmailDomains', 'modules/boonex/antispam/classes/BxAntispamCronUpdateDisposableEmailDomains.php', '');
+

+ 27 - 0
modules/boonex/antispam/updates/15.0.0_15.0.1/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.1"/>
+<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.1"/>
+<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.1"/>
+<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/classes/updates/15.0.2_15.0.3/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' => '15.0.2',
+    'version_to' => '15.0.3',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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_15.0.2_15.0.3/',
+    'home_uri' => 'classes_update_1502_1503',
+
+    '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/15.0.2_15.0.3/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);
+    }
+}

+ 33 - 0
modules/boonex/classes/updates/15.0.2_15.0.3/source/classes/BxClssFormsEntryHelper.php

@@ -0,0 +1,33 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Classes Classes
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Entry forms helper functions
+ */
+class BxClssFormsEntryHelper extends BxBaseModTextFormsEntryHelper
+{
+    public function __construct($oModule)
+    {
+        parent::__construct($oModule);
+    }
+
+    protected function redirectAfterDelete($aContentInfo, $sUrl = '')
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if ($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']] < 0 && ($oProfileContext = BxDolProfile::getInstance(abs($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]))) !== false)
+            $sUrl = $oProfileContext->getUrl();
+
+        return parent::redirectAfterDelete($aContentInfo, $sUrl);
+    }
+}
+
+/** @} */

+ 152 - 0
modules/boonex/classes/updates/15.0.2_15.0.3/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' => '15.0.3',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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'
+    ),
+
+);
+
+/** @} */

+ 58 - 0
modules/boonex/courses/updates/15.0.1_15.0.2/install/config.php

@@ -0,0 +1,58 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'title' => 'Courses',
+    'version_from' => '15.0.1',
+    'version_to' => '15.0.2',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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_15.0.1_15.0.2/',
+    'home_uri' => 'courses_update_1501_1502',
+
+    'module_dir' => 'boonex/courses/',
+    'module_uri' => 'courses',
+
+    'db_prefix' => 'bx_courses_',
+    'class_prefix' => 'BxCourses',
+
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+        'trigger_group_view_submenu',
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Courses',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/courses/updates/15.0.1_15.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 BxCoursesUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 152 - 0
modules/boonex/courses/updates/15.0.1_15.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    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' => '15.0.2',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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',
+    ),
+);
+
+/** @} */

+ 56 - 0
modules/boonex/courses/updates/15.0.1_15.0.2/source/template/unit_personal.html

@@ -0,0 +1,56 @@
+<div class="__class__ p-2 box-border">
+    <div class="bx-base-pofile-unit-cnt bx-def-box bx-def-box-round-corners bx-def-color-bg-box relative h-full overflow-hidden">
+        <div class="absolute z-0 w-full h-40 bg-gradient-to-b from-green-400 to-green-500"></div>
+        <a class="bx-base-pofile-unit-cover relative flex items-center justify-start w-full h-40 bg-no-repeat bg-center bg-cover hover:no-underline" style="background-image:url(__cover_url__); __cover_settings__" href="__content_url__" onclick="__content_click__" aria-hidden="true" tabindex="-1">
+            <bx_if:show_thumbnail>
+                <div class="bx-def-unit-img bx-def-__size__-size __class_size__ relative ml-5">
+                    <bx_if:show_thumb_image>
+                        <img alt="" class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ ring-4 ring-white dark:ring-gray-900 rounded-md box-border" src="__thumb_url__" />
+                    </bx_if:show_thumb_image>
+                    <bx_if:show_thumb_letter>
+                        <p class="bx-base-pofile-unit-thumb bx-def-__size__ bx-def-__size__-size __class_size__ flex items-center justify-center m-0 text-white font-bold ring-4 ring-white dark:ring-gray-900 rounded-md box-border" style="background-color:rgba(__color__)">__letter__</p>
+                    </bx_if:show_thumb_letter>
+                    <bx_if:show_online>
+                        <div class="bx-base-pofile-unit-online"></div>
+                    </bx_if:show_online>
+                </div>
+            </bx_if:show_thumbnail>
+        </a>
+        <div class="bx-base-pofile-unit-info p-4 text-left">
+            <bx_if:show_pass_progress>
+                <div class="bx-courses-progress mb-2">
+                    <div class="bx-courses-progress-bar border rounded-full">
+                        <div class="h-1 bg-gray-300 rounded-full" style="width:__progress__%"></div>
+                    </div>
+                    <div class="bx-courses-progress-percent text-xs"><bx_text:_bx_courses_txt_passed />: __progress__%</div>
+                </div>
+            </bx_if:show_pass_progress>
+            <div class="bx-base-pofile-unit-title">
+                <div class="bx-base-put-title truncate">
+                    <a href="__content_url__" onclick="__content_click__" title="__title_attr__" class="hover:underline text-gray-700 dark:text-gray-200 hover:text-gray-900 dark:hover:text-white text-xl font-semibold">__title__</a>__badges__
+                </div>
+                <div class="bx-base-put-addon text-sm text-gray-500 dark:text-gray-400">__addon__</div>
+            </div>
+            <bx_if:meta>
+                <div class="bx-base-general-unit-meta flex flex-col items-stretch justify-center -mx-1 mt-2">__meta__</div>
+            </bx_if:meta>
+            <bx_if:show_pass_stats>
+                <div class="bx-courses-stats flex flex-row flex-wrap items-center -m-px pt-2">
+                    <div class="bx-courses-counters flex flex-row flex-wrap flex-1">
+                        <bx_repeat:counters>
+                            <div class="bx-courses-counter m-px px-4 bg-gray-300 rounded-full flex flex-nowrap">
+                                <div class="capitalize mr-1">__cn_title__:</div>
+                                <div class="font-bold">__cn_progress__</div>
+                            </div>
+                        </bx_repeat:counters>
+                    </div>
+                    <bx_if:show_pass>
+                        <div class="bx-courses-pass m-px">
+                            <a class="bx-btn bx-btn-small" href="__pass_link__">__pass_title__</a>
+                        </div>
+                    </bx_if:show_pass>
+                </div>
+            </bx_if:show_pass_stats>
+        </div>
+    </div>
+</div>

+ 51 - 0
modules/boonex/credits/updates/15.0.2_15.0.3/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' => 'Credits',
+    'version_from' => '15.0.2',
+    'version_to' => '15.0.3',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/credits/updates/update_15.0.2_15.0.3/',
+    'home_uri' => 'credits_update_1502_1503',
+
+    'module_dir' => 'boonex/credits/',
+    'module_uri' => 'credits',
+
+    'db_prefix' => 'bx_credits_',
+    'class_prefix' => 'BxCredits',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Credits',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/credits/updates/15.0.2_15.0.3/install/installer.php

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

+ 341 - 0
modules/boonex/credits/updates/15.0.2_15.0.3/source/classes/BxCreditsConfig.php

@@ -0,0 +1,341 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCreditsConfig extends BxBaseModGeneralConfig
+{
+    protected $_iAuthor;
+    protected $_aCurrency;
+
+    protected $_sCheckoutSessionKey;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->CNF = array (
+
+            // module icon
+            'ICON' => 'copyright',
+
+            // database tables
+            'TABLE_BUNDLES' => $aModule['db_prefix'] . 'bundles',
+            'TABLE_ORDERS' => $aModule['db_prefix'] . 'orders',
+            'TABLE_ORDERS_DELETED' => $aModule['db_prefix'] . 'orders_deleted',
+            'TABLE_PROFILES' => $aModule['db_prefix'] . 'profiles',
+            'TABLE_HISTORY' => $aModule['db_prefix'] . 'history',
+            'TABLE_WITHDRAWALS' => $aModule['db_prefix'] . 'withdrawals',
+
+            // database fields
+            'FIELD_ID' => 'id',
+            'FIELD_AUTHOR' => '',
+            'FIELD_ADDED' => 'added',
+            'FIELD_CHANGED' => '',
+            'FIELD_NAME' => 'name',
+            'FIELD_TITLE' => 'title',
+            'FIELD_DESCRIPTION' => 'description',
+            'FIELD_AMOUNT' => 'amount',
+            'FIELD_BONUS' => 'bonus',
+            'FIELD_PRICE' => 'price',
+            'FIELD_STATUS' => 'active',
+
+            'FIELD_C_CLEARED' => 'cleared',
+            'FIELD_C_AMOUNT' => 'amount',
+            'FIELD_C_MESSAGE' => 'message',
+
+            'FIELD_H_ID' => 'id',
+            'FIELD_H_DIRECTION' => 'direction',
+            'FIELD_H_DATE' => 'date',
+            'FIELD_H_CLEARED' => 'cleared',
+
+            // page URIs
+            'URI_HOME' => 'credits-home',
+            'URL_HOME' => 'page.php?i=credits-home',
+            'URL_CHECKOUT' => 'page.php?i=credits-checkout',
+            'URL_ORDERS_COMMON' => 'page.php?i=credits-orders',
+            'URL_ORDERS_ADMINISTRATION' => 'page.php?i=credits-orders-administration',
+            'URL_HISTORY_COMMON' => 'page.php?i=credits-history-common',
+            'URL_HISTORY_ADMINISTRATION' => 'page.php?i=credits-history-administration',
+            'URL_WITHDRAWALS_COMMON' => 'page.php?i=credits-withdrawals-common',
+            'URL_WITHDRAWALS_ADMINISTRATION' => 'page.php?i=credits-withdrawals-administration',
+
+            // some params
+            'PARAM_WITHDRAW' => 'bx_credits_enable_withdraw',
+            'PARAM_WITHDRAW_EMAIL' => 'bx_credits_withdraw_email',
+            'PARAM_WITHDRAW_CLEARING' => 'bx_credits_withdraw_clearing',
+            'PARAM_WITHDRAW_MINIMUM' => 'bx_credits_withdraw_minimum',
+            'PARAM_WITHDRAW_REMAINING' => 'bx_credits_withdraw_remaining',
+            'PARAM_PRECISION' => 'bx_credits_precision',
+            'PARAM_CR_USE' => 'bx_credits_conversion_rate_use',
+            'PARAM_CR_WITHDRAW' => 'bx_credits_conversion_rate_withdraw',
+            'PARAM_PROVIDER_NAME' => 'credits',
+            'PARAM_PROVIDER_ENABLE' => 'bx_credits_enable_provider',
+            'PARAM_CODE' => 'bx_credits_code',
+            'PARAM_ICON' => 'bx_credits_icon',
+
+            // objects
+            'OBJECT_FORM_CREDIT' => 'bx_credits_credit',
+            'OBJECT_FORM_CREDIT_DISPLAY_GRANT' => 'bx_credits_credit_grant',
+            'OBJECT_FORM_CREDIT_DISPLAY_SEND' => 'bx_credits_credit_send',
+            'OBJECT_FORM_CREDIT_DISPLAY_WITHDRAW_REQUEST' => 'bx_credits_credit_withdraw_request',
+            'OBJECT_FORM_BUNDLE' => 'bx_credits_bundle',
+            'OBJECT_FORM_BUNDLE_DISPLAY_ADD' => 'bx_credits_bundle_add',
+            'OBJECT_FORM_BUNDLE_DISPLAY_EDIT' => 'bx_credits_bundle_edit',
+            'OBJECT_FORM_PROFILE' => 'bx_credits_profile',
+            'OBJECT_FORM_PROFILE_DISPLAY_EDIT' => 'bx_credits_profile_edit',
+            'OBJECT_MENU_SUBMENU' => 'bx_credits_submenu', 
+            'OBJECT_MENU_MANAGE_SUBMENU' => 'bx_credits_manage_submenu',
+            'OBJECT_GRID_BUNDLES' => 'bx_credits_bundles',
+            'OBJECT_GRID_ORDERS_ADMINISTRATION' => 'bx_credits_orders_administration',
+            'OBJECT_GRID_ORDERS_COMMON' => 'bx_credits_orders_common',
+            'OBJECT_GRID_HISTORY_ADMINISTRATION' => 'bx_credits_history_administration',
+            'OBJECT_GRID_HISTORY_COMMON' => 'bx_credits_history_common',
+            'OBJECT_GRID_WITHDRAWALS_ADMINISTRATION' => 'bx_credits_withdrawals_administration',
+            'OBJECT_GRID_WITHDRAWALS_COMMON' => 'bx_credits_withdrawals_common',
+            'OBJECT_GRID_PROFILES_ADMINISTRATION' => 'bx_credits_profiles_administration',
+
+            // email templates
+            'ETEMPLATE_GRANTED' => 'bx_credits_granted',
+            'ETEMPLATE_RECEIVED' => 'bx_credits_received',
+            'ETEMPLATE_PURCHASED' => 'bx_credits_purchased',
+            'ETEMPLATE_IN' => 'bx_credits_in',
+            'ETEMPLATE_OUT' => 'bx_credits_out',
+            'ETEMPLATE_WITHDRAW_REQUESTED' => 'bx_credits_withdraw_requested',
+            'ETEMPLATE_WITHDRAW_CANCELED' => 'bx_credits_withdraw_canceled',
+            'ETEMPLATE_WITHDRAW_SENT' => 'bx_credits_withdraw_sent',
+
+            // some language keys
+            'T' => array (
+                'txt_sample_single' => '_bx_credits_txt_sample_single',
+                'grant_popup' => '_bx_credits_grid_popup_title_htr_grant',
+                'send_popup' => '_bx_credits_grid_popup_title_htr_send',
+                'withdraw_request_popup' => '_bx_credits_grid_popup_title_wdw_withdraw_request',
+            ),
+        );
+
+        $this->_aJsClasses = array_merge($this->_aJsClasses, array(
+            'studio' => 'BxCreditsStudio',
+            'checkout' => 'BxCreditsCheckout',
+            'subscribe' => 'BxCreditsSubscribe',
+            'withdraw' => 'BxCreditsWithdraw',
+        ));
+
+        $this->_aJsObjects = array_merge($this->_aJsObjects, array(
+            'studio' => 'oBxCreditsStudio',
+            'checkout' => 'oBxCreditsCheckout',
+            'subscribe' => 'oBxCreditsSubscribe',
+            'withdraw' => 'oBxCreditsWithdraw',
+        ));
+
+        $sPrefix = str_replace('_', '-', $this->_sName);
+        $this->_aHtmlIds = array(
+            'add_bundle_popup' =>  $sPrefix . '-add-bundle-popup',
+            'edit_bundle_popup' =>  $sPrefix . '-edit-bundle-popup',
+
+            'send_popup' =>  $sPrefix . '-send-popup',
+            'grant_popup' =>  $sPrefix . '-grant-popup',
+            'withdraw_request_popup' =>  $sPrefix . '-withdraw-request-popup',
+            'withdraw_confirm_popup' =>  $sPrefix . '-withdraw-confirm-popup',
+            'withdraw_field_rate' => $sPrefix . '-wff-rate',
+            'withdraw_field_amount' => $sPrefix . '-wff-amount',
+            'withdraw_field_result' => $sPrefix . '-wff-result',
+
+            'edit_profile_popup' =>  $sPrefix . '-edit-profile-popup',
+
+            'subscribe_popup' =>  $sPrefix . '-subscribe-popup',
+        );
+
+        $this->_aPrefixes = array(
+            'style' => 'bx-credits',
+        );
+
+        $oPayments = BxDolPayments::getInstance();
+        $this->_iAuthor = (int)$oPayments->getOption('site_admin');
+        $this->_aCurrency = array(
+            'code' => $oPayments->getOption('default_currency_code'),
+            'sign' => $oPayments->getOption('default_currency_sign')
+        );
+        
+        $this->_sCheckoutSessionKey = $this->_sName . '_checkout';
+    }
+
+    public function getHtmlIds($sKey = '')
+    {
+        if(empty($sKey))
+            return $this->_aHtmlIds;
+
+        return isset($this->_aHtmlIds[$sKey]) ? $this->_aHtmlIds[$sKey] : '';
+    }
+
+    public function getAuthor()
+    {
+    	return $this->_iAuthor;
+    }
+
+    public function getCurrency()
+    {
+    	return $this->_aCurrency;
+    }
+
+    public function isWithdraw()
+    {
+        return getParam($this->CNF['PARAM_WITHDRAW']) == 'on';
+    }
+
+    public function getWithdrawEmail()
+    {
+        $sEmail = getParam($this->CNF['PARAM_WITHDRAW_EMAIL']);
+        if(empty($sEmail))
+            $sEmail = getParam('site_email');
+
+        return $sEmail;
+    }
+
+    public function getWithdrawClearing()
+    {
+        return (int)getParam($this->CNF['PARAM_WITHDRAW_CLEARING']);
+    }
+
+    public function getWithdrawMinimum()
+    {
+        return (int)getParam($this->CNF['PARAM_WITHDRAW_MINIMUM']);
+    }
+
+    public function getWithdrawRemaining()
+    {
+        return (int)getParam($this->CNF['PARAM_WITHDRAW_REMAINING']);
+    }
+
+    public function getTransferTypesForClearing()
+    {
+        return [
+            BX_CREDITS_TRANSFER_TYPE_PURCHASE,
+            BX_CREDITS_TRANSFER_TYPE_CHECKOUT,
+            BX_CREDITS_TRANSFER_TYPE_GRANT,
+            BX_CREDITS_TRANSFER_TYPE_SEND,
+            BX_CREDITS_TRANSFER_TYPE_SERVICE
+        ];
+    }
+
+    public function getTransferTypesForSpending()
+    {
+        return [
+            BX_CREDITS_TRANSFER_TYPE_CHECKOUT,
+            BX_CREDITS_TRANSFER_TYPE_SEND,
+            BX_CREDITS_TRANSFER_TYPE_WITHDRAW,
+            BX_CREDITS_TRANSFER_TYPE_SERVICE
+        ];
+    }
+
+    public function getPrecision()
+    {
+        return (int)getParam($this->CNF['PARAM_PRECISION']);
+    }
+
+    public function getConversionRateUse()
+    {
+        return $this->_getConversionRate('use');
+    }
+
+    public function getConversionRateWithdraw()
+    {
+        return $this->_getConversionRate('withdraw');
+    }
+
+    public function getCheckoutUrl()
+    {
+        return bx_absolute_url(BxDolPermalinks::getInstance()->permalink($this->CNF['URL_CHECKOUT']));
+    }
+
+    public function getBundleUrl($aBundle)
+    {
+        return bx_absolute_url(BxDolPermalinks::getInstance()->permalink($this->CNF['URL_HOME']));
+    }
+
+    public function getBundleName($sName)
+    {
+        return uriGenerate($sName, $this->CNF['TABLE_BUNDLES'], $this->CNF['FIELD_NAME'], ['empty' => 'cat', 'divider' => '_', 'lowercase' => false]);
+    }
+
+    public function getBundleDescription($aBundle)
+    {
+        if(($sDsc = _t($aBundle[$this->CNF['FIELD_DESCRIPTION']]) ?: false))
+            return $sDsc;
+
+        if(($iBns = (int)$aBundle[$this->CNF['FIELD_BONUS']] ?: false))
+            return _t('_bx_credits_txt_n_plus_m_for_free', $aBundle[$this->CNF['FIELD_AMOUNT']], $iBns);
+
+        return $aBundle[$this->CNF['FIELD_AMOUNT']];
+    }
+
+    public function getCheckoutData()
+    {
+        return BxDolSession::getInstance()->getUnsetValue($this->_sCheckoutSessionKey);
+    }
+
+    public function setCheckoutData($aData)
+    {
+        return BxDolSession::getInstance()->setValue($this->_sCheckoutSessionKey, $aData);
+    }
+
+    public function getOrder($iLength = 16)
+    {
+        return strtoupper(genRndPwd($iLength, false));
+    }
+
+    public function constructCheckoutCustomData()
+    {
+        $aParams = func_get_args();
+        return urlencode(base64_encode(implode('|', $aParams)));
+    }
+
+    public function deconstructCheckoutCustomData($sData)
+    {
+        return explode('|', base64_decode(urldecode($sData)));
+    }
+
+    /**
+     * Convert Credits to Money
+     */
+    public function convertC2M($fCredits, $fRate = false, $iPrecision = false)
+    {
+        if($fRate === false)
+            $fRate = $this->getConversionRateUse();
+        if($iPrecision === false)
+            $iPrecision = $this->getPrecision();
+
+        return round((float)$fCredits * $fRate, $iPrecision);
+    }
+
+    /**
+     * Convert Money to Credits
+     */
+    public function convertM2C($fMoney, $fRate = false, $iPrecision = false)
+    {
+        if($fRate === false)
+            $fRate = $this->getConversionRateUse();
+        if($iPrecision === false)
+            $iPrecision = $this->getPrecision();
+
+        return round((float)$fMoney / $fRate, $iPrecision);
+    }
+
+    /*
+     * Internal methods
+     */
+    protected function _getConversionRate($sType)
+    {
+        $fRate = (float)getParam($this->CNF['PARAM_CR_' . strtoupper($sType)]);
+
+        return $fRate > 0 && $fRate <= 1 ? $fRate : 1;
+    }
+}
+
+/** @} */

+ 1517 - 0
modules/boonex/credits/updates/15.0.2_15.0.3/source/classes/BxCreditsModule.php

@@ -0,0 +1,1517 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_CREDITS_TRANSFER_TYPE_PURCHASE', 'purchase');
+define('BX_CREDITS_TRANSFER_TYPE_CHECKOUT', 'checkout');
+define('BX_CREDITS_TRANSFER_TYPE_CANCELLATION', 'cancellation');
+define('BX_CREDITS_TRANSFER_TYPE_GRANT', 'grant');
+define('BX_CREDITS_TRANSFER_TYPE_SEND', 'send');
+define('BX_CREDITS_TRANSFER_TYPE_WITHDRAW', 'withdraw');
+define('BX_CREDITS_TRANSFER_TYPE_SERVICE', 'service');
+
+define('BX_CREDITS_DIRECTION_IN', 'in');
+define('BX_CREDITS_DIRECTION_OUT', 'out');
+
+define('BX_CREDITS_ORDER_TYPE_SINGLE', 'single'); //--- one-time payment
+define('BX_CREDITS_ORDER_TYPE_RECURRING', 'recurring'); //--- recurring payment (subscription)
+
+define('BX_CREDITS_WITHDRAWAL_STATUS_REQUESTED', 'requested');
+define('BX_CREDITS_WITHDRAWAL_STATUS_CANCELED', 'canceled');
+define('BX_CREDITS_WITHDRAWAL_STATUS_CONFIRMED', 'confirmed');
+
+class BxCreditsModule extends BxBaseModGeneralModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    public function actionGetProfiles()
+    {
+        $sTerm = bx_get('term');
+
+        $a = BxDolService::call('system', 'profiles_search', array($sTerm), 'TemplServiceProfiles');
+
+        echoJson($a);
+    }
+
+    public function actionCheckBundleName()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$sName = bx_process_input(bx_get('name'));
+    	if(empty($sName))
+            return echoJson(array());
+
+        $sResult = '';
+
+        $iId = (int)bx_get('id');
+        if(!empty($iId)) {
+            $aBundle = $this->_oDb->getBundle(array('type' => 'id', 'id' => $iId)); 
+            if(strcmp($sName, $aBundle[$CNF['FIELD_NAME']]) == 0) 
+                $sResult = $sName;
+        }
+
+    	echoJson(array(
+            'name' => !empty($sResult) ? $sResult : $this->_oConfig->getBundleName($sName)
+    	));
+    }
+
+    public function actionCheckout()
+    {
+        return echoJson($this->serviceCheckout());
+    }
+
+    public function actionSubscribe()
+    {
+        return echoJson($this->serviceSubscribe());
+    }
+
+    public function serviceGetSafeServices()
+    {
+        return [
+            'GetBlockBundles' => '',
+            'GetBlockOrders' => '',
+            'GetBlockHistory' => '',
+            'AddToCart' => '',
+            'Checkout' => '',
+            'Subscribe' => ''
+        ];
+    }
+
+    public function serviceCheckout()
+    {
+        $iBuyerId = bx_get_logged_profile_id();
+        if(!$iBuyerId)
+            return ['code' => 1, 'msg' => _t('_bx_credits_err_unknown_buyer')];
+
+        $sErrIncorrectData = _t('_bx_credits_err_incorrect_data');
+
+        $aData = $this->_oConfig->getCheckoutData();
+        if(empty($aData) || !is_array($aData))
+            return ['code' => 2, 'msg' => $sErrIncorrectData];
+
+        $fConversion = $this->_oConfig->getConversionRateUse();
+        $iPrecision = $this->_oConfig->getPrecision();
+
+        $iSellerId = (int)$aData['seller'];
+        $fAmount = (float)$aData['amount'];
+        $aCustomData = $this->_oConfig->deconstructCheckoutCustomData($aData['custom']);
+
+        $oPayments = BxDolPayments::getInstance();
+        $aPpOrders = $oPayments->getPendingOrdersInfo(['id' => (int)$aCustomData[1]]);
+        if(!empty($aPpOrders) && is_array($aPpOrders)) {
+            $aPpOrder = reset($aPpOrders);
+            if(empty($aPpOrder) || !is_array($aPpOrder) || (int)$aPpOrder['seller_id'] != $iSellerId)
+                return ['code' => 3, 'msg' => $sErrIncorrectData];
+
+            $fAmountM = (float)$aPpOrder['amount'];
+            $sCurrencyDefault = $oPayments->getCurrencyCode();
+            if(strcmp($aPpOrder['currency'], $sCurrencyDefault) != 0)
+                $fAmountM = $oPayments->convert ($fAmountM, $aPpOrder['currency'], $sCurrencyDefault);
+
+            if($this->_oConfig->convertM2C($fAmountM, $fConversion, $iPrecision) != $fAmount)
+                return ['code' => 3, 'msg' => $sErrIncorrectData];
+        }
+
+        $fBalance = (float)$this->_oDb->getProfile(['type' => 'balance', 'id' => $iBuyerId]);
+        if($fAmount > $fBalance)
+            return ['code' => 4, 'msg' => _t('_bx_credits_err_low_balance')];
+
+        $sOrder = $this->_oConfig->getOrder();
+        $sInfo = '_bx_credits_txt_history_info_checkout';
+        $sData = serialize([
+            'conversion' => $fConversion,
+            'precision' => $iPrecision
+        ]);
+
+        $this->updateProfileBalance($iBuyerId, $iSellerId, -$fAmount, BX_CREDITS_TRANSFER_TYPE_CHECKOUT, $sOrder, $sInfo, $sData);
+        $this->updateProfileBalance($iSellerId, $iBuyerId, $fAmount, BX_CREDITS_TRANSFER_TYPE_CHECKOUT, $sOrder, $sInfo, $sData);
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-checkout 'bx_credits', 'checkout' - hook after checkout with creadits was performed
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `checkout`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `seller` - [int] seller profile id
+         *      - `buyer` - [int] buyer profile id
+         *      - `amount` - [float] amount was paid
+         *      - `order` - [string] order number for processed checkout action
+         * @hook @ref hook-bx_credits-checkout
+         */
+        bx_alert($this->getName(), 'checkout', 0, false, [
+            'seller' => $iSellerId,
+            'buyer' => $iBuyerId,
+            'amount' => $fAmount, 
+            'order' => $sOrder
+        ]);
+
+        $aParams = [
+            'o' => $sOrder, 
+            'c' => $aData['custom']
+        ];
+
+        return $this->_bIsApi ? [
+            'code' => 0, 
+            'request_url' => $aData['return_data_url'] . '&params[]=' . json_encode($aParams)
+        ] : [
+            'code' => 0,
+            'redirect' => bx_append_url_params($aData['return_data_url'], $aParams)
+        ];
+    }
+
+    public function serviceSubscribe()
+    {
+        $iBuyerId = bx_get_logged_profile_id();
+        if(!$iBuyerId)
+            return ['code' => 1, 'msg' => _t('_bx_credits_err_unknown_buyer')];
+
+        $aData = $this->_oConfig->getCheckoutData();
+        if(empty($aData) || !is_array($aData))
+            return ['code' => 2, 'msg' => _t('_bx_credits_err_incorrect_data')];
+
+        $iSellerId = (int)$aData['seller'];
+        $fAmount = (float)$aData['amount'];
+
+        $fBalance = (float)$this->_oDb->getProfile(array('type' => 'balance', 'id' => $iBuyerId));
+        if($fAmount > $fBalance)
+            return ['code' => 3, 'msg' => _t('_bx_credits_err_low_balance')];
+
+        $sUnique = $this->_oConfig->getOrder(9);
+        $sCustomer = 'bx_cus_' . $sUnique;
+        $sSubscription = 'bx_sub_' . $sUnique;
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-subscribe 'bx_credits', 'subscribe' - hook after subscription was registered
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `subscribe`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `seller` - [int] seller profile id
+         *      - `buyer` - [int] buyer profile id
+         *      - `amount` - [float] amount was paid
+         *      - `trial` - [boolean] if trial period is available or not
+         *      - `customer` - [string] unique customer id for newly registered subscription
+         *      - `subscription` - [string] unique subscription id
+         * @hook @ref hook-bx_credits-subscribe
+         */
+        bx_alert($this->getName(), 'subscribe', 0, false, [
+            'seller' => $iSellerId,
+            'buyer' => $iBuyerId,
+            'amount' => $fAmount, 
+            'trial' => $aData['trial'],
+            'customer' => $sCustomer,
+            'subscription' => $sSubscription
+        ]);
+
+        $aParams = [
+            'cs' => $sCustomer, 
+            'sb' => $sSubscription,
+            'tr' => $aData['trial'],
+            'c' => $aData['custom']
+        ];
+
+        return $this->_bIsApi ? [
+            'code' => 0,
+            'request_url' => $aData['return_data_url'] . '&params[]=' . json_encode($aParams)
+        ] : [
+            'code' => 0,
+            'redirect' => bx_append_url_params($aData['return_data_url'], $aParams)
+        ];
+    }
+
+    public function serviceGetCheckoutUrl()
+    {
+        return $this->_oConfig->getCheckoutUrl();
+    }
+
+    public function serviceConvertMoneyToCredits($fMoney, $fRate = false, $iPrecision = false)
+    {
+        return $this->_oConfig->convertM2C($fMoney, $fRate, $iPrecision);
+    }
+
+    public function serviceConvertCreditsToMoney($fCredits, $fRate = false, $iPrecision = false)
+    {
+        return $this->_oConfig->convertC2M($fCredits, $fRate, $iPrecision);
+    }
+
+    public function serviceValidateCheckout($iSeller, $iBuyer, $fAmount, $sOrder)
+    {
+        $fAmount = (float)$fAmount;
+
+        $aOut = $this->_oDb->getHistory(array('type' => 'row_by', 'by' => array('first_pid' => $iBuyer, 'direction' => BX_CREDITS_DIRECTION_OUT, 'order' => $sOrder)));
+        if(empty($aOut) || !is_array($aOut) || (int)$aOut['second_pid'] != (int)$iSeller)
+            return false;
+        
+        $aDataOut = unserialize($aOut['data']);
+        if($this->_oConfig->convertM2C($fAmount, $aDataOut['conversion'], $aDataOut['precision']) != (float)$aOut['amount'])
+            return false;
+
+        $aIn = $this->_oDb->getHistory(array('type' => 'row_by', 'by' => array('first_pid' => $iSeller, 'direction' => BX_CREDITS_DIRECTION_IN, 'order' => $sOrder)));
+        if(empty($aIn) || !is_array($aIn) || (int)$aIn['second_pid'] != (int)$iBuyer)
+            return false;
+
+        $aDataIn = unserialize($aIn['data']);
+        if($this->_oConfig->convertM2C($fAmount, $aDataIn['conversion'], $aDataIn['precision']) != (float)$aIn['amount'])
+            return false;
+
+        return true;
+    }
+
+    public function serviceGetBlockCheckout()
+    {
+        $oBuyer = BxDolProfile::getInstance();
+        if(!$oBuyer)
+            return ($sMsg = _t('_bx_credits_err_unknown_buyer')) && $this->_bIsApi ? [bx_api_get_msg($sMsg)] : MsgBox($sMsg);
+        
+        $iSeller = (int)bx_get('seller');
+        $oSeller = BxDolProfile::getInstance($iSeller);
+        if(!$oSeller)
+            return ($sMsg = _t('_bx_credits_err_unknown_seller')) && $this->_bIsApi ? [bx_api_get_msg($sMsg)] : MsgBox($sMsg);
+
+        $fAmountM = $fAmountMInDef = (float)bx_get('amount');
+
+        $oPayments = BxDolPayments::getInstance();
+        $sCurrency = bx_process_input(bx_get('currency_code'));
+        $sCurrencyDefault = $oPayments->getCurrencyCode();
+        if(strcmp($sCurrency, $sCurrencyDefault) != 0)
+            $fAmountMInDef = $oPayments->convert($fAmountM, $sCurrency, $sCurrencyDefault);
+
+        $fAmountC = $this->_oConfig->convertM2C($fAmountMInDef);
+
+        $aData = [
+            'seller' => $iSeller,
+            'currency' => [
+                'code' => $sCurrency,
+                'sign' => bx_process_input(bx_get('currency_sign')),
+            ],
+            'amountm' => $fAmountM,
+            'amountc' => $fAmountC
+        ];
+
+        $iItemsCount = (int)bx_get('items_count');
+        for($i = 0; $i < $iItemsCount; $i++) {
+            $aData['items'][] = [
+                'title' => bx_process_input(bx_get('item_title_' . $i)),
+                'quantity' => (int)bx_get('item_quantity_' . $i)
+            ];
+        }
+
+        $this->_oConfig->setCheckoutData([
+            'seller' => $iSeller,
+            'amount' => $fAmountC,
+            'custom' => bx_process_input(bx_get('custom')),
+            'return_data_url' => bx_process_input(bx_get('return_data_url')),
+        ]);
+
+        $mixedResult = $this->_oTemplate->getBlockCheckout($oBuyer, $oSeller, $aData);
+        return $this->_bIsApi ? [bx_api_get_block('credits_checkout', $mixedResult)] : $mixedResult;
+    }
+
+    public function serviceGetPopupSubscribe($aData)
+    {
+        $oBuyer = BxDolProfile::getInstance();
+        if(!$oBuyer)
+            return ['msg' => _t('_bx_credits_err_unknown_buyer')];
+
+        $iSeller = (int)$aData['seller'];
+        $oSeller = BxDolProfile::getInstance($iSeller);
+        if(!$oSeller)
+            return ['msg' => _t('_bx_credits_err_unknown_seller')];
+
+        $fAmountM = $fAmountMInDef = (float)$aData['amount'];
+
+        $oPayments = BxDolPayments::getInstance();
+        $sCurrency = $aData['currency_code'];
+        $sCurrencyDefault = $oPayments->getCurrencyCode();
+        if(strcmp($sCurrency, $sCurrencyDefault) != 0)
+            $fAmountMInDef = $oPayments->convert($fAmountM, $sCurrency, $sCurrencyDefault);
+
+        $fAmountC = $this->_oConfig->convertM2C($fAmountMInDef);
+
+        $aData = array_merge($aData, [
+            'currency' => [
+                'code' => $sCurrency,
+                'sign' => $aData['currency_sign'],
+            ],
+            'amountm' => $fAmountM,
+            'amountc' => $fAmountC
+        ]);
+
+        $this->_oConfig->setCheckoutData([
+            'seller' => $iSeller,
+            'amount' => $fAmountC,
+            'trial' => (int)$aData['trial'] > 0,
+            'custom' => bx_process_input($aData['custom']),
+            'return_data_url' => bx_process_input($aData['return_data_url']),
+        ]);
+        return $this->_oTemplate->getPopupSubscribe($oBuyer, $oSeller, $aData);
+    }
+
+    public function serviceGetMenuItemAddonAmount()
+    {
+        $iProfileId = bx_get_logged_profile_id();
+        if(!$iProfileId)
+            return '';
+
+        return array(
+            'unit' => $this->_oTemplate->getUnit(), 
+            'value' => $this->convertC2S($this->_oDb->getProfile(array('type' => 'balance', 'id' => $iProfileId)), false)
+        );
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-get_block_bundles get_block_bundles
+     * 
+     * @code bx_srv('bx_credits', 'get_block_bundles'); @endcode
+     * 
+     * Get page block with bundles
+     * 
+     * @see BxCreditsModule::serviceGetBlockBundles
+     */
+    /** 
+     * @ref bx_credits-get_block_bundles "get_block_bundles"
+     */
+    public function serviceGetBlockBundles()
+    {
+        $mixedResult = $this->_oTemplate->getBlockBundles();
+
+        if($this->_bIsApi)
+            return [
+                bx_api_get_block('bundles', $mixedResult)
+            ];
+
+        return $mixedResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-get_block_orders_note get_block_orders_note
+     * 
+     * @code bx_srv('bx_credits', 'get_block_orders_note', [...]); @endcode
+     * 
+     * Get page block with a notice for orders usage.
+     *
+     * @return HTML string with block content to display on the site.
+     * 
+     * @see BxCreditsModule::serviceBlockLicensesNote
+     */
+    /** 
+     * @ref bx_credits-get_block_orders_note "get_block_orders_note"
+     */
+    public function serviceGetBlockOrdersNote()
+    {
+        return MsgBox(_t('_bx_credits_page_block_content_orders_common_note'));
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-get_block_orders get_block_orders
+     * 
+     * @code bx_srv('bx_credits', 'get_block_orders', [...]); @endcode
+     * 
+     * Get page block with a list of orders 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 BxCreditsModule::serviceGetBlockOrders
+     */
+    /** 
+     * @ref bx_credits-get_block_orders "get_block_orders"
+     */
+    public function serviceGetBlockOrders($sType = 'common') 
+    {
+        return $this->_getBlockOrders($sType);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-get_block_history get_block_history
+     * 
+     * @code bx_srv('bx_credits', 'get_block_history', [...]); @endcode
+     * 
+     * Get page block with a list of all changes with credits.
+     *
+     * @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 BxCreditsModule::serviceGetBlockHistory
+     */
+    /** 
+     * @ref bx_credits-get_block_history "get_block_history"
+     */
+    public function serviceGetBlockHistory($sType = 'common') 
+    {
+        return $this->_getBlockHistory($sType);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-get_block_withdrawals get_block_withdrawals
+     * 
+     * @code bx_srv('bx_credits', 'get_block_withdrawals', [...]); @endcode
+     * 
+     * Get page block with a list of all credits withdrawals.
+     *
+     * @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 BxCreditsModule::serviceGetBlockHistory
+     */
+    /** 
+     * @ref bx_credits-get_block_withdrawals "get_block_withdrawals"
+     */
+    public function serviceGetBlockWithdrawals($sType = 'common') 
+    {
+        if(!$this->_oConfig->isWithdraw())
+            return MsgBox (_t('_bx_credits_msg_action_withdrawals_disabled'));
+
+        return $this->_getBlockWithdrawals($sType);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-get_block_profiles get_block_profiles
+     * 
+     * @code bx_srv('bx_credits', 'get_block_profiles', [...]); @endcode
+     * 
+     * Get page block with a list of profiles for managing their parameters.
+     *
+     * @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 BxCreditsModule::serviceGetBlockProfiles
+     */
+    /** 
+     * @ref bx_credits-get_block_profiles "get_block_profiles"
+     */
+    public function serviceGetBlockProfiles($sType = 'common') 
+    {
+        return $this->_getBlockProfiles($sType);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-get_payment_data get_payment_data
+     * 
+     * @code bx_srv('bx_credits', '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 BxCreditsModule::serviceGetPaymentData
+     */
+    /** 
+     * @ref bx_credits-get_payment_data "get_payment_data"
+     */
+    public function serviceGetPaymentData()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oPermalink = BxDolPermalinks::getInstance();
+        return array_merge($this->_aModule, [
+            'url_browse_order_common' => bx_absolute_url(bx_append_url_params($oPermalink->permalink($CNF['URL_ORDERS_COMMON']), ['filter' => '{order}'], false)),
+            'url_browse_order_administration' => bx_absolute_url(bx_append_url_params($oPermalink->permalink($CNF['URL_ORDERS_ADMINISTRATION']), ['filter' => '{order}'], false))
+        ]);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-add_to_cart add_to_cart
+     * 
+     * @code bx_srv('bx_credits', 'add_to_cart', [...]); @endcode
+     * 
+     * Adds an item in Shopping Cart. Is needed for payments processing module.
+     * 
+     * @return an array with module's description.
+     * 
+     * @see BxCreditsModule::serviceAddToCart
+     */
+    /** 
+     * @ref bx_credits-add_to_cart "add_to_cart"
+     */
+    public function serviceAddToCart($iItemId)
+    {
+        $iVendor = $this->_oConfig->getAuthor();
+        $oPayments = BxDolPayments::getInstance();
+
+        $aResult = $oPayments->addToCart($iVendor, $this->_oConfig->getName(), $iItemId, 1, true);
+        if(isset($aResult['code']) && (int)$aResult['code'] != 0)
+            return $this->_bIsApi ? [bx_api_get_msg($aResult['message'])] : $aResult;
+
+        return $this->_bIsApi ? [
+            'url' => $oPayments->getCartUrl($iVendor)
+        ] : $aResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-get_cart_item get_cart_item
+     * 
+     * @code bx_srv('bx_credits', 'get_cart_item', [...]); @endcode
+     * 
+     * Get an array with prodict's description. Is used in Shopping Cart in payments processing module.
+     * 
+     * @param $iItemId product's ID.
+     * @return an array with prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxCreditsModule::serviceGetCartItem
+     */
+    /** 
+     * @ref bx_credits-get_cart_item "get_cart_item"
+     */
+    public function serviceGetCartItem($iItemId)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        if(!$iItemId)
+            return array();
+
+        $aItem = $this->_oDb->getBundle(array('type' => 'id', 'id' => $iItemId));
+        if(empty($aItem) || !is_array($aItem))
+            return array();
+
+        return array (
+            'id' => $aItem[$CNF['FIELD_ID']],
+            'author_id' => $this->_oConfig->getAuthor(),
+            'name' => $aItem[$CNF['FIELD_NAME']],
+            'title' => _t($aItem[$CNF['FIELD_TITLE']]),
+            'description' => $this->_oConfig->getBundleDescription($aItem),
+            'url' => $this->_oConfig->getBundleUrl($aItem),
+            'price_single' => $aItem[$CNF['FIELD_PRICE']],
+            'price_recurring' => '',
+            'period_recurring' => 1,
+            'period_unit_recurring' => '',
+            'trial_recurring' => ''
+        );
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-get_cart_items get_cart_items
+     * 
+     * @code bx_srv('bx_credits', '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 BxCreditsModule::serviceGetCartItems
+     */
+    /** 
+     * @ref bx_credits-get_cart_items "get_cart_items"
+     */
+    public function serviceGetCartItems($iSellerId)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        $iAuthorId = $this->_oConfig->getAuthor();
+        if($iSellerId != $iAuthorId && !BxDolAcl::getInstance()->isMemberLevelInSet([MEMBERSHIP_ID_ADMINISTRATOR], $iSellerId))
+            return array();
+
+        $aItems = $this->_oDb->getBundle(array('type' => 'all', 'active' => 1));
+
+        $aResult = array();
+        foreach($aItems as $aItem)
+            $aResult[] = array(
+                'id' => $aItem[$CNF['FIELD_ID']],
+                'author_id' => $iAuthorId,
+                'name' => $aItem[$CNF['FIELD_NAME']],
+                'title' => _t($aItem[$CNF['FIELD_TITLE']]),
+                'description' => $this->_oConfig->getBundleDescription($aItem),
+                'url' => $this->_oConfig->getBundleUrl($aItem),
+                'price_single' => $aItem[$CNF['FIELD_PRICE']],
+                'price_recurring' => '',
+                'period_recurring' => 1,
+                'period_unit_recurring' => '',
+                'trial_recurring' => ''
+            );
+
+        return $aResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-register_cart_item register_cart_item
+     * 
+     * @code bx_srv('bx_credits', 'register_cart_item', [...]); @endcode
+     * 
+     * Register a processed single time payment inside the Credits 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 BxCreditsModule::serviceRegisterCartItem
+     */
+    /** 
+     * @ref bx_credits-register_cart_item "register_cart_item"
+     */
+    public function serviceRegisterCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, BX_CREDITS_ORDER_TYPE_SINGLE);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-register_subscription_item register_subscription_item
+     * 
+     * @code bx_srv('bx_credits', 'register_subscription_item', [...]); @endcode
+     * 
+     * Register a processed subscription (recurring payment) inside the Credits 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 BxCreditsModule::serviceRegisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_credits-register_subscription_item "register_subscription_item"
+     */
+    public function serviceRegisterSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, BX_CREDITS_ORDER_TYPE_RECURRING);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-reregister_cart_item reregister_cart_item
+     * 
+     * @code bx_srv('bx_credits', 'reregister_cart_item', [...]); @endcode
+     * 
+     * Reregister a single time payment inside the Credits 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 BxCreditsModule::serviceReregisterCartItem
+     */
+    /** 
+     * @ref bx_credits-reregister_cart_item "reregister_cart_item"
+     */
+    public function serviceReregisterCartItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        return array();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-reregister_subscription_item reregister_subscription_item
+     * 
+     * @code bx_srv('bx_credits', 'reregister_subscription_item', [...]); @endcode
+     * 
+     * Reregister a subscription (recurring payment) inside the Credits 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 BxCreditsModule::serviceReregisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_credits-reregister_subscription_item "reregister_subscription_item"
+     */
+    public function serviceReregisterSubscriptionItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        return array();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-unregister_cart_item unregister_cart_item
+     * 
+     * @code bx_srv('bx_credits', 'unregister_cart_item', [...]); @endcode
+     * 
+     * Unregister an earlier processed single time payment inside the Credits 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 BxCreditsModule::serviceUnregisterCartItem
+     */
+    /** 
+     * @ref bx_credits-unregister_cart_item "unregister_cart_item"
+     */
+    public function serviceUnregisterCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, BX_CREDITS_ORDER_TYPE_SINGLE);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-unregister_subscription_item unregister_subscription_item
+     * 
+     * @code bx_srv('bx_credits', 'unregister_subscription_item', [...]); @endcode
+     * 
+     * Unregister an earlier processed subscription (recurring payment) inside the Credits 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 BxCreditsModule::serviceUnregisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_credits-unregister_subscription_item "unregister_subscription_item"
+     */
+    public function serviceUnregisterSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+    	return $this->_serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, BX_CREDITS_ORDER_TYPE_RECURRING); 
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-cancel_subscription_item cancel_subscription_item
+     * 
+     * @code bx_srv('bx_credits', 'cancel_subscription_item', [...]); @endcode
+     * 
+     * Cancel an earlier processed subscription (recurring payment) inside the Credits 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 BxCreditsModule::serviceCancelSubscriptionItem
+     */
+    /** 
+     * @ref bx_credits-cancel_subscription_item "cancel_subscription_item"
+     */
+    public function serviceCancelSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder)
+    {
+    	return true;
+    }
+
+    public function serviceGetProfileBalance($iProfileId = 0)
+    {
+        return $this->getProfileBalance($iProfileId);
+    }
+
+    public function serviceUpdateProfileBalance($iFirstPid, $fAmount, $iSecondPid = 0, $sOrder = '', $sInfo = '')
+    {
+        if(empty($sInfo))
+            $sInfo = '_bx_credits_txt_history_info_service';
+
+        return $this->updateProfileBalance($iFirstPid, $iSecondPid, $fAmount, BX_CREDITS_TRANSFER_TYPE_SERVICE, $sOrder, $sInfo);
+    }
+    
+    public function serviceMakePayment($iBuyerPid, $fAmount, $iSellerPid, $sOrder = '')
+    {
+        $sInfo = '_bx_credits_txt_history_info_checkout';
+
+        if(!$this->updateProfileBalance($iBuyerPid, $iSellerPid, -$fAmount, BX_CREDITS_TRANSFER_TYPE_CHECKOUT, $sOrder, $sInfo))
+            return false;
+
+        if(!$this->updateProfileBalance($iSellerPid, $iBuyerPid, $fAmount, $sOrder, BX_CREDITS_TRANSFER_TYPE_CHECKOUT, $sInfo)) {
+            $this->updateProfileBalance($iBuyerPid, 0, $fAmount, BX_CREDITS_TRANSFER_TYPE_CANCELLATION, $sOrder, '_bx_credits_txt_history_info_cancellation');
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Delete all content by profile 
+     * @param $iProfileId profile id 
+     * @return number of deleted items
+     */
+    public function serviceDeleteEntitiesByAuthor ($iProfileId)
+    {
+        $this->_oDb->deleteProfile(array('id' => $iProfileId));
+        $this->_oDb->deleteHistory(array('first_pid' => $iProfileId));
+    }
+
+
+    /*
+     * Common methods
+     */
+    public function convertC2S($fCredits, $bWithUnit = true)
+    {
+        $sResult = '';
+        if($bWithUnit)
+            $sResult .= $this->_oTemplate->getUnit();
+
+        $iPrecision = $this->_oConfig->getPrecision();
+        $sResult .= sprintf("%01." . $iPrecision . "f", round((float)$fCredits, $iPrecision));
+
+        return $sResult;
+    }
+
+    public function getProfileLimits($iProfileId = 0)
+    {
+        if(empty($iProfileId))
+            $iProfileId = bx_get_logged_profile_id();
+
+        $aResults = [
+            'wdw_clearing' => $this->_oConfig->getWithdrawClearing(),
+            'wdw_minimum' => $this->_oConfig->getWithdrawMinimum(),
+            'wdw_remaining' => $this->_oConfig->getWithdrawRemaining(),
+        ];
+
+        if(empty($iProfileId))
+            return $aResults;
+
+        $aProfile = $this->_oDb->getProfile(['type' => 'id', 'id' => $iProfileId]);
+
+        foreach($aResults as $sKey => $mixedValue)
+            if(!empty($aProfile[$sKey]))
+                $aResults[$sKey] = $aProfile[$sKey];
+
+        return $aResults;
+    }
+
+    public function getProfileBalance($iProfileId = 0)
+    {
+        if(empty($iProfileId))
+            $iProfileId = bx_get_logged_profile_id();
+
+        if(empty($iProfileId))
+            return 0;
+
+        return (float)$this->_oDb->getProfile(array('type' => 'balance', 'id' => $iProfileId));
+    }
+
+    public function getProfileBalanceCleared($iProfileId = 0)
+    {
+        if(empty($iProfileId))
+            $iProfileId = bx_get_logged_profile_id();
+
+        if(empty($iProfileId))
+            return 0;
+
+        $fCleared = (float)$this->_oDb->getHistory(['type' => 'cleared', 'profile' => $iProfileId]);
+        $fSpent = (float)$this->_oDb->getHistory(['type' => 'spent', 'profile' => $iProfileId]);
+
+        return $fCleared > $fSpent ? round($fCleared - $fSpent, 2) : 0;
+    }
+
+    public function updateProfileBalance($iFirstPid, $iSecondPid, $fAmount, $sType, $sOrder = '', $sInfo = '', $sData = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($sOrder))
+            $sOrder = $this->_oConfig->getOrder();
+
+        if(empty($sData))
+            $sData = serialize(array(
+                'conversion' => $this->_oConfig->getConversionRateUse(),
+                'precision' => $this->_oConfig->getPrecision()
+            ));
+
+        if(!$this->_oDb->updateProfileBalance($iFirstPid, $fAmount))
+            return false;
+
+        $fAmountAbs = abs($fAmount);
+        $sDirection = $fAmount > 0 ? BX_CREDITS_DIRECTION_IN : BX_CREDITS_DIRECTION_OUT;
+
+        $iHistoryId = $this->_oDb->insertHistory([
+            'first_pid' => $iFirstPid,
+            'second_pid' => $iSecondPid,
+            'amount' => $fAmountAbs,
+            'type' => $sType,
+            'direction' => $sDirection,
+            'order' => $sOrder,
+            'data' => $sData,
+            'info' => $sInfo,
+            'date' => time()
+        ]);
+
+        if(!$iHistoryId)
+            return false;
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-update_balance 'bx_credits', 'update_balance' - hook after profile's credits balance was updated (credits were transferred from one profile to another)
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `update_balance`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `first_pid` - [int] first profile id
+         *      - `second_pid` - [int] second profile id
+         *      - `amount` - [float] amount was transferred
+         *      - `type` - [string] transfer type: purchase, checkout, cancellation, grant, send, withdraw, service
+         *      - `direction` - [string] transfer direction: in or out
+         *      - `order` - [string] unique order number associated with the transfer
+         * @hook @ref hook-bx_credits-update_balance
+         */
+        bx_alert($this->getName(), 'update_balance', 0, false, [
+            'first_pid' => $iFirstPid,
+            'second_pid' => $iSecondPid,
+            'amount' => $fAmountAbs,
+            'type' => $sType,
+            'direction' => $sDirection,
+            'order' => $sOrder,
+        ]);
+
+        $oSecondProfile = BxDolProfile::getInstanceMagic($iSecondPid);
+        sendMailTemplate($CNF['ETEMPLATE_' . strtoupper($sDirection)], 0, $iFirstPid, array(
+            'second_profile_name' => $oSecondProfile->getDisplayName(),
+            'amount' => $fAmountAbs,
+        ));
+
+        return $iHistoryId;
+    }
+
+    public function processGrant($iUserId, $iProfileId, $fAmount, $sMessage = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return ['code' => 2, 'msg' => '_bx_credits_err_profile_not_found'];
+
+        $sInfo = '_bx_credits_txt_history_info_grant';
+        $iHistoryId = $this->updateProfileBalance($iProfileId, 0, $fAmount, BX_CREDITS_TRANSFER_TYPE_GRANT, '', $sInfo);
+        if(!$iHistoryId)
+            return ['code' => 3, 'msg' => '_bx_credits_err_cannot_update_balance'];
+
+        sendMailTemplate($CNF['ETEMPLATE_GRANTED'], 0, $iProfileId, [
+            'amount' => $fAmount,
+            'message' => $sMessage
+        ]);
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-granted 'bx_credits', 'granted' - hook after some amount of credits was granted to a profile
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `granted`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `profile` - [int] recipient profile id
+         *      - `amount` - [float] amount was granted
+         * @hook @ref hook-bx_credits-granted
+         */
+        bx_alert($this->getName(), 'granted', 0, false, [
+            'profile' => $iProfileId,
+            'amount' => $fAmount,
+        ]);
+
+        return ['code' => 0, 'id' => $iHistoryId];
+    }
+
+    public function processSend($iUserId, $iProfileId, $fAmount, $sMessage = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $fAmountAvail = $this->getProfileBalance($iUserId);
+        if($fAmount > $fAmountAvail)
+            return ['code' => 1, 'msg' => '_bx_credits_err_low_balance'];
+
+        $oProfile = null;
+        if(!$iProfileId || !($oProfile = BxDolProfile::getInstance($iProfileId)))
+            return ['code' => 2, 'msg' => '_bx_credits_err_profile_not_found'];
+
+        $sOrder = $this->_oConfig->getOrder();
+        $sInfo = !empty($sMessage) ? $sMessage : '_bx_credits_txt_history_info_send';
+
+        $iHistoryId = $this->updateProfileBalance($iUserId, $iProfileId, -$fAmount, BX_CREDITS_TRANSFER_TYPE_SEND, $sOrder, $sInfo);
+        if(!$iHistoryId)
+            return ['code' => 3, 'msg' => '_bx_credits_err_cannot_update_balance'];
+
+        if(!$this->updateProfileBalance($iProfileId, $iUserId, $fAmount, BX_CREDITS_TRANSFER_TYPE_SEND, $sOrder, $sInfo)) {
+            $sInfo = '_bx_credits_txt_history_info_cancellation';
+            $iHistoryId = $this->updateProfileBalance($iUserId, 0, $fAmount, BX_CREDITS_TRANSFER_TYPE_CANCELLATION, $sOrder, $sInfo);
+
+            return ['code' => 3, 'id' => $iHistoryId, 'msg' => '_bx_credits_err_cannot_update_balance'];
+        }
+
+        $oUser = BxDolProfile::getInstance($iUserId);
+        sendMailTemplate($CNF['ETEMPLATE_RECEIVED'], 0, $iProfileId, [
+            'performer_id' => $iUserId,
+            'performer_name' => $oUser->getDisplayName(),
+            'performer_link' => $oUser->getUrl(),
+            'amount' => $fAmount,
+            'order' => $sOrder,
+            'message' => $sMessage
+        ]);
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-sent 'bx_credits', 'sent' - hook after some amount of credits was sent from one profile to another
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `sent`
+         * - $object_id - not used
+         * - $sender_id - sender profile id
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `performer` - [int] sender profile id
+         *      - `profile` - [int] recipient profile id
+         *      - `amount` - [float] amount was sent
+         *      - `order` - [string] unique order number associated with 'sent' action
+         * @hook @ref hook-bx_credits-sent
+         */
+        bx_alert($this->getName(), 'sent', 0, $iUserId, [
+            'performer' => $iUserId,
+            'profile' => $iProfileId,
+            'amount' => $fAmount,
+            'order' => $sOrder,
+        ]);
+
+        return ['code' => 0, 'id' => $iHistoryId];
+    }
+
+    public function processWithdrawRequest($iUserId, $iProfileId, $fAmount, $sMessage = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($iProfileId))
+            $iProfileId = bx_get_logged_profile_id ();
+
+        if(empty($iProfileId))
+            return ['code' => 1, 'msg' => '_bx_credits_err_login_required'];
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return ['code' => 1, 'msg' => '_bx_credits_err_login_required'];
+
+        $fBalanceCleared = $this->getProfileBalanceCleared($iProfileId);
+        if($fAmount > $fBalanceCleared)
+            return ['code' => 2, 'msg' => '_bx_credits_err_low_balance'];
+
+        $aProfileInfo = $this->_oDb->getProfile(['type' => 'id', 'id' => $iProfileId]);
+
+        $iWithdrawMinimum = !empty($aProfileInfo['wdw_minimum']) ? (int)$aProfileInfo['wdw_minimum'] : (int)getParam($CNF['PARAM_WITHDRAW_MINIMUM']);
+        if($fAmount < $iWithdrawMinimum)
+            return ['code' => 3, 'msg' => _t('_bx_credits_err_withdraw_minimum', $iWithdrawMinimum)];
+
+        $fBalance = $this->getProfileBalance($iProfileId);
+        $iWithdrawRemaining = !empty($aProfileInfo['wdw_remaining']) ? (int)$aProfileInfo['wdw_remaining'] : (int)getParam($CNF['PARAM_WITHDRAW_REMAINING']);
+        if(($fBalance - $fAmount) < $iWithdrawRemaining)
+            return ['code' => 4, 'msg' => _t('_bx_credits_err_withdraw_remaining', $iWithdrawRemaining)];
+
+        $aResult = ['code' => 5, 'msg' => '_bx_credits_err_cannot_send'];
+
+        $fRate = $this->_oConfig->getConversionRateWithdraw();
+
+        $iId = $this->_oDb->insertWithdrawal([
+            'profile_id' => $iProfileId, 
+            'amount' => $fAmount,
+            'rate' => $fRate,
+            'message' => $sMessage,
+            'added' => time()
+        ]);
+
+        if(!$iId)
+            return $aResult;
+
+        $aTemplateVars = [
+            'profile_link' => $oProfile->getUrl(),
+            'profile_name' => $oProfile->getDisplayName(),
+            'amount' => $fAmount,
+            'rate' => $fRate,
+            'message' => $sMessage,
+            'confirm_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink($CNF['URL_HISTORY_ADMINISTRATION']))
+        ];
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate($CNF['ETEMPLATE_WITHDRAW_REQUESTED'], $aTemplateVars);
+        if(!$aTemplate)
+            return $aResult;
+
+        $sEmail = $this->_oConfig->getWithdrawEmail();
+        if(!sendMail($sEmail, $aTemplate['Subject'], $aTemplate['Body'], 0, [], BX_EMAIL_SYSTEM))
+            return $aResult;
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-withdraw_requested 'bx_credits', 'withdraw_requested' - hook after a profile requested to withdraw some amount of credits from his balance
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `withdraw_requested`
+         * - $object_id - not used
+         * - $sender_id - performer profile id
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `profile` - [int] performer profile id
+         *      - `amount` - [float] amount to withdraw
+         *      - `rate` - [float] withdraw conversion rate
+         * @hook @ref hook-bx_credits-withdraw_requested
+         */
+        bx_alert($this->getName(), 'withdraw_requested', 0, $iProfileId, [
+            'profile' => $iProfileId,
+            'amount' => $fAmount,
+            'rate' => $fRate
+        ]);
+
+        return ['code' => 0, 'id' => $iId];
+    }
+
+    public function processWithdrawCancel($iId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aWithdrawal = $this->_oDb->getWithdrawal(['type' => 'id', 'id' => $iId]);
+        if(empty($aWithdrawal) || !is_array($aWithdrawal))
+            return ['code' => 1, 'msg' => '_bx_credits_err_withdrawal_not_found'];
+
+        $iProfileId = (int)$aWithdrawal['profile_id'];
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return ['code' => 2, 'msg' => '_bx_credits_err_profile_not_found'];
+
+        $aResult = ['code' => 3, 'msg' => '_bx_credits_err_cannot_cancel_withdrawal'];       
+
+        $aTemplateVars = [
+            'profile_link' => $oProfile->getUrl(),
+            'profile_name' => $oProfile->getDisplayName(),
+            'amount' => $aWithdrawal['amount'],
+            'manage_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink($CNF['URL_WITHDRAWALS_ADMINISTRATION']))
+        ];
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate($CNF['ETEMPLATE_WITHDRAW_CANCELED'], $aTemplateVars);
+        if(!$aTemplate)
+            return $aResult;
+
+        $sEmail = $this->_oConfig->getWithdrawEmail();
+        if(!sendMail($sEmail, $aTemplate['Subject'], $aTemplate['Body'], 0, [], BX_EMAIL_SYSTEM))
+            return $aResult;
+
+        if(!$this->_oDb->updateWithdrawal(['status' => BX_CREDITS_WITHDRAWAL_STATUS_CANCELED], ['id' => $iId]))
+            return $aResult;
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-withdraw_canceled 'bx_credits', 'withdraw_canceled' - hook after a profile canceled his withdrawal request
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `withdraw_canceled`
+         * - $object_id - not used
+         * - $sender_id - performer profile id
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `profile` - [int] performer profile id
+         *      - `amount` - [float] amount to withdraw
+         *      - `rate` - [float] withdraw conversion rate
+         * @hook @ref hook-bx_credits-withdraw_canceled
+         */
+        bx_alert($this->getName(), 'withdraw_canceled', $iId, $iProfileId, [
+            'profile' => $iProfileId,
+            'amount' => $aWithdrawal['amount'],
+        ]);
+
+        return ['code' => 0, 'id' => $iId];
+    }
+
+    public function processWithdrawConfirm($iUserId, $iWithdrawalId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aWithdrawal = $this->_oDb->getWithdrawal(['type' => 'id', 'id' => $iWithdrawalId]);
+        if(empty($aWithdrawal) || !is_array($aWithdrawal))
+            return ['code' => 1, 'msg' => '_bx_credits_err_withdrawal_not_found'];
+
+        $iProfileId = (int)$aWithdrawal['profile_id'];
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return ['code' => 2, 'msg' => '_bx_credits_err_profile_not_found'];
+
+        $fAmount = (float)$aWithdrawal['amount'];
+        $fBalance = $this->getProfileBalance($iProfileId);
+        if($fAmount > $fBalance)
+            return ['code' => 3, 'msg' => '_bx_credits_err_low_balance'];
+
+        $sInfo = '_bx_credits_txt_history_info_withdraw';
+        $iHistoryId = $this->updateProfileBalance($iProfileId, 0, -$fAmount, BX_CREDITS_TRANSFER_TYPE_WITHDRAW, '', $sInfo);
+        if(!$iHistoryId)
+            return ['code' => 4, 'msg' => '_bx_credits_err_cannot_update_balance'];
+
+        $aHistory = $this->_oDb->getHistory(['type' => 'id', 'id' => $iHistoryId]);
+        
+        if(!$this->_oDb->updateWithdrawal(['performer_id' => $iUserId, 'order' => $aHistory['order'], 'confirmed' => time(), 'status' => BX_CREDITS_WITHDRAWAL_STATUS_CONFIRMED], ['id' => $iWithdrawalId]))
+            return $aResult;
+
+        sendMailTemplate($CNF['ETEMPLATE_WITHDRAW_SENT'], 0, $iProfileId, [
+            'amount' => $fAmount
+        ], BX_EMAIL_SYSTEM);
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-withdraw_sent 'bx_credits', 'withdraw_sent' - hook after the amount of credits requested to withdraw was sent
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `withdraw_sent`
+         * - $object_id - not used
+         * - $sender_id - performer profile id
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `profile` - [int] performer profile id
+         *      - `amount` - [float] amount to withdraw
+         *      - `rate` - [float] withdraw conversion rate
+         * @hook @ref hook-bx_credits-withdraw_sent
+         */
+        bx_alert($this->getName(), 'withdraw_sent', $iWithdrawalId, $iUserId, [
+            'performer' => $iUserId,
+            'profile' => $iProfileId,
+            'amount' => $fAmount,
+        ]);
+
+        return ['code' => 0, 'id' => $iWithdrawalId];
+    }
+
+    public function processClearing()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aItems = $this->_oDb->getHistory(['type' => 'clearing', 'clearing' => $this->_oConfig->getWithdrawClearing()]);
+        if(empty($aItems) || !is_array($aItems))
+            return;
+
+        $iNow = time();
+        foreach($aItems as $aItem) {
+            $this->_oDb->updateHistory([$CNF['FIELD_H_CLEARED'] => $iNow], [$CNF['FIELD_H_ID'] => $aItem['id']]);
+
+            //TODO: Some action(s) related to clearing can be performed here.
+        }
+    }
+
+    /*
+     * Internal methods
+     */
+    protected function _serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, $sType)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aItem = $this->serviceGetCartItem($iItemId);
+        if(empty($aItem) || !is_array($aItem))
+            return array();
+
+        $aBundle = $this->_oDb->getBundle(array('type' => 'id', 'id' => $iItemId));
+        if(empty($aBundle) || !is_array($aBundle))
+            return array();
+
+        $iTrial = 0;
+        $sDuration = '';
+        $sAction = 'register';
+        if($sType == BX_CREDITS_ORDER_TYPE_RECURRING && isset($CNF['FIELD_DURATION_RECURRING'], $CNF['FIELD_TRIAL_RECURRING'])) {
+            $iTrial = $aBundle[$CNF['FIELD_TRIAL_RECURRING']];
+            $sDuration = $aBundle[$CNF['FIELD_DURATION_RECURRING']];
+
+            if($this->_oDb->isOrderByPbo($iClientId, $iItemId, $sOrder))
+                $sAction = 'prolong';
+        }
+
+        if(!$this->_oDb->{$sAction . 'Order'}($iClientId, $iItemId, $iItemCount, $sOrder, $sLicense, $sType, $sDuration, $iTrial))
+            return array();
+
+        $sInfo = '_bx_credits_txt_history_info_purchase';
+        $fAmount = ((float)$aBundle[$CNF['FIELD_AMOUNT']] + (float)$aBundle[$CNF['FIELD_BONUS']]) * $iItemCount;
+        if(!$this->updateProfileBalance($iClientId, 0, $fAmount, BX_CREDITS_TRANSFER_TYPE_PURCHASE, '', $sInfo))
+            return array();
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-order_register 'bx_credits', 'order_register' - hook after the payment for a bundle of credits was processed with payment processing module
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `order_register`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `profile_id` - [int] client (buyer) profile id
+         *      - `bundle_id` - [int] bundle id
+         *      - `product_id` - [int] bundle id
+         *      - `count` - [int] number of items in order
+         *      - `order` - [string] order number provided with payment processing module
+         *      - `license` - [string] license number provided with payment processing module
+         *      - `type` - [string] payment type ('single' or 'recurring'), current version supports 'single' type payments for bundles only
+         *      - `duration` - [string] isn't supported
+         *      - `trial` - [string] isn't supported
+         * @hook @ref hook-bx_credits-order_register
+         */
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-order_prolong 'bx_credits', 'order_prolong' - hook isn't currently used because 'single' type payments for bundles are only supported
+         * It's equivalent to @ref hook-bx_credits-order_register
+         * @hook @ref hook-bx_credits-order_prolong
+         */
+        bx_alert($this->getName(), 'order_' . $sAction, 0, false, [
+            'profile_id' => $iClientId,
+            'bundle_id' => $iItemId,
+            'product_id' => $iItemId,   //--- Alias for 'bundle_id'
+            'count' => $iItemCount,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'type' => $sType,
+            'duration' => $sDuration,
+            'trial' => $iTrial
+        ]);
+
+        $oClient = BxDolProfile::getInstanceMagic($iClientId);
+        $oSeller = BxDolProfile::getInstanceMagic($iSellerId);
+        $sSellerUrl = $oSeller->getUrl();
+        $sSellerName = $oSeller->getDisplayName();
+
+        sendMailTemplate($CNF['ETEMPLATE_PURCHASED'], 0, $iClientId, array(
+            'client_name' => $oClient->getDisplayName(),
+            'bundle_name' => $aBundle[$CNF['FIELD_NAME']],
+            'bundle_title' => _t($aBundle[$CNF['FIELD_TITLE']]),
+            'bundle_url' => $this->_oConfig->getBundleUrl($aBundle),
+            'vendor_url' => $sSellerUrl,
+            'vendor_name' => $sSellerName,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'notes' => _t('_bx_credits_txt_purchased_note', $sSellerUrl, $sSellerName),
+        ));
+
+        return $aItem;
+    }
+
+    protected function _serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, $sType)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aBundle = $this->_oDb->getBundle(array('type' => 'id', 'id' => $iItemId));
+        if(empty($aBundle) || !is_array($aBundle))
+            return false;
+
+        if(!$this->_oDb->unregisterOrder($iClientId, $iItemId, $sOrder, $sLicense, $sType))
+            return false;
+
+        $fAmount = (float)$aBundle[$CNF['FIELD_AMOUNT']] + (float)$aBundle[$CNF['FIELD_BONUS']];
+        $aProfile = $this->_oDb->getProfile(array('type' => 'id', 'id' => $iClientId));
+        if(!empty($aProfile) && is_array($aProfile))
+            $this->_oDb->updateProfile(array('balance' => (float)$aProfile['balance'] - $fAmount), array('id' => $aProfile['id']));
+
+        /**
+         * @hooks
+         * @hookdef hook-bx_credits-order_unregister 'bx_credits', 'order_unregister' - hook after the payment for a bundle of credits was refunded with payment processing module
+         * - $unit_name - equals `bx_credits`
+         * - $action - equals `order_unregister`
+         * - $object_id - not used
+         * - $sender_id - not used
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `profile_id` - [int] client (buyer) profile id
+         *      - `bundle_id` - [int] bundle id
+         *      - `product_id` - [int] bundle id
+         *      - `count` - [int] number of items in order
+         *      - `order` - [string] order number provided with payment processing module
+         *      - `license` - [string] license number provided with payment processing module
+         *      - `type` - [string] payment type ('single' or 'recurring'), current version supports 'single' type payments for bundles only
+         * @hook @ref hook-bx_credits-order_unregister
+         */
+        bx_alert($this->getName(), 'order_unregister', 0, false, [
+            'profile_id' => $iClientId,
+            'bundle_id' => $iItemId,
+            'product_id' => $iItemId,   //--- Alias for 'bundle_id'
+            'count' => $iItemCount,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'type' => $sType,
+        ]);
+
+        return true;
+    }
+
+    protected function _getBlockOrders($sType) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        return $this->_getBlockGrid($CNF['OBJECT_GRID_ORDERS_' . strtoupper($sType)]);
+    }
+
+    protected function _getBlockHistory($sType) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->_getBlockGrid($CNF['OBJECT_GRID_HISTORY_' . strtoupper($sType)]);
+    }
+
+    protected function _getBlockWithdrawals($sType) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->_getBlockGrid($CNF['OBJECT_GRID_WITHDRAWALS_' . strtoupper($sType)]);
+    }
+
+    protected function _getBlockProfiles($sType) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->_getBlockGrid($CNF['OBJECT_GRID_PROFILES_' . strtoupper($sType)]);
+    }
+
+    protected function _getBlockGrid($sGrid) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oGrid = null;
+        if(!$sGrid || !($oGrid = BxDolGrid::getObjectInstance($sGrid)))
+            return $this->_bIsApi ? [] : '';
+
+        $sMenu = $CNF['OBJECT_MENU_MANAGE_SUBMENU'];
+        return $this->_bIsApi ? [
+            'content' => [bx_api_get_block('grid', $oGrid->getCodeAPI())],
+            'menu' => ($oMenu = BxTemplMenu::getObjectInstance($sMenu)) ? $oMenu->getCodeAPI() : ''
+        ] : [
+            'content' => $oGrid->getCode(),
+            'menu' => $sMenu
+        ];
+    }
+}
+
+/** @} */

+ 216 - 0
modules/boonex/credits/updates/15.0.2_15.0.3/source/classes/BxCreditsTemplate.php

@@ -0,0 +1,216 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCreditsTemplate extends BxBaseModGeneralTemplate
+{
+    function __construct(&$oConfig, &$oDb)
+    {
+        $this->MODULE = 'bx_credits';
+
+        parent::__construct($oConfig, $oDb);
+
+        $this->aMethodsToCallAddJsCss = array('getBlockBundles', 'getBlockCheckout');
+    }
+
+    public function getJsCode($sType, $aParams = array(), $bWrap = true)
+    {
+        $aParams = array_merge(array(
+            'aHtmlIds' => $this->_oConfig->getHtmlIds()
+        ), $aParams);
+
+        return parent::getJsCode($sType, $aParams, $bWrap);
+    }
+
+    public function getEmptyAuthor()
+    {
+    	return MsgBox(_t('_bx_credits_msg_empty_author'));
+    }
+
+    public function getUnit()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sUnit = '';
+        if(($sCode = getParam($CNF['PARAM_CODE'])) != '')
+            $sUnit = $sCode;
+        else if(($sIcon = getParam($CNF['PARAM_ICON'])) != '')
+            $sUnit = $this->parseIcon($sIcon, array('class' => $this->_oConfig->getPrefix('style') . '-prefix-icon bx-def-margin-thd-right'));
+
+        return $sUnit;
+    }
+
+    public function getBlockCheckout($oBuyer, $oSeller, $aData)
+    {
+        $sStylePrefix = $this->_oConfig->getPrefix('style');
+        $sJsObject = $this->_oConfig->getJsObject('checkout');
+
+        $sTxtQt = _t('_bx_credits_txt_checkout_qt');
+
+        $fRate = $this->_oConfig->getConversionRateUse();
+        $aCurrency = $this->_oConfig->getCurrency();
+
+        $sCurrencySign = $aCurrency['sign'];
+        if(!empty($aData['currency']['sign']))
+            $sCurrencySign = $aData['currency']['sign'];
+
+        $aTmplVarsItems = array();
+        foreach($aData['items'] as $iIndex => $aItem)
+            $aTmplVarsItems[] = array(
+                'sp' => $sStylePrefix,
+                'item_index' => $iIndex + 1,
+                'item_title' => $aItem['title'],
+                'item_quantity' => $aItem['quantity'] . $sTxtQt
+            );
+        
+        $sTitle = _t('_bx_credits_txt_checkout_to', $oSeller->getDisplayName());
+        if($this->_bIsApi)
+            return [
+                'title' => $sTitle,
+                'items' => $aTmplVarsItems,
+                'amount' => [
+                    'value' => (float)$aData['amountm'],
+                    'currency' => $aData['currency']['code'] ?? $aCurrency['code']
+                ],
+                'rate' => $fRate != 1 ? $fRate : '',
+                'request_url' => '/api.php?r=' . $this->_oConfig->getName() . '/checkout&params[]='
+            ];
+
+        $this->addJs(array('checkout.js'));
+        $this->addCss(array('checkout.css'));
+        return $this->parseHtmlByName('checkout.html', array(
+            'sp' => $sStylePrefix,
+            'jo' => $sJsObject,
+            'title' => $sTitle,
+            'bx_repeat:items' => $aTmplVarsItems,
+            'amount' => $sCurrencySign . sprintf("%.2f", (float)($aData['amountm'])),
+            'bx_if:show_rate' => array(
+                'condition' => $fRate != 1,
+                'content' => array(
+                    'sp' => $sStylePrefix,
+                    'rate' => $fRate,
+                )
+            ),
+            'credits' => $this->getModule()->convertC2S($aData['amountc']),
+            'js_code' => $this->getJsCode('checkout')
+        ));
+    }
+
+    public function getBlockBundles()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sStylePrefix = $this->_oConfig->getPrefix('style');
+
+        $sModule = $this->_oConfig->getName();
+        $iAuthor = $this->_oConfig->getAuthor();
+        $aCurrency = $this->_oConfig->getCurrency();
+
+        $aBundles = $this->_oDb->getBundle(['type' => 'all', 'active' => 1]);
+        if(empty($aBundles) || !is_array($aBundles))
+            return ($sMsg = _t('_Empty')) && $this->_bIsApi ? [bx_api_get_msg($sMsg)] : MsgBox($sMsg);
+
+        $oPayment = BxDolPayments::getInstance();
+
+        $aTmplVarsBundles = [];
+        foreach($aBundles as $aBundle) {
+            $iBundle = $aBundle[$CNF['FIELD_ID']];
+
+            $aJs = $oPayment->getAddToCartJs($iAuthor, $this->MODULE, $iBundle, 1, true);
+            if(empty($aJs) || !is_array($aJs))
+                continue;
+
+            list($sJsCode, $sJsMethod) = $aJs;
+
+            $aTmplVarsBundles[] = array_merge($aBundle, [
+                'title' => _t($aBundle[$CNF['FIELD_TITLE']]),
+                'description' => _t($aBundle[$CNF['FIELD_DESCRIPTION']]),
+                'currency_sign' => $aCurrency['sign'],
+                'currency_code' => $aCurrency['code'],
+            ], !$this->_bIsApi ? [
+                'sp' => $sStylePrefix,
+                'bx_if:show_bonus' => [
+                    'condition' => (int)$aBundle[$CNF['FIELD_BONUS']] > 0,
+                    'content' => [
+                        'sp' => $sStylePrefix,
+                        'bonus' => (int)$aBundle[$CNF['FIELD_BONUS']]
+                    ]
+                ],
+                'onclick' => $sJsMethod
+            ] : [
+                'buttons' => [[
+                    'title' => _t('_bx_credits_txt_purchase'),
+                    'request_url' => $sModule . '/add_to_cart/&params[]=' . $iBundle . '&params[]=',
+                ]]
+            ]);
+        }
+
+        return $this->_bIsApi ? $aTmplVarsBundles : $this->parseHtmlByName('bundles.html', [
+            'sp' => $sStylePrefix,
+            'bx_repeat:bundles' => $aTmplVarsBundles,
+            'js_code' => $oPayment->getCartJs()
+        ]);
+    }
+
+    public function getPopupSubscribe($oBuyer, $oSeller, $aData)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $bDynamic = bx_is_dynamic_request();
+        $sStylePrefix = $this->_oConfig->getPrefix('style');
+        $sJsObject = $this->_oConfig->getJsObject('subscribe');
+
+        $fRate = $this->_oConfig->getConversionRateUse();
+        $aCurrency = $this->_oConfig->getCurrency();
+
+        $sCurrencySign = $aCurrency['sign'];
+        if(!empty($aData['currency']['sign']))
+            $sCurrencySign = $aData['currency']['sign'];
+
+        if(!empty($aData['period']) && !empty($aData['period_unit'])) {
+            $sPeriod = _t('_bx_credits_txt_subscribe_period_unit_' . $aData['period_unit']);
+            if((int)$aData['period'] > 1);
+                $sPeriod = _t('_bx_credits_txt_subscribe_period_mask', $aData['period'], $sPeriod);
+        }
+        else
+            $sPeriod = _t('_bx_credits_txt_subscribe_lifetime');
+
+        $sInclude = '';
+        $sInclude .= $this->addJs(array('subscribe.js'), $bDynamic);
+        $sInclude .= $this->addCss(array('main.css', 'subscribe.css'), $bDynamic);
+
+        $sContent = ($bDynamic ? $sInclude : '') . $this->parseHtmlByName('subscribe.html', array(
+            'sp' => $sStylePrefix,
+            'jo' => $sJsObject,
+            'title' => _t('_bx_credits_txt_subscribe_for', $oSeller->getDisplayName()),
+            'item_title' => $aData['title'],
+            'amount' => $sCurrencySign . sprintf("%.2f", (float)($aData['amountm'])),
+            'period' => $sPeriod,
+            'bx_if:show_rate' => array(
+                'condition' => $fRate != 1,
+                'content' => array(
+                    'sp' => $sStylePrefix,
+                    'rate' => $fRate,
+                )
+            ),
+            'bx_if:show_trial' => array(
+                'condition' => (int)$aData['trial'] > 0,
+                'content' => array(
+                    'sp' => $sStylePrefix,
+                    'trial' => _t('_bx_credits_txt_subscribe_trial_mask', $aData['trial'], _t('_bx_credits_txt_subscribe_period_unit_day'))
+                )
+            ),
+            'credits' => $this->getModule()->convertC2S($aData['amountc']),
+            'js_code' => $this->getJsCode('subscribe')
+        ));
+
+        return BxTemplFunctions::getInstance()->transBox($this->_oConfig->getHtmlIds('subscribe_popup'), $sContent);
+    }
+}
+
+/** @} */

+ 77 - 0
modules/boonex/credits/updates/15.0.2_15.0.3/source/install/config.php

@@ -0,0 +1,77 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_credits',
+    'title' => 'Credits',
+    'note' => 'Credits module.',
+    'version' => '15.0.3',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/credits/',
+    'home_uri' => 'credits',
+
+    'db_prefix' => 'bx_credits_',
+    'class_prefix' => 'BxCredits',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Credits',
+
+    /**
+     * 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(),
+
+    /**
+     * Relations Section
+     */
+    'relations' => array(),
+
+);
+
+/** @} */

+ 25 - 0
modules/boonex/credits/updates/15.0.2_15.0.3/source/install/sql/enable-app.sql

@@ -0,0 +1,25 @@
+-- PAGES: config_api
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_home' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_bundles' AND `title`='_bx_credits_page_block_title_bundles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_checkout' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_checkout' AND `title`='_bx_credits_page_block_title_checkout';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_orders_common' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_orders_common_note' AND `title`='_bx_credits_page_block_title_orders_common_note';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_orders_common' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_orders_common' AND `title`='_bx_credits_page_block_title_orders_common';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_orders_administration' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_orders_administration' AND `title`='_bx_credits_page_block_title_orders_administration';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_history_common' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_history_common' AND `title`='_bx_credits_page_block_title_history_common';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_history_administration' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_history_administration' AND `title`='_bx_credits_page_block_title_history_administration';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_profiles_administration' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_profiles_administration' AND `title`='_bx_credits_page_block_title_profiles_administration';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_credits_checkout' AND `module`='bx_credits' AND `title_system`='_bx_credits_page_block_title_sys_checkout' AND `title`='_bx_credits_page_block_title_checkout';
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_credits_manage_submenu' AND `module`='bx_credits' AND `name`='credits-history-common';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_credits_manage_submenu' AND `module`='bx_credits' AND `name`='credits-history-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_credits_manage_submenu' AND `module`='bx_credits' AND `name`='credits-orders-common';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_credits_manage_submenu' AND `module`='bx_credits' AND `name`='credits-orders-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_credits_manage_submenu' AND `module`='bx_credits' AND `name`='credits-withdrawals-common';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_credits_manage_submenu' AND `module`='bx_credits' AND `name`='credits-withdrawals-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_credits_manage_submenu' AND `module`='bx_credits' AND `name`='credits-profiles-administration';

+ 51 - 0
modules/boonex/datafox/updates/12.0.0_15.0.0/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' => 'Data Fox',
+    'version_from' => '12.0.0',
+    'version_to' => '15.0.0',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/datafox/updates/update_12.0.0_15.0.0/',
+    'home_uri' => 'datafox_update_1200_1500',
+
+    'module_dir' => 'boonex/datafox/',
+    'module_uri' => 'datafox',
+
+    'db_prefix' => 'bx_datafox_',
+    'class_prefix' => 'BxDataFox',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Data Fox',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/datafox/updates/12.0.0_15.0.0/install/installer.php

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

+ 2 - 0
modules/boonex/datafox/updates/12.0.0_15.0.0/install/sql/enable.sql

@@ -0,0 +1,2 @@
+-- OPTIONS
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_datafox_secret';

+ 68 - 0
modules/boonex/datafox/updates/12.0.0_15.0.0/source/install/config.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    DataFox Data Fox API integration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_datafox',
+    'title' => 'Data Fox',
+    'note' => 'Data Fox API integration.',
+    'version' => '15.0.0',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/datafox/',
+    'home_uri' => 'datafox',
+
+    'db_prefix' => 'bx_datafox_',
+    'class_prefix' => 'BxDataFox',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Data Fox',
+
+    /**
+     * 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(),
+);
+
+/** @} */

+ 30 - 0
modules/boonex/datafox/updates/12.0.0_15.0.0/source/install/sql/enable.sql

@@ -0,0 +1,30 @@
+
+-- 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_datafox', '_bx_datafox_adm_stg_cpt_type', 'bx_datafox@modules/boonex/datafox/|std-icon.svg', IF(NOT ISNULL(@iTypeOrder), @iTypeOrder + 1, 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_datafox_general', '_bx_datafox_adm_stg_cpt_category_general', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `order`, `extra`) VALUES
+('bx_datafox_id', '', @iCategId, 'DataFox Client ID', 'digit', '', '', 10, ''),
+('bx_datafox_secret', '', @iCategId, 'DataFox Client Secret', 'secret', '', '', 12, '');
+
+-- alerts
+
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES 
+('bx_datafox', 'BxDataFoxAlertsResponse', 'modules/boonex/datafox/classes/BxDataFoxAlertsResponse.php', '');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('system', 'clear_xss', @iHandler);
+
+-- injections
+
+INSERT INTO `sys_injections` (`name`, `page_index`, `key`, `type`, `data`, `replace`, `active`) VALUES
+('bx_datafox', 0, 'injection_head', 'service', 'a:2:{s:6:"module";s:10:"bx_datafox";s:6:"method";s:14:"include_css_js";}', 0, 1);
+

Разница между файлами не показана из-за своего большого размера
+ 3 - 0
modules/boonex/datafox/updates/12.0.0_15.0.0/source/template/images/icons/std-icon.svg


+ 51 - 0
modules/boonex/facebook_connect/updates/15.0.0_15.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' => 'Facebook connect',
+    'version_from' => '15.0.0',
+    'version_to' => '15.0.1',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/facebook_connect/updates/update_15.0.0_15.0.1/',
+    'home_uri' => 'facebook_connect_update_1500_1501',
+
+    'module_dir' => 'boonex/facebook_connect/',
+    'module_uri' => 'facebook_connect',
+
+    'db_prefix' => 'bx_facebook_',
+    'class_prefix' => 'BxFaceBookConnect',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Facebook',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/facebook_connect/updates/15.0.0_15.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 BxFaceBookConnectUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 2 - 0
modules/boonex/facebook_connect/updates/15.0.0_15.0.1/install/sql/enable.sql

@@ -0,0 +1,2 @@
+-- OPTIONS
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_facebook_connect_secret';

+ 68 - 0
modules/boonex/facebook_connect/updates/15.0.0_15.0.1/source/install/config.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    FacebookConnect Facebook Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_facebook',
+    'title' => 'Facebook connect',
+    'note' => 'Join the site using Facebook account.',
+    'version' => '15.0.1',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/facebook_connect/',
+    'home_uri' => 'facebook_connect',
+
+    'db_prefix' => 'bx_facebook_',
+    'class_prefix' => 'BxFaceBookConnect',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Facebook',
+
+    /**
+     * 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(),
+);
+
+/** @} */

+ 51 - 0
modules/boonex/facebook_connect/updates/15.0.0_15.0.1/source/install/sql/enable.sql

@@ -0,0 +1,51 @@
+
+-- EMAIL 
+
+INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `Body`) VALUES
+('bx_facebook', '_bx_facebook_conect_et_password_generated', 'bx_facebook_password_generated', '_bx_facebook_conect_et_password_generated_subject', '_bx_facebook_conect_et_password_generated_body');
+
+-- OBJECTS: AUTH
+
+INSERT INTO `sys_objects_auths` (`Name`, `Title`, `Link`, `Icon`, `Style`) VALUES
+('bx_facebook', '_bx_facebook_auth_title', 'modules/?r=facebook_connect/login_form', 'fab facebook-square', 'a:1:{s:7:".bx-btn";a:1:{s:10:"background";s:18:"#3b5999 !important";}}');
+
+-- ALERTS
+
+INSERT INTO `sys_alerts_handlers` SET `name` = 'bx_facebook_connect', `class` = 'BxFaceBookConnectAlerts', `file` = 'modules/boonex/facebook_connect/classes/BxFaceBookConnectAlerts.php';
+
+SET @iHandlerId := (SELECT `id` FROM `sys_alerts_handlers`  WHERE `name` = 'bx_facebook_connect');
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('account', 'logout', @iHandlerId),
+('profile', 'delete', @iHandlerId),
+('profile', 'add', @iHandlerId);
+
+-- 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_facebook', '_bx_facebook_adm_stg_cpt_type', 'bx_facebook@modules/boonex/facebook_connect/|std-icon.svg', IF(NOT ISNULL(@iTypeOrder), @iTypeOrder + 1, 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_facebook_general', '_sys_connect_adm_stg_cpt_category_general', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `order`, `extra`) VALUES
+('bx_facebook_connect_api_key', '', @iCategId, '_bx_facebook_option_app_id', 'digit', '', '', 10, ''),
+('bx_facebook_connect_secret', '', @iCategId, '_bx_facebook_option_app_secret', 'secret', '', '', 20, ''),
+('bx_facebook_connect_redirect_page', 'dashboard', @iCategId, '_sys_connect_option_redirect', 'select', '', '', 30, 'join,settings,dashboard,index'),
+('bx_facebook_connect_module', 'bx_persons', @iCategId, '_sys_connect_option_module', 'select', '', '', 40, 'a:2:{s:6:"module";s:11:"bx_facebook";s:6:"method";s:20:"get_profiles_modules";}'),
+('bx_facebook_connect_privacy', '3', @iCategId, '_sys_connect_option_privacy', 'select', '', '', 44, 'a:2:{s:6:"module";s:11:"bx_facebook";s:6:"method";s:18:"get_privacy_groups";}'),
+('bx_facebook_connect_auto_friends', 'on', @iCategId, '_bx_facebook_option_auto_friends', 'checkbox', '', '', 50, ''),
+('bx_facebook_connect_extended_info', '', @iCategId, '_bx_facebook_option_fetch_extended_info', 'checkbox', '', '', 60, ''),
+('bx_facebook_connect_confirm_email', 'on', @iCategId, '_sys_connect_option_confirm_email', 'checkbox', '', '', 70, ''),
+('bx_facebook_connect_approve', '', @iCategId, '_sys_connect_option_approve', 'checkbox', '', '', 80, '');
+
+-- PAGE: error
+
+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_facebook_error', 'facebook-error', '_bx_facebook_error', '_bx_facebook_error', 'bx_facebook', 5, 2147483647, 0, '', '', '', '', 0, 1, 0, 'BxFaceBookConnectPage', 'modules/boonex/facebook_connect/classes/BxFaceBookConnectPage.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_facebook_error', 1, 'bx_facebook', '_bx_facebook_error', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:11:\"bx_facebook\";s:6:\"method\";s:10:\"last_error\";}', 0, 0, 1, 1);

Разница между файлами не показана из-за своего большого размера
+ 6 - 0
modules/boonex/facebook_connect/updates/15.0.0_15.0.1/source/template/images/icons/std-icon.svg


+ 51 - 0
modules/boonex/files/updates/15.0.4_15.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' => 'Files',
+    'version_from' => '15.0.4',
+    'version_to' => '15.0.5',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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_15.0.4_15.0.5/',
+    'home_uri' => 'files_update_1504_1505',
+
+    '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' => 1,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Files',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/files/updates/15.0.4_15.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 BxFilesUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 4 - 0
modules/boonex/files/updates/15.0.4_15.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_files_queue_downloads"><![CDATA[Download jobs]]></string_add>
+</resources>

+ 36 - 0
modules/boonex/files/updates/15.0.4_15.0.5/source/classes/BxFilesFormsEntryHelper.php

@@ -0,0 +1,36 @@
+<?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
+ *
+ * @{
+ */
+
+/**
+ * Entry forms helper functions
+ */
+class BxFilesFormsEntryHelper extends BxBaseModFilesFormsEntryHelper
+{
+    public function __construct($oModule)
+    {
+        $this->_sDisplayForFormAdd ='bx_files_entry_upload';
+        $this->_sObjectNameForFormAdd ='bx_files_upload';
+
+        parent::__construct($oModule);
+    }
+
+    protected function redirectAfterDelete($aContentInfo, $sUrl = '')
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (($oProfile = BxDolProfile::getInstance($aContentInfo[$CNF['FIELD_AUTHOR']])) !== false)
+            $sUrl = 'page.php?i=' . $CNF['URI_AUTHOR_ENTRIES'] . '&profile_id=' . $oProfile->id();
+
+        return parent::redirectAfterDelete($aContentInfo, $sUrl);
+    }
+}
+
+/** @} */

+ 669 - 0
modules/boonex/files/updates/15.0.4_15.0.5/source/classes/BxFilesModule.php

@@ -0,0 +1,669 @@
+<?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 = shell_exec($sCommand);
+            @unlink($sFilePath);
+
+            $this->_oDb->updateFileData ($aContentInfo[$CNF['FIELD_ID']], $sData);
+        }
+    }
+
+    /**
+     * The Studio Dashboard's Queues block: archive download jobs. Clearing removes the jobs older than an hour (finished or
+     * abandoned), the module's own cron prunes them after a day.
+     */
+    public function serviceGetDashboardQueues()
+    {
+        return array(
+            'downloads' => array(
+                'name' => '_bx_files_queue_downloads',
+                'icon' => 'download',
+                'all' => "SELECT COUNT(*) FROM `bx_files_downloading_jobs`",
+                'action' => "DELETE FROM `bx_files_downloading_jobs` WHERE UNIX_TIMESTAMP() - `started` > 3600"
+            )
+        );
+    }
+
+    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/15.0.4_15.0.5/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' => '15.0.5',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * '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',
+    ),
+
+);
+
+/** @} */

+ 310 - 0
modules/boonex/files/updates/15.0.4_15.0.5/source/install/langs/en.xml

@@ -0,0 +1,310 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+
+    <string name="_bx_files"><![CDATA[Files]]></string>
+    <string name="_bx_files_cmts"><![CDATA[Files' Comments]]></string>
+    <string name="_bx_files_reactions"><![CDATA[Files' Reactions]]></string>
+
+    <string name="_bx_files_option_enable_auto_approve"><![CDATA[Auto approve a file after creation]]></string>
+    <string name="_bx_files_option_summary_chars"><![CDATA[Number of characters in auto-cropped file summary]]></string>
+    <string name="_bx_files_option_per_page_browse"><![CDATA[Number of items per page]]></string>
+    <string name="_bx_files_option_per_page_profile"><![CDATA[Number of items on profile/group page]]></string>
+    <string name="_bx_files_option_rss_num"><![CDATA[Number of items in RSS feed]]></string>
+    <string name="_bx_files_option_searchable_fields"><![CDATA[Searchable fields for keyword search]]></string>
+    <string name="_bx_files_option_per_page_for_favorites_lists"><![CDATA[Number of favorite lists per page]]></string>
+
+    <string name="_bx_files_form_upload"><![CDATA[Files Upload]]></string>
+    <string name="_bx_files_form_entry"><![CDATA[Files]]></string>
+    <string name="_bx_files_form_entry_display_add"><![CDATA[Add Files]]></string>
+    <string name="_bx_files_form_entry_display_edit"><![CDATA[Edit File]]></string>
+    <string name="_bx_files_form_entry_display_delete"><![CDATA[Delete File]]></string>
+    <string name="_bx_files_form_entry_display_view"><![CDATA[View File]]></string>
+    <string name="_bx_files_form_entry_input_author"><![CDATA[Author]]></string>
+    <string name="_bx_files_form_entry_input_sys_profile_id"><![CDATA[Profile ID]]></string>
+    <string name="_bx_files_form_entry_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_files_form_entry_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_files_form_entry_input_sys_do_publish"><![CDATA[Publish]]></string>
+    <string name="_bx_files_form_entry_input_do_publish"><![CDATA[Publish]]></string>
+    <string name="_bx_files_form_entry_input_sys_title"><![CDATA[Title]]></string>
+    <string name="_bx_files_form_entry_input_title"><![CDATA[Title]]></string>
+    <string name="_bx_files_form_entry_input_title_err"><![CDATA[Title is essential min 3 max 160.]]></string>
+    <string name="_bx_files_form_entry_input_sys_cat"><![CDATA[Category]]></string>
+    <string name="_bx_files_form_entry_input_cat"><![CDATA[Category]]></string>
+    <string name="_bx_files_form_entry_input_cat_err"><![CDATA[Please select category]]></string>
+    <string name="_bx_files_form_entry_input_sys_desc"><![CDATA[Description]]></string>
+    <string name="_bx_files_form_entry_input_desc"><![CDATA[Description]]></string>
+    <string name="_bx_files_form_entry_input_sys_data"><![CDATA[File data]]></string>
+    <string name="_bx_files_form_entry_input_data"><![CDATA[File data]]></string>
+    <string name="_bx_files_form_entry_input_sys_delete_confirm"><![CDATA[Delete confirmation]]></string>
+    <string name="_bx_files_form_entry_input_delete_confirm"><![CDATA[I am sure I want to delete this file]]></string>
+    <string name="_bx_files_form_entry_input_delete_confirm_info"><![CDATA[This action can not be undone]]></string>
+    <string name="_bx_files_form_entry_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete the file]]></string>
+    <string name="_bx_files_form_entry_input_sys_attachment"><![CDATA[File]]></string>
+    <string name="_bx_files_form_entry_input_attachment"><![CDATA[File]]></string>
+    <string name="_bx_files_form_entry_input_attachment_err"><![CDATA[File is essential. Please, upload a file.]]></string>
+    <string name="_bx_files_form_entry_input_sys_attachments"><![CDATA[Files]]></string>
+    <string name="_bx_files_form_entry_input_attachments"><![CDATA[Files]]></string>
+    <string name="_bx_files_form_entry_input_attachments_err"><![CDATA[File is essential. Please, upload some file(s).]]></string>    
+    <string name="_bx_files_form_entry_input_sys_allow_view_to"><![CDATA[Visibility]]></string>
+    <string name="_bx_files_form_entry_input_allow_view_to"><![CDATA[Visibility]]></string>
+    <string name="_bx_files_form_entry_input_allow_view_favorite_list"><![CDATA[Visibility]]></string>
+    <string name="_bx_files_form_entry_input_picture_use_as_thumb"><![CDATA[Cover Image]]></string>
+    <string name="_bx_files_form_entry_input_sys_date_added"><![CDATA[Created]]></string>
+    <string name="_bx_files_form_entry_input_date_added"><![CDATA[Created]]></string>
+    <string name="_bx_files_form_entry_input_sys_date_changed"><![CDATA[Updated]]></string>
+    <string name="_bx_files_form_entry_input_date_changed"><![CDATA[Updated]]></string>
+    <string name="_bx_files_form_entry_input_pictures_upload"><![CDATA[Upload]]></string>
+
+    <string name="_bx_files_acl_action_create_entry"><![CDATA[Create File]]></string>
+    <string name="_bx_files_acl_action_delete_entry"><![CDATA[Delete File]]></string>
+    <string name="_bx_files_acl_action_view_entry"><![CDATA[View File]]></string>
+    <string name="_bx_files_acl_action_edit_any_entry"><![CDATA[Edit Any File]]></string>
+    <string name="_bx_files_acl_action_delete_any_entry"><![CDATA[Delete Any File]]></string>
+
+    <string name="_bx_files_page_title_sys_create_entry"><![CDATA[Upload Files]]></string>
+    <string name="_bx_files_page_title_create_entry"><![CDATA[Upload Files]]></string>
+    <string name="_bx_files_page_block_title_create_entry"><![CDATA[Upload Files]]></string>
+    <string name="_bx_files_page_title_sys_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_files_page_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_files_page_block_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_files_page_title_sys_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_files_page_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_files_page_block_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_files_page_title_sys_view_entry"><![CDATA[View File]]></string>
+    <string name="_bx_files_page_title_view_entry"><![CDATA[{title}]]></string>
+    <string name="_bx_files_page_block_title_entry_text"><![CDATA[File Description]]></string>
+    <string name="_bx_files_page_title_browse"><![CDATA[Files]]></string>
+    <string name="_bx_files_page_title_search_results"><![CDATA[Files matching "{0}"]]></string>
+    <string name="_bx_files_page_title_browse_by_author"><![CDATA[Files of {display_name}]]></string>
+    <string name="_bx_files_page_title_browse_by_context"><![CDATA[Files in {display_name}]]></string>
+    <string name="_bx_files_page_title_browse_featured"><![CDATA[Featured Files]]></string>
+    <string name="_bx_files_page_title_browse_recent"><![CDATA[Latest Files]]></string>
+    <string name="_bx_files_page_title_browse_popular"><![CDATA[Popular Files]]></string>
+    <string name="_bx_files_page_title_browse_top"><![CDATA[Top Files]]></string>
+    <string name="_bx_files_page_title_browse_updated"><![CDATA[Updated Files]]></string>
+    <string name="_bx_files_page_title_sys_home"><![CDATA[Files Home]]></string>
+    <string name="_bx_files_page_title_home"><![CDATA[Files]]></string>
+    <string name="_bx_files_page_block_title_featured_entries_view_extended"><![CDATA[Featured Files]]></string>
+    <string name="_bx_files_page_block_title_recent_entries"><![CDATA[Latest Files (Gallery View)]]></string>
+    <string name="_bx_files_page_block_title_recent_entries_view_extended"><![CDATA[Latest Files]]></string>
+    <string name="_bx_files_page_block_title_entry_social_sharing"><![CDATA[Share]]></string>
+    <string name="_bx_files_page_block_title_entry_author"><![CDATA[Author]]></string>
+    <string name="_bx_files_page_block_title_entry_actions"><![CDATA[Actions]]></string>
+    <string name="_bx_files_page_block_title_sys_entry_context"><![CDATA[Posted in]]></string>
+    <string name="_bx_files_page_block_title_entry_context"><![CDATA[Posted in]]></string>
+    <string name="_bx_files_page_block_title_entry_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_files_page_block_title_entry_reports"><![CDATA[Reports]]></string>
+    <string name="_bx_files_page_block_title_entry_comments_link"><![CDATA[Comments to <a href="{entry_link}">{title}</a>]]></string>
+    <string name="_bx_files_page_block_title_entry_location"><![CDATA[Location]]></string>
+    <string name="_bx_files_page_block_title_entry_attachments"><![CDATA[Attachments]]></string>
+    <string name="_bx_files_page_block_title_entry_all_actions"><![CDATA[All Actions]]></string>
+    <string name="_bx_files_page_title_sys_entries_popular"><![CDATA[Popular Files]]></string>
+    <string name="_bx_files_page_title_entries_popular"><![CDATA[Popular Files]]></string>
+    <string name="_bx_files_page_title_sys_entries_top"><![CDATA[Top Files]]></string>
+    <string name="_bx_files_page_title_entries_top"><![CDATA[Top Files]]></string>
+    <string name="_bx_files_page_title_sys_entries_updated"><![CDATA[Updated Files]]></string>
+    <string name="_bx_files_page_title_entries_updated"><![CDATA[Updated Files]]></string>
+    <string name="_bx_files_page_block_title_popular_entries"><![CDATA[Popular Files]]></string>
+    <string name="_bx_files_page_block_title_popular_entries_view_extended"><![CDATA[Popular Files (Extended View)]]></string>
+    <string name="_bx_files_page_block_title_top_entries"><![CDATA[Top Files]]></string>
+    <string name="_bx_files_page_block_title_updated_entries"><![CDATA[Updated Files]]></string>
+    <string name="_bx_files_page_block_title_sys_favorites_entries"><![CDATA[Favorite List]]></string>
+    <string name="_bx_files_page_block_title_favorites_entries"><![CDATA["{title}" List]]></string>
+    <string name="_bx_files_page_block_title_favorites_entries_info"><![CDATA[Info]]></string>
+    <string name="_bx_files_page_block_title_favorites_entries_actions"><![CDATA[Favorites List Actions]]></string>
+    <string name="_bx_files_page_title_sys_view_entry_comments"><![CDATA[File Comments]]></string>
+    <string name="_bx_files_page_title_view_entry_comments"><![CDATA[Comments to {title}]]></string>
+    <string name="_bx_files_page_title_sys_entries_of_author"><![CDATA[Files of author]]></string>
+    <string name="_bx_files_page_title_entries_of_author"><![CDATA[Files by {display_name}]]></string>
+    <string name="_bx_files_page_title_sys_entries_in_context"><![CDATA[Files in context]]></string>
+    <string name="_bx_files_page_title_entries_in_context"><![CDATA[Files in {display_name}]]></string>
+    <string name="_bx_files_page_block_title_favorites_of_author"><![CDATA[Favorites by <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_files_page_block_title_sys_favorites_of_author"><![CDATA[Favorites of author]]></string>
+    <string name="_bx_files_page_block_title_entries_of_author"><![CDATA[Files by <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_files_page_block_title_sys_entries_of_author"><![CDATA[Files of author]]></string>
+    <string name="_bx_files_page_block_title_entries_in_context"><![CDATA[Files in <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_files_page_block_title_sys_entries_in_context"><![CDATA[Files in context]]></string>
+    <string name="_bx_files_page_block_title_entries_actions"><![CDATA[Files actions]]></string>
+    <string name="_bx_files_page_block_title_browse_cmts"><![CDATA[Files' Comments]]></string>
+    <string name="_bx_files_page_title_sys_manage"><![CDATA[Manage Own]]></string>
+    <string name="_bx_files_page_title_sys_manage_administration"><![CDATA[Manage All]]></string>
+    <string name="_bx_files_page_title_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_files_page_block_title_system_manage"><![CDATA[Manage Own]]></string>
+    <string name="_bx_files_page_block_title_system_manage_administration"><![CDATA[Manage All]]></string>
+    <string name="_bx_files_page_block_title_manage"><![CDATA[Manage]]></string>    
+    <string name="_bx_files_page_block_title_popular_keywords"><![CDATA[Popular Hashtags]]></string>
+    <string name="_bx_files_page_block_title_cats"><![CDATA[Files Categories]]></string>
+    <string name="_bx_files_page_block_title_entry_info"><![CDATA[Info]]></string>
+    <string name="_bx_files_page_block_title_sys_my_entries"><![CDATA[User Files]]></string>
+    <string name="_bx_files_page_block_title_my_entries"><![CDATA[{display_name} Files]]></string>
+    <string name="_bx_files_page_block_title_entry_file_preview"><![CDATA[File Preview]]></string>
+    <string name="_bx_files_page_title_sys_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_files_page_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_files_page_title_sys_entries_favorites"><![CDATA[Favorites Files]]></string>
+    <string name="_bx_files_page_title_entries_favorites"><![CDATA[Favorites Files]]></string>
+    <string name="_bx_files_page_block_title_search_form"><![CDATA[Search Form]]></string>
+    <string name="_bx_files_page_block_title_search_results"><![CDATA[Search Results]]></string>
+    <string name="_bx_files_page_block_title_search_form_cmts"><![CDATA[Comments Search Form]]></string>
+    <string name="_bx_files_page_block_title_search_results_cmts"><![CDATA[Comments Search Results]]></string>
+    <string name="_bx_files_page_block_title_sys_recent_entries_view_showcase"><![CDATA[Latest Files (Showcase View)]]></string>
+    <string name="_bx_files_page_block_title_recent_entries_view_showcase"><![CDATA[Latest Files]]></string>
+    <string name="_bx_files_page_block_title_sys_popular_entries_view_showcase"><![CDATA[Popular Files (Showcase View)]]></string>
+    <string name="_bx_files_page_block_title_popular_entries_view_showcase"><![CDATA[Popular Files]]></string>
+    <string name="_bx_files_page_block_title_sys_featured_entries_view_showcase"><![CDATA[Featured Files (Showcase View)]]></string>
+    <string name="_bx_files_page_block_title_featured_entries_view_showcase"><![CDATA[Featured Files]]></string>
+
+    <string name="_bx_files_menu_set_title_view_entry"><![CDATA[File Actions]]></string>
+    <string name="_bx_files_menu_title_view_entry"><![CDATA[File Actions]]></string>
+    <string name="_bx_files_menu_title_submenu"><![CDATA[Files Submenu]]></string>
+    <string name="_bx_files_menu_set_title_submenu"><![CDATA[Files Submenu]]></string>
+    <string name="_bx_files_menu_item_title_system_create_entry"><![CDATA[File]]></string>
+    <string name="_bx_files_menu_item_title_create_entry"><![CDATA[File]]></string>
+    <string name="_bx_files_menu_item_title_system_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_files_menu_item_title_edit_entry"><![CDATA[Edit]]></string>
+    <string name="_bx_files_menu_item_title_system_edit_title"><![CDATA[Rename]]></string>
+    <string name="_bx_files_menu_item_title_edit_title"><![CDATA[Rename]]></string>
+    <string name="_bx_files_menu_item_title_system_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_files_menu_item_title_delete_entry"><![CDATA[Delete]]></string>
+    <string name="_bx_files_menu_item_title_system_view_entry"><![CDATA[View File]]></string>
+    <string name="_bx_files_menu_item_title_system_entries_home"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_item_title_entries_home"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_item_title_system_entries_public"><![CDATA[Latest]]></string>
+    <string name="_bx_files_menu_item_title_entries_public"><![CDATA[Latest]]></string>
+    <string name="_bx_files_menu_item_title_system_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_files_menu_item_title_entries_search"><![CDATA[Search]]></string>
+    <string name="_bx_files_menu_item_title_system_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_files_menu_item_title_entries_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_files_menu_item_title_system_entries_popular"><![CDATA[Popular]]></string>
+    <string name="_bx_files_menu_item_title_entries_popular"><![CDATA[Popular]]></string>
+    <string name="_bx_files_menu_item_title_system_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_files_menu_item_title_entries_top"><![CDATA[Top]]></string>
+    <string name="_bx_files_menu_title_view_entry_submenu"><![CDATA[View File Submenu]]></string>
+    <string name="_bx_files_menu_set_title_view_entry_submenu"><![CDATA[View File Submenu]]></string>
+    <string name="_bx_files_menu_item_title_view_entry_submenu_entry"><![CDATA[File]]></string>
+    <string name="_bx_files_menu_item_title_view_entry_submenu_comments"><![CDATA[Comments]]></string>
+    <string name="_bx_files_menu_item_title_system_view_entry_comments"><![CDATA[File Comments]]></string>
+    <string name="_bx_files_menu_item_title_system_view_entries_author"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_item_title_view_entries_author"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_item_title_system_view_entries_in_context"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_item_title_view_entries_in_context"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_title_entries_my"><![CDATA[My Files Action Menu]]></string>
+    <string name="_bx_files_menu_set_title_entries_my"><![CDATA[My Files Menu]]></string>
+    <string name="_bx_files_menu_item_title_system_manage_my_files"><![CDATA[My files]]></string>
+    <string name="_bx_files_menu_item_title_manage_my_files"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_set_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_files_menu_title_manage_tools"><![CDATA[Manage Tools Submenu]]></string>
+    <string name="_bx_files_menu_item_title_system_manage_account"><![CDATA[Manage account]]></string>
+    <string name="_bx_files_menu_item_title_manage_account"><![CDATA[Manage account]]></string>
+    <string name="_bx_files_menu_item_title_system_admt_files"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_item_title_admt_files"><![CDATA[Files]]></string>
+    <string name="_bx_files_menu_item_title_manage_my"><![CDATA[My Files]]></string>
+    <string name="_bx_files_menu_item_title_manage_all"><![CDATA[All Files]]></string>
+    <string name="_bx_files_menu_item_title_system_download_file"><![CDATA[Download]]></string>
+    <string name="_bx_files_menu_item_title_download_file"><![CDATA[Download]]></string>
+
+    <string name="_bx_files_grid_filter_item_title_adm_select_one_filter1"><![CDATA[All Statuses]]></string>
+    <string name="_bx_files_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string>
+    <string name="_bx_files_grid_filter_item_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_files_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string>
+    <string name="_bx_files_grid_filter_item_title_adm_pending"><![CDATA[Pending]]></string>
+    <string name="_bx_files_grid_column_title_adm_active"><![CDATA[Active]]></string>
+    <string name="_bx_files_grid_column_title_adm_title"><![CDATA[Title]]></string>
+    <string name="_bx_files_grid_column_title_adm_added"><![CDATA[Added]]></string>
+    <string name="_bx_files_grid_column_title_adm_author"><![CDATA[Author]]></string>
+    <string name="_bx_files_grid_action_title_adm_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_files_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_files_grid_action_title_adm_clear_reports"><![CDATA[Clear reports]]></string>
+    <string name="_bx_files_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
+    <string name="_bx_files_grid_action_title_adm_audit_content"><![CDATA[Audit actions]]></string>
+    <string name="_bx_files_grid_column_title_adm_status_admin"><![CDATA[Moderation]]></string>
+    <string name="_bx_files_grid_action_err_delete"><![CDATA[Cannot delete the selected file(s).]]></string>
+    <string name="_bx_files_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+
+    <string name="_bx_files_chart_growth"><![CDATA[Files: Growth]]></string>
+    <string name="_bx_files_chart_growth_speed"><![CDATA[Files: Growth Speed]]></string>
+
+    <string name="_bx_files_search_extended"><![CDATA[Files Search]]></string>
+    <string name="_bx_files_search_extended_cmts"><![CDATA[Files Comments Search]]></string>
+
+    <string name="_bx_files_pre_lists_cats"><![CDATA[File categories]]></string>
+    <string name="_bx_files_cat_documentation"><![CDATA[Documentation]]></string>
+    <string name="_bx_files_cat_presentation"><![CDATA[Presentation]]></string>
+    <string name="_bx_files_cat_report"><![CDATA[Report]]></string>
+    <string name="_bx_files_cat_proposal"><![CDATA[Proposal]]></string>
+    <string name="_bx_files_cat_list"><![CDATA[List]]></string>
+    <string name="_bx_files_cat_record"><![CDATA[Audio record]]></string>
+    <string name="_bx_files_cat_screencast"><![CDATA[Screencast]]></string>
+    <string name="_bx_files_cat_screenshot"><![CDATA[Screenshot]]></string>
+    <string name="_bx_files_cat_sketch"><![CDATA[Sketch]]></string>
+    <string name="_bx_files_cat_archive"><![CDATA[Archive]]></string>
+    <string name="_bx_files_cat_backup"><![CDATA[Backup]]></string>
+
+    <string name="_bx_files_txt_all_entries_by"><![CDATA[{0} files]]></string>
+    <string name="_bx_files_txt_all_entries_in"><![CDATA[<a href="{0}">All Files in {1} ({2})</a>]]></string>
+    <string name="_bx_files_txt_preview_not_available"><![CDATA[Preview isn't available, please download this file to view it]]></string>
+    <string name="_bx_files_txt_sample_single"><![CDATA[file]]></string>
+    <string name="_bx_files_txt_sample_single_with_article"><![CDATA[a file]]></string>
+    <string name="_bx_files_txt_sample_comment_single"><![CDATA[comment to file]]></string>
+    <string name="_bx_files_txt_sample_vote_single"><![CDATA[vote to file]]></string>
+    <string name="_bx_files_txt_sample_reaction_single"><![CDATA['{0}' reaction to file]]></string>
+    <string name="_bx_files_txt_sample_score_up_single"><![CDATA[score up vote to file]]></string>
+    <string name="_bx_files_txt_sample_score_down_single"><![CDATA[score down vote to file]]></string>
+
+    <string name="_bx_files_privacy_public"><![CDATA[Public]]></string>
+    <string name="_bx_files_privacy_participants"><![CDATA[Participants]]></string>
+
+    <string name="_bx_files_menu_title_view_inline"><![CDATA[File inline menu]]></string>
+    <string name="_bx_files_menu_set_title_view_inline"><![CDATA[File inline menu]]></string>
+    <string name="_bx_files_menu_item_title_system_move_to_entry"><![CDATA[Move to...]]></string>
+    <string name="_bx_files_menu_item_title_move_to_entry"><![CDATA[Move to...]]></string>
+    <string name="_bx_files_menu_item_title_system_info_entry"><![CDATA[Info]]></string>
+    <string name="_bx_files_menu_item_title_info_entry"><![CDATA[Info]]></string>
+    <string name="_bx_files_menu_item_title_system_bookmark"><![CDATA[Bookmark]]></string>
+    <string name="_bx_files_menu_item_title_bookmark"><![CDATA[Bookmark]]></string>
+
+    <string name="_bx_files_menu_item_title_system_sm_size"><![CDATA[Size]]></string>
+    <string name="_bx_files_menu_item_title_sm_size"><![CDATA[Size]]></string>
+
+    <string name="_bx_files_option_link_to_preview"><![CDATA[Link file title to a preview popup instead of a file view page in context blocks]]></string>
+    <string name="_bx_files_option_max_nesting_level"><![CDATA[Folders max nesting level]]></string>
+    <string name="_bx_files_option_max_bulk_download_size"><![CDATA[Max size of files in a bulk/folder download archive (Mb)]]></string>
+
+    <string name="_bx_files_entry_info_size"><![CDATA[Size]]></string>
+    <string name="_bx_files_entry_info_date"><![CDATA[Date]]></string>
+    <string name="_bx_files_entry_info_author"><![CDATA[By]]></string>
+    <string name="_bx_files_entry_info_context"><![CDATA[Uploaded to]]></string>
+
+    <string name="_bx_files_toolbar_view_table"><![CDATA[Table View]]></string>
+    <string name="_bx_files_toolbar_view_gallery"><![CDATA[Gallery View]]></string>
+    <string name="_bx_files_toolbar_view_bookmarks"><![CDATA[Bookmarked files]]></string>
+    <string name="_bx_files_toolbar_sorting_name"><![CDATA[Order by Name]]></string>
+    <string name="_bx_files_toolbar_sorting_date"><![CDATA[Order by Date]]></string>
+    <string name="_bx_files_toolbar_sorting_type"><![CDATA[Order by Type]]></string>
+    <string name="_bx_files_toolbar_sorting_author"><![CDATA[Order by Author]]></string>
+
+    <string name="_bx_files_txt_msg_status_hidden"><![CDATA[This file was hidden.]]></string>
+    <string name="_bx_files_txt_msg_status_pending"><![CDATA[This file is pending approval.]]></string>
+    <string name="_bx_files_txt_error"><![CDATA[File not found]]></string>
+    <string name="_bx_files_txt_permission_denied"><![CDATA[Permission denied]]></string>
+    <string name="_bx_files_txt_upload"><![CDATA[Upload]]></string>
+    <string name="_bx_files_txt_add_folder"><![CDATA[Add Folder]]></string>
+    <string name="_bx_files_txt_folder_name"><![CDATA[Folder name]]></string>
+    <string name="_bx_files_txt_folder_root"><![CDATA[Root]]></string>
+    <string name="_bx_files_txt_move_to"><![CDATA[Move To]]></string>
+    <string name="_bx_files_txt_move_to_nowhere"><![CDATA[There are no folders created yet]]></string>
+    <string name="_bx_files_txt_move_here"><![CDATA[Move here?]]></string>
+    <string name="_bx_files_txt_max_size_reached"><![CDATA[Too large archive for downloading. Max overall size of files for bulk downloading is {0}.]]></string>
+
+    <string name="_bx_files_bulk_action_title_download"><![CDATA[Download]]></string>
+    <string name="_bx_files_bulk_action_title_bookmark"><![CDATA[Bookmark]]></string>
+    <string name="_bx_files_bulk_action_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_files_bulk_action_title_move_to"><![CDATA[Move To...]]></string>
+
+    <string name="_bx_files_txt_status_downloading"><![CDATA[Downloading...]]></string>
+    <string name="_bx_files_txt_status_downloading_msg"><![CDATA[The download will start shortly...]]></string>
+    <string name="_bx_files_txt_status_downloading_ready_msg"><![CDATA[Click <a href={0}>here</a> if the download doesn't start automatically.]]></string>
+
+    <string name="_bx_files_option_allowed_ext"><![CDATA[Allow only the following extensions (comma separated list)]]></string>
+    <string name="_bx_files_option_default_layout_mode"><![CDATA[Files default layout]]></string>
+    <string name="_bx_files_queue_downloads"><![CDATA[Download jobs]]></string>
+</resources>

+ 51 - 0
modules/boonex/forum/updates/15.0.4_15.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' => 'Discussions',
+    'version_from' => '15.0.4',
+    'version_to' => '15.0.5',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/forum/updates/update_15.0.4_15.0.5/',
+    'home_uri' => 'forum_update_1504_1505',
+
+    'module_dir' => 'boonex/forum/',
+    'module_uri' => 'forum',
+
+    'db_prefix' => 'bx_forum_',
+    'class_prefix' => 'BxForum',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Discussions',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/forum/updates/15.0.4_15.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 BxForumUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 274 - 0
modules/boonex/forum/updates/15.0.4_15.0.5/source/classes/BxForumConfig.php

@@ -0,0 +1,274 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Forum Forum
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxForumConfig extends BxBaseModTextConfig
+{
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $aMenuItems2Methods = array (
+            'subscribe-discussion' => 'checkAllowedSubscribe',
+            'unsubscribe-discussion' => 'checkAllowedUnsubscribe',
+            'stick-discussion' => 'checkAllowedStickAnyEntry',
+            'unstick-discussion' => 'checkAllowedUnstickAnyEntry',
+            'resolve-discussion' => 'checkAllowedResolveAnyEntry',
+            'unresolve-discussion' => 'checkAllowedUnresolveAnyEntry',
+            'lock-discussion' => 'checkAllowedLockAnyEntry',
+            'unlock-discussion' => 'checkAllowedUnlockAnyEntry',
+            'hide-discussion' => 'checkAllowedHideAnyEntry',
+            'unhide-discussion' => 'checkAllowedUnhideAnyEntry',
+            'approve' => 'checkAllowedApprove',
+            'edit-discussion' => 'checkAllowedEdit',
+            'delete-discussion' => 'checkAllowedDelete'
+        );
+
+        $this->CNF = array_merge($this->CNF, array (
+
+            // module icon
+            'ICON' => 'far comments',
+
+            // database tables
+            'TABLE_ENTRIES' => $aModule['db_prefix'] . 'discussions',
+            'TABLE_ENTRIES_FULLTEXT' => 'title_text',
+
+            // database fields
+            'FIELD_ID' => 'id',
+            'FIELD_AUTHOR' => 'author',
+            'FIELD_ADDED' => 'added',
+            'FIELD_CHANGED' => 'changed',
+            'FIELD_TITLE' => 'title',
+            'FIELD_TEXT' => 'text',
+            'FIELD_TEXT_ID' => 'discussion-text',
+            'FIELD_TEXT_COMMENTS' => 'text_comments',
+            'FIELD_CATEGORY' => 'cat',
+            'FIELD_MULTICAT' => 'multicat',
+            'FIELD_LR_AUTHOR' => 'lr_profile_id',
+            'FIELD_LR_ADDED' => 'lr_timestamp',
+            'FIELD_LR_COMMENT_ID' => 'lr_comment_id',
+            'FIELD_ALLOW_VIEW_TO' => 'allow_view_to',
+            'FIELD_CF' => 'cf',
+            'FIELD_COVER' => 'covers',
+            'FIELD_PHOTO' => 'pictures',
+            'FIELD_VIDEO' => 'videos',
+            'FIELD_FILE' => 'files',
+            'FIELD_POLL' => 'polls',
+            'FIELD_THUMB' => 'thumb',
+            'FIELD_THUMB_POSITION' => 'thumb_data',
+            'FIELD_ATTACHMENTS' => 'attachments',
+            'FIELD_LINK' => 'link',
+            'FIELD_VIEWS' => 'views',
+            'FIELD_VOTES' => 'votes',
+            'FIELD_COMMENTS' => 'comments',
+            'FIELD_SCORE' => 'score',
+            'FIELD_STICK' => 'stick',
+            'FIELD_LOCK' => 'lock',
+            'FIELD_RESOLVE' => 'resolved',
+            'FIELD_RESOLVABLE' => 'resolvable',
+            'FIELD_STATUS' => 'status',
+            'FIELD_STATUS_ADMIN' => 'status_admin',
+            'FIELD_LABELS' => 'labels',
+            'FIELD_ANONYMOUS' => 'anonymous',
+            'FIELDS_WITH_KEYWORDS' => 'auto', // can be 'auto', array of fields or comma separated string of field names, works only when OBJECT_METATAGS is specified
+
+             // some params
+            'PARAM_MULTICAT_ENABLED' => true,
+            'PARAM_MULTICAT_AUTO_ACTIVATION_FOR_CATEGORIES' => 'bx_forum_auto_activation_for_categories',
+            'PARAM_PER_PAGE_FOR_FAVORITES_LISTS' => 'bx_forum_per_page_for_favorites_lists',
+
+
+            // page URIs
+            'URI_VIEW_ENTRY' => 'view-discussion',
+            'URI_AUTHOR_ENTRIES' => 'discussions-author',
+            'URI_ENTRIES_BY_CONTEXT' => 'discussions-context',
+            'URI_CATEGORY_ENTRIES' => 'discussions-category',
+            'URI_KEYWORD_ENTRIES' => 'discussions-keyword',
+            'URI_ADD_ENTRY' => 'create-discussion',
+            'URI_EDIT_ENTRY' => 'edit-discussion',
+            'URI_MANAGE_COMMON' => 'discussions-manage',
+            'URI_FAVORITES_LIST' => 'discussions-favorites',
+
+            'URL_HOME' => 'page.php?i=discussions-home',
+            'URL_UPDATED' => 'page.php?i=discussions-updated',
+            'URL_NEW' => 'page.php?i=discussions-new',
+            'URL_TOP' => 'page.php?i=discussions-top',
+            'URL_POPULAR' => 'page.php?i=discussions-popular',
+            'URL_PARTAKEN' => 'page.php?i=discussions-partaken',
+            'URL_MANAGE_COMMON' => 'page.php?i=discussions-manage',
+            'URL_MANAGE_ADMINISTRATION' => 'page.php?i=discussions-administration',
+
+            // some params
+            'PARAM_AUTO_APPROVE' => 'bx_forum_enable_auto_approve',
+            'PARAM_CHARS_SUMMARY' => 'bx_forum_summary_chars',
+            'PARAM_CHARS_SUMMARY_PLAIN' => 'bx_forum_plain_summary_chars',
+            'PARAM_NUM_RSS' => 'bx_forum_rss_num',
+            'PARAM_SEARCHABLE_FIELDS' => 'bx_forum_searchable_fields',
+            'PARAM_PER_PAGE_BROWSE_SHOWCASE' => 'bx_forum_per_page_browse_showcase',
+            'PARAM_VISIBLE_CATEGORIES' => 'bx_forum_visible_categories',
+            'PARAM_AUTOSUBSCRIBE_CREATED' => 'bx_forum_autosubscribe_created',
+            'PARAM_AUTOSUBSCRIBE_REPLIED' => 'bx_forum_autosubscribe_replied', 
+            'PARAM_LINKS_ENABLED' => true,
+
+            // objects
+            'OBJECT_GRID' => 'bx_forum',
+            'OBJECT_GRID_FAVORITE' => 'bx_forum_favorite',
+            'OBJECT_GRID_FEATURE' => 'bx_forum_feature',
+            'OBJECT_GRID_ADMINISTRATION' => 'bx_forum_administration',
+            'OBJECT_GRID' => 'bx_forum',
+            'OBJECT_GRID_COMMON' => 'bx_forum_common',
+            'OBJECT_GRID_CATEGORIES' => 'bx_forum_categories',
+            'OBJECT_STORAGE' => 'bx_forum_covers',
+            'OBJECT_STORAGE_FILES' => 'bx_forum_files',
+            'OBJECT_STORAGE_PHOTOS' => 'bx_forum_photos',
+            'OBJECT_STORAGE_VIDEOS' => 'bx_forum_videos',
+            'OBJECT_STORAGE_CMTS' => 'bx_forum_files_cmts', // for comments
+            'OBJECT_IMAGES_TRANSCODER_PREVIEW' => 'bx_forum_preview',
+            'OBJECT_IMAGES_TRANSCODER_MINIATURE' => 'bx_forum_miniature',
+            'OBJECT_IMAGES_TRANSCODER_GALLERY' => 'bx_forum_gallery',
+            'OBJECT_IMAGES_TRANSCODER_COVER' => 'bx_forum_cover',
+            'OBJECT_IMAGES_TRANSCODER_PREVIEW_FILES' => 'bx_forum_preview_files',
+            'OBJECT_IMAGES_TRANSCODER_GALLERY_FILES' => 'bx_forum_gallery_files',
+            'OBJECT_IMAGES_TRANSCODER_PREVIEW_PHOTOS' => 'bx_forum_preview_photos',
+            'OBJECT_IMAGES_TRANSCODER_MINIATURE_PHOTOS' => 'bx_forum_miniature_photos',
+            'OBJECT_IMAGES_TRANSCODER_GALLERY_PHOTOS' => 'bx_forum_gallery_photos',
+            'OBJECT_IMAGES_TRANSCODER_VIEW_PHOTOS' => 'bx_forum_view_photos',
+            'OBJECT_IMAGES_TRANSCODER_PREVIEW_CMTS' => 'bx_forum_preview_cmts', // for comments
+            'OBJECT_VIDEOS_TRANSCODERS' => array(
+                'poster' => 'bx_forum_videos_poster', 
+            	'poster_preview' => 'bx_forum_videos_poster_preview',
+            	'mp4' => 'bx_forum_videos_mp4', 
+            	'mp4_hd' => 'bx_forum_videos_mp4_hd'
+            ),
+            'OBJECT_VIDEO_TRANSCODER_HEIGHT' => '480px',
+            'OBJECT_REPORTS' => 'bx_forum',
+            'OBJECT_VIEWS' => 'bx_forum',
+            'OBJECT_VOTES' => 'bx_forum',
+            'OBJECT_REACTIONS' => 'bx_forum_reactions',
+            'OBJECT_SCORES' => 'bx_forum',
+            'OBJECT_FAVORITES' => 'bx_forum',
+            'OBJECT_FEATURED' => 'bx_forum',
+            'OBJECT_CATEGORY' => 'bx_forum_cats',
+            'OBJECT_COMMENTS' => 'bx_forum',
+            'OBJECT_NOTES' => 'bx_forum_notes',
+            'OBJECT_METATAGS' => 'bx_forum',
+            'OBJECT_PRIVACY_VIEW' => 'bx_forum_allow_view_to',
+            'OBJECT_PRIVACY_LIST_VIEW' => 'bx_forum_allow_view_favorite_list',
+            'OBJECT_FORM_ENTRY' => 'bx_forum',
+            'OBJECT_FORM_ENTRY_DISPLAY_VIEW' => 'bx_forum_entry_view',
+            'OBJECT_FORM_ENTRY_DISPLAY_ADD' => 'bx_forum_entry_add',
+            'OBJECT_FORM_ENTRY_DISPLAY_EDIT' => 'bx_forum_entry_edit',
+            'OBJECT_FORM_ENTRY_DISPLAY_DELETE' => 'bx_forum_entry_delete',
+            'OBJECT_FORM_POLL' => 'bx_forum_poll',
+            'OBJECT_FORM_POLL_DISPLAY_ADD' => 'bx_forum_poll_add',
+            'OBJECT_FORM_SEARCH' => 'bx_forum_search',
+            'OBJECT_FORM_SEARCH_DISPLAY_FULL' => 'bx_forum_search_full',
+            'OBJECT_MENU_ENTRY_ATTACHMENTS' => 'bx_forum_entry_attachments', // attachments menu in create/edit forms
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY' => 'bx_forum_view', // actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE' => 'bx_forum_view_more', // more actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL' => 'bx_forum_view_actions', // all actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_MY_ENTRIES' => 'bx_forum_my', // actions menu on my entries page
+            'OBJECT_MENU_SNIPPET_META' => 'bx_forum_snippet_meta_main', // menu for 'main' snippet meta info
+            'OBJECT_MENU_SNIPPET_META_MAIN' => 'bx_forum_snippet_meta_main', // menu for 'main' snippet meta info
+            'OBJECT_MENU_SNIPPET_META_COUNTERS' => 'bx_forum_snippet_meta_counters', // menu for 'counters' snippet meta info
+            'OBJECT_MENU_SNIPPET_META_REPLY' => 'bx_forum_snippet_meta_reply', // menu for 'reply' snippet meta info
+            'OBJECT_MENU_MANAGE_TOOLS' => 'bx_forum_menu_manage_tools', //manage menu in content administration tools
+            'OBJECT_MENU_SUBMENU' => 'bx_forum_submenu', // main module submenu
+            'OBJECT_MENU_SUBMENU_VIEW_ENTRY' => '', // view entry submenu
+            'OBJECT_MENU_SUBMENU_VIEW_ENTRY_MAIN_SELECTION' => 'forum', // first item in view entry submenu from main module submenu
+            'OBJECT_UPLOADERS' => array('sys_html5'),
+            'OBJECT_CONNECTION_SUBSCRIBERS' => 'bx_forum_subscribers',
+
+            'BADGES_AVALIABLE' => true,
+
+            // menu items which visibility depends on custom visibility checking
+            'MENU_ITEM_TO_METHOD' => array (
+                'bx_forum_view' => $aMenuItems2Methods,
+                'bx_forum_view_more' => $aMenuItems2Methods,
+            ),
+
+            // informer messages
+            'INFORMERS' => array (
+                'approving' => array (
+                    'name' => 'bx-forum-approving',
+                    'map' => array (
+                        'pending' => array('msg' => '_bx_forum_txt_msg_status_pending', 'type' => BX_INFORMER_ALERT),
+                        'hidden' => array('msg' => '_bx_forum_txt_msg_status_hidden', 'type' => BX_INFORMER_ERROR),
+                    ),
+                ),
+            ),
+
+            // some language keys
+            'T' => array (
+                'txt_sample_single' => '_bx_forum_txt_sample_single',
+            	'txt_sample_single_with_article' => '_bx_forum_txt_sample_single_with_article',
+            	'txt_sample_comment_single' => '_bx_forum_txt_sample_comment_single',
+            	'txt_sample_vote_single' => '_bx_forum_txt_sample_vote_single',
+                'txt_sample_reaction_single' => '_bx_forum_txt_sample_reaction_single',
+                'txt_sample_score_up_single' => '_bx_forum_txt_sample_score_up_single',
+                'txt_sample_score_down_single' => '_bx_forum_txt_sample_score_down_single',
+            	'form_field_author' => '_bx_forum_form_entry_input_author',
+                'form_field_covers_uploader_simple' => '_bx_forum_form_entry_input_covers_uploader_simple_title',
+                'form_field_covers_uploader_html5' => '_bx_forum_form_entry_input_covers_uploader_html5_title',
+            	'grid_action_err_delete' => '_bx_forum_grid_action_err_delete',
+            	'grid_txt_account_manager' => '_bx_forum_grid_txt_account_manager',
+            	'filter_item_active' => '_bx_forum_grid_filter_item_title_adm_active',
+            	'filter_item_hidden' => '_bx_forum_grid_filter_item_title_adm_hidden',
+                'filter_item_pending' => '_bx_forum_grid_filter_item_title_adm_pending',
+            	'filter_item_select_one_filter1' => '_bx_forum_grid_filter_item_title_adm_select_one_filter1',
+                'filter_item_select_one_filter2' => '_bx_forum_grid_filter_item_title_adm_select_one_filter2',
+            	'menu_item_manage_my' => '_bx_forum_menu_item_title_manage_my',
+            	'menu_item_manage_all' => '_bx_forum_menu_item_title_manage_all',
+                'txt_all_entries_in' => '_bx_forum_txt_all_entries_in',
+            	'txt_all_entries_by_author' => '_bx_forum_page_title_browse_by_author',
+                'txt_all_entries_by_context' => '_bx_forum_page_title_browse_by_context',
+                'txt_all_categories' => '_bx_forum_txt_all_categories',
+            	'txt_pict_use_as_thumb' => '_bx_forum_form_entry_input_picture_use_as_thumb',
+                'txt_poll_form_answers_add' => '_bx_forum_form_poll_input_answers_add',
+                'txt_poll_menu_view_answers' => '_bx_forum_txt_poll_view_answers',
+                'txt_poll_menu_view_results' => '_bx_forum_txt_poll_view_results',
+                'txt_poll_menu_view_' => '_bx_forum_txt_poll_view_',
+                'txt_poll_answer_vote_do_by' => '_bx_forum_txt_poll_answer_vote_do_by',
+                'txt_poll_answer_vote_counter' => '_bx_forum_txt_poll_answer_vote_counter',
+                'txt_poll_answer_vote_percent' => '_bx_forum_txt_poll_answer_vote_percent'
+            ),
+        ));
+
+        $this->_aPrefixes = array(
+            'style' => 'bx-forum',
+        );
+        
+        $this->_aJsClasses = array_merge($this->_aJsClasses, array(
+            'main' => 'BxForumMain',
+            'entry' => 'BxForumEntry',
+            'manage_tools' => 'BxForumManageTools',
+            'studio' => 'BxForumStudio',
+            'categories' => 'BxDolCategories'
+        ));
+
+        $this->_aJsObjects = array_merge($this->_aJsObjects, array(
+            'main' => 'oBxForumMain',
+            'entry' => 'oBxForumEntry',
+            'manage_tools' => 'oBxForumManageTools',
+            'studio' => 'oBxForumStudio',
+            'categories' => 'oBxDolCategories'
+        ));
+
+        $this->_aGridObjects = array(
+            'common' => $this->CNF['OBJECT_GRID_COMMON'],
+            'administration' => $this->CNF['OBJECT_GRID_ADMINISTRATION'],
+            'main' => $this->CNF['OBJECT_GRID'],
+        );
+
+        $this->_bAttachmentsInTimeline = true;
+    }
+}
+
+/** @} */

+ 661 - 0
modules/boonex/forum/updates/15.0.4_15.0.5/source/classes/BxForumGrid.php

@@ -0,0 +1,661 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Forum Forum
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+define('BX_FORUM_FILTER_STATUS_RESOLVED', 'yes');
+define('BX_FORUM_FILTER_STATUS_UNRESOLVED', 'no');
+
+define('BX_FORUM_FILTER_ORDER_RECENT', 'latest');
+define('BX_FORUM_FILTER_ORDER_NEW', 'new');
+define('BX_FORUM_FILTER_ORDER_TOP', 'top');
+define('BX_FORUM_FILTER_ORDER_UPDATED', 'updated');
+define('BX_FORUM_FILTER_ORDER_POPULAR', 'popular');
+define('BX_FORUM_FILTER_ORDER_FEATURED', 'featured');
+define('BX_FORUM_FILTER_ORDER_FAVORITE', 'favorite');
+define('BX_FORUM_FILTER_ORDER_PARTAKEN', 'partaken');
+
+class BxForumGrid extends BxBaseModGeneralGrid
+{
+    protected $_sDefaultSource; 
+
+    protected $_sFilter1Name;
+    protected $_sFilter1Value;
+    protected $_aFilter1Values;
+    
+    protected $_sFilter2Name;
+    protected $_sFilter2Value;
+    protected $_aFilter2Values;
+    
+    protected $_sFilter3Name;
+    protected $_sFilter3Value;
+    protected $_aFilter3Values;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_forum';
+
+        parent::__construct ($aOptions, $oTemplate);
+        
+        $this->_sFilter1Name = 'filter1';
+        $this->_aFilter1Values = [
+            '' => _t('_bx_forum_grid_filter_resolved_all'),
+            BX_FORUM_FILTER_STATUS_RESOLVED => _t('_bx_forum_grid_filter_resolved_resolved'),
+            BX_FORUM_FILTER_STATUS_UNRESOLVED => _t('_bx_forum_grid_filter_resolved_unresolved'),
+        ];
+
+        if(($sFilter1 = bx_get($this->_sFilter1Name)) !== false) {
+            $this->_sFilter1Value = bx_process_input($sFilter1);
+            $this->_aQueryAppend['filter1'] = $this->_sFilter1Value;
+        }
+
+        $this->_sFilter2Name = 'filter2';
+        $this->_aFilter2Values = [];
+
+        $aBadges = BxDolBadges::getInstance()->getData(['type' => 'by_module', 'module' => $this->_oModule->_aModule['name']]);
+        if(is_array($aBadges) && count($aBadges) > 0) {
+            $this->_aFilter2Values[''] = _t('_bx_forum_grid_filter_badges_all');
+
+            foreach($aBadges as $aBadge)
+                $this->_aFilter2Values[$aBadge['id']] = $aBadge['text'];
+        }
+
+        if(($sFilter2 = bx_get($this->_sFilter2Name)) !== false) {
+            $this->_sFilter2Value = bx_process_input($sFilter2);
+            $this->_aQueryAppend['filter2'] = $this->_sFilter2Value;
+        }
+
+        $this->_sFilter3Name = 'filter3';
+        $this->_aFilter3Values = [
+            BX_FORUM_FILTER_ORDER_RECENT => _t('_bx_forum_grid_filter_order_updated'),
+            BX_FORUM_FILTER_ORDER_NEW => _t('_bx_forum_grid_filter_order_new'),
+            BX_FORUM_FILTER_ORDER_TOP => _t('_bx_forum_grid_filter_order_top'),
+            BX_FORUM_FILTER_ORDER_POPULAR => _t('_bx_forum_grid_filter_order_popular'),
+            BX_FORUM_FILTER_ORDER_FEATURED => _t('_bx_forum_grid_filter_order_featured'),
+            BX_FORUM_FILTER_ORDER_FAVORITE => _t('_bx_forum_grid_filter_order_favorite'),
+            BX_FORUM_FILTER_ORDER_PARTAKEN => _t('_bx_forum_grid_filter_order_partaken'),
+        ];
+
+        if(($sFilter3 = bx_get($this->_sFilter3Name)) !== false) {
+            $this->_sFilter1Value = bx_process_input($sFilter3);
+            $this->_aQueryAppend['filter3'] = $this->_sFilter1Value;
+        }
+
+        $this->_aFilterAreaLabels = [
+            $this->_sFilter3Name => '_sys_grid_lbl_order', // "Order": filter3 has no placeholder option to borrow a name from
+        ];
+
+        $this->_sDefaultSortingOrder = 'DESC';
+        $this->_sDefaultSource = $this->_aOptions['source'];
+    }
+
+    public function setBrowseParams($aParams)
+    {
+        if(isset($aParams['ajax_paginate'])) {
+            if(!$aParams['ajax_paginate']) {
+                list($sPageLink, $aPageParams) = bx_get_base_url_inline();
+
+                $aPageParams[$this->_aOptions['paginate_get_start']] = '{start}';
+                $this->_aOptions['paginate_url'] = BxDolPermalinks::getInstance()->permalink(bx_append_url_params($sPageLink, $aPageParams, true, ['{start}']));
+            }
+
+            unset($aParams['ajax_paginate']);
+        }
+        
+        if ($aParams['type'])
+            $this->_sFilter3Value = $aParams['type'];
+
+        parent::setBrowseParams($aParams);
+
+    	$sField = 'added';
+    	if(!empty($this->_aBrowseParams['type']))
+            switch($this->_aBrowseParams['type']) {
+                case 'new':
+                case 'index':
+                case 'author':
+                case 'favorite':
+                case 'category':
+                    $sField = 'added';
+                    break;
+
+                case 'featured':
+                    $sField = 'featured';
+                    break;
+
+                case 'latest':
+                    $sField = 'lr_timestamp';
+                    break;
+
+                case 'popular':
+                    $sField = 'views';
+                    break;
+            }
+
+        $this->_aOptions['field_order'] = $sField;
+        $this->_aOptions['paginate_per_page'] = !empty($this->_aBrowseParams['per_page']) ? (int)$this->_aBrowseParams['per_page'] : (int)$this->_oModule->_oDb->getParam('bx_forum_per_page_browse');
+    }
+
+    public function getCode ($isDisplayHeader = true)
+    {
+        $sCode = parent::getCode($isDisplayHeader);
+        if(empty($sCode))
+            return '';
+
+    	return $this->_oModule->_oTemplate->parseHtmlByName('units.html', array(
+            'code' => $sCode,
+            'js_code' => $this->_oModule->_oTemplate->getJsCode('main')
+        ));
+    }
+
+    protected function _getActionAdd ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if($this->_oModule->checkAllowedAdd() !== CHECK_ACTION_RESULT_ALLOWED)
+            return '';
+
+        $sUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->_oModule->_oConfig->CNF['URI_ADD_ENTRY']));
+
+        unset($a['attr']['bx_grid_action_independent']);
+        $a['attr'] = array_merge($a['attr'], array(
+    		"onclick" => "window.open('" . $sUrl . "','_self');"
+    	));
+
+        return parent::_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getRowHead ()
+    {
+    	return array();
+    }
+
+    protected function _getCellDefault ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $aField['attr_cell']['class'] = 'bx-grid-table-cell-' . $sKey;
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAuthor($mixedValue, $sKey, $aField, $aRow)
+    {
+    	$mixedValue = $this->_oModule->_oTemplate->getEntryAuthor($aRow);
+
+        return self::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellCategory($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sIcon = $this->_oModule->_oTemplate->parseHtmlByName('default_category.html', []);
+        $o = BxDolCategory::getObjectInstance('bx_forum_cats');
+        
+        $aCategoryData = $this->_oModule->_oDb->getCategories(array('type' => 'by_category', 'category' => $aRow['cat']));
+
+        if(isset($aCategoryData['icon']))
+            $sIcon = $this->_oTemplate->getImage($aCategoryData['icon'], array('class' => 'sys-icon'));
+        
+        $mixedValue = $this->_oModule->_oTemplate->parseHtmlByName('thumb.html', ['icon' => $sIcon, 'title' => $o->getCategoryTitle($aRow['cat'])]);
+        return self::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellRating($mixedValue, $sKey, $aField, $aRow)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oObject = isset($CNF['OBJECT_SCORES']) ? BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], $aRow[$CNF['FIELD_ID']], true, $this->_oModule->_oTemplate) : null;
+        $mixedValue = $oObject ? $oObject->getElementInline() : '';
+        
+        return self::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellParticipants($mixedValue, $sKey, $aField, $aRow)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+    	$oObject = isset($CNF['OBJECT_COMMENTS']) ? BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $aRow[$CNF['FIELD_ID']]) : null;
+      
+        $mixedValue = $oObject ? $oObject->getCounter(['show_counter_empty' => true, 'show_counter' => false, 'show_counter_style' => 'simple', 'dynamic_mode' => true, 'show_icon' => false, 'caption_empty' => '']) : '';
+
+        return self::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellText($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = $this->_oModule->_oTemplate->getEntryPreview($aRow, ['show_meta_counters' => false, 'show_meta_reply' => false]);
+
+        return self::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+
+        $this->_oModule->_oTemplate->addJs(array('main.js'));
+        $this->_oModule->_oTemplate->addCss(array('main.css', 'main-media-phone.css', 'main-media-tablet.css', 'main-media-desktop.css'));
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        
+        if(strpos($sFilter, $this->_sParamsDivider) !== false)
+            list($this->_sFilter1Value, $this->_sFilter2Value, $this->_sFilter3Value, $sFilter) = explode($this->_sParamsDivider, $sFilter);
+
+        $bAuthor = false;
+        $iAuthorId = bx_get_logged_profile_id();
+    	if(!empty($this->_aBrowseParams['author']) && ($oProfileAuthor = BxDolProfile::getInstance((int)$this->_aBrowseParams['author'])) !== false) {
+            $bAuthor = true;
+            $iAuthorId = $oProfileAuthor->id();
+    	}
+
+        // featured
+        if ($this->_sFilter3Value == BX_FORUM_FILTER_ORDER_FEATURED) {
+            $this->_aBrowseParams['where'] = ['fld' => 'featured', 'val' => 0, 'opr' => '<>'];
+        }
+        
+        // partaken
+        if ($this->_sFilter3Value == BX_FORUM_FILTER_ORDER_PARTAKEN) {
+            $this->_aBrowseParams['select'] = [
+                'tbla' => 'tco', 
+                'fld' => 'cmt_author_id',
+            ];
+
+            $this->_aBrowseParams['join'] = [
+                'tp' => 'LEFT',
+                'tbl1' => 'bx_forum_cmts',
+                'tbl1a' => 'tco',
+                'fld1' => 'cmt_object_id',
+                'tbl2' => 'bx_forum_discussions',
+                'fld2' => 'id'
+            ];
+
+            $this->_aBrowseParams['where'] = ['grp' => true, 'opr' => 'OR', 'cnds' => [[
+                    'tbl' => 'bx_forum_discussions',
+                    'fld' => 'author',
+                    'val' => $iAuthorId, 
+                    'opr' => '='
+                ], [
+                    'tbl' => 'tco', 
+                    'fld' => 'cmt_author_id', 
+                    'val' => $iAuthorId, 
+                    'opr' => '='
+                ]
+            ]];
+
+            $this->_aBrowseParams['group_by'] = [
+                'tbl' => 'bx_forum_discussions', 
+                'fld' => 'id',
+            ];
+        }
+
+        //FAVORITE
+        if ($this->_sFilter3Value == BX_FORUM_FILTER_ORDER_FAVORITE){
+            $oProfile = BxDolProfile:: getInstance(bx_process_input(bx_get('profile_id'), BX_DATA_INT));
+            if(!$oProfile)
+                $oProfile = BxDolProfile::getInstance();
+            if(!$oProfile)
+                return '';
+
+            $iProfileAuthor = $oProfile->id();
+            $oFavorite = $this->_oModule->getObjectFavorite();
+            if(!$oFavorite->isPublic() && $iProfileAuthor != bx_get_logged_profile_id())
+                return '';
+            
+            $aConditions = $oFavorite->getConditionsTrack($CNF['TABLE_ENTRIES'], 'id', $iProfileAuthor);
+            if(empty($aConditions) || !is_array($aConditions)) 
+                return '';
+
+            $aJoinGroup = array('grp' => true, 'cnds' => array());
+            if(!empty($aConditions['join']))
+                foreach($aConditions['join'] as $aCondition)
+                    $aJoinGroup['cnds'][] = array(
+                        'tp' => $aCondition['type'],
+                        'tbl1' => $aCondition['table'],
+                        'fld1' => $aCondition['onField'],
+                        'tbl2' => $aCondition['mainTable'],
+                        'fld2' => $aCondition['mainField']
+                    );
+
+            $aWhereGroup = array('grp' => true, 'opr' => 'AND', 'cnds' => array());
+            if(!empty($aConditions['restriction']))
+                foreach($aConditions['restriction'] as $aCondition)
+                    $aWhereGroup['cnds'][] = array(
+                        'tbl' => (!empty($aCondition['table']) ? $aCondition['table'] : ''), 
+                        'fld' => $aCondition['field'], 
+                        'val' => $aCondition['value'], 
+                        'opr' => $aCondition['operator']
+                    );
+            
+            $this->_aBrowseParams['author'] = $iProfileAuthor; 
+            $this->_aBrowseParams['join'] = $aJoinGroup;
+            $this->_aBrowseParams['where'] = $aWhereGroup; 
+        }
+        
+    	$sSelectClause = $sJoinClause = $sWhereClause = $sGroupByClause = '';
+
+    	//--- Check status
+    	$sWhereClause .= " AND `{$CNF['TABLE_ENTRIES']}`.`" . $CNF['FIELD_STATUS'] . "`='active'";
+    	$sWhereClause .= " AND `{$CNF['TABLE_ENTRIES']}`.`" . $CNF['FIELD_STATUS_ADMIN'] . "`='active'";
+
+    	//--- Check privacy
+        $bIncludeContexts = isset($this->_aBrowseParams['include_contexts']) && $this->_aBrowseParams['include_contexts'];
+
+        $sPrivacy = $CNF['OBJECT_PRIVACY_VIEW'];
+        $oPrivacy = BxDolPrivacy::getObjectInstance($sPrivacy);
+        $aCondition = $oPrivacy ? $oPrivacy->{'getContentPublic' . ($bIncludeContexts ? 'AndInContext' : '') . 'AsSQLPart'}($bAuthor ? $iAuthorId : 0) : [];
+        if(isset($aCondition['join']))
+            $sJoinClause .= $aCondition['join'];
+        if(isset($aCondition['where']))
+            $sWhereClause .= $aCondition['where'];
+
+        //--- Check browse params
+        if(!empty($this->_aBrowseParams['select']) && is_array($this->_aBrowseParams['select'])) {
+            $sSelectClauseBrowse = $this->_getSqlSelectFromGroup($this->_aBrowseParams['select']);
+            if(!empty($sSelectClauseBrowse))
+                $sSelectClause .= ", " . $sSelectClauseBrowse;
+        }
+
+        if(!empty($this->_aBrowseParams['join']) && is_array($this->_aBrowseParams['join'])) {
+            $sJoinClauseBrowse = $this->_getSqlJoinFromGroup($this->_aBrowseParams['join']);
+            if(!empty($sJoinClauseBrowse))
+                $sJoinClause .= " " . $sJoinClauseBrowse;
+        }
+
+        if(!empty($this->_aBrowseParams['where']) && is_array($this->_aBrowseParams['where'])) {
+            $sWhereClauseBrowse = $this->_getSqlWhereFromGroup($this->_aBrowseParams['where']);
+            if(!empty($sWhereClauseBrowse))
+                $sWhereClause .= " AND " . $sWhereClauseBrowse;
+        }
+
+        if(!empty($this->_aBrowseParams['group_by']) && is_array($this->_aBrowseParams['group_by'])) {
+            $sGroupByClauseBrowse = $this->_getSqlGroupByFromGroup($this->_aBrowseParams['group_by']);
+            if(!empty($sGroupByClauseBrowse))
+                $sGroupByClause .= " GROUP BY " . $sGroupByClauseBrowse;
+        }
+        
+        $sFilterSql = "";
+
+        // filter by resolved status
+        if(!empty($this->_sFilter1Value))
+            $sWhereClause .= " AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_RESOLVABLE'] . "` = 1 AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_RESOLVE'] . "` = " . ($this->_sFilter1Value == BX_FORUM_FILTER_STATUS_RESOLVED ? 1 : 0);
+
+        // filter out posts of suspended members
+        $sJoinClause .= " INNER JOIN `sys_profiles` AS `p` ON (`p`.`id` = ABS(`{$CNF['TABLE_ENTRIES']}`.`{$CNF['FIELD_AUTHOR']}`) AND `p`.`status` = 'active') ";
+
+        // filter out posts by content filter
+        $oCf = BxDolContentFilter::getInstance();
+        if($oCf->isEnabled())
+            $sWhereClause .= $oCf->getSQLParts ($CNF['TABLE_ENTRIES'], $CNF['FIELD_CF']);
+
+        // filter by badges
+        if(isset($this->_sFilter2Value) && $this->_sFilter2Value != '') {
+            $aObjects = BxDolBadges::getInstance()->getData(['type' => 'by_module&badge', 'badge_id' => $this->_sFilter2Value, 'module' => $this->_oModule->_aModule['name']]);
+            $sWhereClause .= " AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_ID'] . "` IN (" . implode(',', $aObjects ) . ")";
+        }
+
+        $this->_aOptions['source'] = sprintf($this->_sDefaultSource, $sSelectClause, $sJoinClause, $sWhereClause, $sGroupByClause);
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+
+    protected function _getDataSqlOrderClause($sOrderByFilter, $sOrderField, $sOrderDir, $bFieldsOnly = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(isset($this->_sFilter3Value) && $this->_sFilter3Value != ''){
+            $sOrderField = $this->_aBrowseParams['type'] = $this->_sFilter3Value;
+            if (!empty($this->_aBrowseParams['type']) && $this->_aBrowseParams['type'] != 'partaken'){
+                $this->_aBrowseParams['type'] = $this->_aBrowseParams['type'] = $this->_sFilter3Value;
+            }
+        }
+        
+    	$sOrder = parent::_getDataSqlOrderClause($sOrderByFilter, $sOrderField, $sOrderDir, true);
+                
+        if(!empty($this->_aBrowseParams['type']))
+            switch($this->_aBrowseParams['type']) {
+                case 'top':
+                    $aPartsUp = $aPartsDown = array(0);
+                    if(($oVote = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], 0, false)) !== false && $oVote->isEnabled()) {
+                        $aVote = $oVote->getSystemInfo();
+                        if(!empty($aVote['trigger_table']) && !empty($aVote['trigger_field_count']))
+                            $aPartsUp[] = '`' . $aVote['trigger_table'] . '`.`' . $aVote['trigger_field_count'] . '`';
+                    }
+
+                    if(($oReaction = BxDolVote::getObjectInstance($CNF['OBJECT_REACTIONS'], 0, false)) !== false && $oReaction->isEnabled()) {
+                        $aReaction = $oReaction->getSystemInfo();
+                        if(!empty($aReaction['trigger_table']) && !empty($aReaction['trigger_field_count']))
+                            $aPartsUp[] = '`' . $aReaction['trigger_table'] . '`.`' . $aReaction['trigger_field_count'] . '`';
+                    }
+
+                    if(($oScore = BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], 0, false)) !== false && $oScore->isEnabled()) {
+                        $aScore = $oScore->getSystemInfo();
+                        if(!empty($aScore['trigger_table']) && !empty($aScore['trigger_field_cup']) && !empty($aScore['trigger_field_cdown'])) {
+                            $aPartsUp[] = '`' . $aScore['trigger_table'] . '`.`' . $aScore['trigger_field_cup'] . '`';
+                            $aPartsDown[] = '`' . $aScore['trigger_table'] . '`.`' . $aScore['trigger_field_cdown'] . '`';
+                        }
+                    }
+
+                    $sOrder = pow(10, 8) . ' * ((' . implode(' + ', $aPartsUp) . ') - (' . implode(' + ', $aPartsDown) . ')) / (UNIX_TIMESTAMP() - `' . $this->_aOptions['table'] . '`.`' . $CNF['FIELD_ADDED'] . '`) ' . $this->_sDefaultSortingOrder;
+                    break;
+
+                case 'partaken':
+                    $sOrder = 'MAX(IF(`tco`.`cmt_time`, `tco`.`cmt_time`, `' . $CNF['TABLE_ENTRIES'] . '`.`' . $CNF['FIELD_ADDED'] . '`)) ' . $this->_sDefaultSortingOrder;
+                    break;
+            }           
+
+    	return " ORDER BY `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_STICK'] . "` DESC, " . $sOrder;
+    }
+
+    protected function _getSqlSelectFromGroup($aGrp)
+    {
+        if(!isset($aGrp['grp']) || (bool)$aGrp['grp'] !== true)
+            return $this->_getSqlSelectFromCondition($aGrp);
+
+        $sResult = "";
+        if(empty($aGrp['cnds']) || !is_array($aGrp['cnds']))
+            return $sResult;
+
+        $sOprGrp = ", ";
+        foreach($aGrp['cnds'] as $aCnd) {
+            $sResultCnd = $this->_getSqlSelectFromCondition($aCnd);
+            if(!empty($sResultCnd))
+                $sResult .= $sOprGrp . $sResultCnd;
+        }
+
+        return trim($sResult, $sOprGrp);
+    }
+
+    protected function _getSqlSelectFromCondition($aCnd)
+    {
+    	$sResult = "";
+    	if(!isset($aCnd['fld']))
+            return $sResult;
+
+        $sResult = "`" . $aCnd['fld'] . "`";
+        if(!empty($aCnd['tbla']))
+            $sResult = "`" . $aCnd['tbla'] . "`." . $sResult;
+        else if(!empty($aCnd['tbl']))
+            $sResult = "`" . $aCnd['tbl'] . "`." . $sResult;
+
+        return $sResult;
+    }
+
+    protected function _getSqlJoinFromGroup($aGrp)
+    {
+        if(!isset($aGrp['grp']) || (bool)$aGrp['grp'] !== true)
+            return $this->_getSqlJoinFromCondition($aGrp);
+
+        $sResult = "";
+        if(empty($aGrp['cnds']) || !is_array($aGrp['cnds']))
+            return $sResult;
+
+        $sOprGrp = " ";
+        foreach($aGrp['cnds'] as $aCnd) {
+            $sResultCnd = $this->_getSqlJoinFromCondition($aCnd);
+            if(!empty($sResultCnd))
+                $sResult .= $sOprGrp . $sResultCnd;
+        }
+
+        return trim($sResult, $sOprGrp);
+    }
+
+    protected function _getSqlJoinFromCondition($aCnd)
+    {
+    	$sResult = "";
+    	if(!isset($aCnd['tp'], $aCnd['tbl1'], $aCnd['fld1'], $aCnd['fld2']))
+            return $sResult;
+
+        $sTbl1A = $aCnd['tbl1'];
+        if(!empty($aCnd['tbl1a']))
+            $sTbl1A = $aCnd['tbl1a'];
+
+        $sField1 = "`" . $sTbl1A . "`.`" . $aCnd['fld1'] . "`";
+
+        $sField2 = "`" . $aCnd['fld2'] . "`";
+        if(!empty($aCnd['tbl2a']))
+            $sField2 = "`" . $aCnd['tbl2a'] . "`." . $sField2;
+        else if(!empty($aCnd['tbl2']))
+            $sField2 = "`" . $aCnd['tbl2'] . "`." . $sField2;
+
+        return $aCnd['tp'] . " JOIN `" . $aCnd['tbl1'] . "` AS `" . $sTbl1A . "` ON (" . $sField1 . " = " . $sField2 . ")";
+    }
+
+    protected function _getSqlWhereFromGroup($aGrp)
+    {
+        if(!isset($aGrp['grp']) || (bool)$aGrp['grp'] !== true)
+            return $this->_getSqlWhereFromCondition($aGrp);
+
+        $sResult = "";
+        if(!isset($aGrp['opr'], $aGrp['cnds']) || empty($aGrp['cnds']) || !is_array($aGrp['cnds']))
+            return $sResult;
+
+        $sOprGrp = " " . $aGrp['opr'] . " ";
+        foreach($aGrp['cnds'] as $aCnd) {
+            $sMethod = '_getSqlWhereFrom' . (isset($aGrp['grp']) && (bool)$aGrp['grp'] === true ? 'Group' : 'Condition');
+            $sResultCnd = $this->$sMethod($aCnd);
+            if(!empty($sResultCnd))
+                $sResult .= $sOprGrp . $sResultCnd;
+        }
+
+        $sResult = trim($sResult, $sOprGrp);
+        if(!empty($sResult))
+            $sResult = "(" . $sResult . ")";
+
+    	return $sResult;
+    }
+
+    protected function _getSqlWhereFromCondition($aCnd)
+    {
+        $sResult = "";
+        if(!isset($aCnd['fld'], $aCnd['val'], $aCnd['opr']))
+            return $sResult;
+
+        $sMethod = '_getSqlWhereFromCondition' . bx_gen_method_name($aCnd['fld']);
+        if(method_exists($this, $sMethod)) {
+            $mixedResult = $this->$sMethod($aCnd);
+            if(is_array($mixedResult) && isset($mixedResult['fld'], $mixedResult['val'], $mixedResult['opr']))
+                $aCnd = $mixedResult;
+            else
+                return $mixedResult;
+        }
+        
+        $sField = "`" . $aCnd['fld'] . "`";
+        if(!empty($aCnd['tbl']))
+            $sField = "`" . $aCnd['tbl'] . "`." . $sField;
+
+        switch($aCnd['opr']) {
+            case 'IN':
+                if(empty($aCnd['val']) || !is_array($aCnd['val']))
+                    break;
+
+                $sResult .= $sField . " IN (" . $this->_oModule->_oDb->implode_escape($aCnd['val']) . ")";
+                break;
+
+            case 'LIKE':
+                $sResult .= $sField . " LIKE " . $this->_oModule->_oDb->escape('%' . $aCnd['val'] . '%');
+                break;
+
+            default:
+                $sResult .= $sField . " " . $aCnd['opr'] . " " . $this->_oModule->_oDb->escape($aCnd['val']);
+        }
+
+        return $sResult;
+    }
+
+    protected function _getSqlGroupByFromGroup($aGrp)
+    {
+        if(!isset($aGrp['grp']) || (bool)$aGrp['grp'] !== true)
+            return $this->_getSqlGroupByFromCondition($aGrp);
+
+        $sResult = "";
+        if(empty($aGrp['cnds']) || !is_array($aGrp['cnds']))
+            return $sResult;
+
+        $sOprGrp = ", ";
+        foreach($aGrp['cnds'] as $aCnd) {
+            $sResultCnd = $this->_getSqlGroupByFromCondition($aCnd);
+            if(!empty($sResultCnd))
+                $sResult .= $sOprGrp . $sResultCnd;
+        }
+
+        return trim($sResult, $sOprGrp);
+    }
+
+    protected function _getSqlGroupByFromCondition($aCnd)
+    {
+    	$sResult = "";
+    	if(!isset($aCnd['fld']))
+            return $sResult;
+
+        $sResult = "`" . $aCnd['fld'] . "`";
+        if(!empty($aCnd['tbla']))
+            $sResult = "`" . $aCnd['tbla'] . "`." . $sResult;
+        else if(!empty($aCnd['tbl']))
+            $sResult = "`" . $aCnd['tbl'] . "`." . $sResult;
+
+        return $sResult;
+    }
+
+    protected function _getSqlWhereFromConditionAuthorComment($aCnd)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aEntriesIds = $this->_oModule->_oDb->getComments(array('type' => 'entries_author_search', 'author' => $aCnd['val']));
+        if(!empty($aEntriesIds) && is_array($aEntriesIds))
+            return array('tbl' => $CNF['TABLE_ENTRIES'], 'fld' => 'id', 'val' => $aEntriesIds, 'opr' => 'IN');
+
+        return '';
+    }
+
+    protected function _getSqlWhereFromConditionKeywordComment($aCnd)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aEntriesIds = $this->_oModule->_oDb->getComments(array('type' => 'entries_keyword_search', 'keyword' => $aCnd['val']));
+        if(!empty($aEntriesIds) && is_array($aEntriesIds))
+            return array('tbl' => $CNF['TABLE_ENTRIES'], 'fld' => 'id', 'val' => $aEntriesIds, 'opr' => 'IN');
+
+        return '';
+    }
+    
+    protected function _getFilterControls ()
+    {
+        parent::_getFilterControls();
+        return  $this->_getFilterSelectOne($this->_sFilter3Name, $this->_sFilter3Value, $this->_aFilter3Values) . $this->_getFilterSelectOne($this->_sFilter1Name, $this->_sFilter1Value, $this->_aFilter1Values) . (count($this->_aFilter2Values) > 0 ? $this->_getFilterSelectOne($this->_sFilter2Name, $this->_sFilter2Value, $this->_aFilter2Values) : '') . $this->_getSearchInput();
+    }
+
+    protected function _getFilterOnChange()
+    {
+        return $this->_oModule->_oConfig->getJsObject('main') . '.onChangeFilter(this)';
+    }
+
+    protected function _getFilterSelectOne($sFilterName, $sFilterValue, $aFilterValues, $bAddSelectOne = false, $bAsArray = false)
+    {
+        return parent::_getFilterSelectOne($sFilterName, $sFilterValue, $aFilterValues, $bAddSelectOne, $bAsArray);
+    }
+}
+
+/** @} */

+ 26 - 0
modules/boonex/forum/updates/15.0.4_15.0.5/source/classes/BxForumMenuCategories.php

@@ -0,0 +1,26 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * Menu representation.
+ * @see BxDolMenu
+ */
+class BxForumMenuCategories extends BxBaseModTextMenuCategories
+{
+    public function __construct ($aObject, $oTemplate)
+    {
+        $this->_sModule = 'bx_forum';
+
+        parent::__construct ($aObject, $oTemplate);
+        
+        $this->_bExtendedMode = true;
+    }
+}
+
+/** @} */

+ 151 - 0
modules/boonex/forum/updates/15.0.4_15.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    Forum Forum
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_forum',
+    'title' => 'Discussions',
+    'note' => 'Discussions module.',
+    'version' => '15.0.5',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/forum/',
+    'home_uri' => 'forum',
+
+    'db_prefix' => 'bx_forum_',
+    'class_prefix' => 'BxForum',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Discussions',
+    
+    /**
+     * 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_forum_files'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+        'bx_forum_preview',
+        'bx_forum_miniature', 
+        'bx_forum_gallery',
+        'bx_forum_cover',
+
+        'bx_forum_preview_photos',
+        'bx_forum_miniature_photos', 
+        'bx_forum_gallery_photos',
+        'bx_forum_view_photos',
+
+        'bx_forum_videos_poster',
+        'bx_forum_videos_poster_preview',
+        'bx_forum_videos_mp4',
+        'bx_forum_videos_mp4_hd',
+
+        'bx_forum_preview_files',
+        'bx_forum_gallery_files',
+
+        'bx_forum_preview_cmts',
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_forum',
+    	'bx_forum_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/google_connect/updates/15.0.0_15.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' => 'Google connect',
+    'version_from' => '15.0.0',
+    'version_to' => '15.0.1',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/google_connect/updates/update_15.0.0_15.0.1/',
+    'home_uri' => 'googlecon_update_1500_1501',
+
+    'module_dir' => 'boonex/google_connect/',
+    'module_uri' => 'googlecon',
+
+    'db_prefix' => 'bx_googlecon_',
+    'class_prefix' => 'BxGoogleCon',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Google Connect',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/google_connect/updates/15.0.0_15.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 BxGoogleConUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 2 - 0
modules/boonex/google_connect/updates/15.0.0_15.0.1/install/sql/enable.sql

@@ -0,0 +1,2 @@
+-- OPTIONS
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_googlecon_secret';

+ 68 - 0
modules/boonex/google_connect/updates/15.0.0_15.0.1/source/install/config.php

@@ -0,0 +1,68 @@
+<?php 
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    GoogleConnect Google Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_googlecon',
+    'title' => 'Google connect',
+    'note' => 'Join the site using LinkedIn account.',
+    'version' => '15.0.1',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/google_connect/',
+    'home_uri' => 'googlecon',
+
+    'db_prefix' => 'bx_googlecon_',
+    'class_prefix' => 'BxGoogleCon',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Google Connect',
+
+    /**
+     * 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(),
+);
+
+/** @} */

+ 42 - 0
modules/boonex/google_connect/updates/15.0.0_15.0.1/source/install/sql/enable.sql

@@ -0,0 +1,42 @@
+
+-- Email template
+
+INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `Body`) VALUES
+('bx_googlecon', '_bx_googlecon_et_password_generated', 'bx_googlecon_password_generated', '_bx_googlecon_et_password_generated_subject', '_bx_googlecon_et_password_generated_body');
+
+-- Auth objects
+
+INSERT INTO `sys_objects_auths` (`Name`, `Title`, `Link`, `Icon`, `Style`) VALUES
+('bx_googlecon', '_bx_googlecon_auth_title', 'modules/?r=googlecon/start', 'fab google', 'a:1:{s:7:".bx-btn";a:1:{s:10:"background";s:18:"#cb4023 !important";}}');
+
+-- Alerts
+
+INSERT INTO `sys_alerts_handlers` SET `name` = 'bx_googlecon', `class` = 'BxGoogleConAlerts', `file` = 'modules/boonex/google_connect/classes/BxGoogleConAlerts.php';
+
+SET @iHandlerId := (SELECT `id` FROM `sys_alerts_handlers`  WHERE `name` = 'bx_googlecon');
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('account', 'logout', @iHandlerId),
+('profile', 'delete', @iHandlerId),
+('profile', 'add', @iHandlerId);
+
+-- Options
+
+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_googlecon', '_bx_googlecon_adm_stg_cpt_type', 'bx_googlecon@modules/boonex/google_connect/|std-icon.svg', IF(NOT ISNULL(@iTypeOrder), @iTypeOrder + 1, 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_googlecon_general', '_sys_connect_adm_stg_cpt_category_general', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `order`, `extra`) VALUES
+('bx_googlecon_api_key', '', @iCategId, '_bx_googlecon_option_app_id', 'digit', '', '', 10, ''),
+('bx_googlecon_secret', '', @iCategId, '_bx_googlecon_option_app_secret', 'secret', '', '', 20, ''),
+('bx_googlecon_redirect_page', 'dashboard', @iCategId, '_sys_connect_option_redirect', 'select', '', '', 40, 'join,settings,dashboard,index'),
+('bx_googlecon_module', 'bx_persons', @iCategId, '_sys_connect_option_module', 'select', '', '', 50, 'a:2:{s:6:"module";s:12:"bx_googlecon";s:6:"method";s:20:"get_profiles_modules";}'),
+('bx_googlecon_privacy', '3', @iCategId, '_sys_connect_option_privacy', 'select', '', '', 54, 'a:2:{s:6:"module";s:12:"bx_googlecon";s:6:"method";s:18:"get_privacy_groups";}'),
+('bx_googlecon_confirm_email', 'on', @iCategId, '_sys_connect_option_confirm_email', 'checkbox', '', '', 70, ''),
+('bx_googlecon_approve', '', @iCategId, '_sys_connect_option_approve', 'checkbox', '', '', 80, '');
+

Разница между файлами не показана из-за своего большого размера
+ 3 - 0
modules/boonex/google_connect/updates/15.0.0_15.0.1/source/template/images/icons/std-icon.svg


+ 51 - 0
modules/boonex/intercom/updates/15.0.0_15.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' => 'Intercom',
+    'version_from' => '15.0.0',
+    'version_to' => '15.0.1',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/intercom/updates/update_15.0.0_15.0.1/',
+    'home_uri' => 'intercom_update_1500_1501',
+
+    'module_dir' => 'boonex/intercom/',
+    'module_uri' => 'intercom',
+
+    'db_prefix' => 'bx_intercom_',
+    'class_prefix' => 'BxIntercom',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Intercom',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/intercom/updates/15.0.0_15.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 BxIntercomUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 2 - 0
modules/boonex/intercom/updates/15.0.0_15.0.1/install/sql/enable.sql

@@ -0,0 +1,2 @@
+-- OPTIONS
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_intercom_option_api_key';

+ 68 - 0
modules/boonex/intercom/updates/15.0.0_15.0.1/source/install/config.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Intercom Intercom integration module
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_intercom',
+    'title' => 'Intercom',
+    'note' => 'Intercom integration.',
+    'version' => '15.0.1',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/intercom/',
+    'home_uri' => 'intercom',
+
+    'db_prefix' => 'bx_intercom_',
+    'class_prefix' => 'BxIntercom',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Intercom',
+
+    /**
+     * 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(),
+);
+
+/** @} */

+ 40 - 0
modules/boonex/intercom/updates/15.0.0_15.0.1/source/install/sql/enable.sql

@@ -0,0 +1,40 @@
+
+-- 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_intercom', '_bx_intercom_adm_stg_cpt_type', 'bx_intercom@modules/boonex/intercom/|std-icon.svg', IF(NOT ISNULL(@iTypeOrder), @iTypeOrder + 1, 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_intercom_general', '_bx_intercom_adm_stg_cpt_category_general', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `order`, `extra`) VALUES
+('bx_intercom_option_app_id', '', @iCategId, '_bx_intercom_option_app_id', 'digit', '', '', 10, ''),
+('bx_intercom_option_api_key', '', @iCategId, '_bx_intercom_option_api_key', 'secret', '', '', 20, ''),
+('bx_intercom_option_hide_launcher', '', @iCategId, '_bx_intercom_option_hide_launcher', 'checkbox', '', '', 22, ''),
+('bx_intercom_option_alignment', 'right', @iCategId, '_bx_intercom_option_alignment', 'select', '', '', 30, 'right,left'),
+('bx_intercom_option_horizontal_padding', '20', @iCategId, '_bx_intercom_option_horizontal_padding', 'digit', '', '', 32, ''),
+('bx_intercom_option_vertical_padding', '20', @iCategId, '_bx_intercom_option_vertical_padding', 'digit', '', '', 34, '');
+
+-- Injections
+
+INSERT INTO `sys_injections` (`name`, `page_index`, `key`, `type`, `data`, `replace`, `active`) VALUES
+('bx_intercom', 0, 'injection_footer', 'service', 'a:2:{s:6:"module";s:11:"bx_intercom";s:6:"method";s:16:"integration_code";}', 0, 1);
+
+-- Alerts
+
+INSERT INTO `sys_alerts_handlers` SET `name` = 'bx_intercom', `class` = 'BxIntercomAlerts', `file` = 'modules/boonex/intercom/classes/BxIntercomAlerts.php';
+
+SET @iHandlerId := (SELECT `id` FROM `sys_alerts_handlers`  WHERE `name` = 'bx_intercom');
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('account', 'delete', @iHandlerId),
+('bx_persons', 'deleted', @iHandlerId),
+('bx_persons', 'edited', @iHandlerId),
+('bx_persons', 'added', @iHandlerId),
+('bx_organizations', 'deleted', @iHandlerId),
+('bx_organizations', 'edited', @iHandlerId),
+('bx_organizations', 'added', @iHandlerId);
+

+ 16 - 0
modules/boonex/intercom/updates/15.0.0_15.0.1/source/template/images/icons/std-icon.svg

@@ -0,0 +1,16 @@
+<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_intercom)"/>
+<g transform="translate(15.000 15.000) scale(2.08333)">
+<svg:path xmlns:svg="http://www.w3.org/2000/svg" d="M21 0H3C1.343 0 0 1.343 0 3v18c0 1.658 1.343 3 3 3h18c1.658 0 3-1.342 3-3V3c0-1.657-1.342-3-3-3zm-5.801 4.399c0-.44.36-.8.802-.8.44 0 .8.36.8.8v10.688c0 .442-.36.801-.8.801-.443 0-.802-.359-.802-.801V4.399zM11.2 3.994c0-.44.357-.799.8-.799s.8.359.8.799v11.602c0 .44-.357.8-.8.8s-.8-.36-.8-.8V3.994zm-4 .405c0-.44.359-.8.799-.8.443 0 .802.36.802.8v10.688c0 .442-.36.801-.802.801-.44 0-.799-.359-.799-.801V4.399zM3.199 6c0-.442.36-.8.802-.8.44 0 .799.358.799.8v7.195c0 .441-.359.8-.799.8-.443 0-.802-.36-.802-.8V6zM20.52 18.202c-.123.105-3.086 2.593-8.52 2.593-5.433 0-8.397-2.486-8.521-2.593-.335-.288-.375-.792-.086-1.128.285-.334.79-.375 1.125-.09.047.041 2.693 2.211 7.481 2.211 4.848 0 7.456-2.186 7.479-2.207.334-.289.839-.25 1.128.086.289.336.25.84-.086 1.128zm.281-5.007c0 .441-.36.8-.801.8-.441 0-.801-.36-.801-.8V6c0-.442.361-.8.801-.8.441 0 .801.357.801.8v7.195z" fill="#6AFDEF" />
+</g>
+<g transform="translate(15.000 16.000) scale(2.08333)">
+<path fill-rule="evenodd" fill="black" fill-opacity="0.1" d="M21.08 0.00 L3.00 0.00 L2.83 0.00 L2.69 0.02 L2.53 0.04 L2.39 0.06 L2.23 0.10 L2.09 0.14 L1.94 0.19 L1.81 0.24 L1.67 0.31 L1.55 0.37 L1.41 0.45 L1.28 0.54 L1.16 0.63 L1.05 0.72 L0.93 0.82 L0.82 0.93 L0.72 1.05 L0.63 1.16 L0.56 1.25 L0.49 1.36 L0.42 1.46 L0.36 1.58 L0.30 1.69 L0.25 1.80 L0.21 1.91 L0.16 2.04 L0.12 2.15 L0.09 2.27 L0.06 2.39 L0.04 2.53 L0.02 2.65 L0.01 2.77 L0.00 2.90 L0.00 3.00 L0.00 21.04 L0.01 21.19 L0.02 21.35 L0.04 21.52 L0.08 21.67 L0.12 21.83 L0.16 21.98 L0.22 22.13 L0.28 22.28 L0.36 22.42 L0.43 22.55 L0.52 22.69 L0.61 22.81 L0.71 22.93 L0.81 23.05 L0.93 23.18 L1.05 23.28 L1.16 23.37 L1.25 23.44 L1.36 23.52 L1.46 23.58 L1.58 23.65 L1.69 23.70 L1.80 23.75 L1.91 23.79 L2.04 23.84 L2.15 23.88 L2.27 23.91 L2.39 23.94 L2.53 23.96 L2.65 23.98 L2.77 23.99 L2.90 24.00 L3.00 24.00 L21.04 24.00 L21.19 23.99 L21.35 23.98 L21.52 23.96 L21.67 23.92 L21.83 23.88 L21.98 23.84 L22.13 23.78 L22.28 23.72 L22.42 23.65 L22.55 23.57 L22.69 23.48 L22.81 23.39 L22.93 23.29 L23.05 23.19 L23.18 23.07 L23.28 22.95 L23.37 22.84 L23.44 22.75 L23.52 22.64 L23.58 22.54 L23.65 22.42 L23.70 22.31 L23.75 22.20 L23.79 22.09 L23.84 21.96 L23.88 21.85 L23.91 21.73 L23.94 21.61 L23.96 21.47 L23.98 21.35 L23.99 21.23 L24.00 21.10 L24.00 21.00 L24.00 3.00 L24.00 2.85 L23.98 2.69 L23.97 2.55 L23.94 2.41 L23.91 2.27 L23.87 2.13 L23.83 2.00 L23.78 1.87 L23.72 1.74 L23.66 1.62 L23.60 1.50 L23.53 1.38 L23.45 1.27 L23.37 1.16 L23.28 1.05 L23.19 0.95 L23.08 0.84 L22.98 0.75 L22.87 0.66 L22.77 0.57 L22.65 0.50 L22.54 0.42 L22.42 0.36 L22.29 0.29 L22.17 0.24 L22.04 0.18 L21.91 0.14 L21.77 0.10 L21.63 0.07 L21.49 0.04 L21.35 0.02 L21.23 0.01 Z"/>
+</g>
+<defs>
+<linearGradient id="paint0_linear_intercom" x1="40" y1="0" x2="40" y2="80" gradientUnits="userSpaceOnUse">
+<stop stop-color="#27272A"/>
+<stop offset="1" stop-color="#09090B"/>
+</linearGradient>
+
+</defs>
+</svg>

+ 51 - 0
modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/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' => 'LinkedIn connect',
+    'version_from' => '14.0.0',
+    'version_to' => '15.0.0',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/linkedin_connect/updates/update_14.0.0_15.0.0/',
+    'home_uri' => 'linkedin_update_1400_1500',
+
+    'module_dir' => 'boonex/linkedin_connect/',
+    'module_uri' => 'linkedin',
+
+    'db_prefix' => 'bx_linkedin_',
+    'class_prefix' => 'BxLinkedin',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'LinkedIn connect',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/install/installer.php

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

+ 2 - 0
modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/install/sql/enable.sql

@@ -0,0 +1,2 @@
+-- OPTIONS
+UPDATE `sys_options` SET `type`='secret' WHERE `name`='bx_linkedin_secret';

+ 68 - 0
modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/source/install/config.php

@@ -0,0 +1,68 @@
+<?php 
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    LinkedInConnect LinkedIn Connect
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_linkedin',
+    'title' => 'LinkedIn connect',
+    'note' => 'Join the site using LinkedIn account.',
+    'version' => '15.0.0',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/linkedin_connect/',
+    'home_uri' => 'linkedin',
+
+    'db_prefix' => 'bx_linkedin_',
+    'class_prefix' => 'BxLinkedin',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'LinkedIn Connect',
+
+    /**
+     * 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(),
+);
+
+/** @} */

+ 42 - 0
modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/source/install/sql/enable.sql

@@ -0,0 +1,42 @@
+
+-- Email template
+
+INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `Body`) VALUES
+('bx_linkedin', '_bx_linkedin_et_password_generated', 'bx_linkedin_password_generated', '_bx_linkedin_et_password_generated_subject', '_bx_linkedin_et_password_generated_body');
+
+-- Auth objects
+
+INSERT INTO `sys_objects_auths` (`Name`, `Title`, `Link`, `Icon`, `Style`) VALUES
+('bx_linkedin', '_bx_linkedin_auth_title', 'modules/?r=linkedin/start', 'fab linkedin-in', 'a:1:{s:7:".bx-btn";a:1:{s:10:"background";s:18:"#1a82b9 !important";}}');
+
+-- Alerts
+
+INSERT INTO `sys_alerts_handlers` SET `name` = 'bx_linkedin', `class` = 'BxLinkedinAlerts', `file` = 'modules/boonex/linkedin_connect/classes/BxLinkedinAlerts.php';
+
+SET @iHandlerId := (SELECT `id` FROM `sys_alerts_handlers`  WHERE `name` = 'bx_linkedin');
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('account', 'logout', @iHandlerId),
+('profile', 'delete', @iHandlerId),
+('profile', 'add', @iHandlerId);
+
+-- Options
+
+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_linkedin', '_bx_linkedin_adm_stg_cpt_type', 'bx_linkedin@modules/boonex/linkedin_connect/|std-icon.svg', IF(NOT ISNULL(@iTypeOrder), @iTypeOrder + 1, 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_linkedin_general', '_sys_connect_adm_stg_cpt_category_general', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `order`, `extra`) VALUES
+('bx_linkedin_api_key', '', @iCategId, '_bx_linkedin_option_app_id', 'digit', '', '', 10, ''),
+('bx_linkedin_secret', '', @iCategId, '_bx_linkedin_option_app_secret', 'secret', '', '', 20, ''),
+('bx_linkedin_redirect_page', 'dashboard', @iCategId, '_sys_connect_option_redirect', 'select', '', '', 40, 'join,settings,dashboard,index'),
+('bx_linkedin_module', 'bx_persons', @iCategId, '_sys_connect_option_module', 'select', '', '', 50, 'a:2:{s:6:"module";s:11:"bx_linkedin";s:6:"method";s:20:"get_profiles_modules";}'),
+('bx_linkedin_privacy', '3', @iCategId, '_sys_connect_option_privacy', 'select', '', '', 54, 'a:2:{s:6:"module";s:11:"bx_linkedin";s:6:"method";s:18:"get_privacy_groups";}'),
+('bx_linkedin_confirm_email', 'on', @iCategId, '_sys_connect_option_confirm_email', 'checkbox', '', '', 70, ''),
+('bx_linkedin_approve', '', @iCategId, '_sys_connect_option_approve', 'checkbox', '', '', 80, '');
+

+ 16 - 0
modules/boonex/linkedin_connect/updates/14.0.0_15.0.0/source/template/images/icons/std-icon.svg

@@ -0,0 +1,16 @@
+<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_linkedin)"/>
+<g transform="translate(15.000 15.000) scale(0.19531)">
+<svg:path xmlns:svg="http://www.w3.org/2000/svg" d="M218.123 218.127h-37.931v-59.403c0-14.165-.253-32.4-19.728-32.4-19.756 0-22.779 15.434-22.779 31.369v60.43h-37.93V95.967h36.413v16.694h.51a39.907 39.907 0 0 1 35.928-19.733c38.445 0 45.533 25.288 45.533 58.186l-.016 67.013ZM56.955 79.27c-12.157.002-22.014-9.852-22.016-22.009-.002-12.157 9.851-22.014 22.008-22.016 12.157-.003 22.014 9.851 22.016 22.008A22.013 22.013 0 0 1 56.955 79.27m18.966 138.858H37.95V95.967h37.97v122.16ZM237.033.018H18.89C8.58-.098.125 8.161-.001 18.471v219.053c.122 10.315 8.576 18.582 18.89 18.474h218.144c10.336.128 18.823-8.139 18.966-18.474V18.454c-.147-10.33-8.635-18.588-18.966-18.453" fill="#0A66C2" />
+</g>
+<g transform="translate(15.000 16.000) scale(0.19531)">
+<path fill-rule="evenodd" fill="black" fill-opacity="0.1" d="M237.03 0.02 L18.18 0.02 L17.24 0.07 L16.32 0.16 L15.39 0.31 L14.48 0.49 L13.58 0.72 L12.68 1.00 L11.80 1.32 L10.95 1.69 L10.11 2.09 L9.28 2.55 L8.50 3.03 L7.72 3.57 L6.98 4.14 L6.26 4.74 L5.58 5.38 L4.92 6.06 L4.37 6.69 L3.84 7.34 L3.35 8.02 L2.88 8.73 L2.44 9.45 L2.04 10.20 L1.67 10.96 L1.34 11.73 L1.05 12.51 L0.79 13.31 L0.56 14.13 L0.38 14.94 L0.22 15.78 L0.11 16.62 L0.04 17.47 L0.00 18.30 L-0.00 237.52 L0.03 238.49 L0.12 239.44 L0.25 240.37 L0.43 241.31 L0.66 242.24 L0.93 243.14 L1.24 244.02 L1.61 244.90 L2.02 245.76 L2.48 246.61 L2.97 247.42 L3.51 248.21 L4.08 248.96 L4.69 249.69 L5.35 250.39 L6.03 251.06 L6.68 251.63 L7.33 252.15 L8.01 252.65 L8.72 253.12 L9.44 253.55 L10.18 253.95 L10.95 254.32 L11.71 254.65 L12.50 254.95 L13.29 255.21 L14.11 255.43 L14.93 255.62 L15.77 255.77 L16.60 255.88 L17.45 255.96 L18.28 255.99 L237.35 256.00 L238.31 255.97 L239.28 255.89 L240.23 255.77 L241.17 255.59 L242.09 255.37 L243.01 255.10 L243.92 254.78 L244.82 254.42 L245.69 254.00 L246.54 253.55 L247.36 253.05 L248.16 252.51 L248.92 251.93 L249.67 251.30 L250.39 250.64 L251.05 249.95 L251.62 249.30 L252.15 248.65 L252.65 247.97 L253.11 247.26 L253.55 246.54 L253.95 245.79 L254.32 245.03 L254.65 244.26 L254.95 243.48 L255.21 242.68 L255.43 241.86 L255.62 241.03 L255.77 240.19 L255.89 239.36 L255.96 238.51 L256.00 237.67 L256.00 18.45 L255.97 17.66 L255.90 16.77 L255.79 15.89 L255.63 15.00 L255.43 14.13 L255.20 13.28 L254.92 12.42 L254.59 11.59 L254.23 10.77 L253.84 9.97 L253.39 9.18 L252.92 8.43 L252.41 7.69 L251.86 6.98 L251.28 6.29 L250.67 5.63 L250.02 5.01 L249.35 4.41 L248.65 3.85 L247.92 3.32 L247.18 2.83 L246.41 2.37 L245.62 1.96 L244.80 1.57 L243.97 1.23 L243.13 0.93 L242.27 0.67 L241.39 0.45 L240.51 0.28 L239.60 0.14 L238.68 0.05 L237.66 0.00 L237.03 0.00 Z"/>
+</g>
+<defs>
+<linearGradient id="paint0_linear_linkedin" x1="40" y1="0" x2="40" y2="80" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F3F4F6"/>
+<stop offset="1" stop-color="#E5E7EB"/>
+</linearGradient>
+
+</defs>
+</svg>

+ 51 - 0
modules/boonex/mailchimp/updates/15.0.0_15.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' => 'Mailchimp',
+    'version_from' => '15.0.0',
+    'version_to' => '15.0.1',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC2'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/mailchimp/updates/update_15.0.0_15.0.1/',
+    'home_uri' => 'mailchimp_update_1500_1501',
+
+    'module_dir' => 'boonex/mailchimp/',
+    'module_uri' => 'mailchimp',
+
+    'db_prefix' => 'bx_mailchimp_',
+    'class_prefix' => 'BxMailchimp',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Mailchimp',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/mailchimp/updates/15.0.0_15.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 BxMailchimpUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

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