Explorar el Código

Configuration file editor:
- support syntax highlighting and checking (ace editor)
- better "apply" and "reset" buttons management
- limit code injection when checking the configuration
- better syntax checking for PHP7

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4926 a333f486-631f-4898-b8df-5754b55c2be0

eespie hace 7 años
padre
commit
bcd194d47b

+ 116 - 122
datamodels/2.x/itop-config/config.php

@@ -39,13 +39,16 @@ function TestConfig($sContents, $oP)
 	{
 		ini_set('display_errors', 1);
 		ob_start();
-		eval('?'.'>'.trim($sContents));
-		$sNoise = trim(ob_get_contents());
+        // in PHP < 7.0.0 syntax errors are in output
+        // in PHP >= 7.0.0 syntax errors are thrown as Error
+        $sSafeContent = preg_replace(array('#^\s*<\?php#', '#\?>\s*$#'), '', $sContents);
+        eval('if(0){'.trim($sSafeContent).'}');
+        $sNoise = trim(ob_get_contents());
 		ob_end_clean();
-	}
-	catch (Exception $e)
-	{
-		// well, never reach in case of parsing error :-(
+    }
+    catch (Error $e)
+    {
+        // ParseError only thrown in PHP7
 		throw new Exception('Error in configuration: '.$e->getMessage());
 	}
 	if (strlen($sNoise) > 0)
@@ -62,14 +65,6 @@ function TestConfig($sContents, $oP)
 			for ($i = 0 ; $i < $iLine - 1; $i++) $iStart += strlen($aLines[$i]);
 			$iEnd = $iStart + strlen($aLines[$iLine - 1]);
 			$iTotalLines = count($aLines);
-			$oP->add_ready_script(
-<<<EOF
-setCursorPos($('#new_config')[0], $iStart, $iEnd);
-$('#new_config')[0].focus();
-var iScroll = Math.floor($('#new_config')[0].scrollHeight * ($iLine - 20) / $iTotalLines);
-$('#new_config').scrollTop(iScroll);
-EOF
-			);
 
 			$sMessage = Dict::Format('config-parse-error', $sMessage, $sLine);
 			throw new Exception($sMessage);
@@ -93,7 +88,11 @@ LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be
 
 $oP = new iTopWebPage(Dict::S('config-edit-title'));
 $oP->set_base(utils::GetAbsoluteUrlAppRoot().'pages/');
