Просмотр исходного кода

Ticket #4092 - Paid Levels: Add an option to immediately start newly purchased level.

AntonLV 4 лет назад
Родитель
Сommit
5c8e55289a

+ 10 - 0
modules/boonex/acl/classes/BxAclConfig.php

@@ -30,6 +30,7 @@ class BxAclConfig extends BxBaseModGeneralConfig
         $this->CNF = array (
             // database tables
             'TABLE_PRICES' => $aModule['db_prefix'] . 'level_prices',
+            'TABLE_LICENSES' => $aModule['db_prefix'] . 'licenses',
 
             // database fields
             'FIELD_ID' => 'id',
@@ -41,6 +42,7 @@ class BxAclConfig extends BxBaseModGeneralConfig
             'URL_VIEW' => 'page.php?i=acl-view',
 
             // some params
+            'PARAM_DATE_FORMAT' => 'd.m.Y',
             'PARAM_RECURRING_RESERVE' => 'bx_acl_recurring_reserve',
             'PARAM_RECURRING_PRIORITIZE' => 'bx_acl_recurring_prioritize',
 
@@ -51,6 +53,9 @@ class BxAclConfig extends BxBaseModGeneralConfig
             'OBJECT_FORM_PRICE_DISPLAY_ADD' => 'bx_acl_price_add',
             'OBJECT_FORM_PRICE_DISPLAY_EDIT' => 'bx_acl_price_edit',
             'OBJECT_FORM_PRELISTS_PERIOD_UNITS' => 'bx_acl_period_units',
+
+            // email templates
+            'ETEMPLATE_SBS_CANCEL_REQUIRED' => 'bx_acl_subscription_cancel_required',
         );
 
         $this->_aGridObjects = array(
@@ -136,6 +141,11 @@ class BxAclConfig extends BxBaseModGeneralConfig
     {
         return uriGenerate($sName, $this->CNF['TABLE_PRICES'], $this->CNF['FIELD_NAME'], ['lowercase' => false]);
     }
+
+    public function formatDate($iTs)
+    {
+        return gmdate($this->CNF['PARAM_DATE_FORMAT'], $iTs);
+    }
 }
 
 /** @} */

+ 104 - 41
modules/boonex/acl/classes/BxAclDb.php

@@ -22,40 +22,39 @@ class BxAclDb extends BxDolModuleDb
 
     public function getProductsNames ($iVendor = 0, $iLimit = 1000)
     {
-        $aBindings = array('limit' => $iLimit);
-        return $this->getColumn("SELECT `name` FROM `bx_acl_level_prices` WHERE 1 LIMIT :limit", $aBindings);
+        return $this->getColumn("SELECT `name` FROM `bx_acl_level_prices` WHERE 1 LIMIT :limit", ['limit' => $iLimit]);
     }
 
