Parcourir la source

Upgrade Patch 8.0.0-A4 to 8.0.0-A5

Alex Trofimov il y a 12 ans
Parent
commit
703c738972

+ 8 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/check.php

@@ -0,0 +1,8 @@
+<?php
+
+$mixCheckResult = 'Update can not be applied';
+
+if ('8.0.0-A4' == $this->oDb->getOne("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'"))
+    $mixCheckResult = true;
+
+return $mixCheckResult;

+ 2150 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/inc/classes/BxDolTemplate.php

@@ -0,0 +1,2150 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+ini_set('pcre.backtrack_limit', 1000000);
+
+define('BX_DOL_TEMPLATE_DEFAULT_CODE', 'uni');
+define('BX_DOL_TEMPLATE_FOLDER_ROOT', 'template');
+
+define('BX_DOL_TEMPLATE_INJECTIONS_CACHE', 'sys_injections.inc');
+
+define('BX_DOL_TEMPLATE_CHECK_IN_BOTH', 'both');
+define('BX_DOL_TEMPLATE_CHECK_IN_BASE', 'base');
+define('BX_DOL_TEMPLATE_CHECK_IN_TMPL', 'tmpl');
+
+define('BX_PAGE_DEFAULT', 0); ///< default, regular page
+define('BX_PAGE_CLEAR', 2); ///< clear page, without any headers and footers
+define('BX_PAGE_POPUP', 44); ///< popup page, without any headers and footers
+define('BX_PAGE_TRANSITION', 150); ///< transition page with redirect to display some msg, like 'please wait', without headers footers
+
+/**
+ * Template engine.
+ *
+ * An object of the class allows to:
+ *  1. Manage HTML templates.
+ *  2. Get URL/path for any template image/icon.
+ *  3. Attach CSS/JavaScript files to the output.
+ *  4. Add some content to any template key using Injection engine.
+ *
+ *
+ * Avalable constructions.
+ *  1. <bx_include_auto:template_name.html /> - the content of the file would be inserted. File would be taken from current template if it existes there, and from base directory otherwise.
+ *  2. <bx_include_base:template_name.html /> - the content of the file would be inserted. File would be taken from base directory.
+ *  3. <bx_include_tmpl:template_name.html /> - the content of the file would be inserted. File would be taken from tmpl_xxx directory.
+ *  4. <bx_url_root /> - the value of BX_DOL_URL_ROOT variable will be inserted.
+ *  5. <bx_url_admin /> - the value of BX_DOL_URL_ADMIN variable will be inserted.
+ *  6. <bx_text:_language_key /> - _language_key will be translated using language file(function _t()) and inserted.
+       <bx_text_js:_language_key /> - _language_key will be translated using language file(function _t()) and inserted, use it to insert text into js string.
+       <bx_text_attribute:_language_key /> - _language_key will be translated using language file(function _t()) and inserted, use it to insert text into html attribute.
+ *  7. <bx_image_url:image_file_name /> - image with 'image_file_name' file name will be searched in the images folder of current template.
+ *     If it's not found, then it will be searched in the images folder of base template. On success full URL will be inserted, otherwise an empty string.
+ *  8. <bx_icon_url:icon_file_name /> - the same with <bx_image_url:image_file_name />, but icons will be searched in the images/icons/ folders.
+ *  9. <bx_injection:injection_name /> - will be replaced with injections registered with the page and injection_name in the `sys_injections`/`sys_injections_admin`/ tables.
+ *  10. <bx_if:tag_name>some_HTML</bx_if:tag_name> - will be replaced with provided content if the condition is true, and with empty string otherwise.
+ *  11. <bx_repeat:cycle_name>some_HTML</bx_repeat:cycle_name> - an inner HTML content will be repeated in accordance with received data.
+ *
+ *
+ * Related classes:
+ *  BxDolTemplateAdmin - for processing admin templates.
+ *  Template classes in modules - for processing modiles' templates.
+ *
+ *
+ * Global variables:
+ *  oSysTemplate - is used for template processing in user part.
+ *  oAdmTemplate - is used for template processing in admin part.
+ *
+ *
+ * Add injection:
+ *  1. Register it in the `sys_injections` table or `sys_injections_admin` table for admin panel.
+ *  2. Clear injections cache(sys_injections.inc and sys_injections_admin.inc in cache folder).
+ *
+ *
+ * Predefined template keys to add injections:
+ *  1. injection_head - add injections in the <head> tag.
+ *  2. injection_body - add ingection(attribute) in the <body> tag.
+ *  3. injection_header - add injection inside the <body> tag at the very beginning.
+ *  4. injection_logo_before - add injection at the left of the main logo(inside logo's DIV).
+ *  5. injection_logo_after - add injection at the right of the main logo(inside logo's DIV).
+ *  6. injection_between_logo_top_menu - add injection between logo and top menu.
+ *  7. injection_top_menu_before - add injection at the left of the top menu(inside top menu's DIV).
+ *  8. injection_top_menu_after - add injection at the right of the top menu(inside top menu's DIV).
+ *  13. injection_content_before - add injection just before main content(inside content's DIV).
+ *  14. injection_content_after - add injection just after main content(inside content's DIV).
+ *  15. injection_between_content_footer - add injection between content and footer.
+ *  16. injection_footer_before - add injection at the left of the footer(inside footer's DIV).
+ *  17. injection_footer_after - add injection at the right of the footer(inside footer's DIV).
+ *  18. injection_footer - add injection inside the <body> tag at the very end.
+ *
+ *
+ * Example of usage:
+ * @code
+ *  $oSysTemplate = BxDolTemplate::getInstance();
+ *
+ *  $oSysTemplate->addCss(array('test1.css', 'test2.css'));
+ *  $oSysTemplate->addJs(array('test1.js', 'test2.js'));
+ *  $oSysTemplate->parseHtmlByName('messageBox.html', array(
+ *    'id' => $iId,
+ *     'msgText' => $sText,
+ *     'bx_if:timer' => array(
+ *        'condition' => $iTimer > 0,
+ *        'content' => array(
+ *           'id' => $iId,
+ *           'time' => 1000 * $iTimer,
+ *           'on_close' => $sOnClose,
+ *        )
+ *     ),
+ *     'bx_if:timer' => array(
+ *        array(
+ *           'name' => $sName,
+ *           'title' => $sTitle
+ *        ),
+ *        array(
+ *           'name' => $sName,
+ *           'title' => $sTitle
+ *        )
+ *     )
+ *  ));
+ * @endcode
+ *
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+class BxDolTemplate extends BxDol implements iBxDolSingleton
+{
+    /**
+     * Main fields
+     */
+    protected $_sPrefix;
+    protected $_sRootPath;
+    protected $_sRootUrl;
+    protected $_sSubPath;
+    protected $_sInjectionsTable;
+    protected $_sInjectionsCache;
+    protected $_sCode;
+    protected $_sCodeKey;
+    protected $_sKeyWrapperHtml;
+    protected $_sFolderHtml;
+    protected $_sFolderCss;
+    protected $_sFolderImages;
+    protected $_sFolderIcons;
+    protected $_aTemplates;
+
+    protected $_aLocations;
+    protected $_aLocationsJs;
+
+    /**
+     * Cache related fields
+     */
+    protected $_bCacheEnable;
+    protected $_sCacheFolderUrl;
+    protected $_sCachePublicFolderUrl;
+    protected $_sCachePublicFolderPath;
+    protected $_sCacheFilePrefix;
+
+    protected $_bImagesInline;
+    protected $_iImagesMaxSize;
+
+    protected $_bCssCache;
+    protected $_bCssArchive;
+    protected $_sCssCachePrefix;
+
+    protected $_bJsCache;
+    protected $_bJsArchive;
+    protected $_sJsCachePrefix;
+
+    /**
+     * Less related fields
+     */
+    protected $_bLessEnable;
+    protected $_sLessCachePrefix;
+
+    /**
+     * Minify related fields
+     */
+    protected $_bMinifyEnable;
+
+    protected $aPage;
+    protected $aPageContent;
+
+    protected $_oConfigTemplate;
+
+    /**
+     * Constructor
+     */
+    protected function __construct($sRootPath = BX_DIRECTORY_PATH_ROOT, $sRootUrl = BX_DOL_URL_ROOT)
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->_sPrefix = 'BxDolTemplate';
+
+        $this->_sRootPath = $sRootPath;
+        $this->_sRootUrl = $sRootUrl;
+        $this->_sInjectionsTable = 'sys_injections';
+        $this->_sInjectionsCache = BX_DOL_TEMPLATE_INJECTIONS_CACHE;
+
+        $this->_sCodeKey = 'skin';
+
+        $sCode = getParam('template');
+        if(empty($sCode))
+            $sCode = BX_DOL_TEMPLATE_DEFAULT_CODE;
+        $this->_checkCode($sCode, false);
+
+        //--- Check selected template in COOKIE(the lowest priority) ---//
+        $sCode = !empty($_COOKIE[$this->_sCodeKey]) ? $_COOKIE[$this->_sCodeKey] : '';
+        $this->_checkCode($sCode, false);
+
+        //--- Check selected template in GET(the highest priority) ---//
+        $sCode = !empty($_GET[$this->_sCodeKey]) ? $_GET[$this->_sCodeKey] : '';
+        $this->_checkCode($sCode, true);
+
+        if(isset($_GET[$this->_sCodeKey])) {
+            bx_import('BxDolPermalinks');
+            if(BxDolPermalinks::getInstance()->redirectIfNecessary(array($this->_sCodeKey)))
+                exit;
+        }
+
+        $this->_sKeyWrapperHtml = '__';
+        $this->_sFolderHtml = '';
+        $this->_sFolderCss = 'css/';
+        $this->_sFolderImages = 'images/';
+        $this->_sFolderIcons = 'images/icons/';
+        $this->_aTemplates = array();
+
+        $this->addLocation('system', $this->_sRootPath, $this->_sRootUrl);
+
+        $this->addLocationJs('system_inc_js', BX_DIRECTORY_PATH_INC . 'js/' , BX_DOL_URL_ROOT . 'inc/js/');
+        $this->addLocationJs('system_inc_js_classes', BX_DIRECTORY_PATH_INC . 'js/classes/' , BX_DOL_URL_ROOT . 'inc/js/classes/');
+        $this->addLocationJs('system_plugins_public', BX_DIRECTORY_PATH_PLUGINS_PUBLIC, BX_DOL_URL_PLUGINS_PUBLIC);
+
+        $this->_bCacheEnable = !defined('BX_DOL_CRON_EXECUTE') && getParam('sys_template_cache_enable') == 'on';
+        $this->_sCacheFolderUrl = '';
+        $this->_sCachePublicFolderUrl = BX_DOL_URL_CACHE_PUBLIC;
+        $this->_sCachePublicFolderPath = BX_DIRECTORY_PATH_CACHE_PUBLIC;
+        $this->_sCacheFilePrefix = "bx_templ_";
+
+        $this->_bImagesInline = getParam('sys_template_cache_image_enable') == 'on';
+        $this->_iImagesMaxSize = (int)getParam('sys_template_cache_image_max_size') * 1024;
+
+        $bArchive = getParam('sys_template_cache_compress_enable') == 'on';
+        $this->_bCssCache = !defined('BX_DOL_CRON_EXECUTE') && getParam('sys_template_cache_css_enable') == 'on';
+        $this->_bCssArchive = $this->_bCssCache && $bArchive;
+        $this->_sCssCachePrefix = $this->_sCacheFilePrefix . 'css_';
+
+        $this->_bJsCache = !defined('BX_DOL_CRON_EXECUTE') && getParam('sys_template_cache_js_enable') == 'on';
+        $this->_bJsArchive = $this->_bJsCache && $bArchive;
+        $this->_sJsCachePrefix = $this->_sCacheFilePrefix . 'js_';
+
+        $this->_bLessEnable = true;
+        $this->_sLessCachePrefix = $this->_sCacheFilePrefix . 'less_';
+
+        $this->_bMinifyEnable = true;
+
+        $this->aPage = array();
+        $this->aPageContent = array();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__])) {
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolTemplate();
+            $GLOBALS['bxDolClasses'][__CLASS__]->init();
+        }
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    /**
+     * Load templates.
+     */
+    function loadTemplates()
+    {
+        $aResult = array();
+        foreach($this->_aTemplates as $sName)
+            $aResult[$sName] = $this->getHtml($sName . '.html');
+        $this->_aTemplates = $aResult;
+    }
+    /**
+     * Initialize template engine.
+     * Note. The method is executed with the system, you shouldn't execute it in your subclasses.
+     */
+    function init()
+    {
+        //--- Load page elements related static variables ---//
+        $this->aPage = array(
+            'name_index' => BX_PAGE_DEFAULT,
+            'header' => '',
+            'header_text' => '',
+            'keywords' => array(),
+            'description'  => '',
+            'robots' => '',
+            'css_name' => array(),
+            'css_compiled' => array(),
+            'css_system' => array(),
+            'js_name' => array(),
+            'js_compiled' => array(),
+            'js_system' => array(),
+            'js_options' => array(),
+            'js_translations' => array(),
+            'js_images' => array(),
+            'injections' => array()
+        );
+
+        //--- Load default CSS ---//
+        $this->addCssSystem(array(
+            'common.css',
+            'default.less',
+            'general.css',
+            'icons.css',
+            'colors.css',
+            'forms.css',
+            'media-desktop.css',
+            'media-tablet.css',
+            'media-phone.css',
+            'media-print.css',
+        ));
+
+        //--- Load default JS ---//
+        $this->addJsSystem(array(
+            'jquery/jquery.min.js',
+            'jquery/jquery-migrate.min.js',
+            'jquery-ui/jquery.ui.position.min.js',
+            'spin.min.js',
+            'jquery.easing.js',
+            'jquery.cookie.min.js',
+            'moment-with-langs.js',
+            'functions.js',
+            'jquery.webForms.js',
+            'jquery.dolPopup.js',
+        ));
+
+        //--- Load default JS ---//
+        bx_import('BxDolLanguages');
+        $this->addJsTranslation(array(
+            '_are you sure?',
+            '_error occured',
+            '_sys_loading',
+        ));
+
+        //--- Load injection's cache ---//
+        if (getParam('sys_db_cache_enable')) {
+            $oDb = BxDolDb::getInstance();
+            $oCache = $oDb->getDbCacheObject();
+            $sCacheKey = $oDb->genDbCacheKey($this->_sInjectionsCache);
+
+            $aInjections = $oCache->getData($sCacheKey);
+            if ($aInjections === null) {
+                $aInjections = $this->getInjectionsData();
+                $oCache->setData ($sCacheKey, $aInjections);
+            }
+        } else {
+            $aInjections = $this->getInjectionsData();
+        }
+
+        $this->aPage['injections'] = $aInjections;
+
+        bx_import('BxTemplConfig');
+        $this->_oConfigTemplate = BxTemplConfig::getInstance();
+    }
+
+    protected function getInjectionsData ()
+    {
+        $oDb = BxDolDb::getInstance();
+
+        $aInjections = $oDb->getAll("SELECT `page_index`, `name`, `key`, `type`, `data`, `replace` FROM `" . $this->_sInjectionsTable . "` WHERE `active`='1'");
+        if (!$aInjections)
+            return array();
+
+        foreach ($aInjections as $aInjection)
+            $aInjections['page_' . $aInjection['page_index']][$aInjection['key']][] = $aInjection;
+
+        return $aInjections;
+    }
+
+    /**
+     * Set page name index
+     * @param int $i name index
+     */
+    function setPageNameIndex($i)
+    {
+        $this->aPage['name_index'] = $i;
+    }
+
+    /**
+     * Get page name index
+     * @return int $i name index
+     */
+    function getPageNameIndex()
+    {
+        return isset($this->aPage['name_index']) ? (int)$this->aPage['name_index'] : 0;
+    }
+
+    /**
+     * Set page header
+     * @param string $s page header
+     */
+    function setPageHeader($s)
+    {
+        $this->aPage['header'] = $s;
+    }
+
+    /**
+     * Get page header
+     * @return string $s page header
+     */
+    function getPageHeader()
+    {
+        return $this->aPage['header'];
+    }
+
+    /**
+     * Set page params. Available page params are: name_index, header
+     * @param array $a page params
+     */
+    function setPageParams($a)
+    {
+        if (!empty($this->aPage))
+            $this->aPage = array_merge($this->aPage, $a);
+        else
+            $this->aPage = $a;
+    }
+
+    /**
+     * Get page params.
+     * @return array $a page params
+     */
+    function getPageParams()
+    {
+        return $this->aPage;
+    }
+
+    /**
+     * Set page description.
+     *
+     * @param string $sDescription necessary page description.
+     */
+    function setPageDescription($sDescription)
+    {
+        $this->aPage['description'] = $sDescription;
+    }
+
+    /**
+     * Set page meta robots.
+     *
+     * @param string $s page meta robots.
+     */
+    function setPageMetaRobots($s)
+    {
+        $this->aPage['robots'] = $s;
+    }
+
+    /**
+     * Set page content for some variable.
+     * @param string $sVar     name of content variable
+     * @param string $sContent content for $sVar variable
+     * @param int    $iIndex   optional page index, default is index which was set before with @see setPageNameIndex function, or 0
+     */
+    function setPageContent($sVar, $sContent, $iIndex = false)
+    {
+        $i = false !== $iIndex ? $iIndex : $this->getPageNameIndex();
+        $this->aPageContent[$i][$sVar] = $sContent;
+    }
+
+    /**
+     * Get page content for some variable.
+     * @param  string $sVar   name of content variable
+     * @param  int    $iIndex optional page index, default is index which was set before with @see setPageNameIndex function, or 0
+     * @return string page content for some variable or for the whole page.
+     */
+    function getPageContent($sVar = false, $iIndex = false)
+    {
+        $i = false !== $iIndex ? $iIndex : $this->getPageNameIndex();
+        return false !== $sVar ? $this->aPageContent[$i][$sVar] : $this->aPageContent[$i];
+    }
+
+    /**
+     * Get currently active template code.
+     *
+     * @return string template's code.
+     */
+    function getCode()
+    {
+        return $this->_sCode;
+    }
+
+    /**
+     * Check whether code is associated with active template.
+     *
+     * @param string  $sCode      template's unique URI.
+     * @param boolean $bSetCookie save code in COOKIE or not.
+     */
+    protected function _checkCode($sCode, $bSetCookie)
+    {
+        if(empty($sCode) || !preg_match('/^[A-Za-z0-9_-]+$/', $sCode))
+            return;
+
+        bx_import('BxDolModuleQuery');
+        $aModule = BxDolModuleQuery::getInstance()->getModuleByUri($sCode);
+        if(empty($aModule) || !is_array($aModule) || (int)$aModule['enabled'] != 1 || !file_exists($this->_sRootPath . 'modules/' . $aModule['path'] . 'data/template/'))
+            return;
+
+        bx_import('BxDolModuleConfig');
+        $oConfig = new BxDolModuleConfig($aModule);
+
+        $this->_sCode = $oConfig->getUri();
+        $this->_sSubPath = $oConfig->getDirectory();
+
+        if(!$bSetCookie || bx_get('preview'))
+            return;
+
+        $aUrl = parse_url(BX_DOL_URL_ROOT);
+        $sPath = isset($aUrl['path']) && !empty($aUrl['path']) ? $aUrl['path'] : '/';
+
+        setcookie( $this->_sCodeKey, $this->_sCode, time() + 60*60*24*365, $sPath);
+    }
+
+    /**
+     * Get currently active template path.
+     *
+     * @return string template's path.
+     */
+    function getPath()
+    {
+        return $this->_sSubPath;
+    }
+
+    /**
+     * Set page title.
+     * @deprecated use setPageHeader
+     *
+     * @param string $sTitle necessary page title.
+     */
+    function setPageTitle($sTitle)
+    {
+        $this->setPageHeader($sTitle);
+    }
+
+    /**
+     * Set page's main box title.
+     * @deprecated use setPageParams
+     *
+     * @param string $sTitle necessary page's main box title.
+     */
+    function setPageMainBoxTitle($sTitle)
+    {
+        $this->setPageParams(array('header_text' => $sTitle));
+    }
+
+    /**
+     * Add location in array of locations.
+     * Note. Location is the path/url to folder where 'templates' folder is stored.
+     *
+     * @param string $sKey          - location's    unique key.
+     * @param string $sLocationPath - location's path. For modules: '[path_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @param string $sLocationUrl  - location's url. For modules: '[url_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     */
+    function addLocation($sKey, $sLocationPath, $sLocationUrl)
+    {
+        $this->_aLocations[$sKey] = array(
+            'path' => $sLocationPath,
+            'url' => $sLocationUrl,
+        );
+
+        return $sKey;
+    }
+    /**
+     * Add dynamic location.
+     *
+     * @param  string   $sLocationPath - location's path. For modules: '[path_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @param  string   $sLocationUrl  - location's url. For modules: '[url_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @return location key. Is needed to remove the location.
+     */
+    function addDynamicLocation($sLocationPath, $sLocationUrl)
+    {
+        $sLocationKey = time();
+        $this->addLocation($sLocationKey, $sLocationPath, $sLocationUrl);
+
+        return $sLocationKey;
+    }
+    /**
+     * Remove location from array of locations.
+     * Note. Location is the path/url to folder where templates are stored.
+     *
+     * @param string $sKey - location's    unique key.
+     */
+    function removeLocation($sKey)
+    {
+        if(isset($this->_aLocations[$sKey]))
+           unset($this->_aLocations[$sKey]);
+    }
+    /**
+     * Add JS location in array of JS locations.
+     * Note. Location is the path/url to folder where JS files are stored.
+     *
+     * @param string $sKey          - location's    unique key.
+     * @param string $sLocationPath - location's path. For modules: '[path_to_dolphin]/modules/[vendor_name]/[module_name]/js/'
+     * @param string $sLocationUrl  - location's url. For modules: '[url_to_dolphin]/modules/[vendor_name]/[module_name]/js/'
+     */
+    function addLocationJs($sKey, $sLocationPath, $sLocationUrl)
+    {
+        $this->_aLocationsJs[$sKey] = array(
+            'path' => $sLocationPath,
+            'url' => $sLocationUrl
+        );
+    }
+    /**
+     * Add dynamic JS location.
+     *
+     * @param  string   $sLocationPath - location's path. For modules: '[path_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @param  string   $sLocationUrl  - location's url. For modules: '[url_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @return location key. Is needed to remove the location.
+     */
+    function addDynamicLocationJs($sLocationPath, $sLocationUrl)
+    {
+        $sLocationKey = time();
+        $this->addLocationJs($sLocationKey, $sLocationPath, $sLocationUrl);
+
+        return $sLocationKey;
+    }
+    /**
+     * Remove JS location from array of locations.
+     * Note. Location is the path/url to folder where templates are stored.
+     *
+     * @param string $sKey - JS location's    unique key.
+     */
+    function removeLocationJs($sKey)
+    {
+        if(isset($this->_aLocationsJs[$sKey]))
+           unset($this->_aLocationsJs[$sKey]);
+    }
+    /**
+     * Add Option in JS output.
+     *
+     * @param mixed $mixedName option's name or an array of options' names.
+     */
+    function addJsOption($mixedName)
+    {
+        if(is_string($mixedName))
+            $mixedName = array($mixedName);
+
+        foreach($mixedName as $sName)
+            $this->aPage['js_options'][$sName] = getParam($sName);
+    }
+    /**
+     * Add language translation for key in JS output.
+     *
+     * @param mixed $mixedKey language key or an array of keys.
+     */
+    function addJsTranslation($mixedKey)
+    {
+        if(is_string($mixedKey))
+            $mixedKey = array($mixedKey);
+
+        foreach($mixedKey as $sKey)
+            $this->aPage['js_translations'][$sKey] = _t($sKey, '{0}', '{1}');
+    }
+    /**
+     * Add image in JS output.
+     *
+     * @param array $aImages an array of image descriptors.
+     *                       The descriptor is a key/value pear in the array of descriptors.
+     */
+    function addJsImage($aImages)
+    {
+        if(!is_array($aImages))
+            return;
+
+        foreach($aImages as $sKey => $sFile) {
+            $sUrl = $this->getImageUrl($sFile);
+            if(empty($sUrl))
+                continue;
+
+            $this->aPage['js_images'][$sKey] = $sUrl;
+        }
+    }
+    /**
+     * Add icon in JS output.
+     *
+     * @param array $aIcons an array of icons descriptors.
+     *                      The descriptor is a key/value pear in the array of descriptors.
+     */
+    function addJsIcon($aIcons)
+    {
+        if(!is_array($aIcons))
+            return;
+
+        foreach($aIcons as $sKey => $sFile) {
+            $sUrl = $this->getIconUrl($sFile);
+            if(empty($sUrl))
+                continue;
+
+            $this->aPage['js_images'][$sKey] = $sUrl;
+        }
+    }
+    /**
+     * Set page keywords.
+     *
+     * @param mixed  $mixedKeywords necessary page keywords(string - single keyword, array - an array of keywords).
+     * @param string $sDevider      - string devider.
+     */
+    function addPageKeywords($mixedKeywords, $sDevider = ',')
+    {
+        if(is_string($mixedKeywords))
+            $mixedKeywords = strpos($mixedKeywords, $sDevider) !== false ? explode($sDevider, $mixedKeywords) : array($mixedKeywords);
+
+        foreach($mixedKeywords as $iKey => $sValue)
+            $mixedKeywords[$iKey] = trim($sValue);
+
+        $this->aPage['keywords'] = isset($this->aPage['keywords']) && is_array($this->aPage['keywords']) ? array_merge($this->aPage['keywords'], $mixedKeywords) : $mixedKeywords;
+    }
+    /**
+     * Get template, which was loaded earlier.
+     * @see method this->loadTemplates and field this->_aTemplates
+     *
+     * @param  string $sName - template name.
+     * @return string template's content.
+     */
+    function getTemplate($sName)
+    {
+        return $this->_aTemplates[$sName];
+    }
+    /**
+     * Get full URL for the icon.
+     *
+     * @param  string $sName    icon's file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full URL.
+     */
+    function getIconUrl($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        $sContent = "";
+        if(($sContent = $this->_getInlineData('icon', $sName, $sCheckIn)) !== false)
+            return $sContent;
+
+        return $this->_getAbsoluteLocation('url', $this->_sFolderIcons, $sName, $sCheckIn);
+    }
+    /**
+     * Get absolute Path for the icon.
+     *
+     * @param  string $sName    - icon's file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string absolute path.
+     */
+    function getIconPath($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('path', $this->_sFolderIcons, $sName, $sCheckIn);
+    }
+    /**
+     * Get full URL for the image.
+     *
+     * @param  string $sName    - images's file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full URL.
+     */
+    function getImageUrl($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        $sContent = "";
+        if(($sContent = $this->_getInlineData('image', $sName, $sCheckIn)) !== false)
+            return $sContent;
+
+        return $this->_getAbsoluteLocation('url', $this->_sFolderImages, $sName, $sCheckIn);
+    }
+    /**
+     * Get absolute Path for the image.
+     *
+     * @param  string $sName    - image's file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string absolute path.
+     */
+    function getImagePath($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('path', $this->_sFolderImages, $sName, $sCheckIn);
+    }
+    /**
+     * Get full URL of CSS file.
+     *
+     * @param  string $sName    - CSS file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full URL.
+     */
+    function getCssUrl($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('url', $this->_sFolderCss, $sName, $sCheckIn);
+    }
+    /**
+     * Get full Path of CSS file.
+     *
+     * @param  string $sName    - CSS file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full URL.
+     */
+    function getCssPath($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('path', $this->_sFolderCss, $sName, $sCheckIn);
+    }
+
+    /**
+     * Get menu.
+     * @param $s menu object name
+     * @return html or empty string
+     */
+    function getMenu ($s)
+    {
+        bx_import('BxDolMenu');
+        $oMenu = BxDolMenu::getObjectInstance($s);
+        return $oMenu ? $oMenu->getCode () : '';
+    }
+
+    /**
+     * Get content of HTML file.
+     *
+     * @param  string $sName    - HTML file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full content of the file and false on failure.
+     */
+    function getHtml($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        $sAbsolutePath = $this->_getAbsoluteLocation('path', $this->_sFolderHtml, $sName, $sCheckIn);
+        return !empty($sAbsolutePath) ? trim(file_get_contents($sAbsolutePath)) : false;
+    }
+
+    /**
+     * Parse HTML template. Search for the template with accordance to it's file name.
+     *
+     * @see allows to use cache.
+     *
+     * @param  string $sName               - HTML file name.
+     * @param  array  $aVariables          - key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @param  mixed  $mixedKeyWrapperHtml - key wrapper(string value if left and right parts are the same, array(left, right) otherwise).
+     * @param  string $sCheckIn            where the content would be searched(base, template, both)
+     * @return string the result of operation.
+     */
+    function parseHtmlByName($sName, $aVariables, $mixedKeyWrapperHtml = null, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginTemplate($sName, $sRand = time().rand());
+
+        if (($sContent = $this->getCached($sName, $aVariables, $mixedKeyWrapperHtml, $sCheckIn)) !== false) {
+            if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endTemplate($sName, $sRand, $sRet, true);
+            return $sContent;
+        }
+
+        $sRet = '';
+        if (($sContent = $this->getHtml($sName, $sCheckIn)) !== false)
+            $sRet = $this->_parseContent($sContent, $aVariables, $mixedKeyWrapperHtml);
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endTemplate($sName, $sRand, $sRet, false);
+
+        return $sRet;
+    }
+    /**
+     * Parse HTML template.
+     *
+     * @see Doesn't allow to use cache.
+     *
+     * @param  string $sContent            - HTML file content.
+     * @param  array  $aVariables          - key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @param  mixed  $mixedKeyWrapperHtml - key wrapper(string value if left and right parts are the same, array(left, right) otherwise).
+     * @return string the result of operation.
+     */
+    function parseHtmlByContent($sContent, $aVariables, $mixedKeyWrapperHtml = null)
+    {
+        if(empty($sContent))
+            return "";
+
+        return $this->_parseContent($sContent, $aVariables, $mixedKeyWrapperHtml);
+    }
+    /**
+     * Parse earlier loaded HTML template.
+     *
+     * @see Doesn't allow to use cache.
+     *
+     * @param  string $sName      - template name.
+     * @param  array  $aVariables - key/value pairs. Key should be the same as template's key, excluding prefix and postfix.
+     * @return string the result of operation.
+     * @see $this->_aTemplates
+     */
+    function parseHtmlByTemplateName($sName, $aVariables, $mixedKeyWrapperHtml = null)
+    {
+        if(!isset($this->_aTemplates[$sName]) || empty($this->_aTemplates[$sName]))
+            return "";
+
+        return $this->_parseContent($this->_aTemplates[$sName], $aVariables, $mixedKeyWrapperHtml);
+    }
+    /**
+     * Parse page HTML template. Search for the page's template with accordance to it's file name.
+     *
+     * @see allows to use cache.
+     *
+     * @param  string $sName      - HTML file name.
+     * @param  array  $aVariables - key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @return string the result of operation.
+     */
+    function parsePageByName($sName, $aVariables)
+    {
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginPage($sName);
+
+        $sContent = $this->parseHtmlByName($sName, $aVariables, $this->_sKeyWrapperHtml, BX_DOL_TEMPLATE_CHECK_IN_BOTH);
+        if(empty($sContent))
+            $sContent = $this->parseHtmlByName('default.html', $aVariables, $this->_sKeyWrapperHtml, BX_DOL_TEMPLATE_CHECK_IN_BOTH);
+
+        //--- Add CSS and JS at the very last ---//
+        if(strpos($sContent , '<bx_include_css />') !== false) {
+            if (!empty($this->aPage['css_name']))
+                $this->addCss($this->aPage['css_name']);
+            $sContent = str_replace('<bx_include_css />', $this->includeFiles('css', true) . $this->includeFiles('css'), $sContent);
+        }
+
+        if(strpos($sContent , '<bx_include_js />') !== false) {
+            if (!empty($this->aPage['js_name']))
+                $this->addJs($this->aPage['js_name']);
+            $sContent = str_replace('<bx_include_js />', $this->includeFiles('js', true) . $this->includeFiles('js'), $sContent);
+        }
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endPage($sContent);
+
+        return $sContent;
+    }
+    /**
+     * Parse system keys.
+     *
+     * @param  string $sKey key
+     * @return string value associated with the key.
+     */
+    function parseSystemKey($sKey, $mixedKeyWrapperHtml = null, $bProcessInjection = true)
+    {
+        $aKeyWrappers = $this->_getKeyWrappers($mixedKeyWrapperHtml);
+
+        $sRet = '';
+        switch( $sKey ) {
+            case 'page_charset':
+                $sRet = 'UTF-8';
+                break;
+            case 'page_robots':
+                if(!empty($this->aPage['robots']) && is_string($this->aPage['robots']))
+                    $sRet = '<meta name="robots" content="' . bx_html_attribute($this->aPage['robots']) . '" />';
+                break;
+            case 'page_keywords':
+                if(!empty($this->aPage['keywords']) && is_array($this->aPage['keywords']))
+                    $sRet = '<meta name="keywords" content="' . bx_html_attribute(implode(',', $this->aPage['keywords'])) . '" />';
+                break;
+            case 'page_description':
+                if(!empty($this->aPage['description']) && is_string($this->aPage['description']))
+                    $sRet = '<meta name="description" content="' . bx_html_attribute($this->aPage['description']) . '" />';
+                break;
+            case 'page_header':
+                if(isset($this->aPage['header']))
+                    $sRet = bx_process_output(strip_tags($this->aPage['header']));
+                break;
+            case 'page_header_text':
+                if(isset($this->aPage['header_text']))
+                    $sRet = bx_process_output($this->aPage['header_text']);
+                break;
+            case 'page_icons':
+                bx_import('BxTemplFunctions');
+                $sRet = BxTemplFunctions::getInstance()->getMetaIcons();
+                break;
+            case 'popup_loading':
+                $sContent = $this->parsePageByName('popup_loading.html', array());
+
+                bx_import('BxTemplFunctions');
+                $sRet = BxTemplFunctions::getInstance()->transBox('bx-popup-loading', $sContent, true);
+                break;
+            case 'lang':
+                $sRet = bx_lang_name();
+                break;
+            case 'main_logo':
+                bx_import('BxTemplFunctions');
+                $sRet = BxTemplFunctions::getInstance()->getMainLogo();
+                break;
+            case 'informer':
+                bx_import('BxDolInformer');
+                $oInformer = BxDolInformer::getInstance($this);
+                $sRet = $oInformer ? $oInformer->display() : '';
+                break;
+            case 'dol_images':
+                $sRet = $this->_processJsImages();
+                break;
+            case 'dol_lang':
+                $sRet = $this->_processJsTranslations();
+                break;
+            case 'dol_options':
+                $sRet = $this->_processJsOptions();
+                break;
+            case 'bottom_text':
+                $sRet = _t( '_bottom_text', date('Y') );
+                break;
+            case 'copyright':
+                $sRet = _t( '_copyright',   date('Y') ) . getVersionComment();
+                break;
+            case 'extra_js':
+                $sRet = empty($this->aPage['extra_js']) ? '' : $this->aPage['extra_js'];
+                break;
+            case 'is_profile_page':
+                $sRet = (defined('BX_PROFILE_PAGE')) ? 'true' : 'false';
+                break;
+
+            default:
+                bx_import('BxTemplFunctions');
+                $sRet = ($sTemplAdd = BxTemplFunctions::getInstance()->TemplPageAddComponent($sKey)) !== false ? $sTemplAdd : $aKeyWrappers['left'] . $sKey . $aKeyWrappers['right'];
+        }
+
+        if($bProcessInjection)
+            $sRet = $this->processInjection($this->getPageNameIndex(), $sKey, $sRet);
+
+        return $sRet;
+    }
+    function getCacheFilePrefix($sType)
+    {
+    	$sResult = '';
+    	switch($sType) {
+    		case 'template':
+				$sResult = $this->_sCacheFilePrefix;
+				break;
+
+    		case 'css':
+    			$sResult = $this->_sCssCachePrefix;
+    			break;
+
+    		case 'js':
+    			$sResult = $this->_sJsCachePrefix;
+    			break;
+    	}
+
+    	return $sResult;
+    }
+    /**
+     * Get cache object for templates
+     * @return cache class instance
+     */
+    function getTemplatesCacheObject ()
+    {
+        $sCacheEngine = getParam('sys_template_cache_engine');
+        $oCacheEngine = bx_instance('BxDolCache' . $sCacheEngine);
+        if(!$oCacheEngine->isAvailable())
+            $oCacheEngine = bx_instance('BxDolCacheFileHtml');
+        return $oCacheEngine;
+    }
+    /**
+     * Get template from cache if it's enabled.
+     *
+     * @param  string  $sName               template name
+     * @param  string  $aVariables          key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @param  mixed   $mixedKeyWrapperHtml - key wrapper(string value if left and right parts are the same, array(0 => left, 1 => right) otherwise).
+     * @param  string  $sCheckIn            where the content would be searched(base, template, both)
+     * @param  boolean $bEvaluate           need to evaluate the template or not.
+     * @return string  result of operation or false on failure.
+     */
+    function getCached($sName, &$aVariables, $mixedKeyWrapperHtml = null, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH, $bEvaluate = true)
+    {
+        // initialization
+
+        if(!$this->_bCacheEnable)
+            return false;
+
+        $sAbsolutePath = $this->_getAbsoluteLocation('path', $this->_sFolderHtml, $sName, $sCheckIn);
+        if(empty($sAbsolutePath))
+            return false;
+
+        $oCacheEngine = $this->getTemplatesCacheObject ();
+        $isFileBasedEngine = $bEvaluate && method_exists($oCacheEngine, 'getDataFilePath');
+
+        // try to get cached content
+
+        $sCacheVariableName = "a";
+        $sCacheKey = $this->_getCacheFileName('html', $sAbsolutePath) . '.php';
+        if ($isFileBasedEngine)
+            $sCacheContent = $oCacheEngine->getDataFilePath($sCacheKey);
+        else
+            $sCacheContent = $oCacheEngine->getData($sCacheKey);
+
+
+        // recreate cache if it is empty
+
+        if ($sCacheContent === null && ($sContent = file_get_contents($sAbsolutePath)) !== false && ($sContent = $this->_compileContent($sContent, "\$" . $sCacheVariableName, 1, $aVariables, $mixedKeyWrapperHtml)) !== false) {
+            if (false === $oCacheEngine->setData($sCacheKey, $sContent))
+                return false;
+
+            if ($isFileBasedEngine)
+                $sCacheContent = $oCacheEngine->getDataFilePath($sCacheKey);
+            else
+                $sCacheContent = $sContent;
+        }
+
+        if ($sCacheContent === null)
+            return false;
+
+        // return simple cache content
+
+        if(!$bEvaluate)
+            return $sCacheContent;
+
+        // return evaluated cache content
+
+        ob_start();
+
+        $$sCacheVariableName = &$aVariables;
+
+        if ($isFileBasedEngine)
+            include($sCacheContent);
+        else
+            eval('?'.'>' . $sCacheContent);
+
+        $sContent = ob_get_clean();
+
+        return $sContent;
+    }
+
+    /**
+     * Add JS file(s) to global output.
+     *
+     * @param  mixed          $mixedFiles string value represents a single JS file name. An array - array of JS file names.
+     * @param  boolean        $bDynamic   in the dynamic mode JS file(s) are not included to global output, but are returned from the function directly.
+     * @return boolean/string result of operation.
+     */
+    function addJs($mixedFiles, $bDynamic = false)
+    {
+        return $this->_processFiles('js', 'add', $mixedFiles, $bDynamic);
+    }
+
+    /**
+     * Add System JS file(s) to global output.
+     * System JS files are the files which are attached to all pages. They will be cached separately from the others.
+     *
+     * @param  mixed          $mixedFiles string value represents a single JS file name. An array - array of JS file names.
+     * @param  boolean        $bDynamic   in the dynamic mode JS file(s) are not included to global output, but are returned from the function directly.
+     * @return boolean/string result of operation.
+     */
+    function addJsSystem($mixedFiles)
+    {
+        return $this->_processFiles('js', 'add', $mixedFiles, false, true);
+    }
+
+    /**
+     * Delete JS file(s) from global output.
+     *
+     * @param  mixed   $mixedFiles string value represents a single JS file name. An array - array of JS file names.
+     * @return boolean result of operation.
+     */
+    function deleteJs($mixedFiles)
+    {
+        return $this->_processFiles('js', 'delete', $mixedFiles);
+    }
+
+    /**
+     * Delete System JS file(s) from global output.
+     *
+     * @param  mixed   $mixedFiles string value represents a single JS file name. An array - array of JS file names.
+     * @return boolean result of operation.
+     */
+    function deleteJsSystem($mixedFiles)
+    {
+        return $this->_processFiles('js', 'delete', $mixedFiles, false, true);
+    }
+
+    /**
+     * Compile JS files in one file.
+     *
+     * @param  string $sAbsolutePath CSS file absolute path(full URL for external CSS/JS files).
+     * @param  array  $aIncluded     an array of already included JS files.
+     * @return string result of operation.
+     */
+    function _compileJs($sAbsolutePath, &$aIncluded)
+    {
+        if(isset($aIncluded[$sAbsolutePath]))
+           return '';
+
+        $bExternal = strpos($sAbsolutePath, "http://") !== false || strpos($sAbsolutePath, "https://") !== false;
+        if($bExternal) {
+            $sPath = $sAbsolutePath;
+            $sName = '';
+
+            $sContent = bx_file_get_contents($sAbsolutePath);
+        } else {
+            $aFileInfo = pathinfo($sAbsolutePath);
+            $sPath = $aFileInfo['dirname'] . DIRECTORY_SEPARATOR;
+            $sName = $aFileInfo['basename'];
+
+            $sContent = file_get_contents($sPath . $sName);
+        }
+
+        if(empty($sContent))
+            return '';
+
+        $sUrl = bx_ltrim_str($sPath, realpath(BX_DIRECTORY_PATH_ROOT), BX_DOL_URL_ROOT);
+        $sUrl = str_replace(DIRECTORY_SEPARATOR, '/', $sPath);
+
+        $sContent = "\r\n/*--- BEGIN: " . $sUrl . $sName . "---*/\r\n" . $sContent . ";\r\n/*--- END: " . $sUrl . $sName . "---*/\r\n";
+        $sContent = str_replace(array("\n\r", "\r\n", "\r"), "\n", $sContent);
+
+        $aIncluded[$sAbsolutePath] = 1;
+
+        return preg_replace(
+            array(
+                "'<bx_url_root />'",
+                "'\r\n'"
+            ),
+            array(
+                BX_DOL_URL_ROOT,
+                "\n"
+            ),
+            $sContent
+        );
+    }
+    /**
+     * Wrap an URL to JS file into JS tag.
+     *
+     * @param  string $sFile - URL to JS file.
+     * @return string the result of operation.
+     */
+    function _wrapInTagJs($sFile)
+    {
+        return "<script language=\"javascript\" type=\"text/javascript\" src=\"" . $sFile . "\"></script>";
+    }
+    /**
+     * Wrap JS code into JS tag.
+     *
+     * @param  string $sCode - JS code.
+     * @return string the result of operation.
+     */
+    function _wrapInTagJsCode($sCode)
+    {
+        return "<script language=\"javascript\" type=\"text/javascript\">\n<!--\n" . $sCode . "\n-->\n</script>";
+    }
+
+    /**
+     * Add CSS file(s) to global output.
+     *
+     * @param  mixed          $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @param  boolean        $bDynamic   in the dynamic mode CSS file(s) are not included to global output, but are returned from the function directly.
+     * @return boolean/string result of operation
+     */
+    function addCss($mixedFiles, $bDynamic = false)
+    {
+        return $this->_processFiles('css', 'add', $mixedFiles, $bDynamic);
+    }
+
+    /**
+     * Add System CSS file(s) to global output.
+     * System CSS files are the files which are attached to all pages. They will be cached separately from the others.
+     *
+     * @param  mixed          $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @return boolean/string result of operation
+     */
+    function addCssSystem($mixedFiles)
+    {
+        return $this->_processFiles('css', 'add', $mixedFiles, false, true);
+    }
+
+    /**
+     * Delete CSS file(s) from global output.
+     *
+     * @param  mixed   $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @return boolean result of operation.
+     */
+    function deleteCss($mixedFiles)
+    {
+        return $this->_processFiles('css', 'delete', $mixedFiles);
+    }
+    /**
+     * Delete System CSS file(s) from global output.
+     *
+     * @param  mixed   $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @return boolean result of operation.
+     */
+    function deleteCssSystem($mixedFiles)
+    {
+        return $this->_processFiles('css', 'delete', $mixedFiles, false, true);
+    }
+    /**
+     * Compile CSS files' structure(@see @import css_file_path) in one file.
+     *
+     * @param  string $sAbsolutePath CSS file absolute path(full URL for external CSS/JS files).
+     * @param  array  $aIncluded     an array of already included CSS files.
+     * @return string result of operation.
+     */
+    function _compileCss($sAbsolutePath, &$aIncluded)
+    {
+        if(isset($aIncluded[$sAbsolutePath]))
+           return '';
+
+        $bExternal = strpos($sAbsolutePath, "http://") !== false || strpos($sAbsolutePath, "https://") !== false;
+        if($bExternal) {
+            $sPath = $sAbsolutePath;
+            $sName = '';
+
+            $sContent = bx_file_get_contents($sAbsolutePath);
+        } else {
+            $aFileInfo = pathinfo($sAbsolutePath);
+            $sPath = $aFileInfo['dirname'] . DIRECTORY_SEPARATOR;
+            $sName = $aFileInfo['basename'];
+
+            $sContent = file_get_contents($sPath . $sName);
+        }
+
+        if(empty($sContent))
+            return '';
+
+        $sUrl = bx_ltrim_str($sPath, realpath(BX_DIRECTORY_PATH_ROOT), BX_DOL_URL_ROOT);
+        $sUrl = str_replace(DIRECTORY_SEPARATOR, '/', $sPath);
+
+        $sContent = "\r\n/*--- BEGIN: " . $sUrl . $sName . "---*/\r\n" . $sContent . "\r\n/*--- END: " . $sUrl . $sName . "---*/\r\n";
+        $aIncluded[$sAbsolutePath] = 1;
+
+        $sContent = str_replace(array("\n\r", "\r\n", "\r"), "\n", $sContent);
+        if($bExternal) {
+            $sContent = preg_replace(
+                array(
+                    "'@import\s+url\s*\(\s*[\'|\"]*\s*([a-zA-Z0-9\.\/_-]+)\s*[\'|\"]*\s*\)\s*;'e",
+                    "'url\s*\(\s*[\'|\"]*\s*([a-zA-Z0-9\.\/\?\#_=-]+)\s*[\'|\"]*\s*\)'e"
+                ),
+                array(
+                    "",
+                    "'url(' . \$sPath . '\\1)'"
+                ),
+                $sContent
+            );
+        } else {
+            $sContent = preg_replace(
+                "'@import\s+url\s*\(\s*[\'|\"]*\s*([a-zA-Z0-9\.\/_-]+)\s*[\'|\"]*\s*\)\s*;'e",
+                "\$this->_compileCss(realpath(\$sPath . dirname('\\1')) . DIRECTORY_SEPARATOR . basename('\\1'), \$aIncluded)",
+                $sContent
+            );
+
+            $sContent = preg_replace_callback(
+                "'url\s*\(\s*[\'|\"]*\s*([a-zA-Z0-9\.\/\?\#_=-]+)\s*[\'|\"]*\s*\)'",
+                create_function('$aMatches', 'return BxDolTemplate::_callbackParseUrl("' . addslashes($sPath) . '", $aMatches);'),
+                $sContent
+            );
+        }
+        return $sContent;
+    }
+
+    /**
+     * Less CSS
+     *
+     * @param  mixed $mixed CSS string to process with Less compiler or an array with CSS file's Path and URL.
+     * @return mixed string or an array with CSS file's Path and URL.
+     */
+    function _lessCss($mixed)
+    {
+        require_once(BX_DIRECTORY_PATH_PLUGINS . 'lessphp/lessc.inc.php');
+        $oLess = new lessc();
+        $oLess->setVariables($this->_oConfigTemplate->aLessConfig);
+
+        if(is_array($mixed) && isset($mixed['url']) && isset($mixed['path'])) {
+            $sPathFile = realpath($mixed['path']);
+            $aInfoFile = pathinfo($sPathFile);
+            if (!isset($aInfoFile['extension']) || $aInfoFile['extension'] != 'less')
+                return $mixed;
+
+            $sPathRoot = realpath(BX_DIRECTORY_PATH_ROOT);
+            $sFile = $this->_sLessCachePrefix . trim(str_replace(array('.' . $aInfoFile['extension'], DIRECTORY_SEPARATOR), array('', '_'), bx_ltrim_str($sPathFile, $sPathRoot)), '_') . '.css';
+
+            $oLess->checkedCompile($mixed['path'], $this->_sCachePublicFolderPath . $sFile);
+            return array('url' => $this->_sCachePublicFolderUrl . $sFile, 'path' => $this->_sCachePublicFolderPath . $sFile);
+        }
+
+        return $oLess->compile($mixed);
+    }
+
+    /**
+     * Minify CSS
+     *
+     * @param  string $s CSS string to minify
+     * @return string minified CSS string.
+     */
+    function _minifyCss($s)
+    {
+        require_once(BX_DIRECTORY_PATH_PLUGINS . 'minify/lib/Minify/CSS/Compressor.php');
+        return Minify_CSS_Compressor::process($s);
+    }
+
+    /**
+     * Static callback function for CSS compiler.
+     *
+     * @param  string $sPath    CSS file absolute path.
+     * @param  array  $aMatches matched parts of image's URL.
+     * @return string converted image's URL.
+     */
+    public static function _callbackParseUrl($sPath, $aMatches)
+    {
+        $sFile = basename($aMatches[1]);
+        $sDirectory = dirname($aMatches[1]);
+
+        $sRootPath = realpath(BX_DIRECTORY_PATH_ROOT);
+        $sAbsolutePath = realpath($sPath . $sDirectory) . DIRECTORY_SEPARATOR . $sFile;
+
+        $sRootPath = str_replace(DIRECTORY_SEPARATOR, '/', $sRootPath);
+        $sAbsolutePath = str_replace(DIRECTORY_SEPARATOR, '/', $sAbsolutePath);
+
+        return 'url(' . bx_ltrim_str($sAbsolutePath, $sRootPath, BX_DOL_URL_ROOT) . ')';
+    }
+
+    /**
+     * Wrap an URL to CSS file into CSS tag.
+     *
+     * @param  string $sFile - URL to CSS file.
+     * @return string the result of operation.
+     */
+    function _wrapInTagCss($sFile)
+    {
+        if (!$sFile)
+            return '';
+        return "<link href=\"" . $sFile . "\" rel=\"stylesheet\" type=\"text/css\" />";
+    }
+    /**
+     * Wrap CSS code into CSS tag.
+     *
+     * @param  string $sCode - CSS code.
+     * @return string the result of operation.
+     */
+    function _wrapInTagCssCode($sCode)
+    {
+        return "<link rel=\"stylesheet\" type=\"text/css\">" . $sCode . "</link>";
+    }
+    /**
+     * Include CSS/JS file(s) attached to the page in its head section.
+     * @see the method is system and would be called automatically.
+     *
+     * @param  string $sType the type of file('js' or 'css')
+     * @return string the result CSS code.
+     */
+    function includeFiles($sType, $bSystem = false)
+    {
+        $sUpcaseType = ucfirst($sType);
+
+        $sArrayKey = $sType . ($bSystem ? '_system' : '_compiled');
+        $aFiles = isset($this->aPage[$sArrayKey]) ? $this->aPage[$sArrayKey] : array();
+        if(empty($aFiles) || !is_array($aFiles))
+            return "";
+
+        if(!$this->{'_b' . $sUpcaseType . 'Cache'})
+            return $this->_includeFiles($sType, $aFiles);
+
+        //--- If cache already exists, return it ---//
+        $sMethodWrap = '_wrapInTag' . $sUpcaseType;
+        $sMethodCompile = '_compile' . $sUpcaseType;
+        $sMethodLess = '_less' . $sUpcaseType;
+        $sMethodMinify = '_minify' . $sUpcaseType;
+
+        ksort($aFiles);
+
+        $sName = "";
+        foreach($aFiles as $aFile)
+            $sName .= $aFile['url'];
+        $sName = $this->_getCacheFileName($sType, $sName);
+
+        $sCacheAbsoluteUrl = $this->_sCachePublicFolderUrl . $sName . '.' . $sType;
+        $sCacheAbsolutePath = $this->_sCachePublicFolderPath . $sName . '.' . $sType;
+        if(file_exists($sCacheAbsolutePath)) {
+            if($this->{'_b' . $sUpcaseType . 'Archive'})
+                $sCacheAbsoluteUrl = $this->_getLoaderUrl($sType, $sName);
+
+           return $this->$sMethodWrap($sCacheAbsoluteUrl);
+        }
+
+        //--- Collect all attached CSS/JS in one file ---//
+        $bLess = $this->_bLessEnable && method_exists($this, $sMethodLess);
+
+        $sResult = "";
+        $aIncluded = array();
+        foreach($aFiles as $aFile) {
+        	if($bLess)
+				$aFile = $this->$sMethodLess($aFile);
+
+            if(($sContent = $this->$sMethodCompile($aFile['path'], $aIncluded)) !== false)
+                $sResult .= $sContent;
+        }
+
+        if ($this->_bMinifyEnable && method_exists($this, $sMethodMinify))
+            $sResult = $this->$sMethodMinify($sResult);
+
+        $mixedWriteResult = false;
+        if(!empty($sResult) && ($rHandler = fopen($sCacheAbsolutePath, 'w')) !== false) {
+            $mixedWriteResult = fwrite($rHandler, $sResult);
+            fclose($rHandler);
+            @chmod ($sCacheAbsolutePath, 0666);
+        }
+
+        if($mixedWriteResult === false)
+            return $this->_includeFiles($sType, $aFiles);
+
+        if($this->{'_b' . $sUpcaseType . 'Archive'})
+            $sCacheAbsoluteUrl = $this->_getLoaderUrl($sType, $sName);
+
+        return $this->$sMethodWrap($sCacheAbsoluteUrl);
+    }
+    /**
+     * Include CSS/JS files without caching.
+     *
+     * @param  string $sType  the file type (css or js)
+     * @param  array  $aFiles CSS/JS files to be added to the page.
+     * @return string result of operation.
+     */
+    function _includeFiles($sType, &$aFiles)
+    {
+        $sUpcaseType = ucfirst($sType);
+
+        $sMethodWrap = '_wrapInTag' . $sUpcaseType;
+        $sMethodLess = '_less' . $sUpcaseType;
+
+        $sResult = "";
+        foreach($aFiles as $aFile) {
+            if($this->_bLessEnable && method_exists($this, $sMethodLess))
+                $aFile = $this->$sMethodLess($aFile);
+
+            $sResult .= $this->$sMethodWrap($aFile['url']);
+        }
+
+        return $sResult;
+    }
+    /**
+     * Insert/Delete CSS file from output stack.
+     *
+     * @param  string  $sType      the file type (css or js)
+     * @param  string  $sAction    add/delete
+     * @param  mixed   $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @return boolean result of operation.
+     */
+    function _processFiles($sType, $sAction, $mixedFiles, $bDynamic = false, $bSystem = false)
+    {
+        if(empty($mixedFiles))
+            return $bDynamic ? "" : false;
+
+        if(is_string($mixedFiles))
+            $mixedFiles = array($mixedFiles);
+
+        $sUpcaseType = ucfirst($sType);
+        $sMethodLocate = '_getAbsoluteLocation' . $sUpcaseType;
+        $sMethodWrap = '_wrapInTag' . $sUpcaseType;
+        $sResult = '';
+        foreach($mixedFiles as $sFile) {
+            //--- Process 3d Party CSS/JS file ---//
+            if(strpos($sFile, "http://") !== false || strpos($sFile, "https://") !== false) {
+                $sUrl = $sFile;
+                $sPath = $sFile;
+            }
+            //--- Process Custom CSS/JS file ---//
+            else if(strpos($sFile, "|") !== false) {
+                $sFile = implode('', explode("|", $sFile));
+
+                $sUrl = BX_DOL_URL_ROOT . $sFile;
+                $sPath = realpath(BX_DIRECTORY_PATH_ROOT . $sFile);
+            }
+            //--- Process Common CSS/JS file(check in default locations) ---//
+            else {
+                $sUrl = $this->$sMethodLocate('url', $sFile);
+                $sPath = $this->$sMethodLocate('path', $sFile);
+            }
+
+            if(empty($sPath) || empty($sUrl))
+                continue;
+
+            $sArrayKey = $sType . ($bSystem ? '_system' : '_compiled');
+            switch($sAction) {
+                case 'add':
+                    if($bDynamic)
+                        $sResult .= $this->$sMethodWrap($sUrl);
+                    else {
+                        $bFound = false;
+                        foreach($this->aPage[$sArrayKey]  as $iKey => $aValue)
+                            if($aValue['url'] == $sUrl && $aValue['path'] == $sPath) {
+                                $bFound = true;
+                                break;
+                            }
+
+                        if(!$bFound)
+                            $this->aPage[$sArrayKey][] = array('url' => $sUrl, 'path' => $sPath);
+                    }
+                    break;
+                case 'delete':
+                    if(!$bDynamic)
+                        foreach($this->aPage[$sArrayKey]  as $iKey => $aValue)
+                            if($aValue['url'] == $sUrl) {
+                                unset($this->aPage[$sArrayKey][$iKey]);
+                                break;
+                            }
+                    break;
+            }
+        }
+
+        return $bDynamic ? $sResult : true;
+    }
+
+    /**
+     * Parse content.
+     *
+     * @param  string $sContent            - HTML file's content.
+     * @param  array  $aVariables          - key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @param  mixed  $mixedKeyWrapperHtml - key wrapper(string value if left and right parts are the same, array(0 => left, 1 => right) otherwise).
+     * @return string the result of operation.
+     */
+    function _parseContent($sContent, $aVariables, $mixedKeyWrapperHtml = null)
+    {
+        $aKeys = array_keys($aVariables);
+        $aValues = array_values($aVariables);
+
+        $aKeyWrappers = $this->_getKeyWrappers($mixedKeyWrapperHtml);
+
+        $iCountKeys = count($aKeys);
+        for ($i = 0; $i < $iCountKeys; $i++) {
+            if (strncmp($aKeys[$i], 'bx_repeat:', 10) === 0) {
+                $sKey = "'<" . $aKeys[$i] . ">(.*)<\/" . $aKeys[$i] . ">'s";
+
+                $aMatches = array();
+                preg_match($sKey, $sContent, $aMatches);
+
+                $sValue = '';
+                if(isset($aMatches[1]) && !empty($aMatches[1])) {
+                    if(is_array($aValues[$i]))
+                        foreach($aValues[$i] as $aValue)
+                            $sValue .= $this->parseHtmlByContent($aMatches[1], $aValue, $mixedKeyWrapperHtml);
+                    else if(is_string($aValues[$i]))
+                        $sValue = $aValues[$i];
+                }
+            } else if (strncmp($aKeys[$i], 'bx_if:', 6) === 0) {
+                $sKey = "'<" . $aKeys[$i] . ">(.*)<\/" . $aKeys[$i] . ">'s";
+
+                $aMatches = array();
+                preg_match($sKey, $sContent, $aMatches);
+
+                $sValue = '';
+                if(isset($aMatches[1]) && !empty($aMatches[1]))
+                    if(is_array($aValues[$i]) && isset($aValues[$i]['content']) && isset($aValues[$i]['condition']) && $aValues[$i]['condition'])
+                        $sValue .= $this->parseHtmlByContent($aMatches[1], $aValues[$i]['content'], $mixedKeyWrapperHtml);
+            } else {
+                $sKey = "'" . $aKeyWrappers['left'] . $aKeys[$i] . $aKeyWrappers['right'] . "'s";
+                $sValue = str_replace('$', '\\$', $aValues[$i]);
+            }
+
+            $aKeys[$i] = $sKey;
+            $aValues[$i] = $sValue;
+        }
+
+        $aKeys = array_merge($aKeys, array(
+            "'<bx_include_auto:([^\s]+) \/>'se",
+            "'<bx_include_tmpl:([^\s]+) \/>'se",
+            "'<bx_include_base:([^\s]+) \/>'se",
+            "'<bx_injection:([^\s]+) />'se",
+            "'<bx_image_url:([^\s]+) \/>'se",
+            "'<bx_icon_url:([^\s]+) \/>'se",
+            "'<bx_text:([_\{\}\w\d\s]+[^\s]{1}) \/>'se",
+            "'<bx_text_js:([^\s]+) \/>'se",
+            "'<bx_text_attribute:([^\s]+) \/>'se",
+            "'<bx_menu:([^\s]+) \/>'se",
+            "'<bx_url_root />'",
+            "'<bx_url_studio />'",
+        ));
+        $aValues = array_merge($aValues, array(
+            "\$this->parseHtmlByName('\\1', \$aVariables, \$mixedKeyWrapperHtml, BX_DOL_TEMPLATE_CHECK_IN_BOTH)",
+            "\$this->parseHtmlByName('\\1', \$aVariables, \$mixedKeyWrapperHtml, BX_DOL_TEMPLATE_CHECK_IN_TMPL)",
+            "\$this->parseHtmlByName('\\1', \$aVariables, \$mixedKeyWrapperHtml, BX_DOL_TEMPLATE_CHECK_IN_BASE)",
+            "\$this->processInjection(\$this->aPage['name_index'], '\\1')",
+            "\$this->getImageUrl('\\1')",
+            "\$this->getIconUrl('\\1')",
+            "_t('\\1')",
+            "bx_js_string(_t('\\1'))",
+            "bx_html_attribute(_t('\\1'))",
+            "\$this->getMenu('\\1')",
+            BX_DOL_URL_ROOT,
+            BX_DOL_URL_STUDIO,
+        ));
+
+        //--- Parse Predefined Keys ---//
+        $sContent = preg_replace($aKeys, $aValues, $sContent);
+        //--- Parse System Keys ---//
+        $sContent = preg_replace( "'" . $aKeyWrappers['left'] . "([a-zA-Z0-9_-]+)" . $aKeyWrappers['right'] . "'e", "\$this->parseSystemKey('\\1', \$mixedKeyWrapperHtml)", $sContent);
+
+        return $sContent;
+    }
+
+    /**
+     * Compile content
+     *
+     * @param  string  $sContent            template.
+     * @param  string  $aVarName            variable name to be saved in the output file.
+     * @param  integer $iVarDepth           depth is used to process nesting, for example, in cycles.
+     * @param  array   $aVarValues          values to be compiled in.
+     * @param  mixed   $mixedKeyWrapperHtml key wrapper(string value if left and right parts are the same, array(0 => left, 1 => right) otherwise).
+     * @return string  the result of operation.
+     */
+    function _compileContent($sContent, $aVarName, $iVarDepth, $aVarValues, $mixedKeyWrapperHtml = null)
+    {
+        $aKeys = array_keys($aVarValues);
+        $aValues = array_values($aVarValues);
+
+        $aKeyWrappers = $this->_getKeyWrappers($mixedKeyWrapperHtml);
+
+        for($i = 0; $i < count($aKeys); $i++) {
+            if(strpos($aKeys[$i], 'bx_repeat:') === 0) {
+                $sKey = "'<" . $aKeys[$i] . ">(.*)<\/" . $aKeys[$i] . ">'s";
+
+                $aMatches = array();
+                preg_match($sKey, $sContent, $aMatches);
+
+                $sValue = '';
+                if(isset($aMatches[1]) && !empty($aMatches[1])) {
+                    if(empty($aValues[$i]) || !is_array($aValues[$i]))
+                        return false;
+
+                    $sIndex = "\$" . str_repeat("i", $iVarDepth);
+                    $sValue .= "<?php if(is_array(" . $aVarName . "['" . $aKeys[$i] . "'])) for(" . $sIndex . "=0; " . $sIndex . "<count(" . $aVarName . "['" . $aKeys[$i] . "']); " . $sIndex . "++){ ?>";
+                    if(($sInnerValue = $this->_compileContent($aMatches[1], $aVarName . "['" . $aKeys[$i] . "'][" . $sIndex . "]", $iVarDepth + 1, current($aValues[$i]), $mixedKeyWrapperHtml)) === false)
+                        return false;
+                    $sValue .= $sInnerValue;
+                    $sValue .= "<?php } else if(is_string(" . $aVarName . "['" . $aKeys[$i] . "'])) echo " . $aVarName . "['" . $aKeys[$i] . "']; ?>";
+                }
+            } else if(strpos($aKeys[$i], 'bx_if:') === 0) {
+                $sKey = "'<" . $aKeys[$i] . ">(.*)<\/" . $aKeys[$i] . ">'s";
+
+                $aMatches = array();
+                preg_match($sKey, $sContent, $aMatches);
+
+                $sValue = '';
+                if(isset($aMatches[1]) && !empty($aMatches[1])) {
+                    if(!is_array($aValues[$i]) || !isset($aValues[$i]['content']) || empty($aValues[$i]['content']) || !is_array($aValues[$i]['content']))
+                        return false;
+
+                    $sValue .= "<?php if(" . $aVarName . "['" . $aKeys[$i] . "']['condition']){ ?>";
+                    if(($sInnerValue = $this->_compileContent($aMatches[1], $aVarName . "['" . $aKeys[$i] . "']['content']", $iVarDepth, $aValues[$i]['content'], $mixedKeyWrapperHtml)) === false)
+                        return false;
+                    $sValue .= $sInnerValue;
+                    $sValue .= "<?php } ?>";
+                }
+            } else {
+                $sKey = "'" . $aKeyWrappers['left'] . $aKeys[$i] . $aKeyWrappers['right'] . "'s";
+                $sValue = "<?php echo " . $aVarName . "['" . $aKeys[$i] . "'];?>";
+            }
+
+            $aKeys[$i] = $sKey;
+            $aValues[$i] = $sValue;
+        }
+
+        try {
+        	$oTemplate = &$this;
+
+        	$aCallbackPatterns = array(
+        		"'<bx_include_auto:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_BOTH,
+        		"'<bx_include_base:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_BASE,
+        		"'<bx_include_tmpl:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_TMPL
+        	);
+	        foreach($aCallbackPatterns as $sPattern => $sCheckIn)
+		        $sContent = preg_replace_callback($sPattern, function($aMatches) use($oTemplate, $aVarValues, $mixedKeyWrapperHtml, $sCheckIn) {
+		        	$mixedResult = $oTemplate->getCached($aMatches[1], $aVarValues, $mixedKeyWrapperHtml, $sCheckIn, false);
+		        	if($mixedResult === false)
+		        		throw new Exception('Unable to create cache file.');
+
+		        	return $mixedResult;
+		        }, $sContent);
+
+			$aCallbackPatterns = array(
+				"'<bx_image_url:([^\s]+) \/>'s" => "get_image_url",
+				"'<bx_icon_url:([^\s]+) \/>'s" => "get_icon_url",
+				"'<bx_text:([_\{\}\w\d\s]+[^\s]{1}) \/>'s" => "get_text",
+				"'<bx_text_js:([^\s]+) \/>'s" => "get_text_js",
+				"'<bx_text_attribute:([^\s]+) \/>'s" => "get_text_attribute",
+			);
+			foreach($aCallbackPatterns as $sPattern => $sAction)
+				$sContent = preg_replace_callback($sPattern, function($aMatches) use($oTemplate, $sAction) {
+			    	$sResult = '';
+
+		        	switch($sAction) {
+		        		case 'get_image_url':
+		        			$sResult = $oTemplate->getImageUrl($aMatches[1]);
+		        			break;
+		        		case 'get_icon_url':
+		        			$sResult = $oTemplate->getIconUrl($aMatches[1]);
+		        			break;
+						case 'get_text':
+		        			$sResult = _t($aMatches[1]);
+		        			break;
+		        		case 'get_text_js':
+		        			$sResult = bx_js_string(_t($aMatches[1]));
+		        			break;
+		        		case 'get_text_attribute':
+		        			$sResult = bx_html_attribute(_t($aMatches[1]));
+		        			break;
+		        	}
+
+		        	return $sResult;
+		        }, $sContent);
+        }
+        catch(Exception $oException) {
+        	return false;
+        }
+
+        $aKeys = array_merge($aKeys, array(
+            "'<bx_injection:([^\s]+) />'s",
+            "'<bx_menu:([^\s]+) \/>'s",
+            "'<bx_url_root />'",
+            "'<bx_url_studio />'"
+        ));
+        $aValues = array_merge($aValues, array(
+            "<?php echo \$this->processInjection(\$this->aPage['name_index'], '\\1'); ?>",
+            "<?php echo \$this->getMenu('\\1'); ?>",
+            BX_DOL_URL_ROOT,
+            BX_DOL_URL_STUDIO
+        ));
+
+        //--- Parse Predefined Keys ---//
+        $sContent = preg_replace($aKeys, $aValues, $sContent);
+        //--- Parse System Keys ---//
+        $sContent = preg_replace( "'" . $aKeyWrappers['left'] . "([a-zA-Z0-9_-]+)" . $aKeyWrappers['right'] . "'", "<?php echo \$this->parseSystemKey('\\1', \$mixedKeyWrapperHtml);?>", $sContent);
+
+        return $sContent;
+    }
+    /**
+     * Get absolute location of some template's part.
+     *
+     * @param  string $sType    - result type. Available values 'url' and 'path'.
+     * @param  string $sFolder  - folders to be searched in. @see $_sFolderHtml, $_sFolderCss, $_sFolderImages and $_sFolderIcons
+     * @param  string $sName    - requested part name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string absolute location (path/url) of the part.
+     */
+    function _getAbsoluteLocation($sType, $sFolder, $sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        $sDirectory = $this->getPath();
+
+        if($sType == 'path') {
+            $sDivider = DIRECTORY_SEPARATOR;
+            $sRoot = BX_DIRECTORY_PATH_ROOT;
+        } else if($sType == 'url') {
+            $sDivider = '/';
+            $sRoot = BX_DOL_URL_ROOT;
+        }
+
+        if(strpos($sName,'|') !== false) {
+            $aParts = explode('|', $sName);
+            $sName = $aParts[1];
+
+            if(strpos($aParts[0],'@') !== false) {
+                $aLocationParts = explode('@', $aParts[0]);
+                $sLocationKey = $this->addLocation($aLocationParts[0], BX_DIRECTORY_PATH_ROOT . $aLocationParts[1], BX_DOL_URL_ROOT . $aLocationParts[1]);
+            }
+        }
+
+        $sResult = '';
+        $aLocations = array_reverse($this->_aLocations, true);
+        foreach($aLocations as $sKey => $aLocation) {
+            if(($sCheckIn == BX_DOL_TEMPLATE_CHECK_IN_BOTH || $sCheckIn == BX_DOL_TEMPLATE_CHECK_IN_TMPL) && extFileExists(BX_DIRECTORY_PATH_MODULES . $this->getPath(). 'data' . DIRECTORY_SEPARATOR . BX_DOL_TEMPLATE_FOLDER_ROOT . DIRECTORY_SEPARATOR . $sKey . DIRECTORY_SEPARATOR . $sFolder . $sName))
+                $sResult = $sRoot . 'modules' . $sDivider . $sDirectory. 'data' . $sDivider . 'template' . $sDivider . $sKey . $sDivider . $sFolder . $sName;
+            else if(($sCheckIn == BX_DOL_TEMPLATE_CHECK_IN_BOTH || $sCheckIn == BX_DOL_TEMPLATE_CHECK_IN_BASE) && extFileExists($aLocation['path'] . BX_DOL_TEMPLATE_FOLDER_ROOT . DIRECTORY_SEPARATOR . $sFolder . $sName))
+                $sResult = $aLocation[$sType] . BX_DOL_TEMPLATE_FOLDER_ROOT . $sDivider . $sFolder . $sName;
+            else
+                continue;
+            break;
+        }
+
+        /**
+         * try to find from received path
+         */
+        if(!$sResult && @is_file(BX_DIRECTORY_PATH_ROOT . $aParts[0] . DIRECTORY_SEPARATOR . $aParts[1])) {
+            $sResult = $sRoot . $aParts[0] . $sDivider . $aParts[1];
+        }
+
+        if(isset($sLocationKey))
+           $this->removeLocation($sLocationKey);
+
+        return $sType == 'path' && !empty($sResult) ? realpath($sResult) : $sResult;
+    }
+    /**
+     * Get absolute location of some template's part.
+     *
+     * @param  string $sType result type. Available values 'url' and 'path'.
+     * @param  string $sName requested part name.
+     * @return string absolute location (path/url) of the part.
+     */
+    function _getAbsoluteLocationJs($sType, $sName)
+    {
+        $sResult = '';
+        $aLocations = array_reverse($this->_aLocationsJs, true);
+        foreach($aLocations as $sKey => $aLocation) {
+            if(extFileExists($aLocation['path'] . $sName))
+                $sResult = $aLocation[$sType] . $sName;
+            else
+                continue;
+            break;
+        }
+        return $sType == 'path' && !empty($sResult) ? realpath($sResult) : $sResult;
+    }
+    function _getAbsoluteLocationCss($sType, $sName)
+    {
+        return $this->_getAbsoluteLocation($sType, $this->_sFolderCss, $sName);
+    }
+    /**
+     * Get inline data for Images and Icons.
+     *
+     * @param  string  $sType    image/icon
+     * @param  string  $sName    file name
+     * @param  string  $sCheckIn where the content would be searched(base, template, both)
+     * @return unknown
+     */
+    function _getInlineData($sType, $sName, $sCheckIn)
+    {
+        switch($sType) {
+            case 'image':
+                $sFolder = $this->_sFolderImages;
+                break;
+            case 'icon':
+                $sFolder = $this->_sFolderIcons;
+                break;
+        }
+        $sPath = $this->_getAbsoluteLocation('path', $sFolder, $sName, $sCheckIn);
+
+        $iFileSize = 0;
+        if($this->_bImagesInline && ($iFileSize = filesize($sPath)) !== false && $iFileSize < $this->_iImagesMaxSize) {
+            $aFileInfo = pathinfo($sPath);
+            return "data:image/" . strtolower($aFileInfo['extension']) . ";base64," . base64_encode(file_get_contents($sPath));
+        }
+
+        return false;
+    }
+    /**
+     * Get file name where the template would be cached.
+     *
+     * @param  string $sAbsolutePath template's real path.
+     * @return string the result of operation.
+     */
+    function _getCacheFileName($sType, $sAbsolutePath)
+    {
+        $sResult = bx_site_hash($sAbsolutePath);
+        switch($sType) {
+            case 'html':
+                $sResult = $this->_sCacheFilePrefix . bx_lang_name() . '_' . $this->_sCode .  '_' . $sResult;
+                break;
+            case 'css':
+                $sResult = $this->_sCssCachePrefix . $sResult;
+                break;
+            case 'js':
+                $sResult = $this->_sJsCachePrefix . $sResult;
+                break;
+        }
+
+        return $sResult;
+    }
+    /**
+     * Get template key wrappers(left, right)
+     *
+     * @param  mixed $mixedKeyWrapperHtml key wrapper(string value if left and right parts are the same, array(0 => left, 1 => right) otherwise).
+     * @return array result of operation.
+     */
+    function _getKeyWrappers($mixedKeyWrapperHtml)
+    {
+        $aResult = array();
+        if(!empty($mixedKeyWrapperHtml) && is_string($mixedKeyWrapperHtml))
+            $aResult = array('left' => $mixedKeyWrapperHtml, 'right' => $mixedKeyWrapperHtml);
+        else if(!empty($mixedKeyWrapperHtml) && is_array($mixedKeyWrapperHtml))
+            $aResult = array('left' => $mixedKeyWrapperHtml[0], 'right' => $mixedKeyWrapperHtml[1]);
+        else
+            $aResult = array('left' => $this->_sKeyWrapperHtml, 'right' => $this->_sKeyWrapperHtml);
+        return $aResult;
+    }
+
+    /**
+     * Process all added language translations and return them as a string.
+     *
+     * @return string with JS code.
+     */
+    function _processJsTranslations()
+    {
+        $sReturn = '';
+        if(isset($this->aPage['js_translations']) && is_array($this->aPage['js_translations'])) {
+            foreach($this->aPage['js_translations'] as $sKey => $sString)
+                $sReturn .= "'" .  bx_js_string($sKey) . "': '" . bx_js_string($sString) . "',";
+
+            $sReturn = substr($sReturn, 0, -1);
+        }
+
+        return '<script type="text/javascript" language="javascript">var aDolLang = {' . $sReturn . '};</script>';
+    }
+    /**
+     * Process all added options and return them as a string.
+     *
+     * @return string with JS code.
+     */
+    function _processJsOptions()
+    {
+        $sReturn = '';
+        if(isset($this->aPage['js_options']) && is_array($this->aPage['js_options'])) {
+            foreach($this->aPage['js_options'] as $sName => $mixedValue)
+                $sReturn .= "'" .  bx_js_string($sName) . "': '" . bx_js_string($mixedValue) . "',";
+
+            $sReturn = substr($sReturn, 0, -1);
+        }
+
+        return '<script type="text/javascript" language="javascript">var aDolOptions = {' . $sReturn . '};</script>';
+    }
+    /**
+     * Process all added images and return them as a string.
+     *
+     * @return string with JS code.
+     */
+    function _processJsImages()
+    {
+        $sReturn = '';
+        if(isset($this->aPage['js_images']) && is_array($this->aPage['js_images'])) {
+            foreach($this->aPage['js_images'] as $sKey => $sUrl)
+                $sReturn .= "'" .  bx_js_string($sKey) . "': '" . bx_js_string($sUrl) . "',";
+
+            $sReturn = substr($sReturn, 0, -1);
+        }
+
+        return '<script type="text/javascript" language="javascript">var aDolImages = {' . $sReturn . '};</script>';
+    }
+
+    /**
+     * Get Gzip loader URL.
+     *
+     * @param $sType content type CSS/JS
+     * @param $sName file name.
+     * @return string with URL
+     */
+    function _getLoaderUrl($sType, $sName)
+    {
+        return BX_DOL_URL_ROOT . 'gzip_loader.php?file=' . $sName . '.' . $sType;
+    }
+
+    /**
+     *
+     * Functions to display pages with errors, messages and so on.
+     *
+     */
+    function displayAccessDenied ($sMsg = '')
+    {
+        header("HTTP/1.0 403 Forbidden");
+        $this->displayMsg($sMsg ? $sMsg : _t('_Access denied'));
+    }
+    function displayNoData ($sMsg = '')
+    {
+        header("HTTP/1.0 204 No Content");
+        $this->displayMsg($sMsg ? $sMsg : _t('_Empty'));
+    }
+    function displayErrorOccured ($sMsg = '')
+    {
+        header("HTTP/1.0 500 Internal Server Error");
+        $this->displayMsg($sMsg ? $sMsg : _t('_error occured'));
+    }
+    function displayPageNotFound ($sMsg = '')
+    {
+        header("HTTP/1.0 404 Not Found");
+        $this->displayMsg($sMsg ? $sMsg : _t('_sys_request_page_not_found_cpt'));
+    }
+    function displayMsg ($s, $bTranslate = false)
+    {
+        $sTitle = $bTranslate ? _t($s) : $s;
+        $oTemplate = BxDolTemplate::getInstance();
+        $oTemplate->setPageNameIndex (BX_PAGE_DEFAULT);
+        $oTemplate->setPageHeader ($sTitle);
+        $oTemplate->setPageContent ('page_main_code', DesignBoxContent($sTitle, MsgBox($sTitle), BX_DB_PADDING_DEF));
+        $oTemplate->getPageCode();
+        exit;
+    }
+
+    /**
+     * * * * Static methods for work with template injections * * *
+     *
+     * Static method is used to add/replace the content of some key in the template.
+     * It's usefull when you don't want to modify existing template but need to add some data to existing template key.
+     *
+     * @param  integer $iPageIndex - page index where injections would processed. Use 0 if you want it to be done on all the pages.
+     * @param  string  $sKey       - template key.
+     * @param  string  $sValue     - the data to be added.
+     * @return string  the result of operation.
+     */
+    function processInjection($iPageIndex, $sKey, $sValue = "")
+    {
+        if($iPageIndex != 0 && isset($this->aPage['injections']['page_0'][$sKey]) && isset($this->aPage['injections']['page_' . $iPageIndex][$sKey]))
+           $aSelection = @array_merge($this->aPage['injections']['page_0'][$sKey], $this->aPage['injections']['page_' . $iPageIndex][$sKey]);
+        else if(isset($this->aPage['injections']['page_0'][$sKey]))
+           $aSelection = $this->aPage['injections']['page_0'][$sKey];
+        else if(isset($this->aPage['injections']['page_' . $iPageIndex][$sKey]))
+            $aSelection = $this->aPage['injections']['page_' . $iPageIndex][$sKey];
+        else
+            $aSelection = array();
+
+        if(is_array($aSelection))
+            foreach($aSelection as $aInjection) {
+
+                if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginInjection($sRand = time().rand());
+
+                $sInjData = '';
+                switch($aInjection['type']) {
+                    case 'text':
+                        $sInjData = $aInjection['data'];
+                        break;
+
+                    case 'service':
+                    	if(BxDolService::isSerializedService($aInjection['data']))
+                    		$sInjData = BxDolService::callSerialized($aInjection['data']);                    	
+                        break;
+                }
+
+                if((int)$aInjection['replace'] == 1)
+                    $sValue = $sInjData;
+                else
+                    $sValue .= $sInjData;
+
+                if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endInjection($sRand, $aInjection);
+
+            }
+
+        return $sValue != '__' . $sKey . '__' ? str_replace('__' . $sKey . '__', '', $sValue) : $sValue;
+    }
+    /**
+     * Static method to add ingection available on the current page only.
+     *
+     * @param string  $sKey     - template's key.
+     * @param string  $sType    - injection type(text, php).
+     * @param string  $sData    - the data to be added.
+     * @param integer $iReplace - replace already existed data or not.
+     */
+    function addInjection($sKey, $sType, $sData, $iReplace = 0)
+    {
+        $this->aPage['injections']['page_0'][$sKey][] = array(
+            'page_index' => 0,
+            'key' => $sKey,
+            'type' => $sType,
+            'data' => $sData,
+            'replace' => $iReplace
+        );
+    }
+
+    function getPageCode($oTemplate = null)
+    {
+        if (empty($oTemplate))
+           $oTemplate = $this;
+
+        header( 'Content-type: text/html; charset=utf-8' );
+        header( 'X-Frame-Options: sameorigin' );
+        echo $oTemplate->parsePageByName('page_' . $oTemplate->getPageNameIndex() . '.html', $oTemplate->getPageContent());
+    }
+}
+
+/** @} */