-
+$oP->add_linked_script(utils::GetCurrentModuleUrl().'/js/ace.js');
+$oP->add_linked_script(utils::GetCurrentModuleUrl().'/js/mode-php.js');
+$oP->add_linked_script(utils::GetCurrentModuleUrl().'/js/theme-eclipse.js');
+$oP->add_linked_script(utils::GetCurrentModuleUrl().'/js/ext-searchbox.js');
+//$oP->add_linked_script(utils::GetCurrentModuleUrl().'/js/ext-textarea.js');
 
 try
 {
@@ -111,134 +110,129 @@ try
 	}
 	else
 	{
-		$oP->add_style(
-<<<EOF
-textarea {
-	-webkit-box-sizing: border-box;
-	-moz-box-sizing: border-box;
-	box-sizing: border-box;
-
-	width: 100%;
-	height: 550px;
-}
-.current_line {
-	display: none;
-	margin-left: 20px;
-}
-EOF
-		);
-	
 		$sConfigFile = APPROOT.'conf/'.utils::GetCurrentEnvironment().'/config-itop.php';
-	
-		if ($sOperation == 'save')
-		{
-			$sConfig = utils::ReadParam('new_config', '', false, 'raw_data');
-			$sTransactionId = utils::ReadParam('transaction_id', '');
-			$sOrginalConfig = utils::ReadParam('prev_config', '', false, 'raw_data');
-			if (!utils::IsTransactionValid($sTransactionId, true))
-			{
-				$oP->add("<div class=\"header_message message_info\">Error: invalid Transaction ID. The configuration was <b>NOT</b> modified.</div>");
-			}
-			else
-			{
-				if ($sConfig == $sOrginalConfig)
-				{
-					$oP->add('<div id="save_result" class="header_message">'.Dict::S('config-no-change').'</div>');
-				}
-				else
-				{
-					try
-					{
-						TestConfig($sConfig, $oP); // 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, 0444); // Read-only
-			
-						$oP->p('<div id="save_result" class="header_message message_ok">'.Dict::S('Successfully recorded.').'</div>');
-						$sOrginalConfig = str_replace("\r\n", "\n", file_get_contents($sConfigFile));
-					}
-					catch (Exception $e)
-					{
-						$oP->p('<div id="save_result" class="header_message message_error">'.$e->getMessage().'</div>');
-					}
-				}
-			}
-		}
-		else
-		{
-			$sConfig = str_replace("\r\n", "\n", file_get_contents($sConfigFile));
-			$sOrginalConfig = $sConfig;
-		}
-	
+
+        $iEditorTopMargin = 9;
+        $sConfig = str_replace("\r\n", "\n", file_get_contents($sConfigFile));
+        $sOrginalConfig = $sConfig;
+
+        if (!empty($sOperation))
+        {
+            $iEditorTopMargin = 14;
+            $sConfig = utils::ReadParam('new_config', '', false, 'raw_data');
+            $sOrginalConfig = utils::ReadParam('prev_config', '', false, 'raw_data');
+        }
+
+        if ($sOperation == 'revert')
+        {
+            $oP->add('<div id="save_result" class="header_message message_info">'.Dict::S('config-reverted').'</div>');
+        }
+        if ($sOperation == 'save')
+        {
+            $sTransactionId = utils::ReadParam('transaction_id', '');
+            if (!utils::IsTransactionValid($sTransactionId, true))
+            {
+                $oP->add("<div class=\"header_message message_info\">Error: invalid Transaction ID. The configuration was <b>NOT</b> modified.</div>");
+            }
+            else
+            {
+                if ($sConfig == $sOrginalConfig)
+                {
+                    $oP->add('<div id="save_result" class="header_message">'.Dict::S('config-no-change').'</div>');
+                }
+                else
+                {
+                    try
+                    {
+                        TestConfig($sConfig, $oP); // 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, 0444); // Read-only
+
+                        $oP->p('<div id="save_result" class="header_message message_ok">'.Dict::S('config-saved').'</div>');
+                        $sOrginalConfig = str_replace("\r\n", "\n", file_get_contents($sConfigFile));
+                    }
+                    catch (Exception $e)
+                    {
+                        $oP->p('<div id="save_result" class="header_message message_error">'.$e->getMessage().'</div>');
+                    }
+                }
+            }
+        }
+
+
 		$sConfigEscaped = htmlentities($sConfig, ENT_QUOTES, 'UTF-8');
 		$sOriginalConfigEscaped = htmlentities($sOrginalConfig, ENT_QUOTES, 'UTF-8');
 		$oP->p(Dict::S('config-edit-intro'));
 		$oP->add("<form method=\"POST\">");
-		$oP->add("<input type=\"hidden\" name=\"operation\" value=\"save\">");
+        $oP->add("<input id=\"operation\" type=\"hidden\" name=\"operation\" value=\"save\">");
 		$oP->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\">");
-		$oP->add("<input type=\"submit\" value=\"".Dict::S('config-apply')."\"><button onclick=\"ResetConfig(); return false;\">".Dict::S('config-cancel')."</button>");
-		$oP->add("<span class=\"current_line\">".Dict::Format('config-current-line', "<span class=\"line_number\"></span>")."</span>");
+        $oP->add("<input id=\"submit_button\" type=\"submit\" value=\"".Dict::S('config-apply')."\"><button id=\"cancel_button\" disabled=\"disabled\" onclick=\"return ResetConfig();\">".Dict::S('config-cancel')."</button>");
 		$oP->add("<input type=\"hidden\" id=\"prev_config\" name=\"prev_config\" value=\"$sOriginalConfigEscaped\">");
