AntonLV 9 лет назад
Родитель
Сommit
6aea61d7e7

+ 24 - 0
modules/base/text/classes/BxBaseModTextDb.php

@@ -24,6 +24,30 @@ class BxBaseModTextDb extends BxBaseModGeneralDb
         $sQuery = $this->prepare ("SELECT * FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` WHERE `" . $this->_oConfig->CNF['FIELD_ID'] . "` = ?", $iContentId);
         return $this->getRow($sQuery);
     }
+
+    public function searchByAuthorTerm($iAuthor, $sTerm, $iLimit)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if (empty($CNF['FIELDS_QUICK_SEARCH']))
+            return array();
+
+		$aBindings = array(
+		    'author' => $iAuthor
+		);
+
+        $sWhere = '';
+        foreach ($CNF['FIELDS_QUICK_SEARCH'] as $sField) {
+        	$aBindings[$sField] = '%' . $sTerm . '%';
+
+            $sWhere .= " OR `c`.`$sField` LIKE :" . $sField;
+        }
+
+        $sOrderBy = $this->prepareAsString(" ORDER BY `c`.`added` DESC LIMIT ?", (int)$iLimit);
+
+        $sQuery = "SELECT `c`.* FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `c` WHERE `c`.`" . $CNF['FIELD_AUTHOR'] . "`=:author AND (0 $sWhere)" . $sOrderBy;
+        return $this->getAll($sQuery, $aBindings);
+    }
 }
 
 /** @} */

+ 3 - 0
modules/boonex/market/classes/BxMarketConfig.php

@@ -61,6 +61,7 @@ class BxMarketConfig extends BxBaseModTextConfig
 			'FIELD_ALLOW_PURCHASE_TO' => 'allow_purchase_to',
 			'FIELD_ALLOW_COMMENT_TO' => 'allow_comment_to',
 			'FIELD_ALLOW_VOTE_TO' => 'allow_vote_to',
+            'FIELD_SUBENTRIES' => 'subentries',
             'FIELD_PHOTO' => 'pictures',
             'FIELD_THUMB' => 'thumb',
         	'FIELD_COVER' => 'cover',
@@ -71,6 +72,7 @@ class BxMarketConfig extends BxBaseModTextConfig
         	'FIELD_STATUS' => 'status',
         	'FIELD_STATUS_ADMIN' => 'status_admin',
             'FIELD_LOCATION_PREFIX' => 'location',
+        	'FIELDS_QUICK_SEARCH' => array('title'),
             'FIELDS_WITH_KEYWORDS' => 'auto', // can be 'auto', array of fields or comma separated string of field names, works only when OBJECT_METATAGS is specified
 
             // page URIs
@@ -133,6 +135,7 @@ class BxMarketConfig extends BxBaseModTextConfig
         	'OBJECT_GRID_COMMON' => 'bx_market_common',
         	'OBJECT_GRID_LICENSES' => 'bx_market_licenses',
             'OBJECT_UPLOADERS' => array('bx_market_simple', 'bx_market_html5'),
