Explorar el Código

Modules updates.

AntonLV hace 4 años
padre
commit
0ab245ad5f
Se han modificado 42 ficheros con 1815 adiciones y 8 borrados
  1. 1 0
      modules/boonex/courses/updates/13.0.0_13.0.1/install/langs/en.xml
  2. 1 0
      modules/boonex/courses/updates/13.0.0_13.0.1/source/classes/BxCoursesConfig.php
  3. 1 0
      modules/boonex/courses/updates/13.0.0_13.0.1/source/install/langs/en.xml
  4. 1 0
      modules/boonex/credits/updates/13.0.0_13.0.1/install/langs/en.xml
  5. 194 0
      modules/boonex/credits/updates/13.0.0_13.0.1/source/classes/BxCreditsTemplate.php
  6. 2 1
      modules/boonex/credits/updates/13.0.0_13.0.1/source/install/langs/en.xml
  7. 1 0
      modules/boonex/english/updates/13.0.0_13.0.1/install/langs/en.xml
  8. 1 0
      modules/boonex/english/updates/13.0.0_13.0.1/source/data/langs/system/en.xml
  9. 1 0
      modules/boonex/events/updates/13.0.0_13.0.1/install/langs/en.xml
  10. 1 0
      modules/boonex/events/updates/13.0.0_13.0.1/source/classes/BxEventsConfig.php
  11. 1 0
      modules/boonex/events/updates/13.0.0_13.0.1/source/install/langs/en.xml
  12. 6 6
      modules/boonex/forum/updates/13.0.0_13.0.1/install/installer.php
  13. 1 1
      modules/boonex/forum/updates/13.0.0_13.0.1/install/sql/enable.sql
  14. 1 0
      modules/boonex/groups/updates/13.0.0_13.0.1/install/langs/en.xml
  15. 1 0
      modules/boonex/groups/updates/13.0.0_13.0.1/source/classes/BxGroupsConfig.php
  16. 1 0
      modules/boonex/groups/updates/13.0.0_13.0.1/source/install/langs/en.xml
  17. 133 0
      modules/boonex/invites/updates/12.0.0_13.0.0/source/classes/BxInvResponse.php
  18. 1 0
      modules/boonex/organizations/updates/13.0.0_13.0.1/install/langs/en.xml
  19. 2 0
      modules/boonex/organizations/updates/13.0.0_13.0.1/install/sql/enable.sql
  20. 1 0
      modules/boonex/organizations/updates/13.0.0_13.0.1/source/classes/BxOrgsConfig.php
  21. 1 0
      modules/boonex/organizations/updates/13.0.0_13.0.1/source/install/langs/en.xml
  22. 776 0
      modules/boonex/payment/updates/13.0.0_13.0.1/source/classes/BxPaymentDb.php
  23. 114 0
      modules/boonex/payment/updates/13.0.0_13.0.1/source/classes/BxPaymentGridSbsAdministration.php
  24. 549 0
      modules/boonex/payment/updates/13.0.0_13.0.1/source/classes/BxPaymentSubscriptions.php
  25. 2 0
      modules/boonex/persons/updates/13.0.0_13.0.1/install/sql/enable.sql
  26. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_courses/ru.xml
  27. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_credits/ru.xml
  28. 3 0
      modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_events/ru.xml
  29. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_groups/ru.xml
  30. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_organizations/ru.xml
  31. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_spaces/ru.xml
  32. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/system/ru.xml
  33. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_courses/ru.xml
  34. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_credits/ru.xml
  35. 3 0
      modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_events/ru.xml
  36. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_groups/ru.xml
  37. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_organizations/ru.xml
  38. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_spaces/ru.xml
  39. 1 0
      modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/system/ru.xml
  40. 1 0
      modules/boonex/spaces/updates/13.0.0_13.0.1/install/langs/en.xml
  41. 1 0
      modules/boonex/spaces/updates/13.0.0_13.0.1/source/classes/BxSpacesConfig.php
  42. 1 0
      modules/boonex/spaces/updates/13.0.0_13.0.1/source/install/langs/en.xml

+ 1 - 0
modules/boonex/courses/updates/13.0.0_13.0.1/install/langs/en.xml

@@ -3,4 +3,5 @@
     <string_add name="_bx_courses_option_enable_auto_approve"><![CDATA[Auto approve a course after creation]]></string_add>
 	<string_add name="_bx_courses_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string_add>
 	<string_add name="_bx_courses_txt_status_hidden"><![CDATA[This course was hidden.]]></string_add>
+	<string_add name="_bx_courses_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string_add>
 </resources>

+ 1 - 0
modules/boonex/courses/updates/13.0.0_13.0.1/source/classes/BxCoursesConfig.php

@@ -242,6 +242,7 @@ class BxCoursesConfig extends BxBaseModGroupsConfig
                 'txt_n_unit' => '_bx_courses_txt_n_unit',
                 'txt_buy_title' => '_bx_courses_grid_action_title_buy_title',
                 'txt_cart_item_title' => '_bx_courses_txt_cart_item_title',
+                'txt_cart_item_title_lifetime' => '_bx_courses_txt_cart_item_title_lifetime',
                 'txt_subscribe_title' => '_bx_courses_grid_action_title_subscribe_title',
                 'msg_performed' => '_bx_courses_msg_performed',
                 'popup_title_price_add' => '_bx_courses_popup_title_price_add',

+ 1 - 0
modules/boonex/courses/updates/13.0.0_13.0.1/source/install/langs/en.xml

@@ -372,6 +372,7 @@
     <string name="_bx_courses_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Sorry, an error has occurred. You can not join this course. This invitation cannot be used by you.]]></string>
     <string name="_bx_courses_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_courses_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_courses_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string>    
 
     <string name="_bx_courses_msg_performed"><![CDATA[The action was successfully performed.]]></string>
     <string name="_bx_courses_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>

+ 1 - 0
modules/boonex/credits/updates/13.0.0_13.0.1/install/langs/en.xml

@@ -13,4 +13,5 @@
 {email_footer}]]></string_add>
 	<string_add name="_bx_credits_txt_history_info_send"><![CDATA[Transfer]]></string_add>
 	<string_add name="_bx_credits_txt_history_info_cancellation"><![CDATA[Cancellation]]></string_add>
+	<string_add name="_bx_credits_txt_subscribe_lifetime"><![CDATA[lifetime]]></string_add>
 </resources>

+ 194 - 0
modules/boonex/credits/updates/13.0.0_13.0.1/source/classes/BxCreditsTemplate.php

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

+ 2 - 1
modules/boonex/credits/updates/13.0.0_13.0.1/source/install/langs/en.xml

@@ -251,7 +251,8 @@
 
     <!-- Subscribe process -->
     <string name="_bx_credits_txt_subscribe"><![CDATA[Subscribe]]></string>
-    <string name="_bx_credits_txt_subscribe_for"><![CDATA[Subscribe for:]]></string>    
+    <string name="_bx_credits_txt_subscribe_for"><![CDATA[Subscribe for:]]></string>
+    <string name="_bx_credits_txt_subscribe_lifetime"><![CDATA[lifetime]]></string>    
     <string name="_bx_credits_txt_subscribe_period_mask"><![CDATA[{0} {1}]]></string>
     <string name="_bx_credits_txt_subscribe_period_unit_year"><![CDATA[yr.]]></string>
     <string name="_bx_credits_txt_subscribe_period_unit_month"><![CDATA[mo.]]></string>

+ 1 - 0
modules/boonex/english/updates/13.0.0_13.0.1/install/langs/en.xml

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources name="en" flag="gb" title="English">
 	<string_add name="_apps"><![CDATA[Apps]]></string_add>
+	<string_add name="_never"><![CDATA[never]]></string_add>
 	<string_add name="_adm_bp_txt_block_class"><![CDATA[CSS Class]]></string_add>
 	<string_add name="_adm_dbd_txt_c_clear_opcache"><![CDATA[Clear Opcache]]></string_add>
 	<string_add name="_adm_form_btn_search_forms_sortable_fields_reset"><![CDATA[Reset]]></string_add>

+ 1 - 0
modules/boonex/english/updates/13.0.0_13.0.1/source/data/langs/system/en.xml

@@ -76,6 +76,7 @@
 	<string name="_N_points"><![CDATA[<span>{0}</span> pluses]]></string>
 	<string name="_Name"><![CDATA[Name]]></string>
 	<string name="_Name is required"><![CDATA[Name is required]]></string>
+        <string name="_never"><![CDATA[never]]></string>
 	<string name="_No"><![CDATA[No]]></string>
 	<string name="_None"><![CDATA[None]]></string>
 	<string name="_No actions allowed for this membership"><![CDATA[No actions allowed for this membership]]></string>

+ 1 - 0
modules/boonex/events/updates/13.0.0_13.0.1/install/langs/en.xml

@@ -6,4 +6,5 @@
 	<string_add name="_bx_events_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string_add>
 	<string_add name="_bx_events_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string_add>
 	<string_add name="_bx_events_txt_status_hidden"><![CDATA[This event was hidden.]]></string_add>
+	<string_add name="_bx_events_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string_add>
 </resources>

+ 1 - 0
modules/boonex/events/updates/13.0.0_13.0.1/source/classes/BxEventsConfig.php

@@ -261,6 +261,7 @@ class BxEventsConfig extends BxBaseModGroupsConfig
                 'txt_n_unit' => '_bx_events_txt_n_unit',
                 'txt_buy_title' => '_bx_events_grid_action_title_buy_title',
                 'txt_cart_item_title' => '_bx_events_txt_cart_item_title',
+                'txt_cart_item_title_lifetime' => '_bx_events_txt_cart_item_title_lifetime',
                 'txt_subscribe_title' => '_bx_events_grid_action_title_subscribe_title',
                 'popup_title_price_add' => '_bx_events_popup_title_price_add',
                 'popup_title_price_edit' => '_bx_events_popup_title_price_edit',

+ 1 - 0
modules/boonex/events/updates/13.0.0_13.0.1/source/install/langs/en.xml

@@ -431,6 +431,7 @@
     <string name="_bx_events_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Sorry, an error has occurred. You can not join this event. This invitation cannot be used by you.]]></string>
     <string name="_bx_events_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_events_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_events_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string>    
 
     <string name="_bx_events_msg_performed"><![CDATA[The action was successfully performed.]]></string>
     <string name="_bx_events_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>

+ 6 - 6
modules/boonex/forum/updates/13.0.0_13.0.1/install/installer.php

@@ -19,7 +19,7 @@ class BxForumUpdater extends BxDolStudioUpdater
             if(!$this->oDb->isFieldExists('bx_forum_discussions', 'resolved'))
                 $this->oDb->query("ALTER TABLE `bx_forum_discussions` ADD `resolved` tinyint(4) NOT NULL DEFAULT '0' AFTER `resolvable`");
             if(!$this->oDb->isFieldExists('bx_forum_discussions', 'cf'))
-                $this->oDb->query("ALTER TABLE `bx_forum_discussions` ADD `cf` int(11) NOT NULL default '1' AFTER `featured`");
+                $this->oDb->query("ALTER TABLE `bx_forum_discussions` ADD `cf` int(11) NOT NULL default '1' AFTER `allow_view_to`");
 
             if(!$this->oDb->isFieldExists('bx_forum_categories', 'icon'))
                 $this->oDb->query("ALTER TABLE `bx_forum_categories` ADD `icon` text NOT NULL AFTER `visible_for_levels`");
@@ -37,19 +37,19 @@ class BxForumUpdater extends BxDolStudioUpdater
                 $this->oDb->query("ALTER TABLE `bx_forum_reactions` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
 
             if(!$this->oDb->isFieldExists('bx_forum_meta_keywords', 'id'))
-                $this->oDb->query("ALTER TABLE `bx_ads_meta_keywords` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
+                $this->oDb->query("ALTER TABLE `bx_forum_meta_keywords` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
 
             if(!$this->oDb->isFieldExists('bx_forum_meta_mentions', 'id'))
-                $this->oDb->query("ALTER TABLE `bx_ads_meta_mentions` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
+                $this->oDb->query("ALTER TABLE `bx_forum_meta_mentions` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
 
             if(!$this->oDb->isFieldExists('bx_forum_reports', 'id'))
-                $this->oDb->query("ALTER TABLE `bx_ads_reports` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
+                $this->oDb->query("ALTER TABLE `bx_forum_reports` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
 
             if(!$this->oDb->isFieldExists('bx_forum_favorites_track', 'id'))
-                $this->oDb->query("ALTER TABLE `bx_ads_favorites_track` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
+                $this->oDb->query("ALTER TABLE `bx_forum_favorites_track` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
 
             if(!$this->oDb->isFieldExists('bx_forum_scores', 'id'))
-                $this->oDb->query("ALTER TABLE `bx_ads_scores` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
+                $this->oDb->query("ALTER TABLE `bx_forum_scores` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");
 
             if(!$this->oDb->isFieldExists('bx_forum_polls_answers_votes', 'id'))
                 $this->oDb->query("ALTER TABLE `bx_forum_polls_answers_votes` ADD `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST");

+ 1 - 1
modules/boonex/forum/updates/13.0.0_13.0.1/install/sql/enable.sql

@@ -65,7 +65,7 @@ UPDATE `sys_menu_items` SET `name`='profile-stats-my-forum', `link`='page.php?i=
 -- GRIDS
 UPDATE `sys_grid_fields` SET `width`='75%' WHERE `object`=@sName AND `name`='text';
 
-DELETE FROM `sys_grid_fields` WHERE `object`=@sName AND `name` IN ('category', 'participants', 'rating');
+DELETE FROM `sys_grid_fields` WHERE `object`=@sName AND `name` IN ('author', 'category', 'participants', 'rating');
 INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `params`, `order`) VALUES
 (@sName, 'category', '', '10%', '', 1),
 (@sName, 'participants', '', '10%', '', 3),

+ 1 - 0
modules/boonex/groups/updates/13.0.0_13.0.1/install/langs/en.xml

@@ -5,4 +5,5 @@
 	<string_add name="_bx_groups_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string_add>
 	<string_add name="_bx_groups_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string_add>
 	<string_add name="_bx_groups_txt_status_hidden"><![CDATA[This group was hidden.]]></string_add>
+	<string_add name="_bx_groups_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string_add>
 </resources>

+ 1 - 0
modules/boonex/groups/updates/13.0.0_13.0.1/source/classes/BxGroupsConfig.php

@@ -246,6 +246,7 @@ class BxGroupsConfig extends BxBaseModGroupsConfig
                 'txt_n_unit' => '_bx_groups_txt_n_unit',
                 'txt_buy_title' => '_bx_groups_grid_action_title_buy_title',
                 'txt_cart_item_title' => '_bx_groups_txt_cart_item_title',
+                'txt_cart_item_title_lifetime' => '_bx_groups_txt_cart_item_title_lifetime',
                 'txt_subscribe_title' => '_bx_groups_grid_action_title_subscribe_title',
                 'popup_title_price_add' => '_bx_groups_popup_title_price_add',
                 'popup_title_price_edit' => '_bx_groups_popup_title_price_edit',

+ 1 - 0
modules/boonex/groups/updates/13.0.0_13.0.1/source/install/langs/en.xml

@@ -376,6 +376,7 @@
     <string name="_bx_groups_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Sorry, an error has occurred. You can not join this group. This invitation cannot be used by you.]]></string>
     <string name="_bx_groups_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_groups_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_groups_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string>    
 
     <string name="_bx_groups_msg_performed"><![CDATA[The action was successfully performed.]]></string>
     <string name="_bx_groups_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>

+ 133 - 0
modules/boonex/invites/updates/12.0.0_13.0.0/source/classes/BxInvResponse.php

@@ -0,0 +1,133 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Invites Invites
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxInvResponse extends BxDolAlertsResponse
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->_sModule = 'bx_invites';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+    }
+
+    /**
+     * Overwtire the method of parent class.
+     *
+     * @param BxDolAlerts $oAlert an instance of alert.
+     */
+    public function response($oAlert)
+    {
+        $sMethod = '_process' . bx_gen_method_name($oAlert->sUnit . '_' . $oAlert->sAction);
+        if(!method_exists($this, $sMethod))
+            return;
+
+        return $this->$sMethod($oAlert);
+    }
+
+    protected function _processAccountAddForm($oAlert)
+    {
+        $sCode = $this->_oModule->serviceAccountAddFormCheck();
+        if($sCode)
+            $oAlert->aExtras['form_code'] = $sCode;
+    }
+
+    protected function _processAccountAdded($oAlert)
+    {
+        $sKeyCode = $this->_oModule->_oConfig->getKeyCode();
+        $sKey = BxDolSession::getInstance()->getUnsetValue($sKeyCode);
+        if($sKey === false)
+            return;
+        $this->_oModule->attachAccountIdToInvite($oAlert->iObject, $sKey);
+        
+        $sKeysToRemove = $this->_oModule->_oDb->getInvites(array('type' => 'invites_code_by_single', 'value' => $sKey));
+        $aKeysToRemove = explode(',', $sKeysToRemove);
+        $oKeys = BxDolKey::getInstance();
+        if($oKeys){
+            foreach($aKeysToRemove as $sKeyToRemove) {
+                if($oKeys->isKeyExists($sKeyToRemove))
+                    $oKeys->removeKey($sKeyToRemove);
+            }
+        }
+        
+        return;
+    }
+
+    protected function _processProfileAdd($oAlert)
+    {
+        if (getParam('bx_invites_automatically_befriend') != 'on')
+            return;
+        
+		$bNeedToFriend = true;
+		bx_alert($this->_sModule, 'add_friend', 0, 0, [
+			'profile_id' => $oAlert->iObject,
+			'override_result' => &$bNeedToFriend,
+		]);
+		
+		if ($bNeedToFriend){
+			$oProfile = BxDolProfile::getInstanceMagic($oAlert->iObject);
+			if ($oProfile && $oProfile->isActAsProfile()){
+				$iProfileInvitor = $this->_oModule->_oDb->getInvites(array('type' => 'profile_id_by_joined_account_id', 'value' => $oProfile->getAccountId()));
+				if ($iProfileInvitor){
+					$oConnFrinds = BxDolConnection::getObjectInstance('sys_profiles_friends');
+					$oConnFrinds->addConnection($oAlert->iObject, $iProfileInvitor);
+					$oConnFrinds->addConnection($iProfileInvitor, $oAlert->iObject);
+				}
+			}  
+		}
+    }
+    
+    protected function _processProfileDelete($oAlert)
+    {
+        $this->_oModule->_oDb->deleteInvites(array('profile_id' => $oAlert->iObject));
+    }
+    
+    protected function _processAccountDelete($oAlert)
+    {
+        $this->_oModule->_oDb->deleteInvitesByAccount(array('joined_account_id' => $oAlert->iObject));
+    }
+    
+    protected function _processBxAnalyticsGetModules($oAlert)
+    {
+        $oAlert->aExtras['list'][$this->_oModule->_aModule['name']] = $this->_oModule->_aModule['title'];
+    }
+    
+    protected function _processBxAnalyticsGetReports($oAlert)
+    {
+        if ($this->_oModule->_aModule['name'] == $oAlert->aExtras['module']){
+            $oAlert->aExtras['list'] = array();
+            $oAlert->aExtras['list']['content_total_invited'] = _t('_bx_invites_reports_for_analytics_invited_total');
+            $oAlert->aExtras['list']['content_speed_invited'] = _t('_bx_invites_reports_for_analytics_invited_speed_grows');
+            $oAlert->aExtras['list']['content_total_invitation'] = _t('_bx_invites_reports_for_analytics_invitation_total');
+            $oAlert->aExtras['list']['content_speed_invitation'] = _t('_bx_invites_reports_for_analytics_invitation_speed_grows');
+        }
+    }
+    
+    protected function _processBxAnalyticsGetChartDataLine($oAlert)
+    {
+        if ($this->_oModule->_aModule['name'] == $oAlert->aExtras['module']){
+            $bIsInvited = false; 
+            if (substr_count($oAlert->aExtras['report_name'], '_invited') > 0)
+                $bIsInvited = true; 
+            
+            if (substr_count($oAlert->aExtras['report_name'], '_total_') > 0)
+                $oAlert->aExtras['report_type'] = BX_ANALYTICS_CONTENT_TOTAL;
+            
+            $oAlert->aExtras['data'] = $this->_oModule->_oDb->getDataForCharts($oAlert->aExtras['date_from'], $oAlert->aExtras['date_to'], $bIsInvited);
+            $oAlert->aExtras['prev_value'] = $this->_oModule->_oDb->getInitValueForCharts($oAlert->aExtras['date_from'], $bIsInvited);
+        }
+    }
+}
+
+/** @} */

+ 1 - 0
modules/boonex/organizations/updates/13.0.0_13.0.1/install/langs/en.xml

@@ -5,4 +5,5 @@
 	<string_add name="_bx_orgs_page_block_title_sys_familiar_profiles"><![CDATA[Familiar Organizations]]></string_add>
     <string_add name="_bx_orgs_page_block_title_familiar_profiles"><![CDATA[Organizations You May Know]]></string_add>
 	<string_add name="_bx_orgs_txt_ntfs_join_request_for_owner"><![CDATA[<b class="bx-ntfs-content-entry">{subentry_sample}</b> sent a join request to your organization]]></string_add>
+	<string_add name="_bx_orgs_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string_add>
 </resources>

+ 2 - 0
modules/boonex/organizations/updates/13.0.0_13.0.1/install/sql/enable.sql

@@ -7,6 +7,8 @@ INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `t
 
 DELETE FROM `sys_pages_blocks` WHERE `object`='' AND `module`='bx_organizations' AND `title`='_bx_orgs_page_block_title_cover_block';
 
+UPDATE `sys_pages_blocks` SET `content`='a:3:{s:6:\"module\";s:16:\"bx_organizations\";s:6:\"method\";s:22:\"browse_active_profiles\";s:6:\"params\";a:3:{s:9:\"unit_view\";s:12:\"unit_wo_info\";s:13:\"empty_message\";b:0;s:13:\"ajax_paginate\";b:0;}}' WHERE `module`='bx_organizations' AND (`title`='_bx_orgs_page_block_title_active_profiles' OR `title` LIKE '_bx_orgs_page_block_title_active_profiles_%') AND `content` LIKE '%gallery_wo_info%';
+
 DELETE FROM `sys_pages_blocks` WHERE `object`='' AND `module`='bx_organizations' AND `title`='_bx_orgs_page_block_title_familiar_profiles';
 SET @iBlockOrder = (SELECT `order` FROM `sys_pages_blocks` WHERE `object` = '' AND `cell_id` = 0 ORDER BY `order` DESC LIMIT 1);
 INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES

+ 1 - 0
modules/boonex/organizations/updates/13.0.0_13.0.1/source/classes/BxOrgsConfig.php

@@ -276,6 +276,7 @@ class BxOrgsConfig extends BxBaseModGroupsConfig
                 'txt_n_unit' => '_bx_orgs_txt_n_unit',
                 'txt_buy_title' => '_bx_orgs_grid_action_title_buy_title',
                 'txt_cart_item_title' => '_bx_orgs_txt_cart_item_title',
+                'txt_cart_item_title_lifetime' => '_bx_orgs_txt_cart_item_title_lifetime',
                 'txt_subscribe_title' => '_bx_orgs_grid_action_title_subscribe_title',
                 'popup_title_price_add' => '_bx_orgs_popup_title_price_add',
                 'popup_title_price_edit' => '_bx_orgs_popup_title_price_edit',

+ 1 - 0
modules/boonex/organizations/updates/13.0.0_13.0.1/source/install/langs/en.xml

@@ -508,6 +508,7 @@
     <string name="_bx_orgs_ps_group_title_participants"><![CDATA[Employees]]></string>
     <string name="_bx_orgs_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_orgs_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_orgs_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string>    
 
     <string name="_bx_orgs_msg_performed"><![CDATA[The action was successfully performed.]]></string>
     <string name="_bx_orgs_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>

+ 776 - 0
modules/boonex/payment/updates/13.0.0_13.0.1/source/classes/BxPaymentDb.php

@@ -0,0 +1,776 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Payment Payment
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxPaymentDb extends BxBaseModPaymentDb
+{
+    function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+    }
+
+    public function getModulesWithPayments()
+    {
+    	$sQuery = $this->prepare("SELECT `name` FROM `" . $this->_sPrefix . "modules`");
+        return $this->getColumn($sQuery);
+    }
+
+    /**
+     * Payment details methods
+     */
+    public function getForm()
+    {
+        $sQuery = "SELECT
+                `tp`.`id` AS `provider_id`,
+                `tp`.`name` AS `provider_name`,
+                `tp`.`caption` AS `provider_caption`,
+                `tp`.`description` AS `provider_description`,
+                `tp`.`option_prefix` AS `provider_option_prefix`,
+                `tp`.`for_owner_only` AS `provider_for_owner_only`,
+                `tp`.`single_seller` AS `provider_single_seller`,
+                `tpo`.`id` AS `id`,
+                `tpo`.`name` AS `name`,
+                `tpo`.`type` AS `type`,
+                `tpo`.`caption` AS `caption`,
+                `tpo`.`description` AS `description`,
+                `tpo`.`extra` AS `extra`,
+                `tpo`.`check_type` AS `check_type`,
+                `tpo`.`check_params` AS `check_params`,
+                `tpo`.`check_error` AS `check_error`
+            FROM `" . $this->_sPrefix . "providers` AS `tp`
+            LEFT JOIN `" . $this->_sPrefix . "providers_options` AS `tpo` ON `tp`.`id`=`tpo`.`provider_id`
+            WHERE `tp`.`active`='1' 
+            ORDER BY `tp`.`order` ASC, `tpo`.`order` ASC";
+
+        return $this->getAll($sQuery);
+    }
+
+    public function getProviders($aParams = array())
+    {
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sWhereClause = "";
+        if(!empty($aParams['type']))
+	        switch($aParams['type']) {
+                    case 'by_name':
+                        $aMethod['name'] = 'getRow';
+                        $aMethod['params'][1] = array(
+                            'name' => $aParams['name']
+                        );
+
+                        $sWhereClause = " AND `tp`.`name`=:name";
+                        break;
+
+                    case 'for_single':
+                        $aMethod['name'] = 'getAllWithKey';
+                        $aMethod['params'][1] = 'name';
+
+                        $sWhereClause = " AND `tp`.`for_single`='1'";
+                        break;
+
+                    case 'for_recurring':
+                        $aMethod['name'] = 'getAllWithKey';
+                        $aMethod['params'][1] = 'name';
+
+                        $sWhereClause = " AND `tp`.`for_recurring`='1'";
+                        break;
+
+                    case 'all':
+                        $aMethod['name'] = 'getAllWithKey';
+                        $aMethod['params'][1] = 'name';
+
+                        if(!empty($aParams['active'])) 
+                            $sWhereClause = " AND `tp`.`active`='1'";
+                        break;
+	        }          
+
+        $aMethod['params'][0] = "SELECT
+                `tp`.`id` AS `id`,
+                `tp`.`name` AS `name`,
+                `tp`.`caption` AS `caption`,
+                `tp`.`description` AS `description`,
+                `tp`.`option_prefix` AS `option_prefix`,
+                `tp`.`for_visitor` AS `for_visitor`,
+                `tp`.`for_single` AS `for_single`,
+                `tp`.`for_recurring` AS `for_recurring`,
+                `tp`.`class_name` AS `class_name`,
+                `tp`.`class_file` AS `class_file`
+            FROM `" . $this->_sPrefix . "providers` AS `tp`
+            WHERE 1" . $sWhereClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getOption($aParams)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`tpo`.*";
+        $sJoinClause = $sWhereClause = "";
+        if(!empty($aParams['type']))
+	        switch($aParams['type']) {
+                    case 'by_pid_and_name':
+                        $aMethod['name'] = 'getRow';
+                        $aMethod['params'][1] = array(
+                            'provider_id' => $aParams['provider_id'],
+                            'name' => $aParams['name'],
+                        );
+
+                        $sWhereClause = " AND `tpo`.`provider_id`=:provider_id AND `tpo`.`name`=:name";
+                        break;
+                }
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $this->_sPrefix . "providers_options` AS `tpo` " . $sJoinClause . " WHERE 1" . $sWhereClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getOptions($iUserId = BX_PAYMENT_EMPTY_ID, $iProviderId = 0)
+    {
+    	$aBinding = array(
+    		'user_id' => $iUserId
+    	);
+
+        if($iUserId == BX_PAYMENT_EMPTY_ID && empty($iProviderId))
+           return $this->getAll("SELECT `id`, `name`, `type` FROM `" . $this->_sPrefix . "providers_options`");
+
+        $sWhereAddon = "";
+        if(!empty($iProviderId)) {
+        	$aBinding['provider_id'] = $iProviderId;
+
+            $sWhereAddon = " AND `tpo`.`provider_id`=:provider_id";
+        }
+
+        $sQuery = "SELECT
+               `tpo`.`name` AS `name`,
+               `tuv`.`value` AS `value`
+            FROM `" . $this->_sPrefix . "providers_options` AS `tpo`
+            LEFT JOIN `" . $this->_sPrefix . "user_values` AS `tuv` ON `tpo`.`id`=`tuv`.`option_id`
+            WHERE 1" . $sWhereAddon . " AND `tuv`.`user_id`=:user_id ORDER BY `tpo`.`order`";
+
+        return $this->getAllWithKey($sQuery, 'name', $aBinding);
+    }
+
+    public function updateOption($iUserId, $iOptionId, $sValue)
+    {
+        $sQuery = $this->prepare("REPLACE INTO `" . $this->_sPrefix . "user_values` SET `user_id`=?, `option_id`=?, `value`=?", $iUserId, $iOptionId, $sValue);
+        return $this->query($sQuery);
+    }
+
+
+    /**
+     * Shopping cart methods.
+     */
+    public function getCartItems($iId)
+    {
+    	$sQuery = $this->prepare("SELECT `items` FROM `" . $this->_sPrefix . "cart` WHERE `client_id`=? LIMIT 1", $iId);
+        return $this->getOne($sQuery);
+    }
+
+    public function getCartContent($iId)
+    {
+        $aCart = $this->getRow("SELECT * FROM `" . $this->_sPrefix . "cart` WHERE `client_id`=:client_id LIMIT 1", array(
+            'client_id' => $iId
+        ));
+
+        if(empty($aCart) || !is_array($aCart))
+            $aCart = array('items' => '', 'customs' => '');
+
+        return $aCart;
+    }
+
+    public function setCartItems($iId, $sItems, $aCustoms = array())
+    {
+        $sItems = trim($sItems, ":");
+        if(empty($sItems))
+            return $this->query("DELETE FROM `" . $this->_sPrefix . "cart` WHERE `client_id`=:client_id LIMIT 1", array(
+                'client_id' => $iId
+            ));
+
+        return $this->query("REPLACE INTO `" . $this->_sPrefix . "cart` SET `client_id`=:client_id, `items`=:items, `customs`=:customs", array(
+            'client_id' => $iId,
+            'items' => $sItems,
+            'customs' => !empty($aCustoms) && is_array($aCustoms) ? serialize($aCustoms) : ''
+        ));
+    }
+
+	public function getVendorInfoProvidersSingle($iVendorId)
+    {
+    	return $this->getVendorInfoProviders($iVendorId, array('type' => 'for_single'));
+    }
+
+	public function getVendorInfoProvidersRecurring($iVendorId)
+    {
+    	return $this->getVendorInfoProviders($iVendorId, array('type' => 'for_recurring'));
+    }
+
+    public function getVendorInfoProviders($iVendorId, $aParams = array())
+    {
+    	if(empty($aParams))
+    		$aParams = array('type' => 'all');
+
+		$aProviders = $this->getProviders($aParams);
+		$aOptions = $this->getOptions($iVendorId);
+
+		$aResult = array();
+		foreach($aProviders as $sProvider => $aProvider) {
+			if(!isset($aOptions[$aProvider['option_prefix'] . 'active']) || $aOptions[$aProvider['option_prefix'] . 'active']['value'] != 'on') 
+                            continue;
+
+                        if(isset($aOptions[$aProvider['option_prefix'] . 'hidden']) && $aOptions[$aProvider['option_prefix'] . 'hidden']['value'] == 'on') 
+                            continue;
+
+                        foreach($aOptions as $sName => $aOption)
+                                if(strpos($sName, $aProvider['option_prefix']) !== false)
+                                        $aProvider['options'][$sName] = $aOption;
+                        $aResult[$sProvider] = $aProvider;
+                }
+
+		return $aResult;
+	}
+
+    public function getAdminsIds()
+    {
+        $sQuery = "SELECT 
+        		`tp`.`id` AS `id`,
+        		`tp`.`type` AS `type`  
+        	FROM `sys_profiles` AS `tp` 
+        	INNER JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` AND `ta`.`role`&" . BX_DOL_ROLE_ADMIN . " 
+        	WHERE 
+        		`tp`.`type`<>'system' AND `tp`.`status`='active' 
+        	ORDER BY `tp`.`id` ASC";
+
+        $aAdmins = $this->getAllWithKey($sQuery, 'id');
+        foreach($aAdmins as $iId => $aAdmin)
+            if(!BxDolService::call($aAdmin['type'], 'act_as_profile'))
+                unset($aAdmins[$iId]);
+
+        return array_keys($aAdmins);
+    }
+
+
+	/*
+     * Pending Orders methods
+     */
+    public function getOrderPending($aParams)
+    {
+    	$aMethod = array('name' => 'getRow', 'params' => array(0 => 'query'));
+
+    	$sWhereClause = $sLimitClause = '';
+        switch($aParams['type']) {
+            case 'id':
+            	$aMethod['params'][1] = array(
+                	'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `id`=:id";
+                $sLimitClause = " LIMIT 1";
+                break;
+
+            case 'order':
+            	$aMethod['params'][1] = array(
+                	'order' => $aParams['order']
+                );
+
+            	$sWhereClause = " AND `order`=:order";
+                $sLimitClause = " LIMIT 1";
+                break;
+
+            case 'mixed':
+                $aMethod['name'] = 'getAll';
+
+                $sWhereClause = ' AND ' . $this->arrayToSQL($aParams['conditions'], ' AND ');
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT * FROM `" . $this->_sPrefix . "transactions_pending` WHERE 1 " . $sWhereClause . $sLimitClause;
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function insertOrderPending($iClientId, $sType, $sProvider, $aCartInfo, $aCustom = array())
+    {
+        $sItems = "";
+        foreach($aCartInfo['items'] as $aItem) {
+            $sItems .= $this->_oConfig->descriptorA2S(array($aItem['author_id'], $aItem['module_id'], $aItem['id'], $aItem['quantity'])) . ':';
+
+            if(empty($aItem['addons']) || !is_array($aItem['addons']))
+                continue;
+
+            foreach($aItem['addons'] as $sAddon => $aAddon)
+                $sItems .= $this->_oConfig->descriptorA2S(array($aAddon['author_id'], $aAddon['module_id'], $aAddon['id'], $aAddon['quantity'])) . ':';
+        }
+
+        return (int)$this->query("INSERT INTO `" . $this->_sPrefix . "transactions_pending` SET `client_id`=:client_id, `seller_id`=:seller_id, `type`=:type, `provider`=:provider, `items`=:items, `customs`=:customs, `amount`=:amount, `date`=UNIX_TIMESTAMP()", array(
+            'client_id' => $iClientId,
+            'seller_id' => $aCartInfo['vendor_id'],
+            'type' => $sType, 
+            'provider' => $sProvider,
+            'items' => trim($sItems, ':'),
+            'customs' => !empty($aCustom) && is_array($aCustom) ? serialize($aCustom) : '',
+            'amount' => $aCartInfo['items_price']
+        )) > 0 ? $this->lastId() : 0;
+    }
+
+    public function updateOrderPending($iId, $aValues)
+    {
+        $sQuery = $this->prepare("UPDATE `" . $this->_sPrefix . "transactions_pending` SET " . $this->arrayToSQL($aValues) . " WHERE `id`=?", $iId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function deleteOrderPending($mixedId)
+    {
+    	if(!is_array($mixedId))
+    		$mixedId = array($mixedId);
+
+        return (int)$this->query("DELETE FROM `" . $this->_sPrefix . "transactions_pending` WHERE `id` IN (" . $this->implode_escape($mixedId) . ")") > 0;
+    }
+
+    /*
+     * Processed Orders
+     */
+    public function getOrderProcessed($aParams)
+    {
+    	$aMethod = array('name' => 'getRow', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`tt`.`id`, `tt`.`license`, `ttp`.`type`, `tt`.`client_id`, `tt`.`seller_id`, `tt`.`author_id`, `tt`.`module_id`, `tt`.`item_id`, `tt`.`item_count`, `tt`.`amount`, `tt`.`date`, `ttp`.`order`, `ttp`.`error_msg`, `ttp`.`provider`";
+        $sWhereClause = $sGroupClause = "";
+
+        switch($aParams['type']) {
+            case 'id':
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `tt`.`id`=:id";
+                break;
+
+            case 'new':
+                $aMethod['name'] = 'getAll';
+                $aMethod['params'][1] = array(
+                    'seller_id' => $aParams['seller_id']
+                );
+
+                $sWhereClause = " AND `tt`.`seller_id`=:seller_id AND `tt`.`new`='1'";
+                break;
+
+            case 'pending_id':
+                if(empty($aParams['with_key'])) {
+                    $aMethod['name'] = 'getAll';
+                    $aMethod['params'][1] = array(
+                        'pending_id' => $aParams['pending_id']
+                    );
+                }
+                else {
+                    $aMethod['name'] = 'getAllWithKey';
+                    $aMethod['params'][1] = $aParams['with_key'];
+                    $aMethod['params'][2] = array(
+                        'pending_id' => $aParams['pending_id']
+                    );
+                }
+
+                $sWhereClause = " AND `tt`.`pending_id`=:pending_id";
+                break;
+
+            case 'clients':
+                $aMethod['name'] = 'getColumn';
+                $aMethod['params'][1] = array(
+                    'seller_id' => $aParams['seller_id']
+                );
+
+                $sSelectClause = "DISTINCT `tt`.`client_id`";
+                $sWhereClause = " AND `tt`.`seller_id`=:seller_id";
+                break;
+
+            case 'authors':
+                $aMethod['name'] = 'getColumn';
+                $aMethod['params'][1] = array(
+                    'seller_id' => $aParams['seller_id']
+                );
+
+                $sSelectClause = "DISTINCT `tt`.`author_id`";
+                $sWhereClause = " AND `tt`.`seller_id`=:seller_id";
+                break;
+
+            case 'license':
+                $aMethod['name'] = 'getAll';
+                $aMethod['params'][1] = array(
+                    'license' => $aParams['license']
+                );
+
+                $sWhereClause = " AND `tt`.`license`=:license";
+                break;
+            
+            case 'income':
+                $aMethod['name'] = 'getAll';
+                $aMethod['params'][1] = array(
+                    'period_start' => $aParams['period_start'],
+                    'period_end' => $aParams['period_end']
+                );
+
+                $sSelectClause = '`tt`.`author_id` AS `id`, SUM(`tt`.`amount`) AS `amount`';
+                $sWhereClause = ' AND `tt`.`date`>=:period_start AND `tt`.`date`<=:period_end';
+                $sGroupClause = '`tt`.`author_id`';
+                break;
+
+            case 'mixed':
+                $aMethod['name'] = 'getAll';
+
+                $sWhereClause = ' AND ' . $this->arrayToSQL($aParams['conditions'], ' AND ');
+                break;
+
+        }
+
+        if(!empty($sGroupClause))
+            $sGroupClause = ' GROUP BY ' . $sGroupClause;
+
+        $aMethod['params'][0] = "SELECT
+                " . $sSelectClause . "
+            FROM `" . $this->_sPrefix . "transactions` AS `tt`
+            LEFT JOIN `" . $this->_sPrefix . "transactions_pending` AS `ttp` ON `tt`.`pending_id`=`ttp`.`id`
+            WHERE 1" . $sWhereClause . $sGroupClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function insertOrderProcessed($aValues)
+    {
+        return $this->query("INSERT INTO `" . $this->_sPrefix . "transactions` SET " . $this->arrayToSQL($aValues) . ", `date`=UNIX_TIMESTAMP()");
+    }
+
+    public function updateOrderProcessed($iId, $aValues)
+    {        
+        return $this->updateOrdersProcessed($aValues, array('id' => $iId));
+    }
+
+    public function updateOrdersProcessed($aValues, $aWhere)
+    {
+        if(empty($aValues) || !is_array($aValues) || empty($aWhere) || !is_array($aWhere))
+            return false;
+
+        return (int)$this->query("UPDATE `" . $this->_sPrefix . "transactions` SET " . $this->arrayToSQL($aValues) . " WHERE " . $this->arrayToSQL($aWhere, ' AND ')) > 0;
+    }
+
+    public function deleteOrderProcessed($mixedId)
+    {
+    	if(!is_array($mixedId))
+            $mixedId = array($mixedId);
+
+        return (int)$this->query("DELETE FROM `" . $this->_sPrefix . "transactions` WHERE `id` IN (" . $this->implode_escape($mixedId) . ")") > 0;
+    }
+
+    public function getOrderHistory($aParams)
+    {
+        return $this->getOrderProcessed($aParams);
+    }
+
+    /*
+     * Subscriptions methods
+     */
+    public function getOrderSubscription($aParams)
+    {
+        return $this->getOrderPending($aParams);
+    }
+
+    public function getSubscription($aParams)
+    {
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`ts`.*";
+    	$sJoinClause = $sWhereClause = $sLimitClause = '';
+        switch($aParams['type']) {
+            case 'pending_id':
+                $aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'pending_id' => $aParams['pending_id']
+                );
+
+                $sWhereClause = " AND `ts`.`pending_id`=:pending_id";
+                $sLimitClause = " LIMIT 1";
+                break;
+
+            case 'mixed':
+                $sWhereClause = " AND " . $this->arrayToSQL($aParams['conditions'], ' AND ');
+                break;
+
+            case 'mixed_ext':
+                $sSelectClause .= ", `ttp`.`client_id`, `ttp`.`seller_id`, `ttp`.`type`, `ttp`.`provider`, `ttp`.`order`";
+                $sJoinClause = "LEFT JOIN `" . $this->_sPrefix . "transactions_pending` AS `ttp` ON `ts`.`pending_id`=`ttp`.`id`";
+                $sWhereClause = " AND " . $this->arrayToSQL($aParams['conditions'], ' AND ');
+                break;
+
+            case 'time_tracker':
+                $aMethod['params'][1] = array(
+                    'status_active' => $aParams['status_active'],
+                    'status_trial' => $aParams['status_trial'],
+                    'status_unpaid' => $aParams['status_unpaid'],
+                    'pay_attempts_max' => $aParams['pay_attempts_max'],
+                    'pay_attempts_interval' => $aParams['pay_attempts_interval']
+                );
+
+                $sSelectClause .= ", `ttp`.`client_id`, `ttp`.`seller_id`, `ttp`.`type`, `ttp`.`provider`, `ttp`.`amount`, `ttp`.`order`";
+                $sJoinClause = "LEFT JOIN `" . $this->_sPrefix . "transactions_pending` AS `ttp` ON `ts`.`pending_id`=`ttp`.`id` INNER JOIN `" . $this->_sPrefix . "providers` AS `tp` ON `ttp`.`provider`=`tp`.`name` AND `tp`.`time_tracker`='1'";
+                $sWhereClause = " AND `ts`.`date_next`<>0 AND `ts`.`date_next`<=UNIX_TIMESTAMP() AND (`ts`.`status`=:status_active OR `ts`.`status`=:status_trial OR (`ts`.`status`=:status_unpaid AND `ts`.`pay_attempts`<:pay_attempts_max AND DATE_ADD(FROM_UNIXTIME(`ts`.`date_next`), INTERVAL `ts`.`pay_attempts`*:pay_attempts_interval SECOND)<=NOW()))";
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $this->_sPrefix . "subscriptions` AS `ts` " . $sJoinClause . " WHERE 1 " . $sWhereClause . $sLimitClause;
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function isSubscriptionByPending($iPending)
+    {
+    	$aSubscription = $this->getSubscription(array(
+            'type' => 'pending_id', 
+            'pending_id' => $iPending
+    	));
+
+    	return !empty($aSubscription) && is_array($aSubscription);
+    }
+
+    public function insertSubscription($aValues)
+    {
+        if(empty($aValues) || !is_array($aValues))
+            return false;
+
+        $sSetClause = $this->arrayToSQL($aValues);
+        if(empty($aValues['date_add']))
+            $sSetClause .= ", `date_add`=UNIX_TIMESTAMP()";
+
+        return (int)$this->query("INSERT INTO `" . $this->_sPrefix . "subscriptions` SET " . $sSetClause) > 0;
+    }
+
+    public function updateSubscription($aValues, $aWhere)
+    {
+        if(empty($aValues) || !is_array($aValues) || empty($aWhere) || !is_array($aWhere))
+            return false;
+
+        return (int)$this->query("UPDATE `" . $this->_sPrefix . "subscriptions` SET " . $this->arrayToSQL($aValues) . " WHERE " . $this->arrayToSQL($aWhere, ' AND ')) > 0;
+    }
+
+    public function deleteSubscription($mixedId, $sReason)
+    {
+    	if(!is_array($mixedId))
+            $mixedId = array($mixedId);
+
+        //--- Move to deleted subscriptions table.   
+    	$sQuery = "INSERT IGNORE INTO `" . $this->_sPrefix . "subscriptions_deleted` SELECT *, :reason AS `reason`, UNIX_TIMESTAMP() AS `deleted` FROM `" . $this->_sPrefix . "subscriptions` WHERE `id` IN (" . $this->implode_escape($mixedId) . ")";
+        $this->query($sQuery, array(
+            'reason' => $sReason
+        ));
+
+        return (int)$this->query("DELETE FROM `" . $this->_sPrefix . "subscriptions` WHERE `id` IN (" . $this->implode_escape($mixedId) . ")") > 0;
+    }
+
+    //--- Order Administration ---//
+    public function onProfileDelete($iId)
+    {
+    	$sQuery = $this->prepare("DELETE FROM `" . $this->_sPrefix . "cart` WHERE `client_id`=?", $iId);
+    	$this->query($sQuery);
+
+    	$sQuery = $this->prepare("DELETE FROM `" . $this->_sPrefix . "user_values` WHERE `user_id`=?", $iId);
+    	$this->query($sQuery);
+    }
+
+
+    /*
+     * Commissions methods
+     */
+    public function getCommissions($aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`tc`.*";
+        $sWhereClause = $sOrderClause = "";
+        if(!empty($aParams['type']))
+            switch($aParams['type']) {
+                case 'max_order':
+                    $aMethod['name'] = 'getOne';
+                    $aMethod['params'][1] = array();
+
+                    $sSelectClause = "IFNULL(MAX(`tc`.`order`), 0)";
+                    break;
+
+                case 'id':
+                    $aMethod['name'] = 'getRow';
+                    $aMethod['params'][1] = array(
+                        'id' => $aParams['id']
+                    );
+
+                    $sWhereClause = " AND `tc`.`id`=:id";
+                    break;
+
+                case 'acl_id':
+                    $aMethod['name'] = 'getAll';
+                    $aMethod['params'][1] = array(
+                        'acl_id' => $aParams['acl_id']
+                    );
+
+                    $sWhereClause = " AND (`tc`.`acl_id`=:acl_id OR `tc`.`acl_id`=0)";
+                    $sOrderClause = "`tc`.`order` ASC";
+                    break;
+
+                case 'all':
+                    if(!empty($aParams['active'])) 
+                        $sWhereClause = " AND `tc`.`active`='1'";
+                    break;
+            }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = " ORDER BY " . $sOrderClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . "
+            FROM `" . $CNF['TABLE_COMMISSIONS'] . "` AS `tc`
+            WHERE 1" . $sWhereClause . $sOrderClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getInvoices($aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`ti`.*";
+        $sWhereClause = "";
+        if(!empty($aParams['type']))
+            switch($aParams['type']) {
+                case 'index':
+                    $aMethod['name'] = 'getOne';
+                    $aMethod['params'][1] = array(
+                        'commissionaire_id' => $aParams['commissionaire_id'],
+                        'committent_id' => $aParams['committent_id'],
+                    );
+
+                    $sSelectClause = "COUNT(`ti`.`id`) + 1";
+                    $sWhereClause = " AND `ti`.`commissionaire_id`=:commissionaire_id AND `ti`.`committent_id`=:committent_id";
+                    break;
+
+                case 'id':
+                    $aMethod['name'] = 'getRow';
+                    $aMethod['params'][1] = array(
+                        'id' => $aParams['id']
+                    );
+
+                    $sWhereClause = " AND `ti`.`id`=:id";
+                    break;
+
+                case 'commissionaire_id':
+                    $aMethod['params'][1] = array(
+                        'commissionaire_id' => $aParams['commissionaire_id']
+                    );
+
+                    $sWhereClause = " AND `ti`.`commissionaire_id`=:commissionaire_id";
+                    break;
+
+                case 'committent_id':
+                    $aMethod['params'][1] = array(
+                        'committent_id' => $aParams['committent_id']
+                    );
+
+                    $sWhereClause = " AND `ti`.`committent_id`=:committent_id";
+
+                    if(!empty($aParams['period_start'])) {
+                        $aMethod['params'][1]['period_start'] = (int)$aParams['period_start'];
+
+                        $sWhereClause .= " AND `ti`.`period_start`=:period_start";
+                    }
+
+                    if(!empty($aParams['period_end'])) {
+                        $aMethod['params'][1]['period_end'] = (int)$aParams['period_end'];
+
+                        $sWhereClause .= " AND `ti`.`period_end`=:period_end";
+                    }
+                    break;
+
+                case 'expiring':
+                    $aMethod['params'][1] = array(
+                        'notify_days' => $this->_oConfig->getInvoiceExpirationNotify(),
+                        'status' => BX_PAYMENT_INV_STATUS_UNPAID
+                    );
+
+                    $sWhereClause = " AND DATE_SUB(FROM_UNIXTIME(`ti`.`date_due`), INTERVAL :notify_days DAY) < NOW() AND DATE_SUB(FROM_UNIXTIME(`ti`.`date_due`), INTERVAL (:notify_days - 1) DAY) > NOW() AND `ti`.`status`=:status";
+                    break;
+
+                case 'overdue':
+                    $aMethod['params'][1] = array(
+                        'status_unpaid' => BX_PAYMENT_INV_STATUS_UNPAID,
+                        'status_overdue' => BX_PAYMENT_INV_STATUS_OVERDUE
+                    );
+
+                    $sWhereClause = " AND `ti`.`date_due` < UNIX_TIMESTAMP() AND (`ti`.`status`=:status_unpaid OR (`ti`.`status`=:status_overdue AND `ti`.`ntf_due`='0'))";
+                    break;
+
+                case 'status':
+                    $aMethod['params'][1] = array(
+                        'status' => $aParams['status']
+                    );
+
+                    $sWhereClause = " AND `ti`.`status`=:status";
+                    
+                    if(isset($aParams['count']) && $aParams['count'] === true) {
+                        $sSelectClause = "COUNT(`ti`.`id`)";
+                        $aMethod['name'] = 'getOne';
+                    }
+
+                    if(isset($aParams['committent_id'])) {
+                        $aMethod['params'][1]['committent_id'] = $aParams['committent_id'];
+
+                        $sWhereClause .= " AND `ti`.`committent_id`=:committent_id";
+                    }
+                    break;
+
+                case 'all_count':
+                    $sSelectClause = "COUNT(`ti`.`id`)";
+                    $aMethod['name'] = 'getOne';
+                    break;
+                
+                case 'all':
+                    break;
+            }
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . "
+            FROM `" . $CNF['TABLE_INVOICES'] . "` AS `ti`
+            WHERE 1" . $sWhereClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function insertInvoice($aValues)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->query("INSERT INTO `" . $CNF['TABLE_INVOICES'] . "` SET " . $this->arrayToSQL($aValues));
+    }
+
+    public function updateInvoice($mixedId, $aValues)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        if(!is_array($mixedId))
+            $mixedId = array($mixedId);
+
+        return (int)$this->query("UPDATE `" . $CNF['TABLE_INVOICES'] . "` SET " . $this->arrayToSQL($aValues) . " WHERE `id` IN (" . $this->implode_escape($mixedId) . ')') > 0;
+    }
+
+    public function deleteInvoice($mixedId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	if(!is_array($mixedId))
+            $mixedId = array($mixedId);
+
+        return (int)$this->query("DELETE FROM `" . $CNF['TABLE_INVOICES'] . "` WHERE `id` IN (" . $this->implode_escape($mixedId) . ")") > 0;
+    }
+}
+
+/** @} */

+ 114 - 0
modules/boonex/payment/updates/13.0.0_13.0.1/source/classes/BxPaymentGridSbsAdministration.php

@@ -0,0 +1,114 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Payment Payment
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+
+class BxPaymentGridSbsAdministration extends BxBaseModPaymentGridOrders
+{
+    protected $_iNow;
+    protected $_sManageType;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+    	$this->MODULE = 'bx_payment';
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_iNow = time();
+        $this->_sOrdersType = BX_PAYMENT_ORDERS_TYPE_SUBSCRIPTION;
+        $this->_sManageType = 'administration';
+    }
+
+    public function performActionCancel()
+    {
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) 
+            return echoJson(array());
+
+        $oSubscriptions = $this->_oModule->getObjectSubscriptions();
+
+        $iAffected = 0;
+        $aAffected = array();
+        foreach($aIds as $iId)
+            if($oSubscriptions->cancel($iId)) {
+                $aAffected[] = $iId;
+            	$iAffected++;
+            }
+
+        echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aAffected) : array('msg' => _t($this->_sLangsPrefix . 'err_cannot_perform')));
+    }
+
+    protected function _getCellProvider($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(_t('_bx_payment_txt_name_' . $mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellSubscriptionId($mixedValue, $sKey, $aField, $aRow)
+    {
+        $aItems = $this->_oModule->_oConfig->descriptorsM2A($aRow['items']);
+        if(!is_array($aItems) || count($aItems) != 1)
+            return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+
+        $aItem = array_shift($aItems);
+        $aInfo = BxDolService::call((int)$aItem['module_id'], 'get_payment_data');
+        if(!empty($aInfo['url_browse_order_' . $this->_sManageType]))
+            $mixedValue = $this->_oModule->_oTemplate->displayLink('link', array(
+                'href' => bx_replace_markers($aInfo['url_browse_order_' . $this->_sManageType], array(
+                    'order' => $mixedValue
+                )),
+                'title' => bx_html_attribute($mixedValue),
+                'content' => $mixedValue
+            ));
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellDateAdd($mixedValue, $sKey, $aField, $aRow)
+    {
+        return $this->_getCellDefaultDateTime($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellDateNext($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(empty($mixedValue))
+            return parent::_getCellDefault(_t('_never'), $sKey, $aField, $aRow);
+        else if(is_numeric($mixedValue) && (int)$mixedValue < $this->_iNow)
+            return parent::_getCellDefault(_t('_uknown'), $sKey, $aField, $aRow);
+
+        return $this->_getCellDefaultDateTime($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellStatus($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(_t('_bx_payment_txt_status_' . $mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+
+        $this->_oTemplate->addJsTranslation(array(
+            '_bx_payment_msg_confirm_cancelation',
+            '_bx_payment_txt_unsubscribe_yes',
+            '_bx_payment_txt_unsubscribe_no'
+        ));
+    }
+
+    protected function _delete ($mixedId)
+    {
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $mixedId));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return false;
+
+        return $this->_oModule->_oDb->deleteSubscription($aSubscription['id'], 'delete');
+    }
+}
+
+/** @} */

+ 549 - 0
modules/boonex/payment/updates/13.0.0_13.0.1/source/classes/BxPaymentSubscriptions.php

@@ -0,0 +1,549 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Payment Payment
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxPaymentSubscriptions extends BxBaseModPaymentSubscriptions
+{
+    function __construct()
+    {
+    	$this->MODULE = 'bx_payment';
+
+    	parent::__construct();
+    }
+
+    /*
+     * Service methods
+     */
+    
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-page_blocks Page Blocks
+     * @subsubsection bx_payment-get_block_list_my get_block_list_my
+     * 
+     * @code bx_srv('bx_payment', 'get_block_list_my', [...], 'Subscriptions'); @endcode
+     * 
+     * Get page block with a list of currently logged in member's subscriptions.
+     *
+     * @return an array describing a block to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
+     * 
+     * @see BxPaymentSubscriptions::serviceGetBlockListMy
+     */
+    /** 
+     * @ref bx_payment-get_block_list_my "get_block_list_my"
+     */
+    public function serviceGetBlockListMy()
+    {
+        return $this->_getBlock('list_my');
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-page_blocks Page Blocks
+     * @subsubsection bx_payment-get_block_list_all get_block_list_all
+     * 
+     * @code bx_srv('bx_payment', 'get_block_list_all', [...], 'Subscriptions'); @endcode
+     * 
+     * Get page block with a list of all subscriptions. It's available for authorized members only.
+     *
+     * @return an array describing a block to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
+     * 
+     * @see BxPaymentSubscriptions::serviceGetBlockListAll
+     */
+    /** 
+     * @ref bx_payment-get_block_list_all "get_block_list_all"
+     */
+    public function serviceGetBlockListAll()
+    {
+        return $this->_getBlock('list_all');
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-page_blocks Page Blocks
+     * @subsubsection bx_payment-get_block_history get_block_history
+     * 
+     * @code bx_srv('bx_payment', 'get_block_history', [...], 'Subscriptions'); @endcode
+     * 
+     * Get page block with a list of payments related to subscriptions of currently logged in member.
+     *
+     * @return an array describing a block to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
+     * 
+     * @see BxPaymentSubscriptions::serviceGetBlockHistory
+     */
+    /** 
+     * @ref bx_payment-get_block_history "get_block_history"
+     */
+    public function serviceGetBlockHistory()
+    {
+        return $this->_getBlock('history');
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-other Other
+     * @subsubsection bx_payment-get_subscription_orders_info get_subscription_orders_info
+     * 
+     * @code bx_srv('bx_payment', 'get_subscription_orders_info', [...], 'Subscriptions'); @endcode
+     * 
+     * Get subscription transaction(s) which meets all requirements.
+     *
+     * @param $aConditions an array of pears('key' => 'value'). The most useful keys are the following:
+     * a. client_id - client's ID (integer)
+     * b. seller_id - seller's ID (integer)
+     * c. type - transaction type: single or recurring (string)
+     * d. amount - transaction amount (float)
+     * e. order - order ID received from payment provider (string)
+     * f. provider - payment provider name (string)
+     * g. date - the date when the payment was established(UNIXTIME STAMP)
+     * h. processed - whether the payment was processed or not (integer, 0 or 1)
+     * @return an array of transactions. Each transaction has full info(client ID, seller ID, external transaction ID, date and so on)
+     * 
+     * @see BxPaymentSubscriptions::serviceGetSubscriptionOrdersInfo
+     */
+    /** 
+     * @ref bx_payment-get_subscription_orders_info "get_subscription_orders_info"
+     */
+    public function serviceGetSubscriptionOrdersInfo($aConditions)
+    {
+        if(empty($aConditions) || !is_array($aConditions))
+            return array();
+
+        return $this->_oModule->_oDb->getOrderSubscription(array('type' => 'mixed', 'conditions' => $aConditions));
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-other Other
+     * @subsubsection bx_payment-get_subscriptions_info get_subscriptions_info
+     * 
+     * @code bx_srv('bx_payment', 'get_subscriptions_info', [...], 'Subscriptions'); @endcode
+     * 
+     * Get subscription(s) which meets all requirements.
+     *
+     * @param $aConditions an array of pears('key' => 'value'). The most useful keys are the following:
+     * a. pending_id - pending transaction ID (integer)
+     * b. customer_id - customer ID from payment provider (string)
+     * c. subscription_id - subscription ID from payment provider (string)
+     * d. paid - flag determining whether the subscription paid or not (integer)
+     * e. date - the date when the subscription was established(UNIXTIME STAMP)
+     * @param $bCheckInProvider boolean value determining whether the subscription should be checked in associated payment provider.
+     * @return an array of subscriptions. Each subscription has full info(pending ID, customer ID, subscription ID and so on)
+     * 
+     * @see BxPaymentSubscriptions::serviceGetSubscriptionsInfo
+     */
+    /** 
+     * @ref bx_payment-get_subscriptions_info "get_subscriptions_info"
+     */
+    public function serviceGetSubscriptionsInfo($aConditions, $bCheckInProvider = false)
+    {
+        if(empty($aConditions) || !is_array($aConditions))
+            return array();
+
+        $aSubscriptions = $this->_oModule->_oDb->getSubscription(array('type' => 'mixed_ext', 'conditions' => $aConditions));
+        if(empty($aSubscriptions) || !is_array($aSubscriptions) || !$bCheckInProvider)
+            return $aSubscriptions;
+
+        foreach($aSubscriptions as $iKey => $aSubscription) {
+            if(empty($aSubscription['provider']) || $aSubscription['provider'] == 'manual')
+                continue;
+
+            $oProvider = $this->_oModule->getObjectProvider($aSubscription['provider'], $aSubscription['seller_id']);
+            if(!$oProvider)
+                continue;
+
+            $aSubscriptions[$iKey]['data'] = $oProvider->getSubscription($aSubscription['pending_id'], $aSubscription['customer_id'], $aSubscription['subscription_id']);
+        }
+
+        return $aSubscriptions;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-purchase_processing Purchase Processing
+     * @subsubsection bx_payment-subscribe subscribe
+     * 
+     * @code bx_srv('bx_payment', 'subscribe', [...], 'Subscriptions'); @endcode
+     * 
+     * Initialize subscription for specified item.
+     *
+     * @param $iSellerId integer value with seller ID.
+     * @param $sSellerProvider string value with a name of payment provider to be used for processing. Empty value means that payment provider selector should be shown.
+     * @param $iModuleId integer value with module ID.
+     * @param $iItemId integer value with item ID.
+     * @param $iItemCount integer value with a number of items for purchasing. It's equal to 1 in case of subscription.
+     * @param $sRedirect (optional) string value with redirect URL, if it's needed. 
+     * @return an array with special format which describes the result of operation.
+     * 
+     * @see BxPaymentSubscriptions::serviceSubscribe
+     */
+    /** 
+     * @ref bx_payment-subscribe "subscribe"
+     */
+    public function serviceSubscribe($iSellerId, $sSellerProvider, $iModuleId, $iItemId, $iItemCount, $sRedirect = '', $aCustom = array())
+    {
+        return $this->serviceSubscribeWithAddons($iSellerId, $sSellerProvider, $iModuleId, $iItemId, $iItemCount, '', $sRedirect, $aCustom);
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-purchase_processing Purchase Processing
+     * @subsubsection bx_payment-subscribe_with_addons subscribe_with_addons
+     * 
+     * @code bx_srv('bx_payment', 'subscribe_with_addons', [...], 'Subscriptions'); @endcode
+     * 
+     * Initialize subscription for specified item.
+     *
+     * @param $iSellerId integer value with seller ID.
+     * @param $sSellerProvider string value with a name of payment provider to be used for processing. Empty value means that payment provider selector should be shown.
+     * @param $iModuleId integer value with module ID.
+     * @param $iItemId integer value with item ID.
+     * @param $iItemCount integer value with a number of items for purchasing. It's equal to 1 in case of subscription.
+     * @param $sItemAddons (optional) string with attached addons.
+     * @param $sRedirect (optional) string value with redirect URL, if it's needed. 
+     * @return an array with special format which describes the result of operation.
+     * 
+     * @see BxPaymentSubscriptions::serviceSubscribeWithAddons
+     */
+    /** 
+     * @ref bx_payment-subscribe_with_addons "subscribe_with_addons"
+     */
+    public function serviceSubscribeWithAddons($iSellerId, $sSellerProvider, $mixedModuleId, $iItemId, $iItemCount, $sItemAddons = '', $sRedirect = '', $aCustom = array())
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        $iModuleId = $this->_oModule->_oConfig->getModuleId($mixedModuleId);
+    	$iClientId = $this->_oModule->getProfileId();
+
+    	$mixedResult = $this->_oModule->checkData($iClientId, $iSellerId, $iModuleId, $iItemId, $iItemCount);
+    	if($mixedResult !== true)
+            return $mixedResult;
+
+        $aSellerProviders = $this->_oModule->_oDb->getVendorInfoProvidersRecurring($iSellerId);
+        if(empty($aSellerProviders))
+            return array('code' => 5, 'message' => _t($CNF['T']['ERR_NOT_ACCEPT_PAYMENTS']));
+
+        $aCartItem = array($iSellerId, $iModuleId, $iItemId, $iItemCount, $sItemAddons);
+
+        if(empty($sSellerProvider)) {
+            $sId = $this->_oModule->_oConfig->getHtmlIds('cart', 'providers_select') . BX_PAYMENT_TYPE_RECURRING;
+            $sTitle = _t($CNF['T']['POPUP_PROVIDERS_SELECT']);
+            return array('popup' => array(
+                'html' => BxTemplFunctions::getInstance()->popupBox($sId, $sTitle, $this->_oModule->_oTemplate->displayProvidersSelector($aCartItem, $aSellerProviders, $sRedirect, $aCustom)), 
+                'options' => array('closeOnOuterClick' => true)
+            ));
+        }
+
+        $aCustoms = array();
+        $this->_oModule->_oConfig->putCustom($aCartItem, $aCustom, $aCustoms);
+
+        $sCartItem = $this->_oModule->_oConfig->descriptorA2S($aCartItem);
+        $mixedResult = $this->_oModule->serviceInitializeCheckout(BX_PAYMENT_TYPE_RECURRING, $iSellerId, $aSellerProviders[$sSellerProvider]['name'], array($sCartItem), $sRedirect, $aCustoms);
+        if(is_string($mixedResult))
+            return array('code' => 6, 'message' => _t($mixedResult));
+
+        return $mixedResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-purchase_processing Purchase Processing
+     * @subsubsection bx_payment-send_subscription_expiration_letters send_subscription_expiration_letters
+     * 
+     * @code bx_srv('bx_payment', 'send_subscription_expiration_letters', [...], 'Subscriptions'); @endcode
+     * 
+     * Send subscription expiration letters.
+     *
+     * @param $iPendingId integer value with pending transaction ID.
+     * @param $sOrderId string value with order ID.
+     * 
+     * @see BxPaymentSubscriptions::serviceSendSubscriptionExpirationLetters
+     */
+    /** 
+     * @ref bx_payment-send_subscription_expiration_letters "send_subscription_expiration_letters"
+     */
+    public function serviceSendSubscriptionExpirationLetters($iPendingId, $sOrderId)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+        $sPrefix = $this->_oModule->_oConfig->getPrefix('general');
+
+        $aSubscription = $this->serviceGetSubscriptionsInfo(array('subscription_id' => $sOrderId));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return;
+
+        $aSubscription = array_shift($aSubscription);
+
+        $oSeller = BxDolProfile::getInstance((int)$aSubscription['seller_id']);
+        $oClient = BxDolProfile::getInstance((int)$aSubscription['client_id']);
+
+        $aEtParams = array(
+            'sibscription_id' => $aSubscription['subscription_id'],
+            'sibscription_customer' => $aSubscription['customer_id'],
+            'sibscription_date' => bx_time_js($aSubscription['date_add'], BX_FORMAT_DATE, true)
+        );
+
+        /**
+         * Notify seller.
+         */
+        if($oSeller !== false) {
+            $sEmail = '';
+            $oProvider = $this->_oModule->getObjectProvider($aSubscription['provider'], $aSubscription['seller_id']);
+            if($oProvider !== false && $oProvider->isActive())
+                $sEmail = $oProvider->getOption('expiration_email');
+
+            if(empty($sEmail))
+                $sEmail = $oSeller->getAccountObject()->getEmail();
+
+            $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate($sPrefix . 'expiration_notification_seller', $aEtParams, 0, (int)$aSubscription['client_id']);
+
+            sendMail($sEmail, $aTemplate['Subject'], $aTemplate['Body'], 0, array(), BX_EMAIL_SYSTEM);
+        }
+
+        /**
+         * Notify client.
+         */
+        if($oClient !== false) {
+            $sEmail = $oClient->getAccountObject()->getEmail();
+            $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate($sPrefix . 'expiration_notification_client', $aEtParams, 0, (int)$aSubscription['seller_id']);
+
+            sendMail($sEmail, $aTemplate['Subject'], $aTemplate['Body'], 0, array(), BX_EMAIL_SYSTEM);
+        }
+    }
+
+    public function register($aPending, $aParams = array())
+    {
+        if($this->_oModule->_oDb->isSubscriptionByPending($aPending['id']) || empty($aPending['items']))
+            return false;
+
+        $aInfo = $this->_oModule->getObjectCart()->getInfo(BX_PAYMENT_TYPE_RECURRING, $aPending['client_id'], $aPending['seller_id'], $aPending['items']);
+        if(empty($aInfo['items']) || !is_array($aInfo['items']))
+            return false;
+
+        $aItem = array_shift($aInfo['items']);
+
+        $iPeriod = (int)$aItem['period_recurring'];
+        $iTrial = (int)$aItem['trial_recurring'];
+
+        $oDate = date_create();
+        $iNow = $iNext = date_format($oDate, 'U');
+
+        if((!empty($aParams['status']) && $aParams['status'] == BX_PAYMENT_SBS_STATUS_ACTIVE) || $iTrial > 0) {
+            $sInterval = $this->_getInterval($iPeriod, $aItem['period_unit_recurring'], $iTrial);
+            date_add($oDate, new DateInterval($sInterval));
+            $iNext = date_format($oDate, 'U');
+        }
+
+        $sUnique = genRndPwd(9, false);
+
+        $aSubscription = array(
+            'pending_id' => $aPending['id'],
+            'customer_id' => !empty($aParams['customer_id']) ? $aParams['customer_id'] : 'bx_cus_' . $sUnique,
+            'subscription_id' => !empty($aParams['subscription_id']) ? $aParams['subscription_id'] : 'bx_sub_' . $sUnique,
+            'period' => $iPeriod,
+            'period_unit' => $aItem['period_unit_recurring'],
+            'trial' => $iTrial,
+            'date_add' => $iNow,
+            'date_next' => $iNext,
+            'status' => !empty($aParams['status']) ? $aParams['status'] : ($iTrial > 0 ? BX_PAYMENT_SBS_STATUS_TRIAL : BX_PAYMENT_SBS_STATUS_UNPAID)
+        );
+
+        if(!$this->_oModule->_oDb->insertSubscription($aSubscription))
+            return false;
+
+        $this->_oModule->onSubscriptionCreate($aPending, $aSubscription);
+
+        return true;
+    }
+
+    public function prolong($aPending, $aParams = array())
+    {
+        if(empty($aPending['items']))
+            return false;
+
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return false;
+
+        $aInfo = $this->_oModule->getObjectCart()->getInfo(BX_PAYMENT_TYPE_RECURRING, $aPending['client_id'], $aPending['seller_id'], $aPending['items']);
+        if(empty($aInfo['items']) || !is_array($aInfo['items']))
+            return false;
+
+        $aItem = array_shift($aInfo['items']);
+        $iItemPeriod = (int)$aItem['period_recurring'];
+        $sItemPeriodUnit = $aItem['period_unit_recurring'];
+
+        $iDateNext = 0;
+        if(!empty($iItemPeriod) && !empty($sItemPeriodUnit)) {
+            $sInterval = $this->_getInterval($iItemPeriod, $sItemPeriodUnit);
+            if(empty($sInterval))
+                return false;
+
+            $oDate = date_create('@' . $aSubscription['date_next']);
+            date_add($oDate, new DateInterval($sInterval));
+            $iDateNext = date_format($oDate, 'U');
+        }
+
+        $this->_oModule->_oDb->updateSubscription(array_merge(array(
+            'date_next' => $iDateNext,
+            'pay_attempts' => 0,
+            'status' => BX_PAYMENT_SBS_STATUS_ACTIVE
+        ), $aParams), array(
+            'id' => $aSubscription['id']
+        ));
+
+        $this->_oModule->onSubscriptionProlong($aPending, $aSubscription);
+
+        return true;
+    }
+
+    public function overdue($aPending, $aParams = array())
+    {
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return false;
+
+        $this->_oModule->_oDb->updateSubscription(array_merge(array(
+            'status' => BX_PAYMENT_SBS_STATUS_UNPAID
+        ), $aParams), array(
+            'id' => $aSubscription['id']
+        ));
+
+        $this->_oModule->onSubscriptionOverdue($aPending, $aSubscription);
+
+        return true;
+    }
+
+    public function cancel($iPendingId)
+    {
+        if(!$this->cancelRemote($iPendingId))
+            return false;
+
+        if(!$this->cancelLocal($iPendingId))
+            return false;
+
+        return true;
+    }
+
+    public function cancelRemote($mixedPending)
+    {
+        $aPending = is_array($mixedPending) ? $mixedPending : $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => (int)$mixedPending));
+        if(empty($aPending) || !is_array($aPending) || $aPending['type'] != BX_PAYMENT_TYPE_RECURRING)
+            return false;
+
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return false;
+
+        $iSellerId = (int)$aPending['seller_id'];
+        $oProvider = $this->_oModule->getObjectProvider($aPending['provider'], $iSellerId);
+        if($oProvider === false || !$oProvider->isActive())
+            return false;
+
+        if(!$oProvider->cancelRecurring($aPending['id'], $aSubscription['customer_id'], $aSubscription['subscription_id']))
+            return false;
+
+        return true;
+    }
+
+    public function cancelLocal($mixedPending)
+    {
+        $aPending = is_array($mixedPending) ? $mixedPending : $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => (int)$mixedPending));
+        if(empty($aPending) || !is_array($aPending) || $aPending['type'] != BX_PAYMENT_TYPE_RECURRING)
+            return false;
+
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return false;
+
+        $aItems = $this->_oModule->_oConfig->descriptorsM2A($aPending['items']);
+        foreach($aItems as $aItem)
+            $this->_oModule->callCancelSubscriptionItem((int)$aItem['module_id'], array($aPending['client_id'], $aPending['seller_id'], $aItem['item_id'], $aItem['item_count'], $aPending['order']));
+
+        if(!$this->_oModule->_oDb->deleteSubscription($aSubscription['id'], 'cancel'))
+            return false;
+
+        $this->_oModule->onSubscriptionCancel($aPending, $aSubscription);
+
+        return true;
+    }
+
+    protected function _getBlock($sType)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sMethod = 'displayBlockSbs' . bx_gen_method_name($sType);
+        if(!$this->_oModule->_oTemplate->isMethodExists($sMethod))
+            return array(
+                'content' => MsgBox(_t('_Empty'))
+            );
+
+    	$iUserId = $this->_oModule->getProfileId();
+        if(empty($iUserId))
+            return array(
+                'content' => MsgBox(_t($CNF['T']['ERR_REQUIRED_LOGIN']))
+            );
+
+        $this->_oModule->setSiteSubmenu('menu_dashboard', 'system', 'dashboard-subscriptions');
+
+        $sBlockSubmenu = $this->_oModule->_oConfig->getObject('menu_sbs_submenu');
+        $oBlockSubmenu = BxDolMenu::getObjectInstance($sBlockSubmenu);
+        if($oBlockSubmenu) 
+            $oBlockSubmenu->setSelected($this->MODULE, 'sbs-' . str_replace('_', '-', $sType));     
+
+        return array(
+            'content' => $this->_oModule->_oTemplate->$sMethod($iUserId),
+            'menu' => $this->_oModule->_oConfig->getObject('menu_sbs_submenu')
+        );
+    }
+
+    private function _getInterval($iPeriod, $sPeriodUnit, $iTrial = 0)
+    {
+        if((int)$iTrial > 0)
+            return 'P' . $iTrial . 'D';
+
+        $sInterval = '';
+        switch($sPeriodUnit) {
+            case BX_PAYMENT_SBS_PU_YEAR:
+                $sInterval = 'P' . $iPeriod . 'Y';
+                break;
+
+            case BX_PAYMENT_SBS_PU_MONTH:
+                $sInterval = 'P' . $iPeriod . 'M';
+                break;
+
+            case BX_PAYMENT_SBS_PU_WEEK:
+                $sInterval = 'P' . (7 * $iPeriod) . 'D';
+                break;
+
+            case BX_PAYMENT_SBS_PU_DAY:
+                $sInterval = 'P' . $iPeriod . 'D';
+                break;
+
+            case BX_PAYMENT_SBS_PU_HOUR:
+                $sInterval = 'PT' . $iPeriod . 'H';
+                break;
+
+            case BX_PAYMENT_SBS_PU_MINUTE:
+                $sInterval = 'PT' . $iPeriod . 'I';
+                break;
+        }
+
+        return $sInterval;
+    }
+}
+
+/** @} */

+ 2 - 0
modules/boonex/persons/updates/13.0.0_13.0.1/install/sql/enable.sql

@@ -6,6 +6,8 @@ INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `t
 
 DELETE FROM `sys_pages_blocks` WHERE `object`='' AND `module`='bx_persons' AND `title`='_bx_persons_page_block_title_cover_block';
 
+UPDATE `sys_pages_blocks` SET `content`='a:3:{s:6:\"module\";s:10:\"bx_persons\";s:6:\"method\";s:22:\"browse_active_profiles\";s:6:\"params\";a:3:{s:9:\"unit_view\";s:12:\"unit_wo_info\";s:13:\"empty_message\";b:0;s:13:\"ajax_paginate\";b:0;}}' WHERE `module`='bx_persons' AND (`title`='_bx_persons_page_block_title_active_profiles' OR `title` LIKE '_bx_persons_page_block_title_active_profiles_%') AND `content` LIKE '%gallery_wo_info%';
+
 DELETE FROM `sys_pages_blocks` WHERE `object`='' AND `module`='bx_persons' AND `title`='_bx_persons_page_block_title_familiar_profiles';
 SET @iBlockOrder = (SELECT `order` FROM `sys_pages_blocks` WHERE `object` = '' AND `cell_id` = 0 ORDER BY `order` DESC LIMIT 1);
 INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_courses/ru.xml

@@ -3,4 +3,5 @@
 	<string_add name="_bx_courses_option_enable_auto_approve"><![CDATA[Автоматически активировать курс после создания]]></string_add>
 	<string_add name="_bx_courses_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Любая аудитория]]></string_add>
 	<string_add name="_bx_courses_txt_status_hidden"><![CDATA[Этот курс был скрыт.]]></string_add>
+	<string_add name="_bx_courses_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string_add>
 </resources>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_credits/ru.xml

@@ -13,4 +13,5 @@
 {email_footer}]]></string_add>
 	<string_add name="_bx_credits_txt_history_info_send"><![CDATA[Перевод]]></string_add>
 	<string_add name="_bx_credits_txt_history_info_cancellation"><![CDATA[Отмена]]></string_add>
+	<string_add name="_bx_credits_txt_subscribe_lifetime"><![CDATA[пожизненно]]></string_add>
 </resources>

+ 3 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_events/ru.xml

@@ -6,4 +6,7 @@
 	<string_add name="_bx_events_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Любая аудитория]]></string_add>
 	<string_add name="_bx_events_grid_filter_item_title_adm_hidden"><![CDATA[Спрятанные]]></string_add>
 	<string_add name="_bx_events_txt_status_hidden"><![CDATA[Это событие было скрыто.]]></string_add>
+	<string_add name="_bx_events_txt_n_unit"><![CDATA[{0} ({1})]]></string_add>
+    <string_add name="_bx_events_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string_add>
+    <string_add name="_bx_events_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string_add>
 </resources>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_groups/ru.xml

@@ -5,4 +5,5 @@
 	<string_add name="_bx_groups_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Любая аудитория]]></string_add>
 	<string_add name="_bx_groups_grid_filter_item_title_adm_hidden"><![CDATA[Спрятанные]]></string_add>
 	<string_add name="_bx_groups_txt_status_hidden"><![CDATA[Эта группа была скрыта.]]></string_add>
+	<string_add name="_bx_groups_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string_add>
 </resources>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_organizations/ru.xml

@@ -5,4 +5,5 @@
 	<string_add name="_bx_orgs_page_block_title_sys_familiar_profiles"><![CDATA[Знакомые организации]]></string_add>
     <string_add name="_bx_orgs_page_block_title_familiar_profiles"><![CDATA[Организации, которые Вы можете знать]]></string_add>
 	<string_add name="_bx_orgs_txt_ntfs_join_request_for_owner"><![CDATA[<b class="bx-ntfs-content-entry">{subentry_sample}</b> хочет вступить в вашу организацию]]></string_add>
+	<string_add name="_bx_orgs_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string_add>
 </resources>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/bx_spaces/ru.xml

@@ -4,6 +4,7 @@
 	<string_add name="_bx_spaces_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Любая аудитория]]></string_add>
 	<string_add name="_bx_spaces_grid_filter_item_title_adm_hidden"><![CDATA[Спрятанные]]></string_add>
 	<string_add name="_bx_spaces_txt_status_hidden"><![CDATA[Это пространство было скрыто.]]></string_add>
+	<string_add name="_bx_spaces_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string_add>
 
 	<string_upd name="_bx_spaces_txt_account_pending"><![CDATA[Это пространство ожидает одобрения. Пожалуйста, дождитесь ее активации.]]></string_upd>
     <string_upd name="_bx_spaces_txt_account_suspended"><![CDATA[Это пространство заблокировано.]]></string_upd>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/install/langs/system/ru.xml

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources name="ru" flag="ru" title="Russian">
 	<string_add name="_apps"><![CDATA[Приложения]]></string_add>
+	<string_add name="_never"><![CDATA[никогда]]></string_add>
 	<string_add name="_adm_bp_txt_block_class"><![CDATA[CSS класс]]></string_add>
 	<string_add name="_adm_dbd_txt_c_clear_purifier"><![CDATA[Очистить кэш Purifier]]></string_add>
 	<string_add name="_adm_dbd_txt_c_clear_opcache"><![CDATA[Очистить Opcache]]></string_add>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_courses/ru.xml

@@ -371,6 +371,7 @@
     <string name="_bx_courses_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Извините, произошла ошибка. Вы не можете принять участие в курсе. Приглашение адресовано не Вам.]]></string>
     <string name="_bx_courses_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_courses_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_courses_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string>
 
     <string name="_bx_courses_msg_performed"><![CDATA[Действие было успешно выполнено.]]></string>
     <string name="_bx_courses_err_price_duplicate"><![CDATA[Аналогичный ценовой план уже существует.]]></string>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_credits/ru.xml

@@ -251,6 +251,7 @@
     <!-- Subscribe process -->
     <string name="_bx_credits_txt_subscribe"><![CDATA[Подписаться]]></string>
     <string name="_bx_credits_txt_subscribe_for"><![CDATA[Подписаться на:]]></string>
+    <string name="_bx_credits_txt_subscribe_lifetime"><![CDATA[пожизненно]]></string>  
     <string name="_bx_credits_txt_subscribe_period_mask"><![CDATA[{0} {1}]]></string>
     <string name="_bx_credits_txt_subscribe_period_unit_year"><![CDATA[гд.]]></string>
     <string name="_bx_credits_txt_subscribe_period_unit_month"><![CDATA[мес.]]></string>

+ 3 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_events/ru.xml

@@ -375,6 +375,9 @@
     <string name="_bx_events_txt_invite_popup_button_decline"><![CDATA[Отклонить]]></string>
     <string name="_bx_events_txt_invite_popup_error_invitation_absent"><![CDATA[Извините, произошла ошибка. Вы не можете присоединиться к этому событию. Приглашение устарело или удалено.]]></string>
     <string name="_bx_events_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Извините, произошла ошибка. Вы не можете присоединиться к этому событию. Приглашение адресовано не Вам.]]></string>
+    <string name="_bx_events_txt_n_unit"><![CDATA[{0} ({1})]]></string>
+    <string name="_bx_events_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_events_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string>
 
     <string name="_bx_events_alert_action_fan_added"><![CDATA[присоединение нового участника]]></string>
     <string name="_bx_events_txt_ntfs_fan_added"><![CDATA[У события <b class="bx-ntfs-content-profile">{owner_name}</b> новый участник <b class="bx-ntfs-content-entry">{subentry_sample}</b>]]></string>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_groups/ru.xml

@@ -373,6 +373,7 @@
     <string name="_bx_groups_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Извините, произошла ошибка. Вы не можете вступить в эту группу. Приглашение адресовано не Вам.]]></string>
     <string name="_bx_groups_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_groups_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_groups_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string>
 
     <string name="_bx_groups_msg_performed"><![CDATA[Действие было успешно выполнено.]]></string>
     <string name="_bx_groups_err_price_duplicate"><![CDATA[Аналогичный ценовой план уже существует.]]></string>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_organizations/ru.xml

@@ -503,6 +503,7 @@
     <string name="_bx_orgs_ps_group_title_participants"><![CDATA[Сотрудники]]></string>
     <string name="_bx_orgs_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_orgs_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_orgs_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string>
 
     <string name="_bx_orgs_msg_performed"><![CDATA[Действие было успешно выполнено.]]></string>
     <string name="_bx_orgs_err_price_duplicate"><![CDATA[Аналогичный ценовой план уже существует.]]></string>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/bx_spaces/ru.xml

@@ -361,6 +361,7 @@
     <string name="_bx_spaces_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Извините, произошла ошибка. Вы не можете вступить в это пространство. Приглашение адресовано не Вам.]]></string>
     <string name="_bx_spaces_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_spaces_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_spaces_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (пожизненно)]]></string>
 
     <string name="_bx_spaces_msg_performed"><![CDATA[Действие было успешно выполнено.]]></string>
     <string name="_bx_spaces_err_price_duplicate"><![CDATA[Аналогичный ценовой план уже существует.]]></string>

+ 1 - 0
modules/boonex/russian/updates/13.0.0_13.0.1/source/data/langs/system/ru.xml

@@ -76,6 +76,7 @@
 	<string name="_N_points"><![CDATA[<span>{0}</span> плюсов]]></string>
 	<string name="_Name"><![CDATA[Имя]]></string>
 	<string name="_Name is required"><![CDATA[Необходимо указать имя]]></string>
+        <string name="_never"><![CDATA[никогда]]></string>
 	<string name="_No"><![CDATA[Нет]]></string>
 	<string name="_None"><![CDATA[Никакой]]></string>
 	<string name="_No actions allowed for this membership"><![CDATA[Нет разрешённых действий для данного членства]]></string>

+ 1 - 0
modules/boonex/spaces/updates/13.0.0_13.0.1/install/langs/en.xml

@@ -4,4 +4,5 @@
 	<string_add name="_bx_spaces_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string_add>
 	<string_add name="_bx_spaces_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string_add>
 	<string_add name="_bx_spaces_txt_status_hidden"><![CDATA[This space was hidden.]]></string_add>
+	<string_add name="_bx_spaces_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string_add>
 </resources>

+ 1 - 0
modules/boonex/spaces/updates/13.0.0_13.0.1/source/classes/BxSpacesConfig.php

@@ -242,6 +242,7 @@ class BxSpacesConfig extends BxBaseModGroupsConfig
                 'txt_n_unit' => '_bx_spaces_txt_n_unit',
                 'txt_buy_title' => '_bx_spaces_grid_action_title_buy_title',
                 'txt_cart_item_title' => '_bx_spaces_txt_cart_item_title',
+                'txt_cart_item_title_lifetime' => '_bx_spaces_txt_cart_item_title_lifetime',
                 'txt_subscribe_title' => '_bx_spaces_grid_action_title_subscribe_title',
                 'popup_title_price_add' => '_bx_spaces_popup_title_price_add',
                 'popup_title_price_edit' => '_bx_spaces_popup_title_price_edit',

+ 1 - 0
modules/boonex/spaces/updates/13.0.0_13.0.1/source/install/langs/en.xml

@@ -365,6 +365,7 @@
     <string name="_bx_spaces_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Sorry, an error has occurred. You can not join this space. This invitation cannot be used by you.]]></string>
     <string name="_bx_spaces_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_spaces_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+    <string name="_bx_spaces_txt_cart_item_title_lifetime"><![CDATA[{0}: {1} (lifetime)]]></string>    
 
     <string name="_bx_spaces_msg_performed"><![CDATA[The action was successfully performed.]]></string>
     <string name="_bx_spaces_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>