-		$oP->add("<textarea id =\"new_config\" name=\"new_config\" onkeyup=\"UpdateLineNumber();\" onmouseup=\"UpdateLineNumber();\">$sConfigEscaped</textarea>");
-		$oP->add("<input type=\"submit\" value=\"".Dict::S('config-apply')."\"><button onclick=\"ResetConfig(); return false;\">".Dict::S('config-cancel')."</button>");
-		$oP->add("<span class=\"current_line\">".Dict::Format('config-current-line', "<span class=\"line_number\"></span>")."</span>");
+        $oP->add("<input type=\"hidden\"  name=\"new_config\" value=\"$sConfigEscaped\">");
+        $oP->add("<div id =\"new_config\" style=\"position: absolute; top: ".$iEditorTopMargin."em; bottom: 0; left: 5px; right: 5px;\"></div>");
 		$oP->add("</form>");
 	
 		$sConfirmCancel = addslashes(Dict::S('config-confirm-cancel'));
-		$oP->add_script(
-<<<EOF
-function UpdateLineNumber()
+        $oP->add_ready_script(
+            <<<EOF
+var editor = ace.edit("new_config");
+var textarea = $('input[name="new_config"]');
+editor.getSession().setValue(textarea.val());
+editor.getSession().on('change', function()
+{
+  textarea.val(editor.getSession().getValue());
+  UpdateConfigEditorButtonState();
+});
+editor.getSession().on("changeAnnotation", function()
+{
+  UpdateConfigEditorButtonState();
+});
+editor.setTheme("ace/theme/eclipse");
+editor.getSession().setMode("ace/mode/php");
+function UpdateConfigEditorButtonState()
 {
-	var oTextArea = $('#new_config')[0];
-	$('.line_number').html(oTextArea.value.substr(0, oTextArea.selectionStart).split("\\n").length);
-	$('.current_line').show();
+    var editor = ace.edit("new_config");
+    var isSameContent = editor.getValue() == $('#prev_config').val();
+    var hasNoError = $.isEmptyObject(editor.getSession().getAnnotations());
+    $('#cancel_button').attr('disabled', isSameContent);
+    $('#submit_button').attr('disabled', isSameContent || !hasNoError);
 }
+EOF
+        );
+
+		$oP->add_script(
+<<<EOF
 function ResetConfig()
 {
-	if ($('#new_config').val() != $('#prev_config').val())
+    var editor = ace.edit("new_config");
+	$("#operation").attr('value', 'revert');
+	if (editor.getValue() != $('#prev_config').val())
 	{
 		if (confirm('$sConfirmCancel'))
 		{
-			$('#new_config').val($('#prev_config').val());
+			$('input[name="new_config"]').val($('#prev_config').val());
+			return true;
 		}
 	}
-	$('.current_line').hide();
-	$('#save_result').hide();
 	return false;
 }
-
-function setCursorPos(input, start, end) {
-    if (arguments.length < 3) end = start;
-    if ("selectionStart" in input) {
-        setTimeout(function() {
-            input.selectionStart = start;
-            input.selectionEnd = end;
-        }, 1);
-    }
-    else if (input.createTextRange) {
-        var rng = input.createTextRange();
-        rng.moveStart("character", start);
-        rng.collapse();
-        rng.moveEnd("character", end - start);
-        rng.select();
-    }
-}
 EOF
 		);
 	}

+ 2 - 0
datamodels/2.x/itop-config/cs.dict.itop-config.php

@@ -14,8 +14,10 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
     'config-edit-intro' => 'Při úpravách konfiguračního souboru buďte velice opatrní. Nesprávné nastavení může vést k nedostupnosti iTop', //In particular, only the upper items (i.e. the global configuration and modules settings) should be edited.
     'config-apply' => 'Použít',
     'config-cancel' => 'Zrušit',
+    'config-saved' => 'Successfully recorded.~~',
     'config-confirm-cancel' => 'Vaše úpravy nebudou uloženy.',
     'config-no-change' => 'Soubor nebyl změněn.',
+    'config-reverted' => 'The configuration has been reverted.~~',
     'config-parse-error' => 'Řádek %2$d: %1$s.<br/>Soubor nebyl uložen.',
     'config-current-line' => 'Řádek: %1$s',
 ));

+ 2 - 0
datamodels/2.x/itop-config/de.dict.itop-config.php

@@ -30,8 +30,10 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
 	'config-edit-intro' => 'Seien sie bei der Bearbeitung der Konfigurationsdatei sehr vorsichtig.',
 	'config-apply' => 'Anwenden',
 	'config-cancel' => 'Zurücksetzen',