-	public function getLevels($aParams, $bReturnCount = false)
+    public function getLevels($aParams, $bReturnCount = false)
     {
         $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
         $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
 
         $sSelectClause = "
-			`tal`.`ID` AS `id`,
-			`tal`.`Name` AS `name`,
-			`tal`.`Icon` AS `icon`,
-			`tal`.`Description` AS `description`,
-			`tal`.`Active` AS `active`,
-			`tal`.`Purchasable` AS `purchasable`,
-			`tal`.`Removable` AS `removable`,
-			`tal`.`QuotaSize` AS `quota_size`,
-			`tal`.`QuotaNumber` AS `quota_number`,
-			`tal`.`QuotaMaxFileSize` AS `quota_max_file_size`,
-			`tal`.`Order` AS `order`";
+            `tal`.`ID` AS `id`,
+            `tal`.`Name` AS `name`,
+            `tal`.`Icon` AS `icon`,
+            `tal`.`Description` AS `description`,
+            `tal`.`Active` AS `active`,
+            `tal`.`Purchasable` AS `purchasable`,
+            `tal`.`Removable` AS `removable`,
+            `tal`.`QuotaSize` AS `quota_size`,
+            `tal`.`QuotaNumber` AS `quota_number`,
+            `tal`.`QuotaMaxFileSize` AS `quota_max_file_size`,
+            `tal`.`Order` AS `order`";
 
         if(!isset($aParams['order']) || empty($aParams['order']))
             $sOrderClause = "ORDER BY `tal`.`Order` ASC";
 
         switch($aParams['type']) {
-        	case 'by_id':
-        		$aMethod['name'] = 'getRow';
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
                 $aMethod['params'][1] = array(
-                	'id' => $aParams['value']
+                    'id' => $aParams['value']
                 );
 
                 $sWhereClause .= "AND `tal`.`id`=:id";
-        		break;
+                break;
 
             case 'for_selector':
                 $aMethod['name'] = 'getPairs';
@@ -73,35 +72,36 @@ class BxAclDb extends BxDolModuleDb
         if(!$bReturnCount)
             return $aItems;
 
-		$aMethod['name'] = 'getOne';
-		$aMethod['params'][0] = str_replace(array('{select}', '{order}', '{limit}'), array("COUNT(*)", "", ""), $sSql);
+        $aMethod['name'] = 'getOne';
+        $aMethod['params'][0] = str_replace(array('{select}', '{order}', '{limit}'), array("COUNT(*)", "", ""), $sSql);
 
-		return array(
-			'items' => $aItems, 
-			'count' => (int)call_user_func_array(array($this, $aMethod['name']), $aMethod['params'])
-		);
+        return array(
+            'items' => $aItems, 
+            'count' => (int)call_user_func_array(array($this, $aMethod['name']), $aMethod['params'])
+        );
     }
 
-	public function updateLevels($aSet, $aWhere)
+    public function updateLevels($aSet, $aWhere)
     {
-    	$sSql = "UPDATE `sys_acl_levels` SET " . $this->arrayToSQL($aSet, " AND ") . " WHERE " . $this->arrayToSQL($aWhere, " AND ");
+        $sSql = "UPDATE `sys_acl_levels` SET " . $this->arrayToSQL($aSet, " AND ") . " WHERE " . $this->arrayToSQL($aWhere, " AND ");
         return (int)$this->query($sSql) > 0;
     }
 
-	public function getPrices($aParams, $bReturnCount = false)
+    public function getPrices($aParams, $bReturnCount = false)
     {
         $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
         $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
 
         $sSelectClause = "
-			`tap`.`id` AS `id`,
-			`tap`.`level_id` AS `level_id`,
-			`tap`.`name` AS `name`,
-			`tap`.`period` AS `period`,
-			`tap`.`period_unit` AS `period_unit`,
-			`tap`.`price` AS `price`,
-			`tap`.`trial` AS `trial`,
-			`tap`.`order` AS `order`";
+            `tap`.`id` AS `id`,
+            `tap`.`level_id` AS `level_id`,
+            `tap`.`name` AS `name`,
+            `tap`.`period` AS `period`,
+            `tap`.`period_unit` AS `period_unit`,
+            `tap`.`price` AS `price`,
+            `tap`.`trial` AS `trial`,
+            `tap`.`immediate` AS `immediate`,
+            `tap`.`order` AS `order`";
 
         if(!isset($aParams['order']) || empty($aParams['order']))
             $sOrderClause = "ORDER BY `tap`.`Order` ASC";
@@ -139,7 +139,7 @@ class BxAclDb extends BxDolModuleDb
                 break;
 
             case 'by_level_id':
-            	$aMethod['params'][1] = array(
+                $aMethod['params'][1] = array(
                     'level_id' => $aParams['value']
                 );
 
@@ -171,12 +171,12 @@ class BxAclDb extends BxDolModuleDb
         if(!$bReturnCount)
             return $aItems;
 
-		$aMethod['name'] = 'getOne';
-		$aMethod['params'][0] = str_replace(array('{select}', '{order}', '{limit}'), array("COUNT(*)", "", ""), $sSql);
+        $aMethod['name'] = 'getOne';
+        $aMethod['params'][0] = str_replace(array('{select}', '{order}', '{limit}'), array("COUNT(*)", "", ""), $sSql);
 
         return array(
-        	'items' => $aItems, 
-        	'count' => (int)call_user_func_array(array($this, $aMethod['name']), $aMethod['params'])
+            'items' => $aItems, 
+            'count' => (int)call_user_func_array(array($this, $aMethod['name']), $aMethod['params'])
         );
     }
 
@@ -188,9 +188,72 @@ class BxAclDb extends BxDolModuleDb
 
     public function deletePrices($aWhere)
     {
-    	$sSql = "DELETE FROM `" . $this->_oConfig->CNF['TABLE_PRICES'] . "` WHERE " . $this->arrayToSQL($aWhere, " AND ");
+        $sSql = "DELETE FROM `" . $this->_oConfig->CNF['TABLE_PRICES'] . "` WHERE " . $this->arrayToSQL($aWhere, " AND ");
         return (int)$this->query($sSql) > 0;
     }
+    
+    public function getLicenses($aParams, $bReturnCount = false)
+    {
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+
+        $sSelectClause = "`tl`.*";
+        $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        switch($aParams['type']) {
+            case 'by_order':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'order' => $aParams['order']
+                ];
+
+                $sWhereClause .= "AND `tl`.`order`=:order";
+                break;
+
+            case 'by_license':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'license' => $aParams['license']
+                ];
+
+                $sWhereClause .= "AND `tl`.`license`=:license";
+                break;
+        }
+
+        $sSql = "SELECT {select} FROM `" . $this->_oConfig->CNF['TABLE_LICENSES'] . "` AS `tl` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " {order} {limit}";
+
+        $aMethod['params'][0] = str_replace(['{select}', '{order}', '{limit}'], [$sSelectClause, $sOrderClause, $sLimitClause], $sSql);
+        $aItems = call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+
+        if(!$bReturnCount)
+            return $aItems;
+
+        $aMethod['name'] = 'getOne';
+        $aMethod['params'][0] = str_replace(['{select}', '{order}', '{limit}'], ['COUNT(*)', '', ''], $sSql);
+
+        return [
+            'items' => $aItems, 
+            'count' => (int)call_user_func_array([$this, $aMethod['name']], $aMethod['params'])
+        ];
+    }
+
+    public function insertLicense($aSet)
+    {
+        $sSetClause = $this->arrayToSQL($aSet);
+        return (int)$this->query("INSERT INTO `" . $this->_oConfig->CNF['TABLE_LICENSES'] . "` SET " . $sSetClause) > 0;
+    }
+
+    public function updateLicenses($aSet, $aWhere)
+    {
+        $sSetClause = $this->arrayToSQL($aSet);
+        $sWhereClause = $this->arrayToSQL($aWhere, " AND ");
+        return (int)$this->query("UPDATE `" . $this->_oConfig->CNF['TABLE_LICENSES'] . "` SET " . $sSetClause . " WHERE " . $sWhereClause) > 0;
+    }
+
+    public function deleteLicenses($aWhere)
+    {
+        $sWhereClause = $this->arrayToSQL($aWhere, " AND ");
+        return (int)$this->query("DELETE FROM `" . $this->_oConfig->CNF['TABLE_LICENSES'] . "` WHERE " . $sWhereClause) > 0;
+    }
 }
 
 /** @} */

+ 48 - 6
modules/boonex/acl/classes/BxAclModule.php

@@ -540,23 +540,65 @@ class BxAclModule extends BxDolModule
 
     	$aItem = $this->serviceGetCartItem($iItemId);
         if(empty($aItem) || !is_array($aItem))
-            return array();
+            return [];
 
         $oAcl = BxDolAcl::getInstance();
 
-        $aItemInfo = $this->_oDb->getPrices(array('type' => 'by_id', 'value' => $iItemId));
+        $aItemInfo = $this->_oDb->getPrices(['type' => 'by_id', 'value' => $iItemId]);
         $aMembershipInfo = $oAcl->getMemberMembershipInfo($iClientId);
 
-        $aPeriod = array('period' => (int)$aItemInfo['period'], 'period_unit' => $aItemInfo['period_unit']);
+        $aPeriod = ['period' => (int)$aItemInfo['period'], 'period_unit' => $aItemInfo['period_unit']];
         if($sType == BX_ACL_LICENSE_TYPE_RECURRING && (int)$aItemInfo['trial'] > 0 && (int)$aItemInfo['level_id'] != (int)$aMembershipInfo['id'])
-            $aPeriod = array('period' => (int)$aItemInfo['trial'], 'period_unit' => 'day', 'period_trial' => true);
+            $aPeriod = ['period' => (int)$aItemInfo['trial'], 'period_unit' => 'day', 'period_trial' => true];
 
         $iReserve = (int)getParam($CNF['PARAM_RECURRING_RESERVE']);
         if(!empty($iReserve))
             $aPeriod['period_reserve'] = $iReserve;
 
-        if(!$oAcl->setMembership($iClientId, $aItemInfo['level_id'], $aPeriod, true, $sLicense))
-            return array();
+        $bImmediate = (int)$aItemInfo['immediate'] != 0;
+        if(!$oAcl->setMembership($iClientId, $aItemInfo['level_id'], $aPeriod, $bImmediate, $sLicense))
+            return [];
+
+        $this->_oDb->insertLicense([
+            'profile_id' => $iClientId,
+            'price_id' => $iItemId,
+            'type' => $sType,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'added' => time()
+        ]);
+
+        if($bImmediate && !empty($aMembershipInfo['transaction_id'])) {
+            $oPayments = BxDolPayments::getInstance();
+
+            $sLicenseOld = $aMembershipInfo['transaction_id'];
+            $aLicenseOld = $this->_oDb->getLicenses(['type' => 'by_license', 'license' => $sLicenseOld]);
+
+            $sPpOrder = '';
+            if(empty($aLicenseOld) || !is_array($aLicenseOld)) {
+                $aOrders = $oPayments->getOrdersInfo(['license' => $sLicenseOld]);
+                if(!empty($aOrders) && is_array($aOrders)) {
+                    $aOrder = reset($aOrders);
+                    $aPendings = $oPayments->getPendingOrdersInfo(['id' => $aOrder['pending_id']]);
+                    if(!empty($aPendings) && is_array($aPendings)) {
+                        $aPpPending = reset($aPendings);
+                        if($aPpPending['type'] == BX_ACL_LICENSE_TYPE_RECURRING)
+                            $sPpOrder = $aPpPending['order'];
+                    }
+                }    
+            }
+            else if($aLicenseOld['type'] == BX_ACL_LICENSE_TYPE_RECURRING)
+                $sPpOrder = $aLicenseOld['order'];
+
+            if(!empty($sPpOrder) && (($aResult = $oPayments->cancelSubscription($sPpOrder)) === false || (int)$aResult['code'] != 0)) {
+                sendMailTemplate($CNF['ETEMPLATE_SBS_CANCEL_REQUIRED'], 0, $iClientId, [
+                    'level' => _t($aMembershipInfo['name']),
+                    'order' => $sPpOrder,
+                    'date_starts' => $this->_oConfig->formatDate($aMembershipInfo['date_starts']),
+                    'date_expires' => $this->_oConfig->formatDate($aMembershipInfo['date_expires']),
+                ], BX_EMAIL_NOTIFY, true);
+            }
+        }
 
         return $aItem;
     }

+ 107 - 95
modules/boonex/acl/install/langs/en.xml

@@ -1,102 +1,114 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources name="en" flag="gb" title="English">
-	<string name="_bx_acl"><![CDATA[Paid Levels]]></string>
-
-
-        <!-- Option -->
-        <string name="_bx_acl_option_recurring_reserve"><![CDATA[Reserve (in days) to accept and process recurring payment notification]]></string>
-        <string name="_bx_acl_option_recurring_prioritize"><![CDATA[Prioritize recurring payments]]></string>
-
-
-	<!-- Pages -->
-	<string name="_bx_acl_page_title_sys_view"><![CDATA[Membership]]></string>
-	<string name="_bx_acl_page_title_view"><![CDATA[Membership]]></string>
-	<string name="_bx_acl_page_block_title_system_view"><![CDATA[Membership]]></string>
-	<string name="_bx_acl_page_block_title_view"><![CDATA[Membership]]></string>
-
-
-        <!-- Menus -->
-        <string name="_bx_acl_lmi_cpt_manage"><![CDATA[Manage]]></string>
-
-
-	<!-- Forms -->
-	<string name="_bx_acl_form_price"><![CDATA[Price]]></string>
-	<string name="_bx_acl_form_price_display_add"><![CDATA[Add]]></string>
-	<string name="_bx_acl_form_price_display_edit"><![CDATA[Edit]]></string>
-	<string name="_bx_acl_form_price_input_sys_id"><![CDATA[ID]]></string>
-	<string name="_bx_acl_form_price_input_sys_level_id"><![CDATA[Level ID]]></string>
-        <string name="_bx_acl_form_price_input_sys_name"><![CDATA[Name]]></string>
-	<string name="_bx_acl_form_price_input_name"><![CDATA[Name]]></string>
-	<string name="_bx_acl_form_price_input_inf_name"><![CDATA[Unique value which will be used in back end and API calls]]></string>
-        <string name="_bx_acl_form_price_input_err_name"><![CDATA[You need to enter a unique value here]]></string>
-	<string name="_bx_acl_form_price_input_sys_period"><![CDATA[Period]]></string>
-	<string name="_bx_acl_form_price_input_period"><![CDATA[Period]]></string>
-	<string name="_bx_acl_form_price_input_inf_period"><![CDATA[Use 0 for lifetime]]></string>
-	<string name="_bx_acl_form_price_input_sys_period_unit"><![CDATA[Period unit]]></string>
-	<string name="_bx_acl_form_price_input_period_unit"><![CDATA[Period unit]]></string>
-	<string name="_bx_acl_form_price_input_inf_period_unit"><![CDATA[Leave empty for lifetime level.]]></string>
-	<string name="_bx_acl_form_price_input_err_period_unit"><![CDATA[You need to select a period unit here.]]></string>
-	<string name="_bx_acl_form_price_input_sys_price"><![CDATA[Price]]></string>
-	<string name="_bx_acl_form_price_input_price"><![CDATA[Price]]></string>
-	<string name="_bx_acl_form_price_input_inf_price"><![CDATA[Use 0 or leave empty to create free level.]]></string>
-	<string name="_bx_acl_form_price_input_sys_trial"><![CDATA[Trial]]></string>
+    <string name="_bx_acl"><![CDATA[Paid Levels]]></string>
+
+
+    <!-- Option -->
+    <string name="_bx_acl_option_recurring_reserve"><![CDATA[Reserve (in days) to accept and process recurring payment notification]]></string>
+    <string name="_bx_acl_option_recurring_prioritize"><![CDATA[Prioritize recurring payments]]></string>
+
+
+    <!-- Pages -->
+    <string name="_bx_acl_page_title_sys_view"><![CDATA[Membership]]></string>
+    <string name="_bx_acl_page_title_view"><![CDATA[Membership]]></string>
+    <string name="_bx_acl_page_block_title_system_view"><![CDATA[Membership]]></string>
+    <string name="_bx_acl_page_block_title_view"><![CDATA[Membership]]></string>
+
+
+    <!-- Menus -->
+    <string name="_bx_acl_lmi_cpt_manage"><![CDATA[Manage]]></string>
+
+
+    <!-- Forms -->
+    <string name="_bx_acl_form_price"><![CDATA[Price]]></string>
+    <string name="_bx_acl_form_price_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_acl_form_price_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_acl_form_price_input_sys_id"><![CDATA[ID]]></string>
+    <string name="_bx_acl_form_price_input_sys_level_id"><![CDATA[Level ID]]></string>
+    <string name="_bx_acl_form_price_input_sys_name"><![CDATA[Name]]></string>
+    <string name="_bx_acl_form_price_input_name"><![CDATA[Name]]></string>
+    <string name="_bx_acl_form_price_input_inf_name"><![CDATA[Unique value which will be used in back end and API calls]]></string>
+    <string name="_bx_acl_form_price_input_err_name"><![CDATA[You need to enter a unique value here]]></string>
+    <string name="_bx_acl_form_price_input_sys_period"><![CDATA[Period]]></string>
+    <string name="_bx_acl_form_price_input_period"><![CDATA[Period]]></string>
+    <string name="_bx_acl_form_price_input_inf_period"><![CDATA[Use 0 for lifetime]]></string>
+    <string name="_bx_acl_form_price_input_sys_period_unit"><![CDATA[Period unit]]></string>
+    <string name="_bx_acl_form_price_input_period_unit"><![CDATA[Period unit]]></string>
+    <string name="_bx_acl_form_price_input_inf_period_unit"><![CDATA[Leave empty for lifetime level.]]></string>
+    <string name="_bx_acl_form_price_input_err_period_unit"><![CDATA[You need to select a period unit here.]]></string>
+    <string name="_bx_acl_form_price_input_sys_price"><![CDATA[Price]]></string>
+    <string name="_bx_acl_form_price_input_price"><![CDATA[Price]]></string>
+    <string name="_bx_acl_form_price_input_inf_price"><![CDATA[Use 0 or leave empty to create free level.]]></string>
+    <string name="_bx_acl_form_price_input_sys_trial"><![CDATA[Trial]]></string>
     <string name="_bx_acl_form_price_input_trial"><![CDATA[Trial]]></string>
     <string name="_bx_acl_form_price_input_inf_trial"><![CDATA[The number of days new customers should receive a free trial. Leave empty to disable trial period.]]></string>
-	<string name="_bx_acl_form_price_input_sys_do_submit"><![CDATA[Submit]]></string>
-	<string name="_bx_acl_form_price_input_do_submit"><![CDATA[Submit]]></string>
-	<string name="_bx_acl_form_price_input_sys_do_cancel"><![CDATA[Cancel]]></string>
-	<string name="_bx_acl_form_price_input_do_cancel"><![CDATA[Cancel]]></string>
-
-
-	<!-- Pre Lists -->
-	<string name="_bx_acl_pre_lists_period_units"><![CDATA[Billing Interval]]></string>
-	<string name="_bx_acl_pre_values_day"><![CDATA[day]]></string>
-	<string name="_bx_acl_pre_values_week"><![CDATA[week]]></string>
-	<string name="_bx_acl_pre_values_month"><![CDATA[month]]></string>
-	<string name="_bx_acl_pre_values_year"><![CDATA[year]]></string>
-
-
-	<!-- Grids -->
-	<string name="_bx_acl_grid_column_level_icon"><![CDATA[]]></string>
-	<string name="_bx_acl_grid_column_level_name"><![CDATA[Level]]></string>
-	<string name="_bx_acl_grid_column_name"><![CDATA[Name]]></string>
-	<string name="_bx_acl_grid_column_period"><![CDATA[Period]]></string>
-	<string name="_bx_acl_grid_column_price"><![CDATA[Price]]></string>
-	<string name="_bx_acl_grid_column_trial"><![CDATA[Trial]]></string>
-
-	<string name="_bx_acl_grid_action_add"><![CDATA[Add]]></string>
-	<string name="_bx_acl_grid_action_edit"><![CDATA[Edit]]></string>
-	<string name="_bx_acl_grid_action_delete"><![CDATA[Delete]]></string>
-	<string name="_bx_acl_grid_action_buy"><![CDATA[Buy]]></string>
-	<string name="_bx_acl_grid_action_buy_title"><![CDATA[One-time payment]]></string>
-	<string name="_bx_acl_grid_action_subscribe"><![CDATA[Subscribe]]></string>
-	<string name="_bx_acl_grid_action_subscribe_title"><![CDATA[Create a subscription with recurring billing]]></string>
-	<string name="_bx_acl_grid_action_choose"><![CDATA[Choose]]></string>
+    <string name="_bx_acl_form_price_input_sys_immediate"><![CDATA[Activate immediately]]></string>
+    <string name="_bx_acl_form_price_input_immediate"><![CDATA[Activate immediately after purchase]]></string>
+    <string name="_bx_acl_form_price_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_acl_form_price_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_acl_form_price_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_acl_form_price_input_do_cancel"><![CDATA[Cancel]]></string>
+
+
+    <!-- Pre Lists -->
+    <string name="_bx_acl_pre_lists_period_units"><![CDATA[Billing Interval]]></string>
+    <string name="_bx_acl_pre_values_day"><![CDATA[day]]></string>
+    <string name="_bx_acl_pre_values_week"><![CDATA[week]]></string>
+    <string name="_bx_acl_pre_values_month"><![CDATA[month]]></string>
+    <string name="_bx_acl_pre_values_year"><![CDATA[year]]></string>
+
+
+    <!-- Grids -->
+    <string name="_bx_acl_grid_column_level_icon"><![CDATA[]]></string>
+    <string name="_bx_acl_grid_column_level_name"><![CDATA[Level]]></string>
+    <string name="_bx_acl_grid_column_name"><![CDATA[Name]]></string>
+    <string name="_bx_acl_grid_column_period"><![CDATA[Period]]></string>
+    <string name="_bx_acl_grid_column_price"><![CDATA[Price]]></string>
+    <string name="_bx_acl_grid_column_trial"><![CDATA[Trial]]></string>
+
+    <string name="_bx_acl_grid_action_add"><![CDATA[Add]]></string>
+    <string name="_bx_acl_grid_action_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_acl_grid_action_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_acl_grid_action_buy"><![CDATA[Buy]]></string>
+    <string name="_bx_acl_grid_action_buy_title"><![CDATA[One-time payment]]></string>
+    <string name="_bx_acl_grid_action_subscribe"><![CDATA[Subscribe]]></string>
+    <string name="_bx_acl_grid_action_subscribe_title"><![CDATA[Create a subscription with recurring billing]]></string>
+    <string name="_bx_acl_grid_action_choose"><![CDATA[Choose]]></string>
     <string name="_bx_acl_grid_action_choose_title"><![CDATA[Switch to the level for free]]></string>
 
-	<string name="_bx_acl_popup_title_price_add"><![CDATA[Add Pricing Plan]]></string>
-	<string name="_bx_acl_popup_title_price_edit"><![CDATA[Edit Pricing Plan]]></string>
-
-
-	<!-- Menus -->
-	<string name="_bx_acl_menu_item_title_system_acl_view"><![CDATA[Membership]]></string>
-	<string name="_bx_acl_menu_item_title_acl_view"><![CDATA[Membership]]></string>
-
-
-	<!-- Common Texts -->
-	<string name="_bx_acl_msg_empty_owner"><![CDATA['Main seller' is empty. You need to mark some profile as 'Main seller' in Settings of currently active Payment app.]]></string>
-	<string name="_bx_acl_msg_performed"><![CDATA[The action was successfully performed.]]></string>
-	<string name="_bx_acl_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>
-	<string name="_bx_acl_err_cannot_perform"><![CDATA[An error appeared. Please report.]]></string>
-
-	<string name="_bx_acl_txt_select_level"><![CDATA[Select level...]]></string>
-	<string name="_bx_acl_txt_cart_item_title"><![CDATA[{0} ({1} {2})]]></string>
-        <string name="_bx_acl_txt_cart_item_title_lifetime"><![CDATA[{0} (lifetime)]]></string>
-        <string name="_bx_acl_txt_cart_item_description"><![CDATA[Membership level on {0} site.]]></string>
-	<string name="_bx_acl_txt_upgrade"><![CDATA[Upgrade]]></string>
-	<string name="_bx_acl_txt_manage"><![CDATA[Manage]]></string>
-	<string name="_bx_acl_txt_n_unit"><![CDATA[{0} ({1})]]></string>
-	<string name="_bx_acl_txt_none"><![CDATA[none]]></string>
-	<string name="_bx_acl_txt_free"><![CDATA[free]]></string>
-	<string name="_bx_acl_txt_lifetime"><![CDATA[lifetime]]></string>
+    <string name="_bx_acl_popup_title_price_add"><![CDATA[Add Pricing Plan]]></string>
+    <string name="_bx_acl_popup_title_price_edit"><![CDATA[Edit Pricing Plan]]></string>
+
+
+    <!-- Menus -->
+    <string name="_bx_acl_menu_item_title_system_acl_view"><![CDATA[Membership]]></string>
+    <string name="_bx_acl_menu_item_title_acl_view"><![CDATA[Membership]]></string>
+
+
+    <!-- Email Templates -->
+    <string name="_bx_acl_et_txt_name_subscription_cancel_required"><![CDATA[Subscription cancel required.]]></string>
+    <string name="_bx_acl_et_txt_subject_subscription_cancel_required"><![CDATA[Subscription cancel required.]]></string>
+    <string name="_bx_acl_et_txt_body_subscription_cancel_required"><![CDATA[{email_header}
+<p>Hello,</p>
+<p>You've paid for a new membership level but your old level's subscription is still active. You need to cancel it manually.</p>
+<p>Old subscription info:<br />Membership level: {level}<br />Order: {order}<br />Date Starts: {date_starts}<br />Date Expires: {date_expires}</p>
+{email_footer}]]></string>
+
+
+    <!-- Common Texts -->
+    <string name="_bx_acl_msg_empty_owner"><![CDATA['Main seller' is empty. You need to mark some profile as 'Main seller' in Settings of currently active Payment app.]]></string>
+    <string name="_bx_acl_msg_performed"><![CDATA[The action was successfully performed.]]></string>
+    <string name="_bx_acl_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>
+    <string name="_bx_acl_err_cannot_perform"><![CDATA[An error appeared. Please report.]]></string>
+
+    <string name="_bx_acl_txt_select_level"><![CDATA[Select level...]]></string>
+    <string name="_bx_acl_txt_cart_item_title"><![CDATA[{0} ({1} {2})]]></string>
+    <string name="_bx_acl_txt_cart_item_title_lifetime"><![CDATA[{0} (lifetime)]]></string>
+    <string name="_bx_acl_txt_cart_item_description"><![CDATA[Membership level on {0} site.]]></string>
+    <string name="_bx_acl_txt_upgrade"><![CDATA[Upgrade]]></string>
+    <string name="_bx_acl_txt_manage"><![CDATA[Manage]]></string>
+    <string name="_bx_acl_txt_n_unit"><![CDATA[{0} ({1})]]></string>
+    <string name="_bx_acl_txt_none"><![CDATA[none]]></string>
+    <string name="_bx_acl_txt_free"><![CDATA[free]]></string>
+    <string name="_bx_acl_txt_lifetime"><![CDATA[lifetime]]></string>
 </resources>

+ 4 - 0
modules/boonex/acl/install/sql/disable.sql

@@ -20,6 +20,10 @@ DELETE FROM `sys_menu_sets` WHERE `module` = @sName;
 DELETE FROM `sys_menu_items` WHERE `module` = @sName;
 
 
+-- EMAIL TEMPLATES
+DELETE FROM `sys_email_templates` WHERE `Module` = @sName;
+
+
 -- ALERTS
 SET @iHandlerId = (SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = @sName LIMIT 1);
 DELETE FROM `sys_alerts` WHERE `handler_id` = @iHandlerId;

+ 5 - 0
modules/boonex/acl/install/sql/enable.sql

