Parcourir la source

Modules updates.

AntonLV il y a 3 mois
Parent
commit
8f1f5565ef
100 fichiers modifiés avec 13099 ajouts et 0 suppressions
  1. 51 0
      modules/boonex/accounts/updates/15.0.0_15.0.1/install/config.php
  2. 13 0
      modules/boonex/accounts/updates/15.0.0_15.0.1/install/installer.php
  3. 71 0
      modules/boonex/accounts/updates/15.0.0_15.0.1/source/install/config.php
  4. 39 0
      modules/boonex/accounts/updates/15.0.0_15.0.1/source/template/css/manage_tools.css
  5. 16 0
      modules/boonex/accounts/updates/15.0.0_15.0.1/source/template/profiles.html
  6. 9 0
      modules/boonex/accounts/updates/15.0.0_15.0.1/source/template/profiles_more.html
  7. 51 0
      modules/boonex/ads/updates/15.0.3_15.0.4/install/config.php
  8. 13 0
      modules/boonex/ads/updates/15.0.3_15.0.4/install/installer.php
  9. 94 0
      modules/boonex/ads/updates/15.0.3_15.0.4/source/classes/BxAdsFormOffer.php
  10. 106 0
      modules/boonex/ads/updates/15.0.3_15.0.4/source/classes/BxAdsMenuViewActions.php
  11. 2788 0
      modules/boonex/ads/updates/15.0.3_15.0.4/source/classes/BxAdsModule.php
  12. 151 0
      modules/boonex/ads/updates/15.0.3_15.0.4/source/install/config.php
  13. 86 0
      modules/boonex/ads/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  14. 51 0
      modules/boonex/albums/updates/15.0.2_15.0.3/install/config.php
  15. 13 0
      modules/boonex/albums/updates/15.0.2_15.0.3/install/installer.php
  16. 254 0
      modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsConfig.php
  17. 223 0
      modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsMenuViewActionsMedia.php
  18. 1168 0
      modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsModule.php
  19. 300 0
      modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsSearchResultMedia.php
  20. 452 0
      modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsTemplate.php
  21. 143 0
      modules/boonex/albums/updates/15.0.2_15.0.3/source/install/config.php
  22. 106 0
      modules/boonex/albums/updates/15.0.2_15.0.3/source/install/sql/enable-app.sql
  23. 51 0
      modules/boonex/credits/updates/15.0.1_15.0.2/install/config.php
  24. 13 0
      modules/boonex/credits/updates/15.0.1_15.0.2/install/installer.php
  25. 77 0
      modules/boonex/credits/updates/15.0.1_15.0.2/source/install/config.php
  26. 22 0
      modules/boonex/credits/updates/15.0.1_15.0.2/source/install/sql/enable-app.sql
  27. 51 0
      modules/boonex/events/updates/15.0.2_15.0.3/install/config.php
  28. 13 0
      modules/boonex/events/updates/15.0.2_15.0.3/install/installer.php
  29. 152 0
      modules/boonex/events/updates/15.0.2_15.0.3/source/install/config.php
  30. 104 0
      modules/boonex/events/updates/15.0.2_15.0.3/source/install/sql/enable-app.sql
  31. 51 0
      modules/boonex/files/updates/15.0.3_15.0.4/install/config.php
  32. 13 0
      modules/boonex/files/updates/15.0.3_15.0.4/install/installer.php
  33. 142 0
      modules/boonex/files/updates/15.0.3_15.0.4/source/install/config.php
  34. 78 0
      modules/boonex/files/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  35. 51 0
      modules/boonex/forum/updates/15.0.3_15.0.4/install/config.php
  36. 13 0
      modules/boonex/forum/updates/15.0.3_15.0.4/install/installer.php
  37. 151 0
      modules/boonex/forum/updates/15.0.3_15.0.4/source/install/config.php
  38. 76 0
      modules/boonex/forum/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  39. 51 0
      modules/boonex/groups/updates/15.0.1_15.0.2/install/config.php
  40. 13 0
      modules/boonex/groups/updates/15.0.1_15.0.2/install/installer.php
  41. 152 0
      modules/boonex/groups/updates/15.0.1_15.0.2/source/install/config.php
  42. 83 0
      modules/boonex/groups/updates/15.0.1_15.0.2/source/install/sql/enable-app.sql
  43. 51 0
      modules/boonex/market/updates/15.0.3_15.0.4/install/config.php
  44. 13 0
      modules/boonex/market/updates/15.0.3_15.0.4/install/installer.php
  45. 140 0
      modules/boonex/market/updates/15.0.3_15.0.4/source/install/config.php
  46. 27 0
      modules/boonex/market/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  47. 51 0
      modules/boonex/notifications/updates/15.0.3_15.0.4/install/config.php
  48. 13 0
      modules/boonex/notifications/updates/15.0.3_15.0.4/install/installer.php
  49. 82 0
      modules/boonex/notifications/updates/15.0.3_15.0.4/source/install/config.php
  50. 20 0
      modules/boonex/notifications/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  51. 71 0
      modules/boonex/notifications/updates/15.0.3_15.0.4/source/js/view.js
  52. 58 0
      modules/boonex/organizations/updates/15.0.3_15.0.4/install/config.php
  53. 13 0
      modules/boonex/organizations/updates/15.0.3_15.0.4/install/installer.php
  54. 153 0
      modules/boonex/organizations/updates/15.0.3_15.0.4/source/install/config.php
  55. 106 0
      modules/boonex/organizations/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  56. 51 0
      modules/boonex/persons/updates/15.0.3_15.0.4/install/config.php
  57. 13 0
      modules/boonex/persons/updates/15.0.3_15.0.4/install/installer.php
  58. 153 0
      modules/boonex/persons/updates/15.0.3_15.0.4/source/install/config.php
  59. 88 0
      modules/boonex/persons/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  60. 51 0
      modules/boonex/photos/updates/15.0.1_15.0.2/install/config.php
  61. 13 0
      modules/boonex/photos/updates/15.0.1_15.0.2/install/installer.php
  62. 136 0
      modules/boonex/photos/updates/15.0.1_15.0.2/source/install/config.php
  63. 77 0
      modules/boonex/photos/updates/15.0.1_15.0.2/source/install/sql/enable-app.sql
  64. 51 0
      modules/boonex/polls/updates/15.0.1_15.0.2/install/config.php
  65. 13 0
      modules/boonex/polls/updates/15.0.1_15.0.2/install/installer.php
  66. 136 0
      modules/boonex/polls/updates/15.0.1_15.0.2/source/install/config.php
  67. 71 0
      modules/boonex/polls/updates/15.0.1_15.0.2/source/install/sql/enable-app.sql
  68. 51 0
      modules/boonex/posts/updates/15.0.3_15.0.4/install/config.php
  69. 13 0
      modules/boonex/posts/updates/15.0.3_15.0.4/install/installer.php
  70. 155 0
      modules/boonex/posts/updates/15.0.3_15.0.4/source/install/config.php
  71. 74 0
      modules/boonex/posts/updates/15.0.3_15.0.4/source/install/sql/enable-app.sql
  72. 51 0
      modules/boonex/profiler/updates/15.0.2_15.0.3/install/config.php
  73. 13 0
      modules/boonex/profiler/updates/15.0.2_15.0.3/install/installer.php
  74. 799 0
      modules/boonex/profiler/updates/15.0.2_15.0.3/source/classes/BxProfiler.php
  75. 69 0
      modules/boonex/profiler/updates/15.0.2_15.0.3/source/install/config.php
  76. 51 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/install/config.php
  77. 13 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/install/installer.php
  78. 50 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/install/langs/en.xml
  79. 16 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/install/sql/enable.sql
  80. 598 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/classes/BxSMTPMailAuthorization.php
  81. 103 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/classes/BxSMTPMailAuthorizationPHPMailer.php
  82. 5 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/composer.json
  83. 85 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/composer.lock
  84. 68 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/install/config.php
  85. 76 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/install/langs/en.xml
  86. 54 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/install/sql/enable.sql
  87. 22 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/autoload.php
  88. 579 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/ClassLoader.php
  89. 396 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/InstalledVersions.php
  90. 21 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/LICENSE
  91. 10 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_classmap.php
  92. 9 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_namespaces.php
  93. 10 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_psr4.php
  94. 38 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_real.php
  95. 36 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_static.php
  96. 75 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/installed.json
  97. 32 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/installed.php
  98. 26 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/platform_check.php
  99. 265 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/firebase/php-jwt/CHANGELOG.md
  100. 30 0
      modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/firebase/php-jwt/LICENSE

+ 51 - 0
modules/boonex/accounts/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' => 'Accounts Manager',
+    'version_from' => '15.0.0',
+    'version_to' => '15.0.1',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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.0_15.0.1/',
+    'home_uri' => 'accounts_update_1500_1501',
+
+    '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.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 BxAccntUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 71 - 0
modules/boonex/accounts/updates/15.0.0_15.0.1/source/install/config.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    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.1',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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(),
+
+);
+
+/** @} */

+ 39 - 0
modules/boonex/accounts/updates/15.0.0_15.0.1/source/template/css/manage_tools.css

@@ -0,0 +1,39 @@
+@import url(../../../../base/general/template/css/manage_tools.css);
+
+/*--- Set Role popup ---*/
+.bx-accnt-sr-role-cnt {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+}
+.bx-accnt-sr-role-cnt > * {
+    display: inline-block;
+}
+.bx-accnt-sr-role-cnt > input {
+    margin: 0;
+}
+.bx-accnt-sr-done .bx-btn {
+    display: inline-block;
+    float: none;
+}
+
+/*--- Profiles list ---*/
+.bx-accnt-profile img,
+.bx-accnt-profile svg,
+.bx-accnt-profile .sys-icon {
+    width: 1.0rem;
+    height: 1.0rem;
+}
+.bx-accnt-profile .sys-icon {
+    line-height: 1.0rem;
+}
+
+/*--- Profiles popup ---*/
+.bx-accnt-profiles-popup {
+    max-width: 18rem;
+    font-size: 0.875rem;
+}
+.bx-accnt-profiles-popup-cnt > .bx-accnt-profile:last-child > span {
+    display: none;
+}

+ 16 - 0
modules/boonex/accounts/updates/15.0.0_15.0.1/source/template/profiles.html

@@ -0,0 +1,16 @@
+<div class="bx-accnt-profiles bx-clearfix">
+	<div class="bx-accnt-profiles-cnt__class_cnt__ flex flex-wrap space-x-2">
+	    <bx_repeat:profiles>
+	        <div id="__html_id____id__" class="bx-accnt-profile inline-flex items-center space-x-1">
+	            <a href="__url__" target="_blank" title="__name_attr__">__name__</a> __auth_accounts_icons__
+	        </div>
+	    </bx_repeat:profiles>
+	   
+	    <bx_if:profiles_more>
+	        <div class="bx-accnt-profile">
+	            <a href="javascript:void(0);" onclick="$('#__html_id__').dolPopup({pointer:{el:$(this)}})" title="__more_attr__">__more__</a>            
+	        </div>
+	        <div id="__html_id__" style="display:none;">__popup__</div>
+	    </bx_if:profiles_more>
+	</div>
+</div>

+ 9 - 0
modules/boonex/accounts/updates/15.0.0_15.0.1/source/template/profiles_more.html

@@ -0,0 +1,9 @@
+<div class="bx-accnt-profiles-popup">
+    <div class="bx-accnt-profiles-popup-cnt flex flex-wrap p-4 space-x-2">
+        <bx_repeat:profiles>
+            <div class="bx-accnt-profile inline-flex items-center space-x-1">
+                <a href="__url__" target="_blank" title="__name_attr__">__name__</a> __auth_accounts_icons__<span>,</span>
+            </div>
+        </bx_repeat:profiles>
+    </div>
+</div>

+ 51 - 0
modules/boonex/ads/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' => 'Ads',
+    'version_from' => '15.0.3',
+    'version_to' => '15.0.4',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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.3_15.0.4/',
+    'home_uri' => 'ads_update_1503_1504',
+
+    '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.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 BxAdsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 94 - 0
modules/boonex/ads/updates/15.0.3_15.0.4/source/classes/BxAdsFormOffer.php

@@ -0,0 +1,94 @@
+<?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
+ *
+ * @{
+ */
+
+class BxAdsFormOffer extends BxTemplFormView
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_iContentId;
+    protected $_aContentInfo;
+
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_ads';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct($aInfo, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(($iContentId = bx_get('content_id')) !== false) {
+            $this->_iContentId = (int)$iContentId;
+            if($this->_iContentId)
+                $this->_aContentInfo = $this->_oModule->_oDb->getContentInfoById($this->_iContentId);
+        }
+
+        if($this->_aContentInfo && (int)$this->_aContentInfo[$CNF['FIELD_SINGLE']] == 1) {
+            if(isset($this->aInputs[$CNF['FIELD_OFR_QUANTITY']]))
+                $this->aInputs[$CNF['FIELD_OFR_QUANTITY']] = array_merge($this->aInputs[$CNF['FIELD_OFR_QUANTITY']], [
+                    'type' => 'hidden',
+                    'value' => 1
+                ]);
+
+            if(isset($this->aInputs[$CNF['FIELD_OFR_TOTAL']]))
+                unset($this->aInputs[$CNF['FIELD_OFR_TOTAL']]);
+        }
+
+        if(isset($this->aInputs[$CNF['FIELD_OFR_AMOUNT']], $this->aInputs[$CNF['FIELD_OFR_QUANTITY']]) && $this->aInputs[$CNF['FIELD_OFR_QUANTITY']]['type'] != 'hidden') {
+            $sJsObject = $this->_oModule->_oConfig->getJsObject('form_offer');
+            $aMask = ['mask' => "javascript:%s.updateTotal(this, '%s', '%s', '%s');", $sJsObject, $CNF['FIELD_OFR_AMOUNT'], $CNF['FIELD_OFR_QUANTITY'], $CNF['FIELD_OFR_TOTAL']];
+
+            $sOnBlur = call_user_func_array('sprintf', array_values($aMask)); 
+            $this->aInputs[$CNF['FIELD_OFR_AMOUNT']]['attrs']['onblur'] = $sOnBlur;
+            $this->aInputs[$CNF['FIELD_OFR_QUANTITY']]['attrs']['onblur'] = $sOnBlur;
+        }
+    }
+
+    public function getCode($bDynamicMode = false)
+    {
+        $sJs = $this->_oModule->_oTemplate->addJs(['form_offer.js'], $bDynamicMode);
+
+        $sCode = '';
+        if($bDynamicMode)
+            $sCode .= $sJs;
+
+        $sCode .= $this->_oModule->_oTemplate->getJsCode('form_offer');
+        $sCode .= parent::getCode($bDynamicMode);
+
+        return $sCode;
+    }
+
+    public function insert($aValsToAdd = [], $isIgnore = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!isset($aValsToAdd[$CNF['FIELD_OFR_AUTHOR']]))
+            $aValsToAdd[$CNF['FIELD_OFR_AUTHOR']] = bx_get_logged_profile_id();
+
+        if(!isset($aValsToAdd[$CNF['FIELD_OFR_ADDED']]))
+            $aValsToAdd[$CNF['FIELD_OFR_ADDED']] = time();
+
+        return parent::insert ($aValsToAdd, $isIgnore);
+    }
+
+    public function update($iContentId, $aValsToAdd = [], &$aTrackTextFieldsChanges = null)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!isset($aValsToAdd[$CNF['FIELD_OFR_CHANGED']]))
+            $aValsToAdd[$CNF['FIELD_OFR_CHANGED']] = time();
+
+        return parent::update ($iContentId, $aValsToAdd, $aTrackTextFieldsChanges);
+    }
+}
+
+/** @} */

+ 106 - 0
modules/boonex/ads/updates/15.0.3_15.0.4/source/classes/BxAdsMenuViewActions.php

@@ -0,0 +1,106 @@
+<?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
+ *
+ * @{
+ */
+
+/**
+ * View entry social actions menu
+ */
+class BxAdsMenuViewActions extends BxBaseModTextMenuViewActions
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_ads';
+
+        parent::__construct($aObject, $oTemplate);
+    }
+
+    public function getCode ()
+    {
+        $sCode = parent::getCode();
+
+        if(!empty($this->_oMenuActions))
+            $sCode .= $this->_oMenuActions->getJsCode();
+
+    	return $sCode;
+    }
+
+    protected function _getMenuItemInterested($aItem)
+    {
+        $aResult = $this->_getMenuItemByNameActions($aItem);
+
+        if($this->_bIsApi && is_array($aResult)) {
+            return array_merge($aResult, [
+                'display_type' => 'callback',
+                'data' => [
+                    'request_url' => $this->_sModule . '/interested/&params[]=' . $this->_iContentId, 
+                    'on_callback' => 'hide'
+                ]
+            ]);
+        }
+
+        return $aResult;
+    }
+
+    protected function _getMenuItemAddToCart($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemMakeOffer($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemViewOffers($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemEditAd($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemEditAdBudget($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemViewAdPromotion($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemDeleteAd($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemShipped($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemReceived($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
+    protected function _getMenuItemReview($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemComment($aItem, array_merge($aParams, array(
+            'object' => $CNF['OBJECT_REVIEWS']
+        )));
+    }
+}
+
+/** @} */

+ 2788 - 0
modules/boonex/ads/updates/15.0.3_15.0.4/source/classes/BxAdsModule.php

@@ -0,0 +1,2788 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Ads Ads
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_ADS_STATUS_OFFER', 'offer');
+define('BX_ADS_STATUS_SOLD', 'sold');
+
+define('BX_ADS_STATUS_ADMIN_UNPAID', 'unpaid');
+
+define('BX_ADS_COMMODITY_TYPE_PRODUCT', 'product');
+define('BX_ADS_COMMODITY_TYPE_PROMOTION', 'promotion');
+
+define('BX_ADS_OFFER_STATUS_ACCEPTED', 'accepted');
+define('BX_ADS_OFFER_STATUS_AWAITING', 'awaiting');
+define('BX_ADS_OFFER_STATUS_DECLINED', 'declined');
+define('BX_ADS_OFFER_STATUS_CANCELED', 'canceled');
+define('BX_ADS_OFFER_STATUS_PAID', 'paid');
+
+/**
+ * Ads module
+ */
+class BxAdsModule extends BxBaseModTextModule
+{
+    protected $_aOfferStatuses;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_oConfig->init($this->_oDb);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->_aSearchableNamesExcept = array_merge($this->_aSearchableNamesExcept, array(
+            $CNF['FIELD_CATEGORY_VIEW'],
+            $CNF['FIELD_CATEGORY_SELECT']
+        ));
+
+        $this->_aOfferStatuses = array(
+            BX_ADS_OFFER_STATUS_ACCEPTED,
+            BX_ADS_OFFER_STATUS_AWAITING,
+            BX_ADS_OFFER_STATUS_DECLINED
+        );
+    }
+    
+    public function decodeDataApi ($aData, $bExtended = false)
+    {
+        $CNF = $this->_oConfig->CNF;
+        
+        $aResult = array_merge(parent::decodeDataApi($aData, $bExtended), [
+            $CNF['FIELD_PRICE'] => $aData[$CNF['FIELD_PRICE']],
+            $CNF['FIELD_QUANTITY'] => $aData[$CNF['FIELD_QUANTITY']],
+            $CNF['FIELD_NOTES_PURCHASED'] => $aData[$CNF['FIELD_NOTES_PURCHASED']]
+        ]);
+
+        if($this->_oConfig->isSources() && !empty($aData[$CNF['FIELD_URL']]))
+            $aResult[$CNF['FIELD_URL']] = $aData[$CNF['FIELD_URL']];
+
+        return $aResult;
+    }
+
+    public function actionGetSource()
+    {
+        $aEntries = $this->serviceGetSourceData();
+
+        header('Content-Type:text/javascript; charset=utf-8');
+        echo(json_encode($aEntries));
+    }
+    
+    public function serviceGetSourceData()
+    {
+        $sSourceType = bx_get('source_type');
+        if(empty($sSourceType)) {
+            $aSource = $this->serviceGetSource($this->_iProfileId);
+            if(!empty($aSource) && is_array($aSource))
+                $sSourceType = $aSource['name'];
+        }
+
+        $sTerm = bx_get('term');
+
+        $aEntries = $this->serviceLoadEntriesFromSourceByTerm($sSourceType, $sTerm);
+
+        return $aEntries;
+    }
+
+    public function actionLoadEntryFromSource()
+    {
+        $sSourceType = bx_process_url_param(bx_process_input(bx_get('source_type')));
+        $sSource = bx_process_url_param(bx_process_input(bx_get('source')));
+        if(empty($sSourceType) || empty($sSource))
+            return echoJson(['code' => 1]);
+
+        $aEntry = $this->serviceLoadEntryFromSource($sSourceType, $sSource);
+        if(empty($aEntry) || !is_array($aEntry))
+            return echoJson(['code' => 2]);
+
+        return echoJson([
+            'code' => 0,
+            'fields' => $aEntry
+        ]);
+    }
+
+    public function actionGetCategoryForm()
+    {
+        if(($iCategory = bx_get('category')) === false || (int)$iCategory == 0)
+            return echoJson(['msg' => _t('_bx_ads_form_entry_input_category_select_err')]);
+
+        return echoJson([
+            'eval' => $this->_oConfig->getJsObject('form') . '.onSelectCategory(oData)',
+            'content' => $this->serviceGetCreatePostForm([
+                'absolute_action_url' => true,
+                'dynamic_mode' => true,
+                'context_id' => ($iContextId = bx_get('context_id')) !== false ? (int)$iContextId : false
+            ])
+        ]);
+    }
+
+    public function actionCheckName()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iId = (int)bx_get($CNF['FIELD_ID']);
+    	$sTitle = bx_process_input(bx_get($CNF['FIELD_TITLE']));
+
+    	echoJson($this->serviceCheckName($sTitle, $iId));
+    }
+
+    public function actionInterested()
+    {
+        $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.3_15.0.4/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.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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'
+    ),
+
+);
+
+/** @} */

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

@@ -0,0 +1,86 @@
+-- PAGES: config_api
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'post\',\r\n    top: true,\r\n    blocks: {\r\n        author: {\r\n            name: \'bx_ads:entity_author\',\r\n            showTitle: false,\r\n            showBg: false,\r\n            forList: true,\r\n            forHeader: true,\r\n        },\r\n        text: {\r\n            name: \'bx_ads:entity_text_block\',\r\n            showTitle: false,\r\n            showBg: false,\r\n            forList: true,\r\n        },\r\n        attachments: {\r\n            name: \'bx_ads:entity_attachments\',\r\n            showTitle: false,\r\n            showBg: false,\r\n            forList: true,\r\n        },\r\n        actions: {\r\n            name: \'bx_ads:entity_all_actions\',\r\n            showTitle: false,\r\n            showBg: false,\r\n            forList: true,\r\n        },\r\n        \'comments-empty\': {\r\n            name: \'static:comments_empty\',\r\n            showTitle: false,\r\n            showBg: false,\r\n            forList: true,\r\n        },\r\n        comments: {\r\n            name: \'bx_ads:entity_reviews\',\r\n            showTitle: false,\r\n            showBg: false,\r\n        },\r\n    },\r\n    headerSettings: {\r\n        footer: false,\r\n    },\r\n}' WHERE `object`='bx_ads_view_entry';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_create_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_create_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_edit_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_edit_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_delete_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_delete_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_breadcrumb';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_text';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_attachments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_reviews';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_offer_accepted';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_sys_entry_context' AND `title`='_bx_ads_page_block_title_entry_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_reviews_rating';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_location';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_polls';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_featured_entries_view_extended';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_reports';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_view_entry_comments' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_entry_comments' AND `title`='_bx_ads_page_block_title_entry_comments_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_popular' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_popular_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_updated' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_updated_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_categories' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_categories';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_categories' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entries_by_category';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_author' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_author' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_sys_favorites_of_author' AND `title`='_bx_ads_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_author' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_sys_entries_of_author' AND `title`='_bx_ads_page_block_title_entries_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_author' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_sys_entries_in_context' AND `title`='_bx_ads_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_favorites' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_favorites' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_favorites' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_context' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_sys_entries_in_context' AND `title`='_bx_ads_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_home' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_recent_entries_view_extended';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_search' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_search' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_manage' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_system_manage' AND `title`='_bx_ads_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_administration' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_system_manage_administration' AND `title`='_bx_ads_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_licenses' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_licenses_note';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_licenses' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_licenses';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_licenses_administration' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_licenses_administration';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_offers' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_entry_breadcrumb';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_offers' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_offers';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_ads_offers_all' AND `module`='bx_ads' AND `title_system`='' AND `title`='_bx_ads_page_block_title_offers_all';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_ads' AND `title_system`='_bx_ads_page_block_title_sys_my_entries' AND `title`='_bx_ads_page_block_title_my_entries';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_ads' AND `name`='ads-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_content_links' AND `module`='bx_ads' AND `name`='create-ad';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_ads' AND `name`='profile-stats-my-ads';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_dashboard_manage_tools' AND `module`='bx_ads' AND `name`='ads-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_entry_attachments' AND `module`='bx_ads' AND `name`='photo_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_entry_attachments' AND `module`='bx_ads' AND `name`='video_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_entry_attachments' AND `module`='bx_ads' AND `name`='record_video';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_entry_attachments' AND `module`='bx_ads' AND `name`='file_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_entry_attachments' AND `module`='bx_ads' AND `name`='poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='interested';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='add-to-cart';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='make-offer';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='view-offers';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='edit-ad';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='delete-ad';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='shipped';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view' AND `module`='bx_ads' AND `name`='received';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view_actions' AND `module`='bx_ads' AND `name`='edit-ad';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view_actions' AND `module`='bx_ads' AND `name`='delete-ad';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_view_actions' AND `module`='bx_ads' AND `name`='received';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_my' AND `module`='bx_ads' AND `name`='create-ad';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_submenu' AND `module`='bx_ads' AND `name`='ads-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_submenu' AND `module`='bx_ads' AND `name`='ads-popular';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_submenu' AND `module`='bx_ads' AND `name`='ads-categories';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_submenu' AND `module`='bx_ads' AND `name`='ads-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_submenu' AND `module`='bx_ads' AND `name`='ads-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_snippet_meta' AND `module`='bx_ads' AND `name`='date';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_snippet_meta' AND `module`='bx_ads' AND `name`='author';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_snippet_meta' AND `module`='bx_ads' AND `name`='category';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_snippet_meta' AND `module`='bx_ads' AND `name`='price';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_licenses_submenu' AND `module`='bx_ads' AND `name`='ads-licenses-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_ads_licenses_submenu' AND `module`='bx_ads' AND `name`='ads-licenses';

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

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

+ 254 - 0
modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsConfig.php

@@ -0,0 +1,254 @@
+<?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',
+
+            'URL_HOME' => 'page.php?i=albums-home',
+            'URL_POPULAR' => 'page.php?i=albums-popular',
+            'URL_TOP' => 'page.php?i=albums-top',
+            'URL_UPDATED' => 'page.php?i=albums-home',
+            'URL_POPULAR_MEDIA' => 'page.php?i=albums-popular-media',
+            'URL_TOP_MEDIA' => 'page.php?i=albums-top-media',
+            'URL_RECENT_MEDIA' => 'page.php?i=albums-recent-media', // TODO: add page
+            'URL_MANAGE_COMMON' => 'page.php?i=albums-manage',
+            'URL_MANAGE_ADMINISTRATION' => 'page.php?i=albums-administration',
+
+            // some params
+            'PARAM_AUTO_APPROVE' => 'bx_albums_enable_auto_approve',
+            'PARAM_CHARS_SUMMARY' => 'bx_albums_summary_chars',
+            'PARAM_CHARS_SUMMARY_PLAIN' => 'bx_albums_plain_summary_chars',
+            'PARAM_NUM_RSS' => 'bx_albums_rss_num',
+            'PARAM_SEARCHABLE_FIELDS' => 'bx_albums_searchable_fields',
+            'PARAM_PER_PAGE_FOR_FAVORITES_LISTS' => 'bx_albums_per_page_for_favorites_lists',
+            'PARAM_ORDER_BY_GHOSTS' => true,
+
+            // objects
+            'OBJECT_STORAGE' => 'bx_albums_files',
+            'OBJECT_IMAGES_TRANSCODER_PREVIEW' => 'bx_albums_proxy_preview',
+            'OBJECT_IMAGES_TRANSCODER_GALLERY' => '',
+            'OBJECT_IMAGES_TRANSCODER_BIG' => 'bx_albums_big',
+            'OBJECT_VIDEOS_TRANSCODERS' => array(
+                'poster' => 'bx_albums_video_poster_big', 
+                'poster_preview' => 'bx_albums_video_poster_preview', 
+                'mp4' => 'bx_albums_video_mp4', 
+                'mp4_hd' => 'bx_albums_video_mp4_hd'
+            ),
+            'OBJECT_VIDEO_TRANSCODER_HEIGHT' => '480px',
+            'OBJECT_TRANSCODER_BROWSE' => 'bx_albums_proxy_browse',
+            'OBJECT_TRANSCODER_COVER' => 'bx_albums_proxy_cover',
+            'OBJECT_REPORTS' => 'bx_albums',
+            'OBJECT_REPORTS_MEDIA' => 'bx_albums_media',
+            'OBJECT_VIEWS' => 'bx_albums',
+            'OBJECT_VIEWS_MEDIA' => 'bx_albums_media',
+            'OBJECT_VOTES' => 'bx_albums',
+            'OBJECT_VOTES_MEDIA' => 'bx_albums_media',
+            'OBJECT_REACTIONS' => 'bx_albums_reactions',
+            'OBJECT_SCORES' => 'bx_albums',
+            'OBJECT_SCORES_MEDIA' => 'bx_albums_media',
+            'OBJECT_FAVORITES' => 'bx_albums',
+            'OBJECT_FAVORITES_MEDIA' => 'bx_albums_media',
+            'OBJECT_FEATURED' => 'bx_albums',
+            'OBJECT_FEATURED_MEDIA' => 'bx_albums_media',
+            'OBJECT_METATAGS' => 'bx_albums',
+            'OBJECT_METATAGS_MEDIA' => 'bx_albums_media',
+            'OBJECT_METATAGS_MEDIA_CAMERA' => 'bx_albums_media_camera',
+            'OBJECT_COMMENTS' => 'bx_albums',
+            'OBJECT_NOTES' => 'bx_albums_notes',
+            'OBJECT_COMMENTS_MEDIA' => 'bx_albums_media',
+            'OBJECT_PRIVACY_VIEW' => 'bx_albums_allow_view_to',
+            'OBJECT_PRIVACY_LIST_VIEW' => 'bx_albums_allow_view_favorite_list',
+            'OBJECT_FORM_ENTRY' => 'bx_albums',
+            'OBJECT_FORM_ENTRY_DISPLAY_VIEW' => 'bx_albums_entry_view',
+            'OBJECT_FORM_ENTRY_DISPLAY_ADD' => 'bx_albums_entry_add',
+            'OBJECT_FORM_ENTRY_DISPLAY_EDIT' => 'bx_albums_entry_edit',
+            'OBJECT_FORM_ENTRY_DISPLAY_DELETE' => 'bx_albums_entry_delete',
+            'OBJECT_FORM_MEDIA' => 'bx_albums_media',
+            'OBJECT_FORM_MEDIA_DISPLAY_EDIT' => 'bx_albums_media_edit',
+            'OBJECT_FORM_MEDIA_DISPLAY_MOVE' => 'bx_albums_media_move',
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY' => 'bx_albums_view', // actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL' => 'bx_albums_view_actions', // all actions menu on view entry page
+            'OBJECT_MENU_ACTIONS_VIEW_MEDIA' => 'bx_albums_view_media', // actions menu on view media page
+            'OBJECT_MENU_ACTIONS_VIEW_MEDIA_ALL' => 'bx_albums_view_actions_media', // all actions menu on view media page
+            'OBJECT_MENU_ACTIONS_VIEW_MEDIA_UNIT' => 'bx_albums_view_actions_media_unit', // actions menu on media unit
+            'OBJECT_MENU_ACTIONS_MY_ENTRIES' => 'bx_albums_my', // actions menu on my entries page
+            'OBJECT_MENU_SUBMENU' => 'bx_albums_submenu', // main module submenu
+            'OBJECT_MENU_SUBMENU_VIEW_ENTRY' => 'bx_albums_view_submenu', // view entry submenu
+            'OBJECT_MENU_SUBMENU_VIEW_ENTRY_MAIN_SELECTION' => 'albums-home', // first item in view entry submenu from main module submenu
+            'OBJECT_MENU_SNIPPET_META' => 'bx_albums_snippet_meta', // menu for snippet meta info
+            'OBJECT_MENU_MANAGE_TOOLS' => 'bx_albums_menu_manage_tools', //manage menu in content administration tools
+            'OBJECT_GRID_ADMINISTRATION' => 'bx_albums_administration',
+            'OBJECT_GRID_COMMON' => 'bx_albums_common',
+            'OBJECT_UPLOADERS' => array('bx_albums_html5', 'bx_albums_crop'),
+
+            'FUNCTION_FOR_GET_ITEM_INFO' => 'getMediaInfoById',
+
+            '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);
+    }
+}
+
+/** @} */

+ 223 - 0
modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsMenuViewActionsMedia.php

@@ -0,0 +1,223 @@
+<?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, [
+                'display_type' => 'callback',
+                'data' => [
+                    'request_url' => $this->_sModule . '/edit_media/&params[]=' . $this->_iMediaId, 
+                    'on_callback' => 'modal'
+                ]
+            ]);
+
+        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, [
+                'display_type' => 'callback',
+                'data' => [
+                    'request_url' => $this->_sModule . '/delete_media/&params[]=' . $this->_iMediaId, 
+                    'on_callback' => 'hide'
+                ]
+            ]);
+
+        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, [
+                'display_type' => 'callback',
+                'data' => [
+                    'request_url' => $this->_sModule . '/move_media/&params[]=' . $this->_iMediaId, 
+                    'on_callback' => 'modal'
+                ]
+            ]);
+
+        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
+        ));
+    }
+}
+
+/** @} */

+ 1168 - 0
modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsModule.php

@@ -0,0 +1,1168 @@
+<?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' => '',
+            'EditMedia' => '',
+            'DeleteMedia' => '',
+            'MoveMedia' => '',
+            '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 serviceEditMedia($iMediaId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iMediaId = (int)$iMediaId;
+        $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        $aContentInfo = $this->_oDb->getContentInfoById($aMediaInfo['content_id']);
+        if(($sMsg = $this->checkAllowedEdit($aContentInfo)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $this->_bIsApi ? [bx_api_get_msg($sMsg)] : ['msg' => $sMsg];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_MEDIA'], $CNF['OBJECT_FORM_MEDIA_DISPLAY_EDIT']);
+        $oForm->initForm('edit', $iMediaId);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            if($oForm->update($iMediaId) !== false) {
+                $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+                if(!empty($aMediaInfo) && is_array($aMediaInfo) && !empty($CNF['OBJECT_METATAGS_MEDIA'])) {
+                    $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS_MEDIA']);
+                    if ($oMetatags->keywordsIsEnabled())
+                        $oMetatags->keywordsAdd($aMediaInfo['id'], $aMediaInfo['title']);
+                }
+
+                $aRes = ['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'] . '/edit_media/&params[]=' . $iMediaId, 'immutable' => true]
+                ]
+            ])];
+
+        $sContent = BxTemplStudioFunctions::getInstance()->transBox('bx-albums-edit-media-popup', $this->_oTemplate->parseHtmlByName('media-edit.html', [
+            'form_id' => $oForm->aFormAttrs['id'],
+            'form' => $oForm->getCode(true)
+        ]));
+
+        return ['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]];
+    }
+
+    public function serviceDeleteMedia($iMediaId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sUploader = reset($CNF['OBJECT_UPLOADERS']);
+        $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        if(!$sUploader || empty($aMediaInfo) || !is_array($aMediaInfo))
+            return [];
+
+        $oUploader = BxDolUploader::getObjectInstance($sUploader, $CNF['OBJECT_STORAGE'], '');
+        if($oUploader === false) 
+            return ($sMsg = _t('_sys_txt_error_occured')) && $this->_bIsApi ? [bx_api_get_msg($sMsg)] : ['msg' => $sMsg];
+
+        $oUploader->deleteGhost($aMediaInfo['file_id'], bx_get_logged_profile_id());
+
+        $sRedirect = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aMediaInfo['content_id']));
+        return [
+            'redirect' => $this->_bIsApi ? bx_api_get_relative_url($sRedirect) : $sRedirect
+        ];
+    }
+
+    public function serviceMoveMedia($iMediaId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iMediaId = (int)$iMediaId;
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_MEDIA'], $CNF['OBJECT_FORM_MEDIA_DISPLAY_MOVE']);
+        $oForm->initForm('move', $iMediaId);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            if($oForm->update($iMediaId) !== false)
+                $aRes = ['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'] . '/move_media/&params[]=' . $iMediaId, 'immutable' => true]
+                ]
+            ])];
+
+        $sContent = BxTemplStudioFunctions::getInstance()->transBox('bx-albums-move-media-popup', $this->_oTemplate->parseHtmlByName('media-edit.html', [
+            'form_id' => $oForm->aFormAttrs['id'],
+            'form' => $oForm->getCode(true)
+        ]));
+
+        return ['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_albums Albums
+     * @subsection bx_albums-page_blocks Page Blocks
+     * @subsubsection bx_albums-media_comments media_comments
+     * 
+     * @code bx_srv('bx_albums', 'media_comments', [...]); @endcode
+     * 
+     * Display media comments block.
+     * @param $iMediaId media ID, if it's omitted then it's taken from 'id' GET variable.
+     * @return HTML string with comments. On error false or empty string is returned.
+     * 
+     * @see BxAlbumsModule::serviceMediaComments
+     */
+    /** 
+     * @ref bx_albums-media_comments "media_comments"
+     */
+    public function serviceMediaComments ($iMediaId = 0)
+    {
+        return $this->_entityComments($this->_oConfig->CNF['OBJECT_COMMENTS_MEDIA'], $iMediaId);
+    }
+
+    /**
+     * Display media author block.
+     * @param $iMediaId media ID, if it's omitted then it's taken from 'id' GET variable.
+     * @return HTML string with block content. On error false or empty string is returned.
+     */
+    public function serviceMediaAuthor ($iMediaId = 0)
+    {
+        return $this->_serviceTemplateFunc ('mediaAuthor', $iMediaId, 'getMediaInfoById');
+    }
+
+    /**
+     * Entry actions and social sharing block
+     */
+    public function serviceMediaAllActions ($mixedContent = false, $aParams = array())
+    {
+        $iMediaId = 0;
+        $aMediaInfo = array();
+
+        $bContent = !empty($mixedContent);
+        if($bContent && is_array($mixedContent))
+            list($iMediaId, $aMediaInfo) = $mixedContent;
+        else {
+            if($bContent)
+                $iMediaId = (int)$mixedContent;
+            else
+                $iMediaId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+            if(!$iMediaId)
+                return false;
+
+            $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        }
+
+        if(!$iMediaId || !$aMediaInfo)
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        return parent::serviceEntityAllActions (array($iMediaId, $aMediaInfo), array_merge(array(
+            'object_menu' => $CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA_ALL'],
+            'object_transcoder' => $CNF['OBJECT_IMAGES_TRANSCODER_BIG'],
+            'entry_url' => bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_MEDIA'] . '&id=' . $iMediaId)),
+            'entry_title' => $aMediaInfo['title'],
+            'entry_thumb' => $aMediaInfo['file_id']
+        ), $aParams));
+    }
+
+    /**
+     * Entry actions block
+     */
+    public function serviceMediaActions ($iContentId = 0)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_MEDIA']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    /**
+     * Display media social sharing buttons block.
+     * @param $iMediaId media ID, if it's omitted then it's taken from 'id' GET variable.
+     * @return HTML string with block content. On error false or empty string is returned.
+     */
+    public function serviceMediaSocialSharing ($iMediaId = 0)
+    {
+        if(!$iMediaId)
+            $iMediaId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if(!$iMediaId)
+            return false;
+
+        $aMediaInfo = $this->_oDb->getMediaInfoById($iMediaId);
+        if(!$aMediaInfo)
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->serviceEntitySocialSharing(array($iMediaId, $aMediaInfo), array(
+            'uri' => $CNF['URI_VIEW_MEDIA'],
+            'title' => $aMediaInfo['title'],
+            'id_thumb' => $aMediaInfo['file_id'],
+            'object_transcoder' => $CNF['OBJECT_IMAGES_TRANSCODER_BIG']
+        ));
+    }
+
+    /**
+     * Display media preview block.
+     * @param $iMediaId media ID, if it's omitted then it's taken from 'id' GET variable.
+     * @param $mixedContext current context to navigate to next and previous file, possible values: popular, public, author. If omitted then value from 'context' GTE variable is taken. Default context is 'album' - when nothin is specified.
+     * @param $aParams array of additional params. 
+     * @return HTML string with block content. On error false or empty string is returned.
+     */
+    public function serviceMediaView ($iMediaId = 0, $mixedContext = false, $aParams = [])
+    {
+        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)
+    {
+        echoJson($this->serviceEditMedia($iMediaId));
+    }
+
+    public function actionDeleteMedia($iMediaId)
+    {
+        echoJson($this->serviceDeleteMedia($iMediaId));
+    }
+
+    public function actionMoveMedia($iMediaId)
+    {
+        echoJson($this->serviceMoveMedia($iMediaId));
+    }
+
+    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;
+    }
+}
+
+/** @} */

+ 300 - 0
modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsSearchResultMedia.php