+    'config-saved' => 'Successfully recorded.~~',
 	'config-confirm-cancel' => 'Ihre Änderungen werden nicht gespeichert.',
 	'config-no-change' => 'Keine Änderungen: Die Datei wurde nicht verändert.',
+    'config-reverted' => 'The configuration has been reverted.~~',
 	'config-parse-error' => 'Zeile %2$d: %1$s.<br/>Die Datei wurde nicht aktualisiert.',
 	'config-current-line' => 'Editiere Zeile: %1$s',
 	));

+ 2 - 0
datamodels/2.x/itop-config/en.dict.itop-config.php

@@ -13,8 +13,10 @@ Dict::Add('EN US', 'English', 'English', array(
 	'config-edit-intro' => 'Be very cautious when editing the configuration file.',
 	'config-apply' => 'Apply',
 	'config-cancel' => 'Reset',
+    'config-saved' => 'Successfully recorded.',
 	'config-confirm-cancel' => 'Your changes will be lost.',
 	'config-no-change' => 'No change: the file has been left unchanged.',
+    'config-reverted' => 'The configuration has been reverted.',
 	'config-parse-error' => 'Line %2$d: %1$s.<br/>The file has NOT been updated.',
 	'config-current-line' => 'Editing line: %1$s',
 ));

+ 2 - 0
datamodels/2.x/itop-config/fr.dict.itop-config.php

@@ -13,8 +13,10 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'config-edit-intro' => 'Attention: une configuration incorrecte peut rendre iTop inopérant pour tous les utilisateurs!',
 	'config-save' => 'Appliquer',
 	'config-restore' => 'Réinitialiser',
+	'config-saved' => 'Configuration enregistrée.',
 	'config-confirm-cancel' => 'Vos modifications seront perdues.',
 	'config-no-change' => 'Aucun changement : le fichier n\'a pas été altéré.',
+	'config-reverted' => 'Vos modifications ont été écrasées par la version enregistrée.',
 	'config-parse-error' => 'Ligne %2$d: %1$s.<br/>Le fichier n\'a PAS été modifié.',
 	'config-current-line' => 'Ligne en édition : %1$s',
 	'config-apply' => 'Enregistrer',

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
datamodels/2.x/itop-config/js/ace.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
datamodels/2.x/itop-config/js/ext-searchbox.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
datamodels/2.x/itop-config/js/mode-php.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
datamodels/2.x/itop-config/js/theme-eclipse.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
datamodels/2.x/itop-config/js/worker-php.js


+ 35 - 0
datamodels/2.x/itop-config/license.itop-config.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<licenses>
+    <license>
+        <product>ajaxorg/ace</product>
+        <author>Ajax.org B.V.</author>
+        <license_type>BSD 3-clause "New" or "Revised"</license_type>
+        <text><![CDATA[
+        Copyright (c) 2010, Ajax.org B.V.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Ajax.org B.V. nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+]]></text>
+    </license>
+</licenses>

+ 1 - 3
datamodels/2.x/itop-config/module.itop-config.php

@@ -3,7 +3,7 @@
 
 SetupWebPage::AddModule(
 	__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
-	'itop-config/1.0.2',
+    'itop-config/1.0.3',
 	array(
 		// Identification
 		//
@@ -49,5 +49,3 @@ SetupWebPage::AddModule(
 		),
 	)
 );
-
-?>

+ 2 - 0
datamodels/2.x/itop-config/ru.dict.itop-config.php

@@ -17,8 +17,10 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
 	'config-edit-intro' => 'Будьте очень осторожны при редактировании файла конфигурации. В частности, могут быть отредактированы только верхние элементы (т.е. глобальная конфигурация и настройки модулей).',
 	'config-apply' => 'Применить',
 	'config-cancel' => 'Сбросить',
+    'config-saved' => 'Successfully recorded.~~',
 	'config-confirm-cancel' => 'Ваши изменения будут утеряны.',
 	'config-no-change' => 'Изменений нет: файл не был изменён.',
+    'config-reverted' => 'The configuration has been reverted.~~',
 	'config-parse-error' => 'Строка %2$d: %1$s.<br/>Файл не был обновлен.',
 	'config-current-line' => 'Редактируемая строка: %1$s',
 ));

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio