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

Ticket #3719 - Studio: Settings form should be disconnected from Settings page: Core changes.

AntonLV 4 лет назад
Родитель
Сommit
3aa8c0d286
31 измененных файлов с 2022 добавлено и 1483 удалено
  1. 0 37
      modules/boonex/markerio/classes/BxMarkerIoStudioSettings.php
  2. 0 32
      modules/boonex/stripe_connect/classes/BxStripeConnectStudioSettings.php
  3. 0 26
      modules/boonex/xero/classes/BxXeroStudioSettings.php
  4. 2 3
      studio/classes/BxDolStudioDesign.php
  5. 22 15
      studio/classes/BxDolStudioDesigner.php
  6. 435 0
      studio/classes/BxDolStudioOptions.php
  7. 452 0
      studio/classes/BxDolStudioOptionsQuery.php
  8. 3 380
      studio/classes/BxDolStudioSettings.php
  9. 31 0
      studio/inc/utils.inc.php
  10. 64 66
      studio/js/designer.js
  11. 179 0
      studio/js/options.js
  12. 1 128
      studio/js/settings.js
  13. 33 0
      studio/options.php
  14. 1 0
      studio/settings.php
  15. 27 0
      studio/template/css/options.css
  16. 0 27
      studio/template/css/settings.css
  17. 3 2
      studio/template/options.html
  18. 0 0
      studio/template/options_create_mix.html
  19. 0 0
      studio/template/options_import_mix.html
  20. 10 16
      studio/template/scripts/BxBaseStudioAudit.php
  21. 0 5
      studio/template/scripts/BxBaseStudioBadges.php
  22. 25 11
      studio/template/scripts/BxBaseStudioDesign.php
  23. 17 29
      studio/template/scripts/BxBaseStudioDesigner.php
  24. 4 2
      studio/template/scripts/BxBaseStudioLanguage.php
  25. 4 2
      studio/template/scripts/BxBaseStudioModule.php
  26. 2 2
      studio/template/scripts/BxBaseStudioModules.php
  27. 639 0
      studio/template/scripts/BxBaseStudioOptions.php
  28. 29 36
      studio/template/scripts/BxBaseStudioPage.php
  29. 14 21
      studio/template/scripts/BxBaseStudioPolyglot.php
  30. 6 643
      studio/template/scripts/BxBaseStudioSettings.php
  31. 19 0
      studio/template/scripts_templ/BxTemplStudioOptions.php

+ 0 - 37
modules/boonex/markerio/classes/BxMarkerIoStudioSettings.php

@@ -1,37 +0,0 @@
-<?php defined('BX_DOL') or die('hack attempt');
-/**
- * Copyright (c) UNA, Inc - https://una.io
- * MIT License - https://opensource.org/licenses/MIT
- *
- * @defgroup    Marker.io Marker.io
- * @ingroup     UnaModules
- *
- * @{
- */
-
-class BxMarkerIoStudioSettings extends BxTemplStudioSettings
-{
-    protected function field($aItem, $aItems2Mixes)
-    {
-        $sName = trim(str_replace($this->sType, '', $aItem['name']), '_');
-        $mixedValue = isset($aItems2Mixes[$aItem['name']]) ? $aItems2Mixes[$aItem['name']] : $aItem['value'];
-
-        $aField = [];
-        if($aItem['type'] == 'text' && $sName == 'code')
-            $aField = array(
-                'type' => 'textarea',
-                'name' => $aItem['name'],
-                'caption' => _t($aItem['caption']),
-                'value' => $mixedValue,
-                'attrs' => [],
-                'code' => true,
-                'db' => array (
-                    'pass' => 'XssHtml',
-                ),
-            );
-        else 
-            $aField = parent::field($aItem, $aItems2Mixes);
-
-        return $aField;
-    }
-}

+ 0 - 32
modules/boonex/stripe_connect/classes/BxStripeConnectStudioSettings.php

@@ -1,32 +0,0 @@
-<?php defined('BX_DOL') or die('hack attempt');
-/**
- * Copyright (c) UNA, Inc - https://una.io
- * MIT License - https://opensource.org/licenses/MIT
- *
- * @defgroup    StripeConnect Stripe Connect
- * @ingroup     TridentModules
- *
- * @{
- */
-
-class BxStripeConnectStudioSettings extends BxTemplStudioSettings
-{
-    protected $_sModule;
-	protected $_oModule;
-
-    function __construct($sType = '', $sCategory = '')
-    {
-        parent::__construct($sType, $sCategory);
-        
-        $this->_sModule = 'bx_stripe_connect';
-        $this->_oModule = BxDolModule::getInstance($this->_sModule);
-    }
-
-    public function getCustomValueRedirectUrl($aItem, $mixedValue)
-    {
-        $CNF = &$this->_oModule->_oConfig->CNF;
-
-        return BX_DOL_URL_ROOT . $this->_oModule->_oConfig->getBaseUri() . $CNF['URI_REDIRECT'];
-    }
-}
-/** @} */

+ 0 - 26
modules/boonex/xero/classes/BxXeroStudioSettings.php

@@ -1,26 +0,0 @@
-<?php defined('BX_DOL') or die('hack attempt');
-/**
- * Copyright (c) UNA, Inc - https://una.io
- * MIT License - https://opensource.org/licenses/MIT
- *
- * @defgroup    Xero Xero
- * @ingroup     UnaModules
- *
- * @{
- */
-
-class BxXeroStudioSettings extends BxTemplStudioSettings
-{
-    protected function getCustomValueRedirectUrl($aItem, $mixedValue)
-    {
-        return bx_replace_markers($mixedValue, array(
-            'site_url' => BX_DOL_URL_ROOT
-        ));
-    }
-    protected function getCustomValueWebhookUrl($aItem, $mixedValue)
-    {
-        return bx_replace_markers($mixedValue, array(
-            'site_url' => BX_DOL_URL_ROOT
-        ));
-    }
-}

+ 2 - 3
studio/classes/BxDolStudioDesign.php