@@ -0,0 +1,300 @@
+<?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 BxAlbumsSearchResultMedia extends BxBaseModTextSearchResult
+{
+    protected $sOrderDirection = 'DESC';
+
+    function __construct($sMode = '', $aParams = array())
+    {
+        $this->sUnitTemplateLiveSearch = 'unit_media_live_search.html';
+
+        $aParams['unit_views'] = array('gallery' => 'unit_media.html', 'showcase' => 'unit_showcase.html');
+        parent::__construct($sMode, $aParams);
+
+        $this->aCurrent = array(
+            'name' => 'bx_albums_media',
+            'module_name' => 'bx_albums',
+            'object_metatags' => 'bx_albums_media',
+            'title' => _t('_bx_albums_media'),
+            'table' => 'bx_albums_files2albums',
+            'ownFields' => array('id', 'title', 'data', 'content_id', 'file_id', 'order', 'views'),
+            'searchFields' => array('title'),
+            'restriction_sql' => '',
+            'restriction' => array(
+                'author' => array('value' => '', 'field' => 'author', 'operator' => '='),
+                'album' => array('value' => '', 'field' => 'content_id', 'operator' => '='),
+                'featured' => array('value' => '', 'field' => 'featured', 'operator' => '<>'),
+                'status' => array('value' => 'active', 'field' => 'status', 'operator' => '=', 'table' => 'bx_albums_albums'),
+                'statusAdmin' => array('value' => 'active', 'field' => 'status_admin', 'operator' => '=', 'table' => 'bx_albums_albums'),
+            ),
+            'join' => array(
+                'albums' => array(
+                    'type' => 'INNER',
+                    'table' => 'bx_albums_albums',
+                    'mainField' => 'content_id',
+                    'onField' => 'id',
+                    'joinFields' => array(),
+                ),
+                'files' => array(
+                    'type' => 'INNER',
+                    'table' => 'bx_albums_files',
+                    'table_alias' => 'f',
+                    'mainField' => 'file_id',
+                    'onField' => 'id',
+                    'joinFields' => array('added'),
+                ),
+            ),
+            'paginate' => array('perPage' => getParam('bx_albums_per_page_browse'), 'start' => 0),
+            'sorting' => 'last',
+            'rss' => array(
+                'title' => '',
+                'link' => '',
+                'image' => '',
+                'profile' => 0,
+                'fields' => array (
+                    'Guid' => 'link',
+                    'Link' => 'link',
+                    'Title' => 'title',
+                    'DateTimeUTS' => 'added',
+                    'Desc' => 'title',
+                ),
+            ),
+            'ident' => 'id',
+        );
+
+        $this->sFilterName = 'bx_albums_filter';
+        $this->oModule = $this->getMain();
+
+        $oProfileAuthor = isset($aParams['author']) ? BxDolProfile::getInstance((int)$aParams['author']) : null;
+
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        switch ($sMode) {
+            case 'album':
+                $iAlbumId = (int)$aParams['album_id'];
+                $this->aCurrent['restriction']['album']['value'] = $iAlbumId;
+                $this->aCurrent['restriction']['status']['value'] = '';
+                $this->aCurrent['restriction']['statusAdmin']['value'] = '';
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iAlbumId);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_media_in_album');
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss_media/' . $sMode . '/' . $iAlbumId;
+                $this->aCurrent['sorting'] = 'order';
+                $this->sOrderDirection = 'ASC';
+                $this->setProcessPrivateContent(true);
+
+                if($CNF['PARAM_ORDER_BY_GHOSTS'])
+                    $this->_updateCurrentForOrderByGhosts();
+                break;
+
+            case 'favorite':
+                if(!$this->_updateCurrentForFavorite($sMode, array_merge($aParams, array('system' => $CNF['OBJECT_FAVORITES_MEDIA'])), $oProfileAuthor)) {
+                    $this->isError = true;
+                    break;
+                }
+                $this->addConditionsForPrivateContent($CNF, $oProfileAuthor);
+                break;
+
+            case 'recent':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_RECENT_MEDIA']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_recent_media');
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss_media/' . $sMode; 
+                $this->aCurrent['sorting'] = 'last';
+                $this->addConditionsForPrivateContent($CNF, $oProfileAuthor);
+                break;
+
+            case 'featured':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_RECENT_MEDIA']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_featured_media');
+                $this->aCurrent['restriction']['featured']['value'] = '0';
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss_media/' . $sMode;
+                $this->aCurrent['sorting'] = 'featured';
+                $this->addConditionsForPrivateContent($CNF, $oProfileAuthor);
+                break;
+
+            case 'popular':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_POPULAR_MEDIA']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_popular_media');
+                $this->aCurrent['rss']['link'] = BxDolPermalinks::getInstance()->permalink('modules/?r=albums/rss_media/' . $sMode);
+                $this->aCurrent['sorting'] = 'popular';
+                $this->addConditionsForPrivateContent($CNF, $oProfileAuthor);
+                break;
+
+            case 'top':
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink($CNF['URL_TOP_MEDIA']);
+                $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_top_media');
+                $this->aCurrent['rss']['link'] = BxDolPermalinks::getInstance()->permalink('modules/?r=albums/rss_media/' . $sMode);
+                $this->aCurrent['sorting'] = 'top';
+                $this->addConditionsForPrivateContent($CNF, $oProfileAuthor);
+                break;
+
+            case '': // search results
+                $this->sBrowseUrl = BX_DOL_SEARCH_KEYWORD_PAGE;
+                unset($this->aCurrent['paginate']['perPage'], $this->aCurrent['rss']);
+                break;
+
+            default:
+                $sMode = '';
+                $this->isError = true;
+        }
+
+        $this->sBrowseUrl = $this->_replaceMarkers($this->sBrowseUrl);
+        $this->aCurrent['title'] = $this->_replaceMarkers($this->aCurrent['title']); 
+
+        $this->removeContainerClass('bx-def-margin-bottom-neg');
+        $this->addContainerClass(array('bx-def-margin-sec-lefttopright-neg', 'bx-def-margin-sec-bottom-neg', 'bx-albums-medias-wrapper'));
+
+        $this->addCustomConditions($CNF, $oProfileAuthor, $sMode, $aParams);
+    }
+
+    function getAlterOrder()
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $sTable = $this->aCurrent['table'];
+        $sWay = $this->sOrderDirection;
+
+        $aSql = array();
+        switch ($this->aCurrent['sorting']) {
+            case 'order':
+                $aSql['order'] = " ORDER BY `" . $sTable . "`.`order` " . $sWay . ", `" . $sTable . "`.`id` " . $sWay . " ";
+                break;
+
+            case 'order_by_ghosts':
+                $aSql['order'] = " ORDER BY `g`.`order` " . $sWay . ", `" . $sTable . "`.`id` " . $sWay . " ";
+                break;
+
+            case 'last':
+                $aSql['order'] = " ORDER BY `f`.`added` " . $sWay . ", `" . $sTable . "`.`id` " . $sWay . " ";
+                break;
+
+            case 'featured':
+                $aSql['order'] = " ORDER BY `" . $sTable . "`.`featured` " . $sWay . ", `" . $sTable . "`.`id` " . $sWay . "";
+                break;
+
+            case 'popular':
+                $aSql['order'] = " ORDER BY `" . $sTable . "`.`views` " . $sWay . ", `" . $sTable . "`.`id` " . $sWay . "";
+                break;
+
+            case 'top':
+                $aSql['order'] = '';
+
+                $aPartsUp = $aPartsDown = array();
+                if(!empty($CNF['OBJECT_VOTES_MEDIA']) && ($oVote = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES_MEDIA'], 0, false)) !== false) {
+                    $aVote = $oVote->getSystemInfo();
+                    if(!empty($aVote['trigger_table']) && !empty($aVote['trigger_field_count']))
+                        $aPartsUp[] = '`' . $aVote['trigger_table'] . '`.`' . $aVote['trigger_field_count'] . '`';
+                }
+
+                if(!empty($CNF['OBJECT_SCORES_MEDIA']) && ($oScore = BxDolScore::getObjectInstance($CNF['OBJECT_SCORES_MEDIA'], 0, false)) !== false) {
+                    $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'] . '`';
+                    }
+                }
+
+                if(empty($aPartsUp) && empty($aPartsDown))
+                    break;
+
+                $aSql['order'] = ' ORDER BY ' . pow(10, 8) . ' * ((' . implode(' + ', $aPartsUp) . ') - (' . implode(' + ', $aPartsDown) . ')) / (UNIX_TIMESTAMP() - `f`.`added`) ' . $sWay;
+                break;
+        }
+
+        return $aSql;
+    }
+
+    public function getNextPrevItem($aMediaInfo, $isNext)
+    {
+        $sOper = $isNext ? '<' : '>';
+
+        $this->sOrderDirection = $isNext ? 'DESC' : 'ASC';
+        $this->aCurrent['paginate']['perPage'] = 1;
+        $this->aCurrent['restriction']['next_prev2'] = array('value' => $aMediaInfo['id'], 'field' => 'id', 'operator' => '!=');
+
+        $this->setProcessPrivateContent(true);
+        $aRestrictions = array_keys($this->aCurrent['restriction']);
+        foreach ($aRestrictions as $sKey) 
+            if (0 === strpos($sKey, 'privacy_'))
+                unset($this->aCurrent['restriction'][$sKey]);
+
+        switch ($this->aCurrent['sorting']) {
+            case 'order':
+                $this->sOrderDirection = $isNext ? 'ASC' : 'DESC';
+                $sOper = $isNext ? '>' : '<';
+                $this->aCurrent['restriction_sql'] = " AND (`{$this->aCurrent['table']}`.`order` {$sOper} {$aMediaInfo['order']} OR (`{$this->aCurrent['table']}`.`order` = {$aMediaInfo['order']} AND `{$this->aCurrent['table']}`.`id` {$sOper} {$aMediaInfo['id']})) ";
+                break;
+            case 'order_by_ghosts':
+                $this->sOrderDirection = $isNext ? 'ASC' : 'DESC';
+                $sOper = $isNext ? '>' : '<';
+                $this->aCurrent['restriction_sql'] = " AND (`g`.`order` {$sOper} {$aMediaInfo['gorder']} OR (`g`.`order` = {$aMediaInfo['gorder']} AND `{$this->aCurrent['table']}`.`id` {$sOper} {$aMediaInfo['id']})) ";
+                break;
+            case 'last':
+                $this->aCurrent['restriction_sql'] = " AND (`f`.`added` {$sOper} {$aMediaInfo['added']} OR (`f`.`added` = {$aMediaInfo['added']} AND `{$this->aCurrent['table']}`.`id` {$sOper} {$aMediaInfo['id']})) ";
+                break;
+            case 'featured':
+                $this->aCurrent['restriction_sql'] = " AND (`{$this->aCurrent['table']}`.`featured` {$sOper} {$aMediaInfo['featured']} OR (`{$this->aCurrent['table']}`.`featured` = {$aMediaInfo['featured']} AND `{$this->aCurrent['table']}`.`id` {$sOper} {$aMediaInfo['id']})) ";
+                break;
+            case 'popular':
+                $this->aCurrent['restriction_sql'] = " AND (`{$this->aCurrent['table']}`.`views` {$sOper} {$aMediaInfo['views']} OR (`{$this->aCurrent['table']}`.`views` = {$aMediaInfo['views']} AND `{$this->aCurrent['table']}`.`id` {$sOper} {$aMediaInfo['id']})) ";
+                break;
+        }
+
+        $aData = $this->getSearchData();
+        if (count($aData) > 0)
+            return array_shift($aData);
+
+        return false;
+    }
+
+    function getRssUnitLink (&$a)
+    {
+        return bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->oModule->_oConfig->CNF['URI_VIEW_MEDIA'] . '&id=' . $a['id']));
+    }
+
+    function addProcessingParamsAPI()
+    {
+        return array_merge(parent::addProcessingParamsAPI(), [
+            'album_id'
+        ]);
+    }
+
+    function decodeDataAPI($a, $sMethod = 'getDataMediaAPI')
+    {
+        return parent::decodeDataAPI($a, $sMethod);
+    }
+
+    protected function _updateCurrentForOrderByGhosts()
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $this->aCurrent['join']['ghosts'] = [
+            'type' => 'INNER',
+            'table' => 'sys_storage_ghosts',
+            'table_alias' => 'g',
+            'mainField' => 'file_id',
+            'on_sql' => $this->oModule->_oDb->prepareAsString(" `g`.`id`=`bx_albums_files2albums`.`file_id` AND `g`.`content_id`=`bx_albums_files2albums`.`content_id` AND `g`.`object`=? ", $CNF['OBJECT_STORAGE']),
+            'joinFields' => array('order'),
+        ];
+        $this->aCurrent['sorting'] = 'order_by_ghosts';
+        $this->sOrderDirection = 'ASC';
+    }
+
+/*
+    function displaySearchUnit ($aData)
+    {
+        $oMain = $this->getMain();
+        return $oMain->_oTemplate->unit($aData, $this->bProcessPrivateContent, $this->_bLiveSearch ? $this->sUnitTemplateLiveSearch : $this->sUnitTemplate, $this->aUnitParams);
+    }
+*/
+}
+
+/** @} */

+ 452 - 0
modules/boonex/albums/updates/15.0.2_15.0.3/source/classes/BxAlbumsTemplate.php

@@ -0,0 +1,452 @@
+<?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 = '') 
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        if (!($aAlbumInfo = $oModule->_oDb->getContentInfoById($aMediaInfo['content_id'])))
+            return '';
+
+        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.2_15.0.3/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.3',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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'
+    ),
+
+);
+
+/** @} */

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

@@ -0,0 +1,106 @@
+-- PAGES: 
+
+-- PAGES: config_api
+
+-- 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';
+
+-- 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';

+ 51 - 0
modules/boonex/credits/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' => 'Credits',
+    'version_from' => '15.0.1',
+    'version_to' => '15.0.2',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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.1_15.0.2/',
+    'home_uri' => 'credits_update_1501_1502',
+
+    '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.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 BxCreditsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

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

+ 22 - 0
modules/boonex/credits/updates/15.0.1_15.0.2/source/install/sql/enable-app.sql

@@ -0,0 +1,22 @@
+-- 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';
+
+-- 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-profiles-administration';

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

+ 13 - 0
modules/boonex/events/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 BxEventsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

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

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

@@ -0,0 +1,104 @@
+-- PAGES: config_api
+UPDATE `sys_pages_blocks` SET `config_api`='{\r\n\"content_type\":\"browse_simple\",\r\n\"header_more_url\":\"/events-home\",\r\n\"view\":\"row\"\r\n}' WHERE `object`='sys_explore' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_featured_entries_view_showcase';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_create_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_create_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_profile_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_profile_calendar';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_entry_context' AND `title`='_bx_events_page_block_title_entry_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_fans';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_admins';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_entry_location';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_entry_reports';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile_closed' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_profile_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile_closed' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_fans';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_edit_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_edit_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_invite' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_invite_to_group';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_delete_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_delete_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_join_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_join_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_profile_info' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_profile_description';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_profile_info' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_system_profile_info' AND `title`='_bx_events_page_block_title_profile_info_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_profile_info' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_profile_calendar';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_profile_info' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_entry_location';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_profile_pricing' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_profile_pricing';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_home' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_featured_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_home' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_latest_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_top' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_top_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_context' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_entries_in_context' AND `title`='_bx_events_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_context' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_calendar_in_context' AND `title`='_bx_events_page_block_title_calendar_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_past' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_past_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_calendar' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_calendar';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_search' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_search' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_joined_events' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_joined_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_manage' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_system_manage' AND `title`='_bx_events_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_administration' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_system_manage_administration' AND `title`='_bx_events_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_joined' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_entries_actions' AND `title`='_bx_events_page_block_title_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_joined' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_favorites_of_author' AND `title`='_bx_events_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_joined' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_joined_entries' AND `title`='_bx_events_page_block_title_joined_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_favorites' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_favorites_entries' AND `title`='_bx_events_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_favorites' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_favorites' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='system' AND `title_system`='_sys_page_block_title_sys_create_post_context' AND `title`='_sys_page_block_title_create_post_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_joined' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_sys_joined_calendar' AND `title`='_bx_events_page_block_title_joined_calendar';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_view_profile' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_profile_sessions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_profile_sessions' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_profile_sessions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_questionnaire' AND `module`='bx_events' AND `title_system`='' AND `title`='_bx_events_page_block_title_edit_questionnaire';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_manage_item' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_system_fans' AND `title`='_bx_events_page_block_title_fans_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_manage_item' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_system_invites' AND `title`='_bx_events_page_block_title_fans_invites';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_events_fans' AND `module`='bx_events' AND `title_system`='_bx_events_page_block_title_system_fans' AND `title`='_bx_events_page_block_title_fans_link';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_site' AND `module`='bx_events' AND `name`='events-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_events' AND `name`='events-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_content_links' AND `module`='bx_events' AND `name`='create-event-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_events' AND `name`='profile-stats-my-events';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_followings' AND `module`='bx_events' AND `name`='events';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_dashboard_manage_tools' AND `module`='bx_events' AND `name`='events-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions' AND `module`='bx_events' AND `name`='join-event-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions' AND `module`='bx_events' AND `name`='profile-fan-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions' AND `module`='bx_events' AND `name`='profile-subscribe-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions' AND `module`='bx_events' AND `name`='profile-set-badges';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions' AND `module`='bx_events' AND `name`='ical-export';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions' AND `module`='bx_events' AND `name`='profile-actions-more';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions' AND `module`='bx_events' AND `name`='profile-check-in';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_more' AND `module`='bx_events' AND `name`='edit-event-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_more' AND `module`='bx_events' AND `name`='event-pricing';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_more' AND `module`='bx_events' AND `name`='invite-to-event';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_more' AND `module`='bx_events' AND `name`='delete-event-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_more' AND `module`='bx_events' AND `name`='approve-event-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_more' AND `module`='bx_events' AND `name`='event-sessions';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_more' AND `module`='bx_events' AND `name`='event-questionnaire';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_all' AND `module`='bx_events' AND `name`='profile-fan-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_all' AND `module`='bx_events' AND `name`='profile-fan-remove';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_all' AND `module`='bx_events' AND `name`='profile-subscribe-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_all' AND `module`='bx_events' AND `name`='profile-subscribe-remove';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_actions_all' AND `module`='bx_events' AND `name`='profile-check-in';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_meta' AND `module`='bx_events' AND `name`='members';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_meta' AND `module`='bx_events' AND `name`='subscribers';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_meta' AND `module`='bx_events' AND `name`='views';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_meta' AND `module`='bx_events' AND `name`='votes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_meta' AND `module`='bx_events' AND `name`='comments';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_my' AND `module`='bx_events' AND `name`='create-event-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_submenu' AND `module`='bx_events' AND `name`='events-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_submenu' AND `module`='bx_events' AND `name`='events-top';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_submenu' AND `module`='bx_events' AND `name`='events-upcoming';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_submenu' AND `module`='bx_events' AND `name`='events-calendar';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_submenu' AND `module`='bx_events' AND `name`='events-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_submenu' AND `module`='bx_events' AND `name`='events-joined';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_submenu' AND `module`='bx_events' AND `name`='events-followed';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_submenu' AND `module`='bx_events' AND `name`='view-event-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_submenu' AND `module`='bx_events' AND `name`='event-profile-info';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_view_submenu' AND `module`='bx_events' AND `name`='event-fans';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_snippet_meta' AND `module`='bx_events' AND `name`='join-paid';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_snippet_meta' AND `module`='bx_events' AND `name`='join';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_snippet_meta' AND `module`='bx_events' AND `name`='leave';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_snippet_meta' AND `module`='bx_events' AND `name`='subscribe';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_snippet_meta' AND `module`='bx_events' AND `name`='unsubscribe';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_menu_manage_tools' AND `module`='bx_events' AND `name`='clear-reports';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_events_menu_manage_tools' AND `module`='bx_events' AND `name`='delete';

+ 51 - 0
modules/boonex/files/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' => 'Files',
+    'version_from' => '15.0.3',
+    'version_to' => '15.0.4',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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.3_15.0.4/',
+    'home_uri' => 'files_update_1503_1504',
+
+    'module_dir' => 'boonex/files/',
+    'module_uri' => 'files',
+
+    'db_prefix' => 'bx_files_',
+    'class_prefix' => 'BxFiles',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Files',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

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

+ 142 - 0
modules/boonex/files/updates/15.0.3_15.0.4/source/install/config.php

@@ -0,0 +1,142 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Files Files
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_files',
+    'title' => 'Files',
+    'note' => 'Basic blogging module.',
+    'version' => '15.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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',
+    ),
+
+);
+
+/** @} */

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

@@ -0,0 +1,78 @@
+-- PAGES: config_api
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'post\',\r\n    \r\n}' WHERE `object`='bx_files_view_entry';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_create_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_create_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_edit_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_edit_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_delete_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_delete_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_entry_text';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_sys_entry_context' AND `title`='_bx_files_page_block_title_entry_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_entry_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_entry_comments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_entry_location';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_entry_reports';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_view_entry_comments' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_entry_comments' AND `title`='_bx_files_page_block_title_entry_comments_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_popular' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_popular_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_top' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_top_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_updated' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_updated_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_author' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_sys_favorites_of_author' AND `title`='_bx_files_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_author' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_sys_entries_of_author' AND `title`='_bx_files_page_block_title_entries_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_author' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_sys_entries_in_context' AND `title`='_bx_files_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_favorites' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_sys_favorites_entries' AND `title`='_bx_files_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_favorites' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_favorites' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_context' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_sys_entries_in_context' AND `title`='_bx_files_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_home' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_featured_entries_view_extended';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_home' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_recent_entries_view_extended';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_home' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_popular_keywords';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_home' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_cats';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_search' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_search' AND `module`='bx_files' AND `title_system`='' AND `title`='_bx_files_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_manage' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_system_manage' AND `title`='_bx_files_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_files_administration' AND `module`='bx_files' AND `title_system`='_bx_files_page_block_title_system_manage_administration' AND `title`='_bx_files_page_block_title_manage';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_files' AND `name`='profile-stats-my-files';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view' AND `module`='bx_files' AND `name`='download-file';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view' AND `module`='bx_files' AND `name`='edit-file';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view' AND `module`='bx_files' AND `name`='delete-file';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view' AND `module`='bx_files' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='download-file';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='edit-file';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='delete-file';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='view';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='reaction';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='score';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='favorite';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='feature';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='repost';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='notes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='audit';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='social-sharing';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_actions' AND `module`='bx_files' AND `name`='more-auto';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_inline' AND `module`='bx_files' AND `name`='bookmark';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_inline' AND `module`='bx_files' AND `name`='download-file';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_inline' AND `module`='bx_files' AND `name`='delete-file-quick';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_inline' AND `module`='bx_files' AND `name`='move-to';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_inline' AND `module`='bx_files' AND `name`='edit-title';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_inline' AND `module`='bx_files' AND `name`='preview';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_inline' AND `module`='bx_files' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_view_inline' AND `module`='bx_files' AND `name`='more-auto';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_my' AND `module`='bx_files' AND `name`='create-file';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_submenu' AND `module`='bx_files' AND `name`='files-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_submenu' AND `module`='bx_files' AND `name`='files-popular';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_submenu' AND `module`='bx_files' AND `name`='files-top';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_submenu' AND `module`='bx_files' AND `name`='files-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_submenu' AND `module`='bx_files' AND `name`='files-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_snippet_meta' AND `module`='bx_files' AND `name`='date';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_snippet_meta' AND `module`='bx_files' AND `name`='size';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_files_snippet_meta' AND `module`='bx_files' AND `name`='author';

+ 51 - 0
modules/boonex/forum/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' => 'Discussions',
+    'version_from' => '15.0.3',
+    'version_to' => '15.0.4',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * '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.3_15.0.4/',
+    'home_uri' => 'forum_update_1503_1504',
+
+    '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.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 BxForumUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

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

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

@@ -0,0 +1,76 @@
+-- PAGES: config_api
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'post\',\r\n}' WHERE `object`='bx_forum_view_entry';
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'navigator\',\r\n    blocks: {\r\n        browse: {\r\n            name: \'system:categories_list\',\r\n            showTitle: false,\r\n            showBg: false,\r\n        },\r\n        browse_sidebar: {\r\n            name: \'bx_forum:browse_popular\',\r\n            showTitle: true,\r\n            showBg: false,\r\n            sidebar: true,\r\n        },\r\n        categories: {\r\n            name: \'system:categories_list\',\r\n            showTitle: false,\r\n            showBg: true,\r\n            sidebar: false,\r\n            hidden: true,\r\n        },\r\n    },\r\n}' WHERE `object`='bx_forum_categories';
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'navigator\',\r\n    blocks: {\r\n        browse: {\r\n            name: \'bx_forum:browse_category\',\r\n            showTitle: false,\r\n            showBg: false,\r\n            perLine: 1,\r\n            skeleton: \'notifications\',\r\n        },\r\n        browse_sidebar: {\r\n            name: \'bx_forum:browse_popular\',\r\n            showTitle: true,\r\n            showBg: false,\r\n            sidebar: true,\r\n        },\r\n        categories: {\r\n            name: \'system:categories_list\',\r\n            showTitle: false,\r\n            showBg: true,\r\n            sidebar: false,\r\n            hidden: true,\r\n        },\r\n    },\r\n}' WHERE `object`='bx_forum_category';
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'navigator\',\r\n    blocks: {\r\n        browse: {\r\n            name: \'bx_forum:browse_new\',\r\n            showTitle: false,\r\n            showBg: false,\r\n            perLine: 1,\r\n        },\r\n        browse_sidebar: {\r\n            name: \'bx_forum:browse_popular\',\r\n            showTitle: true,\r\n            showBg: false,\r\n            sidebar: true,\r\n            unitType: \'small\',\r\n        },\r\n        categories: {\r\n            name: \'system:categories_list\',\r\n            showTitle: false,\r\n            showBg: true,\r\n            sidebar: false,\r\n            hidden: true,\r\n        },\r\n    },\r\n}' WHERE `object`='bx_forum_home';
+
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_create_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_create_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_edit_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_edit_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_delete_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_delete_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_view_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_view_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entry_text';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_view_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_view_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entry_attachments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_view_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entry_polls';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_view_entry' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entry_comments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_categories' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entries_categories';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_category' AND `module`='bx_forum' AND `title_system`='_bx_forum_page_block_title_sys_entries_by_category' AND `title`='_bx_forum_page_block_title_entries_by_category';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_category' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_cats';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_keyword' AND `module`='bx_forum' AND `title_system`='_bx_forum_page_block_title_sys_entries_by_keyword' AND `title`='_bx_forum_page_block_title_entries_by_keyword';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_author' AND `module`='bx_forum' AND `title_system`='_bx_forum_page_block_title_sys_entries_of_author' AND `title`='_bx_forum_page_block_title_entries_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_favorites' AND `module`='bx_forum' AND `title_system`='_bx_forum_page_block_title_sys_favorites_entries' AND `title`='_bx_forum_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_favorites' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_favorites' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_context' AND `module`='bx_forum' AND `title_system`='_bx_forum_page_block_title_sys_entries_in_context' AND `title`='_bx_forum_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_home' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_cats';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_search' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entries_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_search' AND `module`='bx_forum' AND `title_system`='' AND `title`='_bx_forum_page_block_title_entries_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_manage' AND `module`='bx_forum' AND `title_system`='_bx_forum_page_block_title_system_manage' AND `title`='_bx_forum_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_forum_administration' AND `module`='bx_forum' AND `title_system`='_bx_forum_page_block_title_system_manage_administration' AND `title`='_bx_forum_page_block_title_manage';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_site' AND `module`='bx_forum' AND `name`='discussions-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_forum' AND `name`='discussions-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_content_links' AND `module`='bx_forum' AND `name`='create-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_create_post' AND `module`='bx_forum' AND `name`='create-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_forum' AND `name`='profile-stats-my-forum';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_dashboard_manage_tools' AND `module`='bx_forum' AND `name`='discussions-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_entry_attachments' AND `module`='bx_forum' AND `name`='photo_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_entry_attachments' AND `module`='bx_forum' AND `name`='video_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_entry_attachments' AND `module`='bx_forum' AND `name`='video_record_video';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_entry_attachments' AND `module`='bx_forum' AND `name`='file_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_entry_attachments' AND `module`='bx_forum' AND `name`='poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_entry_attachments' AND `module`='bx_forum' AND `name`='add-link';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view' AND `module`='bx_forum' AND `name`='resolve-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view' AND `module`='bx_forum' AND `name`='stick-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view' AND `module`='bx_forum' AND `name`='lock-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view' AND `module`='bx_forum' AND `name`='hide-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view' AND `module`='bx_forum' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view' AND `module`='bx_forum' AND `name`='more';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view_more' AND `module`='bx_forum' AND `name`='unresolve-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view_more' AND `module`='bx_forum' AND `name`='unstick-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view_more' AND `module`='bx_forum' AND `name`='unlock-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view_more' AND `module`='bx_forum' AND `name`='unhide-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view_more' AND `module`='bx_forum' AND `name`='edit-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view_more' AND `module`='bx_forum' AND `name`='delete-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view_actions' AND `module`='bx_forum' AND `name`='edit-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_view_actions' AND `module`='bx_forum' AND `name`='delete-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_my' AND `module`='bx_forum' AND `name`='create-discussion';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_submenu' AND `module`='bx_forum' AND `name`='discussions-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_submenu' AND `module`='bx_forum' AND `name`='discussions-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_submenu' AND `module`='bx_forum' AND `name`='discussions-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_snippet_meta_main' AND `module`='bx_forum' AND `name`='comments';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_snippet_meta_main' AND `module`='bx_forum' AND `name`='status';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_snippet_meta_counters' AND `module`='bx_forum' AND `name`='views';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_snippet_meta_counters' AND `module`='bx_forum' AND `name`='votes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_snippet_meta_counters' AND `module`='bx_forum' AND `name`='comments';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_snippet_meta_reply' AND `module`='bx_forum' AND `name`='reply-author';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_snippet_meta_reply' AND `module`='bx_forum' AND `name`='reply-date';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_forum_snippet_meta_reply' AND `module`='bx_forum' AND `name`='reply-text';

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

+ 13 - 0
modules/boonex/groups/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 BxGroupsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 152 - 0
modules/boonex/groups/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    Groups Groups
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_groups',
+    'title' => 'Groups',
+    'note' => 'Basic group profiles functionality.',
+    'version' => '15.0.2',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/groups/',
+    'home_uri' => 'groups',
+
+    'db_prefix' => 'bx_groups_',
+    'class_prefix' => 'BxGroups',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Groups',
+
+    /**
+     * 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_groups_pics'
+    ),
+
+	/**
+     * Transcoders.
+     */
+    'transcoders' => array(
+    	'bx_groups_icon', 
+    	'bx_groups_thumb', 
+    	'bx_groups_avatar', 
+        'bx_groups_avatar_big', 
+    	'bx_groups_picture', 
+    	'bx_groups_cover', 
+    	'bx_groups_cover_thumb',
+        'bx_groups_gallery'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_groups',
+    	'bx_groups_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',
+    ),
+);
+
+/** @} */

+ 83 - 0
modules/boonex/groups/updates/15.0.1_15.0.2/source/install/sql/enable-app.sql

@@ -0,0 +1,83 @@
+-- PAGES: config_api
+UPDATE `sys_pages_blocks` SET `config_api`='{\r\n    layout: \'navigator\',\r\n    blocks: {\r\n        browse: { name: \'system:get_results\', showTitle: false, showBg: false},\r\n        search: { name: \'system:get_form\', showTitle: false, showBg: false, sidebar: false, leftbar: true },\r\n    }\r\n}' WHERE `object`='bx_groups_search' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `config_api`='{\r\n\"content_type\":\"browse_simple\",\r\n\"header_more_url\":\"/groups-home\",\r\n\"view\":\"row\"\r\n}' WHERE `object`='sys_explore' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_sys_featured_entries_view_showcase';
+
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_create_profile' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_create_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_view_profile' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_profile_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_view_profile' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_fans';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_view_profile' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_admins';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_view_profile' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_profile_description';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_view_profile_closed' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_profile_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_view_profile_closed' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_fans';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_edit_profile' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_edit_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_invite' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_invite_to_group';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_delete_profile' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_delete_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_join_profile' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_join_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_profile_info' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_system_profile_info' AND `title`='_bx_groups_page_block_title_profile_info_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_profile_info' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_profile_description';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_profile_pricing' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_system_profile_pricing' AND `title`='_bx_groups_page_block_title_profile_pricing_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_home' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_featured_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_home' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_latest_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_top' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_top_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_search' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_search' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_context' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_sys_entries_in_context' AND `title`='_bx_groups_page_block_title_entries_in_context_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_joined_groups' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_joined_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_manage' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_system_manage' AND `title`='_bx_groups_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_administration' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_system_manage_administration' AND `title`='_bx_groups_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_joined' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_sys_entries_actions' AND `title`='_bx_groups_page_block_title_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_joined' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_sys_entries_of_author' AND `title`='_bx_groups_page_block_title_entries_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_joined' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_sys_favorites_of_author' AND `title`='_bx_groups_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_joined' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_sys_joined_entries' AND `title`='_bx_groups_page_block_title_joined_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_favorites' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_sys_favorites_entries' AND `title`='_bx_groups_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_favorites' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_favorites' AND `module`='bx_groups' AND `title_system`='' AND `title`='_bx_groups_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_view_profile' AND `module`='system' AND `title_system`='_sys_page_block_title_sys_create_post_context' AND `title`='_sys_page_block_title_create_post_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_fans' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_system_fans' AND `title`='_bx_groups_page_block_title_fans_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_groups_manage_item' AND `module`='bx_groups' AND `title_system`='_bx_groups_page_block_title_system_fans_manage' AND `title`='_bx_groups_page_block_title_fans_link';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_site' AND `module`='bx_groups' AND `name`='groups-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_groups' AND `name`='groups-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_content_links' AND `module`='bx_groups' AND `name`='create-group-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_groups' AND `name`='profile-stats-my-groups';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_followings' AND `module`='bx_groups' AND `name`='groups';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_dashboard_manage_tools' AND `module`='bx_groups' AND `name`='groups-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions' AND `module`='bx_groups' AND `name`='join-group-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions' AND `module`='bx_groups' AND `name`='profile-fan-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions' AND `module`='bx_groups' AND `name`='profile-subscribe-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions' AND `module`='bx_groups' AND `name`='profile-set-badges';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions' AND `module`='bx_groups' AND `name`='profile-actions-more';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions_more' AND `module`='bx_groups' AND `name`='edit-group-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions_more' AND `module`='bx_groups' AND `name`='invite-to-group';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions_more' AND `module`='bx_groups' AND `name`='delete-group-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions_all' AND `module`='bx_groups' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions_all' AND `module`='bx_groups' AND `name`='profile-fans';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_actions_all' AND `module`='bx_groups' AND `name`='profile-subscriptions';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_meta' AND `module`='bx_groups' AND `name`='members';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_meta' AND `module`='bx_groups' AND `name`='views';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_meta' AND `module`='bx_groups' AND `name`='votes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_meta' AND `module`='bx_groups' AND `name`='comments';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_my' AND `module`='bx_groups' AND `name`='create-group-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_submenu' AND `module`='bx_groups' AND `name`='groups-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_submenu' AND `module`='bx_groups' AND `name`='groups-top';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_submenu' AND `module`='bx_groups' AND `name`='groups-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_submenu' AND `module`='bx_groups' AND `name`='groups-joined';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_submenu' AND `module`='bx_groups' AND `name`='groups-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_submenu' AND `module`='bx_groups' AND `name`='view-group-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_view_submenu' AND `module`='bx_groups' AND `name`='group-fans';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_snippet_meta' AND `module`='bx_groups' AND `name`='join-paid';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_snippet_meta' AND `module`='bx_groups' AND `name`='join';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_snippet_meta' AND `module`='bx_groups' AND `name`='leave';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_snippet_meta' AND `module`='bx_groups' AND `name`='subscribe';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_snippet_meta' AND `module`='bx_groups' AND `name`='unsubscribe';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_snippet_meta' AND `module`='bx_groups' AND `name`='ignore-join';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_menu_manage_tools' AND `module`='bx_groups' AND `name`='clear-reports';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_groups_menu_manage_tools' AND `module`='bx_groups' AND `name`='delete';

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

+ 13 - 0
modules/boonex/market/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 BxMarketUpdater extends BxDolStudioUpdater
+{
+    public function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 140 - 0
modules/boonex/market/updates/15.0.3_15.0.4/source/install/config.php

@@ -0,0 +1,140 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Market Market
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_market',
+    'title' => 'Market',
+    'note' => 'Market module.',
+    'version' => '15.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/market/',
+    'home_uri' => 'market',
+
+    'db_prefix' => 'bx_market_',
+    'class_prefix' => 'BxMarket',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Market',
+
+	/**
+     * 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_market_files',
+        'bx_market_photos'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+        'bx_market_icon',
+        'bx_market_thumb',
+        'bx_market_cover',
+        'bx_market_screenshot',
+		'bx_market_preview',
+        'bx_market_gallery',
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_market',
+    	'bx_market_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_page_triggers' => 1,
+    	'process_menu_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'
+    ),
+
+);
+
+/** @} */

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

@@ -0,0 +1,27 @@
+-- PAGES: config_api
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'post\',\r\n   \r\n}' WHERE `object`='bx_market_view_entry';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_create_entry' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_create_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_edit_entry' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_edit_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_delete_entry' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_delete_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_view_entry' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_entry_text';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_view_entry' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_view_entry' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_entry_comments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_view_entry' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_entry_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_view_entry' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_featured' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_featured_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_top' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_top_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_home' AND `module`='bx_market' AND `title_system`='' AND `title`='_bx_market_page_block_title_latest_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_manage' AND `module`='bx_market' AND `title_system`='_bx_market_page_block_title_system_manage' AND `title`='_bx_market_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_market_administration' AND `module`='bx_market' AND `title_system`='_bx_market_page_block_title_system_manage_administration' AND `title`='_bx_market_page_block_title_manage';
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_market_submenu' AND `module`='bx_market' AND `name`='products-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_market_submenu' AND `module`='bx_market' AND `name`='products-featured';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_market_submenu' AND `module`='bx_market' AND `name`='products-top';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_market_submenu' AND `module`='bx_market' AND `name`='products-manage';

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

+ 13 - 0
modules/boonex/notifications/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 BxNtfsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 82 - 0
modules/boonex/notifications/updates/15.0.3_15.0.4/source/install/config.php

@@ -0,0 +1,82 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Notifications Notifications
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_notifications',
+    'title' => 'Notifications',
+    'note' => 'Notifications module.',
+    'version' => '15.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/notifications/',
+    'home_uri' => 'notifications',
+
+    'db_prefix' => 'bx_notifications_',
+    'class_prefix' => 'BxNtfs',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Notifications',
+
+    /**
+     * 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,
+    	'update_relations_for_all' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+    	'update_relations_for_all' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Relations Section
+     */
+    'relation_handlers' => array(
+    	'on_install' => '',
+    	'on_uninstall' => 'delete_module_events',
+	    'on_enable' => 'add_handlers',
+	    'on_disable' => 'delete_handlers',
+    ),
+);
+
+/** @} */

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

@@ -0,0 +1,20 @@
+-- PAGES: config_api
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_notifications_view' AND `module`='bx_notifications' AND `title_system`='' AND `title`='_bx_ntfs_page_block_title_view';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_notifications_settings' AND `module`='bx_notifications' AND `title_system`='' AND `title`='_bx_ntfs_page_block_title_settings';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_toolbar_member' AND `module`='bx_notifications' AND `name`='notifications-preview';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_settings' AND `module`='bx_notifications' AND `name`='notifications-settings';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_ntfs_submenu' AND `module`='bx_notifications' AND `name`='notifications-view';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_notifications_submenu' AND `module`='bx_notifications' AND `name`='notifications-all';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_notifications_preview' AND `module`='bx_notifications' AND `name`='more';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_notifications_settings' AND `module`='bx_notifications' AND `name`='notifications-site';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_notifications_settings' AND `module`='bx_notifications' AND `name`='notifications-email';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_notifications_settings' AND `module`='bx_notifications' AND `name`='notifications-push';

+ 71 - 0
modules/boonex/notifications/updates/15.0.3_15.0.4/source/js/view.js

@@ -0,0 +1,71 @@
+function BxNtfsView(oOptions) {
+    BxNtfsMain.call(this, oOptions);
+
+    this._sObjName = oOptions.sObjName == undefined ? 'oNtfsView' : oOptions.sObjName;
+    this._iOwnerId = oOptions.iOwnerId == undefined ? 0 : oOptions.iOwnerId;
+}
+
+BxNtfsView.prototype = Object.create(BxNtfsMain.prototype);
+BxNtfsView.prototype.constructor = BxNtfsView;
+
+/**
+ * Is needed to switch between different browsing types dynamically.
+ * NOTE. The function isn't used for now.
+ */
+BxNtfsView.prototype.changeType = function(oElement, sType) {
+	this._oRequestParams.start = 0;
+	this._oRequestParams.type = sType;
+
+    this._getPosts(oElement);
+};
+
+BxNtfsView.prototype.changePage = function(oElement, iStart, iPerPage) {
+	this._oRequestParams.start = iStart;
+    this._oRequestParams.per_page = iPerPage;
+
+    this._getPosts(oElement);
+};
+
+BxNtfsView.prototype.markAsClicked = function(oElement, iId) {
+    var oData = this._getDefaultData();
+    oData['id'] = iId;
+
+    jQuery.get(
+        this._sActionsUrl + 'mark_as_clicked/',
+        oData,
+        function(oData) {
+            if(!oData || parseInt(oData.code) !== 0)
+                return;
+
+            var sLink = $(oElement).attr('href');
+            if(sLink)
+                document.location = sLink;
+        },
+        'json'
+    );
+
+    return false;
+};
+
+BxNtfsView.prototype._getPosts = function(oElement) {
+    var $this = this;
+
+    this.loadingInBlock(oElement, true);
+
+    jQuery.get(
+        this._sActionsUrl + 'get_posts/',
+        this._getDefaultData(),
+        function(oData) {
+        	if(oData && oData.events != undefined) {
+        		var sEvents = $.trim(oData.events);
+
+        		$this.loadingInBlock(oElement, false);
+
+    			$('#' + $this._aHtmlIds['events']).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+	                $(this).html(sEvents).show().bxProcessHtml();
+	            });
+        	}
+        },
+        'json'
+    );
+};

+ 58 - 0
modules/boonex/organizations/updates/15.0.3_15.0.4/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' => 'Organizations',
+    'version_from' => '15.0.3',
+    'version_to' => '15.0.4',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/organizations/updates/update_15.0.3_15.0.4/',
+    'home_uri' => 'orgs_update_1503_1504',
+
+    'module_dir' => 'boonex/organizations/',
+    'module_uri' => 'orgs',
+
+    'db_prefix' => 'bx_organizations_',
+    'class_prefix' => 'BxOrgs',
+
+    /**
+      * Transcoders.
+      */
+    'transcoders' => array(
+        'bx_organizations_badge'
+    ),
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 0,
+        'update_files' => 1,
+        'update_languages' => 0,
+        'clear_db_cache' => 0,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Organizations',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/organizations/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 BxOrgsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 153 - 0
modules/boonex/organizations/updates/15.0.3_15.0.4/source/install/config.php

@@ -0,0 +1,153 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Organizations Organizations
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_organizations',
+    'title' => 'Organizations',
+    'note' => 'Basic organization profiles functionality.',
+    'version' => '15.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/organizations/',
+    'home_uri' => 'orgs',
+
+    'db_prefix' => 'bx_organizations_',
+    'class_prefix' => 'BxOrgs',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Organizations',
+
+    /**
+     * Connections.
+     */
+    'connections' => array(
+    	'sys_profiles_friends' => array ('type' => 'profiles'),
+        'sys_profiles_subscriptions' => array ('type' => 'profiles'),
+        'sys_profiles_relations' => array ('type' => 'profiles'),
+    ),
+
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+        'trigger_profile_view_submenu',
+        'trigger_profile_snippet_meta',
+        'trigger_profile_view_actions',
+    ),
+
+    /**
+     * Page triggers.
+     */
+    'page_triggers' => array (
+    	'trigger_page_profile_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_organizations_pics'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+    	'bx_organizations_icon', 
+    	'bx_organizations_thumb', 
+    	'bx_organizations_avatar', 
+        'bx_organizations_avatar_big', 
+    	'bx_organizations_picture', 
+    	'bx_organizations_cover', 
+    	'bx_organizations_cover_thumb',
+    	'bx_organizations_gallery',
+        'bx_organizations_badge'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_organizations',
+    	'bx_organizations_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',
+    ),
+);
+
+/** @} */

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

@@ -0,0 +1,106 @@
+-- PAGES: 
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `class`, `submenu`, `tabs`, `async`, `visible_for_levels`, `hidden_on`, `type`, `content`, `content_empty`, `text`, `text_updated`, `help`, `cache_lifetime`, `config_api`, `deletable`, `copyable`, `active`, `active_api`, `order`) VALUES
+('bx_organizations_view_profile', 1, 'system', '_sys_page_block_title_sys_create_post_context', '_sys_page_block_title_create_post_context', 11, '', '', 1, 4, 2147483644, '0', 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:20:"get_create_post_form";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}s:5:"class";s:13:"TemplServices";}', '', '', 0, '', 0, '', 1, 0, 0, 1, 0);
+
+-- PAGES: config_api
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_create_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_create_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_profile_description';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_fans';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_profile_friends';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_profile_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_entry_reports';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile_closed' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_profile_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile_closed' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_fans';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_edit_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_edit_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_invite' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_invite_to_organization';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_delete_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_delete_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_join_profile' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_join_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_info' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_profile_info' AND `title`='_bx_orgs_page_block_title_profile_info_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_info' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_profile_description';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_pricing' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_profile_pricing' AND `title`='_bx_orgs_page_block_title_profile_pricing_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_friends' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_profile_friends' AND `title`='_bx_orgs_page_block_title_profile_friends_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_friend_requests' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_friend_requests' AND `title`='_bx_orgs_page_block_title_friend_requests_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_favorites' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_profile_favorites' AND `title`='_bx_orgs_page_block_title_profile_favorites';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_subscriptions' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_profile_subscriptions' AND `title`='_bx_orgs_page_block_title_profile_subscriptions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_subscriptions' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_profile_subscribed_me' AND `title`='_bx_orgs_page_block_title_profile_subscribed_me';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_relations' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_profile_relations' AND `title`='_bx_orgs_page_block_title_profile_relations';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_profile_relations' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_profile_related_me' AND `title`='_bx_orgs_page_block_title_profile_related_me';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_home' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_latest_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_active' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_active_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_online' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_online_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_search' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_search' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_manage' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_manage' AND `title`='_bx_orgs_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_administration' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_system_manage_administration' AND `title`='_bx_orgs_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_joined' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_sys_entries_actions' AND `title`='_bx_orgs_page_block_title_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_joined' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_sys_favorites_of_author' AND `title`='_bx_orgs_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_joined' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_sys_joined_entries' AND `title`='_bx_orgs_page_block_title_joined_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_favorites' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_sys_favorites_entries' AND `title`='_bx_orgs_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_favorites' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_favorites' AND `module`='bx_organizations' AND `title_system`='' AND `title`='_bx_orgs_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_sys_my_entries' AND `title`='_bx_posts_page_block_title_my_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_organizations_view_profile' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_sys_joined_entries' AND `title`='_bx_orgs_page_block_title_joined_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_organizations' AND `title_system`='_bx_orgs_page_block_title_sys_joined_entries' AND `title`='_bx_orgs_page_block_title_joined_entries';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_site' AND `module`='bx_organizations' AND `name`='organizations-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_organizations' AND `name`='organizations-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_profile_links' AND `module`='bx_organizations' AND `name`='create-organization-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_organizations' AND `name`='profile-stats-friend-requests';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_organizations' AND `name`='profile-stats-manage-organizations';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_organizations' AND `name`='profile-stats-favorite-organizations';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_organizations' AND `name`='profile-stats-subscriptions';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_organizations' AND `name`='profile-stats-subscribed-me';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_organizations' AND `name`='profile-stats-relations';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_organizations' AND `name`='profile-stats-related-me';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_followings' AND `module`='bx_organizations' AND `name`='organizations';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_dashboard_manage_tools' AND `module`='bx_organizations' AND `name`='organizations-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions' AND `module`='bx_organizations' AND `name`='join-organization-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions' AND `module`='bx_organizations' AND `name`='profile-fan-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions' AND `module`='bx_organizations' AND `name`='profile-friend-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions' AND `module`='bx_organizations' AND `name`='profile-relation-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions' AND `module`='bx_organizations' AND `name`='profile-subscribe-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions' AND `module`='bx_organizations' AND `name`='profile-set-acl-level';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions' AND `module`='bx_organizations' AND `name`='profile-set-badges';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions' AND `module`='bx_organizations' AND `name`='profile-actions-more';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_more' AND `module`='bx_organizations' AND `name`='edit-organization-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_more' AND `module`='bx_organizations' AND `name`='edit-organization-pricing';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_more' AND `module`='bx_organizations' AND `name`='edit-organization-badge';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_more' AND `module`='bx_organizations' AND `name`='invite-to-organization';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_more' AND `module`='bx_organizations' AND `name`='delete-organization-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_more' AND `module`='bx_organizations' AND `name`='delete-organization-account';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_more' AND `module`='bx_organizations' AND `name`='delete-organization-account-content';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_all' AND `module`='bx_organizations' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_all' AND `module`='bx_organizations' AND `name`='profile-fans';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_actions_all' AND `module`='bx_organizations' AND `name`='profile-subscriptions';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_meta' AND `module`='bx_organizations' AND `name`='membership';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_meta' AND `module`='bx_organizations' AND `name`='badges';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_meta' AND `module`='bx_organizations' AND `name`='members';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_meta' AND `module`='bx_organizations' AND `name`='subscribers';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_meta' AND `module`='bx_organizations' AND `name`='views';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_meta' AND `module`='bx_organizations' AND `name`='votes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_meta' AND `module`='bx_organizations' AND `name`='comments';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_my' AND `module`='bx_organizations' AND `name`='create-organization-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_submenu' AND `module`='bx_organizations' AND `name`='organizations-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_submenu' AND `module`='bx_organizations' AND `name`='organizations-active';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_submenu' AND `module`='bx_organizations' AND `name`='organizations-online';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_submenu' AND `module`='bx_organizations' AND `name`='organizations-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_submenu' AND `module`='bx_organizations' AND `name`='organizations-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_submenu' AND `module`='bx_organizations' AND `name`='view-organization-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_submenu' AND `module`='bx_organizations' AND `name`='organization-profile-info';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_view_submenu' AND `module`='bx_organizations' AND `name`='organization-profile-fans';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_snippet_meta' AND `module`='bx_organizations' AND `name`='members';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_snippet_meta' AND `module`='bx_organizations' AND `name`='friends';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_snippet_meta' AND `module`='bx_organizations' AND `name`='nl';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_snippet_meta' AND `module`='bx_organizations' AND `name`='join-paid';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_snippet_meta' AND `module`='bx_organizations' AND `name`='join';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_snippet_meta' AND `module`='bx_organizations' AND `name`='befriend';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_menu_manage_tools' AND `module`='bx_organizations' AND `name`='clear-reports';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_organizations_menu_manage_tools' AND `module`='bx_organizations' AND `name`='delete-with-content';

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

+ 13 - 0
modules/boonex/persons/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 BxPersonsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 153 - 0
modules/boonex/persons/updates/15.0.3_15.0.4/source/install/config.php

@@ -0,0 +1,153 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Persons Persons
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_persons',
+    'title' => 'Persons',
+    'note' => 'Basic person profiles functionality.',
+    'version' => '15.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/persons/',
+    'home_uri' => 'persons',
+
+    'db_prefix' => 'bx_persons_',
+    'class_prefix' => 'BxPersons',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Persons',
+
+    /**
+     * Connections.
+     */
+    'connections' => array(
+        'sys_profiles_friends' => array ('type' => 'profiles'),
+        'sys_profiles_subscriptions' => array ('type' => 'profiles'),
+        'sys_profiles_relations' => array ('type' => 'profiles'),
+    ),
+
+    /**
+     * Menu triggers.
+     */
+    'menu_triggers' => array(
+        'trigger_profile_view_submenu',
+        'trigger_profile_snippet_meta',
+    	'trigger_profile_view_actions',
+    ),
+
+	/**
+     * Page triggers.
+     */
+    'page_triggers' => array (
+    	'trigger_page_profile_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_persons_pictures'
+    ),
+
+	/**
+     * Transcoders.
+     */
+    'transcoders' => array(
+    	'bx_persons_icon',
+    	'bx_persons_thumb',
+    	'bx_persons_avatar',
+        'bx_persons_avatar_big',
+    	'bx_persons_picture',
+    	'bx_persons_cover',
+    	'bx_persons_cover_thumb',
+        'bx_persons_gallery',
+        'bx_persons_badge'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_persons',
+    	'bx_persons_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',
+    ),
+);
+
+/** @} */

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

@@ -0,0 +1,88 @@
+-- PAGES: 
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `class`, `submenu`, `tabs`, `async`, `visible_for_levels`, `hidden_on`, `type`, `content`, `content_empty`, `text`, `text_updated`, `help`, `cache_lifetime`, `config_api`, `deletable`, `copyable`, `active`, `active_api`, `order`) VALUES
+('bx_persons_view_profile', 1, 'system', '_sys_page_block_title_sys_create_post', '_sys_page_block_title_create_post', 11, '', '', 1, 4, 2147483644, '0', 'service', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:20:"get_create_post_form";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}s:5:"class";s:13:"TemplServices";}', '', '', 0, '', 0, '', 1, 0, 0, 1, 0);
+
+-- PAGES: config_api
+UPDATE `sys_pages_blocks` SET `config_api`='{\r\n\"content_type\":\"browse_simple\",\r\n\"header_more_url\":\"/persons-home\",\r\n\"view\":\"row\"\r\n}' WHERE `object`='sys_explore' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_sys_featured_entries_view_showcase' AND `title`='_bx_persons_page_block_title_featured_entries_view_showcase_1759743271';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_create_profile' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_create_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_sys_cover_block' AND `title`='_bx_persons_page_block_title_cover_block';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_profile_description';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_profile_friends';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_profile_relations';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_profile_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_profile_location';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile_closed' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_profile_private';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_edit_profile' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_edit_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_delete_profile' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_delete_profile';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_profile_info' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_profile_info' AND `title`='_bx_persons_page_block_title_profile_info_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_profile_info' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_profile_description';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_profile_friends' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_profile_friends' AND `title`='_bx_persons_page_block_title_profile_friends_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_friend_requests' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_friend_requests' AND `title`='_bx_persons_page_block_title_friend_requests_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_profile_favorites' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_profile_favorites' AND `title`='_bx_persons_page_block_title_profile_favorites';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_profile_subscriptions' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_profile_subscribed_me' AND `title`='_bx_persons_page_block_title_profile_subscribed_me';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_profile_relations' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_profile_relations' AND `title`='_bx_persons_page_block_title_profile_relations';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_profile_relations' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_profile_related_me' AND `title`='_bx_persons_page_block_title_profile_related_me';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_home' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_featured_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_home' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_latest_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_active' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_active_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_online' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_online_profiles';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_search' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_search' AND `module`='bx_persons' AND `title_system`='' AND `title`='_bx_persons_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_manage' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_manage' AND `title`='_bx_persons_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_administration' AND `module`='bx_persons' AND `title_system`='_bx_persons_page_block_title_system_manage_administration' AND `title`='_bx_persons_page_block_title_manage';
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_persons' AND `name`='persons-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_profile_links' AND `module`='bx_persons' AND `name`='create-persons-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_persons' AND `name`='profile-stats-friend-requests';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_persons' AND `name`='profile-stats-manage-profiles';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_persons' AND `name`='profile-stats-favorite-persons';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_persons' AND `name`='profile-stats-subscriptions';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_persons' AND `name`='profile-stats-subscribed-me';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_persons' AND `name`='profile-stats-relations';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_persons' AND `name`='profile-stats-related-me';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_followings' AND `module`='bx_persons' AND `name`='persons';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions' AND `module`='bx_persons' AND `name`='profile-friend-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions' AND `module`='bx_persons' AND `name`='profile-subscribe-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions' AND `module`='bx_persons' AND `name`='profile-set-acl-level';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions' AND `module`='bx_persons' AND `name`='profile-set-badges';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions' AND `module`='bx_persons' AND `name`='profile-actions-more';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_more' AND `module`='bx_persons' AND `name`='edit-persons-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_more' AND `module`='bx_persons' AND `name`='edit-persons-badge';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_more' AND `module`='bx_persons' AND `name`='delete-persons-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_more' AND `module`='bx_persons' AND `name`='delete-persons-account';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_all' AND `module`='bx_persons' AND `name`='profile-friend-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_all' AND `module`='bx_persons' AND `name`='profile-friend-remove';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_all' AND `module`='bx_persons' AND `name`='profile-subscribe-add';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_all' AND `module`='bx_persons' AND `name`='profile-subscribe-remove';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_all' AND `module`='bx_persons' AND `name`='profile-set-acl-level';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_all' AND `module`='bx_persons' AND `name`='profile-set-badges';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_actions_all' AND `module`='bx_persons' AND `name`='feature';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_meta' AND `module`='bx_persons' AND `name`='membership';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_meta' AND `module`='bx_persons' AND `name`='badges';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_meta' AND `module`='bx_persons' AND `name`='friends';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_meta' AND `module`='bx_persons' AND `name`='subscribers';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_meta' AND `module`='bx_persons' AND `name`='views';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_meta' AND `module`='bx_persons' AND `name`='votes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_meta' AND `module`='bx_persons' AND `name`='comments';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_submenu' AND `module`='bx_persons' AND `name`='persons-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_submenu' AND `module`='bx_persons' AND `name`='persons-active';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_submenu' AND `module`='bx_persons' AND `name`='persons-online';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_submenu' AND `module`='bx_persons' AND `name`='persons-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_submenu' AND `module`='bx_persons' AND `name`='persons-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_submenu' AND `module`='bx_persons' AND `name`='view-persons-profile';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_submenu' AND `module`='bx_persons' AND `name`='persons-profile-info';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_submenu' AND `module`='bx_persons' AND `name`='persons-profile-friends';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_view_submenu' AND `module`='bx_persons' AND `name`='persons-profile-subscriptions';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_snippet_meta' AND `module`='bx_persons' AND `name`='subscribe';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_snippet_meta' AND `module`='bx_persons' AND `name`='ignore-befriend';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_snippet_meta' AND `module`='bx_persons' AND `name`='ignore-subscribe';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_menu_manage_tools' AND `module`='bx_persons' AND `name`='clear-reports';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_menu_manage_tools' AND `module`='bx_persons' AND `name`='manage-cf';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_persons_menu_manage_tools' AND `module`='bx_persons' AND `name`='delete-with-content';

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

+ 13 - 0
modules/boonex/photos/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 BxPhotosUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 136 - 0
modules/boonex/photos/updates/15.0.1_15.0.2/source/install/config.php

@@ -0,0 +1,136 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Photos Photos
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_photos',
+    'title' => 'Photos',
+    'note' => 'Basic photo posting module.',
+    'version' => '15.0.2',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/photos/',
+    'home_uri' => 'photos',
+
+    'db_prefix' => 'bx_photos_',
+    'class_prefix' => 'BxPhotos',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Photos',
+
+    /**
+     * 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_photos_files'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+		'bx_photos_preview',
+        'bx_photos_gallery',
+        'bx_photos_cover'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_photos',
+    	'bx_photos_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'
+    ),
+
+);
+
+/** @} */

+ 77 - 0
modules/boonex/photos/updates/15.0.1_15.0.2/source/install/sql/enable-app.sql

@@ -0,0 +1,77 @@
+-- PAGES: config_api
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'post\',\r\n   \r\n}' WHERE `object`='bx_photos_view_entry';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_create_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_create_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_edit_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_edit_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_delete_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_delete_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_photo';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_text';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_sys_entry_context' AND `title`='_bx_photos_page_block_title_entry_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_media_exif';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_comments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_location';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_featured_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_reports';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry_brief' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_photo';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry_brief' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry_brief' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_view_entry_comments' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_entry_comments' AND `title`='_bx_photos_page_block_title_entry_comments_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_popular' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_popular_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_top' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_top_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_updated' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_updated_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_author' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_author' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_sys_favorites_of_author' AND `title`='_bx_photos_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_author' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_sys_entries_of_author' AND `title`='_bx_photos_page_block_title_entries_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_author' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_sys_entries_in_context' AND `title`='_bx_photos_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_favorites' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_sys_favorites_entries' AND `title`='_bx_photos_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_favorites' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_favorites' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_context' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_sys_entries_in_context' AND `title`='_bx_photos_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_home' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_featured_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_home' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_recent_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_home' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_popular_keywords';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_home' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_cats';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_search' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_search' AND `module`='bx_photos' AND `title_system`='' AND `title`='_bx_photos_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_manage' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_system_manage' AND `title`='_bx_photos_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_photos_administration' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_system_manage_administration' AND `title`='_bx_photos_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_photos' AND `title_system`='_bx_photos_page_block_title_sys_my_entries' AND `title`='_bx_photos_page_block_title_my_entries';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_photos' AND `name`='photos-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_photos' AND `name`='profile-stats-my-photos';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_dashboard_manage_tools' AND `module`='bx_photos' AND `name`='photos-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view' AND `module`='bx_photos' AND `name`='edit-photo';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view' AND `module`='bx_photos' AND `name`='delete-photo';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view' AND `module`='bx_photos' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='edit-photo';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='delete-photo';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='view';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='reaction';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='score';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='favorite';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='feature';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='repost';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='notes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='audit';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='social-sharing';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_view_actions' AND `module`='bx_photos' AND `name`='more-auto';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_my' AND `module`='bx_photos' AND `name`='create-photo';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_submenu' AND `module`='bx_photos' AND `name`='photos-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_submenu' AND `module`='bx_photos' AND `name`='photos-popular';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_submenu' AND `module`='bx_photos' AND `name`='photos-top';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_submenu' AND `module`='bx_photos' AND `name`='photos-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_submenu' AND `module`='bx_photos' AND `name`='photos-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_snippet_meta' AND `module`='bx_photos' AND `name`='rating';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_photos_snippet_meta' AND `module`='bx_photos' AND `name`='comments';

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

+ 13 - 0
modules/boonex/polls/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 BxPollsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 136 - 0
modules/boonex/polls/updates/15.0.1_15.0.2/source/install/config.php

@@ -0,0 +1,136 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Polls Polls
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_polls',
+    'title' => 'Polls',
+    'note' => 'Basic poll module.',
+    'version' => '15.0.2',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/polls/',
+    'home_uri' => 'polls',
+
+    'db_prefix' => 'bx_polls_',
+    'class_prefix' => 'BxPolls',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Polls',
+
+    /**
+     * 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_polls_files'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+		'bx_polls_preview',
+        'bx_polls_gallery',
+        'bx_polls_cover'
+    ),
+
+	/**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_polls',
+    	'bx_polls_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'
+    ),
+
+);
+
+/** @} */

+ 71 - 0
modules/boonex/polls/updates/15.0.1_15.0.2/source/install/sql/enable-app.sql

@@ -0,0 +1,71 @@
+-- PAGES: config_api
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'post\',\r\n   \r\n}' WHERE `object`='bx_polls_view_entry';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_create_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_create_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_edit_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_edit_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_delete_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_delete_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_entry_text_and_subentries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_sys_entry_context' AND `title`='_bx_polls_page_block_title_entry_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_entry_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_entry_comments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_entry_location';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_featured_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_view_entry_comments' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_entry_comments' AND `title`='_bx_polls_page_block_title_entry_comments_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_popular' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_popular_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_top' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_top_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_updated' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_updated_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_author' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_author' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_sys_favorites_of_author' AND `title`='_bx_polls_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_author' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_sys_entries_of_author' AND `title`='_bx_polls_page_block_title_entries_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_author' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_sys_entries_in_context' AND `title`='_bx_polls_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_favorites' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_sys_favorites_entries' AND `title`='_bx_polls_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_favorites' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_favorites' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_context' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_sys_entries_in_context' AND `title`='_bx_polls_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_home' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_recent_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_search' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_search' AND `module`='bx_polls' AND `title_system`='' AND `title`='_bx_polls_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_manage' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_system_manage' AND `title`='_bx_polls_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_polls_administration' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_system_manage_administration' AND `title`='_bx_polls_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_persons_view_profile' AND `module`='bx_polls' AND `title_system`='_bx_polls_page_block_title_sys_my_entries' AND `title`='_bx_polls_page_block_title_my_entries';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_polls' AND `name`='polls-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_content_links' AND `module`='bx_polls' AND `name`='create-poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_polls' AND `name`='profile-stats-my-polls';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_dashboard_manage_tools' AND `module`='bx_polls' AND `name`='polls-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view' AND `module`='bx_polls' AND `name`='edit-poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view' AND `module`='bx_polls' AND `name`='delete-poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view' AND `module`='bx_polls' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='edit-poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='delete-poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='view';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='reaction';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='score';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='favorite';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='feature';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='repost';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='notes';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='audit';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='social-sharing';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_view_actions' AND `module`='bx_polls' AND `name`='more-auto';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_my' AND `module`='bx_polls' AND `name`='create-poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_submenu' AND `module`='bx_polls' AND `name`='polls-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_submenu' AND `module`='bx_polls' AND `name`='polls-popular';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_submenu' AND `module`='bx_polls' AND `name`='polls-top';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_submenu' AND `module`='bx_polls' AND `name`='polls-search';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_submenu' AND `module`='bx_polls' AND `name`='polls-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_snippet_meta' AND `module`='bx_polls' AND `name`='date';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_snippet_meta' AND `module`='bx_polls' AND `name`='comments';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_snippet_meta' AND `module`='bx_polls' AND `name`='author';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_polls_snippet_meta' AND `module`='bx_polls' AND `name`='actions';

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

+ 13 - 0
modules/boonex/posts/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 BxPostsUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 155 - 0
modules/boonex/posts/updates/15.0.3_15.0.4/source/install/config.php

@@ -0,0 +1,155 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Posts Posts
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_posts',
+    'title' => 'Posts',
+    'note' => 'Basic blogging module.',
+    'version' => '15.0.4',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/posts/',
+    'home_uri' => 'posts',
+
+    'db_prefix' => 'bx_posts_',
+    'class_prefix' => 'BxPosts',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Posts',
+
+    /**
+     * 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_posts_covers',
+        'bx_posts_photos',
+        'bx_posts_videos',
+        'bx_posts_files'
+    ),
+
+    /**
+     * Transcoders.
+     */
+    'transcoders' => array(
+        'bx_posts_preview',
+        'bx_posts_miniature',
+        'bx_posts_gallery',
+        'bx_posts_cover',
+
+        'bx_posts_preview_photos',
+        'bx_posts_miniature_photos',
+        'bx_posts_gallery_photos',
+        'bx_posts_view_photos',
+
+        'bx_posts_videos_poster',
+        'bx_posts_videos_poster_preview',
+        'bx_posts_videos_mp4',
+        'bx_posts_videos_mp4_hd',
+        
+        'bx_posts_sounds_mp3',
+
+        'bx_posts_preview_files',
+        'bx_posts_gallery_files'
+    ),
+
+    /**
+     * Extended Search Forms.
+     */
+    'esearches' => array(
+        'bx_posts',
+    	'bx_posts_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'
+    ),
+
+);
+
+/** @} */

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

@@ -0,0 +1,74 @@
+-- PAGES: config_api
+UPDATE `sys_objects_page` SET `config_api`='{\r\n    layout: \'post\',\r\n}' WHERE `object`='bx_posts_view_entry';
+
+UPDATE `sys_pages_blocks` SET `config_api`='{\r\n\"content_type\":\"browse_simple\",\r\n\"header_more_url\":\"/posts-home\",\r\n\"view\":\"row\"\r\n}' WHERE `object`='sys_explore' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_sys_featured_entries_view_showcase';
+
+-- PAGES: active_api
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_create_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_create_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_edit_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_edit_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_delete_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_delete_entry';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_breadcrumb';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_text';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_sys_entry_context' AND `title`='_bx_posts_page_block_title_entry_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_all_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_attachments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_comments';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entry_location';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_featured_entries_view_extended';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_view_entry_comments' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_entry_comments' AND `title`='_bx_posts_page_block_title_entry_comments_link';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_popular' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_popular_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_top' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_top_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_updated' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_updated_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_author' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_author' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_sys_favorites_of_author' AND `title`='_bx_posts_page_block_title_favorites_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_author' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_sys_entries_of_author' AND `title`='_bx_posts_page_block_title_entries_of_author';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_author' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_sys_entries_in_context' AND `title`='_bx_posts_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_favorites' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_sys_favorites_entries' AND `title`='_bx_posts_page_block_title_favorites_entries';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_favorites' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_favorites_entries_info';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_favorites' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_favorites_entries_actions';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_context' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_sys_entries_in_context' AND `title`='_bx_posts_page_block_title_entries_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_home' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_recent_entries_view_extended';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_search' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_search_form';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_search' AND `module`='bx_posts' AND `title_system`='' AND `title`='_bx_posts_page_block_title_search_results';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_manage' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_system_manage' AND `title`='_bx_posts_page_block_title_manage';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_posts_administration' AND `module`='bx_posts' AND `title_system`='_bx_posts_page_block_title_system_manage_administration' AND `title`='_bx_posts_page_block_title_manage';
+
+
+-- MENUS:
+
+-- MENUS: config_api
+
+-- MENUS: active_api
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_site' AND `module`='bx_posts' AND `name`='posts-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_homepage' AND `module`='bx_posts' AND `name`='posts-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_add_content_links' AND `module`='bx_posts' AND `name`='create-post';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_create_post' AND `module`='bx_posts' AND `name`='create-post';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_profile_stats' AND `module`='bx_posts' AND `name`='profile-stats-my-posts';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='sys_account_dashboard_manage_tools' AND `module`='bx_posts' AND `name`='posts-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_entry_attachments' AND `module`='bx_posts' AND `name`='photo_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_entry_attachments' AND `module`='bx_posts' AND `name`='video_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_entry_attachments' AND `module`='bx_posts' AND `name`='video_record_video';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_entry_attachments' AND `module`='bx_posts' AND `name`='sound_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_entry_attachments' AND `module`='bx_posts' AND `name`='file_html5';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_entry_attachments' AND `module`='bx_posts' AND `name`='poll';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_entry_attachments' AND `module`='bx_posts' AND `name`='add-link';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view' AND `module`='bx_posts' AND `name`='edit-post';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view' AND `module`='bx_posts' AND `name`='delete-post';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view' AND `module`='bx_posts' AND `name`='approve';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view_actions' AND `module`='bx_posts' AND `name`='edit-post';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view_actions' AND `module`='bx_posts' AND `name`='delete-post';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view_actions' AND `module`='bx_posts' AND `name`='comment';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view_actions' AND `module`='bx_posts' AND `name`='reaction';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view_actions' AND `module`='bx_posts' AND `name`='score';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view_actions' AND `module`='bx_posts' AND `name`='favorite';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view_actions' AND `module`='bx_posts' AND `name`='feature';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_view_actions' AND `module`='bx_posts' AND `name`='report';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_my' AND `module`='bx_posts' AND `name`='create-post';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_submenu' AND `module`='bx_posts' AND `name`='posts-home';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_submenu' AND `module`='bx_posts' AND `name`='posts-popular';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_submenu' AND `module`='bx_posts' AND `name`='posts-manage';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_snippet_meta' AND `module`='bx_posts' AND `name`='author';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_posts_snippet_meta' AND `module`='bx_posts' AND `name`='date';

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

+ 13 - 0
modules/boonex/profiler/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 BxProfilerUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 799 - 0
modules/boonex/profiler/updates/15.0.2_15.0.3/source/classes/BxProfiler.php

@@ -0,0 +1,799 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Profiler Profiler
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define ('BX_PROFILER', true);
+define ('BX_PROFILER_DISPLAY', 1);
+
+require_once(BX_DIRECTORY_PATH_MODULES . 'boonex/profiler/install/config.php');
+$GLOBALS['bx_profiler_module'] = array (
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_profiler',
+    'title' => $aConfig['title'],
+    'vendor' => $aConfig['vendor'],
+    'path' => $aConfig['home_dir'],
+    'uri' => $aConfig['home_uri'],
+    'class_prefix' => $aConfig['class_prefix'],
+    'db_prefix' => $aConfig['db_prefix'],
+);
+
+bx_import('Template', $GLOBALS['bx_profiler_module']);
+bx_import('Config', $GLOBALS['bx_profiler_module']);
+
+class BxProfiler extends BxDol
+{
+    protected $oConfig, $oTemplate, $oTemplateAdmin;
+
+    protected $aConf = [];
+
+    protected $_iTimeStart = 0;
+
+    protected $_aQueries = [];
+    protected $_sQueryIndex = 0;
+
+    protected $_aModules = [];
+    protected $_aModulesNames = [];
+    protected $_aModulesLevel = 0;
+
+    protected $_aAlerts = [];
+    protected $_iAlertsLevel = 0;
+
+    protected $_aInjections = [];
+    protected $_sInjectionIndex = 0;
+
+    protected $_sLogDateFormat = "Y-m-d H:i:s";
+    protected $_sLogMaxArgLength = 64;
+    protected $_sLogFilename = 64;
+
+    protected $_aTemplates = [];
+    protected $_aTemplateIndexes = [];
+
+    protected $_aPages = [];
+    protected $_sPageIndex = '';
+    protected $_sPageBlockIndex = 0;
+    protected $_aPagesBlocks = [];
+    
+    protected $_aMenus = [];   
+
+    function __construct($iTimeStart)
+    {
+        parent::__construct();
+
+        $this->oConfig = new BxProfilerConfig ($GLOBALS['bx_profiler_module']);
+        $this->oTemplate = new BxProfilerTemplate ($this->oConfig);
+        $this->oTemplateAdmin = BxDolStudioTemplate::getInstance();
+
+        if (getParam ('bx_profiler_long_sql_queries_log'))
+            $this->aConf['long_query'] = getParam ('bx_profiler_long_sql_queries_time');
+        if (getParam ('bx_profiler_long_module_query_log'))
+            $this->aConf['long_module'] = getParam ('bx_profiler_long_module_query_time');
+        if (getParam ('bx_profiler_long_page_log'))
+            $this->aConf['long_page'] = getParam ('bx_profiler_long_page_time');
+
+        $this->_iTimeStart = $iTimeStart;
+    }
+
+    // output profiler debug panel
+    function output ()
+    {
+        $iPageTime = $this->_getCurrentDelay ();
+        if (isset($this->aConf['long_page']) && $iPageTime > $this->aConf['long_page'])
+            $this->logPageOpen ($iPageTime);
+
+        switch (getParam('bx_profiler_show_debug_panel')) {
+        case 'all':
+            break;
+        case 'admins':
+            if (!isset($GLOBALS['logged']) || !$GLOBALS['logged']['admin'])
+                return;
+            break;
+        case 'none':
+        default:
+            return;
+        }
+
+        $aCss = array (
+			'profiler.css',
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'jush/|jush.css',
+        );
+        $aJs = array (
+            'jquery.tablesorter.min.js',
+            'profiler.js',
+            'jush/jush.js',
+        );
+
+        $sJsCss = '';
+        foreach ($aCss as $sCssPath)
+            $sJsCss .= $this->oTemplate->addCss($sCssPath, 1);
+        foreach ($aJs as $sJsPath)
+            $sJsCss .= $this->oTemplate->addJs($sJsPath, 1);
+
+        $sPlanks  = $this->_plankMain ();
+        $sPlanks .= $this->_plankMenus ();
+        $sPlanks .= $this->_plankTemplates ();
+        $sPlanks .= $this->_plankInjections ();
+        $sPlanks .= $this->_plankPagesBlocks ();
+        $sPlanks .= $this->_plankSql ();
+        $sPlanks .= $this->_plankModules ();
+        $sPlanks .= $this->_plankAlerts ();
+
+        echo ob_get_clean();
+
+        $sContentType = $this->oTemplate->getHeaderContentType();
+        if ('text/html' == $sContentType) {            
+            echo $sJsCss . $sPlanks;
+        } 
+        else if ('application/json' == $sContentType) {
+            echo $sPlanks;
+        }
+    }
+
+    function _logBegin ($s)
+    {
+        $sDate = date ($this->_sLogDateFormat);
+        return "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" . $sDate . " " . $s . "\n" .
+        (getLoggedId() ? "User ID: " . getLoggedId()  . "\n" : '') .
+        "User role: " . (!empty($GLOBALS['logged']['admin']) ? 'admin' : (!empty($GLOBALS['logged']['member']) ? 'member' : 'guest')) . "\n";
+    }
+
+    function _logEnd ()
+    {
+        return "\n";
+    }
+
+    function _appendToLog ($s)
+    {
+		bx_log('bx_profiler', $s, BX_LOG_INFO);
+    }
+
+    function logSqlQuery ($iTime, $aSqlQuery, &$oStmt)
+    {
+        $s  = $this->_logBegin ('LONG SQL QUERY: ' . $aSqlQuery['time']);
+        $s .= "Rows: " . $aSqlQuery['rows'] . "\n";
+        $s .= "Affected: " . $aSqlQuery['affected'] . "\n";
+        $s .= "Query: " . $aSqlQuery['sql'] . "\n";
+        if (getParam('bx_profiler_long_sql_queries_debug'))
+            $s .= "Backtrace: \n" . $this->_debugBackTrace (3);
+        $s .= $this->_logEnd();
+        $this->_appendToLog ($s);
+    }
+
+    function logModuleQuery ($iTime, $aModuleQuery)
+    {
+        $s  = $this->_logBegin ('LONG MODULE QUERY: ' . $aModuleQuery['time']);
+        $s .= "Module name: " . $aModuleQuery['name'] . "\n";
+        $s .= "Query type: " . $aModuleQuery['type'] . "\n";
+        $s .= "Class/file: " . $aModuleQuery['class/file'] . "\n";
+        $s .= "Method: " . $aModuleQuery['method'] . "\n";
+        if (getParam('bx_profiler_long_module_query_debug'))
+            $s .= "Backtrace: \n" . $this->_debugBackTrace (3);
+        $s .= $this->_logEnd();
+        $this->_appendToLog ($s);
+    }
+
+    function logPageOpen ($iTime)
+    {
+        $s  = $this->_logBegin ('LONG PAGE OPEN: ' . $this->_formatTime($iTime));
+        $s .= "Request method: " . $_SERVER['REQUEST_METHOD'] . "\n";
+        $s .= "Query string: " . $_SERVER['QUERY_STRING'] . "\n";
+        $s .= "Request URI: " . $_SERVER['REQUEST_URI'] . "\n";
+        $s .= "Script name: " . $_SERVER['SCRIPT_NAME'] . "\n";
+        $s .= "PHP self: " . $_SERVER['PHP_SELF'] . "\n";
+        if (getParam('bx_profiler_long_page_debug'))
+            $s .= "All server vars: \n" . print_r ($_SERVER, true);
+        $s .= $this->_logEnd();
+        $this->_appendToLog ($s);
+    }
+
+    function beginModule($sType, $sHash, &$aModule, $sClassFile, $sMethod = '' )
+    {
+        ++$this->_aModulesLevel;
+        $this->_aModulesNames[$aModule['title']] = isset($this->_aModulesNames[$aModule['title']]) ? $this->_aModulesNames[$aModule['title']] + 1 : 1;
+        $this->_aModules[$sHash] = array (
+            'name' => str_repeat("--", $this->_aModulesLevel-1) . $aModule['title'],
+            'type' => $sType,
+            'class/file' => $sClassFile,
+            'method' => $sMethod,
+            'sql_counter' => BxDolDb::getInstance()->getQueriesCounter(),
+            'sql' => 0,
+            'kb_counter' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+            'kb' => 0,
+            'begin' => microtime (),
+            'time' => -1,
+        );
+    }
+
+    function endModule($sType, $sHash)
+    {
+        --$this->_aModulesLevel;
+
+        $iTime = $this->_calcTime ($this->_aModules[$sHash]['begin']);
+        unset ($this->_aModules[$sHash]['begin']);
+
+        $iSqlCounter = BxDolDb::getInstance()->getQueriesCounter() - $this->_aModules[$sHash]['sql_counter'];
+        unset ($this->_aModules[$sHash]['sql_counter']);
+
+        $iMemCounter = function_exists('memory_get_usage') ? memory_get_usage() - $this->_aModules[$sHash]['kb_counter']: 0;
+        unset ($this->_aModules[$sHash]['kb_counter']);
+
+        $this->_aModules[$sHash]['sql'] = $iSqlCounter;
+        $this->_aModules[$sHash]['kb'] = $this->_formatKb($iMemCounter);
+        $this->_aModules[$sHash]['time'] = $this->_formatTime($iTime);
+        $this->_aModules[$sHash]['raw_time'] = $iTime;
+        if (isset($this->aConf['long_module']) && $iTime > $this->aConf['long_module'])
+            $this->logModuleQuery ($iTime, $this->_aModules[$sHash]);
+    }
+
+    function beginAlert($sUnit, $sAction, $sHandler)
+    {
+        $sHash = md5($sUnit . $sAction . $sHandler . $this->_iAlertsLevel);
+        ++$this->_iAlertsLevel;
+        $this->_aAlerts[$sHash] = array (
+            'name' => str_repeat("--", $this->_iAlertsLevel-1) . $sHandler,
+            'unit' => $sUnit,
+            'action' => $sAction,
+            'handler' => $sHandler,
+            'sql_counter' => BxDolDb::getInstance()->getQueriesCounter(),
+            'sql' => 0,
+            'kb_counter' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+            'kb' => 0,
+            'begin' => microtime (),
+            'time' => -1,
+        );
+    }
+
+    function endAlert($sUnit, $sAction, $sHandler)
+    {
+        --$this->_iAlertsLevel;
+        $sHash = md5($sUnit . $sAction . $sHandler . $this->_iAlertsLevel);
+
+        $iTime = $this->_calcTime ($this->_aAlerts[$sHash]['begin']);
+        unset ($this->_aAlerts[$sHash]['begin']);
+
+        $iSqlCounter = BxDolDb::getInstance()->getQueriesCounter() - $this->_aAlerts[$sHash]['sql_counter'];
+        unset ($this->_aAlerts[$sHash]['sql_counter']);
+
+        $iMemCounter = function_exists('memory_get_usage') ? memory_get_usage() - $this->_aAlerts[$sHash]['kb_counter']: 0;
+        unset ($this->_aAlerts[$sHash]['kb_counter']);
+
+        $this->_aAlerts[$sHash]['sql'] = $iSqlCounter;
+        $this->_aAlerts[$sHash]['kb'] = $this->_formatKb($iMemCounter);
+        $this->_aAlerts[$sHash]['time'] = $this->_formatTime($iTime);
+        $this->_aAlerts[$sHash]['raw_time'] = $iTime;
+    }
+
+    function beginInjection ($sId)
+    {
+        $this->_sInjectionIndex = $sId;
+        $this->_aInjections[$sId] = [
+            'begin' => microtime (),
+            'sql_counter' => BxDolDb::getInstance()->getQueriesCounter(),
+            'kb_counter' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+        ];
+    }
+
+    function endInjection ($sId, $aInjection)
+    {
+        if (!isset($this->_aInjections[$sId]))
+            return;
+
+        $iTime = $this->_calcTime ($this->_aInjections[$sId]['begin']);
+        unset ($this->_aInjections[$sId]['begin']);
+
+        $iSqlCounter = BxDolDb::getInstance()->getQueriesCounter() - $this->_aInjections[$sId]['sql_counter'];
+        unset ($this->_aInjections[$sId]['sql_counter']);
+
+        $iMemCounter = function_exists('memory_get_usage') ? memory_get_usage() - $this->_aInjections[$sId]['kb_counter']: 0;
+        unset ($this->_aInjections[$sId]['kb_counter']);
+
+        $this->_aInjections[$sId]['name'] = isset($aInjection['name']) ? $aInjection['name'] : 'no-name';
+        $this->_aInjections[$sId]['key'] = $aInjection['key'];
+        $this->_aInjections[$sId]['replace'] = $aInjection['replace'] ? 'yes' : 'no';
+        $this->_aInjections[$sId]['sql'] = $iSqlCounter;
+        $this->_aInjections[$sId]['kb'] = $this->_formatKb($iMemCounter);
+        $this->_aInjections[$sId]['time'] = $this->_formatTime($iTime);
+        $this->_aInjections[$sId]['raw_time'] = $iTime;
+    }
+
+    function beginPageBlock ($sName, $iBlockId)
+    {
+        $this->_sPageBlockIndex = $iBlockId;
+        $this->_aPagesBlocks[$this->_sPageBlockIndex] = [
+            'name' => $sName,
+            'begin' => microtime (),
+            'sql_counter' => BxDolDb::getInstance()->getQueriesCounter(),
+            'kb_counter' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+        ];
+    }
+
+    function endPageBlock ($iBlockId, $isEmpty, $isCached)
+    {
+        if (!$this->_sPageBlockIndex)
+            return;
+
+        $iTime = $this->_calcTime ($this->_aPagesBlocks[$this->_sPageBlockIndex]['begin']);
+        unset ($this->_aPagesBlocks[$this->_sPageBlockIndex]['begin']);
+
+        $iSqlCounter = BxDolDb::getInstance()->getQueriesCounter() - $this->_aPagesBlocks[$this->_sPageBlockIndex]['sql_counter'];
+        unset ($this->_aPagesBlocks[$this->_sPageBlockIndex]['sql_counter']);
+
+        $iMemCounter = function_exists('memory_get_usage') ? memory_get_usage() - $this->_aPagesBlocks[$this->_sPageBlockIndex]['kb_counter']: 0;
+        unset ($this->_aPagesBlocks[$this->_sPageBlockIndex]['kb_counter']);
+
+        $this->_aPagesBlocks[$this->_sPageBlockIndex]['cached'] = $isCached ? 'yes' : 'no';
+        $this->_aPagesBlocks[$this->_sPageBlockIndex]['empty'] = $isEmpty ? 'yes' : 'no';
+        $this->_aPagesBlocks[$this->_sPageBlockIndex]['sql'] = $iSqlCounter;
+        $this->_aPagesBlocks[$this->_sPageBlockIndex]['kb'] = $this->_formatKb($iMemCounter);
+        $this->_aPagesBlocks[$this->_sPageBlockIndex]['time'] = $this->_formatTime($iTime);
+        $this->_aPagesBlocks[$this->_sPageBlockIndex]['raw_time'] = $iTime;
+    }
+
+    function beginPage ($sName)
+    {
+        $this->_sPageIndex = md5 ($sName.time().rand());
+        $this->_aPages[$this->_sPageIndex] = [
+            'name' => $sName,
+            'begin' => microtime (),
+            'sql_counter' => BxDolDb::getInstance()->getQueriesCounter(),
+            'kb_counter' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+        ];
+    }
+
+    function endPage (&$sContent)
+    {
+        if (!$this->_sPageIndex || !isset($this->_aPages[$this->_sPageIndex]['begin']))
+            return;
+
+        $iTime = $this->_calcTime ($this->_aPages[$this->_sPageIndex]['begin']);
+        unset ($this->_aPages[$this->_sPageIndex]['begin']);
+
+        $iSqlCounter = BxDolDb::getInstance()->getQueriesCounter() - $this->_aPages[$this->_sPageIndex]['sql_counter'];
+        unset ($this->_aPages[$this->_sPageIndex]['sql_counter']);
+
+        $iMemCounter = function_exists('memory_get_usage') ? memory_get_usage() - $this->_aPages[$this->_sPageIndex]['kb_counter']: 0;
+        unset ($this->_aPages[$this->_sPageIndex]['kb_counter']);
+
+        $this->_aPages[$this->_sPageIndex]['sql'] = $iSqlCounter;
+        $this->_aPages[$this->_sPageIndex]['kb'] = $this->_formatKb($iMemCounter);
+        $this->_aPages[$this->_sPageIndex]['time'] = $this->_formatTime($iTime);
+        $this->_aPages[$this->_sPageIndex]['raw_time'] = $iTime;
+    }
+
+    function beginTemplate ($sName, $sRand)
+    {
+        $this->_aTemplateIndexes[$sName.$sRand] = 1;
+        $this->_aTemplates[$sName.$sRand] = [
+            'name' => $sName,
+            'begin' => microtime (),
+            'sql_counter' => BxDolDb::getInstance()->getQueriesCounter(),
+            'kb_counter' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+        ];
+    }
+
+    function endTemplate ($sName, $sRand, &$sContent, $isCached)
+    {
+        if (!isset($this->_aTemplateIndexes[$sName.$sRand]))
+            return;
+
+        $iTime = $this->_calcTime ($this->_aTemplates[$sName.$sRand]['begin']);
+        unset ($this->_aTemplates[$sName.$sRand]['begin']);
+
+        $iSqlCounter = BxDolDb::getInstance()->getQueriesCounter() - $this->_aTemplates[$sName.$sRand]['sql_counter'];
+        unset ($this->_aTemplates[$sName.$sRand]['sql_counter']);
+
+        $iMemCounter = function_exists('memory_get_usage') ? memory_get_usage() - $this->_aTemplates[$sName.$sRand]['kb_counter']: 0;
+        unset ($this->_aTemplates[$sName.$sRand]['kb_counter']);
+
+        $this->_aTemplates[$sName.$sRand]['sql'] = $iSqlCounter;
+        $this->_aTemplates[$sName.$sRand]['kb'] = $this->_formatKb($iMemCounter);
+        $this->_aTemplates[$sName.$sRand]['cached'] = $isCached ? 'yes' : 'no';
+        $this->_aTemplates[$sName.$sRand]['time'] = $this->_formatTime($iTime);
+        $this->_aTemplates[$sName.$sRand]['raw_time'] = $iTime;
+    }
+
+    function beginQuery ($sSql)
+    {
+        $this->_sQueryIndex = md5 ($sSql.time().rand());
+        $this->_aQueries[$this->_sQueryIndex] = [
+            'sql' => $sSql,
+            'begin' => microtime (),
+            'kb_counter' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+        ];
+    }
+
+    function endQuery (&$oStmt)
+    {
+        if (!$this->_sQueryIndex)
+            return;
+
+        $iTime = $this->_calcTime ($this->_aQueries[$this->_sQueryIndex]['begin']);
+        unset ($this->_aQueries[$this->_sQueryIndex]['begin']);
+
+        $iMemCounter = function_exists('memory_get_usage') ? memory_get_usage() - $this->_aQueries[$this->_sQueryIndex]['kb_counter']: 0;
+        unset ($this->_aQueries[$this->_sQueryIndex]['kb_counter']);
+
+        $this->_aQueries[$this->_sQueryIndex]['time'] = $this->_formatTime($iTime);
+        $this->_aQueries[$this->_sQueryIndex]['raw_time'] = $iTime;
+        $this->_aQueries[$this->_sQueryIndex]['rows'] = $oStmt ? BxDolDb::getInstance()->getNumRows($oStmt) : '';
+        $this->_aQueries[$this->_sQueryIndex]['affected'] = $oStmt ? BxDolDb::getInstance()->getAffectedRows($oStmt) : '';
+        $this->_aQueries[$this->_sQueryIndex]['kb'] = $this->_formatKb($iMemCounter);
+        if (isset($this->aConf['long_query']) && $iTime > $this->aConf['long_query'])
+            $this->logSqlQuery ($iTime, $this->_aQueries[$this->_sQueryIndex], $oStmt);
+    }
+
+    function isMenu($sName)
+    {
+        return isset($this->_aMenus[$sName]);
+    }
+
+    function beginMenu ($sName)
+    {
+        $this->_aMenus[$sName] = [
+            'name' => $sName,
+            'begin' => microtime (),
+            'sql_counter' => BxDolDb::getInstance()->getQueriesCounter(),
+            'kb_counter' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+        ];
+    }
+
+    function endMenu ($sName, $sCacheType = '', $bCacheUsed = false)
+    {
+        if (!isset($this->_aMenus[$sName]))
+            return;
+
+        $iTime = $this->_calcTime ($this->_aMenus[$sName]['begin']);
+        unset ($this->_aMenus[$sName]['begin']);
+
+        $iSqlCounter = BxDolDb::getInstance()->getQueriesCounter() - $this->_aMenus[$sName]['sql_counter'];
+        unset ($this->_aMenus[$sName]['sql_counter']);
+
+        $iMemCounter = function_exists('memory_get_usage') ? memory_get_usage() - $this->_aMenus[$sName]['kb_counter']: 0;
+        unset ($this->_aMenus[$sName]['kb_counter']);
+
+        $this->_aMenus[$sName]['sql'] = $iSqlCounter;
+        $this->_aMenus[$sName]['kb'] = $this->_formatKb($iMemCounter);
+        $this->_aMenus[$sName]['time'] = $this->_formatTime($iTime);
+        $this->_aMenus[$sName]['raw_time'] = $iTime;        
+        $this->_aMenus[$sName]['cache type'] = $sCacheType;
+        $this->_aMenus[$sName]['cache used'] = $bCacheUsed ? 'yes' : 'no';
+    }
+
+    function _getCurrentDelay ()
+    {
+        $i1 = explode(' ', microtime ());
+        $i2 = explode(' ', $this->_iTimeStart);
+        return ($i1[0]+$i1[1]) - ($i2[0]+$i2[1]);
+    }
+
+    function _plankMain ()
+    {
+        $sTime = $this->_formatTime($this->_getCurrentDelay (), 3);
+        if (function_exists('memory_get_usage'))
+            $sMemory = $this->_formatBytes(memory_get_usage(true)) . ' of ' . ini_get('memory_limit') . ' allowed';
+        $aLoad = sys_getloadavg();
+        $sLoad = implode(',', array_map(function ($f) {
+            return round ($f, 2);
+        }, $aLoad));
+        return $this->oTemplate->plank(
+            $this->oTemplate->nameValue('Time:', $sTime) .
+            (function_exists('memory_get_usage') ? $this->oTemplate->nameValue('Memory:', $sMemory) : '') .
+            $this->oTemplate->nameValue('PHP:', phpversion()) .
+            $this->oTemplate->nameValue('SAPI:', php_sapi_name()) .
+            $this->oTemplate->nameValue('OS:', php_uname('s')) .
+            $this->oTemplate->nameValue('Avg load:', $sLoad)
+        );
+    }
+
+    function _plankTemplates ()
+    {
+        if (empty($GLOBALS['bx_profiler']->_aPages) && empty($GLOBALS['bx_profiler']->_aTemplates))
+            return;
+
+        $sPages = '';
+        if (!empty($GLOBALS['bx_profiler']->_aPages)) {
+            $iTimePages = 0;
+            foreach ($GLOBALS['bx_profiler']->_aPages as $k => $r) {
+                if (!isset($r['raw_time']))
+                    continue;
+                $iTimePages += $r['raw_time'];
+                unset ($GLOBALS['bx_profiler']->_aPages[$k]['raw_time']);
+            }
+            $sPages = count($GLOBALS['bx_profiler']->_aPages) . ' (' . $this->_formatTime($iTimePages, 3) . ')';
+        }
+
+        $sTemplatesCached = '';
+        $sTemplatesNotCached = '';
+        if ($GLOBALS['bx_profiler']->_aTemplates) {
+            $iTimeTemplatesCached = 0;
+            $iTimeTemplatesNotCached = 0;
+            $sTemplatesCached = 0;
+            $sTemplatesNotCached = 0;
+            foreach ($GLOBALS['bx_profiler']->_aTemplates as $k => $r) {
+                if (!isset($r['raw_time']))
+                    continue;
+                if ('yes' == $r['cached']) {
+                    $iTimeTemplatesCached += $r['raw_time'];
+                    ++$sTemplatesCached;
+                } else {
+                    $iTimeTemplatesNotCached += $r['raw_time'];
+                    ++$sTemplatesNotCached;
+                }
+                unset ($GLOBALS['bx_profiler']->_aTemplates[$k]['raw_time']);
+            }
+            if ($sTemplatesCached)
+                $sTemplatesCached .= ' (' . $this->_formatTime($iTimeTemplatesCached, 3) . ')';
+            if ($sTemplatesNotCached)
+                $sTemplatesNotCached .= ' (' . $this->_formatTime($iTimeTemplatesNotCached, 3) . ')';
+        }
+
+        return $this->oTemplate->plank(
+            ($sPages ? $this->oTemplate->nameValue('Pages:', $sPages) : '') .
+            ($sTemplatesCached ? $this->oTemplate->nameValue('Templates Cached:', $sTemplatesCached) : '') .
+            ($sTemplatesNotCached ? $this->oTemplate->nameValue('Templates Not Cached:', $sTemplatesNotCached) : ''),
+            $this->oTemplate->table($GLOBALS['bx_profiler']->_aTemplates)
+        );
+    }
+
+    function _plankInjections ()
+    {
+        if (!$GLOBALS['bx_profiler']->_aInjections)
+            return;
+
+        $iTimeInjections = 0;
+        foreach ($GLOBALS['bx_profiler']->_aInjections as $k => $r) {
+            if (!isset($r['raw_time']))
+                continue;
+            $iTimeInjections += $r['raw_time'];
+            unset ($GLOBALS['bx_profiler']->_aInjections[$k]['raw_time']);
+        }
+
+        $sInjections = count($GLOBALS['bx_profiler']->_aInjections) . ' injection (' . $this->_formatTime($iTimeInjections, 3) . ')';
+
+        return $this->oTemplate->plank(
+            $this->oTemplate->nameValue('Injections:', $sInjections),
+            $this->oTemplate->table($GLOBALS['bx_profiler']->_aInjections)
+        );
+    }
+
+    function _plankPagesBlocks ()
+    {
+        if (empty($GLOBALS['bx_profiler']->_aPagesBlocks))
+            return;
+
+        $iTimeBlocks = 0;
+        $iTimeBlocksCached = 0;
+        $iTimeBlocksNotCached = 0;
+        $iTimeBlocksEmpty = 0;
+        $iTimeBlocksNotEmpty = 0;
+
+        $iCountBlocksCached = 0;
+        $iCountBlocksNotCached = 0;
+        $iCountBlocksEmpty = 0;
+        $iCountBlocksNotEmpty = 0;
+
+        foreach ($GLOBALS['bx_profiler']->_aPagesBlocks as $k => $r) {
+            if (!isset($r['raw_time']))
+                continue;
+            $iTimeBlocks += $r['raw_time'];
+            if ($r['cached'] == 'yes') {
+                $iTimeBlocksCached += $r['raw_time'];
+                ++$iCountBlocksCached;
+            } else {
+                $iTimeBlocksNotCached += $r['raw_time'];
+                ++$iCountBlocksNotCached;
+            }
+            if ($r['empty'] == 'yes') {
+                $iTimeBlocksEmpty += $r['raw_time'];
+                ++$iCountBlocksEmpty;
+            } else {
+                $iTimeBlocksNotEmpty += $r['raw_time'];
+                ++$iCountBlocksNotEmpty;
+            }
+            unset ($GLOBALS['bx_profiler']->_aPagesBlocks[$k]['raw_time']);
+        }
+
+        $sBlocks = count($GLOBALS['bx_profiler']->_aPagesBlocks) . ' (' . $this->_formatTime($iTimeBlocks, 3) . ')';
+        if ($iCountBlocksCached)
+            $sBlocksCached = $iCountBlocksCached . ' (' . $this->_formatTime($iTimeBlocksCached, 3) . ')';
+        if ($iCountBlocksNotCached)
+            $sBlocksNotCached = $iCountBlocksNotCached . ' (' . $this->_formatTime($iTimeBlocksNotCached, 3) . ')';
+        if ($iCountBlocksEmpty)
+            $sBlocksEmpty = $iCountBlocksEmpty . ' (' . $this->_formatTime($iTimeBlocksEmpty, 3) . ')';
+        if ($iCountBlocksNotEmpty)
+            $sBlocksNotEmpty = $iCountBlocksNotEmpty . ' (' . $this->_formatTime($iTimeBlocksNotEmpty, 3) . ')';
+
+        return $this->oTemplate->plank(
+            $this->oTemplate->nameValue('Pages Blocks:', $sBlocks) .
+            ($iCountBlocksCached ? $this->oTemplate->nameValue('Cached:', $sBlocksCached) : '') .
+            ($iCountBlocksNotCached ? $this->oTemplate->nameValue('Not Cached:', $sBlocksNotCached) : '') .
+            ($iCountBlocksEmpty ? $this->oTemplate->nameValue('Empty:', $sBlocksEmpty) : '') .
+            ($iCountBlocksNotEmpty ? $this->oTemplate->nameValue('Not Empty:', $sBlocksNotEmpty) : ''),
+            $this->oTemplate->table($GLOBALS['bx_profiler']->_aPagesBlocks)
+        );
+    }
+
+    function _plankMenus ()
+    {
+        if (empty($GLOBALS['bx_profiler']->_aMenus))
+            return;
+
+        $iTimeMenus = 0;
+        foreach ($GLOBALS['bx_profiler']->_aMenus as $k => $r) {
+            if (!isset($r['raw_time']))
+                continue;
+            $iTimeMenus += $r['raw_time'];
+            unset ($GLOBALS['bx_profiler']->_aMenus[$k]['raw_time']);
+        }
+
+        $sMenus = count($GLOBALS['bx_profiler']->_aMenus) . ' menus (' . $this->_formatTime($iTimeMenus, 3) . ')';
+
+        return $this->oTemplate->plank(
+            $this->oTemplate->nameValue('Menus:', $sMenus),
+            $this->oTemplate->table($GLOBALS['bx_profiler']->_aMenus)
+        );
+    }
+
+    function _plankSql ()
+    {
+        if (empty($GLOBALS['bx_profiler']->_aQueries))
+            return;
+
+        $iTimeQueries = 0;
+        foreach ($GLOBALS['bx_profiler']->_aQueries as $k => $r) {
+            if (!isset($r['raw_time']))
+                continue;
+            $iTimeQueries += $r['raw_time'];
+            unset ($GLOBALS['bx_profiler']->_aQueries[$k]['raw_time']);
+        }
+
+        $sQueries = count($GLOBALS['bx_profiler']->_aQueries) . ' queries (' . $this->_formatTime($iTimeQueries, 3) . ')';
+
+        return $this->oTemplate->plank(
+            $this->oTemplate->nameValue('SQL:', $sQueries),
+            $this->oTemplate->table($GLOBALS['bx_profiler']->_aQueries, 'sql')
+        );
+    }
+
+    function _plankModules ()
+    {
+        if (empty($GLOBALS['bx_profiler']->_aModules))
+            return;
+
+        $iTimeModules = 0;
+        foreach ($GLOBALS['bx_profiler']->_aModules as $k => $r) {
+            if (!isset($r['raw_time']))
+                continue;
+            $iTimeModules += $r['raw_time'];
+            unset ($GLOBALS['bx_profiler']->_aModules[$k]['raw_time']);
+        }
+
+        $sModules = count($GLOBALS['bx_profiler']->_aModulesNames) . ' modules loaded';
+        $sModulesQueries = count($GLOBALS['bx_profiler']->_aModules) . ' modules queries (' . $this->_formatTime($iTimeModules, 3) . ')';
+
+        return $this->oTemplate->plank(
+            $this->oTemplate->nameValue('Modules:', $sModules) .
+            $this->oTemplate->nameValue('Modules Queries:', $sModulesQueries),
+            $this->oTemplate->table($GLOBALS['bx_profiler']->_aModules)
+        );
+    }
+
+    function _plankAlerts ()
+    {
+        if (empty($GLOBALS['bx_profiler']->_aAlerts))
+            return;
+
+        $iTimeAlerts = 0;
+        foreach ($GLOBALS['bx_profiler']->_aAlerts as $k => $r) {
+            if (!isset($r['raw_time']))
+                continue;
+            $iTimeAlerts += $r['raw_time'];
+            unset ($GLOBALS['bx_profiler']->_aAlerts[$k]['raw_time']);
+        }
+
+        $sAlerts = count($GLOBALS['bx_profiler']->_aAlerts) . ' alerts responses (' . $this->_formatTime($iTimeAlerts, 3) . ')';
+
+        return $this->oTemplate->plank(
+            $this->oTemplate->nameValue('Alerts:', $sAlerts),
+            $this->oTemplate->table($GLOBALS['bx_profiler']->_aAlerts)
+        );
+    }
+
+    function _formatTime ($i, $iPrecision = 4)
+    {
+        return round($i, $iPrecision) . ' sec';
+    }
+
+    function _formatBytes ($i)
+    {
+        return bx_format_bytes($i);
+    }
+
+    function _formatKb ($i)
+    {
+        $s = round($i / 1024, 0);
+        return $s == '-0' ? '0' : $s;
+    }
+
+    function _isProfilerDisabled()
+    {
+        if (isset($GLOBALS['bx_profiler_disable']) || isset($_GET['bx_profiler_disable']) || defined('BX_API'))
+            return true;
+
+        return false;
+    }
+
+    function _calcTime ($begin)
+    {
+        $i1 = explode(' ', microtime ());
+        $i2 = explode(' ', $begin);
+        return ($i1[0]+$i1[1]) - ($i2[0]+$i2[1]);
+    }
+
+    function _debugPrintArray ($mixed)
+    {
+        $sArgs .= 'Array(';
+        foreach ($mixed as $mixed2)
+            $sArgs .= (is_object($mixed2) ? $this->_debugPrintObject($mixed2) : $this->_debugPrintAny($mixed2)) . ',';
+        $sArgs = substr($sArgs, 0, -1);
+        $sArgs .= ')';
+        return $sArgs;
+    }
+
+    function _debugPrintObject ($mixed)
+    {
+        return get_class($mixed) . ' instance';
+    }
+
+    function _debugPrintAny ($mixed)
+    {
+        if (is_string($mixed))
+            return "'" . (strlen($mixed) > $this->_sLogMaxArgLength ? substr($mixed, 0, $this->_sLogMaxArgLength) . '...' : $mixed) . "'";
+        else
+            return $mixed;
+    }
+
+    function _debugBackTrace ($iShifts = 0)
+    {
+        $a = debug_backtrace();
+        while (--$iShifts > -1)
+            array_shift($a);
+
+        $s = '';
+        foreach ($a as $r) {
+
+            $sArgs = '';
+            foreach ($r['args'] as $mixed) {
+                switch (true) {
+                case is_array($mixed):
+                    $sArgs .= $this->_debugPrintArray($mixed);
+                    break;
+                case is_object($mixed):
+                    $sArgs .= $this->_debugPrintObject($mixed);
+                    break;
+                default:
+                    $sArgs .= $this->_debugPrintAny($mixed);
+                }
+                $sArgs .= ',';
+            }
+            $sArgs = substr($sArgs, 0, -1);
+
+            $s .= "--------------------------------------\n";
+            $s .= "{$r['line']} {$r['file']}\n";
+            $s .= "{$r['class']}{$r['type']}{$r['function']} ({$sArgs});\n";
+        }
+        return $s;
+    }
+}
+
+$GLOBALS['bx_profiler'] = new BxProfiler(BX_DOL_START);
+if (!$GLOBALS['bx_profiler']->_isProfilerDisabled())
+    register_shutdown_function (array ($GLOBALS['bx_profiler'], 'output'));
+
+/** @} */

+ 69 - 0
modules/boonex/profiler/updates/15.0.2_15.0.3/source/install/config.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Profiler Profiler
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_profiler',
+    'title' => 'Profiler',
+    'note' => 'Measure different site timings.',
+    'version' => '15.0.3',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/profiler/',
+    'home_uri' => 'profiler',
+
+    'db_prefix' => 'bx_profiler_',
+    'class_prefix' => 'BxProfiler',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Boonex Profiler',
+
+    /**
+     * 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/smtpmailer/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' => 'SMTP Mailer',
+    'version_from' => '15.0.0',
+    'version_to' => '15.0.1',
+    'vendor' => 'UNA INC',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/smtpmailer/updates/update_15.0.0_15.0.1/',
+    'home_uri' => 'smtpmailer_update_1500_1501',
+
+    'module_dir' => 'boonex/smtpmailer/',
+    'module_uri' => 'smtpmailer',
+
+    'db_prefix' => 'bx_smtp_',
+    'class_prefix' => 'BxSMTP',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_files' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'SMTP Mailer',
+
+    /**
+     * Files Section
+     */
+    'delete_files' => array(),
+);

+ 13 - 0
modules/boonex/smtpmailer/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 BxSMTPUpdater extends BxDolStudioUpdater
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}

+ 50 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/install/langs/en.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string_add name="_bx_smtp_adm_stg_cpt_category_oauth"><![CDATA[OAuth2 for Microsoft Office 365]]></string_add>
+    <string_add name="_bx_smtp_option_oauth_on"><![CDATA[Enable OAuth2 for Microsoft Office 365]]></string_add>
+    <string_add name="_bx_smtp_oauth_client_id"><![CDATA[Client (Application) ID]]></string_add>
+    <string_add name="_bx_smtp_oauth_tenant_id"><![CDATA[Tenant (Directory) ID]]></string_add>
+    <string_add name="_bx_smtp_oauth_tenant_name"><![CDATA[Tenant name]]></string_add>
+    <string_add name="_bx_smtp_oauth_cert_public"><![CDATA[X.509 public certificate]]></string_add>
+    <string_add name="_bx_smtp_oauth_cert_private"><![CDATA[X.509 private key]]></string_add>
+
+    <string_upd name="_bx_smtp_help_text"><![CDATA[
+        <div class="bx-def-vanilla-html" style="max-width: none;">
+        
+        <b>SMTP authentication </b> - most SMTP servers requires SMTP authentication, most probably you need to enable it.
+        <br />
+        <b>SMTP server name</b> - is it better to send mail from your site SMTP server, so it is probably the name of your site or localhost.
+        <br />
+        <b>SMTP username/SMTP password</b> - registered email account username and password, it is better to create different account for this purpose, like no-reply@mysite.com.
+        <br />
+        <b>From name of the message</b> - recipients will see this name as email sender, so it is probably your site name.
+        <br />
+        <b>Is your SMTP server requires secure connection</b> - you can disable it if your hostname is localhost, secure connection is not needed if you send mails from localhost.
+        <br />
+        <b>Enable SMTP mailer</b> - it is possible to disable SMTP mailer at all, for example if you have temporary problems with your SMTP server. 
+        <br />
+        <b>Override default sender email address</b> - set this the same as <b>SMTP username</b> email address, to avoid problems with mail marked as spam.
+        <br />
+        <br />
+        Alternatively, you can get above options from your mail client, if you are going to use the same email address for sending mail from your site.
+        
+        <hr class="bx-def-hr" />
+
+        If emails go to spam folder, check the following:
+        <ul>
+            <li>Try to set "Override default sender email address" field the same as "SMTP username" email address</li>
+            <li>Make sure that "Override default sender email address" is valid and exists</li>
+            <li>Try to change default email templates, it maybe that standard ones can be in spam filters, since they are used on a lot of sites by default</li>
+            <li>Ask your members to add your site email to their contacts - when email is in user's contact list it always goes to inbox</li>            
+        </ul>
+
+        <hr class="bx-def-hr" />
+
+        <b>OAuth2 for Microsoft Office 365</b> - enable OAuth2 for Microsoft Office 365 to send emails from your Microsoft Office 365 account. 
+        You need to create an application in Microsoft Azure and get the client ID, tenant ID, tenant name and set your own public certificate and private key.
+        Generate public certificate and private key which are used for authentication:
+        <a class="bx-def-btn" href="javascript:void(0);" onclick="bx_smtp_generate_cert();">Generate self signed certificate</a>
+
+        </div>
+]]></string_upd>
+</resources>

+ 16 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/install/sql/enable.sql

@@ -0,0 +1,16 @@
+-- SETTINGS
+SET @iTypeId = (SELECT `id` FROM `sys_options_types` WHERE `name`='bx_smtp' LIMIT 1);
+
+DELETE FROM `sys_options_categories` WHERE `name`='bx_smtp_oauth';
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_smtp_oauth', '_bx_smtp_adm_stg_cpt_category_oauth', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+DELETE FROM `sys_options` WHERE `name` LIKE 'bx_smtp_oauth_%';
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `order`, `extra`) VALUES
+('bx_smtp_oauth_on', '', @iCategId, '_bx_smtp_option_oauth_on', 'checkbox', '', '', 10, ''),
+('bx_smtp_oauth_client_id', '', @iCategId, '_bx_smtp_oauth_client_id', 'digit', '', '', 20, ''),
+('bx_smtp_oauth_tenant_id', '', @iCategId, '_bx_smtp_oauth_tenant_id', 'digit', '', '', 30, ''),
+('bx_smtp_oauth_tenant_name', '', @iCategId, '_bx_smtp_oauth_tenant_name', 'digit', '', '', 40, ''),
+('bx_smtp_oauth_cert_public', '', @iCategId, '_bx_smtp_oauth_cert_public', 'text', '', '', 50, ''),
+('bx_smtp_oauth_cert_private', '', @iCategId, '_bx_smtp_oauth_cert_private', 'text', '', '', 60, '');

+ 598 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/classes/BxSMTPMailAuthorization.php

@@ -0,0 +1,598 @@
+<?php
+/**
+ * Retrieve an access token from Microsoft login to authenticate
+ *
+ *
+ * Usage guide:
+ * - create a configuration array or object with all required parameters (see below).
+ * - create a singleton instance of the class, provide your config and request the access token.
+ *
+ * An example
+ * $accessToken = BxSMTPMailAuthorization::getInstance()->setConfig($config)->getAccessToken();
+ * Or as a shorthand
+ * $accessToken = BxSMTPMailAuthorization::getInstance($config)->getAccessToken();
+ *
+ *
+ * Configuration parameters and their values.
+ * You must specify one of the following configurations.
+ * If you have specified both, the class will give preference to the certificate flow over the client secret!
+ *
+ * Client ID + Secret authentication flow:
+ * $config = [
+ * 	 'mailAddress' => '',
+ *   'clientId' => '',
+ *   'clientSecret' => '',
+ *   'tenantId' => '',
+ *   'tenantName' => '',
+ * ];
+ *
+ * Client ID + certificate authentication flow:
+ * $config = [
+ * 	  'mailAddress' => '',
+ *    'clientId' => '',
+ *    'tenantId' => '',
+ *    'tenantName' => '',
+ *    'x509Cert' => '',
+ *    'x509Key' => '',
+ *  ];
+ *
+ * @var string $mailAddress is the senders mail (not the username) of the Outlook user account.
+ * @var string $clientId is a UIDv4 hash representing the Outlook user account.
+ * @var string $cleintSecret is a ~ 320 bit random hash with special chars.
+ * @var string $tenantId is a UIDv4 hash representing the company account.
+ * @var string $tenantName is a primary domain name for the company at Microsoft Outlook.
+ * @var string $x509Cert is the absolute file path of the public x509 certificate.
+ * @var string $x509Key is the absolute file path of the x509 private key.
+ *
+ * If you need to create a new certificate
+ * - open your terminal
+ * - navigate to the folder where the files should be stored
+ * - execute the following command and provide the requested information
+ * - please stick to the naming convention for a better association with the affected mail account
+ * - e.g. x509-max.muster@example.com.com-till-05.11.2026.crt
+ * $ openssl req -newkey rsa:4096 -new -x509 -days 365 -nodes -out x509-<mail-address>-till-<date>.crt -keyout x509-<mail-address>-till-<date>.key
+ *
+ * If you want to decode certificates on your own computer, run this OpenSSL command
+ * $ openssl x509 -in x509-<mail-address>-till-<date>.crt -text -noout
+ *
+ */
+
+use Firebase\JWT\JWT;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\ClientException;
+use GuzzleHttp\Exception\GuzzleException;
+
+/**
+ * Microsoft Announcement
+ * Exchange Online to retire Basic auth for Client Submission (SMTP AUTH)
+ * https://techcommunity.microsoft.com/blog/exchange/exchange-online-to-retire-basic-auth-for-client-submission-smtp-auth/4114750
+ *
+ * General Documentation with focus on Web-Apps
+ * https://learn.microsoft.com/de-de/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
+ *
+ * Documentation MSAL library for server side applications
+ * https://learn.microsoft.com/de-de/entra/identity-platform/scenario-daemon-app-registration
+ *
+ * Documentation for Deamon Apps (serverside logics)
+ * https://learn.microsoft.com/de-de/entra/identity-platform/scenario-daemon-app-configuration
+ */
+class BxSMTPMailAuthorization {
+
+	protected static self|null $instance = null;
+	/**
+	 * Run time cache for access token buffering
+	 * @var array
+	 */
+	protected array $runtimeCache = [];
+	/**
+	 * Email Address (the sending mail address not the username)
+	 * @var string
+	 */
+	protected string $mailAddress = '';
+	/**
+	 * Microsoft client ID
+	 * @var string UIDv4 Hash value
+	 */
+	protected string $clientId = '';
+	/**
+	 * Microsoft application/client secret
+	 * @var string 320-bit random hash with special chars
+	 */
+	private string $clientSecret = '';
+	/**
+	 * Microsoft tenant ID / customer number
+	 * @var string UIDv4 Hash value
+	 */
+	private string $tenantId = '';
+	/**
+	 * Microsoft tenant name / customer subdomain
+	 * @var string
+	 */
+	private string $tenantName = '';
+	/**
+	 * Absolute path to the x509 public certificate, file extension does not matter (.cer, .crt, .pem etc.)
+	 * @var string
+	 */
+	private string $x509Cert = '';
+	/**
+	 * Absolute path to the x509 protected key, file extension does not matter (.key, .cer, crt, etc.)
+	 * @var string
+	 */
+	private string $x509Key = '';
+	/**
+	 * Base64 encoded fingerprint of the public x509 certificate
+	 * @var string
+	 */
+	private string $x509Fingerprint = '';
+	/**
+	 * URL of the Microsoft authentication API
+	 * @var string
+	 */
+	private string $authenticationUrl = 'https://login.microsoftonline.com/%s/oauth2/v2.0/token';
+	/**
+	 * Status value: are the parameters for client secret authentication complete?
+	 * @var bool
+	 */
+	private bool $clientSecretParams = false;
+	/**
+	 * Status value: are the parameters for certificate authentication complete?
+	 * @var bool
+	 */
+	private bool $certificateParams = false;
+	/**
+	 * List of collected errors
+	 * @var array
+	 */
+	private array $errors = [];
+
+
+
+
+
+	/**
+	 * Get singleton class instance
+	 *
+	 * Config for client secret login [clientId, clientSecret, tenantId, tenantName]
+	 * Config for certificate login [clientId, tenantId, tenantName, x509Cert, x509Key]
+	 *  Extended config for certificate login [clientId, tenantId, tenantName, certificates]
+	 *
+	 * @param object|array|null $config Configuration values
+	 * @return self
+	 */
+	public static function getInstance(object|array|null $config): self{
+		if(!self::$instance){
+			self::$instance = new self();
+		}
+
+		return $config ? self::$instance->setConfig($config) : self::$instance;
+	}
+
+	/**
+	 * Set configuration values and reset error collection.
+	 *
+	 * Config for client secret login [clientId, clientSecret, tenantId, tenantName]
+	 * Config for certificate login [clientId, tenantId, tenantName, x509Cert, x509Key]
+	 * Extended config for certificate login [clientId, tenantId, tenantName, certificates]
+	 *
+	 * @param object|array $config
+	 * @return $this
+	 */
+	public function setConfig(object|array $config): self{
+
+		$config = (object)$config;
+
+		$this->mailAddress = $config->mailAddress ?? '';
+		$this->clientId = $config->clientId ?? '';
+		$this->clientSecret = $config->clientSecret ?? '';
+		$this->tenantId = $config->tenantId ?? '';
+		$this->tenantName = $config->tenantName ?? '';
+
+		if(!empty($config->certificates)){
+			$now = date('Y-m-d');
+			// convert to array of objects if array of arrays is given
+			$config->certificates = json_decode(json_encode($config->certificates));
+			usort($config->certificates, fn($a, $b) => strcmp($b->validFrom ?? '', $a->validFrom ?? ''));
+			$certificates = array_filter($config->certificates, function($v) use ($now){
+				return ($v->active ?? false) && ($v->validFrom ?? '') <= $now && ($v->validTill ?? '') >= $now;
+			}, 0);
+			if(!empty($certificates[0])){
+				$this->x509Cert = $certificates[0]->x509Cert ?? '';
+				$this->x509Key = $certificates[0]->x509Key ?? '';
+			}
+		}else{
+			$this->x509Cert = $config->x509Cert ?? '';
+			$this->x509Key = $config->x509Key ?? '';
+		}
+
+		$this->errors = [];
+
+		$this->checkAuthenticationScopes();
+
+		return $this;
+	}
+
+	/**
+	 * Retrieve the current valid access token.
+	 * Serve from cache if available and valid, or generate a new one.
+	 * @return string|null
+	 */
+	public function getAccessToken(): string|null{
+
+		$tokenData = $this->getTokenData($this->mailAddress) ?: $this->retrieveAccessToken__certificate() ?: $this->retrieveAccessToken__clientSecret();
+
+		return $tokenData['accessToken'] ?? null;
+	}
+
+	/**
+	 * Check if the authentication has any errors.
+	 * @return bool
+	 */
+	public function hasErrors(): bool{
+		return count($this->errors) > 0;
+	}
+
+	/**
+	 * Retrieve a list of all collected errors.
+	 * @return array
+	 */
+	public function getErrors(): array{
+		return $this->errors;
+	}
+
+	/**
+	 * Get the name of the certificate for creation routine.
+	 * Can also be used to get the file name if you want to check if it already exists.
+	 * @param string $fileExt file extension (crt, key)
+	 *  @param int $lifetime in days
+	 * @return string
+	 */
+	public function getNewCertFilename(string $fileExt, int $lifetime = 365): string {
+		$deadline = strtotime('+'.$lifetime.' days');
+		return sprintf('x509-%s-till-%s.%s', $this->mailAddress, strtolower(date('d.m.Y', $deadline)), $fileExt);
+	}
+
+	/**
+	 * Create a new certificate for an email account.
+	 * If a certificate crt and key files with the same name already exist, they will NOT be overwritten!!
+	 * @param string|null $absDir
+	 * @return array[]
+	 * @throws Exception
+	 */
+	public function createCertificate(string $absDir = null): array {
+
+		$dir = $absDir ?? dirname($this->x509Cert);
+
+		if(!is_dir($dir)){
+			throw new Exception("Directory '{$dir}' does not exist");
+		}
+
+		$certFileName = $this->getNewCertFilename('crt');
+		$keyFileName = $this->getNewCertFilename('key');
+
+		if(file_exists($certFileName) && file_exists($keyFileName)){
+			goto response;
+		}
+
+		$distinguished_names = [
+			'countryName'            => 'DE',
+			'stateOrProvinceName'    => 'Bavaria',
+			'localityName'           => 'Munich',
+			'organizationName'       => 'Org',
+			'organizationalUnitName' => $this->mailAddress,
+			'commonName'             => $this->mailAddress,
+			'emailAddress'           => $this->mailAddress,
+		];
+
+		$private_key = openssl_pkey_new(["digest_alg"       => "sha512",
+										 "private_key_bits" => 4096,
+										 "private_key_type" => OPENSSL_KEYTYPE_RSA,
+										]);
+
+		$csr = openssl_csr_new($distinguished_names, $private_key, ['digest_alg' => 'sha256']);
+		$x509 = openssl_csr_sign($csr, null, $private_key, 365, ['digest_alg' => 'sha256']);
+
+		openssl_pkey_export_to_file($private_key, $dir.$keyFileName);
+		openssl_x509_export_to_file($x509, $dir.$certFileName);
+
+		response:
+
+		return [
+			'crt' => [
+				'filename' => $certFileName,
+				'dir' => $dir,
+				'path' => $dir.$certFileName,
+			],
+			'key' => [
+				'filename' => $keyFileName,
+				'dir' => $dir,
+				'path' => $dir.$keyFileName,
+			],
+			'details' => $this->getCertificateDetails($dir.$certFileName),
+		];
+
+	}
+
+	/**
+	 * Retrieve the certificate details as an associative array.
+	 * @param string $absPath
+	 * @return array|false
+	 */
+	public function getCertificateDetails(string $absPath): array|false {
+		return openssl_x509_parse(file_get_contents($absPath));
+	}
+
+
+	/**
+	 * Create a new instance
+	 */
+	protected function __construct(){}
+
+	/**
+	 * Load the access token and its expiration from custom caching storage.
+	 * @param string $clientId affected client ID
+	 * @return array|null token data or null on failure
+	 */
+	protected function getCache(string $clientId): array|null {
+
+		// provide your own logic to load the cached data pending on the clientId
+
+		/* a simple example for a file based caching
+
+		$cacheFile = sprintf('%s/cache/azure-ad-tokens.json', MY_DOC_ROOT);
+		$cache = json_decode(file_get_contents($cacheFile), true);
+
+		*/
+
+		return $cache[$clientId] ?? null;
+	}
+
+	/**
+	 * Save the access token and its expiration to the custom caching storage.
+	 * @param string $clientId affected client ID
+	 * @param array $tokenData details about the access token
+	 * @return $this
+	 */
+	protected function setCache(string $clientId, array $tokenData): self{
+		// implement your own logic to store the token data in your cache pending on the clientId
+		// keep in mind, that the accessToken is a JWT and needs some space (string 2000)
+
+		/* a simple example for a file based caching
+
+		$cacheFile = sprintf('%s/cache/azure-ad-tokens.json', MY_DOC_ROOT);
+		$cache = json_decode(file_get_contents($cacheFile), true);
+		$cache[$clientId] = $tokenData;
+		file_put_contents($cacheFile, json_encode($cache, JSON_PRETTY_PRINT));
+
+		*/
+
+		return $this;
+	}
+
+	/**
+	 * Get the token data array from runtime or storage cache.
+	 * @param string $clientId affected client ID
+	 * @return array|null token data or null on failure
+	 */
+	protected function getTokenData(string $clientId): array|null {
+
+		$tokenData = $this->getRuntimeCache($clientId) ?: $this->getCache($clientId);
+
+		if($tokenData && $tokenData['expiresAt'] > time()){
+			$this->setRuntimeCache($clientId, $tokenData);
+			return $tokenData;
+		}
+
+		return null;
+	}
+
+	/**
+	 * Set token data to runtime and storage cache.
+	 * @param string $clientId affected client ID
+	 * @param array $tokenData details about the access token
+	 * @return $this
+	 */
+	protected function setTokenData(string $clientId, array $tokenData): self {
+
+		$this->setRuntimeCache($clientId, $tokenData);
+		$this->setCache($clientId, $tokenData);
+
+		return $this;
+	}
+
+	/**
+	 * Get token from runtime cache.
+	 * @param string $clientId affected client ID
+	 * @return array|null token data or null on failure
+	 */
+	protected function getRuntimeCache(string $clientId): array|null {
+		return $this->runtimeCache[$clientId] ?? null;
+	}
+
+	/**
+	 * Set token data to runtime cache.
+	 * @param string $clientId affected client ID
+	 * @param array $tokenData details about the access token
+	 * @return $this
+	 */
+	protected function setRuntimeCache(string $clientId, array $tokenData): self{
+		$this->runtimeCache[$clientId] = $tokenData;
+		return $this;
+	}
+
+	/**
+	 * Check if the parameters for the available authentication logics and scope are complete.
+	 * @return void
+	 */
+	private function checkAuthenticationScopes(): void{
+
+		$this->clientSecretParams = false;
+		$this->certificateParams = false;
+
+		if($this->clientId && $this->clientSecret && $this->tenantId && $this->tenantName){
+			$this->clientSecretParams = true;
+		}
+		if($this->clientId && $this->tenantId && $this->tenantName && $this->x509Cert && $this->x509Key){
+			if(empty(trim($this->x509Cert))){
+				$this->addError('X509 certificate is not set');
+			}
+			if(empty(trim($this->x509Key))){
+				$this->addError('X509 key is not set');
+			}
+			if(!$this->hasErrors()){
+				if($this->createX509Fingerprint()){
+					$this->certificateParams = true;
+				}
+			}
+		}
+		if(!$this->clientSecretParams && !$this->certificateParams){
+			$this->addError('Configuration values are incomplete or invalid');
+		}
+	}
+
+	/**
+	 * Add a new error message to the collection.
+	 * @param string $message
+	 * @return void
+	 */
+	private function addError(string $message): void{
+		$this->errors[] = $message;
+	}
+
+	/**
+	 * Create the base64 encoded fingerprint fo the x509 certificate.
+	 * @return bool
+	 */
+	private function createX509Fingerprint(): bool{
+		try{
+			$x509 = $this->x509Cert;
+			// read the cert string
+			$cert = openssl_x509_read($x509);
+			// get the sha1 fingerprint --> binary NOT hexadecimal!
+			$hex = openssl_x509_fingerprint($cert, 'sha1', true);
+			// convert binary thumbprint to base64 and remove trailing symbols
+			$this->x509Fingerprint = rtrim(base64_encode($hex), '=');
+			return true;
+		}catch(Exception $e){
+			$this->addError($e->getMessage());
+			return false;
+		}
+	}
+
+	/**
+	 * Retrieve a new access token via API, using the clientID + clientSecret authentication logic.
+	 * @return array|null token data or null on failure
+	 */
+	private function retrieveAccessToken__clientSecret(): array|null{
+
+		if(!$this->clientSecretParams) return null;
+
+		try{
+
+			$params = [
+				'form_params' => [
+					'grant_type'    => 'client_credentials',
+					'client_id'     => $this->clientId,
+					'client_secret' => $this->clientSecret,
+					'scope'         => sprintf('https://%s/.default', $this->tenantName), // maybe https://outlook.office.com/SMTP.Send, https://outlook.office365.com/.default
+				],
+			];
+
+			$this->fetchTokenFromMicrosoftLogin($params);
+
+			return $this->getRuntimeCache($this->mailAddress);
+
+		} catch (ClientException $e) {
+			$errorData = json_decode($e->getResponse()->getBody()->getContents());
+			$this->addError('Authentication Error: '.print_r($errorData, true));
+			return null;
+		}catch(GuzzleException $e){
+			$this->addError('Authentication Error: '.$e->getMessage());
+			return null;
+		}catch(Exception $e){
+			$this->addError('Authentication Error: '.$e->getMessage());
+			return null;
+		}
+	}
+
+	/**
+	 * Retrieve a new access token via API, using the clientID + certificate authentication logic.
+	 * @return array|null token data or null on failure
+	 */
+	private function retrieveAccessToken__certificate(): array|null{
+
+		if(!$this->certificateParams) return null;
+
+		try{
+
+			$claims = [
+				// Issued at timestamp
+				'iat' => strtotime('now'),
+				// Expires timestamp
+				'exp' => strtotime('+10 minutes'),
+				// Issuer
+				'iss' => $this->clientId,
+				// Subject Claim
+				'sub' => $this->clientId,
+				// Audience
+				'aud' => sprintf($this->authenticationUrl, $this->tenantId),
+			];
+
+			$headers = [
+				'typ' => 'JWT',
+				'alg' => 'RS256',
+				'x5t' => $this->x509Fingerprint,
+			];
+
+			$privateKey = $this->x509Key;
+			$jwt = JWT::encode($claims, $privateKey, 'RS256', null, $headers);
+
+			$params = [
+				'form_params' => [
+					'grant_type'            => 'client_credentials',
+					'client_id'             => $this->clientId,
+					'scope'                 => 'https://outlook.office.com/.default', // full mail access (IMAP, POP, SMTP)
+					'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
+					'client_assertion'      => $jwt,
+				]
+			];
+
+			$this->fetchTokenFromMicrosoftLogin($params);
+
+			return $this->getRuntimeCache($this->mailAddress);
+
+		} catch (ClientException $e) {
+			$errorData = json_decode($e->getResponse()->getBody()->getContents());
+			$this->addError('Authentication Error (client): '.print_r($errorData, true));
+			return null;
+		}catch(GuzzleException $e){
+			$this->addError('Authentication Error (guzzle): '.$e->getMessage());
+			return null;
+		}catch(Exception $e){
+			$this->addError('Authentication Error (php): '.$e->getMessage());
+			return null;
+		}
+	}
+
+	/**
+	 * Execute the authentication request and handle the response.
+	 * @param array $params
+	 * @return void
+	 * @throws GuzzleException
+	 */
+	private function fetchTokenFromMicrosoftLogin(array $params): void{
+
+		$client = new Client();
+		$response = $client->request('POST', sprintf($this->authenticationUrl, $this->tenantId), $params);
+		$data = $response->getBody()->getContents();
+		$jsonData = json_decode($data);
+
+		$tokenData = [
+			'accessToken' => $jsonData->access_token,
+			'expiresAt' => strtotime('+'.($jsonData->expires_in - 5).' seconds'),
+		];
+		// update run time scope
+		$this->setTokenData($this->mailAddress, $tokenData);
+	}
+
+
+}

+ 103 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/classes/BxSMTPMailAuthorizationPHPMailer.php

@@ -0,0 +1,103 @@
+<?php
+
+use PHPMailer\PHPMailer\OAuthTokenProvider;
+
+/**
+ * Mail Authorisation via oAuth2 for PHPMailer
+ */
+class BxSMTPMailAuthorizationPHPMailer extends BxSMTPMailAuthorization implements OAuthTokenProvider {
+
+
+	/**
+	 * Get singleton class instance
+	 *
+	 * Config for client secret login [clientId, clientSecret, tenantId, tenantName]
+	 * Config for certificate login [clientId, tenantId, tenantName, x509Cert, x509Key]
+	 *
+	 * @param object|array|null $config Configuration values
+	 * @return self
+	 */
+	public static function getInstance(object|array|null $config): self{
+		if(!self::$instance){
+			self::$instance = new self();
+		}
+
+		return $config ? self::$instance->setConfig($config) : self::$instance;
+	}
+
+	/**
+	 * Generate a base64-encoded OAuth token ensuring that the access token has not expired.
+	 * The string to be base 64 encoded should be in the form:
+	 * "user=<user_email_address>\001auth=Bearer <access_token>\001\001"
+	 *
+	 * @return string
+	 */
+	public function getOauth64(): string{
+
+		$accessToken = $this->getAccessToken();
+
+		if($this->hasErrors()){
+            $a = $this->getErrors();
+            foreach ($a as $s) {
+                bx_log('bx_smtp_mailer_oauth', $s, BX_LOG_ERR);
+            }
+			return '';
+		}
+
+		return base64_encode(
+			sprintf(
+				"user=%s\001auth=Bearer %s\001\001",
+				$this->mailAddress,
+				$accessToken
+			)
+		);
+	}
+
+	/**
+	 * Load the access token and its expiration from custom caching storage.
+	 * This method should be overridden to meet the project- or framework-specific requirements and possibilities.
+	 * @param string $clientId
+	 * @return array|null $tokenData on success, null on failure
+	 */
+	protected function getCache(string $clientId): array|null {
+
+        $oCache = $this->getCacheObject();
+        if ($oCache)
+            return $oCache->getData($this->getCacheKey($clientId));
+
+		return null;
+	}
+
+	/**
+	 * Save the access token and its expiration to the custom caching storage.
+	 * This method should be overridden to meet the project- or framework-specific requirements and possibilities.
+	 * @param string $clientId
+	 * @param array $tokenData
+	 * @return $this
+	 */
+	protected function setCache(string $clientId, array $tokenData): self{
+
+        $oCache = $this->getCacheObject();
+        if ($oCache)
+            $oCache->setData($this->getCacheKey($clientId, $tokenData), $tokenData);
+
+		return $this;
+	}
+
+    protected function getCacheObject(): object{
+
+        $sEngine = BxDolDb::getInstance()->getParam('sys_db_cache_engine');
+        $oCache = bx_instance('BxDolCache'.$sEngine);
+        if ($oCache->isAvailable())
+            return $oCache;
+
+        return null;
+    }
+
+
+    protected function getCacheKey(string $clientId): string{
+
+        return 'bx_smtp_oauth_token_' . $clientId . bx_site_hash() . '.php';
+    }
+
+}

+ 5 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/composer.json

@@ -0,0 +1,5 @@
+{
+    "require": {
+        "firebase/php-jwt": "^7.1"
+    }
+}

+ 85 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/composer.lock

@@ -0,0 +1,85 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "57ead8f94a88c5546e5c032135793e3c",
+    "packages": [
+        {
+            "name": "firebase/php-jwt",
+            "version": "v7.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/googleapis/php-jwt.git",
+                "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0",
+                "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^7.4",
+                "phpfastcache/phpfastcache": "^9.2",
+                "phpseclib/phpseclib": "~3.0",
+                "phpspec/prophecy-phpunit": "^2.0",
+                "phpunit/phpunit": "^9.5",
+                "psr/cache": "^2.0||^3.0",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0"
+            },
+            "suggest": {
+                "ext-sodium": "Support EdDSA (Ed25519) signatures",
+                "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
+                "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Firebase\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Neuman Vong",
+                    "email": "neuman+pear@twilio.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Anant Narayanan",
+                    "email": "anant@php.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+            "homepage": "https://github.com/googleapis/php-jwt",
+            "keywords": [
+                "jwt",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/googleapis/php-jwt/issues",
+                "source": "https://github.com/googleapis/php-jwt/tree/v7.1.0"
+            },
+            "time": "2026-06-11T17:54:14+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {},
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {},
+    "platform-dev": {},
+    "plugin-api-version": "2.6.0"
+}

+ 68 - 0
modules/boonex/smtpmailer/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    SMTPMailer SMTP Mailer
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_smtp',
+    'title' => 'SMTP Mailer',
+    'note' => 'Use remote SMTP server for sending mails.',
+    'version' => '15.0.1',
+    'vendor' => 'UNA INC',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '15.0.0-RC1'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/smtpmailer/',
+    'home_uri' => 'smtpmailer',
+
+    'db_prefix' => 'bx_smtp_',
+    'class_prefix' => 'BxSMTP',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'SMTP Mailer',
+
+    /**
+     * 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(),
+);
+
+/** @} */

+ 76 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/install/langs/en.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+	<string name="_bx_smtp"><![CDATA[SMTP Mailer]]></string>
+	<string name="_bx_smtp_tester"><![CDATA[Test]]></string>
+	<string name="_bx_smtp_recipient"><![CDATA[Recipient]]></string>
+	<string name="_bx_smtp_subject"><![CDATA[Subject]]></string>
+	<string name="_bx_smtp_body"><![CDATA[Body]]></string>
+	<string name="_bx_smtp_is_html"><![CDATA[Is HTML?]]></string>
+	<string name="_bx_smtp_send_ok"><![CDATA[Mail has been successfully sent]]></string>
+	<string name="_bx_smtp_send_fail"><![CDATA[Mail send failed]]></string>
+	<string name="_bx_smtp_help"><![CDATA[Help]]></string>
+	<string name="_bx_smtp_help_text"><![CDATA[
+        <div class="bx-def-vanilla-html" style="max-width: none;">
+        
+        <b>SMTP authentication </b> - most SMTP servers requires SMTP authentication, most probably you need to enable it.
+        <br />
+        <b>SMTP server name</b> - is it better to send mail from your site SMTP server, so it is probably the name of your site or localhost.
+        <br />
+        <b>SMTP username/SMTP password</b> - registered email account username and password, it is better to create different account for this purpose, like no-reply@mysite.com.
+        <br />
+        <b>From name of the message</b> - recipients will see this name as email sender, so it is probably your site name.
+        <br />
+        <b>Is your SMTP server requires secure connection</b> - you can disable it if your hostname is localhost, secure connection is not needed if you send mails from localhost.
+        <br />
+        <b>Enable SMTP mailer</b> - it is possible to disable SMTP mailer at all, for example if you have temporary problems with your SMTP server. 
+        <br />
+        <b>Override default sender email address</b> - set this the same as <b>SMTP username</b> email address, to avoid problems with mail marked as spam.
+        <br />
+        <br />
+        Alternatively, you can get above options from your mail client, if you are going to use the same email address for sending mail from your site.
+        
+        <hr class="bx-def-hr" />
+
+        If emails go to spam folder, check the following:
+        <ul>
+            <li>Try to set "Override default sender email address" field the same as "SMTP username" email address</li>
+            <li>Make sure that "Override default sender email address" is valid and exists</li>
+            <li>Try to change default email templates, it maybe that standard ones can be in spam filters, since they are used on a lot of sites by default</li>
+            <li>Ask your members to add your site email to their contacts - when email is in user's contact list it always goes to inbox</li>            
+        </ul>
+
+        <hr class="bx-def-hr" />
+
+        <b>OAuth2 for Microsoft Office 365</b> - enable OAuth2 for Microsoft Office 365 to send emails from your Microsoft Office 365 account. 
+        You need to create an application in Microsoft Azure and get the client ID, tenant ID, tenant name and set your own public certificate and private key.
+        Generate public certificate and private key which are used for authentication:
+        <a class="bx-def-btn" href="javascript:void(0);" onclick="bx_smtp_generate_cert();">Generate self signed certificate</a>
+
+        </div>
+]]></string>
+    <string name="_bx_smtp_adm_stg_cpt_type"><![CDATA[SMTP Mailer]]></string>
+    <string name="_bx_smtp_adm_stg_cpt_category_general"><![CDATA[General]]></string>
+    <string name="_bx_smtp_adm_stg_cpt_category_oauth"><![CDATA[OAuth2 for Microsoft Office 365]]></string>
+    
+    <string name="_bx_smtp_option_on"><![CDATA[Enable SMTP mailer]]></string>
+    <string name="_bx_smtp_option_auth"><![CDATA[SMTP authentication (Is your SMTP server requires username and password?)]]></string>
+    <string name="_bx_smtp_option_username"><![CDATA[SMTP username (only if SMTP authentication is enabled)]]></string>
+    <string name="_bx_smtp_option_password"><![CDATA[SMTP password (only if SMTP authentication is enabled)]]></string>
+    <string name="_bx_smtp_option_host"><![CDATA[SMTP server name or IP address]]></string>
+    <string name="_bx_smtp_option_port"><![CDATA[SMTP server port number (25 - default, 465 - for secure ssl connection, 587 - for secure tls connection)]]></string>
+    <string name="_bx_smtp_option_secure"><![CDATA[Is your SMTP server requires secure connection]]></string>
+    <string name="_bx_smtp_option_allow_selfsigned"><![CDATA[Allow self-signed certificates]]></string>
+    <string name="_bx_smtp_option_from_name"><![CDATA['From' name of the message]]></string>
+    <string name="_bx_smtp_option_from_email"><![CDATA[Override default sender email address]]></string>
+    <string name="_bx_smtp_option_test_mode"><![CDATA[Test mode (reroute all emails to single email address)]]></string>
+    <string name="_bx_smtp_option_test_email"><![CDATA[Test email]]></string>
+    <string name="_bx_smtp_option_test_subj"><![CDATA[Test subject prefix]]></string>
+    <string name="_bx_smtp_option_oauth_on"><![CDATA[Enable OAuth2 for Microsoft Office 365]]></string>
+    <string name="_bx_smtp_oauth_client_id"><![CDATA[Client (Application) ID]]></string>
+    <string name="_bx_smtp_oauth_tenant_id"><![CDATA[Tenant (Directory) ID]]></string>
+    <string name="_bx_smtp_oauth_tenant_name"><![CDATA[Tenant name]]></string>
+    <string name="_bx_smtp_oauth_cert_public"><![CDATA[X.509 public certificate]]></string>
+    <string name="_bx_smtp_oauth_cert_private"><![CDATA[X.509 private key]]></string>
+    <string name="_bx_smtp_mailer_log"><![CDATA[SMTP Mailer: sending errors]]></string>
+</resources>
+

+ 54 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/install/sql/enable.sql

@@ -0,0 +1,54 @@
+
+-- 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_smtp', '_bx_smtp_adm_stg_cpt_type', 'bx_smtpmailer@modules/boonex/smtpmailer/|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_smtp_general', '_bx_smtp_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_smtp_on', '', @iCategId, '_bx_smtp_option_on', 'checkbox', '', '', 10, ''),
+('bx_smtp_auth', '', @iCategId, '_bx_smtp_option_auth', 'checkbox', '', '', 20, ''),
+('bx_smtp_username', '', @iCategId, '_bx_smtp_option_username', 'digit', '', '', 30, ''),
+('bx_smtp_password', '', @iCategId, '_bx_smtp_option_password', 'digit', '', '', 40, ''),
+('bx_smtp_host', '', @iCategId, '_bx_smtp_option_host', 'digit', '', '', 50, ''),
+('bx_smtp_port', '25', @iCategId, '_bx_smtp_option_port', 'digit', '', '', 60, ''),
+('bx_smtp_secure', 'Not Secure', @iCategId, '_bx_smtp_option_secure', 'select', '', '', 70, 'Not Secure,SSL,TLS'), 
+('bx_smtp_allow_selfsigned', '', @iCategId, '_bx_smtp_option_allow_selfsigned', 'checkbox', '', '', 74, ''),
+('bx_smtp_from_name', '', @iCategId, '_bx_smtp_option_from_name', 'digit', '', '', 80, ''),
+('bx_smtp_from_email', '', @iCategId, '_bx_smtp_option_from_email', 'digit', '', '', 90, ''),
+('bx_smtp_test_mode', '', @iCategId, '_bx_smtp_option_test_mode', 'checkbox', '', '', 100, ''),
+('bx_smtp_test_email', '', @iCategId, '_bx_smtp_option_test_email', 'digit', '', '', 102, ''),
+('bx_smtp_test_subj', '[TEST] ', @iCategId, '_bx_smtp_option_test_subj', 'digit', '', '', 104, '');
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order` )  
+VALUES (@iTypeId, 'bx_smtp_oauth', '_bx_smtp_adm_stg_cpt_category_oauth', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `order`, `extra`) VALUES
+('bx_smtp_oauth_on', '', @iCategId, '_bx_smtp_option_oauth_on', 'checkbox', '', '', 10, ''),
+('bx_smtp_oauth_client_id', '', @iCategId, '_bx_smtp_oauth_client_id', 'digit', '', '', 20, ''),
+('bx_smtp_oauth_tenant_id', '', @iCategId, '_bx_smtp_oauth_tenant_id', 'digit', '', '', 30, ''),
+('bx_smtp_oauth_tenant_name', '', @iCategId, '_bx_smtp_oauth_tenant_name', 'digit', '', '', 40, ''),
+('bx_smtp_oauth_cert_public', '', @iCategId, '_bx_smtp_oauth_cert_public', 'text', '', '', 50, ''),
+('bx_smtp_oauth_cert_private', '', @iCategId, '_bx_smtp_oauth_cert_private', 'text', '', '', 60, '');
+
+
+-- logs
+
+INSERT INTO `sys_objects_logs` (`object`, `module`, `logs_storage`, `title`, `active`, `class_name`, `class_file`) VALUES
+('bx_smtp_mailer', 'system', 'Auto', '_bx_smtp_mailer_log', 1, '', '');
+
+-- alerts
+
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES 
+('bx_smtp', 'BxSMTPAlertsResponse', 'modules/boonex/smtpmailer/classes/BxSMTPAlertsResponse.php', '');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('system', 'before_send_mail', @iHandler);
+

+ 22 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/autoload.php

@@ -0,0 +1,22 @@
+<?php
+
+// autoload.php @generated by Composer
+
+if (PHP_VERSION_ID < 50600) {
+    if (!headers_sent()) {
+        header('HTTP/1.1 500 Internal Server Error');
+    }
+    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
+    if (!ini_get('display_errors')) {
+        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+            fwrite(STDERR, $err);
+        } elseif (!headers_sent()) {
+            echo $err;
+        }
+    }
+    throw new RuntimeException($err);
+}
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit20fad51902f91e7fd3039e016a6556b5::getLoader();

+ 579 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/ClassLoader.php

@@ -0,0 +1,579 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    https://www.php-fig.org/psr/psr-0/
+ * @see    https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    /** @var \Closure(string):void */
+    private static $includeFile;
+
+    /** @var string|null */
+    private $vendorDir;
+
+    // PSR-4
+    /**
+     * @var array<string, array<string, int>>
+     */
+    private $prefixLengthsPsr4 = array();
+    /**
+     * @var array<string, list<string>>
+     */
+    private $prefixDirsPsr4 = array();
+    /**
+     * @var list<string>
+     */
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    /**
+     * List of PSR-0 prefixes
+     *
+     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+     *
+     * @var array<string, array<string, list<string>>>
+     */
+    private $prefixesPsr0 = array();
+    /**
+     * @var list<string>
+     */
+    private $fallbackDirsPsr0 = array();
+
+    /** @var bool */
+    private $useIncludePath = false;
+
+    /**
+     * @var array<string, string>
+     */
+    private $classMap = array();
+
+    /** @var bool */
+    private $classMapAuthoritative = false;
+
+    /**
+     * @var array<string, bool>
+     */
+    private $missingClasses = array();
+
+    /** @var string|null */
+    private $apcuPrefix;
+
+    /**
+     * @var array<string, self>
+     */
+    private static $registeredLoaders = array();
+
+    /**
+     * @param string|null $vendorDir
+     */
+    public function __construct($vendorDir = null)
+    {
+        $this->vendorDir = $vendorDir;
+        self::initializeIncludeClosure();
+    }
+
+    /**
+     * @return array<string, list<string>>
+     */
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+        }
+
+        return array();
+    }
+
+    /**
+     * @return array<string, list<string>>
+     */
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    /**
+     * @return list<string>
+     */
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    /**
+     * @return list<string>
+     */
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    /**
+     * @return array<string, string> Array of classname => path
+     */
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array<string, string> $classMap Class to filename map
+     *
+     * @return void
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string              $prefix  The prefix
+     * @param list<string>|string $paths   The PSR-0 root directories
+     * @param bool                $prepend Whether to prepend the directories
+     *
+     * @return void
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        $paths = (array) $paths;
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string              $prefix  The prefix/namespace, with trailing '\\'
+     * @param list<string>|string $paths   The PSR-4 base directories
+     * @param bool                $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return void
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        $paths = (array) $paths;
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string              $prefix The prefix
+     * @param list<string>|string $paths  The PSR-0 base directories
+     *
+     * @return void
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string              $prefix The prefix/namespace, with trailing '\\'
+     * @param list<string>|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return void
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     *
+     * @return void
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     *
+     * @return void
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     *
+     * @return void
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     *
+     * @return void
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+        if (null === $this->vendorDir) {
+            return;
+        }
+
+        if ($prepend) {
+            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+        } else {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+            self::$registeredLoaders[$this->vendorDir] = $this;
+        }
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     *
+     * @return void
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+
+        if (null !== $this->vendorDir) {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+        }
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return true|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            $includeFile = self::$includeFile;
+            $includeFile($file);
+
+            return true;
+        }
+
+        return null;
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    /**
+     * Returns the currently registered loaders keyed by their corresponding vendor directories.
+     *
+     * @return array<string, self>
+     */
+    public static function getRegisteredLoaders()
+    {
+        return self::$registeredLoaders;
+    }
+
+    /**
+     * @param  string       $class
+     * @param  string       $ext
+     * @return string|false
+     */
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath . '\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        if (file_exists($file = $dir . $pathEnd)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+
+    /**
+     * @return void
+     */
+    private static function initializeIncludeClosure()
+    {
+        if (self::$includeFile !== null) {
+            return;
+        }
+
+        /**
+         * Scope isolated include.
+         *
+         * Prevents access to $this/self from included files.
+         *
+         * @param  string $file
+         * @return void
+         */
+        self::$includeFile = \Closure::bind(static function($file) {
+            include $file;
+        }, null, null);
+    }
+}

+ 396 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/InstalledVersions.php

@@ -0,0 +1,396 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+    /**
+     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+     * @internal
+     */
+    private static $selfDir = null;
+
+    /**
+     * @var mixed[]|null
+     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
+     */
+    private static $installed;
+
+    /**
+     * @var bool
+     */
+    private static $installedIsLocalDir;
+
+    /**
+     * @var bool|null
+     */
+    private static $canGetVendors;
+
+    /**
+     * @var array[]
+     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static $installedByVendor = array();
+
+    /**
+     * Returns a list of all package names which are present, either by being installed, replaced or provided
+     *
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackages()
+    {
+        $packages = array();
+        foreach (self::getInstalled() as $installed) {
+            $packages[] = array_keys($installed['versions']);
+        }
+
+        if (1 === \count($packages)) {
+            return $packages[0];
+        }
+
+        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+    }
+
+    /**
+     * Returns a list of all package names with a specific type e.g. 'library'
+     *
+     * @param  string   $type
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackagesByType($type)
+    {
+        $packagesByType = array();
+
+        foreach (self::getInstalled() as $installed) {
+            foreach ($installed['versions'] as $name => $package) {
+                if (isset($package['type']) && $package['type'] === $type) {
+                    $packagesByType[] = $name;
+                }
+            }
+        }
+
+        return $packagesByType;
+    }
+
+    /**
+     * Checks whether the given package is installed
+     *
+     * This also returns true if the package name is provided or replaced by another package
+     *
+     * @param  string $packageName
+     * @param  bool   $includeDevRequirements
+     * @return bool
+     */
+    public static function isInstalled($packageName, $includeDevRequirements = true)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (isset($installed['versions'][$packageName])) {
+                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks whether the given package satisfies a version constraint
+     *
+     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+     *
+     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+     *
+     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
+     * @param  string        $packageName
+     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+     * @return bool
+     */
+    public static function satisfies(VersionParser $parser, $packageName, $constraint)
+    {
+        $constraint = $parser->parseConstraints((string) $constraint);
+        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+        return $provided->matches($constraint);
+    }
+
+    /**
+     * Returns a version constraint representing all the range(s) which are installed for a given package
+     *
+     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+     * whether a given version of a package is installed, and not just whether it exists
+     *
+     * @param  string $packageName
+     * @return string Version constraint usable with composer/semver
+     */
+    public static function getVersionRanges($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            $ranges = array();
+            if (isset($installed['versions'][$packageName]['pretty_version'])) {
+                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+            }
+            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+            }
+            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+            }
+            if (array_key_exists('provided', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+            }
+
+            return implode(' || ', $ranges);
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getPrettyVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['pretty_version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+     */
+    public static function getReference($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['reference'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['reference'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+     */
+    public static function getInstallPath($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @return array
+     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+     */
+    public static function getRootPackage()
+    {
+        $installed = self::getInstalled();
+
+        return $installed[0]['root'];
+    }
+
+    /**
+     * Returns the raw installed.php data for custom implementations
+     *
+     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+     * @return array[]
+     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
+     */
+    public static function getRawData()
+    {
+        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = include __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        return self::$installed;
+    }
+
+    /**
+     * Returns the raw data of all installed.php which are currently loaded for custom implementations
+     *
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    public static function getAllRawData()
+    {
+        return self::getInstalled();
+    }
+
+    /**
+     * Lets you reload the static array from another file
+     *
+     * This is only useful for complex integrations in which a project needs to use
+     * this class but then also needs to execute another project's autoloader in process,
+     * and wants to ensure both projects have access to their version of installed.php.
+     *
+     * A typical case would be PHPUnit, where it would need to make sure it reads all
+     * the data it needs from this class, then call reload() with
+     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+     * the project in which it runs can then also use this class safely, without
+     * interference between PHPUnit's dependencies and the project's dependencies.
+     *
+     * @param  array[] $data A vendor/composer/installed.php data set
+     * @return void
+     *
+     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
+     */
+    public static function reload($data)
+    {
+        self::$installed = $data;
+        self::$installedByVendor = array();
+
+        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+        // so we have to assume it does not, and that may result in duplicate data being returned when listing
+        // all installed packages for example
+        self::$installedIsLocalDir = false;
+    }
+
+    /**
+     * @return string
+     */
+    private static function getSelfDir()
+    {
+        if (self::$selfDir === null) {
+            self::$selfDir = strtr(__DIR__, '\\', '/');
+        }
+
+        return self::$selfDir;
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static function getInstalled()
+    {
+        if (null === self::$canGetVendors) {
+            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+        }
+
+        $installed = array();
+        $copiedLocalDir = false;
+
+        if (self::$canGetVendors) {
+            $selfDir = self::getSelfDir();
+            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+                $vendorDir = strtr($vendorDir, '\\', '/');
+                if (isset(self::$installedByVendor[$vendorDir])) {
+                    $installed[] = self::$installedByVendor[$vendorDir];
+                } elseif (is_file($vendorDir.'/composer/installed.php')) {
+                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+                    $required = require $vendorDir.'/composer/installed.php';
+                    self::$installedByVendor[$vendorDir] = $required;
+                    $installed[] = $required;
+                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+                        self::$installed = $required;
+                        self::$installedIsLocalDir = true;
+                    }
+                }
+                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+                    $copiedLocalDir = true;
+                }
+            }
+        }
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+                $required = require __DIR__ . '/installed.php';
+                self::$installed = $required;
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        if (self::$installed !== array() && !$copiedLocalDir) {
+            $installed[] = self::$installed;
+        }
+
+        return $installed;
+    }
+}

+ 21 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/LICENSE

@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+

+ 10 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_classmap.php

@@ -0,0 +1,10 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+);

+ 9 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_namespaces.php

@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+);

+ 10 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_psr4.php

@@ -0,0 +1,10 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
+);

