diff --git a/datamodels/2.x/itop-config/config.php b/datamodels/2.x/itop-config/config.php index 69ca685cc..64a3004d2 100644 --- a/datamodels/2.x/itop-config/config.php +++ b/datamodels/2.x/itop-config/config.php @@ -5,7 +5,7 @@ */ use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory; -use Combodo\iTop\Application\WebPage\iTopConfigEditorPage; +use Combodo\iTop\Config\Controller\ConfigEditorController; use Combodo\iTop\Config\Validator\iTopConfigValidator; require_once(APPROOT.'application/startup.inc.php'); @@ -17,98 +17,7 @@ LoginWebPage::DoLogin(); // Check user rights and prompt if needed ApplicationMenu::CheckMenuIdEnabled('ConfigEditor'); -$oP = new iTopConfigEditorPage(); +$oLogCenterController = new ConfigEditorController(); +$oLogCenterController->SetDefaultOperation('Edit'); +$oLogCenterController->HandleOperation(); - -try { - $sOperation = utils::ReadParam('operation', ''); - - - if (MetaModel::GetConfig()->Get('demo_mode')) { - throw new Exception(Dict::S('config-not-allowed-in-demo'), iTopConfigValidator::CONFIG_INFO); - } - - if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled') { - throw new Exception(Dict::S('config-interactive-not-allowed'), iTopConfigValidator::CONFIG_WARNING); - } - - $sConfigFile = APPROOT.'conf/'.utils::GetCurrentEnvironment().'/config-itop.php'; - - $sConfigContent = file_get_contents($sConfigFile); - $sConfigChecksum = md5($sConfigContent); - $sConfig = str_replace("\r\n", "\n", $sConfigContent); - $sOriginalConfig = $sConfig; - - if (!empty($sOperation)) { - $sConfig = utils::ReadParam('new_config', '', false, 'raw_data'); - } - - try { - if ($sOperation == 'revert') { - throw new Exception(Dict::S('config-reverted'), iTopConfigValidator::CONFIG_WARNING); - } - - if ($sOperation == 'save') { - $sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); - if (!utils::IsTransactionValid($sTransactionId, true)) { - throw new Exception(Dict::S('config-error-transaction'), iTopConfigValidator::CONFIG_ERROR); - } - - $sChecksum = utils::ReadParam('checksum'); - if ($sChecksum !== $sConfigChecksum) { - throw new Exception(Dict::S('config-error-file-changed'), iTopConfigValidator::CONFIG_ERROR); - } - - if ($sConfig === $sOriginalConfig) { - throw new Exception(Dict::S('config-no-change'), iTopConfigValidator::CONFIG_INFO); - } - $oValidator = new iTopConfigValidator(); - $oValidator->Validate($sConfig);// throws exceptions - - @chmod($sConfigFile, 0770); // Allow overwriting the file - $sTmpFile = tempnam(SetupUtils::GetTmpDir(), 'itop-cfg-'); - // Don't write the file as-is since it would allow to inject any kind of PHP code. - // Instead, write the interpreted version of the file - // Note: - // The actual raw PHP code will anyhow be interpreted exactly twice: once in TestConfig() above - // and a second time during the load of the Config object below. - // If you are really concerned about an iTop administrator crafting some malicious - // PHP code inside the config file, then turn off the interactive configuration - // editor by adding the configuration parameter: - // 'itop-config' => array( - // 'config_editor' => 'disabled', - // ) - file_put_contents($sTmpFile, $sConfig); - $oTempConfig = new Config($sTmpFile, true); - $oTempConfig->WriteToFile($sConfigFile); - @unlink($sTmpFile); - @chmod($sConfigFile, 0440); // Read-only - - if ($oValidator->DBPasswordIsOk($oTempConfig->Get('db_pwd'))) { - $oAlert = AlertUIBlockFactory::MakeForSuccess('', Dict::S('config-saved')); - } else { - $oAlert = AlertUIBlockFactory::MakeForInformation('', Dict::S('config-saved-warning-db-password')); - } - $oP->AddUiBlock($oAlert); - - $oP->CheckAsyncTasksRetryConfig($oTempConfig); - - // Read the config from disk after save - $sConfigContent = file_get_contents($sConfigFile); - $sConfigChecksum = md5($sConfigContent); - $sConfig = str_replace("\r\n", "\n", $sConfigContent); - $sOriginalConfig = $sConfig; - } - } - catch (Exception $e) { - $oP->AddAlertFromException($e); - } - - $oP->AddEditor($sOriginalConfig, $sOriginalConfig); - - -} catch (Exception $e) { - $oP->AddAlertFromException($e); -} - -$oP->output(); diff --git a/datamodels/2.x/itop-config/module.itop-config.php b/datamodels/2.x/itop-config/module.itop-config.php index a57c4f0af..70c2882a6 100644 --- a/datamodels/2.x/itop-config/module.itop-config.php +++ b/datamodels/2.x/itop-config/module.itop-config.php @@ -23,6 +23,7 @@ SetupWebPage::AddModule( 'src/Validator/iTopConfigAstValidator.php', 'src/Validator/iTopConfigSyntaxValidator.php', 'src/Validator/iTopConfigValidator.php', + 'src/Controller/ConfigEditorController.php', ), 'webservice' => array(), 'dictionary' => array( diff --git a/datamodels/2.x/itop-config/src/Controller/ConfigEditorController.php b/datamodels/2.x/itop-config/src/Controller/ConfigEditorController.php new file mode 100644 index 000000000..e2d37268e --- /dev/null +++ b/datamodels/2.x/itop-config/src/Controller/ConfigEditorController.php @@ -0,0 +1,173 @@ +Get('demo_mode')) { + throw new Exception(Dict::S('config-not-allowed-in-demo'), iTopConfigValidator::CONFIG_INFO); + } + + if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled') { + throw new Exception(Dict::S('config-interactive-not-allowed'), iTopConfigValidator::CONFIG_WARNING); + } + + $sConfigFile = APPROOT.'conf/'.utils::GetCurrentEnvironment().'/config-itop.php'; + + $sCurrentConfig = file_get_contents($sConfigFile); + $sConfigChecksum = md5($sCurrentConfig); + + try { + if ($sOperation == 'revert') { + $this->AddAlert(Dict::S('config-reverted'), iTopConfigValidator::CONFIG_WARNING); + } + else if ($sOperation == 'save') { + $sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); + if (!utils::IsTransactionValid($sTransactionId, true)) { + throw new Exception(Dict::S('config-error-transaction'), iTopConfigValidator::CONFIG_ERROR); + } + $sChecksum = utils::ReadParam('checksum'); + if ($sChecksum !== $sConfigChecksum) { + throw new Exception(Dict::S('config-error-file-changed'), iTopConfigValidator::CONFIG_ERROR); + } + + $sNewConfig = utils::ReadParam('new_config', '', false, 'raw_data'); + $sNewConfig = str_replace("\r\n", "\n", $sNewConfig); + if ($sNewConfig === $sCurrentConfig) { + throw new Exception(Dict::S('config-no-change'), iTopConfigValidator::CONFIG_INFO); + } + $oValidator = new iTopConfigValidator(); + $sNewConfig = str_replace("\r\n", "\n", $sNewConfig); + + $oValidator->Validate($sNewConfig);// throws exceptions + + @chmod($sConfigFile, 0770); // Allow overwriting the file + $sTmpFile = tempnam(SetupUtils::GetTmpDir(), 'itop-cfg-'); + // Don't write the file as-is since it would allow to inject any kind of PHP code. + // Instead, write the interpreted version of the file + // Note: + // The actual raw PHP code will anyhow be interpreted exactly twice: once in TestConfig() above + // and a second time during the load of the Config object below. + // If you are really concerned about an iTop administrator crafting some malicious + // PHP code inside the config file, then turn off the interactive configuration + // editor by adding the configuration parameter: + // 'itop-config' => array( + // 'config_editor' => 'disabled', + // ) + file_put_contents($sTmpFile, $sNewConfig); + $oTempConfig = new Config($sTmpFile, true); + $oTempConfig->WriteToFile($sConfigFile); + @unlink($sTmpFile); + @chmod($sConfigFile, 0440); // Read-only + + if ($oValidator->DBPasswordIsOk($oTempConfig->Get('db_pwd'))) { + $this->AddAlert(Dict::S('config-saved'), iTopConfigValidator::CONFIG_SUCCESS); + } else { + $this->AddAlert(Dict::S('config-saved-warning-db-password'), iTopConfigValidator::CONFIG_INFO); + } + + $this->AddAlert($oValidator->CheckAsyncTasksRetryConfig($oTempConfig), iTopConfigValidator::CONFIG_WARNING); + + + // Read the config from disk after save + $sCurrentConfig = file_get_contents($sConfigFile); + $sConfigChecksum = md5($sCurrentConfig); + } + } + catch (Exception $e) { + $this->AddAlertFromException($e); + } + + $this->AddAceScripts(); + } + catch (Exception $e) { + $bShowEditor = false; + $this->AddAlertFromException($e); + } + + // display page + $this->DisplayPage([ + 'aErrors' => $this->aErrors, + 'aWarnings' => $this->aWarnings, + 'aNotices' => $this->aInfo, + 'aSuccesses' => $this->aSuccesses, + 'bShowEditor' => $bShowEditor, + 'sTransactionId' => utils::GetNewTransactionId(), + 'sChecksum' => $sConfigChecksum, + 'sPrevConfig' => $sCurrentConfig, + 'sNewConfig' => $sCurrentConfig, + ]); + } + + /** + * @return void + * @throws \Exception + */ + public function AddAceScripts(): void { + $sAceDir = 'node_modules/ace-builds/src-min/'; + $this->AddLinkedScript(\utils::GetAbsoluteUrlAppRoot().$sAceDir.'ace.js'); + $this->AddLinkedScript(\utils::GetAbsoluteUrlAppRoot().$sAceDir.'mode-php.js'); + $this->AddLinkedScript(\utils::GetAbsoluteUrlAppRoot().$sAceDir.'theme-eclipse.js'); + $this->AddLinkedScript(\utils::GetAbsoluteUrlAppRoot().$sAceDir.'ext-searchbox.js'); + } + + + public function AddAlertFromException(Exception $e): void { + $this->AddAlert($e->getMessage(), $e->getCode()); + } + + public function AddAlert(array|string $sMessage, $iLevel): void { + if (is_array($sMessage)) { + foreach ($sMessage as $sSingleMessage) { + $this->AddAlert($sSingleMessage, $iLevel); + } + return; + } + switch ($iLevel) { + case iTopConfigValidator::CONFIG_SUCCESS : + $this->aSuccesses[] = $sMessage; + break; + case iTopConfigValidator::CONFIG_WARNING : + $this->aWarnings[] = $sMessage; + break; + case iTopConfigValidator::CONFIG_INFO : + $this->aInfo[] = $sMessage; + break; + default : + $this->aErrors[] = $sMessage; + }; + } + +} \ No newline at end of file diff --git a/datamodels/2.x/itop-config/src/Validator/iTopConfigValidator.php b/datamodels/2.x/itop-config/src/Validator/iTopConfigValidator.php index f0c42fd07..c7165c861 100644 --- a/datamodels/2.x/itop-config/src/Validator/iTopConfigValidator.php +++ b/datamodels/2.x/itop-config/src/Validator/iTopConfigValidator.php @@ -2,10 +2,14 @@ namespace Combodo\iTop\Config\Validator; +use AsyncTask; +use ReflectionClass; + class iTopConfigValidator { const CONFIG_ERROR = 0; const CONFIG_WARNING = 1; const CONFIG_INFO = 2; + const CONFIG_SUCCESS = 3; /** * @param $sRawConfig @@ -32,4 +36,24 @@ class iTopConfigValidator { return true; } + + + public function CheckAsyncTasksRetryConfig(\Config $oTempConfig): array + { + $aWarnings = []; + foreach (get_declared_classes() as $sPHPClass) { + $oRefClass = new ReflectionClass($sPHPClass); + if ($oRefClass->isSubclassOf('AsyncTask') && !$oRefClass->isAbstract()) { + $aMessages = AsyncTask::CheckRetryConfig($oTempConfig, $oRefClass->getName()); + + if (count($aMessages) !== 0) { + foreach ($aMessages as $sMessage) { + $aWarnings[] = $sMessage; + } + } + } + } + return $aWarnings; + + } } \ No newline at end of file diff --git a/datamodels/2.x/itop-config/templates/Edit.html.twig b/datamodels/2.x/itop-config/templates/Edit.html.twig new file mode 100644 index 000000000..5fedcd02a --- /dev/null +++ b/datamodels/2.x/itop-config/templates/Edit.html.twig @@ -0,0 +1,32 @@ +{% for sError in aErrors %} + {% UIAlert ForDanger{sTitle: sError} %}{% EndUIAlert %} +{% endfor %} +{% for sWarning in aWarnings %} + {% UIAlert ForWarning{sTitle: sWarning} %}{% EndUIAlert %} +{% endfor %} +{% for sNotice in aNotices %} + {% UIAlert ForInformation{sTitle: sNotice} %}{% EndUIAlert %} +{% endfor %} +{% for sSuccess in aSuccesses %} + {% UIAlert ForSuccess{sTitle: sSuccess} %}{% EndUIAlert %} +{% endfor %} + +{% UITitle ForPage {'sTitle':'config-edit-title'|dict_s} %}{% EndUITitle %} +{# +{% UIButton ForPrimaryAction {'sLabel':'SysInfo:UI:DownloadReport'|dict_s, 'bIsSubmit':true} %} +{% UIInput ForHidden {sName:'operation', sValue:'SysInfoReport'} %} +#} +{% if bShowEditor %} +
{{ 'config-edit-intro'|dict_s }}
+ {% UIForm Standard {} %} + {% UIButton ForCancel { 'sLabel':'config-cancel'|dict_s, 'bIsSubmit':true, 'sId':'cancel_button', 'sName':'edit_operation', 'sValue': 'revert'} %} + {% UIButton ForPrimaryAction {'sLabel':'config-apply'|dict_s, 'bIsSubmit':true, 'sId':'submit_button', 'sName':'edit_operation', 'sValue': 'save' } %} + + {% UIInput ForHidden {'sName':'transaction_id', 'sValue':sTransactionId} %} + {% UIInput ForHidden {'sName':'checksum', 'sValue':sChecksum} %} + {% UIInput ForHidden {'sName':'prev_config', 'sValue':sPrevConfig} %} + {% UIInput ForHidden {'sName':'new_config', 'sValue':sNewConfig} %} + + + {% EndUIForm %} +{% endif %} \ No newline at end of file diff --git a/datamodels/2.x/itop-config/templates/Edit.ready.js.twig b/datamodels/2.x/itop-config/templates/Edit.ready.js.twig new file mode 100644 index 000000000..343d22338 --- /dev/null +++ b/datamodels/2.x/itop-config/templates/Edit.ready.js.twig @@ -0,0 +1,135 @@ +var EditorUtils = (function() { + var STORAGE_RANGE_KEY = 'cfgEditorRange'; + var STORAGE_LINE_KEY = 'cfgEditorFirstline'; + var _editorSavedRange = null; + var _editorSavedFirstLine = null; + + var saveEditorDisplay = function(editor) { + _initObjectValues(editor); + _persistObjectValues(); + }; + + var _initObjectValues = function(editor) { + _editorSavedRange = editor.getSelectionRange(); + _editorSavedFirstLine = editor.renderer.getFirstVisibleRow(); + }; + + var _persistObjectValues = function() { + sessionStorage.setItem(EditorUtils.STORAGE_RANGE_KEY, JSON.stringify(_editorSavedRange)); + sessionStorage.setItem(EditorUtils.STORAGE_LINE_KEY, _editorSavedFirstLine); + }; + + var restoreEditorDisplay = function(editor) { + _restoreObjectValues(); + _setEditorDisplay(editor); + }; + + var _restoreObjectValues = function() { + if ((sessionStorage.getItem(STORAGE_RANGE_KEY) == null) + || (sessionStorage.getItem(STORAGE_LINE_KEY) == null)) { + return; + } + + _editorSavedRange = JSON.parse(sessionStorage.getItem(EditorUtils.STORAGE_RANGE_KEY)); + _editorSavedFirstLine = sessionStorage.getItem(EditorUtils.STORAGE_LINE_KEY); + sessionStorage.removeItem(STORAGE_RANGE_KEY); + sessionStorage.removeItem(STORAGE_LINE_KEY); + }; + + var _setEditorDisplay = function(editor) { + if ((_editorSavedRange == null) || (_editorSavedFirstLine == null)) { + return; + } + + editor.selection.setRange(_editorSavedRange); + editor.renderer.scrollToRow(_editorSavedFirstLine); + }; + + var getEditorForm = function(editor) { + var editorContainer = $(editor.container); + return editorContainer.closest("form"); + }; + + var updateConfigEditorButtonState = function(editor) { + var isSameContent = (editor.getValue() == $('#prev_config').val()); + var hasNoError = $.isEmptyObject(editor.getSession().getAnnotations()); + $('#cancel_button').prop('disabled', isSameContent); + $('#submit_button').prop('disabled', isSameContent || !hasNoError); + }; + + return { + STORAGE_RANGE_KEY: STORAGE_RANGE_KEY, + STORAGE_LINE_KEY : STORAGE_LINE_KEY, + saveEditorDisplay : saveEditorDisplay, + restoreEditorDisplay : restoreEditorDisplay, + getEditorForm : getEditorForm, + updateConfigEditorButtonState : updateConfigEditorButtonState + }; +})(); + + + + +var editor = ace.edit("new_config"); + +var configurationSource = $('input[name="new_config"]'); +editor.getSession().setValue(configurationSource.val()); + +editor.getSession().on('change', function() +{ + configurationSource.val(editor.getSession().getValue()); + EditorUtils.updateConfigEditorButtonState(editor); +}); +editor.getSession().on("changeAnnotation", function() +{ + EditorUtils.updateConfigEditorButtonState(editor); +}); + +editor.setTheme("ace/theme/eclipse"); +editor.getSession().setMode("ace/mode/php"); +editor.commands.addCommand({ + name: 'save', + bindKey: {win: "Ctrl-S", "mac": "Cmd-S"}, + exec: function(editor) { + var editorForm = EditorUtils.getEditorForm(editor); + var submitButton = $('#submit_button'); + + if (submitButton.is(":enabled")) { + editorForm.trigger('submit'); + } + } +}); + + +var editorForm = EditorUtils.getEditorForm(editor); +editorForm.on('submit', function() { + EditorUtils.saveEditorDisplay(editor); +}); + + +EditorUtils.restoreEditorDisplay(editor); +editor.focus(); + + //$sConfirmCancel = addslashes(Dict::S('config-confirm-cancel')); +function ResetConfig() +{ + +} + +const repositionEditor = () => { + let oSubmitButton = document.getElementById('submit_button'); + let iBottomPosition = oSubmitButton.offsetTop + oSubmitButton.offsetHeight + 10; + document.getElementById('new_config').style.top = iBottomPosition+"px"; +}; +repositionEditor(); + +document.getElementById('ibo-main-content').addEventListener('click',repositionEditor); + +document.getElementById('cancel_button').onclick = ()=>{ + $("#operation").attr('value', 'revert'); + if (confirm({{ 'config-confirm-cancel'|dict_s|json_encode|raw }})) { + $('input[name="new_config"]').val($('input[name="prev_config"]').val()); + return true; + } + return false; +}; \ No newline at end of file diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 6b5cb0dff..eccb877e4 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -386,7 +386,6 @@ return array( 'Combodo\\iTop\\Application\\WebPage\\WebPage' => $baseDir . '/sources/Application/WebPage/WebPage.php', 'Combodo\\iTop\\Application\\WebPage\\XMLPage' => $baseDir . '/sources/Application/WebPage/XMLPage.php', 'Combodo\\iTop\\Application\\WebPage\\iTabbedPage' => $baseDir . '/sources/Application/WebPage/iTabbedPage.php', - 'Combodo\\iTop\\Application\\WebPage\\iTopConfigEditorPage' => $baseDir . '/sources/Application/WebPage/iTopConfigEditorPage.php', 'Combodo\\iTop\\Application\\WebPage\\iTopPDF' => $baseDir . '/sources/Application/WebPage/iTopPDF.php', 'Combodo\\iTop\\Application\\WebPage\\iTopWebPage' => $baseDir . '/sources/Application/WebPage/iTopWebPage.php', 'Combodo\\iTop\\Application\\WebPage\\iTopWizardWebPage' => $baseDir . '/sources/Application/WebPage/iTopWizardWebPage.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 412a887eb..2d9aa046e 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -764,7 +764,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Application\\WebPage\\WebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/WebPage.php', 'Combodo\\iTop\\Application\\WebPage\\XMLPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/XMLPage.php', 'Combodo\\iTop\\Application\\WebPage\\iTabbedPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTabbedPage.php', - 'Combodo\\iTop\\Application\\WebPage\\iTopConfigEditorPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopConfigEditorPage.php', 'Combodo\\iTop\\Application\\WebPage\\iTopPDF' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopPDF.php', 'Combodo\\iTop\\Application\\WebPage\\iTopWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWebPage.php', 'Combodo\\iTop\\Application\\WebPage\\iTopWizardWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWizardWebPage.php', diff --git a/sources/Application/WebPage/iTopConfigEditorPage.php b/sources/Application/WebPage/iTopConfigEditorPage.php deleted file mode 100644 index 73d74fd19..000000000 --- a/sources/Application/WebPage/iTopConfigEditorPage.php +++ /dev/null @@ -1,227 +0,0 @@ -set_base(utils::GetAbsoluteUrlAppRoot().'pages/'); - $sAceDir = 'node_modules/ace-builds/src-min/'; - $this->LinkScriptFromAppRoot($sAceDir.'ace.js'); - $this->LinkScriptFromAppRoot($sAceDir.'mode-php.js'); - $this->LinkScriptFromAppRoot($sAceDir.'theme-eclipse.js'); - $this->LinkScriptFromAppRoot($sAceDir.'ext-searchbox.js'); - - $this->AddUiBlock(TitleUIBlockFactory::MakeForPage(Dict::S('config-edit-title'))); - } - - public function AddAlertFromException(\Exception $e) - { - $oAlert = match ($e->getCode()) { - iTopConfigValidator::CONFIG_WARNING => AlertUIBlockFactory::MakeForWarning('', $e->getMessage()), - iTopConfigValidator::CONFIG_INFO => AlertUIBlockFactory::MakeForInformation('', $e->getMessage()), - default => AlertUIBlockFactory::MakeForDanger('', $e->getMessage()), - }; - $this->AddUiBlock($oAlert); - } - - public function AddConfigScripts() { - $this->add_script(<<'.Dict::S('config-edit-intro').'
')); - - $oForm = new Form(); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'save', 'operation')); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', utils::GetNewTransactionId())); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('checksum', $sConfigChecksum)); - - //--- Cancel button - $oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('config-cancel'), 'cancel_button', null, true, 'cancel_button'); - $oCancelButton->SetOnClickJsCode("return ResetConfig();"); - $oForm->AddSubBlock($oCancelButton); - - //--- Submit button - $oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('config-apply'), null, Dict::S('config-apply'), true, 'submit_button'); - $oForm->AddSubBlock($oSubmitButton); - - //--- Config editor - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('prev_config', $sPrevConfig, 'prev_config')); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('new_config', $sNewConfig)); - $oForm->AddHtml(""); - $this->AddUiBlock($oForm); - - $this->AddConfigScripts(); - } - - -} \ No newline at end of file