+ 12 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/inc/version.inc.php

@@ -0,0 +1,12 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+define('BX_DOL_VERSION', '8.0.0-A5'); ///< Dolphin system files version, for DB version @see bx_get_ver, DB and files version must match
+
+/** @} */

+ 422 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/classes/BxDolStudioInstallerUtils.php

@@ -0,0 +1,422 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinStudio Dolphin Studio
+ * @{
+ */
+
+bx_import('BxDolInstallerUtils');
+
+define('BX_DOL_UNITY_URL_ROOT', 'http://www.boonex.com/');
+define('BX_DOL_UNITY_URL_MARKET', BX_DOL_UNITY_URL_ROOT . 'market/');
+
+class BxDolStudioInstallerUtils extends BxDolInstallerUtils implements iBxDolSingleton
+{
+    public function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if (!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolStudioInstallerUtils();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    public function serviceDownloadFile($aItem, $bUseFtp)
+    {
+    	return $this->performDownload($aItem, $bUseFtp);
+    }
+
+	public function servicePerform($sDirectory, $sOperation, $aParams)
+    {
+    	return $this->performAction($sDirectory, $sOperation, $aParams);
+    }
+
+    public function getModules($bTitleAsKey = true)
+    {
+        $aModules = array();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aInstalledPathes = $aInstalledInfo = array();
+        $this->getInstalledInfo($aInstalledPathes, $aInstalledInfo);
+
+        $sPath = BX_DIRECTORY_PATH_MODULES;
+        if(($rHandleVendor = opendir($sPath)) !== false) {
+            while(($sVendor = readdir($rHandleVendor)) !== false) {
+                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor))
+                    continue;
+
+                if(($rHandleModule = opendir($sPath . $sVendor)) !== false) {
+                    while(($sModule = readdir($rHandleModule)) !== false) {
+                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
+                            continue;
+
+                        $sConfigPath = $sPath . $sVendor . '/' . $sModule . '/install/config.php';
+                        $aModule = $this->getConfigModule($sConfigPath, $aInstalledPathes, $aInstalledInfo);
+                        if(empty($aModule))
+                            continue;
+
+                        $aModules[$bTitleAsKey ? $aModule['title'] : $aModule['name']] = $aModule;
+                    }
+                    closedir($rHandleModule);
+                }
+            }
+            closedir($rHandleVendor);
+        }
+
+        ksort($aModules);
+        return $aModules;
+    }
+
+    public function getUpdates()
+    {
+        $aUpdates = array();
+
+        bx_import('BxDolStudioTemplate');
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aInstalledPathes = $aInstalledInfo = array();
+        $this->getInstalledInfo($aInstalledPathes, $aInstalledInfo);
+
+        $sPath = BX_DIRECTORY_PATH_MODULES;
+        if(($rHandleVendor = opendir($sPath)) !== false) {
+            while(($sVendor = readdir($rHandleVendor)) !== false) {
+                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor))
+                    continue;
+
+                if(($rHandleModule = opendir($sPath . $sVendor . '/')) !== false) {
+                    while(($sModule = readdir($rHandleModule)) !== false) {
+                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
+                            continue;
+
+                        if(($rHandleUpdate = @opendir($sPath . $sVendor . '/' . $sModule . '/updates/')) !== false) {
+                            while(($sUpdate = readdir($rHandleUpdate)) !== false) {
+                                if(!is_dir($sPath . $sVendor . '/' . $sModule . '/updates/' . $sUpdate) || substr($sUpdate, 0, 1) == '.')
+                                    continue;
+
+                                $sConfigPathModule = $sPath . $sVendor . '/' . $sModule . '/install/config.php';
+                                $sConfigPathUpdate = $sPath . $sVendor . '/' . $sModule . '/updates/' . $sUpdate . '/install/config.php';
+                                $aUpdate = $this->getConfigUpdate($sConfigPathModule, $sConfigPathUpdate, $aInstalledPathes, $aInstalledInfo);
+                                if(empty($aUpdate) || !is_array($aUpdate) || version_compare($aUpdate['module_version'], $aUpdate['version_from']) != 0)
+                                    continue;
+
+                                $aUpdates[$aUpdate['title']] = $aUpdate;
+                            }
+                            closedir($rHandleUpdate);
+                        }
+                    }
+                    closedir($rHandleModule);
+                }
+            }
+            closedir($rHandleVendor);
+        }
+
+        ksort($aUpdates);
+        return $aUpdates;
+    }
+
+    public function perform($sDirectory, $sOperation, $aParams = array())
+    {
+    	if(defined('BX_DOL_CRON_EXECUTE') || self::isRealOwner() || in_array($sOperation, array('install', 'uninstall', 'enable', 'disable')))
+    		return $this->performAction($sDirectory, $sOperation, $aParams);
+
+    	bx_import('BxDolCronQuery');
+		if(BxDolCronQuery::getInstance()->addTransientJobService('sys_perform_install', array('system', 'perform', array($sDirectory, $sOperation, $aParams), 'DolStudioInstallerUtils')))
+			return array('code' => 1, 'message' => _t('_adm_mod_msg_process_operation_scheduled'));
+
+		return array(
+			'code' => 2,
+            'message' => _t('_adm_mod_err_process_operation_failed', $sOperation, $sDirectory),
+		);
+    }
+
+    public function checkUpdates($bAuthorizedAccess = false)
+    {
+		bx_import('BxDolModuleQuery');
+        $aModules = BxDolModuleQuery::getInstance()->getModules();
+
+        $aProducts = array();
+        foreach($aModules as $aModule) {
+            if(empty($aModule['name']))
+                continue;
+
+            $aProducts[] = array(
+                'name' => $aModule['name'],
+                'version' => $aModule['version'],
+            	'hash' => $aModule['hash'],
+            );
+        }
+        $sProducts = base64_encode(serialize($aProducts));
+
+        if($bAuthorizedAccess) {
+	        bx_import('BxDolStudioOAuth');
+	        return BxDolStudioOAuth::getInstance()->loadItems(array('dol_type' => 'available_updates', 'dol_products' => $sProducts));
+        }
+
+		bx_import('BxDolStudioJson');
+		return BxDolStudioJson::getInstance()->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_updates', array(
+			'products' => $sProducts,
+			'domain' => BX_DOL_URL_ROOT,
+			'user' => (int)getParam('sys_oauth_user') 
+		));
+    }
+
+    public function downloadFileAuthorized($iFileId)
+    {
+    	bx_import('BxDolStudioOAuth');
+		$aItem = BxDolStudioOAuth::getInstance()->loadItems(array('dol_type' => 'product_file', 'dol_file_id' => $iFileId));
+
+		return $this->downloadFile($aItem);
+    }
+
+    public function downloadUpdatePublic($sModuleName)
+    {
+    	bx_import('BxDolModuleQuery');
+		$aModule = BxDolModuleQuery::getInstance()->getModuleByName($sModuleName);
+
+		bx_import('BxDolStudioJson');
+		$aItem = BxDolStudioJson::getInstance()->load(BX_DOL_UNITY_URL_MARKET . 'json_download_update', array(
+			'product' => base64_encode(serialize(array(
+				'name' => $aModule['name'],
+	            'version' => $aModule['version'],
+	            'hash' => $aModule['hash'],
+			))),
+			'domain' => BX_DOL_URL_ROOT,
+			'user' => (int)getParam('sys_oauth_user') 
+		));
+
+		return $this->downloadFile($aItem);
+    }
+
+    protected function downloadFile($aItem, $bUseFtp = BX_FORCE_USE_FTP_FILE_TRANSFER)
+    {
+    	if(defined('BX_DOL_CRON_EXECUTE') || self::isRealOwner())
+    		return $this->performDownload($aItem, $bUseFtp);
+
+		bx_import('BxDolCronQuery');
+		if(BxDolCronQuery::getInstance()->addTransientJobService('sys_perform_download', array('system', 'download_file', array($aItem, $bUseFtp), 'DolStudioInstallerUtils')))
+			return _t('_adm_str_msg_download_scheduled');
+
+		return false;
+    }
+
+    protected function performDownload($aItem, $bUseFtp)
+    {
+        if(empty($aItem) || !is_array($aItem))
+            return $aItem;
+
+        //--- write ZIP archive.
+        $sFilePath = '';
+        $mixedResult = $this->performWrite($aItem, $sFilePath);
+
+        if($mixedResult !== true)
+        	return $mixedResult;
+
+        //--- Unarchive package.
+        $sPackagePath = '';
+        $mixedResult = $this->performUnarchive($sFilePath, $sPackagePath);
+
+        @unlink($sFilePath);
+        if($mixedResult !== true)
+        	return $mixedResult;
+
+        //--- Copy unarchived package.
+        $mixedResult = $this->performCopy($sPackagePath, $bUseFtp);
+
+        @bx_rrmdir($sPackagePath);
+        if($mixedResult !== true)
+        	return $mixedResult;
+
+        return true;
+    }
+
+    protected function performWrite($aItem, &$sFilePath)
+    {
+    	$sFilePath = BX_DIRECTORY_PATH_TMP . $aItem['name'];
+
+    	if(file_exists($sFilePath))
+        	@unlink($sFilePath);
+
+        if(!$rHandler = fopen($sFilePath, 'w'))
+            return _t('_adm_str_err_cannot_write');
+
+        $sContent = urldecode($aItem['content']);
+        if(!fwrite($rHandler, $sContent, strlen($sContent)))
+            return _t('_adm_str_err_cannot_write');
+
+        fclose($rHandler);
+        
+        return true;
+    }
+
+    protected function performUnarchive($sFilePath, &$sPackagePath)
+    {
+    	if(!class_exists('ZipArchive'))
+            return _t('_adm_str_err_zip_not_available');
+
+        $oZip = new ZipArchive();
+        if($oZip->open($sFilePath) !== true)
+            return _t('_adm_str_err_cannot_unzip_package');
+
+        $sPackageFolder = '';
+        if($oZip->numFiles > 0)
+        	$sPackageFolder = $oZip->getNameIndex(0);
+
+        if(empty($sPackageFolder))
+        	return _t('_adm_str_err_cannot_unzip_package');
+        
+		$sPackagePath = BX_DIRECTORY_PATH_TMP . $sPackageFolder;
+        if(file_exists($sPackagePath)) // remove existing tmp folder with the same name
+            @bx_rrmdir($sPackagePath);
+
+        if(!$oZip->extractTo(BX_DIRECTORY_PATH_TMP))
+            return _t('_adm_str_err_cannot_unzip_package');
+
+        $oZip->close();
+
+        return true;
+    }
+
+    protected function performCopy($sPackagePath, $bUseFtp)
+    {
+    	if($bUseFtp) {
+	        $sLogin = getParam('sys_ftp_login');
+	        $sPassword = getParam('sys_ftp_password');
+	        $sPath = getParam('sys_ftp_dir');
+	        if(empty($sLogin) || empty($sPassword) || empty($sPath))
+	            return _t('_adm_str_err_no_ftp_info');
+	
+	        bx_import('BxDolFtp');
+	        $oFile = new BxDolFtp($_SERVER['HTTP_HOST'], $sLogin, $sPassword, $sPath);
+	
+	        if(!$oFile->connect())
+	            return _t('_adm_str_err_cannot_connect_to_ftp');
+	
+	        if(!$oFile->isDolphin())
+	            return _t('_adm_str_err_destination_not_valid');
+        }
+        else {
+        	bx_import('BxDolFile');
+        	$oFile = BxDolFile::getInstance();
+        }
+
+        $aConfig = self::getModuleConfig($sPackagePath . 'install/config.php');
+        if(empty($aConfig) || !is_array($aConfig) || empty($aConfig['home_dir']))
+            return _t('_adm_str_err_wrong_package_format');
+
+        if(!$oFile->copy($sPackagePath, 'modules/' . $aConfig['home_dir']))
+            return _t('_adm_str_err_files_copy_failed');
+
+		return true;
+    }
+
+    protected function performAction($sDirectory, $sOperation, $aParams = array())
+    {
+        $sConfigFile = 'install/config.php';
+        $sInstallerFile = 'install/installer.php';
+        $sInstallerClass = $sOperation == 'update' ? 'Updater' : 'Installer';
+
+        $aConfig = self::getModuleConfig(BX_DIRECTORY_PATH_MODULES . $sDirectory . $sConfigFile);
+
+        $sPathInstaller = BX_DIRECTORY_PATH_MODULES . $sDirectory . $sInstallerFile;
+        if(empty($aConfig) || !file_exists($sPathInstaller))
+        	return array(
+        		'code' => 2, 
+        		'message' => _t('_adm_mod_err_process_operation_failed', $sOperation, $sDirectory)
+        	); 
+
+		require_once($sPathInstaller);
+
+		$sClassName = $aConfig['class_prefix'] . $sInstallerClass;
+		$oInstaller = new $sClassName($aConfig);
+		$aResult = $oInstaller->$sOperation($aParams);
+
+        return array(
+        	'code' => $aResult['result'] ? 0 : 2,
+        	'message' => $aResult['message']
+        );
+    }
+
+    protected function getConfigModule($sConfigPath, $aInstalledPathes = array(), $aInstalledInfo = array())
+    {
+    	$aConfig = self::getModuleConfig($sConfigPath);
+        if(empty($aConfig) || !is_array($aConfig))
+            return array();
+
+        $sModulePath = $aConfig['home_dir'];
+
+        $bInstalled = !empty($aInstalledPathes) && in_array($sModulePath, $aInstalledPathes);
+        $bEnabled = $bInstalled && !empty($aInstalledInfo) && (int)$aInstalledInfo[$sModulePath]['enabled'] == 1;
+        $sVersion = $bInstalled && !empty($aInstalledInfo) ? $aInstalledInfo[$sModulePath]['version'] : $aConfig['version'];
+
+        return array(
+        	'type' => $aConfig['type'],
+            'name' => isset($aConfig['name']) ? $aConfig['name'] : $aConfig['home_uri'],
+            'title' => bx_process_output($aConfig['title']),
+            'vendor' => $aConfig['vendor'],
+            'version' => $sVersion,
+            'uri' => $aConfig['home_uri'],
+            'dir' => $aConfig['home_dir'],
+            'note' => isset($aConfig['note']) ? bx_process_output($aConfig['note']) : '',
+            'installed' => $bInstalled,
+            'enabled' => $bInstalled && $bEnabled
+        );
+    }
+
+    protected function getConfigUpdate($sConfigPathModule, $sConfigPathUpdate, $aInstalledPathes = array(), $aInstalledInfo = array())
+    {
+        $aModule = $this->getConfigModule($sConfigPathModule, $aInstalledPathes, $aInstalledInfo);
+        if(empty($aModule) || !$aModule['installed'])
+            return array();
+
+		$aConfig = self::getModuleConfig($sConfigPathUpdate);
+        if(empty($aConfig) || !is_array($aConfig))
+            return array();
+
+        return array(
+            'title' => bx_process_output($aConfig['title']),
+            'vendor' => $aConfig['vendor'],
+            'version_from' => $aConfig['version_from'],
+            'version_to' => $aConfig['version_to'],
+            'dir' => $aConfig['home_dir'],
+        	'module_type' => $aModule['type'],
+            'module_name' => $aModule['name'],
+            'module_dir' => $aModule['dir'],
+        	'module_version' => $aModule['version']
+        );
+    }
+
+    private function getInstalledInfo(&$aInstalledPathes, &$aInstalledInfo)
+    {
+        bx_import('BxDolModuleDb');
+        $aModules = BxDolModuleDb::getInstance()->getModules();
+
+        $aInstalledInfo = array();
+        foreach($aModules as $aModule)
+            $aInstalledInfo[$aModule['path']] = $aModule;
+
+        $aInstalledPathes = array_keys($aInstalledInfo);
+    }
+}
+
+/** @} */

+ 366 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/classes/BxDolStudioStore.php

@@ -0,0 +1,366 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinStudio Dolphin Studio
+ * @{
+ */
+
+bx_import('BxTemplStudioPage');
+bx_import('BxDolStudioStoreQuery');
+bx_import('BxDolStudioInstallerUtils');
+
+define('BX_DOL_STUDIO_STR_TYPE_DEFAULT', 'downloaded');
+
+class BxDolStudioStore extends BxTemplStudioPage
+{
+    protected $sPage;
+    protected $aContent;
+
+    protected $iClient;
+
+    protected $aAlias;
+    protected $bAuthAccessUpdates;
+
+    function __construct($sPage = "")
+    {
+        parent::__construct('store');
+
+        $this->oDb = new BxDolStudioStoreQuery();
+
+        $this->aAlias = array(
+            'tag' => array(
+                'modules' => 'extensions',
+                'languages' => 'translations'
+            ),
+            'category' => array()
+        );
+        $this->bAuthAccessUpdates = false;
+
+        $this->sPage = BX_DOL_STUDIO_STR_TYPE_DEFAULT;
+        if(is_string($sPage) && !empty($sPage))
+            $this->sPage = $sPage;
+
+		bx_import('BxDolStudioOAuth');
+        $this->iClient = BxDolStudioOAuth::getAuthorizedClient();
+
+        //--- Check actions ---//
+        if(($sAction = bx_get('str_action')) !== false) {
+            $sAction = bx_process_input($sAction);
+
+            $aResult = array('code' => 1, 'message' => _t('_adm_mod_err_cannot_process_action'));
+            switch($sAction) {
+                /*
+                 * NOTE. Is needed for download popup selector. Isn't used for now.
+                 */
+                case 'get-files':
+                    $iId = (int)bx_get('str_id');
+                    $sType = bx_process_input(bx_get('str_type'));
+                    $aResult = $this->getFiles($iId, $sType);
+                    break;
+
+                case 'get-file':
+                    $iFileId = (int)bx_get('str_id');
+                    $aResult = $this->getFile($iFileId);
+                    break;
+
+                case 'get-product':
+                    $sModuleName = bx_process_input(bx_get('str_id'));
+                    $aResult = $this->getProduct($sModuleName);
+                    break;
+
+                case 'get-update':
+                    $sModuleName = bx_process_input(bx_get('str_id'));
+                    $aResult = $this->getUpdate($sModuleName);
+                    break;
+
+                case 'get-products-by-type':
+                    $this->sPage = bx_process_input(bx_get('str_value'));
+
+                    $sContent = $this->getPageCode();
+                    if(!empty($sContent))
+                        $aResult = array('code' => 0, 'content' => $sContent);
+                    else
+                        $aResult = array('code' => 1, 'message' => _t('_adm_act_err_failed_page_loading'));
+                    break;
+
+                case 'get-products-by-page':
+                    $this->sPage = bx_process_input(bx_get('str_type'));
+
+                    $sContent = $this->getPageContent();
+                    if(!empty($sContent))
+                        $aResult = array('code' => 0, 'content' => $sContent);
+                    else
+                        $aResult = array('code' => 1, 'message' => _t('_adm_act_err_failed_page_loading'));
+                    break;
+
+                case 'add-to-cart':
+                    $sVendor = bx_process_input(bx_get('str_vendor'));
+                    $iItem = (int)bx_get('str_item');
+                    $iItemCount = 1;
+
+                    if(empty($sVendor) || empty($iItem)) {
+                        $aResult = array('code' => 1, 'message' => _t('_adm_err_modules_cannot_add_to_cart'));
+                        break;
+                    }
+
+                    bx_import('BxDolStudioCart');
+                    BxDolStudioCart::getInstance()->add($sVendor, $iItem, $iItemCount);
+                    $aResult = array('code' => 0, 'message' => _t('_adm_msg_modules_success_added_to_cart'));
+                    break;
+
+                case 'delete-from-cart':
+                    $sVendor = bx_process_input(bx_get('str_vendor'));
+                    $iItem = (int)bx_get('str_item');
+
+                    if(empty($sVendor)) {
+                        $aResult = array('code' => 1, 'message' => _t('_adm_err_modules_cannot_delete_from_cart'));
+                        break;
+                    }
+
+                    bx_import('BxDolStudioCart');
+                    BxDolStudioCart::getInstance()->delete($sVendor, $iItem);
+                    $aResult = array('code' => 0, 'message' => '');
+                    break;
+
+                case 'checkout-cart':
+                    $sVendor = bx_process_input(bx_get('str_vendor'));
+                    if(empty($sVendor)) {
+                        $aResult = array('code' => 1, 'message' => _t('_adm_err_modules_cannot_checkout_empty_vendor'));
+                        break;
+                    }
+
+                    $sLocation = $this->checkoutCart($sVendor);
+                    $aResult = array('code' => 0, 'message' => '', 'redirect' => $sLocation);
+                    break;
+
+                case 'install':
+                    $sValue = bx_process_input(bx_get('str_value'));
+                    if(empty($sValue))
+                        break;
+
+                    bx_import('BxDolStudioInstallerUtils');
+                    $aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'install', array('auto_enable' => true));
+                    break;
+
+                case 'update':
+                    $sValue = bx_process_input(bx_get('str_value'));
+                    if(empty($sValue))
+                        break;
+
+                    bx_import('BxDolStudioInstallerUtils');
+                    $aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'update');
+                    break;
+
+                case 'delete':
+                    $sValue = bx_process_input(bx_get('str_value'));
+                    if(empty($sValue))
+                        break;
+
+                    bx_import('BxDolStudioInstallerUtils');
+                    $aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'delete');
+                    break;
+            }
+
+            if(!empty($aResult['message'])) {
+                bx_import('BxDolStudioTemplate');
+                $aResult['message'] = BxDolStudioTemplate::getInstance()->parseHtmlByName('mod_action_result.html', array('content' => $aResult['message']));
+
+                bx_import('BxTemplStudioFunctions');
+                $aResult['message'] = BxTemplStudioFunctions::getInstance()->transBox('', $aResult['message']);
+            }
+
+            echo json_encode($aResult);
+            exit;
+        }
+    }
+
+    protected function loadGoodies()
+    {
+        $iPerPage = 5;
+        $aProducts = array();
+        $sJsObject = $this->getPageJsObject();
+
+        bx_import('BxDolStudioJson');
+        $oJson = BxDolStudioJson::getInstance();
+
+        // Load featured
+        $aProducts[] = array(
+            'caption' => '_adm_block_cpt_last_featured',
+            'actions' => array(
+                array('name' => 'featured', 'caption' => '_adm_action_cpt_see_all_featured', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".changePage('featured', this)")
+            ),
+            'items' => $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_featured', array('start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient))
+        );
+
+
+        // Load modules
+        $aProducts[] = array(
+            'caption' => '_adm_block_cpt_last_modules',
+            'actions' => array(
+                array('name' => 'modules', 'caption' => '_adm_action_cpt_see_all_modules', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".changePage('modules', this)")
+            ),
+            'items' => $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_by_tag', array('value' => 'extensions', 'start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient))
+        );
+
+        // Load templates
+        $aProducts[] = array(
+            'caption' => '_adm_block_cpt_last_templates',
+            'actions' => array(
+                array('name' => 'templates', 'caption' => '_adm_action_cpt_see_all_templates', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".changePage('templates', this)")
+            ),
+            'items' => $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_by_tag', array('value' => 'templates', 'start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient))
+        );
+
+        // Load languages
+        $aProducts[] = array(
+            'caption' => '_adm_block_cpt_last_languages',
+            'actions' => array(
+                array('name' => 'languages', 'caption' => '_adm_action_cpt_see_all_languages', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".changePage('languages', this)")
+            ),
+            'items' => $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_by_tag', array('value' => 'translations', 'start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient))
+        );
+
+        return $aProducts;
+    }
+
+    protected function loadFeatured($iStart, $iPerPage)
+    {
+        bx_import('BxDolStudioJson');
+        return BxDolStudioJson::getInstance()->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_featured', array('start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient));
+    }
+
+    protected function loadTag($sTag, $iStart, $iPerPage)
+    {
+        bx_import('BxDolStudioJson');
+        return BxDolStudioJson::getInstance()->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_by_tag', array('value' => $this->aliasToNameTag($sTag), 'start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient));
+    }
+
+    protected function loadPurchases()
+    {
+        bx_import('BxDolStudioOAuth');
+        $aProducts = BxDolStudioOAuth::getInstance()->loadItems(array('dol_type' => 'purchased_products', 'dol_domain' => BX_DOL_URL_ROOT));
+
+        $this->oDb->updateModule(array('hash' => ''));
+        if(!empty($aProducts) && is_array($aProducts))
+	        foreach ($aProducts as $aProduct)
+	        	$this->oDb->updateModule(array('hash' => $aProduct['hash']), array('name' => $aProduct['name']));
+
+        return $aProducts;
+    }
+
+    protected function loadUpdates()
+    {
+    	bx_import('BxDolStudioInstallerUtils');
+        return BxDolStudioInstallerUtils::getInstance()->checkUpdates($this->bAuthAccessUpdates);
+    }
+
+    protected function loadCheckout()
+    {
+        bx_import('BxDolStudioJson');
+        $oJson = BxDolStudioJson::getInstance();
+
+        bx_import('BxDolStudioCart');
+        $aVendors = BxDolStudioCart::getInstance()->parseByVendor();
+
+        $aResult = array();
+        foreach($aVendors as $sVendor => $aItems) {
+            $aIds = $aCounts = array();
+            foreach($aItems as $aItem) {
+                $aIds[] = $aItem['item_id'];
+                $aCounts[$aItem['item_id']] = $aItem['item_count'];
+            }
+
+            $aProducts = $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_selected', array('products' => base64_encode(serialize($aIds))));
+            if(!empty($aProducts))
+                $aResult[$sVendor] = array(
+                    'ids' => $aIds,
+                    'counts' => $aCounts,
+                    'products' => $aProducts
+                );
+        }
+
+        return $aResult;
+    }
+
+    protected function loadDownloaded()
+    {
+        bx_import('BxDolStudioInstallerUtils');
+        $oInstallerUtils = BxDolStudioInstallerUtils::getInstance();
+
+        return array(
+            'modules' => $oInstallerUtils->getModules(),
+            'updates' => $oInstallerUtils->getUpdates()
+        );
+    }
+
+    protected function loadProduct($sModuleName)
+    {
+        bx_import('BxDolStudioJson');
+        $oJson = BxDolStudioJson::getInstance();
+
+        return $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_get_product_by_name', array('value' => $sModuleName, 'client' => $this->iClient));
+    }
+
+    /*
+     * NOTE. Is needed for download popup selector. Isn't used for now.
+     */
+    protected function loadFiles($iId, $sType)
+    {
+        bx_import('BxDolStudioOAuth');
+        return BxDolStudioOAuth::getInstance()->loadItems(array('dol_type' => 'product_files', 'dol_product_id' => $iId, 'dol_file_type' => $sType));
+    }
+
+    /*
+     * Load package (module, update) using OAuth authorization.
+     */
+    protected function loadFile($iFileId)
+    {
+        bx_import('BxDolStudioInstallerUtils');
+        return BxDolStudioInstallerUtils::getInstance()->downloadFileAuthorized($iFileId);
+    }
+
+    /*
+     * Load update's package publicly.
+     */
+	protected function loadUpdate($sModuleName)
+    {
+        bx_import('BxDolStudioInstallerUtils');
+        return BxDolStudioInstallerUtils::getInstance()->downloadUpdatePublic($sModuleName);
+    }
+
+    private function checkoutCart($sVendor)
+    {
+        bx_import('BxDolStudioCart');
+        $oCart = BxDolStudioCart::getInstance();
+
+        $aItems = $oCart->getByVendor($sVendor);
+        if(empty($aItems) || !is_array($aItems))
+            return false;
+
+        $aIds = array();
+        foreach($aItems as $aItem)
+            $aIds[] = $aItem['item_id'];
+
+        $sSid = bx_site_hash();
+        return BX_DOL_UNITY_URL_MARKET . 'purchase/' . $sVendor . '?sid=' . $sSid . '&products=' . base64_encode(implode(',', $aIds));
+    }
+
+    private function aliasToNameTag($sAlias)
+    {
+        return $this->aliasToName('tag', $sAlias);
+    }
+
+    private function aliasToNameCategory($sAlias)
+    {
+        return $this->aliasToName('category', $sAlias);
+    }
+
+    private function aliasToName($sType, $sAlias)
+    {
+        return isset($this->aAlias[$sType][$sAlias]) ? $this->aAlias[$sType][$sAlias] : $sAlias;
+    }
+}
+
+/** @} */

+ 385 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/js/store.js

@@ -0,0 +1,385 @@
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinStudio Dolphin Studio
+ * @{
+ */
+function BxDolStudioStore(oOptions) {
+	this.sActionsUrl = oOptions.sActionUrl;
+    this.sObjName = oOptions.sObjName == undefined ? 'oBxDolStudioStore' : oOptions.sObjName;
+    this.sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
+    this.iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+
+    this.sIdPageContent = 'bx-std-pc-content';
+    this.sIdPopupProduct = 'bx-std-str-popup-product';
+    this.sIdPopupFiles = 'bx-std-str-popup-files';
+    this.sIdPopupFile = 'bx-std-str-popup-file';
+}
+
+BxDolStudioStore.prototype.addToCart = function(sVendor, iProduct, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'add-to-cart',
+			str_vendor: sVendor,
+			str_item: iProduct,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+
+			$this.showPopup($this.sIdPopupFile, oData.message, oButton);
+
+			if(parseInt(oData.code) == 0) {
+				var oCounter = $('#bx-std-pmi-checkout .bx-std-pmen-item-counter span');
+				oCounter.html(parseInt(oCounter.html()) + 1);
+
+				if(parseInt(oCounter.html()) > 0)
+					oCounter.parent('.bx-std-pmen-item-counter').show();
+			}
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.deleteFromCart = function(sVendor, iProduct, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'delete-from-cart',
+			str_vendor: sVendor,
+			str_item: iProduct,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+
+			if(oData.message.length > 0)
+				$this.showPopup($this.sIdPopupFile, oData.message, iProduct != 0 ? oButton : null);
+
+			var iCode = parseInt(oData.code);
+			var oCounter = $('#bx-std-pmi-checkout .bx-std-pmen-item-counter span');
+			if(iCode == 0 && iProduct != 0) {
+				var oProduct = $('#bx-std-product-' + iProduct);
+				if(oProduct.siblings('.bx-std-product').length == 0)
+					oProduct.parents('.bx-std-block:first').bx_anim('hide', 'fade', 'fast', function() {
+						$(this).remove();
+					});
+				else
+					oProduct.bx_anim('hide', 'fade', 'fast', function() {
+						$(this).remove();
+					});
+
+				oCounter.html(parseInt(oCounter.html()) - 1);
+			}
+			else if(iCode == 0 && iProduct == 0) {
+				var oBlock = $(oButton).parents('.bx-std-block:first');
+				oCounter.html(parseInt(oCounter.html()) - oBlock.find('.bx-std-product').length);
+
+				oBlock.bx_anim('hide', 'fade', 'fast', function() {
+					$(this).remove();
+				});
+			}
+
+			if(parseInt(oCounter.html()) <= 0)
+				oCounter.parent('.bx-std-pmen-item-counter').hide();
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.deleteAllFromCart = function(sVendor, oButton) {
+	this.deleteFromCart(sVendor, 0, oButton);
+};
+
+BxDolStudioStore.prototype.checkoutCart = function(sVendor, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'checkout-cart',
+			str_vendor: sVendor,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+
+			if(oData.message.length > 0)
+				$this.showPopup($this.sIdPopupFile, oData.message, oButton);
+
+			if(parseInt(oData.code) == 0 && oData.redirect.length > 0)
+				document.location=oData.redirect;
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.getFile = function(iFileId, oButton) {
+	this._getFile('get-file', iFileId, oButton);
+};
+
+BxDolStudioStore.prototype.getUpdate = function(sModuleName, oButton) {
+	this._getFile('get-update', sModuleName, oButton);
+};
+
+BxDolStudioStore.prototype._getFile = function(sAction, mixedId, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: sAction,
+			str_id: mixedId,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+			$this.showPopup($this.sIdPopupFile, oData.message, oButton);
+		},
+		'json'
+	);
+};
+
+/*
+ * NOTE. Is needed for download popup selector. Isn't used for now.
+ */
+BxDolStudioStore.prototype.getFiles = function(iId, sType, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'get-files',
+			str_id: iId,
+			str_type: sType,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+			$this.showPopup($this.sIdPopupFiles, oData.message, oButton);
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.info = function(sModuleName, oLink) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'get-product',
+			str_id: sModuleName,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+
+			var sId = $this.sIdPopupProduct;
+			if(oData.code == 0 && oData.popup.length > 0) {
+		        $('#' + sId).remove();
+				$(oData.popup).appendTo('body');
+				$('#' + sId).dolPopup({
+					onShow: function() {
+						$this.initScreenshots(oData.screenshots);
+					}
+				});
+			}
+			else
+				$this.showPopup(sId, oData.message, oLink);
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.initScreenshots = function(iCount) {
+	var iWidth = 202;
+	var iPadding = 20;
+	var iWidthOuter = iWidth + iPadding;
+	var bBusy = false;
+
+	$(".bx-std-pv-screenshots a[rel=group]").fancybox({
+		transitionIn: 'elastic',
+		transitionOut: 'elastic',
+		speedIn: 600,
+		speedOut: 200
+	});
+	if(iCount <= 4)
+		return;
+
+	$(".bx-std-pvs-left").bind('click', function() {
+		if(bBusy || parseInt($(".bx-std-pvs-cnt").css('left')) >= 0)
+			return;
+
+		bBusy = true;
+		$(".bx-std-pvs-cnt").animate({left: '+=' + iWidthOuter}, 500, function() {
+			bBusy = false;
+		});
+	});
+	$(".bx-std-pvs-right").bind('click', function() {
+		if(bBusy || parseInt($(".bx-std-pvs-cnt").css('left')) <= 460 - $(".bx-std-pvs-cnt").width())
+			return;
+
+		bBusy = true;
+		$(".bx-std-pvs-cnt").animate({left: '-=' + iWidthOuter}, 500, function() {
+			bBusy = false;
+		});
+	});
+
+	$(".bx-std-pv-screenshots").hover(function() {
+		$(".bx-std-pvs-left, .bx-std-pvs-right").bx_anim('show', 'fade', 'fast');
+	}, function() {
+		$(".bx-std-pvs-left, .bx-std-pvs-right").bx_anim('hide', 'fade', 'fast');;
+	});
+};
+
+BxDolStudioStore.prototype.install = function(sValue, oInput) {
+	var $this = this;
+	var onSuccess = function() {
+		$(oInput).parent('.bx-std-pc-buttons').hide(0, function() {
+			$(this).siblings(':hidden').show(0);
+		});
+	};
+
+	return this.perform('install', sValue, onSuccess);
+};
+
+BxDolStudioStore.prototype.update = function(sValue, oInput) {
+	var $this = this;
+	var onSuccess = function() {
+		$(oInput).parents('.bx-std-product:first').hide();
+	};
+
+	return this.perform('update', sValue, onSuccess);
+};
+
+BxDolStudioStore.prototype.remove = function(sValue, oInput) {
+	var onSuccess = function() {
+		$(oInput).parents('.bx-std-product:first').hide();
+	};
+
+    return this.perform('delete', sValue, onSuccess);
+};
+
+BxDolStudioStore.prototype.perform = function(sType, sValue, onSuccess) {
+	var oDate = new Date();
+	var $this = this;
+
+	if(!sValue)
+        return false;
+
+	bx_loading(this.sIdPageContent, true);
+
+    $.post(
+    	this.sActionsUrl,
+    	{
+    		str_action: sType,
+    		str_value: sValue,
+    		_t:oDate.getTime()
+    	},
+    	function (oData) {
+    		bx_loading($this.sIdPageContent, false);
+
+    		if(oData.message.length > 0)
+    			$this.showPopup('bx-std-str-popup-' + sType, oData.message);
+
+    		if(oData.code == 0 && typeof onSuccess == 'function')
+    			onSuccess();
+    	},
+    	'json'
+    );
+};
+
+/**
+ * Is needed if AJAX is used to change (reload) pages. 
+ */
+BxDolStudioStore.prototype.changePage = function(sType) {
+	var oDate = new Date();
+	var $this = this;
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'get-products-by-type',
+			str_value: sType,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			if(oData.code != 0) {
+				$this.showPopup('bx-std-str-popup-browsing', oData.message);
+				return;
+			}
+
+			$('#bx-std-pc-menu > .bx-std-pmi-active').removeClass('bx-std-pmi-active');
+			$('#bx-std-pmi-' + sType).addClass('bx-std-pmi-active');
+
+			$('#' + $this.sIdPageContent).bx_anim('hide', $this.sAnimationEffect, $this.iAnimationSpeed, function() {
+				$(this).html(oData.content).bx_anim('show', $this.sAnimationEffect, $this.iAnimationSpeed);
+			});
+		},
+		'json'
+	);
+
+	return true;
+};
+
+BxDolStudioStore.prototype.changePagePaginate = function(oButton, sType, iStart, iPerPage) {
+	var oDate = new Date();
+	var $this = this;
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'get-products-by-page',
+			str_type: sType,
+			str_start: iStart,
+			str_per_page: iPerPage,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			if(oData.code != 0) {
+				$this.showPopup('bx-std-str-popup-browsing', oData.message);
+				return;
+			}
+
+			$(oButton).parents('.bx-std-block-content:first').bx_anim('hide', $this.sAnimationEffect, $this.iAnimationSpeed, function() {
+				$(this).html(oData.content).bx_anim('show', $this.sAnimationEffect, $this.iAnimationSpeed);
+			});
+		},
+		'json'
+	);
+
+	return true;
+};
+
+BxDolStudioStore.prototype.showPopup = function(sId, sContent, mixedPointer) {
+    $('#' + sId).remove();
+    $('<div id="' + sId + '" style="display: none;"></div>').appendTo('body').html(sContent);
+
+    var oParams = {};
+    if(mixedPointer)
+    	oParams.pointer = {
+    		el:$(mixedPointer)
+    	};
+
+    $('#' + sId).dolPopup(oParams);
+};
+/** @} */

+ 702 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/template/scripts/BxBaseStudioStore.php

@@ -0,0 +1,702 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinView Dolphin Studio Representation classes
+ * @ingroup     DolphinStudio
+ * @{
+ */
+
+bx_import('BxDolStudioStore');
+bx_import('BxTemplStudioFunctions');
+
+class BxBaseStudioStore extends BxDolStudioStore
+{
+    protected $iPerPageDefault = 24;
+
+    function __construct($sPage = "")
+    {
+        parent::__construct($sPage);
+    }
+
+    function getPageCss()
+    {
+        return array_merge(parent::getPageCss(), array('store.css', 'store-media-tablet.css', 'store-media-desktop.css'));
+    }
+
+    function getPageJs()
+    {
+        return array_merge(parent::getPageJs(), array('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'), 
+	        'featured' => array('icon' => 'thumbs-up'), 
+	        'purchases' => array('icon' => 'shopping-cart'), 
+	        'updates' => array('icon' => 'refresh'), 
+	        'checkout' => array('icon' => 'credit-card'), 
+	        'downloaded' => array('icon' => 'list')
+        );
+        foreach($aMenuItems as $sMenuItem => $aItem)
+            $aMenu[] = array(
+                'name' => $sMenuItem,
+                'icon' => $aItem['icon'],
+                'link' => BX_DOL_URL_STUDIO . 'store.php?page=' . $sMenuItem,
+                'title' => _t('_adm_lmi_cpt_' . $sMenuItem),
+                'selected' => $sMenuItem == $this->sPage
+            );
+
+        bx_import('BxDolStudioCart');
+        $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($bHidden = false)
+    {
+        $sMethod = 'get' . ucfirst($this->sPage) . 'List';
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        return $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();
+
+        $aProducts = $this->loadGoodies();
+
+        $aTmplVars = array(
+            'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array()
+        );
+
+        $sActions = "";
+        foreach($aProducts as $aBlock) {
+            $sItems = $oTemplate->parseHtmlByName('str_products.html', array(
+                'list' => $this->displayProducts($aBlock['items']),
+                'paginate' => ''
+            ));
+
+            $aTmplVars['bx_repeat:blocks'][] = array(
+                'caption' => $this->getBlockCaption($aBlock),
+                'panel_top' => '',
+                'items' => $sItems,
+                'panel_bottom' => ''
+            );
+        }
+
+        return $oTemplate->parseHtmlByName('store.html', $aTmplVars);
+    }
+
+    protected function getFeaturedList($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;
+
+        $aProducts = $this->loadFeatured($iStart, $iPerPage + 1);
+
+        bx_import('BxTemplPaginate');
+        $oPaginate = new BxTemplPaginate(array(
+            'start' => $iStart,
+            'per_page' => $iPerPage,
+            'on_change_page' => $sJsObject . ".changePagePaginate(this, 'featured', {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,
+            'bx_repeat:blocks' => array(
+                array(
+                    'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_featured'), 'actions' => array())),
+                    'panel_top' => '',
+                    'items' => $sContent,
+                    'panel_bottom' => ''
+                )
+            )
+        ));
+    }
+
+    protected function getModulesList($bWrapInBlock = true)
+    {
+        return $this->getTag('modules', $bWrapInBlock);
+    }
+
+    protected function getTemplatesList($bWrapInBlock = true)
+    {
+        return $this->getTag('templates', $bWrapInBlock);
+    }
+
+    protected function getLanguagesList($bWrapInBlock = true)
+    {
+        return $this->getTag('languages', $bWrapInBlock);
+    }
+
+    protected function getTag($sTag, $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;
+
+        $aProducts = $this->loadTag($sTag, $iStart, $iPerPage + 1);
+
+        bx_import('BxTemplPaginate');
+        $oPaginate = new BxTemplPaginate(array(
+            'start' => $iStart,
+            'per_page' => $iPerPage,
+            'on_change_page' => $sJsObject . ".changePagePaginate(this, '" . $sTag . "', {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,
+            'bx_repeat:blocks' => array(
+                array(
+                    'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_' . $sTag), 'actions' => array())),
+                    'panel_top' => '',
+                    'items' => $sContent,
+                    'panel_bottom' => ''
+                )
+            )
+        ));
+    }
+
+    protected function getPurchasesList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aProducts = $this->loadPurchases();
+
+        $aTmplVars = array(
+            'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                    'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_purchases'), 'actions' => array())),
+                    'panel_top' => '',
+                    'items' => $this->displayProducts($aProducts),
+                    'panel_bottom' => ''
+                )
+            )
+        );
+
+        return $oTemplate->parseHtmlByName('store.html', $aTmplVars);
+    }
+
+    protected function getUpdatesList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aUpdates = $this->loadUpdates();
+
+        $aTmplVars = array(
+            'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                    'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_updates'), 'actions' => array())),
+                    'panel_top' => '',
+                    'items' => $this->displayUpdates($aUpdates),
+                    'panel_bottom' => '',
+                )
+            )
+        );
+
+        return $oTemplate->parseHtmlByName('store.html', $aTmplVars);
+    }
+
+    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) {
+            bx_import('BxDolStudioCart');
+            $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();
+
+        $aTmplVarsBlocks = array();
+        foreach($aVendors as $sName => $aInfo) {
+            $fTotal = 0;
+            $sCurrency = '';
+            foreach($aInfo['products'] as $aProduct) {
+                $iCount = isset($aInfo['counts'][$aProduct['id']]) ? (int)$aInfo['counts'][$aProduct['id']] : 1;
+                $fTotal += $iCount * $aProduct['price'];
+
+                if($sCurrency == '' && isset($aProduct['currency_sign']))
+                    $sCurrency = $aProduct['currency_sign'];
+            }
+
+            $aActions = array(
+                array('name' => 'checkout-' . $sName, 'caption' => '_adm_action_cpt_checkout', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".checkoutCart('" . $sName . "', this);"),
+                array('name' => 'delete-all-' . $sName, 'caption' => '_adm_action_cpt_delete_all', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".deleteAllFromCart('" . $sName . "', this)")
+            );
+
+            $aTmplVarsBlocks[] = array(
+                'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_checkout_by_vendor_csign', $sName, $sCurrency, $fTotal), 'actions' => $aActions)),
+                'panel_top' => '',
+                'items' => $this->displayProducts($aInfo['products'], array('is_shopping_cart' => true, 'counts' => $aInfo['counts'])),
+                'panel_bottom' => ''
+            );
+        }
+
+        if(empty($aTmplVarsBlocks))
+            return $this->getMessage('_adm_block_cpt_checkout', '_Empty');
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'bx_repeat:blocks' => $aTmplVarsBlocks
+        ));
+    }
+
+    protected function getDownloadedList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sModules = $sUpdates = "";
+        $aProducts = $this->loadDownloaded();
+
+        //--- Prepare modules.
+        foreach($aProducts['modules'] as $aModule) {
+        	$sIcon = BxDolStudioUtils::getModuleIcon($aModule, 'store');
+        	$bIcon = strpos($sIcon, '.') === false;
+
+            $sModules .= $oTemplate->parseHtmlByName('str_product_v1.html', array(
+                'js_object' => $sJsObject,
+             	'name' => $aModule['name'],
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array('icon' => $sIcon),
+	            ),
+                'bx_if:image' => 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' => $aModule['installed'],
+                    'content' => array()
+                ),
+                'bx_if:hide_installed' => array(
+                    'condition' => !$aModule['installed'],
+                    '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 = strpos($sIcon, '.') === false;
+
+            $sUpdates .= $oTemplate->parseHtmlByName('str_update_v1.html', array(
+                'js_object' => $sJsObject,
+            	'name' => $aUpdate['module_name'],
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array('icon' => $sIcon),
+	            ),
+                'bx_if:image' => 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']
+            ));
+        }
+
+        $aTmplVars = array(
+            'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                    'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_downloaded_modules'), 'actions' => array())),
+                    'panel_top' => '',
+                    'items' => $sModules,
+                    'panel_bottom' => ''
+                )
+            )
+        );
+
+        if(!empty($sUpdates))
+            $aTmplVars['bx_repeat:blocks'][] = array(
+                'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_downloaded_updates'), 'actions' => array())),
+                'panel_top' => '',
+                'items' => $sUpdates,
+                'panel_bottom' => ''
+            );
+
+        return $oTemplate->parseHtmlByName('store.html', $aTmplVars);
+    }
+
+    protected function getMessage($sCaption, $sContent, $aActions = array())
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                    'caption' => $this->getBlockCaption(array('caption' => _t($sCaption), 'actions' => $aActions)),
+                	'panel_top' => '',
+                    'items' => MsgBox(_t($sContent)),
+                	'panel_bottom' => ''
+                )
+            )
+        ));
+    }
+
+    protected function getProduct($sModuleName)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aProduct = $this->loadProduct($sModuleName);
+        if(empty($aProduct) || !is_array($aProduct))
+            return array('code' => 1, '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;
+
+        $bDownloadable = (int)$aProduct['is_file'] == 1;
+        $bDownloaded = array_key_exists($sModuleName, $aDownloaded);
+        $bDownload = ($bFree || $bPurchased) && $bDownloadable;
+
+        $bDiscount = !empty($aProduct['discount']);
+        $sVersion = $bDownloaded && version_compare($aDownloaded[$sModuleName]['version'], $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']
+                );
+        }
+
+        $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,
+            'price' => !$bFree ? _t('_adm_str_txt_price_csign', $aProduct['author_currency_sign'], $aProduct['price']) : _t('_adm_str_txt_price_free'),
+            'bx_if:show_discount' => array(
+                'condition' => !$bFree && $bDiscount,
+                'content' => array(
+                    'discount' => $bDiscount ? _t('_adm_str_txt_pv_discount_off_csign', $aProduct['discount']['percent'], $aProduct['author_currency_sign'], $aProduct['discount']['save']) : ''
+                )
+            ),
+            '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'],
+            'likes' => _t('_adm_str_txt_pv_stats_likes', $aProduct['likes_cnt']),
+            'views' => _t('_adm_str_txt_pv_stats_views', $aProduct['views_cnt']),
+            'created' => $aProduct['created'],
+            'updated' => $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' => !$bFree && !$bPurchased,
+                'content' => array(
+                    'js_object' => $sJsObject,
+                    'id' => $aProduct['id'],
+                    'vendor' => $aProduct['author_name'],
+                )
+            ),
+            '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' => 0, 'message' => '', 'popup' => PopupBox('bx-std-str-popup-product', $aProduct['title'], $sContent, true), 'screenshots' => $iScreenshots);
+    }
+
+    /*
+     * NOTE. Is needed for download popup selector. Isn't used for now.
+     */
+    protected function getFiles($iId, $sType)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aFiles = $this->loadFiles($iId, $sType);
+        if(!is_array($aFiles))
+            return array('code' => 1, 'message' => (!empty($aFiles) ? $aFiles : _t('_adm_str_err_no_files')));
+
+        $aTmplVarsFiles = array();
+        foreach($aFiles as $aFile)
+            $aTmplVarsFiles[] = array(
+                'js_object' => $sJsObject,
+                'id' => $aFile['id'],
+                'title' => $aFile['name'],
+                'version' => empty($aFile['version_to']) ? $aFile['version'] : _t('_adm_str_txt_update_from_to', $aFile['version'], $aFile['version_to']),
+            );
+
+        $sContent = $oTemplate->parseHtmlByName('str_files.html', array(
+            'bx_repeat:files' => $aTmplVarsFiles
+        ));
+
+        return array('code' => 0, 'message' => $sContent);
+    }
+
+    protected function getFile($iFileId)
+    {
+        $mixedResult = $this->loadFile($iFileId);
+        if($mixedResult !== true)
+            return array('code' => 1, 'message' => (!empty($mixedResult) ? $mixedResult : _t('_adm_str_err_download_failed')));
+
+        return array('code' => 0, 'message' => _t('_adm_str_msg_download_successfully'));
+    }
+
+	protected function getUpdate($sModuleName)
+    {
+        $mixedResult = $this->loadUpdate($sModuleName);
+        if($mixedResult !== true)
+            return array('code' => 1, 'message' => (!empty($mixedResult) ? $mixedResult : _t('_adm_str_err_download_failed')));
+
+        return array('code' => 0, 'message' => _t('_adm_str_msg_download_successfully'));
+    }
+
+    protected function displayProducts($aItems, $aParams = array())
+    {
+        if(!is_array($aItems))
+            return $aItems;
+
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aDownloaded = $this->getDownloadedModules();
+        $bShoppingCart = isset($aParams['is_shopping_cart']) && $aParams['is_shopping_cart'];
+
+        $sResult = '';
+        foreach($aItems as $aItem) {
+            $bFree = (int)$aItem['is_free'] == 1;
+            $bPurchased = (int)$aItem['is_purchased'] == 1;
+
+            $bDownloadable = (int)$aItem['is_file'] == 1;
+            $bDownloaded = in_array($aItem['name'], $aDownloaded);
+            $bDownload = !$bShoppingCart && ($bFree || $bPurchased) && $bDownloadable;
+
+            $sPrice = !$bFree ? _t('_adm_str_txt_price_csign', $aItem['currency_sign'], $aItem['price']) : _t('_adm_str_txt_price_free');
+            $sDiscount = !$bFree && !empty($aItem['discount']) ? _t('_adm_str_txt_discount_csign', $aItem['currency_sign'], $aItem['discount']['price']) : '';
+
+            $sIcon = !empty($aItem['thumbnail']['big']) ? $aItem['thumbnail']['big'] : BxDolStudioUtils::getIconDefault(BX_DOL_MODULE_TYPE_MODULE);
+			$bIcon = strpos($sIcon, '.') === false;
+
+            $sResult .= $oTemplate->parseHtmlByName('str_product_v2.html', array(
+                'js_object' => $sJsObject,
+                'id' => $aItem['id'],
+            	'name' => $aItem['name'],
+                'url' => $aItem['url'],
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array('icon' => $sIcon),
+	            ),
+                'bx_if:image' => array (
+	                'condition' => !$bIcon,
+	                'content' => array('icon_url' => $sIcon),
+	            ),
+                'title' => $aItem['title'],
+                'bx_if:show_vendor_price' => array(
+                    'condition' => !$bShoppingCart,
+                    'content' => array(
+                        'vendor' => $aItem['author'],
+                        'price' => $sPrice,
+                        'discount' => $sDiscount,
+                    )
+                ),
+                'bx_if:show_count_price' => array(
+                    'condition' => $bShoppingCart,
+                    'content' => array(
+                        'count' => isset($aParams['counts'][$aItem['id']]) ? (int)$aParams['counts'][$aItem['id']] : 1,
+                        'price' => $sPrice,
+                        'discount' => $sDiscount,
+                    )
+                ),
+                'bx_if:show_purchase' => array(
+                    'condition' => !$bShoppingCart && !$bFree && !$bPurchased,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor' => $aItem['author']
+                    )
+                ),
+                'bx_if:show_download' => array(
+                    'condition' => $bDownload && !$bDownloaded,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'file_id' => $aItem['file_id']
+                    )
+                ),
+                'bx_if:show_download_disabled' => array(
+					'condition' => $bDownload && $bDownloaded,
+					'content' => array()
+				),
+                'bx_if:show_delete' => array(
+                    'condition' => $bShoppingCart,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor' => $aItem['author']
+                    )
+                )
+            ));
+        }
+
+        return $sResult;
+    }
+
+    protected function displayUpdates($aItems, $aParams = array())
+    {
+        if(!is_array($aItems))
+            return $aItems;
+
+		if(empty($aItems))
+			return MsgBox(_t('_Empty'));
+
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sResult = '';
+        foreach($aItems as $aItem) {
+            $bDownloadable = (int)$aItem['is_file'] == 1;
+
+            $sIcon = !empty($aItem['thumbnail']['big']) ? $aItem['thumbnail']['big'] : BxDolStudioUtils::getIconDefault(BX_DOL_MODULE_TYPE_MODULE);
+			$bIcon = strpos($sIcon, '.') === false;
+
+            $sResult .= $oTemplate->parseHtmlByName('str_update_v2.html', array(
+                'js_object' => $sJsObject,
+                'id' => $aItem['id'],
+            	'name' => $aItem['name'],
+                'url' => $aItem['url'],
+            	'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array('icon' => $sIcon),
+	            ),
+                'bx_if:image' => array (
+	                'condition' => !$bIcon,
+	                'content' => array('icon_url' => $sIcon),
+	            ),
+                'title' => $aItem['title'],
+                'vendor' => $aItem['author'],
+                'versions' => _t('_adm_str_txt_update_from_to', $aItem['file_version'], $aItem['file_version_to']),
+                'bx_if:show_download' => array(
+                    'condition' => $bDownloadable,
+                    'content' => array(
+	            		'on_click' => $sJsObject . "." . ($this->bAuthAccessUpdates ? "getFile(" . $aItem['file_id'] . ", this)" : "getUpdate('" . $aItem['name'] . "', this)")
+                    )
+                )
+            ));
+        }
+
+        return $sResult;
+    }
+
+    private function getDownloadedModules($bNamesOnly = true)
+    {
+		bx_import('BxDolStudioInstallerUtils');
+		$aModules = BxDolStudioInstallerUtils::getInstance()->getModules(false);
+
+        return $bNamesOnly ? array_keys($aModules) : $aModules;
+    }
+}
+
+/** @} */

+ 26 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/template/str_product_v1.html

@@ -0,0 +1,26 @@
+<div class="bx-std-product bx-def-widget-size-max-height bx-def-margin-bottom">
+    <div class="bx-stdp-icon bx-def-widget-size bx-def-round-corners-widget">
+        <a href="javascript:void(0)" onclick="javascript:__js_object__.info('__name__', this)">
+			<bx_if:icon>
+				<img class="bx-def-widget-size" src="<bx_icon_url:wi-empty.png />" />
+				<i class="sys-icon __icon__"></i>
+			</bx_if:icon>
+			<bx_if:image><img class="bx-def-widget-size" src="__icon_url__" /></bx_if:image>
+        </a>
+    </div>
+    <div class="bx-stdp-content bx-def-widget-size-min-height bx-def-widget-size-left-indent bx-def-margin-right">
+        <div class="bx-std-pc-title bx-def-margin-sec-top bx-def-font-large">
+            <a href="javascript:void(0)" onclick="javascript:__js_object__.info('__name__', this)">__title__</a>
+        </div>
+        <div class="bx-std-pc-vendor bx-def-margin-thd-top bx-def-font-small">__vendor__ &bull; <span class="bx-std-pc-version">__version__</span></div>
+        <div class="bx-std-pc-action bx-def-margin-thd-top">
+        	<div class="bx-std-pc-buttons bx-clearfix" <bx_if:hide_install>style="display:none"</bx_if:hide_install>>
+        		<input class="bx-btn bx-btn-small bx-def-margin-sec-left-auto" type="button" value="<bx_text:_adm_btn_install_submit />" onclick="javascript:__js_object__.install('__dir__', this)" />
+        		<input class="bx-btn bx-btn-small bx-def-margin-sec-left-auto" type="button" value="<bx_text:_adm_btn_delete_submit />" onclick="javascript:__js_object__.remove('__dir__', this)" />
+        	</div>
+        	<div class="bx-std-pc-buttons bx-clearfix" <bx_if:hide_installed>style="display:none"</bx_if:hide_installed>>
+            	<input class="bx-btn bx-btn-small bx-btn-disabled" type="button" value="<bx_text:_adm_btn_installed_submit />" />
+            </div>
+        </div>
+    </div>
+</div>

+ 36 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/template/str_product_v2.html

@@ -0,0 +1,36 @@
+<div class="bx-std-product bx-def-widget-size-max-height bx-def-margin-bottom" id="bx-std-product-__id__">
+    <div class="bx-stdp-icon bx-def-widget-size bx-def-round-corners-widget">
+        <a href="javascript:void(0)" onclick="javascript:__js_object__.info('__name__', this)">
+            <bx_if:icon>
+				<img class="bx-def-widget-size" src="<bx_icon_url:wi-empty.png />" />
+				<i class="sys-icon __icon__"></i>
+			</bx_if:icon>
+			<bx_if:image><img class="bx-def-widget-size" src="__icon_url__" /></bx_if:image>
+        </a>
+    </div>
+    <div class="bx-stdp-content bx-def-widget-size-min-height bx-def-widget-size-left-indent bx-def-margin-right">
+        <div class="bx-std-pc-title bx-def-margin-sec-top bx-def-font-large">
+            <a href="javascript:void(0)" onclick="javascript:__js_object__.info('__name__', this)">__title__</a>
+        </div>
+        <bx_if:show_vendor_price>
+        	<div class="bx-std-pc-vendor bx-def-margin-thd-top bx-def-font-small">__vendor__ &bull; <span class="bx-std-pc-price">__price__ __discount__</span></div>
+        </bx_if:show_vendor_price>
+        <bx_if:show_count_price>
+        	<div class="bx-std-pc-count bx-def-margin-thd-top bx-def-font-small">__count__ <bx_text:_adm_str_txt_items_by /> <span class="bx-std-pc-price">__price__ __discount__</span></div>
+        </bx_if:show_count_price>
+        <div class="bx-std-pc-action bx-def-margin-thd-top bx-clearfix">
+        	<bx_if:show_purchase>
+            	<input class="bx-btn bx-btn-small bx-std-pc-purchase" type="button" value="<bx_text:_adm_btn_purchase_submit />" onclick="javascript:__js_object__.addToCart('__vendor__', __id__, this)" />
+            </bx_if:show_purchase>
+        	<bx_if:show_download>
+            	<input class="bx-btn bx-btn-small bx-std-pc-download" type="button" value="<bx_text:_adm_btn_download_submit />" onclick="javascript:__js_object__.getFile(__file_id__, this)" />
+            </bx_if:show_download>
+            <bx_if:show_download_disabled>
+            	<input class="bx-btn bx-btn-small bx-btn-disabled bx-std-pc-download" type="button" value="<bx_text:_adm_btn_download_submit />" disabled="disabled" />
+            </bx_if:show_download_disabled>
+            <bx_if:show_delete>
+            	<input class="bx-btn bx-btn-small bx-std-pc-delete" type="button" value="<bx_text:_adm_btn_delete_submit />" onclick="javascript:__js_object__.deleteFromCart('__vendor__', __id__, this)" />
+            </bx_if:show_delete>
+        </div>
+    </div>
+</div>

+ 92 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/template/str_product_view.html

@@ -0,0 +1,92 @@
+<div class="bx-std-product-view">
+	<bx_if:show_screenshots>
+		<div class="bx-std-pv-screenshots">
+			<div class="bx-std-pvs-left">
+				<i class="sys-icon caret-left"></i>
+			</div>
+			<div class="bx-std-pvs-cnt" style="width:__width__px">
+				<bx_repeat:screenshots>
+					<div class="bx-std-pvs-item bx-def-margin-left-auto" style="background-image:url('__image_url__');">
+						<a href="__view_url__" rel="group">
+			        		<img src="<bx_image_url:spacer.gif />" />
+			        	</a>
+			        </div>
+				</bx_repeat:screenshots>
+				<div class="clear">&#160;</div>
+			</div>
+			<div class="bx-std-pvs-right">
+				<i class="sys-icon caret-right"></i>
+			</div>
+		</div>
+	</bx_if:show_screenshots>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_title />:
+		</span> <a href="__url__" target="_blank">__title__</a>
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_vendor />:
+		</span> <a href="__author_url__">__author_name__</a>
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_version />:
+		</span> __version__
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_price />:
+		</span> __price__ 
+	</div>
+	<bx_if:show_discount>
+		<div class="bx-std-pv-line">
+			<span class="bx-std-pv-cpt bx-def-font-grayed">
+				<bx_text:_adm_str_txt_pv_discount />:
+			</span> __discount__
+		</div>
+	</bx_if:show_discount>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_category />:
+		</span> <a href="__category_url__">__category__</a>
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_tags />:
+		</span> __tags__
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_stats />:
+		</span> <a href="__reviews_url__">__reviews__</a>, __likes__, __views__ 
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_created />:
+		</span> __created__
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_updated />:
+		</span> __updated__
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_description />:
+		</span> __description__
+	</div>
+	<div class="bx-std-pv-actions bx-def-margin-top">
+		<bx_if:show_purchase>
+			<input class="bx-btn bx-def-margin-right" type="button" value="<bx_text:_adm_btn_purchase_submit />" onclick="javascript:__js_object__.addToCart('__vendor__', __id__, this)" />
+		</bx_if:show_purchase>
+		<bx_if:show_download>
+			<input class="bx-btn bx-def-margin-right" type="button" value="<bx_text:_adm_btn_download_submit />" onclick="javascript:__js_object__.getFile(__file_id__, this)" />
+		</bx_if:show_download>
+		<bx_if:show_download_disabled>
+			<input class="bx-btn bx-btn-disabled bx-def-margin-right" type="button" value="<bx_text:_adm_btn_download_submit />" disabled="disabled" />
+		</bx_if:show_download_disabled>
+		<input class="bx-btn bx-def-margin-right" type="button" value="<bx_text:_adm_btn_cancel_submit />" onclick="javascript:$('.bx-popup-applied:visible').dolPopupHide();" />
+		<div class="bx-std-cb"></div>
+	</div>
+</div>

+ 20 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/template/str_update_v1.html

@@ -0,0 +1,20 @@
+<div class="bx-std-product bx-stdp-update bx-def-widget-size-max-height bx-def-margin-bottom">
+    <div class="bx-stdp-icon bx-def-widget-size bx-def-round-corners-widget">
+        <a href="javascript:void(0)" onclick="javascript:__js_object__.info('__name__', this)">
+            <bx_if:icon>
+				<img class="bx-def-widget-size" src="<bx_icon_url:wi-empty.png />" />
+				<i class="sys-icon __icon__"></i>
+			</bx_if:icon>
+			<bx_if:image><img class="bx-def-widget-size" src="__icon_url__" /></bx_if:image>
+        </a>
+    </div>
+    <div class="bx-stdp-content bx-def-widget-size-min-height bx-def-widget-size-left-indent bx-def-margin-right">
+        <div class="bx-std-pc-title bx-def-margin-sec-top bx-def-font-large">
+            <a href="javascript:void(0)" onclick="javascript:__js_object__.info('__name__', this)">__title__</a>
+        </div>
+        <div class="bx-std-pc-vendor bx-def-margin-thd-top bx-def-font-small">__vendor__ &bull; <span class="bx-std-pc-versions">__versions__</span></div>
+        <div class="bx-std-pc-action bx-def-margin-thd-top bx-clearfix">
+        	<input class="bx-btn bx-btn-small" type="button" value="<bx_text:_adm_btn_install_submit />" onclick="javascript:__js_object__.update('__dir__', this)" />
+        </div>
+    </div>
+</div>

+ 22 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/files/studio/template/str_update_v2.html

@@ -0,0 +1,22 @@
+<div class="bx-std-product bx-stdp-update bx-def-widget-size-max-height bx-def-margin-bottom" id="bx-std-product-__id__">
+    <div class="bx-stdp-icon bx-def-widget-size bx-def-round-corners-widget">
+        <a href="javascript:void(0)" onclick="javascript:__js_object__.info('__name__', this)">
+            <bx_if:icon>
+				<img class="bx-def-widget-size" src="<bx_icon_url:wi-empty.png />" />
+				<i class="sys-icon __icon__"></i>
+			</bx_if:icon>
+			<bx_if:image><img class="bx-def-widget-size" src="__icon_url__" /></bx_if:image>
+        </a>
+    </div>
+    <div class="bx-stdp-content bx-def-widget-size-min-height bx-def-widget-size-left-indent bx-def-margin-right">
+        <div class="bx-std-pc-title bx-def-margin-sec-top bx-def-font-large">
+            <a href="javascript:void(0)" onclick="javascript:__js_object__.info('__name__', this)">__title__</a>
+        </div>
+        <div class="bx-std-pc-vendor bx-def-margin-thd-top bx-def-font-small">__vendor__ &bull; <span class="bx-std-pc-versions">__versions__</span></div>
+        <div class="bx-std-pc-action bx-def-margin-thd-top bx-clearfix">
+        	<bx_if:show_download>
+            	<input class="bx-btn bx-btn-small bx-std-pc-download" type="button" value="<bx_text:_adm_btn_download_submit />" onclick="javascript:__on_click__" />
+            </bx_if:show_download>
+        </div>
+    </div>
+</div>

+ 2 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/script.php

@@ -0,0 +1,2 @@
+<?php
+    return true;

+ 5 - 0
upgrade/files/8.0.0.A4-8.0.0.A5/sql.sql

@@ -0,0 +1,5 @@
+
+-- last step is to update current version
+
+UPDATE `sys_modules` SET `version` = '8.0.0-A5' WHERE `version` = '8.0.0-A4' AND `name` = 'system';
+