Browse Source

Merge branch 'master' of https://github.com/unaio/una

AntonLV 3 years ago
parent
commit
72a85cc837

+ 14 - 6
inc/classes/BxDolSearch.php

@@ -540,17 +540,25 @@ class BxDolSearchResult implements iBxDolReplaceable
         $sUnit =  'list';
         $sUnit =  'list';
         if(in_array($this->sUnitViewDefault, ['showcase', 'gallery']))
         if(in_array($this->sUnitViewDefault, ['showcase', 'gallery']))
             $sUnit = 'card';
             $sUnit = 'card';
+        
+        $aData = defined('BX_API_PAGE') ? [] : $this->decodeData($this->getSearchData());
+        
+        $aParams =  [
+            'per_page' => $this->aCurrent['paginate']['perPage'],
+            'start' => $this->aCurrent['paginate']['start'],
+            'type' => $this->_sMode,
+        ];
+        
+        if (isset($this->_aParams['category'])){
+            $aParams['category'] = $this->_aParams['category'];
+        }
 
 
         return [
         return [
             'module' => $sModule,
             'module' => $sModule,
             'unit' => 'general-' . $sUnitType . '-' . $sUnit,
             'unit' => 'general-' . $sUnitType . '-' . $sUnit,
             'request_url' => !empty($this->aCurrent['api_request_url']) ? $this->aCurrent['api_request_url'] : '/api.php?r=' . $sModule . '/browse/&params[]=',
             'request_url' => !empty($this->aCurrent['api_request_url']) ? $this->aCurrent['api_request_url'] : '/api.php?r=' . $sModule . '/browse/&params[]=',
-            'data' =>  defined('BX_API_PAGE') ? [] : $this->decodeData($this->getSearchData()),
-            'params' => [
-                'per_page' => $this->aCurrent['paginate']['perPage'],
-                'start' => $this->aCurrent['paginate']['start'],
-                'type' => $this->_sMode,
-            ],
+            'data' =>  $aData,
+            'params' => $aParams
         ];
         ];
     }
     }
 
 

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

@@ -65,6 +65,8 @@ class BxMarketModule extends BxBaseModTextModule
         $aResult['price_recurring'] = $aData['price_recurring'];
         $aResult['price_recurring'] = $aData['price_recurring'];
         $aResult['duration_recurring'] = $aData['duration_recurring'];
         $aResult['duration_recurring'] = $aData['duration_recurring'];
 
 
+        $aResult['cover_raw'] = $aData['cover_raw'];
+        $aResult['cover'] = $this->serviceGetCover($aData[$CNF['FIELD_COVER']]);  
         return $aResult;
         return $aResult;
     }
     }
 
 

+ 1 - 1
modules/boonex/market/classes/BxMarketSearchResult.php

@@ -21,7 +21,7 @@ class BxMarketSearchResult extends BxBaseModTextSearchResult
             'object_metatags' => 'bx_market',
             'object_metatags' => 'bx_market',
             'title' => _t('_bx_market_page_title_browse'),
             'title' => _t('_bx_market_page_title_browse'),
             'table' => 'bx_market_products',
             'table' => 'bx_market_products',