@@ -30,6 +30,11 @@ INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `tit
 ('sys_account_settings', 'bx_acl', 'acl-view', '_bx_acl_menu_item_title_system_acl_view', '_bx_acl_menu_item_title_acl_view', 'page.php?i=acl-view', '', '', 'shield-alt col-red2', '', '', 2147483646, 1, 0, 1, @iMoAccountSettings + 1);
 
 
+-- EMAIL TEMPLATES
+INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `Body`) VALUES 
+(@sName, '_bx_acl_et_txt_name_subscription_cancel_required', 'bx_acl_subscription_cancel_required', '_bx_acl_et_txt_subject_subscription_cancel_required', '_bx_acl_et_txt_body_subscription_cancel_required');
+
+
 -- ALERTS
 INSERT INTO `sys_alerts_handlers`(`name`, `class`, `file`, `service_call`) VALUES 
 (@sName, 'BxAclResponse', 'modules/boonex/acl/classes/BxAclResponse.php', '');

+ 24 - 6
modules/boonex/acl/install/sql/install.sql

@@ -10,12 +10,27 @@ CREATE TABLE `bx_acl_level_prices` (
   `period_unit` varchar(32) NOT NULL default '',
   `trial` int(11) unsigned NOT NULL default '0',
   `price` float unsigned NOT NULL default '1',
+  `immediate` tinyint(4) NOT NULL default '1',
   `order` int(11) NOT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `name` (`name`),
   UNIQUE KEY `type` (`level_id`,`period`, `period_unit`)
 );
 
+-- TABLE: licenses
+CREATE TABLE IF NOT EXISTS `bx_acl_licenses` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `profile_id` int(11) unsigned NOT NULL default '0',
+  `price_id` int(11) unsigned NOT NULL default '0',
+  `type` varchar(16) NOT NULL default 'single',
+  `order` varchar(32) NOT NULL default '',
+  `license` varchar(32) NOT NULL default '',
+  `added` int(11) unsigned NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `price_id` (`price_id`, `profile_id`),
+  KEY `license` (`license`)
+);
+
 
 -- FORMS
 INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
@@ -33,6 +48,7 @@ INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `c
 ('bx_acl_price', @sName, 'period_unit', '', '#!bx_acl_period_units', 0, 'select', '_bx_acl_form_price_input_sys_period_unit', '_bx_acl_form_price_input_period_unit', '_bx_acl_form_price_input_inf_period_unit', 1, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
 ('bx_acl_price', @sName, 'trial', '', '', 0, 'text', '_bx_acl_form_price_input_sys_trial', '_bx_acl_form_price_input_trial', '_bx_acl_form_price_input_inf_trial', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
 ('bx_acl_price', @sName, 'price', '', '', 0, 'price', '_bx_acl_form_price_input_sys_price', '_bx_acl_form_price_input_price', '_bx_acl_form_price_input_inf_price', 1, 0, 0, '', '', '', '', '', '', 'Float', '', 1, 0),
+('bx_acl_price', @sName, 'immediate', 1, '', 1, 'switcher', '_bx_acl_form_price_input_sys_immediate', '_bx_acl_form_price_input_immediate', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
 ('bx_acl_price', @sName, 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
 ('bx_acl_price', @sName, 'do_submit', '_bx_acl_form_price_input_do_submit', '', 0, 'submit', '_bx_acl_form_price_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
 ('bx_acl_price', @sName, 'do_cancel', '_bx_acl_form_price_input_do_cancel', '', 0, 'button', '_bx_acl_form_price_input_sys_do_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 1, 0);
@@ -45,9 +61,10 @@ INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_fo
 ('bx_acl_price_add', 'period', 2147483647, 1, 5),
 ('bx_acl_price_add', 'period_unit', 2147483647, 1, 6),
 ('bx_acl_price_add', 'trial', 2147483647, 1, 7),
-('bx_acl_price_add', 'controls', 2147483647, 1, 8),
-('bx_acl_price_add', 'do_submit', 2147483647, 1, 9),
-('bx_acl_price_add', 'do_cancel', 2147483647, 1, 10),
+('bx_acl_price_add', 'immediate', 2147483647, 1, 8),
+('bx_acl_price_add', 'controls', 2147483647, 1, 9),
+('bx_acl_price_add', 'do_submit', 2147483647, 1, 10),
+('bx_acl_price_add', 'do_cancel', 2147483647, 1, 11),
 
 ('bx_acl_price_edit', 'id', 2147483647, 1, 1),
 ('bx_acl_price_edit', 'level_id', 2147483647, 1, 2),
@@ -56,9 +73,10 @@ INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_fo
 ('bx_acl_price_edit', 'period', 2147483647, 1, 5),
 ('bx_acl_price_edit', 'period_unit', 2147483647, 1, 6),
 ('bx_acl_price_edit', 'trial', 2147483647, 1, 7),
-('bx_acl_price_edit', 'controls', 2147483647, 1, 8),
-('bx_acl_price_edit', 'do_submit', 2147483647, 1, 9),
-('bx_acl_price_edit', 'do_cancel', 2147483647, 1, 10);
+('bx_acl_price_edit', 'immediate', 2147483647, 1, 8),
+('bx_acl_price_edit', 'controls', 2147483647, 1, 9),
+('bx_acl_price_edit', 'do_submit', 2147483647, 1, 10),
+('bx_acl_price_edit', 'do_cancel', 2147483647, 1, 11);
 
 
 -- PRE-VALUES

+ 1 - 1
modules/boonex/acl/install/sql/uninstall.sql

@@ -2,7 +2,7 @@ SET @sName = 'bx_acl';
 
 
 -- TABLES
-DROP TABLE IF EXISTS `bx_acl_level_prices`;
+DROP TABLE IF EXISTS `bx_acl_level_prices`, `bx_acl_licenses`;
 
 
 -- FORMS

+ 94 - 82
modules/boonex/russian/data/langs/bx_acl/ru.xml

@@ -1,101 +1,113 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources name="ru" flag="ru" title="Russian">
-	<string name="_bx_acl"><![CDATA[Платные Уровни]]></string>
-
-
-        <!-- Options -->
-        <string name="_bx_acl_option_recurring_reserve"><![CDATA[Резерв (в днях) на принятие и обработку сообщения об оплате подписки.]]></string>
-        <string name="_bx_acl_option_recurring_prioritize"><![CDATA[Отдавать предпочтение подпискам]]></string>
-
-
-	<!-- Pages -->
-	<string name="_bx_acl_page_title_sys_view"><![CDATA[Членство]]></string>
-	<string name="_bx_acl_page_title_view"><![CDATA[Членство]]></string>
-	<string name="_bx_acl_page_block_title_system_view"><![CDATA[Членство]]></string>
-	<string name="_bx_acl_page_block_title_view"><![CDATA[Членство]]></string>
-
-
-        <!-- Menus -->
-        <string name="_bx_acl_lmi_cpt_manage"><![CDATA[Управление]]></string>
-	
-
-	<!-- Forms -->
-	<string name="_bx_acl_form_price"><![CDATA[Цена]]></string>
-	<string name="_bx_acl_form_price_display_add"><![CDATA[Добавление]]></string>
-	<string name="_bx_acl_form_price_display_edit"><![CDATA[Редактирование]]></string>
-	<string name="_bx_acl_form_price_input_sys_id"><![CDATA[ID]]></string>
-	<string name="_bx_acl_form_price_input_sys_level_id"><![CDATA[ID уровня]]></string>
-        <string name="_bx_acl_form_price_input_sys_name"><![CDATA[Имя]]></string>
-	<string name="_bx_acl_form_price_input_name"><![CDATA[Имя]]></string>
-	<string name="_bx_acl_form_price_input_inf_name"><![CDATA[Уникальное значение, которое будет использоваться в серверной части и API запросах]]></string>
-        <string name="_bx_acl_form_price_input_err_name"><![CDATA[Введите уникальное значение]]></string>
-	<string name="_bx_acl_form_price_input_sys_period"><![CDATA[Период]]></string>
-	<string name="_bx_acl_form_price_input_period"><![CDATA[Период]]></string>
-	<string name="_bx_acl_form_price_input_inf_period"><![CDATA[Используйте 0 для пожизненного]]></string>
-	<string name="_bx_acl_form_price_input_sys_period_unit"><![CDATA[Единица измерения периода]]></string>
-	<string name="_bx_acl_form_price_input_period_unit"><![CDATA[Единица измерения периода]]></string>
-	<string name="_bx_acl_form_price_input_inf_period_unit"><![CDATA[Оставьте пустым для пожизненного уровня.]]></string>
-	<string name="_bx_acl_form_price_input_err_period_unit"><![CDATA[Здесь Вам необходимо выбрать единицу измерения периода.]]></string>
-	<string name="_bx_acl_form_price_input_sys_price"><![CDATA[Цена]]></string>
-	<string name="_bx_acl_form_price_input_price"><![CDATA[Цена]]></string>
-	<string name="_bx_acl_form_price_input_inf_price"><![CDATA[Введите 0 или оставьте пустым для создания бесплатного уровня.]]></string>
-	<string name="_bx_acl_form_price_input_sys_trial"><![CDATA[Пробный период]]></string>
+    <string name="_bx_acl"><![CDATA[Платные Уровни]]></string>
+
+
+    <!-- Options -->
+    <string name="_bx_acl_option_recurring_reserve"><![CDATA[Резерв (в днях) на принятие и обработку сообщения об оплате подписки.]]></string>
+    <string name="_bx_acl_option_recurring_prioritize"><![CDATA[Отдавать предпочтение подпискам]]></string>
+
+
+    <!-- Pages -->
+    <string name="_bx_acl_page_title_sys_view"><![CDATA[Членство]]></string>
+    <string name="_bx_acl_page_title_view"><![CDATA[Членство]]></string>
+    <string name="_bx_acl_page_block_title_system_view"><![CDATA[Членство]]></string>
+    <string name="_bx_acl_page_block_title_view"><![CDATA[Членство]]></string>
+
+
+    <!-- Menus -->
+    <string name="_bx_acl_lmi_cpt_manage"><![CDATA[Управление]]></string>
+
+
+    <!-- Forms -->
+    <string name="_bx_acl_form_price"><![CDATA[Цена]]></string>
+    <string name="_bx_acl_form_price_display_add"><![CDATA[Добавление]]></string>
+    <string name="_bx_acl_form_price_display_edit"><![CDATA[Редактирование]]></string>
+    <string name="_bx_acl_form_price_input_sys_id"><![CDATA[ID]]></string>
+    <string name="_bx_acl_form_price_input_sys_level_id"><![CDATA[ID уровня]]></string>
+    <string name="_bx_acl_form_price_input_sys_name"><![CDATA[Имя]]></string>
+    <string name="_bx_acl_form_price_input_name"><![CDATA[Имя]]></string>
+    <string name="_bx_acl_form_price_input_inf_name"><![CDATA[Уникальное значение, которое будет использоваться в серверной части и API запросах]]></string>
+    <string name="_bx_acl_form_price_input_err_name"><![CDATA[Введите уникальное значение]]></string>
+    <string name="_bx_acl_form_price_input_sys_period"><![CDATA[Период]]></string>
+    <string name="_bx_acl_form_price_input_period"><![CDATA[Период]]></string>
+    <string name="_bx_acl_form_price_input_inf_period"><![CDATA[Используйте 0 для пожизненного]]></string>
+    <string name="_bx_acl_form_price_input_sys_period_unit"><![CDATA[Единица измерения периода]]></string>
+    <string name="_bx_acl_form_price_input_period_unit"><![CDATA[Единица измерения периода]]></string>
+    <string name="_bx_acl_form_price_input_inf_period_unit"><![CDATA[Оставьте пустым для пожизненного уровня.]]></string>
+    <string name="_bx_acl_form_price_input_err_period_unit"><![CDATA[Здесь Вам необходимо выбрать единицу измерения периода.]]></string>
+    <string name="_bx_acl_form_price_input_sys_price"><![CDATA[Цена]]></string>
+    <string name="_bx_acl_form_price_input_price"><![CDATA[Цена]]></string>
+    <string name="_bx_acl_form_price_input_inf_price"><![CDATA[Введите 0 или оставьте пустым для создания бесплатного уровня.]]></string>
+    <string name="_bx_acl_form_price_input_sys_trial"><![CDATA[Пробный период]]></string>
     <string name="_bx_acl_form_price_input_trial"><![CDATA[Пробный период]]></string>
     <string name="_bx_acl_form_price_input_inf_trial"><![CDATA[Количество пробных дней, которое новые пользователи получат бесплатно. Оставьте пустым для отключения пробного периода.]]></string>
-	<string name="_bx_acl_form_price_input_sys_do_submit"><![CDATA[Выполнить]]></string>
-	<string name="_bx_acl_form_price_input_do_submit"><![CDATA[Выполнить]]></string>
-	<string name="_bx_acl_form_price_input_sys_do_cancel"><![CDATA[Отменить]]></string>
-	<string name="_bx_acl_form_price_input_do_cancel"><![CDATA[Отменить]]></string>
+    <string name="_bx_acl_form_price_input_sys_immediate"><![CDATA[Активировать немедленно]]></string>
+    <string name="_bx_acl_form_price_input_immediate"><![CDATA[Активировать сразу после покупки]]></string>
+    <string name="_bx_acl_form_price_input_sys_do_submit"><![CDATA[Выполнить]]></string>
+    <string name="_bx_acl_form_price_input_do_submit"><![CDATA[Выполнить]]></string>
+    <string name="_bx_acl_form_price_input_sys_do_cancel"><![CDATA[Отменить]]></string>
+    <string name="_bx_acl_form_price_input_do_cancel"><![CDATA[Отменить]]></string>
 
 
-	<!-- Pre Lists -->
-	<string name="_bx_acl_pre_lists_period_units"><![CDATA[Платежный интервал]]></string>
-	<string name="_bx_acl_pre_values_day"><![CDATA[день]]></string>
-	<string name="_bx_acl_pre_values_week"><![CDATA[неделя]]></string>
-	<string name="_bx_acl_pre_values_month"><![CDATA[месяц]]></string>
-	<string name="_bx_acl_pre_values_year"><![CDATA[год]]></string>
+    <!-- Pre Lists -->
+    <string name="_bx_acl_pre_lists_period_units"><![CDATA[Платежный интервал]]></string>
+    <string name="_bx_acl_pre_values_day"><![CDATA[день]]></string>
+    <string name="_bx_acl_pre_values_week"><![CDATA[неделя]]></string>
+    <string name="_bx_acl_pre_values_month"><![CDATA[месяц]]></string>
+    <string name="_bx_acl_pre_values_year"><![CDATA[год]]></string>
 
 
-	<!-- Grids -->
+    <!-- Grids -->
     <string name="_bx_acl_grid_column_level_icon"><![CDATA[]]></string>
-	<string name="_bx_acl_grid_column_level_name"><![CDATA[Уровень]]></string>
-	<string name="_bx_acl_grid_column_name"><![CDATA[Имя]]></string>
-	<string name="_bx_acl_grid_column_period"><![CDATA[Период]]></string>
-	<string name="_bx_acl_grid_column_price"><![CDATA[Цена]]></string>
-	<string name="_bx_acl_grid_column_trial"><![CDATA[Проба]]></string>
-
-	<string name="_bx_acl_grid_action_add"><![CDATA[Добавить]]></string>
-	<string name="_bx_acl_grid_action_edit"><![CDATA[Редактировать]]></string>
-	<string name="_bx_acl_grid_action_delete"><![CDATA[Удалить]]></string>
-	<string name="_bx_acl_grid_action_buy"><![CDATA[Купить]]></string>
-	<string name="_bx_acl_grid_action_buy_title"><![CDATA[Единовременный платеж]]></string>
-	<string name="_bx_acl_grid_action_subscribe"><![CDATA[Подписаться]]></string>
-	<string name="_bx_acl_grid_action_subscribe_title"><![CDATA[Создать подписку с регулярным выставлением счетов]]></string>
-	<string name="_bx_acl_grid_action_choose"><![CDATA[Выбирать]]></string>
+    <string name="_bx_acl_grid_column_level_name"><![CDATA[Уровень]]></string>
+    <string name="_bx_acl_grid_column_name"><![CDATA[Имя]]></string>
+    <string name="_bx_acl_grid_column_period"><![CDATA[Период]]></string>
+    <string name="_bx_acl_grid_column_price"><![CDATA[Цена]]></string>
+    <string name="_bx_acl_grid_column_trial"><![CDATA[Проба]]></string>
+
+    <string name="_bx_acl_grid_action_add"><![CDATA[Добавить]]></string>
+    <string name="_bx_acl_grid_action_edit"><![CDATA[Редактировать]]></string>
+    <string name="_bx_acl_grid_action_delete"><![CDATA[Удалить]]></string>
+    <string name="_bx_acl_grid_action_buy"><![CDATA[Купить]]></string>
+    <string name="_bx_acl_grid_action_buy_title"><![CDATA[Единовременный платеж]]></string>
+    <string name="_bx_acl_grid_action_subscribe"><![CDATA[Подписаться]]></string>
+    <string name="_bx_acl_grid_action_subscribe_title"><![CDATA[Создать подписку с регулярным выставлением счетов]]></string>
+    <string name="_bx_acl_grid_action_choose"><![CDATA[Выбирать]]></string>
     <string name="_bx_acl_grid_action_choose_title"><![CDATA[Бесплатно переключиться на уровень]]></string>
 
-	<string name="_bx_acl_popup_title_price_add"><![CDATA[Добавить ценовой план]]></string>
-	<string name="_bx_acl_popup_title_price_edit"><![CDATA[Редактировать ценовой план]]></string>
+    <string name="_bx_acl_popup_title_price_add"><![CDATA[Добавить ценовой план]]></string>
+    <string name="_bx_acl_popup_title_price_edit"><![CDATA[Редактировать ценовой план]]></string>
 
 
-	<!-- Menus -->
-	<string name="_bx_acl_menu_item_title_system_acl_view"><![CDATA[Членство]]></string>
-	<string name="_bx_acl_menu_item_title_acl_view"><![CDATA[Членство]]></string>
+    <!-- Menus -->
+    <string name="_bx_acl_menu_item_title_system_acl_view"><![CDATA[Членство]]></string>
+    <string name="_bx_acl_menu_item_title_acl_view"><![CDATA[Членство]]></string>
 
 
-	<!-- Common Texts -->
-	<string name="_bx_acl_msg_empty_owner"><![CDATA['Главный продавец' не задан. Вам необходимо пометить какой-либо профайл как 'Главный продавец' в настройках активного на данный момент платежного модуля.]]></string>
-	<string name="_bx_acl_msg_performed"><![CDATA[Действие было успешно выполнено.]]></string>
-	<string name="_bx_acl_err_price_duplicate"><![CDATA[Аналогичный ценовой план уже существует.]]></string>
-	<string name="_bx_acl_err_cannot_perform"><![CDATA[Невозможно выполнить действие. Пожалуйста, оповестите нас об этой проблеме.]]></string>
+    <!-- Email Templates -->
+    <string name="_bx_acl_et_txt_name_subscription_cancel_required"><![CDATA[Требуется отмена подписки.]]></string>
+    <string name="_bx_acl_et_txt_subject_subscription_cancel_required"><![CDATA[Требуется отмена подписки.]]></string>
+    <string name="_bx_acl_et_txt_body_subscription_cancel_required"><![CDATA[{email_header}
+<p>Привет,</p>
+<p>Вы оплатили новый уровень членства, но подписка вашего старого уровня все ещё активна. Вам нужно отменить её вручную.</p>
+<p>Информация о старой подписке:<br />Уровень членства: {level}<br />Номер заказа: {order}<br />Дата начала: {date_starts}<br />Дата окончания: {date_expires}</p>
+{email_footer}]]></string>
 
-	<string name="_bx_acl_txt_select_level"><![CDATA[Выберите уровень...]]></string>
-	<string name="_bx_acl_txt_cart_item_title"><![CDATA[{0} ({1} {2})]]></string>
-        <string name="_bx_acl_txt_cart_item_title_lifetime"><![CDATA[{0} (пожизненно)]]></string>
-        <string name="_bx_acl_txt_cart_item_description"><![CDATA[Членство на сайте {0}.]]></string>
-	<string name="_bx_acl_txt_upgrade"><![CDATA[Улучшить]]></string>
-	<string name="_bx_acl_txt_manage"><![CDATA[Изменить]]></string>
-	<string name="_bx_acl_txt_n_unit"><![CDATA[{0} ({1})]]></string>
+
+    <!-- Common Texts -->
+    <string name="_bx_acl_msg_empty_owner"><![CDATA['Главный продавец' не задан. Вам необходимо пометить какой-либо профайл как 'Главный продавец' в настройках активного на данный момент платежного модуля.]]></string>
+    <string name="_bx_acl_msg_performed"><![CDATA[Действие было успешно выполнено.]]></string>
+    <string name="_bx_acl_err_price_duplicate"><![CDATA[Аналогичный ценовой план уже существует.]]></string>
+    <string name="_bx_acl_err_cannot_perform"><![CDATA[Невозможно выполнить действие. Пожалуйста, оповестите нас об этой проблеме.]]></string>
+
+    <string name="_bx_acl_txt_select_level"><![CDATA[Выберите уровень...]]></string>
+    <string name="_bx_acl_txt_cart_item_title"><![CDATA[{0} ({1} {2})]]></string>
+    <string name="_bx_acl_txt_cart_item_title_lifetime"><![CDATA[{0} (пожизненно)]]></string>
+    <string name="_bx_acl_txt_cart_item_description"><![CDATA[Членство на сайте {0}.]]></string>
+    <string name="_bx_acl_txt_upgrade"><![CDATA[Улучшить]]></string>
+    <string name="_bx_acl_txt_manage"><![CDATA[Изменить]]></string>
+    <string name="_bx_acl_txt_n_unit"><![CDATA[{0} ({1})]]></string>
     <string name="_bx_acl_txt_none"><![CDATA[нет]]></string>
     <string name="_bx_acl_txt_free"><![CDATA[бесплатно]]></string>
     <string name="_bx_acl_txt_lifetime"><![CDATA[пожизненно]]></string>