+        	'OBJECT_CONNECTION_SUBENTRIES' => 'bx_market_subentries',
 
             // menu items which visibility depends on custom visibility checking
             'MENU_ITEM_TO_METHOD' => array (

+ 60 - 11
modules/boonex/market/classes/BxMarketDb.php

@@ -294,23 +294,49 @@ class BxMarketDb extends BxBaseModTextDb
 				$aMethod['name'] = "getRow";
 				$aMethod['params'][1] = array(
                 	'profile_id' => $aParams['profile_id'],
-					'file_id' => $aParams['file_id'],
 					'key' => $aParams['key']
                 );
 
-				$sJoinClause .= " LEFT JOIN `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `te` ON `tl`.`product_id`=`te`.`" . $CNF['FIELD_ID'] . "`";
-				$sWhereClause .= " AND `tl`.`profile_id`=:profile_id AND `te`.`" . $CNF['FIELD_PACKAGE'] . "`=:file_id AND (`tl`.`domain`='' OR `tl`.`domain`=:key)";
+				$iProductId = $this->getOne("SELECT `" . $CNF['FIELD_ID'] . "` FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` WHERE `" . $CNF['FIELD_PACKAGE'] . "`=:file_id LIMIT 1", array(
+				    'file_id' => $aParams['file_id']
+				));
+
+				if(isset($aParams['parent']) && (int)$aParams['parent'] == 1) {
+				    $oConnnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTION_SUBENTRIES']);
+                    $aConnectionSql = $oConnnection->getConnectedInitiatorsAsSQLParts('tl', 'product_id', $iProductId);
+
+                    $sJoinClause .= $aConnectionSql['join'];
+				}
+				else {
+                    $aMethod['params'][1]['product_id'] = $iProductId;
+
+                    $sWhereClause .= " AND `tl`.`product_id`=:product_id";
+				}
+
+				$sWhereClause .= " AND `tl`.`profile_id`=:profile_id AND (`tl`.`domain`='' OR `tl`.`domain`=:key)";
+				$sLimitClause = "1";
 				break;
 
 			case 'has_by':
 				$aMethod['name'] = "getOne";
 				$aMethod['params'][1] = array(
-                	'profile_id' => $aParams['profile_id'],
-    				'product_id' => $aParams['product_id'],
+                	'profile_id' => $aParams['profile_id']
                 );
 
                 $sSelectClause = "`tl`.`id`";
-                $sWhereClause = " AND `tl`.`profile_id`=:profile_id AND `tl`.`product_id`=:product_id";
+                $sWhereClause = " AND `tl`.`profile_id`=:profile_id";
+
+                if(isset($aParams['parent']) && (int)$aParams['parent'] == 1) {
+                    $oConnnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTION_SUBENTRIES']);
+                    $aConnectionSql = $oConnnection->getConnectedInitiatorsAsSQLParts('tl', 'product_id', $aParams['product_id']);
+
+                    $sJoinClause .= $aConnectionSql['join'];
+                }
+                else {
+                    $aMethod['params'][1]['product_id'] = $aParams['product_id'];
+
+                    $sWhereClause .= " AND `tl`.`product_id`=:product_id";
+                }
 
                 if(!empty($aParams['domain'])) {
                 	$aMethod['params'][1]['domain'] = $aParams['domain'];
@@ -351,24 +377,47 @@ class BxMarketDb extends BxBaseModTextDb
         return (int)$this->query($sQuery) > 0;
     }
 
+    //TODO: Check both "direct" and "by_parent" here. Do the same for getLicense service. 
     public function hasLicense ($iProfileId, $iProductId, $sDomain = '')
     {
-    	return (int)$this->getLicense(array(
+        $aParams = array(
     		'type' => 'has_by', 
     		'profile_id' => $iProfileId, 
     		'product_id' => $iProductId, 
     		'domain' => !empty($sDomain) ? $sDomain : ''
-    	)) > 0;
+    	);
+ 
+        $iLicenseId = (int)$this->getLicense($aParams);
+    	if($iLicenseId > 0)
+    	    return true;
+
+        $aParams['parent'] = 1;
+        $iLicenseId = (int)$this->getLicense($aParams);
+    	if($iLicenseId > 0)
+    	    return true;
+
+    	return false;
     }
 
 	public function hasLicenseByOrder ($iProfileId, $iProductId, $sOrder = '')
     {
-    	return (int)$this->getLicense(array(
+        $aParams = array(
     		'type' => 'has_by', 
     		'profile_id' => $iProfileId, 
     		'product_id' => $iProductId, 
     		'order' => !empty($sOrder) ? $sOrder : ''
-    	)) > 0;
+    	);
+
+        $iLicenseId = (int)$this->getLicense($aParams);
+        if($iLicenseId > 0)
+    	    return true;
+
+        $aParams['parent'] = 1;
+        $iLicenseId = (int)$this->getLicense($aParams);
+    	if($iLicenseId > 0)
+    	    return true;
+
+    	return false;
     }
 
 	public function registerLicense($iProfileId, $iProductId, $iCount, $sOrder, $sLicense, $sType, $sDuration = '', $iTrial = 0)
@@ -465,7 +514,7 @@ class BxMarketDb extends BxBaseModTextDb
         }
 
         $sQuery = "DELETE FROM `" . $sTable . "` WHERE 1 " . $sWhere;
-        return $this->query($sQuery, $aBindings);
+        return $this->query($sQuery, $aBindings) !== false;
     }
 
 	protected function _getAttachment($sTable, $aParams = array())

+ 47 - 0
modules/boonex/market/classes/BxMarketFormEntry.php

@@ -82,6 +82,9 @@ class BxMarketFormEntry extends BxBaseModTextFormEntry
 			$this->aInputs[$CNF['FIELD_ALLOW_VOTE_TO']] = BxDolPrivacy::getGroupChooser($CNF['OBJECT_PRIVACY_VOTE'], $iProfileId, array('dynamic_groups' => $aDynamicGroups));
 			$this->aInputs[$CNF['FIELD_ALLOW_VOTE_TO']]['db']['pass'] = 'Xss';
 		}
+
+		if(isset($this->aInputs[$CNF['FIELD_SUBENTRIES']]) && $this->_oModule->checkAllowedSetSubentries() !== CHECK_ACTION_RESULT_ALLOWED)
+		    unset($this->aInputs[$CNF['FIELD_SUBENTRIES']]);
     }
 
 	function initChecker ($aValues = array (), $aSpecificValues = array())
@@ -99,6 +102,12 @@ class BxMarketFormEntry extends BxBaseModTextFormEntry
             $this->aInputs[$CNF['FIELD_FILE']]['ghost_template'] = $this->_oModule->_oTemplate->getGhostTemplateFile($this, $aContentInfo);
         }
 
+        if(isset($this->aInputs[$CNF['FIELD_SUBENTRIES']]) && !empty($aValues[$CNF['FIELD_ID']])) {
+            $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTION_SUBENTRIES']);
+            if($oConnection)
+		        $this->aInputs[$CNF['FIELD_SUBENTRIES']]['value'] = $oConnection->getConnectedContent((int)$aValues[$CNF['FIELD_ID']]);
+		}
+
         return parent::initChecker($aValues, $aSpecificValues);
     }
 
@@ -214,6 +223,44 @@ class BxMarketFormEntry extends BxBaseModTextFormEntry
 
     	return $aResult;
 	}
+
+    protected function genCustomInputSubentries($aInput)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sGetSubentriesUrl = BX_DOL_URL_ROOT . $this->_oModule->_oConfig->getBaseUri() . 'get_subentries';
+        $sPlaceholder = bx_html_attribute(_t('_bx_market_form_entry_input_subentries_placeholder'), BX_ESCAPE_STR_QUOTE);
+
+        $this->addJsJQueryUI();
+
+        $this->oTemplate->addJs(array(
+            'jquery.form.min.js',
+        ));
+
+        $sVals = '';
+        if (!empty($aInput['value']) && is_array($aInput['value'])) {
+            foreach ($aInput['value'] as $iValue) {
+                $iValue = (int)$iValue;
+                if(!$iValue)
+                    continue;
+
+                $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iValue);
+                if(empty($aContentInfo) || !is_array($aContentInfo))
+                    continue;
+                
+               $sVals .= '<b class="val bx-def-color-bg-hl bx-def-round-corners">' . $aContentInfo[$CNF['FIELD_TITLE']] . '<input type="hidden" name="' . $aInput['name'] . '[]" value="' . $iValue . '" /></b>';
+            }
+            $sVals = trim($sVals, ',');
+        }
+
+        return $this->oTemplate->parseHtmlByName('form_field_custom_suggestions.html', array(
+            'id' => $aInput['name'] . time(),
+            'url_get_recipients' => $sGetSubentriesUrl,
+            'name' => $aInput['name'],
+            'placeholder' => $sPlaceholder,
+            'vals' => $sVals,
+        ));
+    }
 }
 
 /** @} */

+ 59 - 0
modules/boonex/market/classes/BxMarketFormsEntryHelper.php

@@ -18,6 +18,65 @@ class BxMarketFormsEntryHelper extends BxBaseModTextFormsEntryHelper
     {
         parent::__construct($oModule);
     }
+
+    public function onDataAddAfter($iAccountId, $iContentId)
+    {
+        $s = parent::onDataAddAfter($iAccountId, $iContentId);
+        if(!empty($s))
+            return $s;
+
+        $this->associateSubentries($iContentId, bx_get('subentries'));
+
+        return '';
+    }
+
+    public function onDataEditAfter($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)
+    {
+        $s = parent::onDataEditAfter($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm);
+        if(!empty($s))
+            return $s;
+
+        $this->associateSubentries($iContentId, bx_get('subentries'));
+
+        return '';
+    }
+
+    public function onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTION_SUBENTRIES']);
+        if(!$oConnection)
+            return '';
+
+        $oConnection->onDeleteInitiatorAndContent($iContentId);
+
+        return '';
+    }
+
+    protected function associateSubentries($iContentId, $aSubentries)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(empty($aSubentries) || !is_array($aSubentries))
+            return false;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTION_SUBENTRIES']);
+        if(!$oConnection)
+            return false;
+
+        $oConnection->onDeleteInitiator((int)$iContentId);
+
+        $iProcessed = 0;
+        foreach($aSubentries as $iSubentry) {
+            if(!$oConnection->addConnection((int)$iContentId, (int)$iSubentry))
+                continue;
+
+            $iProcessed += 1;
+        }
+
+        return count($aSubentries) == $iProcessed;
+    }
 }
 
 /** @} */

+ 31 - 0
modules/boonex/market/classes/BxMarketModule.php

@@ -39,6 +39,28 @@ class BxMarketModule extends BxBaseModTextModule
     	));
     }
 
+    public function actionGetSubentries()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sTerm = bx_get('term');
+        $iLimit = (int)bx_get('limit');
+        if(empty($iLimit))
+            $iLimit = 10;
+
+        $aResult = array();
+        $aEntries = $this->_oDb->searchByAuthorTerm($this->_iProfileId, $sTerm, $iLimit);
+        foreach ($aEntries as $aEntry)
+            $aResult[] = array('value' => $aEntry[$CNF['FIELD_ID']], 'label' => $aEntry[$CNF['FIELD_TITLE']]);
+
+        // sort result
+        usort($aResult, function($r1, $r2) {
+            return strcmp($r1['label'], $r2['label']);
+        });
+
+        echoJson(array_slice($aResult, 0, $iLimit));
+    }
+
     public function serviceGetSearchableFields ()
     {
         $CNF = &$this->_oConfig->CNF;
@@ -450,6 +472,15 @@ class BxMarketModule extends BxBaseModTextModule
         return CHECK_ACTION_RESULT_ALLOWED;
     }
 
+    public function checkAllowedSetSubentries ()
+    {
+        $aCheck = checkActionModule($this->_iProfileId, 'set subentries', $this->getName(), false);
+        if($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
 	public function checkAllowedDownload($aDataEntry, $isPerformAction = false)
     {
         $CNF = &$this->_oConfig->CNF;

+ 5 - 0
modules/boonex/market/install/langs/en.xml

@@ -91,12 +91,17 @@
     <string name="_bx_market_form_entry_input_changed"><![CDATA[Updated]]></string>
     <string name="_bx_market_form_entry_input_sys_warning_single"><![CDATA[Warning for single payments]]></string>
     <string name="_bx_market_form_entry_input_sys_warning_recurring"><![CDATA[Warning for recurring payments]]></string>
+    <string name="_bx_market_form_entry_input_sys_subentries"><![CDATA[Subproducts]]></string>
+    <string name="_bx_market_form_entry_input_subentries"><![CDATA[Subproducts]]></string>
+    <string name="_bx_market_form_entry_input_subentries_inf"><![CDATA[Leave empty if you don't want to create association.]]></string>
+    <string name="_bx_market_form_entry_input_subentries_placeholder"><![CDATA[Type product name here...]]></string>
 
 	<string name="_bx_market_acl_action_create_entry"><![CDATA[Create Product]]></string>
 	<string name="_bx_market_acl_action_delete_entry"><![CDATA[Delete Product]]></string>
 	<string name="_bx_market_acl_action_download_entry"><![CDATA[Download Product]]></string>
 	<string name="_bx_market_acl_action_view_entry"><![CDATA[View Product]]></string>
 	<string name="_bx_market_acl_action_set_cover"><![CDATA[Set Product Cover]]></string>
+	<string name="_bx_market_acl_action_set_subentries"><![CDATA[Set Product Subproducts]]></string>
 	<string name="_bx_market_acl_action_set_thumb"><![CDATA[Set Product Thumbnail]]></string>
 	<string name="_bx_market_acl_action_edit_any_entry"><![CDATA[Edit Any Product]]></string>
 

+ 3 - 0
modules/boonex/market/install/sql/disable.sql

@@ -25,6 +25,9 @@ DELETE FROM `sys_acl_actions` WHERE `Module` = 'bx_market';
 -- SEARCH
 DELETE FROM `sys_objects_search` WHERE `ObjectName` IN ('bx_market', 'bx_market_cmts');
 
+-- CONNECTIONS
+DELETE FROM `sys_objects_connection` WHERE `object` = 'bx_market_subentries';
+
 -- METATAGS
 DELETE FROM `sys_objects_metatags` WHERE `object` = 'bx_market';
 

+ 14 - 0
modules/boonex/market/install/sql/enable.sql

@@ -317,6 +317,10 @@ INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`,
 ('bx_market', 'set cover', NULL, '_bx_market_acl_action_set_cover', '', 1, 3);
 SET @iIdActionSetCover = LAST_INSERT_ID();
 
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_market', 'set subentries', NULL, '_bx_market_acl_action_set_subentries', '', 1, 3);
+SET @iIdActionSetSubentries = LAST_INSERT_ID();
+
 INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
 ('bx_market', 'edit any entry', NULL, '_bx_market_acl_action_edit_any_entry', '', 1, 3);
 SET @iIdActionEntryEditAny = LAST_INSERT_ID();
@@ -373,6 +377,12 @@ INSERT INTO `sys_acl_matrix` (`IDLevel`, `IDAction`) VALUES
 (@iAdministrator, @iIdActionSetCover),
 (@iPremium, @iIdActionSetCover),
 
+-- set entry subentries
+(@iStandard, @iIdActionSetSubentries),
+(@iModerator, @iIdActionSetSubentries),
+(@iAdministrator, @iIdActionSetSubentries),
+(@iPremium, @iIdActionSetSubentries),
+
 -- edit any entry
 (@iModerator, @iIdActionEntryEditAny),
 (@iAdministrator, @iIdActionEntryEditAny);
@@ -384,6 +394,10 @@ INSERT INTO `sys_objects_search` (`ObjectName`, `Title`, `Order`, `ClassName`, `
 ('bx_market', '_bx_market', @iSearchOrder + 1, 'BxMarketSearchResult', 'modules/boonex/market/classes/BxMarketSearchResult.php'),
 ('bx_market_cmts', '_bx_market_cmts', @iSearchOrder + 2, 'BxMarketCmtsSearchResult', 'modules/boonex/market/classes/BxMarketCmtsSearchResult.php');
 
+-- CONNECTIONS
+INSERT INTO `sys_objects_connection` (`object`, `table`, `type`, `override_class_name`, `override_class_file`) VALUES
+('bx_market_subentries', 'bx_market_subproducts', 'one-way', '', '');
+
 -- METATAGS
 INSERT INTO `sys_objects_metatags` (`object`, `table_keywords`, `table_locations`, `table_mentions`, `override_class_name`, `override_class_file`) VALUES
 ('bx_market', 'bx_market_meta_keywords', 'bx_market_meta_locations', '', '', '');

+ 67 - 104
modules/boonex/market/install/sql/install.sql

@@ -37,6 +37,17 @@ CREATE TABLE IF NOT EXISTS `bx_market_products` (
   FULLTEXT KEY `title_text` (`title`,`text`)
 );
 
+-- TABLE: subentries
+CREATE TABLE IF NOT EXISTS `bx_market_subproducts` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `initiator` int(11) NOT NULL,
+  `content` int(11) NOT NULL,
+  `added` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `initiator` (`initiator`,`content`),
+  KEY `content` (`content`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
 -- TABLE: licenses
 CREATE TABLE IF NOT EXISTS `bx_market_licenses` (
   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
@@ -316,117 +327,69 @@ INSERT INTO `sys_form_inputs`(`object`, `module`, `name`, `value`, `values`, `ch
 ('bx_market', 'bx_market', 'header_end_recurring', '', '', 0, 'block_end', '_bx_market_form_entry_input_sys_header_end_recurring', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
 ('bx_market', 'bx_market', 'header_end_privacy', '', '', 0, 'block_end', '_bx_market_form_entry_input_sys_header_end_privacy', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
 ('bx_market', 'bx_market', 'warning_single', '_bx_market_err_not_accept_payments_single', '', 0, 'value', '_bx_market_form_entry_input_sys_warning_single', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
-('bx_market', 'bx_market', 'warning_recurring', '_bx_market_err_not_accept_payments_recurring', '', 0, 'value', '_bx_market_form_entry_input_sys_warning_recurring', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0);
+('bx_market', 'bx_market', 'warning_recurring', '_bx_market_err_not_accept_payments_recurring', '', 0, 'value', '_bx_market_form_entry_input_sys_warning_recurring', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_market', 'bx_market', 'subentries', '', '', 0, 'custom', '_bx_market_form_entry_input_sys_subentries', '_bx_market_form_entry_input_subentries', '_bx_market_form_entry_input_subentries_inf', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0);
 
 
 INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES 
-('bx_market_entry_add', 'delete_confirm', 2147483647, 0, 1),
-('bx_market_entry_add', 'title', 2147483647, 1, 2),
-('bx_market_entry_add', 'name', 2147483647, 1, 3),
-('bx_market_entry_add', 'cat', 2147483647, 1, 4),
-('bx_market_entry_add', 'text', 2147483647, 1, 5),
-('bx_market_entry_add', 'pictures', 2147483647, 1, 6),
-('bx_market_entry_add', 'files', 2147483647, 1, 7),
-('bx_market_entry_add', 'header_beg_single', 2147483647, 1, 8),
-('bx_market_entry_add', 'warning_single', 2147483647, 1, 9),
-('bx_market_entry_add', 'price_single', 2147483647, 1, 10),
-('bx_market_entry_add', 'header_end_single', 2147483647, 1, 11),
-('bx_market_entry_add', 'header_beg_recurring', 2147483647, 1, 12),
-('bx_market_entry_add', 'warning_recurring', 2147483647, 1, 13),
-('bx_market_entry_add', 'duration_recurring', 2147483647, 1, 14),
-('bx_market_entry_add', 'price_recurring', 2147483647, 1, 15),
-('bx_market_entry_add', 'trial_recurring', 2147483647, 1, 16),
-('bx_market_entry_add', 'header_end_recurring', 2147483647, 1, 17),
-('bx_market_entry_add', 'header_beg_privacy', 2147483647, 1, 18),
-('bx_market_entry_add', 'allow_view_to', 2147483647, 1, 19),
-('bx_market_entry_add', 'allow_purchase_to', 2147483647, 1, 20),
-('bx_market_entry_add', 'allow_comment_to', 2147483647, 1, 21),
-('bx_market_entry_add', 'allow_vote_to', 2147483647, 1, 22),
-('bx_market_entry_add', 'header_end_privacy', 2147483647, 1, 23),
-('bx_market_entry_add', 'notes', 2147483647, 1, 24),
-('bx_market_entry_add', 'location', 2147483647, 1, 25),
-('bx_market_entry_add', 'do_submit', 2147483647, 0, 26),
-('bx_market_entry_add', 'do_publish', 2147483647, 1, 27),
-
-('bx_market_entry_delete', 'notes', 2147483647, 0, 0),
-('bx_market_entry_delete', 'location', 2147483647, 0, 0),
-('bx_market_entry_delete', 'cat', 2147483647, 0, 0),
-('bx_market_entry_delete', 'header_beg_single', 2147483647, 0, 0),
-('bx_market_entry_delete', 'price_single', 2147483647, 0, 0),
-('bx_market_entry_delete', 'header_end_single', 2147483647, 0, 0),
-('bx_market_entry_delete', 'header_beg_recurring', 2147483647, 0, 0),
-('bx_market_entry_delete', 'duration_recurring', 2147483647, 0, 0),
-('bx_market_entry_delete', 'price_recurring', 2147483647, 0, 0),
-('bx_market_entry_delete', 'trial_recurring', 2147483647, 0, 0),
-('bx_market_entry_delete', 'header_end_recurring', 2147483647, 0, 0),
-('bx_market_entry_delete', 'pictures', 2147483647, 0, 0),
-('bx_market_entry_delete', 'files', 2147483647, 0, 0),
-('bx_market_entry_delete', 'text', 2147483647, 0, 0),
-('bx_market_entry_delete', 'do_publish', 2147483647, 0, 0),
-('bx_market_entry_delete', 'title', 2147483647, 0, 0),
-('bx_market_entry_delete', 'name', 2147483647, 0, 0),
-('bx_market_entry_delete', 'header_beg_privacy', 2147483647, 0, 0),
-('bx_market_entry_delete', 'allow_view_to', 2147483647, 0, 0),
-('bx_market_entry_delete', 'allow_purchase_to', 2147483647, 0, 0),
-('bx_market_entry_delete', 'allow_comment_to', 2147483647, 0, 0),
-('bx_market_entry_delete', 'allow_vote_to', 2147483647, 0, 0),
-('bx_market_entry_delete', 'header_end_privacy', 2147483647, 0, 0),
+('bx_market_entry_add', 'title', 2147483647, 1, 1),
+('bx_market_entry_add', 'name', 2147483647, 1, 2),
+('bx_market_entry_add', 'cat', 2147483647, 1, 3),
+('bx_market_entry_add', 'text', 2147483647, 1, 4),
+('bx_market_entry_add', 'pictures', 2147483647, 1, 5),
+('bx_market_entry_add', 'files', 2147483647, 1, 6),
+('bx_market_entry_add', 'header_beg_single', 2147483647, 1, 7),
+('bx_market_entry_add', 'warning_single', 2147483647, 1, 8),
+('bx_market_entry_add', 'price_single', 2147483647, 1, 9),
+('bx_market_entry_add', 'header_end_single', 2147483647, 1, 10),
+('bx_market_entry_add', 'header_beg_recurring', 2147483647, 1, 11),
+('bx_market_entry_add', 'warning_recurring', 2147483647, 1, 12),
+('bx_market_entry_add', 'duration_recurring', 2147483647, 1, 13),
+('bx_market_entry_add', 'price_recurring', 2147483647, 1, 14),
+('bx_market_entry_add', 'trial_recurring', 2147483647, 1, 15),
+('bx_market_entry_add', 'header_end_recurring', 2147483647, 1, 16),
+('bx_market_entry_add', 'header_beg_privacy', 2147483647, 1, 17),
+('bx_market_entry_add', 'allow_view_to', 2147483647, 1, 18),
+('bx_market_entry_add', 'allow_purchase_to', 2147483647, 1, 19),
+('bx_market_entry_add', 'allow_comment_to', 2147483647, 1, 20),
+('bx_market_entry_add', 'allow_vote_to', 2147483647, 1, 21),
+('bx_market_entry_add', 'header_end_privacy', 2147483647, 1, 22),
+('bx_market_entry_add', 'notes', 2147483647, 1, 23),
+('bx_market_entry_add', 'location', 2147483647, 1, 24),
+('bx_market_entry_add', 'subentries', 2147483647, 1, 25),
+('bx_market_entry_add', 'do_publish', 2147483647, 1, 26),
+
 ('bx_market_entry_delete', 'delete_confirm', 2147483647, 1, 1),
 ('bx_market_entry_delete', 'do_submit', 2147483647, 1, 2),
 
-('bx_market_entry_edit', 'do_publish', 2147483647, 0, 1),
-('bx_market_entry_edit', 'delete_confirm', 2147483647, 0, 2),
-('bx_market_entry_edit', 'title', 2147483647, 1, 3),
-('bx_market_entry_edit', 'name', 2147483647, 1, 4),
-('bx_market_entry_edit', 'cat', 2147483647, 1, 5),
-('bx_market_entry_edit', 'text', 2147483647, 1, 6),
-('bx_market_entry_edit', 'pictures', 2147483647, 1, 7),
-('bx_market_entry_edit', 'files', 2147483647, 1, 8),
-('bx_market_entry_edit', 'header_beg_single', 2147483647, 1, 9),
-('bx_market_entry_edit', 'warning_single', 2147483647, 1, 10),
-('bx_market_entry_edit', 'price_single', 2147483647, 1, 11),
-('bx_market_entry_edit', 'header_end_single', 2147483647, 1, 12),
-('bx_market_entry_edit', 'header_beg_recurring', 2147483647, 1, 13),
-('bx_market_entry_edit', 'warning_recurring', 2147483647, 1, 14),
-('bx_market_entry_edit', 'duration_recurring', 2147483647, 1, 15),
-('bx_market_entry_edit', 'price_recurring', 2147483647, 1, 16),
-('bx_market_entry_edit', 'trial_recurring', 2147483647, 1, 17),
-('bx_market_entry_edit', 'header_end_recurring', 2147483647, 1, 18),
-('bx_market_entry_edit', 'header_beg_privacy', 2147483647, 1, 19),
-('bx_market_entry_edit', 'allow_view_to', 2147483647, 1, 20),
-('bx_market_entry_edit', 'allow_purchase_to', 2147483647, 1, 21),
-('bx_market_entry_edit', 'allow_comment_to', 2147483647, 1, 22),
-('bx_market_entry_edit', 'allow_vote_to', 2147483647, 1, 23),
-('bx_market_entry_edit', 'header_end_privacy', 2147483647, 1, 24),
-('bx_market_entry_edit', 'notes', 2147483647, 1, 25),
-('bx_market_entry_edit', 'location', 2147483647, 1, 26),
-('bx_market_entry_edit', 'do_submit', 2147483647, 1, 27),
-
-('bx_market_entry_view', 'notes', 2147483647, 0, 0),
-('bx_market_entry_view', 'location', 2147483647, 0, 0),
+('bx_market_entry_edit', 'title', 2147483647, 1, 1),
+('bx_market_entry_edit', 'name', 2147483647, 1, 2),
+('bx_market_entry_edit', 'cat', 2147483647, 1, 3),
+('bx_market_entry_edit', 'text', 2147483647, 1, 4),
+('bx_market_entry_edit', 'pictures', 2147483647, 1, 5),
+('bx_market_entry_edit', 'files', 2147483647, 1, 6),
+('bx_market_entry_edit', 'header_beg_single', 2147483647, 1, 7),
+('bx_market_entry_edit', 'warning_single', 2147483647, 1, 8),
+('bx_market_entry_edit', 'price_single', 2147483647, 1, 9),
+('bx_market_entry_edit', 'header_end_single', 2147483647, 1, 10),
+('bx_market_entry_edit', 'header_beg_recurring', 2147483647, 1, 11),
+('bx_market_entry_edit', 'warning_recurring', 2147483647, 1, 12),
+('bx_market_entry_edit', 'duration_recurring', 2147483647, 1, 13),
+('bx_market_entry_edit', 'price_recurring', 2147483647, 1, 14),
+('bx_market_entry_edit', 'trial_recurring', 2147483647, 1, 15),
+('bx_market_entry_edit', 'header_end_recurring', 2147483647, 1, 16),
+('bx_market_entry_edit', 'header_beg_privacy', 2147483647, 1, 17),
+('bx_market_entry_edit', 'allow_view_to', 2147483647, 1, 18),
+('bx_market_entry_edit', 'allow_purchase_to', 2147483647, 1, 19),
+('bx_market_entry_edit', 'allow_comment_to', 2147483647, 1, 20),
+('bx_market_entry_edit', 'allow_vote_to', 2147483647, 1, 21),
+('bx_market_entry_edit', 'header_end_privacy', 2147483647, 1, 22),
+('bx_market_entry_edit', 'notes', 2147483647, 1, 23),
+('bx_market_entry_edit', 'location', 2147483647, 1, 24),
+('bx_market_entry_edit', 'subentries', 2147483647, 1, 25),
+('bx_market_entry_edit', 'do_submit', 2147483647, 1, 26),
+
 ('bx_market_entry_view', 'cat', 2147483647, 1, 1),
-('bx_market_entry_view', 'header_beg_single', 2147483647, 0, 0),
-('bx_market_entry_view', 'price_single', 2147483647, 0, 0),
-('bx_market_entry_view', 'header_end_single', 2147483647, 0, 0),
-('bx_market_entry_view', 'header_beg_recurring', 2147483647, 0, 0),
-('bx_market_entry_view', 'duration_recurring', 2147483647, 0, 0),
-('bx_market_entry_view', 'price_recurring', 2147483647, 0, 0),
-('bx_market_entry_view', 'trial_recurring', 2147483647, 0, 0),
-('bx_market_entry_view', 'header_end_recurring', 2147483647, 0, 0),
-('bx_market_entry_view', 'pictures', 2147483647, 0, 0),
-('bx_market_entry_view', 'files', 2147483647, 0, 0),
-('bx_market_entry_view', 'delete_confirm', 2147483647, 0, 0),
-('bx_market_entry_view', 'text', 2147483647, 0, 0),
-('bx_market_entry_view', 'do_publish', 2147483647, 0, 0),
-('bx_market_entry_view', 'title', 2147483647, 0, 0),
-('bx_market_entry_view', 'name', 2147483647, 0, 0),
-('bx_market_entry_view', 'do_submit', 2147483647, 0, 0),
-('bx_market_entry_view', 'header_beg_privacy', 2147483647, 0, 0),
-('bx_market_entry_view', 'allow_view_to', 2147483647, 0, 0),
-('bx_market_entry_view', 'allow_purchase_to', 2147483647, 0, 0),
-('bx_market_entry_view', 'allow_comment_to', 2147483647, 0, 0),
-('bx_market_entry_view', 'allow_vote_to', 2147483647, 0, 0),
-('bx_market_entry_view', 'header_end_privacy', 2147483647, 0, 0),
 ('bx_market_entry_view', 'added', 2147483647, 1, 2),
 ('bx_market_entry_view', 'changed', 2147483647, 1, 3),
 

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

@@ -1,6 +1,6 @@
 
 -- TABLES
-DROP TABLE IF EXISTS `bx_market_products`, `bx_market_licenses`, `bx_market_licenses_deleted`, `bx_market_files`, `bx_market_files2products`, `bx_market_downloads_track`, `bx_market_photos`, `bx_market_photos2products`, `bx_market_photos_resized`, `bx_market_cmts`, `bx_market_votes`, `bx_market_votes_track`, `bx_market_views_track`, `bx_market_meta_keywords`, `bx_market_meta_locations`, `bx_market_reports`, `bx_market_reports_track`, `bx_market_favorites_track`;
+DROP TABLE IF EXISTS `bx_market_products`, `bx_market_subproducts`, `bx_market_licenses`, `bx_market_licenses_deleted`, `bx_market_files`, `bx_market_files2products`, `bx_market_downloads_track`, `bx_market_photos`, `bx_market_photos2products`, `bx_market_photos_resized`, `bx_market_cmts`, `bx_market_votes`, `bx_market_votes_track`, `bx_market_views_track`, `bx_market_meta_keywords`, `bx_market_meta_locations`, `bx_market_reports`, `bx_market_reports_track`, `bx_market_favorites_track`;
 
 -- STORAGES & TRANSCODERS
 DELETE FROM `sys_objects_storage` WHERE `object` IN('bx_market_files', 'bx_market_photos', 'bx_market_photos_resized');

+ 5 - 0
modules/boonex/russian/data/langs/bx_market/ru.xml

@@ -91,12 +91,17 @@
     <string name="_bx_market_form_entry_input_changed"><![CDATA[Обновлено]]></string>
     <string name="_bx_market_form_entry_input_sys_warning_single"><![CDATA[Предупреждение: Разовые платежи]]></string>
     <string name="_bx_market_form_entry_input_sys_warning_recurring"><![CDATA[Предупреждение: Подписки]]></string>
+    <string name="_bx_market_form_entry_input_sys_subentries"><![CDATA[Подпродукты]]></string>
+    <string name="_bx_market_form_entry_input_subentries"><![CDATA[Подпродукты]]></string>
+    <string name="_bx_market_form_entry_input_subentries_inf"><![CDATA[Оставьте поле пустым, если не хотите создавать ассоциацию.]]></string>
+    <string name="_bx_market_form_entry_input_subentries_placeholder"><![CDATA[Введите название продукта...]]></string>
 
 	<string name="_bx_market_acl_action_create_entry"><![CDATA[Создать продукт]]></string>
 	<string name="_bx_market_acl_action_delete_entry"><![CDATA[Удалить продукт]]></string>
 	<string name="_bx_market_acl_action_download_entry"><![CDATA[Скачать продукт]]></string>
 	<string name="_bx_market_acl_action_view_entry"><![CDATA[Просмотреть продукт]]></string>
 	<string name="_bx_market_acl_action_set_cover"><![CDATA[Установить обложку продукта]]></string>
+	<string name="_bx_market_acl_action_set_subentries"><![CDATA[Установить подпродукты продукта]]></string>
 	<string name="_bx_market_acl_action_set_thumb"><![CDATA[Установить значок продукта]]></string>
 	<string name="_bx_market_acl_action_edit_any_entry"><![CDATA[Редактировать любой продукт]]></string>