-            'ownFields' => array('id', 'title', 'text', 'price_single', 'price_recurring', 'duration_recurring', 'thumb', 'cover', 'cover_raw', 'author', 'added'),
+            'ownFields' => array('id', 'title', 'text', 'price_single', 'price_recurring', 'duration_recurring', 'thumb', 'cover', 'cover_raw', 'author', 'added', 'cover_raw'),
             'searchFields' => array(),
             'searchFields' => array(),
             'restriction' => array(
             'restriction' => array(
                 'author' => array('value' => '', 'field' => 'author', 'operator' => '='),
                 'author' => array('value' => '', 'field' => 'author', 'operator' => '='),

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

@@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS `bx_market_products` (
   `changed` int(11) NOT NULL default '0',
   `changed` int(11) NOT NULL default '0',
   `thumb` int(11) NOT NULL default '0',
   `thumb` int(11) NOT NULL default '0',
   `cover` int(11) NOT NULL default '0',
   `cover` int(11) NOT NULL default '0',
-  `cover_raw` text NOT NULL,
+  `cover_raw` longtext NOT NULL,
   `package` int(11) NOT NULL default '0',
   `package` int(11) NOT NULL default '0',
   `name` varchar(255) NOT NULL,
   `name` varchar(255) NOT NULL,
   `title` varchar(255) NOT NULL,
   `title` varchar(255) NOT NULL,

+ 1209 - 0
upgrade/files/13.1.0.B1-13.1.0.B2/files/inc/classes/BxDolSearch.php

@@ -0,0 +1,1209 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT', 10);
+define('BX_DOL_SEARCH_KEYWORD_PAGE', 'site-search-page');
+
+/**
+ * Search class for different content.
+ *
+ * Allows present content in main search area for all modules.
+ *
+ * To add taking part content from your module to search area you need add a record to `sys_objects_search` table:
+ * @code
+ *   ID - autoincremented id for internal usage
+ *   ObjectName - your unique module name, with vendor prefix, lowercase and spaces are underscored
+ *   Title - language key for module's choicer in global search form
+ *   ClassName - class name which is responsible for search process ad showing result for found results
+ *   ClassPath - file where your ClassName is stored (if class is stored in template folder then you can use key {tmpl})
+ * @endcode
+ *
+ * For using this class you should have unit of BxDolSearchResult class (see description below) with method processing which is responsible for processing of search request.
+ *
+ * Example of usage can be seen in the BoonEx modules, for example: Posts
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+class BxDolSearch extends BxDol
+{
+    protected $_bIsApi;
+
+    protected $aClasses = array(); ///< array of all search classes
+    protected $aChoice  = array(); ///< array of current search classes which were choosen in search area
+    protected $_bRawProcessing = false; ///< display search results without design box and paginate
+    protected $_bDataProcessing = false; ///< return result as array of data
+
+    protected $_bLiveSearch = false;
+    protected $_sMetaType = '';
+    protected $_sCategoryObject = ''; ///< by default = object from sys_objects_category table, for multicategories set to 'multi' string 
+    protected $_aCustomSearchCondition = array();
+    protected $_aCustomCurrentCondition = array();
+    protected $_sUnitTemplate = '';
+
+    /**
+     * Constructor
+     * @param array $aChoice - array of choosen classes (will take a part only existing in `sys_objects_search` table)
+     */
+    public function __construct ($aChoice = '')
+    {
+        parent::__construct();
+
+        $this->_bIsApi = bx_is_api();
+
+        $this->aClasses = BxDolDb::getInstance()->fromCache('sys_objects_search', 'getAllWithKey',
+           'SELECT `ID` as `id`,
+                   `Title` as `title`,
+                   `ClassName` as `class`,
+                   `ClassPath` as `file`,
+                   `ObjectName`,
+                   `GlobalSearch`
+            FROM `sys_objects_search`
+            ORDER BY `Order` ASC', 'ObjectName'
+        );
+
+        if (is_array($aChoice) && !empty($aChoice)) {
+            foreach ($aChoice as $sValue) {
+                if (isset($this->aClasses[$sValue]))
+                    $this->aChoice[$sValue] = $this->aClasses[$sValue];
+            }
+        } else {
+            $this->aChoice = $this->aClasses;
+        }
+    }
+
+    /**
+     * create units for all classes and calling their processing methods
+     */
+    public function response ()
+    {
+        $sCode = $this->_bDataProcessing ? array() : '';
+
+        $bSingle = count($this->aChoice) == 1;
+        foreach ($this->aChoice as $sKey => $aValue) {
+            if (!$this->_sMetaType && !$aValue['GlobalSearch'])
+                continue;
+
+        	$sClassName = 'BxTemplSearchResult';
+	        if(!empty($aValue['class'])) {
+	            $sClassName = $aValue['class'];
+	            if(!empty($aValue['file']))
+	                require_once(BX_DIRECTORY_PATH_ROOT . $aValue['file']);
+	        }
+
+            $oEx = new $sClassName();            
+            if ($this->_sMetaType && !$oEx->isMetaEnabled($this->_sMetaType))
+                continue;
+            $oEx->setId($aValue['id']);
+            $oEx->setLiveSearch($this->_bLiveSearch);
+            $oEx->setMetaType($this->_sMetaType);
+            $oEx->setCategoryObject($this->_sCategoryObject);
+            $oEx->setCenterContentUnitSelector(false);
+            $oEx->setSingleSearch($bSingle);
+            $oEx->setCustomSearchCondition($this->_aCustomSearchCondition);
+            
+            $oEx->aCurrent = array_merge_recursive($oEx->aCurrent, $this->_aCustomCurrentCondition);
+            if ($this->_sUnitTemplate)
+                $oEx->setUnitTemplate($this->_sUnitTemplate);
+
+            if ($this->_bDataProcessing) {
+                if($this->_bIsApi) {
+                    if($bSingle)
+                        $sCode = $oEx->decodeData($oEx->getSearchData());
+                    else
+                        $sCode[$sKey] = $oEx->getSearchQuery(['for_union' => true]);
+                }
+                else
+                    $sCode[$sKey] = $oEx->getSearchData();
+            }
+            else
+                $sCode .= $this->_bRawProcessing ? $oEx->processingRaw() : $oEx->processing();
+        }
+
+        if($this->_bIsApi && !$bSingle && is_array($sCode)) {
+            $bExtendedUnits = getParam('sys_api_extended_units') == 'on';
+
+            $aQueries = [];
+            foreach($sCode as $aQuery)
+                $aQueries[] = $aQuery['query'];
+
+            $aItems = BxDolDb::getInstance()->getAll('(' . implode(') UNION (', $aQueries) . ') ORDER BY `added` DESC ' . current($sCode)['limit']);
+
+            $sCode = [];
+            foreach($aItems as $aItem)
+                $sCode[] = bx_srv($aItem['module'], 'get_info_api', [$aItem['id'], $bExtendedUnits]);
+        }
+
+        return $sCode;
+    }
+
+    public function getEmptyResult ()
+    {
+        $sKey = _t('_Empty');
+        return DesignBoxContent($sKey, MsgBox($sKey), BX_DB_PADDING_DEF);
+    }
+
+    protected function getKeyTitlesPairs ()
+    {
+        $a = array();
+        foreach ($this->aChoice as $sKey => $r)
+            if ($this->_sMetaType || $r['GlobalSearch'])
+                $a[$sKey] = _t($r['title']);
+        return $a;
+    }
+
+    public function setLiveSearch($bLiveSearch)
+    {
+        $this->_bLiveSearch = $bLiveSearch;
+    }
+
+    public function setMetaType($s)
+    {
+        $aMetaTypes = array('location_country', 'location_country_city', 'location_country_state', 'mention', 'keyword');
+        if (in_array($s, $aMetaTypes))
+            $this->_sMetaType = $s;
+    }
+    
+    public function setCategoryObject($s)
+    {
+        $this->_sCategoryObject = $s;
+    }
+
+    /**
+     * Set custom search condition to use istead of GET/POST variables
+     * @param $a array of params, such as 'keyword', 'state', 'city'
+     * @return nothing
+     */
+    public function setCustomSearchCondition($a)
+    {
+        $this->_aCustomSearchCondition = $a;
+    }
+
+    /**
+     * Set custom data for aCurrent array
+     * @param $a array of params
+     * @return nothing
+     */
+    public function setCustomCurrentCondition($a)
+    {
+        $this->_aCustomCurrentCondition = $a;
+    }
+
+    /**
+     * Set custom unit template
+     * @param $s template name
+     * @return nothing
+     */
+    public function setUnitTemplate($s)
+    {
+        $this->_sUnitTemplate = $s;
+    }
+    
+    /**
+     * Display search results without design box and paginate
+     */ 
+    public function setRawProcessing($b)
+    {
+        $this->_bRawProcessing = $b;
+    }
+
+    /**
+     * Return search result as array of data
+     */ 
+    public function setDataProcessing($b)
+    {
+        $this->_bDataProcessing = $b;
+    }
+}
+
+/*
+ * Search class for processing search requests and displaying search results.
+ *
+ * Allows present content from modules on search params or internal (via fields of class) conditions.
+ *
+ * Example of usage (you can see example in any of default Dolhpin's modules):
+ *
+ * 1. Extends your own search class from this one (or from BxBaseSearchResult or BxBaseSearchResultSharedMedia classes)
+ * 2. Set necessary fields of class (using as example BxFilesSearch):
+ *
+ * @code
+ *
+ *  // main field of search class
+ *  $this->aCurrent = array(
+ *
+ *      // name of module
+ *      'name' => 'bx_files',
+ *
+ *      // language key with name of module
+ *      'title' => '_bx_files',
+ *
+ *      // main content table
+ *      'table' => 'bx_files_main',
+ *
+ *      // array of all fields which can be choosen for select in result query
+ *      'ownFields' => array('ID', 'Title', 'Uri', 'Desc', 'Date', 'Size', 'Ext', 'Views', 'Rate', 'RateCount', 'Type'),
+ *
+ *      // array of fields which take a part in search by keyword (global search and related words only - in other cases leave it blank)
+ *      'searchFields' => array('Title', 'Tags', 'Desc', 'Categories'),
+ *
+ *      // array of join tables
+ *      //   'type' - type of join
+ *      //   'table' - join table
+ *      //   'mainField' - field from main table for 'on' condition
+ *      //   'onField' - field from joining table for 'on' condition
+ *      //   'joinFields' - array of fields from joining table
+ *
+ *      'join' => array(
+ *          'profile' => array(
+ *              'type' => 'left',
+ *              'table' => 'Profiles',
+ *              'mainField' => 'Owner',
+ *              'onField' => 'ID',
+ *              'joinFields' => array('NickName'),
+ *          ),
+ *          // ...
+ *      ),
+ *
+ *      // array of search parameters
+ *      //   'value' - value of search parameter (can be number, string or array)
+ *      //   'field' - field which will take value of search, may be lefy as blank then in search query will be pasted to WHERE conditon via operator AGAINST
+ *      //   'operator' - operator between field and value (can be 'in', 'not in', '>', '<', 'against', 'like' and '=' by default),
+ *      //   'paramName' - GET param which will keep 'value' for pagianation
+ *
+ *      'restriction' => array(
+ *          'activeStatus' => array('value'=>'approved', 'field'=>'Status', 'operator'=>'=', 'paramName' => 'status'),
+ *          'albumType' => array('value'=>'', 'field'=>'Type', 'operator'=>'=', 'paramName'=>'albumType', 'table'=>'sys_albums'),
+ *      ),
+ *
+ *      // array of pagination
+ *      //   'perPage' - units per page
+ *      //   'start' - show search results starting from this number
+ *      //   'num' - number of element to show on current page + 1
+ *
+ *      'paginate' => array('perPage' => 10, 'start' => 0, 'num' => 11),
+ *
+ *      // sort mode - by default it was last (DESC by date)
+ *      'sorting' => 'last',
+ *
+ *      // mode of units presentation in search result, can be 'short' or 'full' view
+ *      'view' => 'full',
+ *
+ *      // field of id
+ *      'ident' => 'ID',
+ *
+ *      // rss feed array
+ *      'rss' => array(
+ *          'title' => '',
+ *          'link' => '',
+ *          'image' => '',
+ *          'profile' => 0,
+ *          'fields' => array (
+ *              'Link' => '',
+ *              'Title' => 'Title',
+ *              'DateTimeUTS' => 'Date',
+ *              'Desc' => 'Desc',
+ *              'Photo' => '',
+ *          ),
+ *      ),
+ *  );
+ *
+ *  // array of fields renamings
+ *  $aPseud - filling in costructor by function _getPseud
+ *
+ *  // unique identificator from `sys_objects_search` table
+ *  $id;
+ *
+ * @endcode
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+
+class BxDolSearchResult implements iBxDolReplaceable
+{
+    public $aCurrent; ///< search results configuration
+
+    protected $aPseud; ///< array of fields renamings
+    protected $id; ///< unique identificator of block on mixed search results page
+    protected $bDisplayEmptyMsg = false; ///< display empty message instead of nothing, when no results
+    protected $sDisplayEmptyMsgKey = ''; ///< custom empty message language key, instead of default "empty" message
+    protected $bProcessPrivateContent = true; ///< check each item for privacy, if view isn't allowed then display private view instead
+    protected $aPrivateConditionsIndexes = array('restriction' => array(), 'join' => array()); ///< conditions indexes for bProcessPrivateContent
+    protected $bForceAjaxPaginate = false; ///< force ajax paginate
+    protected $iPaginatePerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT; ///< default 'per page' value for paginate.
+
+    protected $_bIsApi;
+    protected $_bSingleSearch = true;
+    protected $_bLiveSearch = false;
+    protected $_sMetaType = '';
+    protected $_sMode = '';
+    protected $_aParams = [];
+    protected $_sCategoryObject = '';
+    protected $_aCustomSearchCondition = array();
+
+    protected $_aMarkers = array (); ///< markers to replace somewhere, usually title and browse url (defined in custom class)
+    
+    /**
+     * constructor
+     * filling identificator field
+     */
+    function __construct ()
+    {
+        $this->_bIsApi = bx_is_api();
+
+        if (isset($this->aPseud['id']))
+            $this->aCurrent['ident'] = $this->aPseud['id'];
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setId($sId)
+    {
+        $this->id = $sId;
+    }
+
+    public function setAjaxPaginate($b = true)
+    {
+        $this->bForceAjaxPaginate = $b;
+    }
+
+    public function setPaginatePerPage($iPerPage)
+    {
+        $this->iPaginatePerPage = $iPerPage;
+    }
+
+    public function setSingleSearch($bSingleSearch)
+    {
+        $this->_bSingleSearch = $bSingleSearch;
+    }
+
+    public function setLiveSearch($bLiveSearch)
+    {
+        $this->_bLiveSearch = $bLiveSearch;
+    }
+
+    public function setMetaType($s)
+    {
+        $this->_sMetaType = $s;
+    }
+
+    public function isMetaEnabled($s)
+    {
+        if (empty($this->aCurrent['object_metatags']))
+            return false;
+            
+        if (!($o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags'])))
+            return false;
+
+        switch ($s) {
+            case 'location_country_city':
+            case 'location_country_state':            
+            case 'location_country':
+                return $o->locationsIsEnabled();
+            case 'mention':
+                return $o->mentionsIsEnabled();
+            case 'keyword':
+                return $o->keywordsIsEnabled();
+        }
+
+        return false;
+    }
+    
+    public function setCategoryObject($s)
+    {
+        $this->_sCategoryObject = $s;
+    }
+
+    public function setCustomSearchCondition($a)
+    {
+        $this->_aCustomSearchCondition = $a;
+    }
+    
+    public function setCustomCurrentCondition($a)
+    {
+        $this->aCurrent = array_merge_recursive($this->aCurrent, $a);
+    }
+
+    public function setCategoriesCondition($sKeyword)
+    {
+		$this->aCurrent['join']['multicat'] = array(
+            'type' => 'INNER',
+            'table' => 'sys_categories2objects',
+            'mainField' => 'id',
+            'onField' => 'object_id',
+            'joinFields' => array(),
+        );
+		
+		if (isset($this->aCurrent['tableSearch'])){
+			$this->aCurrent['join']['multicat']['mainTable'] = $this->aCurrent['tableSearch'];
+		}
+		
+        $this->aCurrent['join']['multicat2'] = array(
+           'type' => 'INNER',
+           'table' => 'sys_categories',
+           'mainField' => 'category_id',
+           'mainTable' => 'sys_categories2objects',
+           'onField' => 'id',
+           'joinFields' => array(),
+       );
+       $this->aCurrent['restriction']['multicat'] = array('value' => $sKeyword, 'field' => 'value', 'operator' => '=', 'table' => 'sys_categories');
+    }
+    
+    /**
+     * Display empty message if there is no content, custom empty message can be used.
+     * @param $b - boolan value to enable or disable 'empty' message
+     * @param $sLangKey [optional] - custom 'empty' message
+     */
+    public function setDisplayEmptyMsg($b, $sLangKey = '')
+    {
+        $this->bDisplayEmptyMsg = $b;
+        if ($sLangKey)
+           $this->sDisplayEmptyMsgKey = $sLangKey;
+    }
+
+    /**
+     * Perform privacy checking for every unit
+     * @param $b - boolan value to enable or disable privacy checking
+     */
+    public function setProcessPrivateContent ($b)
+    {
+        $this->bProcessPrivateContent = $b;
+        if ($b) {
+            // unset condition which was set when 'bProcessPrivateContent' was set to 'false'
+            foreach ($this->aPrivateConditionsIndexes['restriction'] as $sKey) 
+                unset($this->aCurrent['restriction'][$sKey]);
+            foreach ($this->aPrivateConditionsIndexes['join'] as $sKey)
+                unset($this->aCurrent['join'][$sKey]);
+        }
+    }
+
+    /**
+     * Get search results without design box and paginate
+     * @return html code
+     */
+    function processingRaw ()
+    {
+        $sCode = $this->displayResultBlock();
+        return $this->aCurrent['paginate']['num'] > 0 ? $sCode : '';
+    }
+    
+    /**
+     * Get html box of search results (usually used in grlobal search)
+     * @return html code
+     */
+    function processing ()
+    {
+        if (defined('BX_API')) {
+            return $this->processingAPI ();
+        }
+
+        $sCode = $this->displayResultBlock();
+        if ($this->aCurrent['paginate']['num'] > 0) {
+            $sPaginate = $this->showPagination();
+            $sCode = $this->displaySearchBox($sCode, $sPaginate);
+        } else {
+            $sCode = $this->bDisplayEmptyMsg ? $this->displaySearchBox(MsgBox(_t($this->sDisplayEmptyMsgKey ? $this->sDisplayEmptyMsgKey : '_Empty'))) : '';
+        }
+        return $sCode;
+    }
+
+    function processingAPI () 
+    {
+        $sModule = 'system';
+        $sUnitType = 'content';
+        if(!empty($this->oModule)) {
+            $sModule = $this->oModule->getName();
+
+            if(method_exists($this->oModule, 'serviceActAsProfile') && $this->oModule->serviceActAsProfile())
+                 $sUnitType = 'profile';
+            if(method_exists($this->oModule, 'serviceIsGroupProfile') && $this->oModule->serviceIsGroupProfile())
+                 $sUnitType = 'context';
+        }
+
+        $sUnit =  'list';
+        if(in_array($this->sUnitViewDefault, ['showcase', 'gallery']))
+            $sUnit = 'card';
+        
+        $aParams =  [
+            'per_page' => $this->aCurrent['paginate']['perPage'],
+            'start' => $this->aCurrent['paginate']['start'],
+            'type' => $this->_sMode,
+        ];
+        
+        if (isset($this->_aParams['category'])){
+            $aParams['category'] = $this->_aParams['category'];
+        }
+
+        return [
+            'module' => $sModule,
+            'unit' => 'general-' . $sUnitType . '-' . $sUnit,
+            'request_url' => !empty($this->aCurrent['api_request_url']) ? $this->aCurrent['api_request_url'] : '/api.php?r=' . $sModule . '/browse/&params[]=',
+            'data' =>  defined('BX_API_PAGE') ? [] : $this->decodeData($this->getSearchData()),
+            'params' => $aParams
+        ];
+    }
+
+    function decodeData ($a)
+    {
+        return $a;
+    }
+
+    /**
+     * Get html output of search result
+     * @return html code
+     */
+    function displayResultBlock ()
+    {
+        $aData = $this->getSearchData();
+
+        $sCode = '';
+        if (count($aData) > 0) {
+            $sCode = $this->addCustomParts();
+            foreach ($aData as $iKey => $aValue) {
+                $sCode .= $this->displaySearchUnit($aValue);
+            }
+        }
+        return $sCode;
+    }
+
+    /**
+     * Add different code before html output (usually redeclared)
+     * no return result
+     */
+    function addCustomParts ()
+    {
+
+    }
+
+    /**
+     * Get XML string for rss output
+     */
+    function rss ()
+    {
+        if (!isset($this->aCurrent['rss']['fields']) || !isset($this->aCurrent['rss']['link']))
+            return '';
+
+        $aData = $this->getSearchData();
+        $f = &$this->aCurrent['rss']['fields'];
+        if ($aData) {
+            foreach ($aData as $k => $a) {
+                $aData[$k][$f['Link']] = $this->getRssUnitLink ($a);
+
+                if(isset($f['Image']))
+                    $aData[$k][$f['Image']] = $this->getRssUnitImage ($a, $f['Image']);
+            }
+        }
+
+        $oRss = new BxDolRssFactory ();
+
+        return $oRss->GenRssByCustomData(
+            $aData,
+            isset($this->aCurrent['rss']['title']) && $this->aCurrent['rss']['title'] ? $this->aCurrent['rss']['title'] : $this->aCurrent['title'],
+            $this->aCurrent['rss']['link'],
+            $this->aCurrent['rss']['fields'],
+            isset($this->aCurrent['rss']['image']) ? $this->aCurrent['rss']['image'] : '',
+            isset($this->aCurrent['rss']['profile']) ? $this->aCurrent['rss']['profile'] : 0
+        );
+    }
+
+    /**
+     * Output RSS XML with XML header
+     */
+    function outputRSS ()
+    {
+        header('Content-Type: text/xml; charset=UTF-8');
+        echo $this->rss();
+    }
+
+    /**
+     * Return rss unit link (redeclared)
+     */
+    function getRssUnitLink (&$a)
+    {
+        // override this functions to return permalink to rss unit
+    }
+
+	/**
+     * Return rss unit image (redeclared)
+     */
+    function getRssUnitImage (&$a, $sField)
+    {
+        // override this functions to return image for rss unit
+    }
+
+    /**
+     * Naming fields in query's body
+     * @param string  $sFieldName  name of field
+     * @param string  $sTableName  name of field's table
+     *                             $param string $sOperator of field's calculation (like MAX)
+     * @param boolean $bRenameMode indicator for renaming and unsetting fields from field of class $this->aPseud
+     *                             return $sqlUnit sql code and unsetting elements from aPseud field
+     */
+    function setFieldUnit ($sFieldName, $sTableName, $sOperator = '', $bRenameMode = true)
+    {
+        if (!empty($sOperator))
+            $sqlUnit  = "$sOperator(`$sTableName`.`$sFieldName`)";
+        else
+            $sqlUnit  = "`$sTableName`.`$sFieldName`";
+
+        if (isset($this->aPseud) && $bRenameMode !== false) {
+            $sKey = array_search($sFieldName, $this->aPseud);
+            if ($sKey !== false) {
+                $sqlUnit .= " as `$sKey`";
+                unset($this->aPseud[$sKey]);
+            }
+        }
+        return $sqlUnit . ', ';
+    }
+
+    /**
+     * Get html code of of every search unit
+     * @param array $aData array of every search unit
+     *                     return html code
+     */
+    function displaySearchUnit ($aData)
+    {
+
+    }
+
+    /**
+     * Get html code of search box with search results
+     * @param string $sCode html code of search results
+     *                      $param $sPaginate html code of paginate
+     *                      return html code
+     */
+    function displaySearchBox ($sCode, $sPaginate = '')
+    {
+
+    }
+
+    /**
+     * Get html code of pagination
+     */
+    function showPagination ($bAdmin = false, $bChangePage = true, $bPageReload = true)
+    {
+
+    }
+
+    /**
+     * Get array of data with search results
+     * return array with data
+     */
+    function getSearchData ()
+    {
+        $this->aPseud = $this->_getPseud();
+        $this->setConditionParams();
+        if ($this->aCurrent['paginate']['num'] > 0) {
+            $aData = $this->getSearchDataByParams();
+            return $aData;
+        }
+        return array();
+    }
+    
+    function getSearchQuery($aParams = [])
+    {
+        $this->aPseud = ['id' => 'id', 'added' => 'added'];
+
+        $this->setConditionParams();
+        return $this->getSearchDataByParams($aParams);
+    }
+
+    /**
+     * Get array with code for sql elements
+     * @param $bRenameMode indicator of renmaing fields
+     * return array with joinFields, ownFields, groupBy and join elements
+     */
+    function getJoins ($bRenameMode = true)
+    {
+        $aSql = array();
+        // joinFields & join
+        if (isset($this->aCurrent['join']) && is_array($this->aCurrent['join'])) {
+            $aSql = array('join'=>'', 'ownFields'=>'', 'joinFields'=>'', 'groupBy'=>'');
+            foreach ($this->aCurrent['join'] as $sKey => $aValue) {
+                $sAlias = isset($aValue['table_alias']) ? $aValue['table_alias'] : $aValue['table'];
+                $sTableAlias = isset($aValue['table_alias']) ? " AS {$aValue['table_alias']} " : '';
+
+                if (is_array($aValue['joinFields'])) {
+                    foreach ($aValue['joinFields'] as $sValue) {
+                        $aSql['joinFields'] .= $this->setFieldUnit($sValue, $sAlias, isset($aValue['operator']) ? $aValue['operator'] : null, $bRenameMode);
+                    }
+                }
+                // group by
+                if (isset($aValue['groupTable']))
+                    $aSql['groupBy'] =  "GROUP BY `{$aValue['groupTable']}`.`{$aValue['groupField']}`, ";
+                $sOn = isset($aValue['mainTable']) ? $aValue['mainTable'] : $this->aCurrent['table'];
+                $aSql['join'] .= " {$aValue['type']} JOIN `{$aValue['table']}` $sTableAlias ON " . (!empty($aValue['on_sql']) ? $aValue['on_sql'] : "`{$sAlias}`.`{$aValue['onField']}`=`$sOn`.`{$aValue['mainField']}`");
+                $aSql['ownFields'] .= $this->setFieldUnit($aValue['mainField'], $sOn, '', $bRenameMode);
+            }
+            $aSql['joinFields'] = trim($aSql['joinFields'], ', ');
+            $aSql['groupBy'] = trim($aSql['groupBy'], ', ');
+        }
+        return $aSql;
+    }
+
+    /**
+     * Concat sql parts of query, run it and return result array
+     * @param $aParams addon param
+     * return $aData multivariate array
+     */
+    function getSearchDataByParams ($aParams = '')
+    {
+        $bForUnion = isset($aParams['for_union']) && $aParams['for_union'] === true;
+
+        $aSql = array('ownFields'=>'', 'joinFields'=>'', 'order'=>'');
+
+        // searchFields
+        if($bForUnion) {
+            $sTable = isset($this->aCurrent['tableSearch']) ? $this->aCurrent['tableSearch'] : $this->aCurrent['table'];
+
+            $aSql['ownFields'] .= $this->setFieldUnit('id', $sTable);
+            $aSql['ownFields'] .= $this->setFieldUnit('added', $sTable);
+            $aSql['ownFields'] .= "'" . $this->aCurrent['module_name']  . "' AS `module`";
+        }
+        else
+            foreach ($this->aCurrent['ownFields'] as $sValue)
+                $aSql['ownFields'] .= $this->setFieldUnit($sValue, $this->aCurrent['table']);
+
+        // joinFields & join
+        $aJoins = $this->getJoins();
+        if (!empty($aJoins)) {
+            if(!$bForUnion) {
+                $aSql['ownFields'] .= $aJoins['ownFields'];
+                $aSql['ownFields'] .= $aJoins['joinFields'];
+            }
+
+            $aSql['join'] = $aJoins['join'];
+            $aSql['groupBy'] = $aJoins['groupBy'];
+        } 
+
+        $aSql['ownFields'] = trim($aSql['ownFields'], ', ');
+
+        // from
+        $aSql['from'] = " FROM `{$this->aCurrent['table']}`";
+
+        // where
+        $aSql['where'] = $this->getRestriction();
+
+        // limit
+        $aSql['limit'] = $this->getLimit();
+
+        // sorting
+        $this->setSorting();
+
+        $aSort = $this->getSorting($this->aCurrent['sorting']);
+        foreach ($aSort as $sKey => $sValue)
+            $aSql[$sKey] .= $sValue;
+
+        // execution
+        $sqlQuery = 'SELECT';
+
+        if(isset($this->aCurrent['distinct']) && $this->aCurrent['distinct'] === true)
+            $sqlQuery .= ' DISTINCT';
+
+        $sqlQuery .= ' ' . $aSql['ownFields'];
+
+        $sqlQuery .= ' ' . $aSql['from'];
+
+        if (isset($aSql['join']))
+            $sqlQuery .= ' ' . $aSql['join'];
+
+        if (isset($aSql['where']))
+            $sqlQuery .= ' ' . $aSql['where'];
+
+        if (isset($aSql['groupBy']))
+            $sqlQuery .= ' ' . $aSql['groupBy'];
+
+        if($bForUnion)
+            return [
+                'query' => $sqlQuery, 
+                'limit' => $aSql['limit']
+            ];
+
+        if (isset($aSql['order']))
+            $sqlQuery .= ' ' . $aSql['order'];
+
+        if (isset($aSql['limit']))
+            $sqlQuery .= ' ' . $aSql['limit'];
+
+        // echoDbg($sqlQuery);
+        $aRes = BxDolDb::getInstance()->getAll($sqlQuery);
+        return $aRes;
+    }
+
+    /**
+     * Set class fields condition params and paginate array
+     */
+    function setConditionParams()
+    {
+        $this->aCurrent['paginate']['num'] = 0;
+
+        // keyword
+        $sKeyword = bx_process_input(isset($this->_aCustomSearchCondition['keyword']) ? $this->_aCustomSearchCondition['keyword'] : bx_get('keyword'));
+
+        if ($this->_bLiveSearch && empty($sKeyword))
+            return;
+
+        if ($sKeyword !== false) {
+            if (substr($sKeyword, 0, 1) == '@') {
+                $sModule = $this->aCurrent['module_name'];
+                $sMethod = 'act_as_profile';
+                if(!bx_is_srv($sModule, $sMethod) || !bx_srv($sModule, $sMethod))
+                    return;
+
+                $sKeyword = substr($sKeyword, 1);
+            }
+
+            $this->aCurrent['restriction']['keyword'] = array(
+                'value' => $sKeyword,
+                'field' => '',
+                'operator' => 'against'
+            );
+
+            // for search results we need to show all items, not only public content
+            $this->setProcessPrivateContent(true);
+        }
+
+        // owner
+        if (isset($_GET['ownerName'])) {
+            $sName = bx_process_input($_GET['ownerName']);
+            $iUser = (int)BxDolProfileQuery::getInstance()->getIdByNickname($sName);
+            BxDolMenu::getInstance()->setCurrentProfileID($iUser);
+        } elseif (isset($_GET['userID']))
+            $iUser = bx_process_input($_GET['userID'], BX_DATA_INT);
+
+        if (!empty($iUser))
+            $this->aCurrent['restriction']['owner']['value'] = $iUser;
+        
+        // meta info
+        if ($this->_sMetaType && !empty($this->aCurrent['object_metatags'])) {
+            $o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags']);
+            if ($o) {
+                unset($this->aCurrent['restriction']['keyword']);
+                switch ($this->_sMetaType) {
+                    case 'location_country':
+                        $o->locationsSetSearchCondition($this, $sKeyword);
+                        break;
+                    case 'location_country_state':
+                        $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state')));
+                        break;
+                    case 'location_country_city':
+                        $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state')), bx_process_input(isset($this->_aCustomSearchCondition['city']) ? $this->_aCustomSearchCondition['city'] : bx_get('city')));
+                        break;
+                    case 'mention':
+                        $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
+                        $o->mentionsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
+                        break;
+                    case 'keyword':
+                        $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
+                        $o->keywordsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
+                        break;
+                }
+            }
+        }
+
+        // category
+        if ($this->_sCategoryObject){
+            if(($o = BxDolCategory::getObjectInstance($this->_sCategoryObject)) && $this->_bSingleSearch) {
+                if ($this->aCurrent['name'] == $o->getSearchObject()) {
+                    unset($this->aCurrent['restriction']['keyword']);
+                    $o->setSearchCondition($this, $sKeyword);
+                }
+            }
+            if ($this->_sCategoryObject == 'multi'){
+                unset($this->aCurrent['restriction']['keyword']);
+                $this->setCategoriesCondition($sKeyword);
+            }
+        }
+
+        $this->setPaginate();
+        $iNum = $this->getNum();
+        if ($iNum > 0)
+            $this->aCurrent['paginate']['num'] = $iNum;
+    }
+
+    /**
+     * Check number of records on current page
+     * return number of records on current page + 1
+     */
+    function getNum ()
+    {
+        $aJoins = $this->getJoins(false);
+        $sqlQuery =  "SELECT * FROM `{$this->aCurrent['table']}` " . (isset($aJoins['join']) ? ' ' . $aJoins['join'] : '' ). $this->getRestriction() . (isset($aJoins['groupBy']) ? ' ' . $aJoins['groupBy'] : '') . ' ' . $this->getLimit(true);
+        return count(BxDolDb::getInstance()->getAll($sqlQuery));
+    }
+
+    /**
+     * Check restriction params and make condition part of query
+     * return $sqlWhere sql code of query for WHERE part
+     */
+    function getRestriction ()
+    {
+        $oDb = BxDolDb::getInstance();
+        $sqlWhere = '';
+        if (isset($this->aCurrent['restriction'])) {
+            $aWhere[] = '1';
+            foreach ($this->aCurrent['restriction'] as $sKey => $aValue) {
+                $sqlCondition = '';
+                if (isset($aValue['operator']) && isset($aValue['value']) && $aValue['value'] !== '' && $aValue['value'] !== false && $aValue['value'] !== null) {
+                   $sFieldTable = isset($aValue['table']) ? $aValue['table'] : $this->aCurrent['table'];
+                   $sqlCondition = "`{$sFieldTable}`.`{$aValue['field']}` ";
+                   switch ($aValue['operator']) {
+                       case 'empty value':
+                            $sqlCondition .= " = '' ";
+                            break;
+                       case 'not empty value':
+                            $sqlCondition .= " != '' ";
+                            break;
+                       case 'nothing':
+                            $sqlCondition = " 0 ";
+                            break;
+                       case 'against':
+                            $aCond = isset($aValue['field']) && strlen($aValue['field']) > 0 ? $aValue['field'] : $this->aCurrent['searchFields'];
+                            $sqlCondition = !empty($aCond) ? $this->getSearchFieldsCond($aCond, $aValue['value']) : "";
+                            break;
+                       case 'like':
+                            $sqlCondition .= "LIKE " . $oDb->escape('%' . $aValue['value'] . '%');
+                            break;
+                       case 'in':
+                       case 'not in':
+                            $sValuesString = $this->getMultiValues($aValue['value']);
+                            $sqlCondition .= strtoupper($aValue['operator']) . '(' . $sValuesString . ')';
+                            break;
+                       case 'in_set':
+                           $sqlCondition = "1 << (" . $sqlCondition . " - 1) & " . (int)$aValue['value'];
+                           break;
+                       default:
+                               $sqlCondition .= $aValue['operator'] . (isset($aValue['no_quote_value']) && $aValue['no_quote_value'] ?  $aValue['value'] : $oDb->escape($aValue['value']));
+                       break;
+                    }
+                }
+                if (strlen($sqlCondition) > 0)
+                    $aWhere[] = $sqlCondition;
+            }
+            $sqlWhere .= "WHERE ". implode(' AND ', $aWhere) . (isset($this->aCurrent['restriction_sql']) ? $this->aCurrent['restriction_sql'] : '');
+        }
+
+        return $sqlWhere;
+    }
+
+    /**
+     * Get limit part of query
+     * return $sqlFrom code for limit part pf query
+     */
+    function getLimit ($isAddPlusOne = false)
+    {
+        if (!isset($this->aCurrent['paginate']))
+            return;
+
+        $sqlFrom = (int)$this->aCurrent['paginate']['start'] > 0 ? (int)$this->aCurrent['paginate']['start'] : 0;
+        $sqlTo = $this->aCurrent['paginate']['perPage'] + ($isAddPlusOne ? 1 : 0);
+        if ($sqlTo > 0)
+            return 'LIMIT ' . $sqlFrom . ', ' . $sqlTo;
+    }
+
+    /**
+     * Set sorting field of class
+     */
+    function setSorting ()
+    {
+        $this->aCurrent['sorting'] = isset($_GET[$this->aCurrent['name'] . '_mode']) ? $_GET[$this->aCurrent['name'] . '_mode'] : $this->aCurrent['sorting'];
+    }
+
+    /**
+     * Get sorting part of query according current sorting mode
+     * @param string $sSortType sorting type
+     *                          return array with sql elements order and ownFields
+     */
+    function getSorting ($sSortType = 'last')
+    {
+        if (isset($this->aCurrent['sorting_sql']))
+            return array('order' => $this->aCurrent['sorting_sql']);
+        
+        $aOverride = $this->getAlterOrder();
+        if (is_array($aOverride) && !empty($aOverride))
+            return $aOverride;
+
+       $aSql = array();
+       switch ($sSortType) {
+           case 'rand':
+                $aSql['order'] = "ORDER BY RAND()";
+                break;
+           case 'score':
+                if (is_array($this->aCurrent['restriction']['keyword'])) {
+                    $sPseud = '';
+                    if ('on' == getParam('useLikeOperator')) {
+                        $aSql['order'] = "ORDER BY `score` DESC";
+                        $sPseud = 'score';
+                    }
+                    $aSql['ownFields'] .= $this->getSearchFieldsCond($this->aCurrent['searchFields'], $this->aCurrent['restriction']['keyword']['value'], $sPseud) . ',';
+                }
+                break;
+           case 'none':
+                $aSql['order'] = "";
+                break;
+           default:
+                $aSql['order'] = "ORDER BY `date` DESC";
+        }
+        return $aSql;
+    }
+
+    /**
+     * Return own varaint for sorting (redeclare if necessary)
+     * return array of sql elements
+     */
+    function getAlterOrder ()
+    {
+        return array();
+    }
+
+    /**
+     * Set paginate fields of class according GET params 'start' and 'per_page'
+     * forcePage is need for setting most important number of current page
+     */
+    function setPaginate ()
+    {
+        $iPerPage = 0;
+
+        //--- Check in aCurrent (low priority).
+        if(isset($this->aCurrent['paginate']['perPage']) && (int)$this->aCurrent['paginate']['perPage'] != 0)
+            $iPerPage = (int)$this->aCurrent['paginate']['perPage'];
+
+        //--- Check in GET params (high priority).
+        if(isset($_GET['per_page']) && (int)$_GET['per_page'] != 0)
+            $iPerPage = (int)$_GET['per_page'];
+
+        //--- Trying to get from System settings.
+        if (empty($iPerPage))
+            $iPerPage = (int)getParam('sys_per_page_search_keyword_' . ($this->_bSingleSearch ? 'single' : 'plural'));
+
+        //--- Use default value in case of emergency.
+        if (empty($iPerPage))
+            $iPerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT;
+        
+        if ($this->iPaginatePerPage != BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT)
+            $iPerPage = $this->iPaginatePerPage;
+
+        $this->aCurrent['paginate']['perPage'] = $iPerPage;
+
+        $this->aCurrent['paginate']['start'] = isset($this->aCurrent['paginate']['forceStart'])
+            ? (int)$this->aCurrent['paginate']['forceStart']
+            : (empty($_GET['start']) ? 0 : (int)$_GET['start']);
+
+        if ($this->aCurrent['paginate']['start'] < 0)
+            $this->aCurrent['paginate']['start'] = 0;
+    }
+
+    function unsetPaginate()
+    {
+    	unset($this->aCurrent['paginate']);
+    }
+
+    /**
+     * Get sql where condition for search fields
+     * @param array of search fields
+     * @param string $sKeyword keyword value for search
+     * @param string sPseud for setting new name for generated set of fields in query
+     *                         return sql code of WHERE part in query
+     */
+    function getSearchFieldsCond ($aFields, $sKeyword, $sPseud = '')
+    {
+        if (!$sKeyword)
+            return '';
+
+        $sTable = empty($this->aCurrent['tableSearch']) ? $this->aCurrent['table'] : $this->aCurrent['tableSearch'];
+
+        $oDb = BxDolDb::getInstance();
+
+        $bLike = getParam('useLikeOperator');
+
+        if (!is_array($aFields))
+            $aFields = array($aFields);
+
+        if ($bLike == 'on') {
+            $sKeyword = $oDb->escape('%' . preg_replace('/\s+/', '%', $sKeyword) . '%');
+
+            $sSqlWhere = '';
+            foreach ($aFields as $sValue)
+                $sSqlWhere .= "`{$sTable}`.`$sValue` LIKE  " . $sKeyword . " OR ";
+
+            $sSqlWhere = '(' . trim($sSqlWhere, 'OR ') . ')';
+
+        } else {
+        	$sKeyword = $oDb->escape($sKeyword);
+
+            $sSqlWhere = '';
+            foreach ($aFields as $sValue)
+                $sSqlWhere .= "`{$sTable}`.`$sValue`, ";
+
+            $sSqlWhere = trim($sSqlWhere, ', ');
+            $sSqlWhere = " MATCH({$sSqlWhere}) AGAINST (" . $sKeyword . ") ";
+
+            if (!empty($sPseud))
+                $sSqlWhere .= " AS `$sPseud` ";
+        }
+
+        return $sSqlWhere;
+    }
+
+    /**
+     * Get set from several values for 'in' and 'not in' operators
+     * @param $aValues array of values
+     * return sql code for field with operator IN (NOT IN)
+     */
+    function getMultiValues ($aValues)
+    {
+        $oDb = BxDolDb::getInstance();
+        return $oDb->implode_escape($aValues);
+    }
+
+    /**
+     * System method for filling aPseud array.
+     * Fill field aPseud for current class (if you will use own getSearchUnit methods then not necessary to redeclare).
+     */
+    function _getPseud ()
+    {
+
+    }
+
+    /**
+     * Add replace markers. Markers are replaced in titles and browse urls
+     * @param $a array of markers as key => value
+     * @return true on success or false on error
+     */
+    public function addMarkers ($a)
+    {
+        if (empty($a) || !is_array($a))
+            return false;
+        $this->_aMarkers = array_merge ($this->_aMarkers, $a);
+        return true;
+    }
+
+    /**
+     * Replace provided markers in a string
+     * @param $mixed string or array to replace markers in
+     * @return string where all occured markers are replaced
+     */
+    protected function _replaceMarkers ($mixed)
+    {
+        return bx_replace_markers($mixed, $this->_aMarkers);
+    }
+}
+
+/** @} */

+ 1 - 1
upgrade/files/13.1.0.B1-13.1.0.B2/files/plugins/autoload.php

@@ -4,4 +4,4 @@
 
 
 require_once __DIR__ . '/composer/autoload_real.php';
 require_once __DIR__ . '/composer/autoload_real.php';
 
 
-return ComposerAutoloaderInitd5dcc449dd8007a4d8a0788d00e29da0::getLoader();
+return ComposerAutoloaderInitc15ee1646d01dc8236b634f7c5bc911b::getLoader();

+ 7 - 7
upgrade/files/13.1.0.B1-13.1.0.B2/files/plugins/composer/autoload_real.php

@@ -2,7 +2,7 @@
 
 
 // autoload_real.php @generated by Composer
 // autoload_real.php @generated by Composer
 
 
-class ComposerAutoloaderInitd5dcc449dd8007a4d8a0788d00e29da0
+class ComposerAutoloaderInitc15ee1646d01dc8236b634f7c5bc911b
 {
 {
     private static $loader;
     private static $loader;
 
 
@@ -24,15 +24,15 @@ class ComposerAutoloaderInitd5dcc449dd8007a4d8a0788d00e29da0
 
 
         require __DIR__ . '/platform_check.php';
         require __DIR__ . '/platform_check.php';
 
 
-        spl_autoload_register(array('ComposerAutoloaderInitd5dcc449dd8007a4d8a0788d00e29da0', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInitc15ee1646d01dc8236b634f7c5bc911b', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
         self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
-        spl_autoload_unregister(array('ComposerAutoloaderInitd5dcc449dd8007a4d8a0788d00e29da0', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInitc15ee1646d01dc8236b634f7c5bc911b', 'loadClassLoader'));
 
 
         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
         if ($useStaticLoader) {
         if ($useStaticLoader) {
             require __DIR__ . '/autoload_static.php';
             require __DIR__ . '/autoload_static.php';
 
 
-            call_user_func(\Composer\Autoload\ComposerStaticInitd5dcc449dd8007a4d8a0788d00e29da0::getInitializer($loader));
+            call_user_func(\Composer\Autoload\ComposerStaticInitc15ee1646d01dc8236b634f7c5bc911b::getInitializer($loader));
         } else {
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
             foreach ($map as $namespace => $path) {
@@ -53,19 +53,19 @@ class ComposerAutoloaderInitd5dcc449dd8007a4d8a0788d00e29da0
         $loader->register(true);
         $loader->register(true);
 
 
         if ($useStaticLoader) {
         if ($useStaticLoader) {
-            $includeFiles = Composer\Autoload\ComposerStaticInitd5dcc449dd8007a4d8a0788d00e29da0::$files;
+            $includeFiles = Composer\Autoload\ComposerStaticInitc15ee1646d01dc8236b634f7c5bc911b::$files;
         } else {
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequired5dcc449dd8007a4d8a0788d00e29da0($fileIdentifier, $file);
+            composerRequirec15ee1646d01dc8236b634f7c5bc911b($fileIdentifier, $file);
         }
         }
 
 
         return $loader;
         return $loader;
     }
     }
 }
 }
 
 
-function composerRequired5dcc449dd8007a4d8a0788d00e29da0($fileIdentifier, $file)
+function composerRequirec15ee1646d01dc8236b634f7c5bc911b($fileIdentifier, $file)
 {
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         require $file;
         require $file;

+ 5 - 5
upgrade/files/13.1.0.B1-13.1.0.B2/files/plugins/composer/autoload_static.php

@@ -4,7 +4,7 @@
 
 
 namespace Composer\Autoload;
 namespace Composer\Autoload;
 
 
-class ComposerStaticInitd5dcc449dd8007a4d8a0788d00e29da0
+class ComposerStaticInitc15ee1646d01dc8236b634f7c5bc911b
 {
 {
     public static $files = array (
     public static $files = array (
         '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
         '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
@@ -216,10 +216,10 @@ class ComposerStaticInitd5dcc449dd8007a4d8a0788d00e29da0
     public static function getInitializer(ClassLoader $loader)
     public static function getInitializer(ClassLoader $loader)
     {
     {
         return \Closure::bind(function () use ($loader) {
         return \Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInitd5dcc449dd8007a4d8a0788d00e29da0::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInitd5dcc449dd8007a4d8a0788d00e29da0::$prefixDirsPsr4;
-            $loader->prefixesPsr0 = ComposerStaticInitd5dcc449dd8007a4d8a0788d00e29da0::$prefixesPsr0;
-            $loader->classMap = ComposerStaticInitd5dcc449dd8007a4d8a0788d00e29da0::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInitc15ee1646d01dc8236b634f7c5bc911b::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitc15ee1646d01dc8236b634f7c5bc911b::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInitc15ee1646d01dc8236b634f7c5bc911b::$prefixesPsr0;
+            $loader->classMap = ComposerStaticInitc15ee1646d01dc8236b634f7c5bc911b::$classMap;
 
 
         }, null, ClassLoader::class);
         }, null, ClassLoader::class);
     }
     }

+ 2 - 2
upgrade/files/13.1.0.B1-13.1.0.B2/files/plugins/composer/installed.php

@@ -5,7 +5,7 @@
         'type' => 'library',
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
         'aliases' => array(),
-        'reference' => '192a2e5ada3a58e32a78b91bf5fb3a1e15e44142',
+        'reference' => '0dff12a3be5236b99a0ba59826c7ea4143554788',
         'name' => 'unaio/una',
         'name' => 'unaio/una',
         'dev' => true,
         'dev' => true,
     ),
     ),
@@ -359,7 +359,7 @@
             'type' => 'library',
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
             'aliases' => array(),
-            'reference' => '192a2e5ada3a58e32a78b91bf5fb3a1e15e44142',
+            'reference' => '0dff12a3be5236b99a0ba59826c7ea4143554788',
             'dev_requirement' => false,
             'dev_requirement' => false,
         ),
         ),
         'wikimedia/less.php' => array(
         'wikimedia/less.php' => array(

+ 1088 - 0
upgrade/files/13.1.0.B1-13.1.0.B2/files/studio/template/scripts/BxBaseStudioStore.php

@@ -0,0 +1,1088 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxBaseStudioStore extends BxDolStudioStore
+{
+    protected $iPerPageDefault = 24;
+
+    function __construct($sPage = "")
+    {
+        parent::__construct($sPage);
+    }
+
+    function getPageCss()
+    {
+        return array_merge(parent::getPageCss(), array(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'fancybox/|jquery.fancybox.css', 'module.css', 'store.css', 'store-media-tablet.css', 'store-media-desktop.css'));
+    }
+
+    function getPageJs()
+    {
+    	BxDolStudioTemplate::getInstance()->addJsTranslation(array('_adm_btn_queued_submit'));
+        return array_merge(parent::getPageJs(), array('fancybox/jquery.fancybox.pack.js', 'store.js'));
+    }
+
+    function getPageJsObject()
+    {
+        return 'oBxDolStudioStore';
+    }
+
+    function getPageMenu($aMenu = array(), $aMarkers = array())
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aMenu = array();
+        $aMenuItems = array(
+	        'goodies' => array('icon' => 'home'),
+        	'latest' => array('icon' => 'th-large'),
+	        'featured' => array('icon' => 'thumbs-up'),
+        	'popular' => array('icon' => 'star'),
+        	'categories' => array('icon' => 'tag'),
+        	'search' => array('icon' => 'search'),
+	        'purchases' => array('icon' => 'shopping-cart'), 
+	        'updates' => array('icon' => 'sync'), 
+	        'checkout' => array('icon' => 'credit-card'), 
+	        'downloaded' => array('icon' => 'list')
+        );
+        foreach($aMenuItems as $sMenuItem => $aItem)
+            $aMenu[] = array(
+                'name' => $sMenuItem,
+                'icon' => $aItem['icon'],
+                'link' => $this->getBaseUrl($sMenuItem),
+                'title' => _t('_adm_lmi_cpt_' . $sMenuItem),
+                'selected' => $sMenuItem == $this->sPage
+            );
+
+        $iCounter = BxDolStudioCart::getInstance()->getCount();
+
+        $aMarkers = array(
+            'checkout_counter' => $oTemplate->parseHtmlByName('menu_side_counter.html', array(
+                'bx_if:hide_counter' => array(
+                    'condition' => $iCounter <= 0,
+                    'content' => array()
+                ),
+                'counter' => $iCounter
+            ))
+        );
+
+        return parent::getPageMenu($aMenu, $aMarkers);
+    }
+
+    function getPageCode($sPage = '', $bWrap = true)
+    {
+        $sResult = parent::getPageCode($sPage, $bWrap);
+        if($sResult === false)
+            return false;
+
+        $sMethod = 'get' . ucfirst($this->sPage) . 'List';
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        return $sResult . $this->$sMethod();
+    }
+
+    function getPageContent()
+    {
+        $sMethod = 'get' . ucfirst($this->sPage) . 'List';
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        return $this->$sMethod(false);
+    }
+
+    protected function getGoodiesList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $mixedResult = $this->authorizeClient();
+        if($mixedResult === true) {
+            $aProducts = $this->loadGoodies();
+
+            $sContent = "";
+            $sContent .= $this->displayNonOwnerNotification();
+
+            $sContent .= $this->getBlocksLine(array(
+                array(
+                    'caption' => '_adm_block_cpt_categories',
+                        'actions' => array(),
+                    'items' => $this->getCategoriesList(false)
+                ),
+                array(
+                    'caption' => '_adm_block_cpt_tags',
+                        'actions' => array(),
+                    'items' => $this->getTagsList(false)
+                )
+            ));
+
+            foreach($aProducts as $aBlock) {
+                $aBlock['items'] = $oTemplate->parseHtmlByName('str_products.html', array(
+                    'list' => $this->displayProducts($aBlock['items']),
+                    'paginate' => ''
+                ));
+
+                $sContent .= $this->getBlockCode($aBlock);
+            }
+        }
+        else
+        	$sContent = $this->getBlockCode(array(
+                'caption' => '_adm_block_cpt_goodies',
+                'items' => $mixedResult,
+            ));
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $sContent
+        ));
+    }
+
+    protected function getLatestList($bWrapInBlock = true)
+    {
+        return $this->getBrowsingList('latest', $bWrapInBlock);
+    }
+
+    protected function getFeaturedList($bWrapInBlock = true)
+    {
+        return $this->getBrowsingList('featured', $bWrapInBlock);
+    }
+
+    protected function getPopularList($bWrapInBlock = true)
+    {
+        return $this->getBrowsingList('popular', $bWrapInBlock);
+    }
+
+    protected function getBrowsingList($sType, $bWrapInBlock = true)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $mixedResult = $this->authorizeClient();
+        if($mixedResult === true) {
+	        $iStart = (int)bx_get('str_start');
+	        $iPerPage = (int)bx_get('str_per_page');
+	        if(empty($iPerPage))
+	            $iPerPage = $this->iPerPageDefault;
+
+	        $aProducts = $this->{'load' . bx_gen_method_name($sType)}($iStart, $iPerPage + 1);
+
+	        $oPaginate = new BxTemplPaginate(array(
+	            'start' => $iStart,
+	            'per_page' => $iPerPage,
+	            'on_change_page' => $sJsObject . ".changePagePaginate(this, '" . $sType . "', null, {start}, {per_page})"
+	        ));
+	        $oPaginate->setNumFromDataArray($aProducts);
+
+	        $sContent = $oTemplate->parseHtmlByName('str_products.html', array(
+	            'list' => $this->displayProducts($aProducts),
+	            'paginate' => $oPaginate->getSimplePaginate()
+	        ));
+        }
+        else 
+            $sContent = $mixedResult;
+
+		if(!$bWrapInBlock)
+			return $sContent;
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_' . $sType,
+				'items' => $sContent
+			))
+        ));
+    }
+
+    protected function getCategoriesList($bWrapInBlock = true)
+    {
+        return $this->getLabelsList('categories', $bWrapInBlock);
+    }
+
+    protected function getCategoryList($bWrapInBlock = true)
+    {
+        $sKey = 'value';
+        if(bx_get($sKey) === false)
+            $sKey = 'str_value';
+
+        $aValue = array();
+        $aValue['value'] = (int)bx_get($sKey);
+        if(bx_get('title') !== false)
+            $aValue['title'] = bx_process_input(urldecode(bx_get('title')));
+
+        return $this->getLabelList('category', $aValue, $bWrapInBlock);
+    }
+
+    protected function getTagsList($bWrapInBlock = true)
+    {
+        return $this->getLabelsList('tags', $bWrapInBlock);
+    }
+
+    protected function getTagList($bWrapInBlock = true)
+    {
+        $sKey = 'value';
+        if(bx_get($sKey) === false)
+            $sKey = 'str_value';
+
+        return $this->getLabelList('tag', bx_process_input(bx_get($sKey)), $bWrapInBlock);
+    }
+
+    protected function getLabelsList($sType, $bWrapInBlock = true)
+    {
+        $mixedResult = $this->authorizeClient();
+        if($mixedResult === true) {
+            $aLabels = $this->{'load' . bx_gen_method_name($sType)}();
+            $sContent = $this->{'display' . bx_gen_method_name($sType)}($aLabels);
+        }
+        else 
+            $sContent = $mixedResult;
+
+    	if(!$bWrapInBlock)
+			return $sContent;
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('store.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_' . $sType,
+				'items' => $sContent
+			))
+        ));
+    }
+
+    protected function getLabelList($sType, $mixedValue, $bWrapInBlock = true)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $iStart = (int)bx_get('str_start');
+        $iPerPage = (int)bx_get('str_per_page');
+        if(empty($iPerPage))
+            $iPerPage = $this->iPerPageDefault;
+
+        $bArray = is_array($mixedValue);
+        $sValue = $bArray ? $mixedValue['value'] : $mixedValue;
+        $sTitle = $bArray && !empty($mixedValue['title']) ? $mixedValue['title'] : $sValue;
+
+		$sMethod = 'load' . bx_gen_method_name($sType);
+        $aProducts = $this->$sMethod($sValue, $iStart, $iPerPage + 1);
+
+        $oPaginate = new BxTemplPaginate(array(
+            'start' => $iStart,
+            'per_page' => $iPerPage,
+            'on_change_page' => $sJsObject . ".changePagePaginate(this, '" . $sType . "', '" . $sValue . "', {start}, {per_page})"
+        ));
+        $oPaginate->setNumFromDataArray($aProducts);
+
+        $sContent = $oTemplate->parseHtmlByName('str_products.html', array(
+            'list' => $this->displayProducts($aProducts),
+            'paginate' => $oPaginate->getSimplePaginate()
+        ));
+
+        if(!$bWrapInBlock)
+            return $sContent;
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $this->getBlockCode(array(
+				'caption' => array('_adm_block_cpt_' . $sType, $sTitle),
+				'items' => $sContent,
+			))
+        ));
+    }
+
+    protected function getSearchList($bWrapInBlock = true)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $mixedResult = $this->authorizeClient();
+        if($mixedResult === true) {
+            //--- Search form
+            $aForm = array(
+                'form_attrs' => array(
+                    'id' => 'adm-str-search-form',
+                    'name' => 'adm-str-search-form',
+                    'action' => '',
+                    'method' => 'post'
+                ),
+                'params' => array(
+                    'db' => array(
+                        'table' => '',
+                        'key' => '',
+                        'uri' => '',
+                        'uri_title' => '',
+                        'submit_name' => 'search'
+                    ),
+                ),
+                'inputs' => array(
+                    'page' => array(
+                        'type' => 'hidden',
+                        'name' => 'page',
+                        'value' => $this->sPage
+                    ),
+                    'keyword' => array(
+                        'type' => 'text',
+                        'name' => 'keyword',
+                        'caption' => '',
+                        'value' => '',
+                        'attrs' => array(
+                            'placeholder' => bx_html_attribute(_t('_sys_search_placeholder'))
+                        ),
+                        'db' => array (
+                            'pass' => 'Xss',
+                        )
+                    ),
+                    'search' => array(
+                        'type' => 'submit',
+                        'name' => 'search',
+                        'value' => _t('_adm_btn_store_search'),
+                    )
+                )
+            );
+    
+            $oForm = new BxTemplStudioFormView($aForm);
+            $oForm->initChecker();
+    
+            $sResults = '';
+            if($oForm->isSubmittedAndValid()) {
+                $sKeyword = $oForm->getCleanValue('keyword');
+
+                $iStart = (int)bx_get('str_start');
+    	        $iPerPage = (int)bx_get('str_per_page');
+    	        if(empty($iPerPage))
+    	            $iPerPage = 999;
+    
+    	        $aProducts = $this->loadSearch($sKeyword, $iStart, $iPerPage + 1);
+	            $sResults = empty($aProducts) || !is_array($aProducts) ? MsgBox(_t('_Empty')) : $oTemplate->parseHtmlByName('str_products.html', array(
+    	            'list' => $this->displayProducts($aProducts),
+    	            'paginate' => ''
+    	        ));
+            }         
+
+            $sContent = $oTemplate->parseHtmlByName('str_search.html', array(
+                'form' => $oForm->getCode(),
+                'bx_if:show_results' => array(
+                    'condition' => !empty($sResults),
+                    'content' => array(
+                        'results' => $sResults
+                    )
+                )
+            ));
+        }
+        else 
+            $sContent = $mixedResult;
+
+        if(!$bWrapInBlock)
+			return $sContent;
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_search',
+				'items' => $sContent
+			))
+        ));
+    }
+
+    protected function getPurchasesList()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aProducts = $this->loadPurchases();
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_purchases',
+				'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+		            'list' => $this->displayProducts($aProducts),
+		            'paginate' => ''
+		        ))
+			))
+        ));
+    }
+
+    protected function getUpdatesList()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aUpdates = $this->loadUpdates();
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_updates',
+				'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+		            'list' => $this->displayUpdates($aUpdates),
+		            'paginate' => ''
+		        ))
+			))
+        ));
+    }
+
+    protected function getCheckoutList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        //--- Empty cart by vendor if the payment was accepted.
+        $mixedVendor = bx_get('vendor');
+        $mixedProducts = bx_get('products');
+        if($mixedVendor !== false && $mixedProducts !== false) {
+            $oCart = BxDolStudioCart::getInstance();
+
+            $aProducts = explode(',', base64_decode($mixedProducts));
+            foreach($aProducts as $iId)
+                $oCart->delete($mixedVendor, $iId);
+
+            return $this->getMessage('_adm_block_cpt_checkout', '_adm_msg_modules_success_checkouted');
+        }
+
+        $aVendors = $this->loadCheckout();
+        if(empty($aVendors))
+            return $this->getMessage('_adm_block_cpt_checkout', '_Empty');        
+
+        $sContent = '';
+        foreach($aVendors as $iVendor => $aInfo) {
+            $fTotal = 0;
+            $sVendor = $sCurrency = '';
+            if(!empty($aInfo['products']) && is_array($aInfo['products']))
+                foreach($aInfo['products'] as $aProduct) {
+                    $iCount = isset($aInfo['counts'][$aProduct['id']]) ? (int)$aInfo['counts'][$aProduct['id']] : 1;
+                    $fTotal += $iCount * $aProduct['price_single'];
+
+                    if($sVendor == '' && isset($aProduct['author_name']))
+                        $sVendor = $aProduct['author_name'];
+
+                                    if($sCurrency == '' && isset($aProduct['author_currency_sign']))
+                        $sCurrency = $aProduct['author_currency_sign'];
+                }
+
+            $aMenu = array(
+                array('id' => 'checkout-' . $iVendor, 'name' => 'checkout-' . $iVendor, 'link' => 'javascript:void(0)', 'onclick' => $sJsObject . ".checkoutCart(" . $iVendor . ", this);", 'target' => '_self', 'title' => '_adm_action_cpt_checkout', 'active' => 1),
+                array('id' => 'delete-all-' . $iVendor, 'name' => 'delete-all-' . $iVendor, 'link' => 'javascript:void(0)', 'onclick' => $sJsObject . ".deleteAllFromCart(" . $iVendor . ", this)", 'target' => '_self', 'title' => '_adm_action_cpt_delete_all', 'active' => 1)
+            );
+	        $oMenu = new BxTemplMenu(array('object'=> 'adm-std-checkout', 'template' => 'menu_buttons_hor.html', 'menu_items' => $aMenu));
+
+	        $sContent .= $this->getBlockCode(array(
+                'caption' => _t('_adm_block_cpt_checkout_by_vendor_csign', $sVendor, $sCurrency, $fTotal),
+                'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+		            'list' => $this->displayProducts($aInfo['products'], array('is_shopping_cart' => true, 'counts' => $aInfo['counts'])),
+		            'paginate' => ''
+		        )),
+                'panel_bottom' => $oMenu->getCode()
+            ));
+        }
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $sContent
+        ));
+    }
+
+    protected function getDownloadedList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sContent = $sModules = $sUpdates = "";
+        $aProducts = $this->loadDownloaded();
+
+        //--- Prepare modules.
+        $bCacheImage = getParam('sys_template_cache_image_enable') == 'on';
+        foreach($aProducts['modules'] as $aModule) {
+            $sIcon = BxDolStudioUtils::getModuleIcon($aModule, 'store');
+            $bIcon = ($bCacheImage && substr($sIcon, 0, 10) == 'data:image') || strpos($sIcon, '.') !== false;
+
+            $bInstalled = $aModule['installed'];
+            $bQueued = !$bInstalled && $this->oDb->isQueued('action', $aModule['dir']);
+
+            $sModules .= $oTemplate->parseHtmlByName('str_product_v1.html', array(
+                'js_object' => $sJsObject,
+             	'name' => $aModule['name'],
+            	'bx_if:no_icon' => array (
+                    'condition' => !$bIcon,
+                    'content' => array(
+                            'icon' => $sIcon
+                    ),
+                ),
+                'bx_if:icon' => array (
+                    'condition' => $bIcon,
+                    'content' => array(
+                            'icon_url' => $sIcon,
+                    ),
+                ),
+                'title' => $aModule['title'],
+                'vendor' => $aModule['vendor'],
+                'version' => $aModule['version'],
+                'dir' => $aModule['dir'],
+                'bx_if:hide_install' => array(
+                    'condition' => $bInstalled || $bQueued,
+                    'content' => array()
+                ),
+                'bx_if:hide_queued' => array(
+                    'condition' => !$bQueued,
+                    'content' => array()
+                ),
+                'bx_if:hide_installed' => array(
+                    'condition' => !$bInstalled || $bQueued,
+                    'content' => array()
+                )
+            ));
+        }
+
+        //--- Prepare updates.
+        foreach($aProducts['updates'] as $aUpdate) {
+            $sIcon = BxDolStudioUtils::getModuleIcon(array('type' => $aUpdate['module_type'], 'name' => $aUpdate['module_name'], 'dir' => $aUpdate['module_dir']), 'store');
+            $bIcon = ($bCacheImage && substr($sIcon, 0, 10) == 'data:image') || strpos($sIcon, '.') !== false;
+
+            $sUpdates .= $oTemplate->parseHtmlByName('str_update_v1.html', array(
+                'js_object' => $sJsObject,
+            	'name' => $aUpdate['module_name'],
+            	'bx_if:no_icon' => array (
+                    'condition' => !$bIcon,
+                    'content' => array(
+                        'icon' => $sIcon
+                    ),
+                ),
+                'bx_if:icon' => array (
+                    'condition' => $bIcon,
+                    'content' => array(
+                        'icon_url' => $sIcon,
+                    ),
+                ),
+                'title' => $aUpdate['title'],
+                'vendor' => $aUpdate['vendor'],
+                'versions' => _t('_adm_str_txt_update_from_to', $aUpdate['version_from'], $aUpdate['version_to']),
+                'dir' => $aUpdate['dir']
+            ));
+        }
+
+        if(!empty($sModules))
+            $sContent .= $this->getBlockCode(array(
+                'caption' => '_adm_block_cpt_downloaded_modules',
+                'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+                    'list' => $sModules,
+                    'paginate' => ''
+                )),
+            ));
+
+        if(!empty($sUpdates))
+            $sContent .= $this->getBlockCode(array(
+                'caption' => '_adm_block_cpt_downloaded_updates',
+                'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+                    'list' => $sUpdates,
+                    'paginate' => ''
+                )),
+            ));
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $sContent
+        ));
+    }
+
+    protected function getMessage($sCaption, $sContent, $aActions = array())
+    {
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('store.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'content' => $this->getBlockCode(array(
+				'caption' => $sCaption,
+        		'actions' => $aActions,
+				'items' => MsgBox(_t($sContent))
+			))
+        ));
+    }
+
+    protected function getProduct($sModuleName)
+    {
+        $bShowRating = true;
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aProduct = $this->loadProduct($sModuleName);
+        if(empty($aProduct) || !is_array($aProduct))
+            return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => (!empty($aProduct) ? $aProduct : _t('_adm_str_err_no_product_info')));
+
+		$aDownloaded = $this->getDownloadedModules(false);
+
+        $bFree = (int)$aProduct['is_free'] == 1;
+        $bPurchased = (int)$aProduct['is_purchased'] == 1;
+
+        $bPurchaseSingle = !$bFree && !$bPurchased && (float)$aProduct['price_single'] != 0;
+		$bInCart = $bPurchaseSingle && BxDolStudioCart::getInstance()->exists($aProduct['author_id'], $aProduct['id']);
+
+		$bPurchaseRecurring = !$bFree && !$bPurchased && (float)$aProduct['price_recurring'] != 0;
+
+        $bDownloadable = (int)$aProduct['is_file'] == 1;
+        $bDownloaded = array_key_exists($sModuleName, $aDownloaded);
+        $bDownload = ($bFree || $bPurchased) && $bDownloadable;
+
+        $bDiscount = !empty($aProduct['discount_single']);
+        $sVersion = $bDownloaded && version_compare(strtolower($aDownloaded[$sModuleName]['version']), strtolower($aProduct['version'])) != 0 ? _t('_adm_str_txt_pv_version_mask', $aDownloaded[$sModuleName]['version'], $aProduct['version']) : $aProduct['version'];
+
+        $iScreenshots = 0;
+        $aScreenshots = array();
+        $bScreenshots = is_array($aProduct['screenshots']) && !empty($aProduct['screenshots']);
+        if($bScreenshots) {
+            $iScreenshots = count($aProduct['screenshots']);
+            foreach($aProduct['screenshots'] as $aScreenshot)
+                $aScreenshots[] = array(
+                    'view_url' => $aProduct['url'],
+                    'image_url' => $aScreenshot['big']
+                );
+        }
+
+		$bTmplVarsRate = $bShowRating;
+		if($bTmplVarsRate)
+		    $aTmplVarsRate = array(
+		        'rate' => $this->getVoteStars($aProduct)
+		    );
+
+		$aTmplVarsVotes = array();
+		$bTmplVarsVotes = $bShowRating && (int)$aProduct['votes_cnt'] > 0;
+		if($bTmplVarsVotes)
+		    $aTmplVarsVotes = array(
+		        'count' => (int)$aProduct['votes_cnt']
+		    );
+
+        $sContent = $oTemplate->parseHtmlByname('str_product_view.html', array(
+            'id' => $aProduct['id'],
+            'title' => $aProduct['title'],
+            'url' => $aProduct['url'],
+            'author_name' => $aProduct['author_name'],
+            'author_url' => $aProduct['author_url'],
+            'version' => $sVersion,
+        	'bx_if:show_price_single' => array(
+        		'condition' => $bFree || (float)$aProduct['price_single'] != 0,
+        		'content' => array(
+        			'price_single' => !$bFree ? _t('_adm_str_txt_price_single', $aProduct['author_currency_sign'], $aProduct['price_single']) : _t('_adm_str_txt_price_free'),
+		        	'bx_if:show_discount' => array(
+		                'condition' => !$bFree && $bDiscount,
+		                'content' => array(
+		                    'discount_single' => $bDiscount ? _t('_adm_str_txt_pv_discount_off_csign', $aProduct['discount_single']['percent'], $aProduct['author_currency_sign'], $aProduct['discount_single']['save']) : ''
+		                )
+		            )
+        		)
+        	),
+        	'bx_if:show_price_recurring' => array(
+				'condition' => (float)$aProduct['price_recurring'] != 0,
+        		'content' => array(
+					'price_recurring' => _t('_adm_str_txt_price_recurring', $aProduct['author_currency_sign'], $aProduct['price_recurring'], _t('_adm_str_txt_per_' . $aProduct['duration_recurring']))
+        		)
+        	),
+        	'bx_if:show_rate' => array(
+                'condition' => $bTmplVarsRate,
+                'content' => $aTmplVarsRate
+            ),
+            'bx_if:show_votes' =>  array(
+                'condition' => $bTmplVarsVotes,
+                'content' => $aTmplVarsVotes
+            ),
+            'category' => $aProduct['category'],
+            'category_url' => $aProduct['category_url'],
+            'tags' => implode(', ', explode(',', $aProduct['tags'])),
+            'reviews' => _t('_adm_str_txt_pv_stats_reviews', $aProduct['reviews_cnt']),
+            'reviews_url' => $aProduct['reviews_url'],
+            'votes' => _t('_adm_str_txt_pv_stats_votes', $aProduct['votes_cnt']),
+            'views' => _t('_adm_str_txt_pv_stats_views', $aProduct['views_cnt']),
+            'created' => bx_time_js($aProduct['created']),
+            'updated' => bx_time_js($aProduct['updated']),
+            'description' => $aProduct['description'],
+            'bx_if:show_screenshots' => array(
+                'condition' => $bScreenshots,
+                'content' => array(
+                    'width' => 222 * $iScreenshots - 20,
+                    'bx_repeat:screenshots' => $aScreenshots
+                )
+            ),
+            'bx_if:show_purchase' => array(
+            	'condition' => $bPurchaseSingle || $bPurchaseRecurring,
+            	'content' => array(
+            		'bx_if:show_purchase_single' => array(
+		                'condition' => $bPurchaseSingle && !$bInCart,
+		                'content' => array(
+		                    'js_object' => $sJsObject,
+		                    'id' => $aProduct['id'],
+		                    'vendor_id' => $aProduct['author_id'],
+		            		'price_single' => _t('_adm_str_txt_price_single', $aProduct['author_currency_sign'], $aProduct['price_single'])
+		                )
+		            ),
+		            'bx_if:show_checkout_single' => array(
+						'condition' => $bPurchaseSingle,
+						'content' => array(
+							'js_object' => $sJsObject,
+							'id' => $aProduct['id'],
+							'vendor_id' => $aProduct['author_id'],
+							'bx_if:show_as_hidden' => array(
+								'condition' => !$bInCart,
+								'content' => array()
+							)
+						)
+					),
+					'bx_if:show_purchase_recurring' => array(
+						'condition' => $bPurchaseRecurring,
+						'content' => array(
+							'js_object' => $sJsObject,
+							'id' => $aProduct['id'],
+							'vendor_id' => $aProduct['author_id'],
+							'price_recurring' => _t('_adm_str_txt_price_recurring', $aProduct['author_currency_sign'], $aProduct['price_recurring'], _t('_adm_str_txt_per_' . $aProduct['duration_recurring']))
+						)
+					)
+            	)
+            ),
+            'bx_if:show_download' => array(
+                'condition' => $bDownload && !$bDownloaded,
+                'content' => array(
+                    'js_object' => $sJsObject,
+                    'file_id' => $aProduct['file_id'],
+                )
+            ),
+            'bx_if:show_download_disabled' => array(
+				'condition' => $bDownload && $bDownloaded,
+				'content' => array()
+			),
+        ));
+
+        return array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => '', 'popup' => PopupBox('bx-std-str-popup-product', $aProduct['title'], $sContent, true), 'screenshots' => $iScreenshots);
+    }
+
+    protected function getFile($iFileId)
+    {
+        $mixedResult = $this->loadFile($iFileId);
+        if($mixedResult === true)
+        	return array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => _t('_adm_str_msg_download_successfully'));
+
+        if(is_string($mixedResult))
+			return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => (!empty($mixedResult) ? $mixedResult : _t('_adm_str_err_download_failed')));
+
+		if(is_array($mixedResult))
+			return array_merge(array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_str_err_download_failed')), $mixedResult);
+    }
+
+    protected function getUpdate($sModuleName, $bApplyUpdate = false)
+    {
+        $aResult = array();
+
+        $mixedResult = $this->loadUpdate($sModuleName, $bApplyUpdate);
+        if($mixedResult === true)
+            $aResult = array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => _t('_adm_str_msg_download' . ($bApplyUpdate ? '_and_install' : '') . '_successfully'));
+        else if(is_string($mixedResult))
+            $aResult = array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => (!empty($mixedResult) ? $mixedResult : _t('_adm_str_err_download_failed')));
+        else if(is_array($mixedResult))
+            $aResult = array_merge(array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_str_err_download_failed')), $mixedResult);
+
+        $aResult['reload'] = 3000;
+        return $aResult;
+    }
+
+    protected function displayProducts($mixedItems, $aParams = array())
+    {
+        $bShowRating = false;
+
+        if(!is_array($mixedItems))
+            return MsgBox($mixedItems);
+
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aDownloaded = $this->getDownloadedModules();
+        $bShoppingCart = isset($aParams['is_shopping_cart']) && $aParams['is_shopping_cart'];
+
+        $sResult = '';
+        foreach($mixedItems as $aItem) {
+            $bFree = (int)$aItem['is_free'] == 1;
+            $bPurchased = (int)$aItem['is_purchased'] == 1;
+
+            $bPurchaseSingle = !$bShoppingCart && !$bFree && !$bPurchased && (float)$aItem['price_single'] != 0;
+            $bInCart = $bPurchaseSingle && BxDolStudioCart::getInstance()->exists($aItem['author_id'], $aItem['id']);
+
+            $bPurchaseRecurring = !$bShoppingCart && !$bFree && !$bPurchased && (float)$aItem['price_recurring'] != 0;
+
+            $bDownloadable = (int)$aItem['is_file'] == 1;
+            $bDownloaded = in_array($aItem['name'], $aDownloaded);
+            $bDownload = !$bShoppingCart && ($bFree || $bPurchased) && $bDownloadable;
+            $bQueued = $this->oDb->isQueued('download', $aItem['name']);
+
+            $sPrice = !$bFree ? _t('_adm_str_txt_price_single', $aItem['author_currency_sign'], $aItem['price_single']) : _t('_adm_str_txt_price_free');
+            $sDiscount = !$bFree && !empty($aItem['discount_single']) ? _t('_adm_str_txt_discount_csign', $aItem['author_currency_sign'], $aItem['discount_single']['price']) : '';
+
+            $sIcon = !empty($aItem['thumbnail']['small']) ? $aItem['thumbnail']['small'] : BxDolStudioUtils::getIconDefault(BX_DOL_MODULE_TYPE_MODULE);
+            $bIcon = strpos($sIcon, '.') !== false;
+
+            $sImage = '';
+            if(!empty($aItem['cover']['medium']))
+                $sImage = $aItem['cover']['medium'];
+            else if(!empty($aItem['cover']['large']))
+                $sImage = $aItem['cover']['large'];
+            else if(!empty($aItem['cover']['big']))
+                $sImage = $aItem['cover']['big'];
+            $bImage = !empty($sImage) && strpos($sImage, '.') !== false;
+
+            $aTmplVarsRate = array();
+            $bTmplVarsRate = $bShowRating && !$bShoppingCart;
+            if($bTmplVarsRate)
+                $aTmplVarsRate = array(
+                    'rate' => $this->getVoteStars($aItem)
+                );
+
+            $aTmplVarsVotes = array();
+            $bTmplVarsVotes = $bShowRating && !$bShoppingCart && (int)$aItem['votes_cnt'] > 0;
+            if($bTmplVarsVotes)
+                $aTmplVarsVotes = array(
+                    'count' => (int)$aItem['votes_cnt']
+                );
+
+            $sResult .= $oTemplate->parseHtmlByName('str_product_v2.html', array(
+                'js_object' => $sJsObject,
+                'id' => $aItem['id'],
+            	'name' => $aItem['name'],
+                'url' => $aItem['url'],
+            	'description_plain_attr' => bx_html_attribute($aItem['description_plain']),
+            	'bx_if:no_icon' => array (
+	                'condition' => !$bIcon,
+	                'content' => array(
+            			'icon' => $sIcon
+            		),
+	            ),
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array(
+	                	'icon_url' => $sIcon,
+	            	),
+	            ),
+                'bx_if:no_image' => array (
+	                'condition' => !$bImage,
+	                'content' => array(
+            			'description_plain' => $aItem['description_plain'],
+	                	'strecher' => mb_strlen($aItem['description_plain']) > 240 ? '' : str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', round((240 - mb_strlen($aItem['description_plain'])) / 6))
+            		),
+	            ),
+                'bx_if:image' => array (
+	                'condition' => $bImage,
+	                'content' => array(
+	                	'image_url' => $sImage,
+	            		'strecher' => str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', 40)
+	            	),
+	            ),
+                'title' => $aItem['title'],
+                'bx_if:show_vendor' => array(
+                    'condition' => !$bShoppingCart,
+                    'content' => array(
+                        'vendor_name' => $aItem['author_name'],
+	            		'vendor_url' => $aItem['author_url'],
+	            		'bx_if:show_version' => array(
+	            			'condition' => !empty($aItem['file_version']),
+	            			'content' => array(
+	            				'version' => $aItem['file_version'],
+	            			)
+	            		),
+                    )
+                ),
+                'bx_if:show_count_price' => array(
+                    'condition' => $bShoppingCart,
+                    'content' => array(
+                        'count' => isset($aParams['counts'][$aItem['id']]) ? (int)$aParams['counts'][$aItem['id']] : 1,
+                        'price_single' => $sPrice,
+                        'discount_single' => $sDiscount,
+                    )
+                ),
+                'bx_if:show_rate' => array(
+                    'condition' => $bTmplVarsRate,
+                    'content' => $aTmplVarsRate
+                ),
+                'bx_if:show_votes' =>  array(
+                    'condition' => $bTmplVarsVotes,
+                    'content' => $aTmplVarsVotes
+                ),
+                'bx_if:show_purchase_single' => array(
+                    'condition' => $bPurchaseSingle && !$bInCart,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor_id' => $aItem['author_id'],
+                		'price_single' => _t('_adm_str_txt_price_single', $aItem['author_currency_sign'], $aItem['price_single'])
+                    )
+                ),
+                'bx_if:show_checkout_single' => array(
+                    'condition' => $bPurchaseSingle,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor_id' => $aItem['author_id'],
+                		'bx_if:show_as_hidden' => array(
+                			'condition' => !$bInCart,
+                			'content' => array()
+                		)
+                    )
+                ),
+                'bx_if:show_purchase_recurring' => array(
+                	'condition' => $bPurchaseRecurring,
+                	'content' => array(
+                		'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor_id' => $aItem['author_id'],
+                		'price_recurring' => _t('_adm_str_txt_price_recurring', $aItem['author_currency_sign'], $aItem['price_recurring'], _t('_adm_str_txt_per_' . $aItem['duration_recurring'] . '_short'))
+                	)
+                ),
+                'bx_if:show_download' => array(
+                    'condition' => $bDownload && !$bQueued && !$bDownloaded,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'file_id' => $aItem['file_id']
+                    )
+                ),
+                'bx_if:show_download_disabled' => array(
+					'condition' => $bDownload && !$bQueued && $bDownloaded,
+					'content' => array()
+				),
+				'bx_if:show_queued_disabled' => array(
+					'condition' => $bDownload && $bQueued && !$bDownloaded,
+					'content' => array()
+				),
+                'bx_if:show_delete' => array(
+                    'condition' => $bShoppingCart,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor_id' => $aItem['author_id']
+                    )
+                )
+            ));
+        }
+
+        return $sResult;
+    }
+
+    protected function displayUpdates($mixedItems, $aParams = array())
+    {
+        if(!is_array($mixedItems))
+            return MsgBox($mixedItems);
+
+		if(empty($mixedItems))
+			return MsgBox(_t('_Empty'));
+
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sResult = '';
+        foreach($mixedItems as $aItem) {
+            $bDownloadable = (int)$aItem['is_file'] == 1;
+            $bQueued = $this->oDb->isQueued('download', $aItem['name']);
+
+            $sIcon = !empty($aItem['thumbnail']['big']) ? $aItem['thumbnail']['big'] : BxDolStudioUtils::getIconDefault(BX_DOL_MODULE_TYPE_MODULE);
+            $bIcon = strpos($sIcon, '.') !== false;
+
+            $sImage = !empty($aItem['cover']['big']) ? $aItem['cover']['big'] : '';
+            $bImage = strpos($sImage, '.') !== false;
+
+            $sResult .= $oTemplate->parseHtmlByName('str_update_v2.html', array(
+                'js_object' => $sJsObject,
+                'id' => $aItem['id'],
+            	'name' => $aItem['name'],
+                'url' => $aItem['url'],
+            	'bx_if:no_icon' => array (
+	                'condition' => !$bIcon,
+	                'content' => array(
+            			'icon' => $sIcon
+            		),
+	            ),
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array(
+	                	'icon_url' => $sIcon,
+	            	),
+	            ),
+            	'bx_if:no_image' => array (
+	                'condition' => !$bImage,
+	                'content' => array(
+	                	'note' => $aItem['description_plain'],
+	                	'strecher' => mb_strlen($aItem['description_plain']) > 240 ? '' : str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', round((240 - mb_strlen($aItem['description_plain'])) / 6))
+            		),
+	            ),
+                'bx_if:image' => array (
+	                'condition' => $bImage,
+	                'content' => array(
+						'image_url' => $sImage,
+						'strecher' => str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', 40)
+	            	),
+	            ),
+                'title' => $aItem['title'],
+                'vendor' => $aItem['author_name'],
+                'versions' => _t('_adm_str_txt_update_from_to', $aItem['file_version'], $aItem['file_version_to']),
+                'bx_if:show_download' => array(
+                    'condition' => $bDownloadable && !$bQueued,
+                    'content' => array(
+                        'caption' => _t('_adm_btn_install_submit'),
+                        'on_click' => $sJsObject . ".getUpdateAndInstall('" . $aItem['name'] . "', this)"
+                    )
+                ),
+                'bx_if:show_queued_disabled' => array(
+                    'condition' => $bDownloadable && $bQueued,
+                    'content' => array()
+                )
+            ));
+        }
+
+        return $sResult;
+    }
+
+    protected function displayCategories($aCategories)
+    {
+        foreach($aCategories['bx_repeat:cats'] as $iKey => $aItem)
+            $aCategories['bx_repeat:cats'][$iKey]['url'] = $this->getBaseUrl(array('page' => 'category', 'value' => $aItem['value'], 'title' => urlencode($aItem['name'])));
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('str_lbl_categories.html', $aCategories);
+    }
+
+    protected function displayTags($aTags)
+    {
+        foreach($aTags['bx_repeat:units'] as $iKey => $aItem)
+            $aTags['bx_repeat:units'][$iKey]['href'] = $this->getBaseUrl(array('page' => 'tag', 'value' => $aItem['keyword']));
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('str_lbl_tags.html', $aTags);
+    }
+
+    protected function displayNonOwnerNotification()
+    {
+        if(BxDolStudioOAuth::isAuthorizedOwner())
+            return;
+
+        $oSession = BxDolSession::getInstance();
+        if($oSession->getValue($this->sSessionKeyNonOwnerNotified) === true)
+            return;
+
+        $oSession->setValue($this->sSessionKeyNonOwnerNotified, true);
+        return $this->getJsResultBy(array(
+            'message' => '_adm_msg_oauth_non_owner_logged',
+            'translate' => array(BX_DOL_MARKET_URL_ROOT),
+            'on_page_load' => true
+        ));
+    }
+
+    private function getDownloadedModules($bNamesOnly = true)
+    {
+		$aModules = BxDolStudioInstallerUtils::getInstance()->getModules(false);
+
+        return $bNamesOnly ? array_keys($aModules) : $aModules;
+    }
+
+    private function getVoteStars($aItem)
+    {
+        $aTmplVarsStars = array();
+        for($i = (int)$aItem['rate_min']; $i <= (int)$aItem['rate_max']; $i++)
+            $aTmplVarsStars[] = array('value' => $i);
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('str_vote_stars.html', array(
+        	'id' => $aItem['id'],
+        	'value' => (float)$aItem['rate'],
+            'bx_repeat:stars' => $aTmplVarsStars,
+            'bx_repeat:slider' => $aTmplVarsStars,
+        ));
+    }
+}
+
+/** @} */