@@ -15,8 +15,6 @@ define('BX_DOL_STUDIO_TEMPL_TYPE_DEFAULT', BX_DOL_STUDIO_TEMPL_TYPE_SETTINGS);
 
 class BxDolStudioDesign extends BxTemplStudioModule
 {
-    protected $MODULE;
-
     public function __construct($sModule, $mixedPageName, $sPage = "")
     {
         parent::__construct($sModule, $mixedPageName, $sPage);
@@ -82,8 +80,9 @@ class BxDolStudioDesign extends BxTemplStudioModule
     {
     	$oPage = new BxTemplStudioDesigner($this->sPage);
     	$oPage->setManageUrl($this->sManageUrl);
+        $oPage->setParamPrefix($this->sParamPrefix);
 
-    	$oModule = BxDolModule::getInstance($this->MODULE);
+    	$oModule = BxDolModule::getInstance($this->sModule);
     	$oPage->setLogoParams($oModule->_oConfig->getLogoParams());
 
     	return $oPage;

+ 22 - 15
studio/classes/BxDolStudioDesigner.php

@@ -22,6 +22,7 @@ class BxDolStudioDesigner extends BxTemplStudioWidget
     protected $sPage;
 
     protected $sManageUrl;
+    protected $sParamPrefix;
 
     protected $sParamLogo;
     protected $sParamLogoAlt;
@@ -41,6 +42,7 @@ class BxDolStudioDesigner extends BxTemplStudioWidget
             $this->sPage = $sPage;
 
         $this->sManageUrl = BX_DOL_URL_STUDIO . 'designer.php';
+        $this->sParamPrefix = 'dsg';
 
         $this->sParamLogo = 'sys_site_logo';
         $this->sParamLogoAlt = 'sys_site_logo_alt';
@@ -65,7 +67,7 @@ class BxDolStudioDesigner extends BxTemplStudioWidget
 
     function checkAction()
     {
-    	$sAction = bx_get('dsg_action');
+    	$sAction = bx_get($this->sParamPrefix . '_action');
     	if($sAction === false)
             return false;
 
@@ -80,7 +82,7 @@ class BxDolStudioDesigner extends BxTemplStudioWidget
                 break;
 
             case 'delete_cover':
-                $sValue = bx_process_input(bx_get('dsg_value'));
+                $sValue = bx_process_input(bx_get($this->sParamPrefix . '_value'));
 
                 $aResult = array('code' => 0, 'message' => '');
                 if(empty($sValue) || !$this->deleteCover($sValue))
@@ -94,7 +96,7 @@ class BxDolStudioDesigner extends BxTemplStudioWidget
                 break;
 
             case 'get-page-by-type':
-                $sValue = bx_process_input(bx_get('dsg_value'));
+                $sValue = bx_process_input(bx_get($this->sParamPrefix . '_value'));
                 if(empty($sValue))
                     break;
 
@@ -111,6 +113,11 @@ class BxDolStudioDesigner extends BxTemplStudioWidget
         $this->sManageUrl = $sUrl;
     }
 
+    function setParamPrefix($sPrefix)
+    {
+        $this->sParamPrefix = $sPrefix;
+    }
+
     function setLogoParams($aParams)
     {
         list($this->sParamLogo, $this->sParamLogoAlt, $this->sParamLogoWidth, $this->sParamLogoHeight) = $aParams;
@@ -118,7 +125,7 @@ class BxDolStudioDesigner extends BxTemplStudioWidget
 
     function makeDefault()
     {
-        $sValue = bx_get('dsg_value');
+        $sValue = bx_get($this->sParamPrefix . '_value');
         if($sValue === false)
             return false;
 
@@ -195,23 +202,23 @@ class BxDolStudioDesigner extends BxTemplStudioWidget
 
         foreach($this->aCovers as $sCover => $aCover) {
             $iIdNew = $oForm->getCleanValue($sCover);
-        	if(empty($iIdNew))
-        		continue;
+            if(empty($iIdNew))
+                continue;
 
-			$iIdOld = (int)getParam($aCover['setting']);
-			if(!$this->deleteCover($sCover))
-				return $this->getJsResult('_adm_dsg_err_remove_old_cover');
+            $iIdOld = (int)getParam($aCover['setting']);
+            if(!$this->deleteCover($sCover))
+                    return $this->getJsResult('_adm_dsg_err_remove_old_cover');
 
-			$this->oDb->setParam($aCover['setting'], $iIdNew);
-			$oStorage->afterUploadCleanup($iIdNew, $iProfile);
+            $this->oDb->setParam($aCover['setting'], $iIdNew);
+            $oStorage->afterUploadCleanup($iIdNew, $iProfile);
         }
-		
-		$this->oDb->setParam('sys_site_cover_disabled', $oForm->getCleanValue('disabled') == 'on' ? 'on' : '');
 
-		return $this->getJsResult('_adm_dsg_scs_save', true, true, BX_DOL_URL_STUDIO . 'designer.php?page=' . BX_DOL_STUDIO_DSG_TYPE_COVER);
+        $this->oDb->setParam('sys_site_cover_disabled', $oForm->getCleanValue('disabled') == 'on' ? 'on' : '');
+
+        return $this->getJsResult('_adm_dsg_scs_save', true, true, BX_DOL_URL_STUDIO . 'designer.php?page=' . BX_DOL_STUDIO_DSG_TYPE_COVER);
     }
 
-	function deleteCover($sCover)
+    function deleteCover($sCover)
     {
         $iProfile = bx_get_logged_profile_id();
         $oStorage = BxDolStorage::getObjectInstance($this->sCoverStorage);

+ 435 - 0
studio/classes/BxDolStudioOptions.php

@@ -0,0 +1,435 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+define('BX_DOL_STUDIO_STG_TYPE_SYSTEM', 'system');
+define('BX_DOL_STUDIO_STG_TYPE_DEFAULT', BX_DOL_STUDIO_STG_TYPE_SYSTEM);
+
+define('BX_DOL_STUDIO_STG_GROUP_SYSTEM', 'system');
+define('BX_DOL_STUDIO_STG_GROUP_MODULES', 'modules');
+define('BX_DOL_STUDIO_STG_GROUP_LANGUAGES', 'languages');
+define('BX_DOL_STUDIO_STG_GROUP_TEMPLATES', 'templates');
+
+define('BX_DOL_STUDIO_STG_CATEGORY_SYSTEM', 'system');
+define('BX_DOL_STUDIO_STG_CATEGORY_LANGUAGES', 'languages');
+define('BX_DOL_STUDIO_STG_CATEGORY_TEMPLATES', 'templates');
+define('BX_DOL_STUDIO_STG_CATEGORY_AUDIT', 'audit');
+
+define('BX_DOL_STUDIO_STG_MIX_SYSTEM', 'system');
+define('BX_DOL_STUDIO_STG_MIX_DEFAULT', BX_DOL_STUDIO_STG_MIX_SYSTEM);
+
+class BxDolStudioOptions extends BxDol
+{
+    protected $oDb;
+    protected $sParamPrefix;
+    protected $sBaseUrl;
+
+    protected $sType;
+    protected $sCategory;
+    protected $aCategories;
+    protected $aCustomCategories;
+
+    protected $bReadOnly;	//--- Enable/Disable ReadOnly mode for system settings. Note. It doesn't affect Editable mixes.
+
+    protected $bMixes;	//--- Enable/Disable mixes feature.
+    protected $sMix;
+    protected $aMix;
+
+    protected $sStorage;
+    protected $sTranscoder;
+
+    protected $sErrorMessage;
+
+    function __construct($sType = '', $mixedCategory = '', $sMix = '')
+    {
+        parent::__construct();
+
+        $this->oDb = new BxDolStudioOptionsQuery();
+
+        $this->sParamPrefix = 'opt';
+        $this->sBaseUrl = BX_DOL_URL_STUDIO . 'options.php';
+
+        $this->sType = BX_DOL_STUDIO_STG_TYPE_DEFAULT;
+        if(!empty($sType) && is_string($sType))
+            $this->sType = $sType;
+
+        $this->sCategory = '';
+        if(!empty($mixedCategory)) {
+            if(is_array($mixedCategory))
+                $this->sCategory = $mixedCategory;
+            else if(is_string($mixedCategory)) {
+                $this->sCategory = json_decode($mixedCategory);
+                if(empty($this->sCategory))
+                    $this->sCategory = $mixedCategory;
+            }
+        }
+
+        $this->bReadOnly = false;
+
+        $this->bMixes = false;
+        $this->sMix = $sMix;
+        $this->aMix = [];
+
+        $this->sStorage = 'sys_images_custom';
+        $this->sTranscoder = 'sys_custom_images';
+
+        $this->sErrorMessage = '';
+    }
+
+    public function checkAction()
+    {
+        $sAction = bx_get($this->sParamPrefix . '_action');
+    	if($sAction === false)
+            return false;
+
+        $sAction = bx_process_input($sAction);
+
+        $sValue = '';
+        if(bx_get($this->sParamPrefix . '_value') !== false)
+            $sValue = bx_process_input(bx_get($this->sParamPrefix . '_value'));
+
+        $aResult = ['code' => 0, 'message' => ''];
+        switch($sAction) {
+            case 'select-mix':
+                $aResult = array_merge($aResult, $this->selectMix($sValue));
+                break;
+
+            case 'create-mix':
+                $aResult = array_merge($aResult, $this->getPopupCodeCreateMix());
+                break;
+
+            case 'import-mix':
+                $aResult = array_merge($aResult, $this->getPopupCodeImportMix());
+                break;
+
+            case 'export-mix':
+                $aResult = array_merge($aResult, $this->exportMix((int)$sValue));
+                break;
+
+            case 'download-mix':
+                $aResult = array_merge($aResult, $this->downloadMix((int)$sValue));
+                break;
+
+            case 'publish-mix':
+                $aResult = array_merge($aResult, $this->publishMix((int)$sValue));
+                break;
+
+            case 'hide-mix':
+                $aResult = array_merge($aResult, $this->hideMix((int)$sValue));
+                break;
+
+            case 'delete-mix':
+                $aResult = array_merge($aResult, $this->deleteMix((int)$sValue));
+                break;
+
+            case 'get-page-by-type':
+                $this->sType = $sValue;
+                $aResult['content'] = $this->getPageCode();
+                break;
+        }
+
+        return $aResult;
+    }
+
+    public function getType()
+    {
+        return $this->sType;
+    }
+
+    public function enableReadOnly($bReadOnly = true)
+    {
+    	$this->bReadOnly = $bReadOnly;
+    }
+
+    public function enableMixes($bMixes = true)
+    {
+    	$this->bMixes = $bMixes;
+    }
+
+    public function selectMix($sName)
+    {
+        $this->oDb->updateMixes(['active' => 0], [
+            'type' => $this->sType,
+            'category' => is_string($this->sCategory) ? $this->sCategory : '',
+            'active' => 1
+        ]);
+
+        $aResult = [];
+        if($sName == BX_DOL_STUDIO_STG_MIX_SYSTEM || $this->oDb->updateMixes(['active' => 1], ['name' => $sName])) {
+            $this->clearCache();
+
+            $aResult = ['eval' => $this->getJsObject() . '.onMixSelect(oData);'];
+        }
+        else 
+            $aResult = ['message' => _t('_adm_stg_err_cannot_perform')];
+
+        return $aResult;
+    }
+
+    public function exportMix($iId)
+    {
+    	$aMix = [];
+    	$this->oDb->getMixes(['type' => 'by_id', 'value' => $iId], $aMix, false);
+    	if(empty($aMix) || !is_array($aMix))
+            return array('message' => _t('_adm_stg_err_cannot_perform'));
+
+        return [
+            'url' => bx_append_url_params($this->sBaseUrl, [
+                $this->sParamPrefix . '_action' => 'download-mix',
+                $this->sParamPrefix . '_value' => $iId,
+            ]),
+            'eval' => $this->getJsObject() . '.onMixExport(oData);'
+        ];
+    }
+
+    public function downloadMix($iId)
+    {
+    	$aMix = [];
+    	$this->oDb->getMixes(['type' => 'by_id', 'value' => $iId], $aMix, false);
+    	if(empty($aMix) || !is_array($aMix))
+            BxDolStudioTemplate::getInstance()->displayPageNotFound();
+
+        $aOptions = [];
+        $this->oDb->getMixesOptions(['type' => 'by_mix_id_pair_option_value', 'value' => $iId, 'for_export' => 1], $aOptions, false);
+
+    	$sContent = json_encode([
+            'mix' => [
+                'type' => $aMix['type'],
+                'category' => $aMix['category'],
+                'name' => $aMix['name'],
+                'title' => $aMix['title']
+            ],
+            'options' => $aOptions
+    	]);
+
+    	header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
+        header("Content-type: application/json");
+        header("Content-Length: " . strlen($sContent));
+        header("Content-Disposition: attachment; filename=\"". strtolower($aMix['name']) . ".json\"");
+
+        echo $sContent;
+        exit;
+    }
+
+    public function publishMix($iId)
+    {
+    	$aResult = [];
+        if($this->oDb->updateMixes(['published' => 1], ['id' => $iId])) {
+            $this->clearCache();
+
+            $aResult = ['eval' => $this->getJsObject() . '.onMixPublish(oData);'];
+        }
+        else 
+            $aResult = ['message' => _t('_adm_stg_err_cannot_perform')];
+
+        return $aResult;
+    }
+
+    public function hideMix($iId)
+    {
+    	$aResult = [];
+        if($this->oDb->updateMixes(['published' => 0], ['id' => $iId])) {
+            $this->clearCache();
+
+            $aResult = ['eval' => $this->getJsObject() . '.onMixHide(oData);'];
+        }
+        else 
+            $aResult = ['message' => _t('_adm_stg_err_cannot_perform')];
+
+        return $aResult;
+    }
+
+    public function deleteMix($iId)
+    {
+    	$aResult = [];
+    	if($this->oDb->deleteMixesOptions(['mix_id' => $iId]) && $this->oDb->deleteMixes(['id' => $iId])) {
+    	    $this->clearCache();
+
+            $aResult = ['eval' => $this->getJsObject() . '.onMixDelete(oData);'];
+    	}
+    	else 
+            $aResult = ['message' => _t('_adm_stg_err_cannot_perform')]; 
+
+    	return $aResult;
+    }
+
+    public function saveChanges(&$oForm)
+    {
+    	$iMixId = $oForm->getCleanValue('mix_id');
+        $aCategories = explode(',', $oForm->getCleanValue('categories'));
+        $sEvalRenewToken = $this->getJsObject() . ".onSubmitted('" . $oForm->getId() . "', '" . $oForm->getCsrfToken() . "', oData);";
+
+        foreach ($aCategories as $sCategory) {
+            $aOptions = [];
+            $iOptions = $this->oDb->getOptions(['type' => 'by_category_name_full', 'value' => $sCategory], $aOptions);
+
+            $aData = [];
+            foreach($aOptions as $aOption) {
+                $aData[$aOption['name']] = $this->getSubmittedValue($aOption, $oForm);
+                if($aData[$aOption['name']] === false && !empty($this->sErrorMessage)) {
+                    $this->sCategory = $sCategory;
+                    return bx_get_js_result([
+                        'code' => 1,
+                        'message' => _t('_adm_stg_err_save_error_message', _t($aOption['caption']), _t($this->sErrorMessage)),
+                        'translate' => false,
+                        'eval' => $sEvalRenewToken
+                    ]);
+                }
+
+                if(!empty($aOption['check'])) {
+                    $sCheckerHelper = '';
+                    if(!empty($aOption['type_name']) && BxDolRequest::serviceExists($aOption['type_name'], 'get_settings_checker_helper'))
+                        $sCheckerHelper = BxDolService::call($aOption['type_name'], 'get_settings_checker_helper');
+
+                    if($sCheckerHelper == '') {
+                        bx_import('BxDolStudioForm');
+                        $sCheckerHelper = 'BxDolStudioFormCheckerHelper';
+                    }
+
+                    $oChecker = new $sCheckerHelper();
+                    $aCheckFunction = [$oChecker, 'check' . bx_gen_method_name($aOption['check'])];
+                    $aCheckFunctionParams = [$aData[$aOption['name']]];
+                    if(!empty($aOption['check_params']))
+                        $aCheckFunctionParams = array_merge($aCheckFunctionParams, unserialize($aOption['check_params']));
+
+                    if(is_callable($aCheckFunction) && !call_user_func_array($aCheckFunction, $aCheckFunctionParams)) {
+                        $this->sCategory = $sCategory;
+                        return bx_get_js_result([
+                            'code' => 2,
+                            'message' => _t('_adm_stg_err_save_error_message', _t($aOption['caption']), _t($aOption['check_error'])), 
+                            'translate' => false, 
+                            'eval' => $sEvalRenewToken
+                        ]);
+                    }
+                }
+
+                if(isset($aData[$aOption['name']]))
+                    $aData[$aOption['name']] = $this->getProcessedValue($aOption, $aData[$aOption['name']]);
+                else
+                    $aData[$aOption['name']] = $this->getEmptyValue($aOption);
+
+                $mixedValue = $this->oDb->getParam($aOption['name']);
+                if($this->oDb->setParam($aOption['name'], $aData[$aOption['name']], $iMixId)) {
+                    $aCategoryInfo = [];
+                    $this->oDb->getCategories(['type' => 'by_name', 'value' => $sCategory], $aCategoryInfo, false);
+
+                    bx_alert('system', 'save_setting', 0, 0, [
+                        'category' => $aCategoryInfo, 
+                        'option' => $aOption['name'], 
+                        'value' => $aData[$aOption['name']],
+                        'value_prior' => $mixedValue
+                    ]);
+                }
+            }
+        }
+
+        $this->clearCache();
+        return bx_get_js_result([
+            'code' => 0,
+            'message' => '_adm_stg_scs_save',
+            'eval' => $sEvalRenewToken
+        ]);
+    }
+
+    protected function isReadOnly()
+    {
+    	$bMix = !empty($this->aMix);
+    	return (!$bMix && $this->bReadOnly) || ($bMix && (int)$this->aMix['editable'] == 0);
+    }
+
+    protected function getSubmittedValue($aOption, &$oForm)
+    {
+    	$mixedValue = '';
+
+    	switch($aOption['type']) {
+            case 'image':
+                $mixedValue = (int)getParam($aOption['name']);
+
+                $aIds = $oForm->getCleanValue($aOption['name']);
+                if(empty($aIds))
+                    break;
+
+                $oStorage = BxDolStorage::getObjectInstance($this->sStorage);
+                if(!$oStorage)
+                    break;
+
+                //--- Concatenation integer values as strings is required to get unique content id
+                $iContentId = (int)($aOption['id'] . (int)$oForm->getCleanValue('mix_id'));
+                foreach($aIds as $iId) {
+                    $oStorage->updateGhostsContentId($iId, false, $iContentId);
+                    $mixedValue = $iId;
+                }
+                break;
+
+            default: 
+                $mixedValue = $oForm->getCleanValue($aOption['name']);
+    	}
+
+    	return $mixedValue;
+    }
+
+    protected function getProcessedValue($aOption, $mixedValue)
+    {
+        $sMethod = 'processCustomValue' . bx_gen_method_name(trim(str_replace($this->sType, '', $aOption['name']), '_'));
+    	if(method_exists($this, $sMethod))
+    	    return $this->$sMethod($aOption, $mixedValue);
+
+        if(is_array($mixedValue))
+            $mixedValue = implode(',', $mixedValue);
+
+        return $mixedValue;
+    }
+
+    protected function getEmptyValue($aOption)
+    {
+        $mixedValue = '';
+
+        switch($aOption['type']) {
+            case 'digit':
+                $mixedValue = 0;
+                break;
+
+            case 'select':
+                if (BxDolService::isSerializedService($aOption['extra']))
+                    $aValues = BxDolService::callSerialized($aOption['extra']);
+                else
+                    $aValues = explode(',', $aOption['extra']);
+                $mixedValue = $aValues[0];
+                break;
+
+            case 'text':
+            case 'checkbox':
+            case 'file':
+                $mixedValue = "";
+                break;
+        }
+
+        return $mixedValue;
+    }
+
+    protected function clearCache()
+    {
+        BxDolDb::getInstance()->cacheParams(true, true);
+
+        $oCacheUtilities = BxDolCacheUtilities::getInstance();
+        $oCacheUtilities->clear('db');
+        $oCacheUtilities->clear('css');
+    }
+
+    protected function getCustomValueCurrencySign($aItem, $mixedValue)
+    {
+        return htmlspecialchars($mixedValue);
+    }
+
+    protected function processCustomValueCurrencySign($aItem, $mixedValue)
+    {
+        return htmlspecialchars_decode($mixedValue);
+    }
+}
+
+/** @} */

+ 452 - 0
studio/classes/BxDolStudioOptionsQuery.php

@@ -0,0 +1,452 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+class BxDolStudioOptionsQuery extends BxDolDb 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()
+    {
+        $sClass = __CLASS__;
+        if (!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new $sClass();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    public function getTypes($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tt`.`order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'id' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `tt`.`id`=:id";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'by_name':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'name' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `tt`.`name`=:name";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'all':
+                if(!empty($aParams['in_group'])) {
+                    if(!is_array($aParams['in_group']))
+                        $aParams['in_group'] = [$aParams['in_group']];
+
+                    $sWhereClause .= "AND `tt`.`group` IN (" . $this->implode_escape($aParams['in_group']) . ")";
+                }
+
+                if(!empty($aParams['not_in_group'])) {
+                    if(!is_array($aParams['not_in_group']))
+                        $aParams['not_in_group'] = [$aParams['not_in_group']];
+
+                    $sWhereClause .= "AND `tt`.`group` NOT IN (" . $this->implode_escape($aParams['not_in_group']) . ")";
+                }
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tt`.`id` AS `id`,
+                `tt`.`group` AS `group`,
+                `tt`.`name` AS `name`,
+                `tt`.`caption` AS `caption`,
+                `tt`.`icon` AS `icon`,
+                `tt`.`order` AS `order` " . $sSelectClause . "
+            FROM `sys_options_types` AS `tt` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    public function getTypeId($sName)
+    {
+    	$aType = [];
+    	$this->getTypes(['type' => 'by_name', 'value' => $sName], $aType, false);
+
+    	return !empty($aType) && is_array($aType) ? $aType['id'] : 0;
+    }
+
+    public function getCategories($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tc`.`order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'id' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `tc`.`id`=:id";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'by_name':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'name' => $aParams['value']
+                ];
+
+                $sSelectClause .= ", `tt`.`name` AS `type_name`, `tt`.`group` AS `type_group`";
+                $sJoinClause .= "LEFT JOIN `sys_options_types` AS `tt` ON `tc`.`type_id`=`tt`.`id` ";
+                $sWhereClause .= "AND `tc`.`name`=:name";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'all_key_name':
+                $aMethod['name'] = 'getAllWithKey';
+                $aMethod['params'][1] = 'name';
+                break;
+
+            case 'by_type_id_key_name':
+                $aMethod['name'] = 'getAllWithKey';
+                $aMethod['params'][1] = 'name';
+                $aMethod['params'][2] = [
+                    'type_id' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `tc`.`type_id`=:type_id";
+                break;
+
+            case 'by_type_name_key_name':
+                $aMethod['name'] = 'getAllWithKey';
+                $aMethod['params'][1] = 'name';
+
+                $sJoinClause = "LEFT JOIN `sys_options_types` AS `tt` ON `tc`.`type_id`=`tt`.`id` ";
+                if(isset($aParams['category_name']) && !empty($aParams['category_name'])) {
+                    if(is_string($aParams['category_name']))
+                        $aParams['category_name'] = [$aParams['category_name']];
+
+                    $sWhereClause .= "AND `tt`.`name`=:name AND `tc`.`name` IN (" . $this->implode_escape($aParams['category_name']) . ")";
+                    $aMethod['params'][2] = [
+                    	'name' => $aParams['type_name']
+                    ];
+                }
+                else {
+                    $sWhereClause .= "AND `tt`.`name`=:name AND `tc`.`hidden`=:hidden";
+                    $aMethod['params'][2] = [
+                        'name' => $aParams['type_name'],
+                        'hidden' => $aParams['hidden']
+                    ];
+                }
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tc`.`id` AS `id`,
+                `tc`.`type_id` AS `type_id`,
+                `tc`.`name` AS `name`,
+                `tc`.`caption` AS `caption`,
+                `tc`.`order` AS `order`" . $sSelectClause . "
+            FROM `sys_options_categories` AS `tc` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    public function getCategoryId($sName)
+    {
+    	$aCategory = [];
+    	$this->getCategories(['type' => 'by_name', 'value' => $sName], $aCategory, false);
+
+    	return !empty($aCategory) && is_array($aCategory) ? $aCategory['id'] : 0;
+    }
+
+    public function getMixes($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tm`.`name` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'id' => $aParams['value']
+                ];
+                
+                $sWhereClause .= "AND `tm`.`id`=:id";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'by_name':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'name' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `tm`.`name`=:name";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'by_type':
+                $aMethod['params'][1] = [
+                    'type' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `tm`.`type`=:type";
+                break;
+
+            case 'by_category':
+                $aMethod['params'][1] = [
+                    'category' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `tm`.`category`=:category";
+                break;
+
+            case 'by_type_category':
+                $aMethod['params'][1] = [
+                    'type' => $aParams['mix_type'],
+                    'category' => $aParams['mix_category']
+                ];
+
+                $sWhereClause .= "AND `tm`.`type`=:type AND `tm`.`category`=:category";
+                break;
+        }
+
+        if(!empty($aParams['active'])) {
+            if((int)$aParams['active'] == 1)
+                $aMethod['name'] = 'getRow';
+
+            $aMethod['params'][1]['active'] = $aParams['active'];
+
+            $sWhereClause .= " AND `tm`.`active`=:active";
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tm`.`id` AS `id`,
+                `tm`.`type` AS `type`,
+                `tm`.`category` AS `category`,
+                `tm`.`name` AS `name`,
+                `tm`.`title` AS `title`,
+                `tm`.`active` AS `active`,
+                `tm`.`published` AS `published`,
+                `tm`.`editable` AS `editable` " . $sSelectClause . "
+            FROM `sys_options_mixes` AS `tm` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    public function updateMixes($aParamsSet, $aParamsWhere)
+    {
+        if(empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        $sSql = "UPDATE `sys_options_mixes` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND ");
+        return (int)$this->query($sSql) > 0;
+    }
+
+    public function deleteMixes($aParamsWhere)
+    {
+        if(empty($aParamsWhere))
+            return false;
+
+        $sSql = "DELETE FROM `sys_options_mixes` WHERE " . $this->arrayToSQL($aParamsWhere, " AND ");
+        return $this->query($sSql) !== false;
+    }
+
+    public function getMixesOptions($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        switch($aParams['type']) {
+            case 'by_mix_id_pair_option_value':
+                $aMethod['name'] = 'getPairs'; 
+                $aMethod['params'][1] = 'option';
+                $aMethod['params'][2] = 'value';
+                $aMethod['params'][3] = [
+                    'mix_id' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `tmo`.`mix_id`=:mix_id ";
+
+                if(!empty($aParams['for_export'])) {
+                    $sJoinClause .= "LEFT JOIN `sys_options` AS `to` ON `tmo`.`option`=`to`.`name` ";
+                    $sWhereClause .= "AND `to`.`type` NOT IN ('file', 'image')";
+                }
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tmo`.`option` AS `option`,
+                `tmo`.`mix_id` AS `mix_id`,
+                `tmo`.`value` AS `value` " . $sSelectClause . "
+            FROM `sys_options_mixes2options` AS `tmo` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    public function insertMixesOptions($aParamsSet)
+    {
+        if(empty($aParamsSet))
+            return false;
+
+        return (int)$this->query("INSERT INTO `sys_options_mixes2options` SET " . $this->arrayToSQL($aParamsSet)) > 0;
+    }
+
+    public function deleteMixesOptions($aParamsWhere)
+    {
+        if(empty($aParamsWhere))
+            return false;
+
+        $sSql = "DELETE FROM `sys_options_mixes2options` WHERE " . $this->arrayToSQL($aParamsWhere, " AND ");
+        return $this->query($sSql) !== false;
+    }
+
+    public function duplicateMixesOptions($iIdFrom, $iIdTo)
+    {
+    	$aBindings = [
+            'mix_id_from' => $iIdFrom,
+            'mix_id_to' => $iIdTo
+    	];
+
+    	$sSql = "INSERT INTO `sys_options_mixes2options`(`option`, `mix_id`, `value`) SELECT `option`, :mix_id_to, `value` FROM `sys_options_mixes2options` WHERE `mix_id`=:mix_id_from";
+    	return $this->query($sSql, $aBindings) !== false;
+    }
+
+    public function getOptions($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `to`.`order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'id' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `to`.`id`=:id";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'by_name':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = [
+                    'name' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `to`.`name`=:name";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'by_category_id':
+            	$aMethod['params'][1] = [
+                    'category_id' => $aParams['value']
+                ];
+
+                $sWhereClause .= "AND `to`.`category_id`=:category_id";
+                break;
+
+            case 'by_category_name':
+            	$aMethod['params'][1] = [
+                    'name' => $aParams['value']
+                ];
+
+                $sJoinClause .= "LEFT JOIN `sys_options_categories` AS `tc` ON `to`.`category_id`=`tc`.`id` ";
+                $sWhereClause .= "AND `tc`.`name`=:name";
+                break;
+
+            case 'by_category_name_full':
+            	$aMethod['params'][1] = [
+                    'name' => $aParams['value']
+                ];
+
+                $sSelectClause .= ", `tc`.`name` AS `category_name`, `tt`.`name` AS `type_name`";
+                $sJoinClause .= "LEFT JOIN `sys_options_categories` AS `tc` ON `to`.`category_id`=`tc`.`id` LEFT JOIN `sys_options_types` AS `tt` ON `tc`.`type_id`=`tt`.`id` ";
+                $sWhereClause .= "AND `tc`.`name`=:name";
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `to`.`id` AS `id`,
+                `to`.`category_id` AS `category_id`,
+                `to`.`name` AS `name`,
+                `to`.`caption` AS `caption`,
+                `to`.`value` AS `value`,
+                `to`.`type` AS `type`,
+                `to`.`extra` AS `extra`,
+                `to`.`check` AS `check`,
+                `to`.`check_params` AS `check_params`,
+                `to`.`check_error` AS `check_error`,
+                `to`.`order` AS `order`" . $sSelectClause . "
+            FROM `sys_options` AS `to` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+}
+
+/** @} */

+ 3 - 380
studio/classes/BxDolStudioSettings.php

@@ -7,39 +7,9 @@
  * @{
  */
 
-define('BX_DOL_STUDIO_STG_TYPE_SYSTEM', 'system');
-define('BX_DOL_STUDIO_STG_TYPE_DEFAULT', BX_DOL_STUDIO_STG_TYPE_SYSTEM);
-
-define('BX_DOL_STUDIO_STG_GROUP_SYSTEM', 'system');
-define('BX_DOL_STUDIO_STG_GROUP_MODULES', 'modules');
-define('BX_DOL_STUDIO_STG_GROUP_LANGUAGES', 'languages');
-define('BX_DOL_STUDIO_STG_GROUP_TEMPLATES', 'templates');
-
-define('BX_DOL_STUDIO_STG_CATEGORY_SYSTEM', 'system');
-define('BX_DOL_STUDIO_STG_CATEGORY_LANGUAGES', 'languages');
-define('BX_DOL_STUDIO_STG_CATEGORY_TEMPLATES', 'templates');
-define('BX_DOL_STUDIO_STG_CATEGORY_AUDIT', 'audit');
-
-define('BX_DOL_STUDIO_STG_MIX_SYSTEM', 'system');
-define('BX_DOL_STUDIO_STG_MIX_DEFAULT', BX_DOL_STUDIO_STG_MIX_SYSTEM);
-
 class BxDolStudioSettings extends BxTemplStudioWidget
 {
-    protected $sType;
-    protected $sCategory;
-    protected $aCategories;
-    protected $aCustomCategories;
-
-    protected $bReadOnly;	//--- Enable/Disable ReadOnly mode for system settings. Note. It doesn't affect Editable mixes.
-
-    protected $bMixes;	//--- Enable/Disable mixes feature.
-    protected $sMix;
-    protected $aMix;
-
-    protected $sStorage;
-    protected $sTranscoder;
-
-    protected $sErrorMessage;
+    protected $oOptions;
 
     function __construct($sType = '', $mixedCategory = '')
     {
@@ -47,359 +17,12 @@ class BxDolStudioSettings extends BxTemplStudioWidget
 
         $this->oDb = new BxDolStudioSettingsQuery();
 
-        $this->sType = BX_DOL_STUDIO_STG_TYPE_DEFAULT;
-        if(!empty($sType) && is_string($sType))
-            $this->sType = $sType;
-
-        $this->sCategory = '';
-        if(!empty($mixedCategory)) {
-        	if(is_array($mixedCategory))
-        		$this->sCategory = $mixedCategory;
-        	else if(is_string($mixedCategory)) {
-        		$this->sCategory = json_decode($mixedCategory);
-        		if(empty($this->sCategory))
-        			$this->sCategory = $mixedCategory;
-        	}
-        }
-
-        $this->bReadOnly = false;
-
-        $this->bMixes = false;
-        $this->sMix = '';
-        $this->aMix = array();
-
-        $this->sStorage = 'sys_images_custom';
-        $this->sTranscoder = 'sys_custom_images';
-
-        $this->sErrorMessage = '';
+        $this->oOptions = new BxTemplStudioOptions($sType, $mixedCategory);
     }
 
     public function checkAction()
     {
-        $sAction = bx_get('stg_action');
-    	if($sAction === false)
-            return false;
-
-        $sAction = bx_process_input($sAction);
-
-        $sValue = '';
-        if(bx_get('stg_value') !== false)
-            $sValue = bx_process_input(bx_get('stg_value'));
-
-        $aResult = array('code' => 0, 'message' => '');
-        switch($sAction) {
-            case 'select-mix':
-                $aResult = array_merge($aResult, $this->selectMix($sValue));
-                break;
-
-            case 'create-mix':
-                $aResult = array_merge($aResult, $this->getPopupCodeCreateMix());
-                break;
-
-            case 'import-mix':
-                $aResult = array_merge($aResult, $this->getPopupCodeImportMix());
-                break;
-
-            case 'export-mix':
-                $aResult = array_merge($aResult, $this->exportMix((int)$sValue));
-                break;
-
-            case 'download-mix':
-                $aResult = array_merge($aResult, $this->downloadMix((int)$sValue));
-                break;
-
-            case 'publish-mix':
-                $aResult = array_merge($aResult, $this->publishMix((int)$sValue));
-                break;
-
-            case 'hide-mix':
-                $aResult = array_merge($aResult, $this->hideMix((int)$sValue));
-                break;
-
-            case 'delete-mix':
-                $aResult = array_merge($aResult, $this->deleteMix((int)$sValue));
-                break;
-
-            case 'get-page-by-type':
-                $this->sType = $sValue;
-                $aResult['content'] = $this->getPageCode();
-                break;
-        }
-
-        return $aResult;
-    }
-
-    public function enableReadOnly($bReadOnly = true)
-    {
-    	$this->bReadOnly = $bReadOnly;
-    }
-
-    public function enableMixes($bMixes = true)
-    {
-    	$this->bMixes = $bMixes;
-    }
-
-    public function selectMix($sName)
-    {
-        $this->oDb->updateMixes(array('active' => 0), array(
-            'type' => $this->sType,
-            'category' => is_string($this->sCategory) ? $this->sCategory : '',
-            'active' => 1
-        ));
-
-        $aResult = array();
-        if($sName == BX_DOL_STUDIO_STG_MIX_SYSTEM || $this->oDb->updateMixes(array('active' => 1), array('name' => $sName))) {
-            $this->clearCache();
-
-            $aResult = array('eval' => $this->getPageJsObject() . '.onMixSelect(oData);');
-        }
-        else 
-            $aResult = array('message' => _t('_adm_stg_err_cannot_perform'));
-
-        return $aResult;
-    }
-
-    public function exportMix($iId)
-    {
-    	$aMix = array();
-    	$this->oDb->getMixes(array('type' => 'by_id', 'value' => $iId), $aMix, false);
-    	if(empty($aMix) || !is_array($aMix))
-            return array('message' => _t('_adm_stg_err_cannot_perform'));
-
-        return array(
-            'url' => bx_append_url_params(BX_DOL_URL_STUDIO . 'settings.php', array(
-                'stg_action' => 'download-mix',
-                'stg_value' => $iId,
-            )),
-            'eval' => $this->getPageJsObject() . '.onMixExport(oData);'
-        );
-    }
-
-    public function downloadMix($iId)
-    {
-    	$aMix = array();
-    	$this->oDb->getMixes(array('type' => 'by_id', 'value' => $iId), $aMix, false);
-    	if(empty($aMix) || !is_array($aMix))
-            BxDolStudioTemplate::getInstance()->displayPageNotFound();
-
-        $aOptions = array();
-        $this->oDb->getMixesOptions(array('type' => 'by_mix_id_pair_option_value', 'value' => $iId, 'for_export' => 1), $aOptions, false);
-
-    	$aContent = array(
-            'mix' => array(
-                'type' => $aMix['type'],
-                'category' => $aMix['category'],
-                'name' => $aMix['name'],
-                'title' => $aMix['title']
-            ),
-            'options' => $aOptions
-    	);
-    	$sContent = json_encode($aContent);
-
-    	header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
-        header("Content-type: application/json");
-        header("Content-Length: " . strlen($sContent));
-        header("Content-Disposition: attachment; filename=\"". strtolower($aMix['name']) . ".json\"");
-
-        echo $sContent;
-        exit;
-    }
-
-    public function publishMix($iId)
-    {
-    	$aResult = array();
-        if($this->oDb->updateMixes(array('published' => 1), array('id' => $iId))) {
-            $this->clearCache();
-
-            $aResult = array('eval' => $this->getPageJsObject() . '.onMixPublish(oData);');
-        }
-        else 
-            $aResult = array('message' => _t('_adm_stg_err_cannot_perform'));
-
-        return $aResult;
-    }
-
-    public function hideMix($iId)
-    {
-    	$aResult = array();
-        if($this->oDb->updateMixes(array('published' => 0), array('id' => $iId))) {
-            $this->clearCache();
-
-            $aResult = array('eval' => $this->getPageJsObject() . '.onMixHide(oData);');
-        }
-        else 
-            $aResult = array('message' => _t('_adm_stg_err_cannot_perform'));
-
-        return $aResult;
-    }
-
-    public function deleteMix($iId)
-    {
-    	$aResult = array();
-    	if($this->oDb->deleteMixesOptions(array('mix_id' => $iId)) && $this->oDb->deleteMixes(array('id' => $iId))) {
-    	    $this->clearCache();
-
-            $aResult = array('eval' => $this->getPageJsObject() . '.onMixDelete(oData);');
-    	}
-    	else 
-            $aResult = array('message' => _t('_adm_stg_err_cannot_perform')); 
-
-    	return $aResult;
-    }
-
-    function saveChanges(&$oForm)
-    {
-    	$iMixId = $oForm->getCleanValue('mix_id');
-        $aCategories = explode(',', $oForm->getCleanValue('categories'));
-        $sEvalRenewToken = '$("#' . $oForm->getId() . ' input[name=csrf_token]").val("' . $oForm->getCsrfToken() . '");';        
-
-        foreach ($aCategories as $sCategory) {
-            $aOptions = array();
-            $iOptions = $this->oDb->getOptions(array('type' => 'by_category_name_full', 'value' => $sCategory), $aOptions);
-
-            $aData = array();
-            foreach($aOptions as $aOption) {
-                $aData[$aOption['name']] = $this->getSubmittedValue($aOption, $oForm);
-                if($aData[$aOption['name']] === false && !empty($this->sErrorMessage)) {
-                    $this->sCategory = $sCategory;
-                    return $this->getJsResult(_t('_adm_stg_err_save_error_message', _t($aOption['caption']), _t($this->sErrorMessage)), false, false, '', $sEvalRenewToken);
-                }
-
-                if(!empty($aOption['check'])) {
-                    $sCheckerHelper = '';
-                    if(!empty($aOption['type_name']) && BxDolRequest::serviceExists($aOption['type_name'], 'get_settings_checker_helper'))
-                        $sCheckerHelper = BxDolService::call($aOption['type_name'], 'get_settings_checker_helper');
-
-                    if($sCheckerHelper == '') {
-                        bx_import('BxDolStudioForm');
-                        $sCheckerHelper = 'BxDolStudioFormCheckerHelper';
-                    }
-
-                    $oChecker = new $sCheckerHelper();
-                    $aCheckFunction = array($oChecker, 'check' . bx_gen_method_name($aOption['check']));
-                    $aCheckFunctionParams = array($aData[$aOption['name']]);
-                    if(!empty($aOption['check_params']))
-                        $aCheckFunctionParams = array_merge($aCheckFunctionParams, unserialize($aOption['check_params']));
-
-                    if(is_callable($aCheckFunction) && !call_user_func_array($aCheckFunction, $aCheckFunctionParams)) {
-                        $this->sCategory = $sCategory;
-                        return $this->getJsResult(_t('_adm_stg_err_save_error_message', _t($aOption['caption']), _t($aOption['check_error'])), false, false, '', $sEvalRenewToken);
-                    }
-                }
-
-                if(isset($aData[$aOption['name']]))
-                    $aData[$aOption['name']] = $this->getProcessedValue($aOption, $aData[$aOption['name']]);
-                else
-                    $aData[$aOption['name']] = $this->getEmptyValue($aOption);
-
-                $mixedValue = $this->oDb->getParam($aOption['name']);
-                if($this->oDb->setParam($aOption['name'], $aData[$aOption['name']], $iMixId)) {
-                    $aCategoryInfo = array();
-                    $this->oDb->getCategories(array('type' => 'by_name', 'value' => $sCategory), $aCategoryInfo, false);
-
-                    bx_alert('system', 'save_setting', 0, 0, array(
-                        'category' => $aCategoryInfo, 
-                        'option' => $aOption['name'], 
-                        'value' => $aData[$aOption['name']],
-                        'value_prior' => $mixedValue
-                    ));
-                }
-            }
-        }
-
-        $this->clearCache();
-        return $this->getJsResult('_adm_stg_scs_save', true, false, '', $sEvalRenewToken);
-    }
-
-    protected function isReadOnly()
-    {
-    	$bMix = !empty($this->aMix);
-    	return (!$bMix && $this->bReadOnly) || ($bMix && (int)$this->aMix['editable'] == 0);
-    }
-
-    protected function getSubmittedValue($aOption, &$oForm)
-    {
-    	$mixedValue = '';
-
-    	switch($aOption['type']) {
-    		case 'image':
-    			$mixedValue = (int)getParam($aOption['name']);
-
-    			$aIds = $oForm->getCleanValue($aOption['name']);
-    			if(empty($aIds))
-    				break;
-
-    			$oStorage = BxDolStorage::getObjectInstance($this->sStorage);
-		        if(!$oStorage)
-					break;
-
-				//--- Concatenation integer values as strings is required to get unique content id
-				$iContentId = (int)($aOption['id'] . (int)$oForm->getCleanValue('mix_id'));
-    			foreach($aIds as $iId) {
-    				$oStorage->updateGhostsContentId($iId, false, $iContentId);
-    				$mixedValue = $iId;
-    			}
-    			break;
-
-    		default: 
-    			$mixedValue = $oForm->getCleanValue($aOption['name']);
-    	}
-
-    	return $mixedValue;
-    }
-
-    protected function getProcessedValue($aOption, $mixedValue)
-    {
-        $sMethod = 'processCustomValue' . bx_gen_method_name(trim(str_replace($this->sType, '', $aOption['name']), '_'));
-    	if(method_exists($this, $sMethod))
-    	    return $this->$sMethod($aOption, $mixedValue);
-
-        if(is_array($mixedValue))
-            $mixedValue = implode(',', $mixedValue);
-
-        return $mixedValue;
-    }
-
-    protected function getEmptyValue($aOption)
-    {
-        $mixedValue = '';
-        switch($aOption['type']) {
-            case 'digit':
-                $mixedValue = 0;
-                break;
-            case 'select':
-                if (BxDolService::isSerializedService($aOption['extra']))
-                    $aValues = BxDolService::callSerialized($aOption['extra']);
-                else
-                    $aValues = explode(',', $aOption['extra']);
-                $mixedValue = $aValues[0];
-                break;
-            case 'text':
-            case 'checkbox':
-            case 'file':
-                $mixedValue = "";
-                break;
-        }
-        return $mixedValue;
-    }
-
-    protected function clearCache()
-    {
-        BxDolDb::getInstance()->cacheParams(true, true);
-
-        $oCacheUtilities = BxDolCacheUtilities::getInstance();
-        $oCacheUtilities->clear('db');
-        $oCacheUtilities->clear('css');
-    }
-
-    protected function getCustomValueCurrencySign($aItem, $mixedValue)
-    {
-        return htmlspecialchars($mixedValue);
-    }
-
-    protected function processCustomValueCurrencySign($aItem, $mixedValue)
-    {
-        return htmlspecialchars_decode($mixedValue);
+        return $this->oOptions->checkAction();
     }
 }
 

+ 31 - 0
studio/inc/utils.inc.php

@@ -7,6 +7,37 @@
  * @{
  */
 
+function bx_get_js_result($aParams)
+{
+    $aResult = [];
+    if(isset($aParams['code']))
+        $aResult['code'] = $aParams['code'];
+
+    if(!empty($aParams['message'])) {
+        $aResult['message'] = $aParams['message'];
+
+        if(!isset($aParams['translate']) || !empty($aParams['translate'])) {
+            $aTrtParams = array($aResult['message']);
+            if(!empty($aParams['translate']) && is_array($aParams['translate']))
+                $aTrtParams = array_merge($aTrtParams, $aParams['translate']);
+
+            $aResult['message'] = call_user_func_array ('_t', $aTrtParams);
+        }
+    }
+
+    if(isset($aParams['redirect']) && $aParams['redirect'] !== false)
+        $aResult['redirect'] = is_string($aParams['redirect']) ? $aParams['redirect'] : BX_DOL_URL_STUDIO;
+
+    if(!empty($aParams['eval']))
+        $aResult['eval'] = $aParams['eval'];
+
+    $sResult = "window.parent.processJsonData(" . json_encode($aResult) . ");";
+    if(isset($aParams['on_page_load']) && $aParams['on_page_load'] === true)
+        $sResult = "$(document).ready(function() {" . $sResult . "});";
+
+    return BxDolStudioTemplate::getInstance()->_wrapInTagJsCode($sResult);
+}
+
 function bx_array_insert_before($aInsert, $aSource, $sKey)
 {
     return bx_array_insert($aInsert, $aSource, $sKey, 0);

+ 64 - 66
studio/js/designer.js

@@ -6,8 +6,9 @@
  * @{
  */
 function BxDolStudioDesigner(oOptions) {
-	this.sActionsUrl = oOptions.sActionUrl;
+    this.sActionsUrl = oOptions.sActionUrl;
     this.sObjName = oOptions.sObjName == undefined ? 'oBxDolStudioDesigner' : oOptions.sObjName;
+    this.sParamPrefix = oOptions.sParamPrefix == undefined ? 'dsg' : oOptions.sParamPrefix;
     this.sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
     this.iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
     this.sCodeMirror = oOptions.sCodeMirror == undefined ? '' : oOptions.sCodeMirror;
@@ -20,84 +21,81 @@ function BxDolStudioDesigner(oOptions) {
 }
 
 BxDolStudioDesigner.prototype.initCodeMirror = function(sSelector) {
-	var oSelector = $(sSelector);
-	for(var i = 0; i < oSelector.length; i++) {
-	    var e = CodeMirror.fromTextArea(oSelector.get(i), {
-	        lineNumbers: true,
-	        mode: "htmlmixed",
-	        htmlMode: true,
-	        matchBrackets: true
-	    });
-	}
+    var oSelector = $(sSelector);
+    for(var i = 0; i < oSelector.length; i++) {
+        var e = CodeMirror.fromTextArea(oSelector.get(i), {
+            lineNumbers: true,
+            mode: "htmlmixed",
+            htmlMode: true,
+            matchBrackets: true
+        });
+    }
 };
 
 BxDolStudioDesigner.prototype.makeDefault = function(sUri) {
-	var $this = this;
-	var oDate = new Date();
+    var $this = this;
+    var oDate = new Date();
+    var aParams = {_t: oDate.getTime()};
+    aParams[this.sParamPrefix + '_action'] = 'make_default';
+    aParams[this.sParamPrefix + '_value'] = sUri;
 
-	$.post(
-		this.sActionsUrl,
-		{
-			dsg_action: 'make_default',
-			dsg_value: sUri,
-			_t: oDate.getTime()
-		},
-		function(oData) {
-			if(oData.code != 0 && oData.message.length > 0) {
-				bx_alert(oData.message);
-				return;
-			}
+    $.post(
+        this.sActionsUrl,
+        aParams,
+        function(oData) {
+            if(oData.code != 0 && oData.message.length > 0) {
+                bx_alert(oData.message);
+                return;
+            }
 
-			document.location.href = document.location.href; 
-		},
-		'json'
-	);
+            document.location.href = document.location.href; 
+        },
+        'json'
+    );
 };
 
 BxDolStudioDesigner.prototype.deleteLogo = function() {
-	var $this = this;
-	var oDate = new Date();
+    var $this = this;
+    var oDate = new Date();
+    var aParams = {_t: oDate.getTime()};
+    aParams[this.sParamPrefix + '_action'] = 'delete_logo';
 
-	$.post(
-		this.sActionsUrl,
-		{
-			dsg_action: 'delete_logo',
-			_t: oDate.getTime()
-		},
-		function(oData) {
-			if(oData.code != 0 && oData.message.length > 0) {
-				bx_alert(oData.message);
-				return;
-			}
+    $.post(
+        this.sActionsUrl,
+        aParams,
+        function(oData) {
+            if(oData.code != 0 && oData.message.length > 0) {
+                bx_alert(oData.message);
+                return;
+            }
 
-			document.location.href = document.location.href; 
-		},
-		'json'
-	);
+            document.location.href = document.location.href; 
+        },
+        'json'
+    );
 };
 
 BxDolStudioDesigner.prototype.deleteCover = function(sType, iId) {
-	var $this = this;
-	var oDate = new Date();
+    var $this = this;
+    var oDate = new Date();
+    var aParams = {_t: oDate.getTime()};
+    aParams[this.sParamPrefix + '_action'] = 'delete_cover';
+    aParams[this.sParamPrefix + '_value'] = sType;
 
-	$.post(
-		this.sActionsUrl,
-		{
-			dsg_action: 'delete_cover',
-			dsg_value: sType, 
-			_t: oDate.getTime()
-		},
-		function(oData) {
-			if(oData.code != 0 && oData.message.length > 0) {
-				bx_alert(oData.message);
-				return;
-			}
+    $.post(
+        this.sActionsUrl,
+        aParams,
+        function(oData) {
+            if(oData.code != 0 && oData.message.length > 0) {
+                bx_alert(oData.message);
+                return;
+            }
 
-			$('#bx-dsg-cover-' + iId).bx_anim('hide', $this.sAnimationEffect, $this.iAnimationSpeed, function() {
-				$(this).remove();
-			});
-		},
-		'json'
-	);
+            $('#bx-dsg-cover-' + iId).bx_anim('hide', $this.sAnimationEffect, $this.iAnimationSpeed, function() {
+                $(this).remove();
+            });
+        },
+        'json'
+    );
 };
-/** @} */
+/** @} */

+ 179 - 0
studio/js/options.js

@@ -0,0 +1,179 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+function BxDolStudioOptions(oOptions) {
+    this.sActionsUrl = oOptions.sActionUrl;
+    this.sObjName = oOptions.sObjName == undefined ? 'oBxDolStudioOptions' : oOptions.sObjName;
+    this.sParamPrefix = oOptions.sParamPrefix == undefined ? 'opt' : oOptions.sParamPrefix;
+
+    this.sType = oOptions.sType == undefined ? '' : oOptions.sType;
+    this.sCategory = oOptions.sCategory == undefined ? '' : oOptions.sCategory;
+    this.sMix = oOptions.sMix == undefined ? '' : oOptions.sMix;
+
+    this.sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
+    this.iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+
+    this.bBusy = false;
+}
+
+BxDolStudioOptions.prototype.onSubmit = function(oForm) {
+    if(this.bBusy)
+        return false;
+
+    var oSubmit = $(oForm).find('input[type="submit"],button[type="submit"]');
+
+    oSubmit.addClass('bx-btn-disabled');
+    bx_loading_btn(oSubmit, true);
+
+    return (this.bBusy = true);
+};
+
+BxDolStudioOptions.prototype.onSubmitted = function(sFormId, sTocken, oData) {
+    var oForm = $('#' + sFormId);
+    var oSubmit = oForm.find('input[type="submit"],button[type="submit"]');
+
+    oForm.find('input[name="csrf_token"]').val(sTocken);
+
+    oSubmit.removeClass('bx-btn-disabled');
+    bx_loading_btn(oSubmit, false);
+
+    this.bBusy = false;
+};
+
+BxDolStudioOptions.prototype.mixCreate = function(oButton) {
+    this.mixActionWithValue(oButton, 'create-mix');
+};
+
+BxDolStudioOptions.prototype.onMixCreate = function(oData) {
+    document.location.href = document.location.href;
+};
+
+BxDolStudioOptions.prototype.mixImport = function(oButton) {
+    this.mixActionWithValue(oButton, 'import-mix');
+};
+
+BxDolStudioOptions.prototype.onMixImport = function(oData) {
+    document.location.href = document.location.href;
+};
+
+BxDolStudioOptions.prototype.mixSelect = function(oSelect) {
+    this.mixActionWithValue(oSelect, 'select-mix', $(oSelect).val());
+};
+
+BxDolStudioOptions.prototype.onMixSelect = function(oData) {
+    document.location.href = document.location.href;
+};
+
+BxDolStudioOptions.prototype.mixExport = function(oButton, iId) {
+    this.mixActionWithValue(oButton, 'export-mix', iId);
+};
+
+BxDolStudioOptions.prototype.onMixExport = function(oData) {
+    document.location.href = oData.url;
+};
+
+BxDolStudioOptions.prototype.mixPublish = function(oButton, iId) {
+    this.mixActionWithValue(oButton, 'publish-mix', iId);
+};
+
+BxDolStudioOptions.prototype.onMixPublish = function(oData) {
+    document.location.href = document.location.href;
+};
+
+BxDolStudioOptions.prototype.mixHide = function(oButton, iId) {
+    this.mixActionWithValue(oButton, 'hide-mix', iId);
+};
+
+BxDolStudioOptions.prototype.onMixHide = function(oData) {
+    document.location.href = document.location.href;
+};
+
+BxDolStudioOptions.prototype.mixDelete = function(oButton, iId) {
+    this.mixActionWithValue(oButton, 'delete-mix', iId, 1);
+};
+
+BxDolStudioOptions.prototype.onMixDelete = function(oData) {
+    document.location.href = document.location.href;
+};
+
+BxDolStudioOptions.prototype.mixAction = function(oSource, sAction) {
+    var $this = this;
+    var oDate = new Date();
+    var aParams = {
+        type: this.sType,
+        category: this.sCategory,
+        _t:oDate.getTime()
+    };
+    aParams[this.sParamPrefix + '_action'] = sAction;
+
+    $.post(
+        this.sActionsUrl,
+        aParams,
+        function (oData) {
+            processJsonData(oData);
+        },
+        'json'
+    );
+};
+
+BxDolStudioOptions.prototype.mixActionWithValue = function(oSource, sAction, mixedValue, bConfirm) {
+    var $this = this;
+    var oDate = new Date();
+    var aParams = {
+        type: $this.sType,
+        category: $this.sCategory,
+        _t:oDate.getTime()
+    };
+    aParams[this.sParamPrefix + '_action'] = sAction;
+    aParams[this.sParamPrefix + '_value'] = mixedValue;
+
+    var oPerform = function() {
+        $.post(
+            $this.sActionsUrl,
+            aParams,
+            function (oData) {
+                processJsonData(oData)
+            },
+            'json'
+        );
+    };
+
+    if(bConfirm != undefined && parseInt(bConfirm) == 1)
+	bx_confirm('', oPerform);
+    else
+        oPerform();
+};
+
+BxDolStudioOptions.prototype.processResult = function(oData) {
+    var $this = this;
+
+    if(oData && oData.message != undefined && oData.message.length != 0)
+        $(document).dolPopupAlert({
+            message: oData.message
+        });
+
+    if(oData && oData.reload != undefined && parseInt(oData.reload) == 1)
+    	document.location = document.location;
+
+    if(oData && oData.popup != undefined) {
+    	var oPopup = $(oData.popup).hide(); 
+
+    	$('#' + oPopup.attr('id')).remove();
+        oPopup.prependTo('body').dolPopup({
+            fog: {
+                color: '#fff',
+                opacity: .7
+            },
+            closeOnOuterClick: false
+        });
+    }
+
+    if (oData && oData.eval != undefined)
+        eval(oData.eval);
+};
+/** @} */

+ 1 - 128
studio/js/settings.js

@@ -6,112 +6,13 @@
  * @{
  */
 function BxDolStudioSettings(oOptions) {
-	this.sActionsUrl = oOptions.sActionUrl;
+    this.sActionsUrl = oOptions.sActionUrl;
     this.sObjName = oOptions.sObjName == undefined ? 'oBxDolStudioSettings' : oOptions.sObjName;
 
-    this.sType = oOptions.sType == undefined ? '' : oOptions.sType;
-    this.sCategory = oOptions.sCategory == undefined ? '' : oOptions.sCategory;
-    this.sMix = oOptions.sMix == undefined ? '' : oOptions.sMix;
-
     this.sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
     this.iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
 }
 
-BxDolStudioSettings.prototype.mixCreate = function(oButton) {
-    this.mixActionWithValue(oButton, 'create-mix');
-};
-BxDolStudioSettings.prototype.onMixCreate = function(oData) {
-    document.location.href = document.location.href;
-};
-
-BxDolStudioSettings.prototype.mixImport = function(oButton) {
-    this.mixActionWithValue(oButton, 'import-mix');
-};
-BxDolStudioSettings.prototype.onMixImport = function(oData) {
-    document.location.href = document.location.href;
-};
-
-BxDolStudioSettings.prototype.mixSelect = function(oSelect) {
-    this.mixActionWithValue(oSelect, 'select-mix', $(oSelect).val());
-};
-BxDolStudioSettings.prototype.onMixSelect = function(oData) {
-    document.location.href = document.location.href;
-};
-
-BxDolStudioSettings.prototype.mixExport = function(oButton, iId) {
-    this.mixActionWithValue(oButton, 'export-mix', iId);
-};
-BxDolStudioSettings.prototype.onMixExport = function(oData) {
-    document.location.href = oData.url;
-};
-
-BxDolStudioSettings.prototype.mixPublish = function(oButton, iId) {
-    this.mixActionWithValue(oButton, 'publish-mix', iId);
-};
-BxDolStudioSettings.prototype.onMixPublish = function(oData) {
-    document.location.href = document.location.href;
-};
-
-BxDolStudioSettings.prototype.mixHide = function(oButton, iId) {
-    this.mixActionWithValue(oButton, 'hide-mix', iId);
-};
-BxDolStudioSettings.prototype.onMixHide = function(oData) {
-    document.location.href = document.location.href;
-};
-
-BxDolStudioSettings.prototype.mixDelete = function(oButton, iId) {
-    this.mixActionWithValue(oButton, 'delete-mix', iId, 1);
-};
-BxDolStudioSettings.prototype.onMixDelete = function(oData) {
-    document.location.href = document.location.href;
-};
-
-BxDolStudioSettings.prototype.mixAction = function(oSource, sAction) {
-    var $this = this;
-    var oDate = new Date();
-
-    $.post(
-        sUrlStudio + 'settings.php',
-        {
-            page: this.sType,
-            category: this.sCategory,
-            stg_action: sAction,
-            _t:oDate.getTime()
-        },
-        function (oData) {
-            $this.processResult(oData);
-        },
-        'json'
-    );
-};
-
-BxDolStudioSettings.prototype.mixActionWithValue = function(oSource, sAction, mixedValue, bConfirm) {
-    var $this = this;
-    var oDate = new Date();
-
-    var oPerform = function() {
-        $.post(
-            sUrlStudio + 'settings.php',
-            {
-                page: $this.sType,
-                category: $this.sCategory,
-                stg_action: sAction,
-                stg_value: mixedValue,
-                _t:oDate.getTime()
-            },
-            function (oData) {
-                $this.processResult(oData);
-            },
-            'json'
-        );
-    };
-
-    if(bConfirm != undefined && parseInt(bConfirm) == 1)
-	bx_confirm('', oPerform);
-    else
-        oPerform();
-};
-
 /**
  * Is needed if AJAX is used to change (reload) pages. 
  */
@@ -144,32 +45,4 @@ BxDolStudioSettings.prototype.changePage = function(sType) {
 
     return true;
 };
-
-BxDolStudioSettings.prototype.processResult = function(oData) {
-    var $this = this;
-
-    if(oData && oData.message != undefined && oData.message.length != 0)
-        $(document).dolPopupAlert({
-            message: oData.message
-        });
-
-    if(oData && oData.reload != undefined && parseInt(oData.reload) == 1)
-    	document.location = document.location;
-
-    if(oData && oData.popup != undefined) {
-    	var oPopup = $(oData.popup).hide(); 
-
-    	$('#' + oPopup.attr('id')).remove();
-        oPopup.prependTo('body').dolPopup({
-            fog: {
-                color: '#fff',
-                opacity: .7
-            },
-            closeOnOuterClick: false
-        });
-    }
-
-    if (oData && oData.eval != undefined)
-        eval(oData.eval);
-};
 /** @} */

+ 33 - 0
studio/options.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaEndAdmin UNA Studio End Admin Pages
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+require_once('./../inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
+require_once(BX_DOL_DIR_STUDIO_INC . 'utils.inc.php');
+
+bx_import('BxDolLanguages');
+
+bx_require_authentication(true);
+
+$sType = bx_get('type');
+$sType = $sType !== false ? bx_process_input($sType) : '';
+
+$mixedCategory = bx_get('category');
+$mixedCategory = $mixedCategory !== false ? bx_process_input($mixedCategory) : '';
+
+$oOptions = new BxTemplStudioOptions($sType, $mixedCategory);
+
+if(($mixedResult = $oOptions->checkAction()) !== false) {
+    echoJson($mixedResult);
+    exit;
+}
+
+echo $oOptions->getCode();
+/** @} */

+ 1 - 0
studio/settings.php

@@ -10,6 +10,7 @@
 
 require_once('./../inc/header.inc.php');
 require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
+require_once(BX_DOL_DIR_STUDIO_INC . 'utils.inc.php');
 
 bx_import('BxDolLanguages');
 

+ 27 - 0
studio/template/css/options.css

@@ -0,0 +1,27 @@
+div.bx-stg-mixes {
+    position: relative;
+    white-space: nowrap;
+}
+div.bx-stg-mixes div.bx-sm-cell {
+    position: relative;
+}
+div.bx-stg-mixes div.bx-sm-cell.bx-sm-select {
+    float: left;
+    width: 50%;
+}
+div.bx-stg-mixes div.bx-sm-cell.bx-sm-new {
+    float: right;
+    width: 50%;
+
+    text-align: right;
+}
+
+div.bx-stg-mixes div.bx-sm-cell select {
+    width: auto;
+}
+
+div.bx-stg-mixes div.bx-sm-cell .bx-btn {
+    display: inline-block;
+    float: none;
+    vertical-align: middle;
+}

+ 0 - 27
studio/template/css/settings.css

@@ -1,27 +0,0 @@
-div.bx-stg-mixes {
-	position: relative;
-	white-space: nowrap;
-}
-div.bx-stg-mixes div.bx-sm-cell {
-	position: relative;
-}
-div.bx-stg-mixes div.bx-sm-cell.bx-sm-select {
-	float: left;
-	width: 50%;
-}
-div.bx-stg-mixes div.bx-sm-cell.bx-sm-new {
-	float: right;
-	width: 50%;
-
-	text-align: right;
-}
-
-div.bx-stg-mixes div.bx-sm-cell select {
-	width: auto;
-}
-
-div.bx-stg-mixes div.bx-sm-cell .bx-btn {
-	display: inline-block;
-	float: none;
-	vertical-align: middle;
-}

+ 3 - 2
studio/template/settings.html → studio/template/options.html

@@ -1,6 +1,7 @@
 <script defer language="javascript">
-    var __js_object__ = new BxDolStudioSettings({
-        sActionUrl: '<bx_url_studio />settings.php',
+    var __js_object__ = new BxDolStudioOptions({
+        sActionUrl: '__base_url__',
+        sParamPrefix: '__param_prefix__',
         sType: '__type__',
         sCategory: '__category__',
         sMix: '__mix__',

+ 0 - 0
studio/template/stg_create_mix.html → studio/template/options_create_mix.html


+ 0 - 0
studio/template/stg_import_mix.html → studio/template/options_import_mix.html


+ 10 - 16
studio/template/scripts/BxBaseStudioAudit.php

@@ -18,6 +18,8 @@ class BxBaseStudioAudit extends BxDolStudioAudit
     {
         parent::__construct($sPage);
 
+        $this->aPageCss = array_merge($this->aPageCss, ['forms.css', 'paginate.css']);
+
         $this->sSubpageUrl = BX_DOL_URL_STUDIO . 'audit.php?page=';
 
         $this->aMenuItems = array(
@@ -25,16 +27,6 @@ class BxBaseStudioAudit extends BxDolStudioAudit
             BX_DOL_STUDIO_AUD_TYPE_SETTINGS => array('icon' => 'cogs'),
         );
     }
-	
-    function getPageCss()
-    {
-        return array_merge(parent::getPageCss(), array('forms.css', 'paginate.css'));
-    }
-	
-    function getPageJs()
-    {
-        return array_merge(parent::getPageJs(), array('settings.js'));
-    }
 
     function getPageJsCode($aOptions = array(), $bWrap = true)
     {
@@ -44,7 +36,7 @@ class BxBaseStudioAudit extends BxDolStudioAudit
 
         return parent::getPageJsCode($aOptions, $bWrap);
     }
-    
+
     function getPageMenu($aMenu = array(), $aMarkers = array())
     {
         $sJsObject = $this->getPageJsObject();
@@ -61,18 +53,20 @@ class BxBaseStudioAudit extends BxDolStudioAudit
 
         return parent::getPageMenu($aMenu);
     }
-    
+
     protected function getGeneral()
     {
         return $this->getGrid();
     }
-    
+
     protected function getSettings()
     {
-        $oPage = new BxTemplStudioSettings(BX_DOL_STUDIO_STG_TYPE_SYSTEM, BX_DOL_STUDIO_STG_CATEGORY_AUDIT);
-        
+        $oOptions = new BxTemplStudioOptions(BX_DOL_STUDIO_STG_TYPE_SYSTEM, BX_DOL_STUDIO_STG_CATEGORY_AUDIT);
+
+        $this->aPageCss = array_merge($this->aPageCss, $oOptions->getCss());
+        $this->aPageJs = array_merge($this->aPageJs, $oOptions->getJs());
         return BxDolStudioTemplate::getInstance()->parseHtmlByName('audit.html', array(
-            'content' => $oPage->getFormCode(),
+            'content' => $oOptions->getCode(),
             'js_content' => ''
         ));
     }

+ 0 - 5
studio/template/scripts/BxBaseStudioBadges.php

@@ -29,11 +29,6 @@ class BxBaseStudioBadges extends BxDolStudioBadges
     {
         return array_merge(parent::getPageCss(), array('forms.css', 'paginate.css', 'badges.css'));
     }
-	
-    function getPageJs()
-    {
-        return array_merge(parent::getPageJs(), array('settings.js'));
-    }
 
     function getPageJsCode($aOptions = array(), $bWrap = true)
     {

+ 25 - 11
studio/template/scripts/BxBaseStudioDesign.php

@@ -19,20 +19,19 @@ class BxBaseStudioDesign extends BxDolStudioDesign
     {
         parent::__construct($sModule, $mixedPageName, $sPage);
 
-        $this->oHelper = BxTemplStudioDesigns::getInstance();
-    }
+        $this->aPageJs = array_merge($this->aPageJs, ['jquery.form.min.js', 'jquery.webForms.js']);
 
-    public function getPageJs()
-    {
-        return array_merge(parent::getPageJs(), array('jquery.form.min.js', 'jquery.webForms.js'));
+        $this->oHelper = BxTemplStudioDesigns::getInstance();
     }
 
     protected function getSettings($mixedCategory = '', $sMix = '')
     {
-        $oPage = new BxTemplStudioSettings($this->sModule, $mixedCategory, $sMix);
+        $oOptions = new BxTemplStudioOptions($this->sModule, $mixedCategory, $sMix);
 
+        $this->aPageCss = array_merge($this->aPageCss, $oOptions->getCss());
+        $this->aPageJs = array_merge($this->aPageJs, $oOptions->getJs());
         return BxDolStudioTemplate::getInstance()->parseHtmlByName('design.html', array(
-            'content' => $oPage->getFormCode(),
+            'content' => $oOptions->getCode(),
             'js_content' => $this->getPageJsCode(),
         ));
     }
@@ -41,14 +40,29 @@ class BxBaseStudioDesign extends BxDolStudioDesign
     {
     	$oPage = $this->getObjectDesigner();
 
-        $oTemplate = BxDolStudioTemplate::getInstance();
-        $oTemplate->addJs($oPage->getPageJs());
-        $oTemplate->addCss($oPage->getPageCss());
-    	return $oTemplate->parseHtmlByName('design.html', array(
+        $this->aPageCss = array_merge($this->aPageCss, $oPage->getPageCss());
+        $this->aPageJs = array_merge($this->aPageJs, $oPage->getPageJs());
+    	return BxDolStudioTemplate::getInstance()->parseHtmlByName('design.html', array(
             'content' => $oPage->getPageCode(BX_DOL_STUDIO_TEMPL_TYPE_LOGO, false),
             'js_content' => $this->getPageJsCode()
         ));
     }
+    
+    protected function getStyles($mixedCategory = '', $sMix = '')
+    {
+    	$oOptions = new BxTemplStudioOptions($this->sModule, $mixedCategory, $sMix);
+    	$oOptions->enableReadOnly(true);
+    	$oOptions->enableMixes(true);
+
+        $this->aPageCss = array_merge($this->aPageCss, $oOptions->getCss(), [BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'codemirror/|codemirror.css']);
+        $this->aPageJs = array_merge($this->aPageJs, $oOptions->getJs(), ['codemirror/codemirror.min.js']);
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('design.html', [
+            'content' => $oOptions->getCode(),
+            'js_content' => $this->getPageJsCode([
+                'sCodeMirror' => "textarea[name='" . $this->sModule . "_styles_custom']"
+            ])
+        ]);
+    }
 }
 
 /** @} */

+ 17 - 29
studio/template/scripts/BxBaseStudioDesigner.php

@@ -29,41 +29,27 @@ class BxBaseStudioDesigner extends BxDolStudioDesigner
     protected $sInjectionsIframeId = 'adm-dsg-injections-iframe';
     protected $sInjectionsEditorId = 'adm-dsg-injections-editor';
 
-    protected $aPageCss;
-    protected $aPageJs;
-
-    function __construct($sPage = '')
+    public function __construct($sPage = '')
     {
         parent::__construct($sPage);
-                
-        $this->aPageCss = array('forms.css', 'designer.css');
-        $this->aPageJs = array('settings.js', 'designer.js');
-    }
-    function getPageCss()
-    {
-        return array_merge(parent::getPageCss(), $this->aPageCss);
-    }
-    function getPageJs()
-    {
-        return array_merge(parent::getPageJs(), $this->aPageJs);
-    }
-    function getPageJsObject()
-    {
-        return 'oBxDolStudioDesigner';
-    }
-    function getPageJsClass()
-    {
-    	return 'BxDolStudioDesigner';
+
+        $this->aPageCss = array_merge($this->aPageCss, ['forms.css', 'designer.css']);
+        $this->aPageJs = array_merge($this->aPageJs, ['designer.js']);
+        $this->sPageJsClass = 'BxDolStudioDesigner';
+        $this->sPageJsObject = 'oBxDolStudioDesigner';
     }
+
     public function getPageJsCode($aOptions = array(), $bWrap = true)
     {
-    	$aOptions = array_merge(array(
-    		'sActionUrl' => $this->sManageUrl
-    	), $aOptions);
+    	$aOptions = array_merge([
+            'sActionUrl' => $this->sManageUrl,
+            'sParamPrefix' => $this->sParamPrefix
+    	], $aOptions);
 
     	return parent::getPageJsCode($aOptions, $bWrap);
     }
-    function getPageMenu($aMenu = array(), $aMarkers = array())
+
+    public function getPageMenu($aMenu = array(), $aMarkers = array())
     {
         $sJsObject = $this->getPageJsObject();
 
@@ -618,10 +604,12 @@ class BxBaseStudioDesigner extends BxDolStudioDesigner
 
     protected function getSettings()
     {
-        $oPage = new BxTemplStudioSettings(BX_DOL_STUDIO_STG_TYPE_SYSTEM, BX_DOL_STUDIO_STG_CATEGORY_TEMPLATES);
+        $oOptions = new BxTemplStudioOptions(BX_DOL_STUDIO_STG_TYPE_SYSTEM, BX_DOL_STUDIO_STG_CATEGORY_TEMPLATES);
 
+        $this->aPageCss = array_merge($this->aPageCss, $oOptions->getCss());
+        $this->aPageJs = array_merge($this->aPageJs, $oOptions->getJs());
         return BxDolStudioTemplate::getInstance()->parseHtmlByName('designer.html', array(
-            'content' => $oPage->getFormCode(),
+            'content' => $oOptions->getCode(),
             'js_content' => $this->getPageJsCode()
         ));
     }

+ 4 - 2
studio/template/scripts/BxBaseStudioLanguage.php

@@ -34,10 +34,12 @@ class BxBaseStudioLanguage extends BxDolStudioLanguage
 
     protected function getSettings()
     {
-        $oPage = new BxTemplStudioSettings($this->sModule);
+        $oOptions = new BxTemplStudioOptions($this->sModule);
 
+        $this->aPageCss = array_merge($this->aPageCss, $oOptions->getCss());
+        $this->aPageJs = array_merge($this->aPageJs, $oOptions->getJs());
         return BxDolStudioTemplate::getInstance()->parseHtmlByName('language.html', array(
-            'content' => $oPage->getFormCode()
+            'content' => $oOptions->getCode()
         ));
     }
 }

+ 4 - 2
studio/template/scripts/BxBaseStudioModule.php

@@ -90,10 +90,12 @@ class BxBaseStudioModule extends BxDolStudioModule
 
     protected function getSettings()
     {
-        $oPage = new BxTemplStudioSettings($this->sModule);
+        $oOptions = new BxTemplStudioOptions($this->sModule);
 
+        $this->aPageCss = array_merge($this->aPageCss, $oOptions->getCss());
+        $this->aPageJs = array_merge($this->aPageJs, $oOptions->getJs());
         return BxDolStudioTemplate::getInstance()->parseHtmlByName('module.html', array(
-            'content' => $oPage->getFormCode(),
+            'content' => $oOptions->getCode(),
         ));
     }
 

+ 2 - 2
studio/template/scripts/BxBaseStudioModules.php

@@ -17,12 +17,12 @@ class BxBaseStudioModules extends BxDolStudioModules
 
     public function getCss()
     {
-        return array('settings.css', 'module.css');
+        return array('module.css');
     }
 
     public function getJs()
     {
-        return array('jquery.anim.js', 'page.js', 'settings.js', 'module.js');
+        return array('jquery.anim.js', 'page.js', 'module.js');
     }
 
     public function getJsClass()

+ 639 - 0
studio/template/scripts/BxBaseStudioOptions.php

@@ -0,0 +1,639 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxBaseStudioOptions extends BxDolStudioOptions
+{
+    public function __construct($sType = '', $mixedCategory = '', $sMix = '')
+    {
+        parent::__construct($sType, $mixedCategory);
+    }
+
+    public function getCss()
+    {
+        return ['forms.css', 'options.css'];
+    }
+
+    public function getJs()
+    {
+        return ['jquery.form.min.js', 'jquery.webForms.js', 'options.js'];
+    }
+
+    public function getJsObject()
+    {
+        return 'oBxDolStudioOptions';
+    }
+
+    public function getCode($sCategorySelected = '')
+    {
+        $oTemplate = BxDolStudioTemplate::getInstance();
+        $sJsObject = $this->getJsObject();
+
+        $aCategories = [];
+        $iCategories = $this->oDb->getCategories(['type' => 'by_type_name_key_name', 'type_name' => $this->sType, 'category_name' => $this->sCategory, 'hidden' => 0], $aCategories);
+        if($iCategories > 0)
+            $aCategories = array_keys($aCategories);
+
+        $bMix = false;
+        $aOptions2Mixes = [];
+        if($this->bMixes) {
+            if(!empty($this->sMix))
+                $aMixesBrowse = ['type' => 'by_name', 'value' => $this->sMix];
+            else if(is_string($this->sCategory))
+                $aMixesBrowse = ['type' => 'by_type_category', 'mix_type' => $this->sType, 'mix_category' => $this->sCategory, 'active' => 1];
+            else
+                $aMixesBrowse = ['type' => 'by_type', 'value' => $this->sType, 'active' => 1];
+
+            $aMix = array();
+            $this->oDb->getMixes($aMixesBrowse, $aMix, false);
+            if(!empty($aMix) && is_array($aMix)) {
+                $this->aMix = $aMix;
+                $this->sMix = $aMix['name'];
+
+                $bMix = true;
+                $this->oDb->getMixesOptions(['type' => 'by_mix_id_pair_option_value', 'value' => $this->aMix['id']], $aOptions2Mixes, false);
+            }
+            else
+                $this->sMix = BX_DOL_STUDIO_STG_MIX_DEFAULT;
+        }
+
+        $bWrap = count($aCategories) > 1;
+        $aForm = [
+            'form_attrs' => [
+                'id' => 'adm-settings-form',
+                'name' => 'adm-settings-form',
+                'action' => bx_append_url_params($this->sBaseUrl, ['type' => $this->sType]),
+                'method' => 'post',
+                'enctype' => 'multipart/form-data',
+                'target' => 'adm-settings-iframe',
+                'onsubmit' => 'return ' . $sJsObject . '.onSubmit(this)'
+            ],
+            'params' => [
+                'db' => [
+                    'table' => 'sys_options',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'save'
+                ],
+            ],
+            'inputs' => []
+        ];
+
+        if($bMix)
+            $aForm['inputs']['mix_id'] = [
+                'type' => 'hidden',
+                'name' => 'mix_id',
+                'value' => $this->aMix['id'],
+                'db' => [
+                    'pass' => 'Int',
+                ],
+            ];
+
+        foreach($aCategories as $sCategory) {
+            $aFields = [];
+
+            if(empty($sCategory))
+                continue;
+
+            $aCategory = array();
+            $iCategory = $this->oDb->getCategories(['type' => 'by_name', 'value' => $sCategory], $aCategory);
+            if($iCategory != 1)
+                continue;
+
+            $aOptions = array();
+            $iOptions = $this->oDb->getOptions(['type' => 'by_category_id', 'value' => $aCategory['id']], $aOptions);
+
+            foreach($aOptions as $aOption)
+                $aFields[$aOption['name']] = $this->field($aOption, $aOptions2Mixes);
+
+            if($bWrap) {
+                $aCategory['selected'] = $aCategory['name'] == $sCategorySelected;
+                $aFields = $this->header($aCategory, $aFields);
+            }
+
+            $aForm['inputs'] = array_merge($aForm['inputs'], $aFields);
+        }
+
+        $aForm['inputs'] = array_merge(
+            $aForm['inputs'], (!$bWrap ? [] : [
+                'header_save' => [
+                    'type' => 'block_header',
+                ],
+            ]), [
+                'categories' => [
+                    'type' => 'hidden',
+                    'name' => 'categories',
+                    'value' => implode(',', $aCategories),
+                    'db' => [
+                        'pass' => 'Xss',
+                    ],
+                ]
+            ], ($this->isReadOnly() ? [] : [
+                'save' => [
+                    'type' => 'submit',
+                    'name' => 'save',
+                    'value' => _t("_adm_btn_settings_save")
+                ],
+            ])
+        );
+
+        $oForm = new BxTemplStudioFormView($aForm, $oTemplate);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            echo $this->saveChanges($oForm);
+            exit;
+        }
+
+        $bTmplVarsMixes = false;
+        $aTmplVarsMixes = [];
+        if($this->bMixes) {
+            if(is_string($this->sCategory))
+                $aMixesBrowse = ['type' => 'by_type_category', 'mix_type' => $this->sType, 'mix_category' => $this->sCategory];
+            else
+                $aMixesBrowse = ['type' => 'by_type', 'value' => $this->sType];
+
+            $aMixes = [];
+            $this->oDb->getMixes($aMixesBrowse, $aMixes, false);
+            $aMixes = array_merge([['name' => BX_DOL_STUDIO_STG_MIX_DEFAULT, 'title' => _t('_adm_stg_txt_mix_' . BX_DOL_STUDIO_STG_MIX_DEFAULT)]], $aMixes);
+
+            foreach($aMixes as $aMix)
+                $aTmplVarsMixes[] = [
+                    'value' => $aMix['name'],
+                    'title' => $aMix['title'],
+                    'bx_if:show_checked_mix' => [
+                        'condition' => !empty($this->sMix) && $aMix['name'] == $this->sMix,
+                        'content' => []
+                    ]
+                ];
+
+            $bTmplVarsMixes = !empty($aTmplVarsMixes);
+        }
+
+        $bMixSelected = !empty($this->sMix) && !empty($this->aMix);
+
+        $aTmplVarsButton = [];
+        if($bMixSelected)
+            $aTmplVarsButton = [
+                'js_object' => $sJsObject,
+                'id' => $this->aMix['id']
+            ];
+
+        return $oTemplate->parseHtmlByName('options.html', [
+            'base_url' => $this->sBaseUrl,
+            'param_prefix' => $this->sParamPrefix,
+            'js_object' => $sJsObject,
+            'type' => $this->sType,
+            'category' => is_array($this->sCategory) ? json_encode($this->sCategory) : $this->sCategory,
+            'mix' => $this->sMix,
+            'bx_if:show_mixes' => [
+                'condition' => $this->bMixes,
+                'content' => [
+                    'js_object' => $sJsObject,
+                    'bx_if:show_select_mix' => [
+                        'condition' => $bTmplVarsMixes,
+                        'content' => [
+                            'js_object' => $sJsObject,
+                            'bx_repeat:mixes' => $aTmplVarsMixes,
+                        ]
+                    ],
+                    'bx_if:show_publish_mix' => [
+                        'condition' => $bMixSelected && (int)$this->aMix['published'] == 0,
+                        'content' => $aTmplVarsButton
+                    ],
+                    'bx_if:show_hide_mix' => [
+                        'condition' => $bMixSelected && (int)$this->aMix['published'] != 0,
+                        'content' => $aTmplVarsButton
+                    ],
+                    'bx_if:show_export_mix' => [
+                        'condition' => $bMixSelected,
+                        'content' => $aTmplVarsButton
+                    ],
+                    'bx_if:show_delete_mix' => [
+                        'condition' => $bMixSelected,
+                        'content' => $aTmplVarsButton
+                    ],
+                ]
+            ], 
+            'form' => $oForm->getCode()
+        ]);
+    }
+
+    public function getPopupCodeCreateMix()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+    	$sJsObject = $this->getJsObject();
+
+    	$sForm = 'adm-settings-create-mix-form';
+    	$aForm = [
+            'form_attrs' => [
+                'id' => $sForm,
+                'name' => $sForm,
+                'action' => bx_append_url_params($this->sBaseUrl, [$this->sParamPrefix . '_action' => 'create-mix']),
+                'method' => 'post',
+                'enctype' => 'multipart/form-data'
+            ],
+            'params' => [
+                'db' => [
+                    'table' => 'sys_options_mixes',
+                    'key' => 'id',
+                    'uri' => 'name',
+                    'uri_title' => 'title',
+                    'submit_name' => 'save'
+                ],
+            ],
+            'inputs' => [
+            	'type' => [
+                    'type' => 'hidden',
+                    'name' => 'type',
+                    'value' => $this->sType
+                ],
+                'category' => [
+                    'type' => 'hidden',
+                    'name' => 'category',
+                    'value' => is_array($this->sCategory) ? json_encode($this->sCategory) : $this->sCategory,
+                ],
+            	'title' => [
+                    'type' => 'text',
+                    'name' => 'title',
+                    'caption' => _t('_adm_stg_txt_mix_title'),
+                    'info' => _t('_adm_stg_txt_mix_title_inf'),
+                    'value' => '',
+                    'required' => true,
+                    'checker' => [
+                        'func' => 'avail',
+                        'params' => [],
+                        'error' => _t('_adm_stg_txt_mix_title_err')
+                    ],
+                    'db' => ['pass' => 'Xss']
+                ],
+                'duplicate' => [
+                    'type' => 'select',
+                    'name' => 'duplicate',
+                    'caption' => _t('_adm_stg_txt_mix_duplicate'),
+                    'info' => _t('_adm_stg_txt_mix_duplicate_inf'),
+                    'values' => [
+                        ['key' => '', 'value' => _t('_None')]
+                    ],
+                    'value' => '',
+                ],
+                'controls' => [
+                    'type' => 'input_set', [
+                        'type' => 'submit',
+                        'name' => 'save',
+                        'value' => _t('_adm_btn_settings_save'),
+                    ], [
+                        'type' => 'button',
+                        'name' => 'cancel',
+                        'value' => _t('_adm_txt_confirm_cancel'),
+                        'attrs' => [
+                            'class' => 'bx-def-margin-sec-left-auto',
+                            'onclick' => '$(".bx-popup-applied:visible").dolPopupHide()'
+                        ]
+                    ]
+                ]
+            ]
+        ];
+
+        if(is_string($this->sCategory))
+            $aMixesBrowse = ['type' => 'by_type_category', 'mix_type' => $this->sType, 'mix_category' => $this->sCategory];
+    	else
+            $aMixesBrowse = ['type' => 'by_type', 'value' => $this->sType];
+
+        $aMixes = [];
+        $this->oDb->getMixes($aMixesBrowse, $aMixes, false);
+        foreach($aMixes as $aMix)
+            $aForm['inputs']['duplicate']['values'][] = ['key' => $aMix['name'], 'value' => $aMix['title']];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $iId = $oForm->insert([
+                'type' => $this->sType,
+                'category' => is_string($this->sCategory) ? $this->sCategory : '',
+                'name' => $oForm->generateUri()
+            ]);
+
+            if($iId === false)
+                return ['code' => '1', 'message' => _t('_adm_stg_err_cannot_perform')];
+
+            $this->oDb->updateMixes(['active' => 0], [
+                'type' => $this->sType,
+                'category' => is_string($this->sCategory) ? $this->sCategory : '',
+                'active' => 1
+            ]);
+            $this->oDb->updateMixes(['active' => 1], ['id' => $iId]);
+
+            $aDuplicate = [];
+            $this->oDb->getMixes(['type' => 'by_name', 'value' => $oForm->getCleanValue('duplicate')], $aDuplicate, false);
+            if(!empty($aDuplicate) && is_array($aDuplicate)) 
+                $this->oDb->duplicateMixesOptions($aDuplicate['id'], $iId);
+
+            $this->clearCache();
+            return ['eval' => $sJsObject . '.onMixCreate(oData);'];
+        }
+
+        return [
+            'popup' => BxTemplStudioFunctions::getInstance()->popupBox('adm-stg-create-mix-popup', _t('_adm_stg_txt_create_mix_popup'), $oTemplate->parseHtmlByName('options_create_mix.html', [
+                'js_object' => $sJsObject,
+                'form_id' => $sForm,
+                'form' => $oForm->getCode(true),
+            ]))
+        ];
+    }
+
+    public function getPopupCodeImportMix()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+    	$sJsObject = $this->getJsObject();
+
+    	$sForm = 'adm-settings-import-mix-form';
+    	$aForm = [
+            'form_attrs' => [
+                'id' => $sForm,
+                'name' => $sForm,
+                'action' => bx_append_url_params($this->sBaseUrl, [$this->sParamPrefix . '_action' => 'import-mix']),
+                'method' => 'post',
+                'enctype' => 'multipart/form-data'
+            ],
+            'params' => [
+                'db' => [
+                    'table' => 'sys_options_mixes',
+                    'key' => 'id',
+                    'uri' => 'name',
+                    'uri_title' => 'title',
+                    'submit_name' => 'save'
+                ],
+            ],
+            'inputs' => [
+            	'type' => [
+                    'type' => 'hidden',
+                    'name' => 'type',
+                    'value' => $this->sType
+                ],
+                'category' => [
+                    'type' => 'hidden',
+                    'name' => 'category',
+                    'value' => is_array($this->sCategory) ? json_encode($this->sCategory) : $this->sCategory,
+                ],
+            	'file' => [
+                    'type' => 'file',
+                    'name' => 'file',
+                    'caption' => '',
+                    'value' => '',
+                ],
+                'controls' => [
+                    'type' => 'input_set', [
+                        'type' => 'submit',
+                        'name' => 'save',
+                        'value' => _t('_adm_btn_settings_import'),
+                    ], [
+                        'type' => 'button',
+                        'name' => 'cancel',
+                        'value' => _t('_adm_txt_confirm_cancel'),
+                        'attrs' => [
+                            'class' => 'bx-def-margin-sec-left-auto',
+                            'onclick' => '$(".bx-popup-applied:visible").dolPopupHide()'
+                        ]
+                    ]
+                ]
+            ]
+        ];
+
+        $oForm = new BxTemplStudioFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $sError = _t('_adm_stg_err_cannot_perform');
+
+            $aFile = $_FILES['file'];
+            if(empty($aFile['tmp_name']))
+                return ['code' => '1', 'message' => $sError];
+
+            $sFile = $aFile['tmp_name'];
+            $rHandle = @fopen($sFile, "r");
+            if(!$rHandle)
+                return ['code' => '2', 'message' => $sError];
+
+            $sContents = fread($rHandle, filesize($sFile));
+            fclose($rHandle);
+
+            $aContent = json_decode($sContents, true);
+            if(!is_array($aContent) || empty($aContent['mix']) || empty($aContent['options']))
+                return ['code' => '3', 'message' => $sError];
+
+            $aMix = [];
+            $this->oDb->getMixes(['type' => 'by_name', 'value' => $aContent['mix']['name']], $aMix, false);
+            if(!empty($aMix) && is_array($aMix))
+                return ['code' => '4', 'message' => _t('_adm_stg_err_mix_already_exists')];
+
+            $iId = $oForm->insert([
+                'type' => $aContent['mix']['type'],
+                'category' => $aContent['mix']['category'],
+                'name' => $aContent['mix']['name'],
+                'title' => $aContent['mix']['title']
+            ]);
+            if($iId === false)
+                return ['code' => '5', 'message' => $sError];
+
+            foreach($aContent['options'] as $sKey => $sValue)
+                $this->oDb->insertMixesOptions([
+                    'option' => $sKey,
+                    'mix_id' => $iId,
+                    'value' => $sValue
+                ]);
+
+            $this->oDb->updateMixes(['active' => 0], [
+                'type' => $this->sType,
+                'category' => is_string($this->sCategory) ? $this->sCategory : '',
+                'active' => 1
+            ]);
+            $this->oDb->updateMixes(['active' => 1], ['id' => $iId]);
+
+            $this->clearCache();
+            return ['eval' => $sJsObject . '.onMixImport(oData);'];
+        }
+
+        return [
+            'popup' => BxTemplStudioFunctions::getInstance()->popupBox('adm-stg-import-mix-popup', _t('_adm_stg_txt_import_mix_popup'), $oTemplate->parseHtmlByName('options_import_mix.html', [
+                'js_object' => $sJsObject,
+                'form_id' => $sForm,
+                'form' => $oForm->getCode(true),
+            ]))
+        ];
+    }
+
+    protected function header($aCategory, $aFields)
+    {
+        return array_merge([
+                'category_' . $aCategory['id'] . '_beg' => [
+                    'type' => 'block_header',
+                    'name' => 'category_' . $aCategory['id'] . '_beg',
+                    'caption' => _t($aCategory['caption']),
+                    'collapsable' => true,
+                    'collapsed' => !$aCategory['selected']
+                ]
+            ],
+            $aFields
+        );
+    }
+
+    protected function field($aItem, $aItems2Mixes)
+    {
+    	$mixedValue = isset($aItems2Mixes[$aItem['name']]) ? $aItems2Mixes[$aItem['name']] : $aItem['value'];
+
+    	$sMethod = 'getCustomValue' . bx_gen_method_name(trim(str_replace($this->sType, '', $aItem['name']), '_'));
+    	if(method_exists($this, $sMethod))
+    	    $mixedValue = $this->$sMethod($aItem, $mixedValue);
+
+    	$aAttributes = array();
+    	if($this->isReadOnly())
+            $aAttributes = array_merge($aAttributes, [
+                'disabled' => 'disabled'
+            ]);
+
+        $aField = [];
+        switch($aItem['type']) {
+            case 'value':
+                $aField = [
+                    'type' => 'text',
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => $mixedValue,
+                    'attrs' => array_merge($aAttributes, ['readonly' => 'readonly']),
+                    'db' => ['pass' => 'Xss'],
+                ];
+                break;
+            case 'digit':
+                $aField = [
+                    'type' => 'text',
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => $mixedValue,
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'Xss'],
+                ];
+                break;
+            case 'text':
+                $aField = [
+                    'type' => 'textarea',
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => $mixedValue,
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'XssHtml'],
+                ];
+                break;
+            case 'checkbox':
+                $aField = [
+                    'type' => 'checkbox',
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => 'on',
+                    'checked' => $mixedValue == 'on',
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'Xss'],
+                ];
+                break;
+            case 'list':
+            case 'rlist':
+                $aField = [
+                    'type' => 'checkbox_set',
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => !empty($mixedValue) ? explode(',', $mixedValue) : [],
+                    'reverse' => $aItem['type'] == 'rlist',
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'Xss'],
+                ];
+
+                if (BxDolService::isSerializedService($aItem['extra']))
+                    $aField['values'] = BxDolService::callSerialized($aItem['extra']);
+                else
+                    foreach(explode(',', $aItem['extra']) as $sValue)
+                        $aField['values'][$sValue] = $sValue;
+                break;
+            case 'select':
+                $aField = [
+                    'type' => 'select',
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => $mixedValue,
+                    'values' => [],
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'Xss'],
+                ];
+
+                if (BxDolService::isSerializedService($aItem['extra']))
+                    $aField['values'] = BxDolService::callSerialized($aItem['extra']);
+                else
+                    foreach(explode(',', $aItem['extra']) as $sValue)
+                        $aField['values'][] = ['key' => $sValue, 'value' => $sValue];
+                break;
+            case 'file':
+                $aField = [
+                    'type' => 'file',
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => $mixedValue,
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'Xss']
+                ];
+                break;
+            case 'image':
+                //--- Concatenation integer values as strings is required to get unique content id
+                $iContentId = (int)($aItem['id'] . ($this->bMixes && isset($this->aMix['id']) ? $this->aMix['id'] : 0));
+
+                $aField = [
+                    'type' => 'files',
+                    'name' => $aItem['name'],
+                    'storage_object' => $this->sStorage,
+                    'storage_private' => 0, 
+                    'images_transcoder' => $this->sTranscoder,
+                    'uploaders' => ['sys_settings_html5'],
+                    'upload_buttons_titles' => ['HTML5' => _t('_sys_uploader_button_name_single')],
+                    'multiple' => false,
+                    'content_id' => $iContentId,
+                    'ghost_template' => ['inputs' => []],
+                    'caption' => _t($aItem['caption']),
+                    'value' => $mixedValue,
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'Xss']
+                ];
+                break;
+            case 'rgb':
+            case 'rgba':
+                $aField = [
+                    'type' => $aItem['type'],
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => $mixedValue,
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'Xss'],
+                ];
+                break;
+            case 'datetime':
+                $aField = [
+                    'type' => 'datetime',
+                    'name' => $aItem['name'],
+                    'caption' => _t($aItem['caption']),
+                    'value' => $mixedValue,
+                    'attrs' => $aAttributes,
+                    'db' => ['pass' => 'DateTimeUtc'],
+                ];
+                break;
+        }
+
+        return $aField;
+    }
+}
+
+/** @} */

+ 29 - 36
studio/template/scripts/BxBaseStudioPage.php

@@ -10,9 +10,32 @@
 
 class BxBaseStudioPage extends BxDolStudioPage
 {
+    protected $aPageCss;
+    protected $aPageJs;
+    protected $sPageJsClass;
+    protected $sPageJsObject;
+
     function __construct($mixedPageName)
     {
         parent::__construct($mixedPageName);
+
+        $this->aPageCss = [
+            'page.css', 
+            'page-media-tablet.css', 
+            'page-media-desktop.css', 
+            'page_columns.css',
+            'menu_top.css'
+        ];
+
+        $this->aPageJs = [
+            'jquery.anim.js', 
+            'jquery.jfeed.pack.js', 
+            'jquery.dolRSSFeed.js', 
+            'page.js'
+        ];
+
+        $this->sPageJsClass = 'BxDolStudioPage';
+        $this->sPageJsObject = 'oBxDolStudioPage';
     }
 
     public function getPageIndex()
@@ -32,17 +55,17 @@ class BxBaseStudioPage extends BxDolStudioPage
 
     public function getPageJs()
     {
-        return array('jquery.anim.js', 'jquery.jfeed.pack.js', 'jquery.dolRSSFeed.js', 'page.js');
+        return $this->aPageJs;
     }
 
     public function getPageJsClass()
     {
-        return 'BxDolStudioPage';
+        return $this->sPageJsClass;
     }
 
     public function getPageJsObject()
     {
-        return 'oBxDolStudioPage';
+        return $this->sPageJsObject;
     }
 
     public function getPageJsCode($aOptions = array(), $bWrap = true)
@@ -65,13 +88,7 @@ class BxBaseStudioPage extends BxDolStudioPage
 
     public function getPageCss()
     {
-        return array(
-            'page.css', 
-            'page-media-tablet.css', 
-            'page-media-desktop.css', 
-            'page_columns.css',
-            'menu_top.css'
-        );
+        return $this->aPageCss;
     }
 
     public function getPageHeader()
@@ -173,7 +190,7 @@ class BxBaseStudioPage extends BxDolStudioPage
 
     protected function getJsResult($sMessage, $bTranslate = true, $bRedirect = false, $sRedirect = '', $sOnResult = '')
     {
-        return $this->getJsResultBy(array(
+        return bx_get_js_result(array(
             'message' => $sMessage,
             'translate' => $bTranslate,
             'redirect' => $bRedirect === true && !empty($sRedirect) ? $sRedirect : $bRedirect,
@@ -183,31 +200,7 @@ class BxBaseStudioPage extends BxDolStudioPage
 
     protected function getJsResultBy($aParams)
     {
-        $aResult = array();
-
-        if(!empty($aParams['message'])) {
-            $aResult['message'] = $aParams['message'];
-
-            if(!isset($aParams['translate']) || !empty($aParams['translate'])) {
-                $aTrtParams = array($aResult['message']);
-                if(!empty($aParams['translate']) && is_array($aParams['translate']))
-                    $aTrtParams = array_merge($aTrtParams, $aParams['translate']);
-
-                $aResult['message'] = call_user_func_array ('_t', $aTrtParams);
-            }
-        }
-
-        if(isset($aParams['redirect']) && $aParams['redirect'] !== false)
-            $aResult['redirect'] = is_string($aParams['redirect']) ? $aParams['redirect'] : BX_DOL_URL_STUDIO;
-
-        if(!empty($aParams['eval']))
-            $aResult['eval'] = $aParams['eval'];
-
-        $sResult = "window.parent.processJsonData(" . json_encode($aResult) . ");";
-        if(isset($aParams['on_page_load']) && $aParams['on_page_load'] === true)
-            $sResult = "$(document).ready(function() {" . $sResult . "});";
-
-        return BxDolStudioTemplate::getInstance()->_wrapInTagJsCode($sResult);
+        return bx_get_js_result($aParams);
     }
 }
 

+ 14 - 21
studio/template/scripts/BxBaseStudioPolyglot.php

@@ -14,10 +14,15 @@ class BxBaseStudioPolyglot extends BxDolStudioPolyglot
     protected $aMenuItems;
     protected $aGridObjects;
 
-    function __construct($sPage = '')
+    public function __construct($sPage = '')
     {
         parent::__construct($sPage);
 
+        $this->aPageCss = array_merge($this->aPageCss, ['forms.css', 'paginate.css', 'polyglot.css']);
+        $this->aPageJs = array_merge($this->aPageJs, ['polyglot.js']);
+        $this->sPageJsClass = 'BxDolStudioPolyglot';
+        $this->sPageJsObject = 'oBxDolStudioPolyglot';
+        
         $this->sSubpageUrl = BX_DOL_URL_STUDIO . 'polyglot.php?page=';
 
         $this->aMenuItems = array(
@@ -32,23 +37,8 @@ class BxBaseStudioPolyglot extends BxDolStudioPolyglot
             'etemplates' => 'sys_studio_lang_etemplates',
         );
     }
-    function getPageCss()
-    {
-        return array_merge(parent::getPageCss(), array('forms.css', 'paginate.css', 'polyglot.css'));
-    }
-    function getPageJs()
-    {
-        return array_merge(parent::getPageJs(), array('settings.js', 'polyglot.js'));
-    }
-    function getPageJsClass()
-    {
-        return 'BxDolStudioPolyglot';
-    }
-    function getPageJsObject()
-    {
-        return 'oBxDolStudioPolyglot';
-    }
-    function getPageJsCode($aOptions = array(), $bWrap = true)
+
+    public function getPageJsCode($aOptions = array(), $bWrap = true)
     {
         $aOptions = array_merge($aOptions, array(
             'sActionUrl' => BX_DOL_URL_STUDIO . 'polyglot.php'
@@ -56,7 +46,8 @@ class BxBaseStudioPolyglot extends BxDolStudioPolyglot
 
         return parent::getPageJsCode($aOptions, $bWrap);
     }
-    function getPageMenu($aMenu = array(), $aMarkers = array())
+
+    public function getPageMenu($aMenu = array(), $aMarkers = array())
     {
         $sJsObject = $this->getPageJsObject();
 
@@ -75,10 +66,12 @@ class BxBaseStudioPolyglot extends BxDolStudioPolyglot
 
     protected function getSettings()
     {
-        $oPage = new BxTemplStudioSettings(BX_DOL_STUDIO_STG_TYPE_DEFAULT, BX_DOL_STUDIO_STG_CATEGORY_LANGUAGES);
+        $oOptions = new BxTemplStudioOptions(BX_DOL_STUDIO_STG_TYPE_DEFAULT, BX_DOL_STUDIO_STG_CATEGORY_LANGUAGES);
 
+        $this->aPageCss = array_merge($this->aPageCss, $oOptions->getCss());
+        $this->aPageJs = array_merge($this->aPageJs, $oOptions->getJs());
         return BxDolStudioTemplate::getInstance()->parseHtmlByName('polyglot.html', array(
-            'content' => $oPage->getFormCode(),
+            'content' => $oOptions->getCode(),
             'js_content' => ''
         ));
     }

+ 6 - 643
studio/template/scripts/BxBaseStudioSettings.php

@@ -17,12 +17,12 @@ class BxBaseStudioSettings extends BxDolStudioSettings
 
     public function getPageCss()
     {
-        return array_merge(parent::getPageCss(), array('forms.css', 'settings.css'));
+        return array_merge(parent::getPageCss(), $this->oOptions->getCss());
     }
 
     public function getPageJs()
     {
-        return array_merge(parent::getPageJs(), array('jquery.form.min.js', 'jquery.webForms.js', 'settings.js'));
+        return array_merge(parent::getPageJs(), $this->oOptions->getJs());
     }
 
     public function getPageJsObject()
@@ -32,6 +32,8 @@ class BxBaseStudioSettings extends BxDolStudioSettings
 
     public function getPageMenu($aMenu = array(), $aMarkers = array())
     {
+        $sType = $this->oOptions->getType();
+
         $aTypes = $aMenu = array();
         if($this->oDb->getTypes(array('type' => 'all', 'not_in_group' => array(BX_DOL_STUDIO_STG_GROUP_TEMPLATES)), $aTypes) > 0 ) {
             $aTypesGrouped = array();
@@ -45,7 +47,7 @@ class BxBaseStudioSettings extends BxDolStudioSettings
                         'icon' => $this->getMenuIcon($sGroup, $aType),
                         'link' => BX_DOL_URL_STUDIO . 'settings.php?page=' . $aType['name'],
                         'title' => $aType['caption'],
-                        'selected' => $aType['name'] == $this->sType
+                        'selected' => $aType['name'] == $sType
                     );
         }
 
@@ -59,649 +61,10 @@ class BxBaseStudioSettings extends BxDolStudioSettings
             return false;
 
         return $sResult . $this->getBlockCode(array(
-            'content' => $this->getFormCode()
+            'content' => $this->oOptions->getCode()
         ));
     }
 
-    public function getFormCode($sCategorySelected = '')
-    {
-        $oTemplate = BxDolStudioTemplate::getInstance();
-        $sJsObject = $this->getPageJsObject();
-
-        $aCategories = array();
-        $iCategories = $this->oDb->getCategories(array('type' => 'by_type_name_key_name', 'type_name' => $this->sType, 'category_name' => $this->sCategory, 'hidden' => 0), $aCategories);
-        if($iCategories > 0)
-            $aCategories = array_keys($aCategories);
-
-        $bMix = false;
-        $aOptions2Mixes = array();
-        if($this->bMixes) {
-            if(is_string($this->sCategory))
-                $aMixesBrowse = array('type' => 'by_type_category', 'mix_type' => $this->sType, 'mix_category' => $this->sCategory, 'active' => 1);
-            else
-                $aMixesBrowse = array('type' => 'by_type', 'value' => $this->sType, 'active' => 1);
-
-            $aMix = array();
-            $this->oDb->getMixes($aMixesBrowse, $aMix, false);
-
-            $this->sMix = BX_DOL_STUDIO_STG_MIX_DEFAULT; 
-            if(!empty($aMix) && is_array($aMix)) {
-                $this->aMix = $aMix;
-                $this->sMix = $aMix['name'];
-
-                $bMix = true;
-                $this->oDb->getMixesOptions(array('type' => 'by_mix_id_pair_option_value', 'value' => $this->aMix['id']), $aOptions2Mixes, false);
-            }
-        }
-
-        $bWrap = count($aCategories) > 1;
-        $aForm = array(
-            'form_attrs' => array(
-                'id' => 'adm-settings-form',
-                'name' => 'adm-settings-form',
-                'action' => BX_DOL_URL_STUDIO . 'settings.php?page=' . $this->sType,
-                'method' => 'post',
-                'enctype' => 'multipart/form-data',
-                'target' => 'adm-settings-iframe'
-            ),
-            'params' => array(
-                'db' => array(
-                    'table' => 'sys_options',
-                    'key' => 'id',
-                    'uri' => '',
-                    'uri_title' => '',
-                    'submit_name' => 'save'
-                ),
-            ),
-            'inputs' => array()
-        );
-
-        if($bMix)
-            $aForm['inputs']['mix_id'] = array(
-                'type' => 'hidden',
-                'name' => 'mix_id',
-                'value' => $this->aMix['id'],
-                'db' => array (
-                    'pass' => 'Int',
-                ),
-            );
-
-        foreach($aCategories as $sCategory) {
-            $aFields = array();
-
-            if(empty($sCategory))
-                continue;
-
-            $aCategory = array();
-            $iCategory = $this->oDb->getCategories(array('type' => 'by_name', 'value' => $sCategory), $aCategory);
-            if($iCategory != 1)
-                continue;
-
-            $aOptions = array();
-            $iOptions = $this->oDb->getOptions(array('type' => 'by_category_id', 'value' => $aCategory['id']), $aOptions);
-
-            foreach($aOptions as $aOption)
-                $aFields[$aOption['name']] = $this->field($aOption, $aOptions2Mixes);
-
-            if($bWrap) {
-                $aCategory['selected'] = $aCategory['name'] == $sCategorySelected;
-                $aFields = $this->header($aCategory, $aFields);
-            }
-
-            $aForm['inputs'] = array_merge($aForm['inputs'], $aFields);
-        }
-
-        $aForm['inputs'] = array_merge(
-            $aForm['inputs'],
-
-            (!$bWrap ? array() : array(
-                'header_save' => array(
-                    'type' => 'block_header',
-                ),
-            )),
-
-            array(
-                'categories' => array(
-                    'type' => 'hidden',
-                    'name' => 'categories',
-                    'value' => implode(',', $aCategories),
-                    'db' => array (
-                        'pass' => 'Xss',
-                    ),
-                )
-            ),
-
-            ($this->isReadOnly() ? array() : array(
-                'save' => array(
-                    'type' => 'submit',
-                    'name' => 'save',
-                    'value' => _t("_adm_btn_settings_save"),
-                ),
-            ))
-        );
-
-        $oForm = new BxTemplStudioFormView($aForm, $oTemplate);
-        $oForm->initChecker();
-
-        if($oForm->isSubmittedAndValid()) {
-            echo $this->saveChanges($oForm);
-            exit;
-        }
-
-        $bTmplVarsMixes = false;
-        $aTmplVarsMixes = array();
-        if($this->bMixes) {
-            if(is_string($this->sCategory))
-                $aMixesBrowse = array('type' => 'by_type_category', 'mix_type' => $this->sType, 'mix_category' => $this->sCategory);
-            else
-                $aMixesBrowse = array('type' => 'by_type', 'value' => $this->sType);
-
-            $aMixes = array();
-            $this->oDb->getMixes($aMixesBrowse, $aMixes, false);
-            $aMixes = array_merge(array(array('name' => BX_DOL_STUDIO_STG_MIX_DEFAULT, 'title' => _t('_adm_stg_txt_mix_' . BX_DOL_STUDIO_STG_MIX_DEFAULT))), $aMixes);
-
-            foreach($aMixes as $aMix)
-                $aTmplVarsMixes[] = array(
-                    'value' => $aMix['name'],
-                    'title' => $aMix['title'],
-                    'bx_if:show_checked_mix' => array(
-                        'condition' => !empty($this->sMix) && $aMix['name'] == $this->sMix,
-                        'content' => array()
-                    )
-                );
-
-            $bTmplVarsMixes = !empty($aTmplVarsMixes);
-        }
-
-        $bMixSelected = !empty($this->sMix) && !empty($this->aMix);
-
-        $aTmplVarsButton = array();
-        if($bMixSelected)
-            $aTmplVarsButton = array(
-                'js_object' => $sJsObject,
-                'id' => $this->aMix['id']
-            );
-
-        return $oTemplate->parseHtmlByName('settings.html', array(
-            'js_object' => $sJsObject,
-            'type' => $this->sType,
-            'category' => is_array($this->sCategory) ? json_encode($this->sCategory) : $this->sCategory,
-            'mix' => $this->sMix,
-            'bx_if:show_mixes' => array(
-                'condition' => $this->bMixes,
-                'content' => array(
-                    'js_object' => $sJsObject,
-                    'bx_if:show_select_mix' => array(
-                        'condition' => $bTmplVarsMixes,
-                        'content' => array(
-                            'js_object' => $sJsObject,
-                            'bx_repeat:mixes' => $aTmplVarsMixes,
-                        ) 
-                    ),
-                    'bx_if:show_publish_mix' => array(
-                        'condition' => $bMixSelected && (int)$this->aMix['published'] == 0,
-                        'content' => $aTmplVarsButton
-                    ),
-                    'bx_if:show_hide_mix' => array(
-                        'condition' => $bMixSelected && (int)$this->aMix['published'] != 0,
-                        'content' => $aTmplVarsButton
-                    ),
-                    'bx_if:show_export_mix' => array(
-                        'condition' => $bMixSelected,
-                        'content' => $aTmplVarsButton
-                    ),
-                    'bx_if:show_delete_mix' => array(
-                        'condition' => $bMixSelected,
-                        'content' => $aTmplVarsButton
-                    ),
-                )
-            ), 
-            'form' => $oForm->getCode()
-        ));
-    }
-
-    public function getPopupCodeCreateMix()
-    {
-    	$oTemplate = BxDolStudioTemplate::getInstance();
-    	$sJsObject = $this->getPageJsObject();
-
-    	$sForm = 'adm-settings-create-mix-form';
-    	$aForm = array(
-            'form_attrs' => array(
-                'id' => $sForm,
-                'name' => $sForm,
-                'action' => bx_append_url_params(BX_DOL_URL_STUDIO . 'settings.php', array('stg_action' => 'create-mix')),
-                'method' => 'post',
-                'enctype' => 'multipart/form-data'
-            ),
-            'params' => array(
-                'db' => array(
-                    'table' => 'sys_options_mixes',
-                    'key' => 'id',
-                    'uri' => 'name',
-                    'uri_title' => 'title',
-                    'submit_name' => 'save'
-                ),
-            ),
-            'inputs' => array(
-            	'page' => array(
-                    'type' => 'hidden',
-                    'name' => 'page',
-                    'value' => $this->sType
-                ),
-                'category' => array(
-                    'type' => 'hidden',
-                    'name' => 'category',
-                    'value' => is_array($this->sCategory) ? json_encode($this->sCategory) : $this->sCategory,
-                ),
-            	'title' => array(
-                    'type' => 'text',
-                    'name' => 'title',
-                    'caption' => _t('_adm_stg_txt_mix_title'),
-                    'info' => _t('_adm_stg_txt_mix_title_inf'),
-                    'value' => '',
-                    'required' => true,
-                    'checker' => array(
-                        'func' => 'avail',
-                        'params' => array(),
-                        'error' => _t('_adm_stg_txt_mix_title_err')
-                    ),
-                    'db' => array (
-                        'pass' => 'Xss',
-                    )
-                ),
-                'duplicate' => array(
-                    'type' => 'select',
-                    'name' => 'duplicate',
-                    'caption' => _t('_adm_stg_txt_mix_duplicate'),
-                    'info' => _t('_adm_stg_txt_mix_duplicate_inf'),
-                    'values' => array(
-                        array('key' => '', 'value' => _t('_None'))
-                    ),
-                    'value' => '',
-                ),
-                'controls' => array(
-                    'type' => 'input_set',
-                    array(
-                        'type' => 'submit',
-                        'name' => 'save',
-                        'value' => _t('_adm_btn_settings_save'),
-                    ),
-                    array(
-                        'type' => 'button',
-                        'name' => 'cancel',
-                        'value' => _t('_adm_txt_confirm_cancel'),
-                        'attrs' => array(
-                            'class' => 'bx-def-margin-sec-left-auto',
-                            'onclick' => '$(".bx-popup-applied:visible").dolPopupHide()'
-                        )
-                    )
-                )
-            )
-        );
-
-        if(is_string($this->sCategory))
-            $aMixesBrowse = array('type' => 'by_type_category', 'mix_type' => $this->sType, 'mix_category' => $this->sCategory);
-    	else
-            $aMixesBrowse = array('type' => 'by_type', 'value' => $this->sType);
-
-        $aMixes = array();
-        $this->oDb->getMixes($aMixesBrowse, $aMixes, false);
-        foreach($aMixes as $aMix)
-            $aForm['inputs']['duplicate']['values'][] = array('key' => $aMix['name'], 'value' => $aMix['title']);
-
-        $oForm = new BxTemplStudioFormView($aForm);
-        $oForm->initChecker();
-
-        if($oForm->isSubmittedAndValid()) {
-            $iId = $oForm->insert(array(
-                'type' => $this->sType,
-                'category' => is_string($this->sCategory) ? $this->sCategory : '',
-                'name' => $oForm->generateUri()
-            ));
-
-            if($iId === false)
-                return array('code' => '1', 'message' => _t('_adm_stg_err_cannot_perform'));
-
-            $this->oDb->updateMixes(array('active' => 0), array(
-                'type' => $this->sType,
-                'category' => is_string($this->sCategory) ? $this->sCategory : '',
-                'active' => 1
-            ));
-            $this->oDb->updateMixes(array('active' => 1), array('id' => $iId));
-
-            $aDuplicate = array();
-            $this->oDb->getMixes(array('type' => 'by_name', 'value' => $oForm->getCleanValue('duplicate')), $aDuplicate, false);
-            if(!empty($aDuplicate) && is_array($aDuplicate)) 
-                $this->oDb->duplicateMixesOptions($aDuplicate['id'], $iId);
-
-            $this->clearCache();
-            return array(
-                'eval' => $sJsObject . '.onMixCreate(oData);'
-            );
-        }
-
-        return array(
-            'popup' => BxTemplStudioFunctions::getInstance()->popupBox('adm-stg-create-mix-popup', _t('_adm_stg_txt_create_mix_popup'), $oTemplate->parseHtmlByName('stg_create_mix.html', array(
-                'js_object' => $sJsObject,
-                'form_id' => $sForm,
-                'form' => $oForm->getCode(true),
-            )))
-        );
-    }
-
-    public function getPopupCodeImportMix()
-    {
-    	$oTemplate = BxDolStudioTemplate::getInstance();
-    	$sJsObject = $this->getPageJsObject();
-
-    	$sForm = 'adm-settings-import-mix-form';
-    	$aForm = array(
-            'form_attrs' => array(
-                'id' => $sForm,
-                'name' => $sForm,
-                'action' => bx_append_url_params(BX_DOL_URL_STUDIO . 'settings.php', array('stg_action' => 'import-mix')),
-                'method' => 'post',
-                'enctype' => 'multipart/form-data'
-            ),
-            'params' => array(
-                'db' => array(
-                    'table' => 'sys_options_mixes',
-                    'key' => 'id',
-                    'uri' => 'name',
-                    'uri_title' => 'title',
-                    'submit_name' => 'save'
-                ),
-            ),
-            'inputs' => array(
-            	'page' => array(
-            		'type' => 'hidden',
-                    'name' => 'page',
-                    'value' => $this->sType
-				),
-				'category' => array(
-            		'type' => 'hidden',
-                    'name' => 'category',
-                    'value' => is_array($this->sCategory) ? json_encode($this->sCategory) : $this->sCategory,
-				),
-            	'file' => array(
-            		'type' => 'file',
-                    'name' => 'file',
-                    'caption' => '',
-                    'value' => '',
-				),
-				'controls' => array(
-					'type' => 'input_set',
-	            	array(
-	                    'type' => 'submit',
-	                    'name' => 'save',
-	                    'value' => _t('_adm_btn_settings_import'),
-	                ),
-	                array(
-	                    'type' => 'button',
-	                    'name' => 'cancel',
-	                    'value' => _t('_adm_txt_confirm_cancel'),
-	                	'attrs' => array(
-	                		'class' => 'bx-def-margin-sec-left-auto',
-	                		'onclick' => '$(".bx-popup-applied:visible").dolPopupHide()'
-	                	)
-	                )
-				)
-            )
-        );
-
-        $oForm = new BxTemplStudioFormView($aForm);
-        $oForm->initChecker();
-
-        if($oForm->isSubmittedAndValid()) {
-            $sError = _t('_adm_stg_err_cannot_perform');
-
-            $aFile = $_FILES['file'];
-            if(empty($aFile['tmp_name']))
-                return array('code' => '1', 'message' => $sError);
-
-            $sFile = $aFile['tmp_name'];
-            $rHandle = @fopen($sFile, "r");
-            if(!$rHandle)
-                return array('code' => '2', 'message' => $sError);
-
-            $sContents = fread($rHandle, filesize($sFile));
-            fclose($rHandle);
-
-            $aContent = json_decode($sContents, true);
-            if(!is_array($aContent) || empty($aContent['mix']) || empty($aContent['options']))
-                return array('code' => '3', 'message' => $sError);
-
-            $aMix = array();
-            $this->oDb->getMixes(array('type' => 'by_name', 'value' => $aContent['mix']['name']), $aMix, false);
-            if(!empty($aMix) && is_array($aMix))
-                return array('code' => '4', 'message' => _t('_adm_stg_err_mix_already_exists'));
-
-            $iId = $oForm->insert(array(
-                'type' => $aContent['mix']['type'],
-                'category' => $aContent['mix']['category'],
-                'name' => $aContent['mix']['name'],
-                'title' => $aContent['mix']['title']
-            ));
-            if($iId === false)
-                return array('code' => '5', 'message' => $sError); 
-
-            foreach($aContent['options'] as $sKey => $sValue)
-                $this->oDb->insertMixesOptions(array(
-                    'option' => $sKey,
-                    'mix_id' => $iId,
-                    'value' => $sValue
-                ));
-
-            $this->oDb->updateMixes(array('active' => 0), array(
-                'type' => $this->sType,
-                'category' => is_string($this->sCategory) ? $this->sCategory : '',
-                'active' => 1
-            ));
-            $this->oDb->updateMixes(array('active' => 1), array('id' => $iId));
-
-            $this->clearCache();
-            return array(
-                'eval' => $sJsObject . '.onMixImport(oData);'
-            );
-        }
-
-        return array(
-            'popup' => BxTemplStudioFunctions::getInstance()->popupBox('adm-stg-import-mix-popup', _t('_adm_stg_txt_import_mix_popup'), $oTemplate->parseHtmlByName('stg_import_mix.html', array(
-                'js_object' => $sJsObject,
-                'form_id' => $sForm,
-                'form' => $oForm->getCode(true),
-            )))
-        );
-    }
-
-    protected function header($aCategory, $aFields)
-    {
-        return array_merge(
-            array(
-                'category_' . $aCategory['id'] . '_beg' => array(
-                    'type' => 'block_header',
-                    'name' => 'category_' . $aCategory['id'] . '_beg',
-                    'caption' => _t($aCategory['caption']),
-                    'collapsable' => true,
-                    'collapsed' => !$aCategory['selected']
-                )
-            ),
-            $aFields);
-    }
-
-    protected function field($aItem, $aItems2Mixes)
-    {
-    	$mixedValue = isset($aItems2Mixes[$aItem['name']]) ? $aItems2Mixes[$aItem['name']] : $aItem['value'];
-
-    	$sMethod = 'getCustomValue' . bx_gen_method_name(trim(str_replace($this->sType, '', $aItem['name']), '_'));
-    	if(method_exists($this, $sMethod))
-    	    $mixedValue = $this->$sMethod($aItem, $mixedValue);
-
-    	$aAttributes = array();
-    	if($this->isReadOnly())
-            $aAttributes = array_merge($aAttributes, array(
-                'disabled' => 'disabled'
-            ));
-
-        $aField = array();
-        switch($aItem['type']) {
-            case 'value':
-                $aField = array(
-                    'type' => 'text',
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => $mixedValue,
-                	'attrs' => array_merge($aAttributes, array(
-                    	'readonly' => 'readonly'
-                    )),
-                    'db' => array (
-                        'pass' => 'Xss',
-                    ),
-                );
-                break;
-            case 'digit':
-                $aField = array(
-                    'type' => 'text',
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => $mixedValue,
-                	'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'Xss',
-                    ),
-                );
-                break;
-            case 'text':
-                $aField = array(
-                    'type' => 'textarea',
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => $mixedValue,
-                	'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'XssHtml',
-                    ),
-                );
-                break;
-            case 'checkbox':
-                $aField = array(
-                    'type' => 'checkbox',
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => 'on',
-                    'checked' => $mixedValue == 'on',
-                	'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'Xss',
-                    ),
-                );
-                break;
-            case 'list':
-            case 'rlist':
-                $aField = array(
-                    'type' => 'checkbox_set',
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => !empty($mixedValue) ? explode(',', $mixedValue) : array(),
-                	'reverse' => $aItem['type'] == 'rlist',
-                	'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'Xss',
-                    ),
-                );
-
-                if (BxDolService::isSerializedService($aItem['extra']))
-                    $aField['values'] = BxDolService::callSerialized($aItem['extra']);
-                else
-                    foreach(explode(',', $aItem['extra']) as $sValue)
-                        $aField['values'][$sValue] = $sValue;
-                break;
-            case 'select':
-                $aField = array(
-                    'type' => 'select',
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => $mixedValue,
-                    'values' => array(),
-                	'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'Xss',
-                    ),
-                );
-
-                if (BxDolService::isSerializedService($aItem['extra']))
-                    $aField['values'] = BxDolService::callSerialized($aItem['extra']);
-                else
-                    foreach(explode(',', $aItem['extra']) as $sValue)
-                        $aField['values'][] = array('key' => $sValue, 'value' => $sValue);
-                break;
-            case 'file':
-                $aField = array(
-                    'type' => 'file',
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => $mixedValue,
-                	'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'Xss'
-                    )
-                );
-                break;
-            case 'image':
-                //--- Concatenation integer values as strings is required to get unique content id
-                $iContentId = (int)($aItem['id'] . ($this->bMixes && isset($this->aMix['id']) ? $this->aMix['id'] : 0));
-
-                $aField = array(
-                    'type' => 'files',
-                    'name' => $aItem['name'],
-                    'storage_object' => $this->sStorage,
-                    'storage_private' => 0, 
-                    'images_transcoder' => $this->sTranscoder,
-                    'uploaders' => array('sys_settings_html5'),
-                    'upload_buttons_titles' => array('HTML5' => _t('_sys_uploader_button_name_single')),
-                    'multiple' => false,
-                    'content_id' => $iContentId,
-                    'ghost_template' => array(
-                        'inputs' => array(),
-                    ),
-                    'caption' => _t($aItem['caption']),
-                    'value' => $mixedValue,
-                    'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'Xss'
-                    )
-                );
-                break;
-            case 'rgb':
-            case 'rgba':
-                $aField = array(
-                    'type' => $aItem['type'],
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => $mixedValue,
-                    'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'Xss',
-                    ),
-                );
-                break;
-            case 'datetime':
-                $aField = array(
-                    'type' => 'datetime',
-                    'name' => $aItem['name'],
-                    'caption' => _t($aItem['caption']),
-                    'value' => $mixedValue,
-                    'attrs' => $aAttributes,
-                    'db' => array (
-                        'pass' => 'DateTimeUtc',
-                    ),
-                );
-                break;
-        }
-        return $aField;
-    }
-
     protected function getMenuIcon($sGroup, &$aType)
     {
         if(empty($aType['icon']) || ($sUrl = BxDolStudioTemplate::getInstance()->getIconUrl($aType['icon'])) == "")

+ 19 - 0
studio/template/scripts_templ/BxTemplStudioOptions.php

@@ -0,0 +1,19 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudioView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+defined('BX_DOL') or die('hack attempt');
+
+class BxTemplStudioOptions extends BxBaseStudioOptions
+{
+    function __construct($sType = '', $sCategory = '', $sMix = '')
+    {
+        parent::__construct($sType, $sCategory, $sMix);
+    }
+}
+/** @} */