+ 38 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_real.php

@@ -0,0 +1,38 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit20fad51902f91e7fd3039e016a6556b5
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        require __DIR__ . '/platform_check.php';
+
+        spl_autoload_register(array('ComposerAutoloaderInit20fad51902f91e7fd3039e016a6556b5', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+        spl_autoload_unregister(array('ComposerAutoloaderInit20fad51902f91e7fd3039e016a6556b5', 'loadClassLoader'));
+
+        require __DIR__ . '/autoload_static.php';
+        call_user_func(\Composer\Autoload\ComposerStaticInit20fad51902f91e7fd3039e016a6556b5::getInitializer($loader));
+
+        $loader->register(true);
+
+        return $loader;
+    }
+}

+ 36 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/autoload_static.php

@@ -0,0 +1,36 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit20fad51902f91e7fd3039e016a6556b5
+{
+    public static $prefixLengthsPsr4 = array (
+        'F' => 
+        array (
+            'Firebase\\JWT\\' => 13,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Firebase\\JWT\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
+        ),
+    );
+
+    public static $classMap = array (
+        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInit20fad51902f91e7fd3039e016a6556b5::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit20fad51902f91e7fd3039e016a6556b5::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInit20fad51902f91e7fd3039e016a6556b5::$classMap;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 75 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/installed.json

@@ -0,0 +1,75 @@
+{
+    "packages": [
+        {
+            "name": "firebase/php-jwt",
+            "version": "v7.1.0",
+            "version_normalized": "7.1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/googleapis/php-jwt.git",
+                "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0",
+                "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^7.4",
+                "phpfastcache/phpfastcache": "^9.2",
+                "phpseclib/phpseclib": "~3.0",
+                "phpspec/prophecy-phpunit": "^2.0",
+                "phpunit/phpunit": "^9.5",
+                "psr/cache": "^2.0||^3.0",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0"
+            },
+            "suggest": {
+                "ext-sodium": "Support EdDSA (Ed25519) signatures",
+                "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
+                "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures"
+            },
+            "time": "2026-06-11T17:54:14+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Firebase\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Neuman Vong",
+                    "email": "neuman+pear@twilio.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Anant Narayanan",
+                    "email": "anant@php.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+            "homepage": "https://github.com/googleapis/php-jwt",
+            "keywords": [
+                "jwt",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/googleapis/php-jwt/issues",
+                "source": "https://github.com/googleapis/php-jwt/tree/v7.1.0"
+            },
+            "install-path": "../firebase/php-jwt"
+        }
+    ],
+    "dev": true,
+    "dev-package-names": []
+}

+ 32 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/installed.php

@@ -0,0 +1,32 @@
+<?php return array(
+    'root' => array(
+        'name' => '__root__',
+        'pretty_version' => 'dev-master',
+        'version' => 'dev-master',
+        'reference' => '8c8b9e395565a5064aa73019b8e46c1670911a5b',
+        'type' => 'library',
+        'install_path' => __DIR__ . '/../../',
+        'aliases' => array(),
+        'dev' => true,
+    ),
+    'versions' => array(
+        '__root__' => array(
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
+            'reference' => '8c8b9e395565a5064aa73019b8e46c1670911a5b',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../../',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+        'firebase/php-jwt' => array(
+            'pretty_version' => 'v7.1.0',
+            'version' => '7.1.0.0',
+            'reference' => 'b374a5d1a4f1f67fadc2165cdb284645945e2fc0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../firebase/php-jwt',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+    ),
+);

+ 26 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/composer/platform_check.php

@@ -0,0 +1,26 @@
+<?php
+
+// platform_check.php @generated by Composer
+
+$issues = array();
+
+if (!(PHP_VERSION_ID >= 80000)) {
+    $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+    if (!headers_sent()) {
+        header('HTTP/1.1 500 Internal Server Error');
+    }
+    if (!ini_get('display_errors')) {
+        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+        } elseif (!headers_sent()) {
+            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+        }
+    }
+    trigger_error(
+        'Composer detected issues in your platform: ' . implode(' ', $issues),
+        E_USER_ERROR
+    );
+}

+ 265 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/firebase/php-jwt/CHANGELOG.md

@@ -0,0 +1,265 @@
+# Changelog
+
+## [7.1.0](https://github.com/googleapis/php-jwt/compare/v7.0.5...v7.1.0) (2026-06-11)
+
+
+### Features
+
+* Add support for PS256 ([#637](https://github.com/googleapis/php-jwt/issues/637)) ([aa0b447](https://github.com/googleapis/php-jwt/commit/aa0b4472c137255545b177a1840891304eac55f1))
+
+
+### Bug Fixes
+
+* Skip parsing valid unsupported curves ([#638](https://github.com/googleapis/php-jwt/issues/638)) ([8a6c2a7](https://github.com/googleapis/php-jwt/commit/8a6c2a75d7f45e781c93d618939f8a045fe69f53))
+* Update homepage URL in composer.json ([#635](https://github.com/googleapis/php-jwt/issues/635)) ([f7ae571](https://github.com/googleapis/php-jwt/commit/f7ae571efe255aed89c8f8d0f2cb7f71c5835664))
+* Validate numeric type of iat, nbf and exp claims in encode ([#634](https://github.com/googleapis/php-jwt/issues/634)) ([958e422](https://github.com/googleapis/php-jwt/commit/958e422961e874843a7fcc339e55a0101a66201d))
+
+## [7.0.5](https://github.com/firebase/php-jwt/compare/v7.0.4...v7.0.5) (2026-03-31)
+
+
+### Bug Fixes
+
+* RSA from JWK sometimes returns empty Instance ([#628](https://github.com/firebase/php-jwt/issues/628)) ([b4c78aa](https://github.com/firebase/php-jwt/commit/b4c78aa731664122198ad36c0033aa29e807397a))
+
+## [7.0.4](https://github.com/firebase/php-jwt/compare/v7.0.3...v7.0.4) (2026-03-27)
+
+
+### Bug Fixes
+
+* readme examples, add tests for all examples ([#626](https://github.com/firebase/php-jwt/issues/626)) ([510a00c](https://github.com/firebase/php-jwt/commit/510a00c0e6353bc7d68412fab67e57a13954cb46))
+* use urlsafeB64Decode everywhere ([#627](https://github.com/firebase/php-jwt/issues/627)) ([b889495](https://github.com/firebase/php-jwt/commit/b889495c83ddc3f3885ca3f0b65b41b1cb37a3b1))
+
+## [7.0.3](https://github.com/firebase/php-jwt/compare/v7.0.2...v7.0.3) (2026-02-18)
+
+
+### Miscellaneous Chores
+
+* add environment for Release Please job ([#619](https://github.com/firebase/php-jwt/issues/619)) ([300fd02](https://github.com/firebase/php-jwt/commit/300fd02c883f096c9067df652dbd23f62cb5e2a7))
+
+## [7.0.2](https://github.com/firebase/php-jwt/compare/v7.0.1...v7.0.2) (2025-12-16)
+
+
+### Bug Fixes
+
+* add key length validation for ec keys ([#615](https://github.com/firebase/php-jwt/issues/615)) ([7044f9a](https://github.com/firebase/php-jwt/commit/7044f9ae7e7d175d28cca71714feb236f1c0e252))
+
+## [7.0.0](https://github.com/firebase/php-jwt/compare/v6.11.1...v7.0.0) (2025-12-15)
+
+
+### ⚠️ ⚠️ ⚠️  Security Fixes ⚠️ ⚠️ ⚠️ 
+ * add key size validation ([#613](https://github.com/firebase/php-jwt/issues/613)) ([6b80341](https://github.com/firebase/php-jwt/commit/6b80341bf57838ea2d011487917337901cd71576))
+ **NOTE**: This fix will cause keys with a size below the minimally allowed size to break. 
+
+### Features
+
+* add SensitiveParameter attribute to security-critical parameters ([#603](https://github.com/firebase/php-jwt/issues/603)) ([4dbfac0](https://github.com/firebase/php-jwt/commit/4dbfac0260eeb0e9e643063c99998e3219cc539b))
+* store timestamp in `ExpiredException` ([#604](https://github.com/firebase/php-jwt/issues/604)) ([f174826](https://github.com/firebase/php-jwt/commit/f1748260d218a856b6a0c23715ac7fae1d7ca95b))
+
+
+### Bug Fixes
+
+* validate iat and nbf on payload ([#568](https://github.com/firebase/php-jwt/issues/568)) ([953b2c8](https://github.com/firebase/php-jwt/commit/953b2c88bb445b7e3bb82a5141928f13d7343afd))
+
+## [6.11.1](https://github.com/firebase/php-jwt/compare/v6.11.0...v6.11.1) (2025-04-09)
+
+
+### Bug Fixes
+
+* update error text for consistency ([#528](https://github.com/firebase/php-jwt/issues/528)) ([c11113a](https://github.com/firebase/php-jwt/commit/c11113afa13265e016a669e75494b9203b8a7775))
+
+## [6.11.0](https://github.com/firebase/php-jwt/compare/v6.10.2...v6.11.0) (2025-01-23)
+
+
+### Features
+
+* support octet typed JWK ([#587](https://github.com/firebase/php-jwt/issues/587)) ([7cb8a26](https://github.com/firebase/php-jwt/commit/7cb8a265fa81edf2fa6ef8098f5bc5ae573c33ad))
+
+
+### Bug Fixes
+
+* refactor constructor Key to use PHP 8.0 syntax ([#577](https://github.com/firebase/php-jwt/issues/577)) ([29fa2ce](https://github.com/firebase/php-jwt/commit/29fa2ce9e0582cd397711eec1e80c05ce20fabca))
+
+## [6.10.2](https://github.com/firebase/php-jwt/compare/v6.10.1...v6.10.2) (2024-11-24)
+
+
+### Bug Fixes
+
+* Mitigate PHP8.4 deprecation warnings ([#570](https://github.com/firebase/php-jwt/issues/570)) ([76808fa](https://github.com/firebase/php-jwt/commit/76808fa227f3811aa5cdb3bf81233714b799a5b5))
+* support php 8.4 ([#583](https://github.com/firebase/php-jwt/issues/583)) ([e3d68b0](https://github.com/firebase/php-jwt/commit/e3d68b044421339443c74199edd020e03fb1887e))
+
+## [6.10.1](https://github.com/firebase/php-jwt/compare/v6.10.0...v6.10.1) (2024-05-18)
+
+
+### Bug Fixes
+
+* ensure ratelimit expiry is set every time ([#556](https://github.com/firebase/php-jwt/issues/556)) ([09cb208](https://github.com/firebase/php-jwt/commit/09cb2081c2c3bc0f61e2f2a5fbea5741f7498648))
+* ratelimit cache expiration ([#550](https://github.com/firebase/php-jwt/issues/550)) ([dda7250](https://github.com/firebase/php-jwt/commit/dda725033585ece30ff8cae8937320d7e9f18bae))
+
+## [6.10.0](https://github.com/firebase/php-jwt/compare/v6.9.0...v6.10.0) (2023-11-28)
+
+
+### Features
+
+* allow typ header override ([#546](https://github.com/firebase/php-jwt/issues/546)) ([79cb30b](https://github.com/firebase/php-jwt/commit/79cb30b729a22931b2fbd6b53f20629a83031ba9))
+
+## [6.9.0](https://github.com/firebase/php-jwt/compare/v6.8.1...v6.9.0) (2023-10-04)
+
+
+### Features
+
+* add payload to jwt exception ([#521](https://github.com/firebase/php-jwt/issues/521)) ([175edf9](https://github.com/firebase/php-jwt/commit/175edf958bb61922ec135b2333acf5622f2238a2))
+
+## [6.8.1](https://github.com/firebase/php-jwt/compare/v6.8.0...v6.8.1) (2023-07-14)
+
+
+### Bug Fixes
+
+* accept float claims but round down to ignore them ([#492](https://github.com/firebase/php-jwt/issues/492)) ([3936842](https://github.com/firebase/php-jwt/commit/39368423beeaacb3002afa7dcb75baebf204fe7e))
+* different BeforeValidException messages for nbf and iat ([#526](https://github.com/firebase/php-jwt/issues/526)) ([0a53cf2](https://github.com/firebase/php-jwt/commit/0a53cf2986e45c2bcbf1a269f313ebf56a154ee4))
+
+## [6.8.0](https://github.com/firebase/php-jwt/compare/v6.7.0...v6.8.0) (2023-06-14)
+
+
+### Features
+
+* add support for P-384 curve ([#515](https://github.com/firebase/php-jwt/issues/515)) ([5de4323](https://github.com/firebase/php-jwt/commit/5de4323f4baf4d70bca8663bd87682a69c656c3d))
+
+
+### Bug Fixes
+
+* handle invalid http responses ([#508](https://github.com/firebase/php-jwt/issues/508)) ([91c39c7](https://github.com/firebase/php-jwt/commit/91c39c72b22fc3e1191e574089552c1f2041c718))
+
+## [6.7.0](https://github.com/firebase/php-jwt/compare/v6.6.0...v6.7.0) (2023-06-14)
+
+
+### Features
+
+* add ed25519 support to JWK (public keys) ([#452](https://github.com/firebase/php-jwt/issues/452)) ([e53979a](https://github.com/firebase/php-jwt/commit/e53979abae927de916a75b9d239cfda8ce32be2a))
+
+## [6.6.0](https://github.com/firebase/php-jwt/compare/v6.5.0...v6.6.0) (2023-06-13)
+
+
+### Features
+
+* allow get headers when decoding token ([#442](https://github.com/firebase/php-jwt/issues/442)) ([fb85f47](https://github.com/firebase/php-jwt/commit/fb85f47cfaeffdd94faf8defdf07164abcdad6c3))
+
+
+### Bug Fixes
+
+* only check iat if nbf is not used ([#493](https://github.com/firebase/php-jwt/issues/493)) ([398ccd2](https://github.com/firebase/php-jwt/commit/398ccd25ea12fa84b9e4f1085d5ff448c21ec797))
+
+## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12)
+
+
+### Bug Fixes
+
+* allow KID of '0' ([#505](https://github.com/firebase/php-jwt/issues/505)) ([9dc46a9](https://github.com/firebase/php-jwt/commit/9dc46a9c3e5801294249cfd2554c5363c9f9326a))
+
+
+### Miscellaneous Chores
+
+* drop support for PHP 7.3 ([#495](https://github.com/firebase/php-jwt/issues/495))
+
+## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08)
+
+
+### Features
+
+* add support for W3C ES256K ([#462](https://github.com/firebase/php-jwt/issues/462)) ([213924f](https://github.com/firebase/php-jwt/commit/213924f51936291fbbca99158b11bd4ae56c2c95))
+* improve caching by only decoding jwks when necessary ([#486](https://github.com/firebase/php-jwt/issues/486)) ([78d3ed1](https://github.com/firebase/php-jwt/commit/78d3ed1073553f7d0bbffa6c2010009a0d483d5c))
+
+## [6.3.2](https://github.com/firebase/php-jwt/compare/v6.3.1...v6.3.2) (2022-11-01)
+
+
+### Bug Fixes
+
+* check kid before using as array index ([bad1b04](https://github.com/firebase/php-jwt/commit/bad1b040d0c736bbf86814c6b5ae614f517cf7bd))
+
+## [6.3.1](https://github.com/firebase/php-jwt/compare/v6.3.0...v6.3.1) (2022-11-01)
+
+
+### Bug Fixes
+
+* casing of GET for PSR compat ([#451](https://github.com/firebase/php-jwt/issues/451)) ([60b52b7](https://github.com/firebase/php-jwt/commit/60b52b71978790eafcf3b95cfbd83db0439e8d22))
+* string interpolation format for php 8.2 ([#446](https://github.com/firebase/php-jwt/issues/446)) ([2e07d8a](https://github.com/firebase/php-jwt/commit/2e07d8a1524d12b69b110ad649f17461d068b8f2))
+
+## 6.3.0 / 2022-07-15
+
+ - Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399))
+ - Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435))
+
+## 6.2.0 / 2022-05-14
+
+ - Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397))
+ - Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)).
+
+## 6.1.0 / 2022-03-23
+
+ - Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0
+ - Add parameter typing and return types where possible
+
+## 6.0.0 / 2022-01-24
+
+ - **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information.
+ - New Key object to prevent key/algorithm type confusion (#365)
+ - Add JWK support (#273)
+ - Add ES256 support (#256)
+ - Add ES384 support (#324)
+ - Add Ed25519 support (#343)
+
+## 5.0.0 / 2017-06-26
+- Support RS384 and RS512.
+  See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)!
+- Add an example for RS256 openssl.
+  See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)!
+- Detect invalid Base64 encoding in signature.
+  See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)!
+- Update `JWT::verify` to handle OpenSSL errors.
+  See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)!
+- Add `array` type hinting to `decode` method
+  See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)!
+- Add all JSON error types.
+  See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)!
+- Bugfix 'kid' not in given key list.
+  See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)!
+- Miscellaneous cleanup, documentation and test fixes.
+  See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115),
+  [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and
+  [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman),
+  [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)!
+
+## 4.0.0 / 2016-07-17
+- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)!
+- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)!
+- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)!
+- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)!
+
+## 3.0.0 / 2015-07-22
+- Minimum PHP version updated from `5.2.0` to `5.3.0`.
+- Add `\Firebase\JWT` namespace. See
+[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to
+[@Dashron](https://github.com/Dashron)!
+- Require a non-empty key to decode and verify a JWT. See
+[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to
+[@sjones608](https://github.com/sjones608)!
+- Cleaner documentation blocks in the code. See
+[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to
+[@johanderuijter](https://github.com/johanderuijter)!
+
+## 2.2.0 / 2015-06-22
+- Add support for adding custom, optional JWT headers to `JWT::encode()`. See
+[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to
+[@mcocaro](https://github.com/mcocaro)!
+
+## 2.1.0 / 2015-05-20
+- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew
+between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)!
+- Add support for passing an object implementing the `ArrayAccess` interface for
+`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)!
+
+## 2.0.0 / 2015-04-01
+- **Note**: It is strongly recommended that you update to > v2.0.0 to address
+  known security vulnerabilities in prior versions when both symmetric and
+  asymmetric keys are used together.
+- Update signature for `JWT::decode(...)` to require an array of supported
+  algorithms to use when verifying token signatures.

+ 30 - 0
modules/boonex/smtpmailer/updates/15.0.0_15.0.1/source/vendor/firebase/php-jwt/LICENSE

@@ -0,0 +1,30 @@
+Copyright (c) 2011, Neuman Vong
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of the copyright holder nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff