Przeglądaj źródła

Merge pull request #4 from frytimo/new_feature_autocomplete

New feature autocomplete
FusionPBX 4 miesięcy temu
rodzic
commit
d408eb72a0

+ 0 - 2
app_config.php

@@ -180,5 +180,3 @@
 		$apps[$x]['db'][$y]['fields'][$z]['type']['sqlite'] = "text";
 		$apps[$x]['db'][$y]['fields'][$z]['type']['mysql'] = "char(36)";
 		$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "";
-
-?>

+ 0 - 2
app_languages.php

@@ -815,5 +815,3 @@ $text['label-file']['uk-ua'] = "Файл:";
 $text['label-file']['zh-cn'] = "文件:";
 $text['label-file']['ja-jp'] = "ファイル:";
 $text['label-file']['ko-kr'] = "파일:";
-
-?>

+ 0 - 2
app_menu.php

@@ -114,5 +114,3 @@
 	$apps[$x]['menu'][$y]['path'] = "/app/edit/index.php?dir=php";
 	$apps[$x]['menu'][$y]['groups'][] = "superadmin";
 	$y++;
-
-?>

+ 0 - 2
clip_add.php

@@ -133,5 +133,3 @@ if (count($_POST)>0) {
 
 //include the footer
 	require_once "footer.php";
-
-?>

+ 0 - 2
clip_delete.php

@@ -63,5 +63,3 @@
 
 //redirect the browser
 	header("Location: clip_options.php");
-
-?>

+ 0 - 2
clip_list.php

@@ -226,5 +226,3 @@
 
 //inclue the footer
 require_once "footer.php";
-
-?>

+ 0 - 2
clip_options.php

@@ -71,5 +71,3 @@
 
 //include footer
 	require_once "footer.php";
-
-?>

+ 0 - 2
clip_options_list.php

@@ -182,5 +182,3 @@
 
 //include the footer
 	require_once "footer.php";
-
-?>

+ 0 - 2
clip_update.php

@@ -155,5 +155,3 @@
 
 //include the footer
 	require_once "footer.php";
-
-?>

+ 0 - 1
file_delete.php

@@ -108,4 +108,3 @@
 		//include the footer
 		require_once "footer.php";
 	}
-?>

+ 17 - 113
file_list.php

@@ -69,7 +69,7 @@
 					else {
 						$dir_array[] = $newpath;
 					}
-					if ($x > 1000) { break; };
+					if ($x > 1000) { break; }
 					$x++;
 				}
 			}
@@ -92,7 +92,7 @@
 				$newpath = str_replace ('//', '/', $newpath);
 				$newpath = str_replace ("\\", "/", $newpath);
 				$html_file_list .= "<div style='white-space: nowrap; padding-left: 16px;'>\n";
-				$html_file_list .= "<a href='javascript:void(0);' onclick=\"parent.document.getElementById('filepath').value='".$newpath."'; parent.document.getElementById('current_file').value = '".$newpath."'; makeRequest('file_read.php','file=".urlencode($newpath)."');\" title='".$newpath." &#10; ".$filesize." KB'>";
+				$html_file_list .= "<a href='javascript:void(0);' onclick=\"document.getElementById('filepath').value='".$newpath."'; document.getElementById('current_file').value = '".$newpath."'; makeRequest('file_read.php','file=".urlencode($newpath)."');\" title='".$newpath." &#10; ".$filesize." KB'>";
 				$html_file_list .= "<img src='resources/images/icon_file.png' border='0' align='absmiddle' style='margin: 1px 2px 3px -1px;'>".$filename."</a>\n";
 				$html_file_list .= "</div>\n";
 			}
@@ -106,13 +106,13 @@
 	if (!isset($_SESSION)) { session_start(); }
 	switch ($_SESSION["app"]["edit"]["dir"]) {
 		case 'scripts':
-			$edit_directory = $_SESSION['switch']['scripts']['dir'];
+			$edit_directory = $settings->get('switch', 'scripts');
 			break;
 		case 'php':
-			$edit_directory = $_SERVER["DOCUMENT_ROOT"].'/'.PROJECT_PATH;
+			$edit_directory = dirname(__DIR__, 2);
 			break;
 		case 'grammar':
-			$edit_directory = $_SESSION['switch']['grammar']['dir'];
+			$edit_directory = $settings->get('switch', 'grammar');
 			break;
 		case 'provision':
 			switch (PHP_OS) {
@@ -159,112 +159,22 @@
 			}
 			break;
 		case 'xml':
-			$edit_directory = $_SESSION['switch']['conf']['dir'];
+			$edit_directory = $settings->get('switch', 'conf');
 			break;
+		default:
+			//do not allow unknown settings
+			exit();
 	}
-	if (!isset($edit_directory) && is_array($_SESSION['editor']['path'])) {
-		foreach ($_SESSION['editor']['path'] as $path) {
-			if ($_SESSION["app"]["edit"]["dir"] == $path) {
-				$edit_directory = $path;
-				break;
-			}
-		}
-	}
-
-
-
-//define ajax functions
-echo "<script type=\"text/javascript\" language=\"javascript\">\n";
-echo "    function makeRequest(url, strpost) {\n";
-echo "        var http_request = false;\n";
-echo "\n";
-echo "        if (window.XMLHttpRequest) { // Mozilla, Safari, ...\n";
-echo "            http_request = new XMLHttpRequest();\n";
-echo "            if (http_request.overrideMimeType) {\n";
-echo "                http_request.overrideMimeType('text/xml');\n";
-echo "                // See note below about this line\n";
-echo "            }\n";
-echo "        } else if (window.ActiveXObject) { // IE\n";
-echo "            try {\n";
-echo "                http_request = new ActiveXObject(\"Msxml2.XMLHTTP\");\n";
-echo "            } catch (e) {\n";
-echo "                try {\n";
-echo "                    http_request = new ActiveXObject(\"Microsoft.XMLHTTP\");\n";
-echo "                } catch (e) {}\n";
-echo "            }\n";
-echo "        }\n";
-echo "\n";
-echo "        if (!http_request) {\n";
-echo "            alert('".$text['message-give-up']."');\n";
-echo "            return false;\n";
-echo "        }\n";
-echo "        http_request.onreadystatechange = function() { returnContent(http_request); };\n";
-echo "        if (http_request.overrideMimeType) {\n";
-echo "              http_request.overrideMimeType('text/html');\n";
-echo "        }\n";
-echo "        http_request.open('POST', url, true);\n";
-echo "\n";
-echo "\n";
-echo "        if (strpost.length == 0) {\n";
-echo "            //http_request.send(null);\n";
-echo "            http_request.send('name=value&foo=bar');\n";
-echo "        }\n";
-echo "        else {\n";
-echo "            http_request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');\n";
-echo "            http_request.send(strpost);\n";
-echo "        }\n";
-echo "\n";
-echo "    }\n";
-echo "\n";
-echo "    function returnContent(http_request) {\n";
-echo "\n";
-echo "        if (http_request.readyState == 4) {\n";
-echo "            if (http_request.status == 200) {\n";
-echo "			parent.document.getElementById('editor_source').value=http_request.responseText;";
-echo "			parent.editor.getSession().setValue(parent.document.getElementById('editor_source').value);";
-echo "			parent.editor.gotoLine(1);";
-echo "			parent.editor.scrollToLine(1, true, true, function() {});";
-echo "			parent.editor.focus();";
-echo "\n";
-echo "            }\n";
-echo "            else {\n";
-echo "                alert('".$text['message-problem']."');\n";
-echo "            }\n";
-echo "        }\n";
-echo "\n";
-echo "    }\n";
-echo "</script>";
-
-
-echo "<SCRIPT LANGUAGE=\"JavaScript\">\n";
-//echo "// ---------------------------------------------\n";
-//echo "// --- http://www.codeproject.com/jscript/dhtml_treeview.asp\n";
-//echo "// --- Name:    Easy DHTML Treeview           --\n";
-//echo "// --- Author:  D.D. de Kerf                  --\n";
-//echo "// --- Version: 0.2          Date: 13-6-2001  --\n";
-//echo "// ---------------------------------------------\n";
-echo "function Toggle(node) {\n";
-echo "	// Unfold the branch if it isn't visible\n";
-echo "	if (node.nextSibling.style.display == 'none') {\n";
-echo "  	node.nextSibling.style.display = 'block';\n";
-echo "	}\n";
-echo "	// Collapse the branch if it IS visible\n";
-echo "	else {\n";
-echo "  	node.nextSibling.style.display = 'none';\n";
-echo "	}\n";
-echo "\n";
-echo "}\n";
-echo "</SCRIPT>";
 
 // keyboard shortcut bindings
-echo "<script language='JavaScript' type='text/javascript' src='".PROJECT_PATH."/resources/jquery/jquery-3.6.1.min.js'></script>\n";
+echo "<script src='".PROJECT_PATH."/resources/jquery/jquery-3.6.1.min.js'></script>\n";
 echo "<script src='https://code.jquery.com/jquery-migrate-3.1.0.js'></script>\n";
 
 //save file
-key_press('ctrl+s', 'down', 'window', null, null, "parent.$('form#frm_edit').submit(); return false;", true);
+key_press('ctrl+s', 'down', 'window', null, null, "$('form#frm_edit').submit(); return false;", true);
 
 //open file manager/clip library pane
-key_press('ctrl+q', 'down', 'window', null, null, 'parent.toggle_sidebar(); parent.focus_editor(); return false;', true);
+key_press('ctrl+q', 'down', 'window', null, null, 'toggle_sidebar(); focus_editor(); return false;', true);
 
 //prevent backspace (browser history back)
 key_press('backspace', 'down', 'window', null, null, 'return false;', true);
@@ -274,17 +184,11 @@ echo "<body style='margin: 0px; padding: 5px;'>\n";
 
 echo "<div style='text-align: left; padding-top: 3px; padding-bottom: 3px;'><a href='javascript:void(0);' onclick=\"window.open('file_options.php','filewin','left=20,top=20,width=310,height=350,toolbar=0,resizable=0');\" style='text-decoration:none;' title='".$text['label-files']."'><img src='resources/images/icon_gear.png' border='0' align='absmiddle' style='margin: 0px 2px 4px -1px;'>".$text['label-files']."</a></div>\n";
 echo "<div style='text-align: left; margin-left: -16px;'>\n";
-if (file_exists($edit_directory)) {
-	echo recur_dir($edit_directory);
-}
-echo "</div>\n";
 
-require_once "footer.php";
+if (file_exists($edit_directory)) {
+	$edit_html_list = recur_dir($edit_directory);
 
-unset ($result_count);
-unset ($result);
-unset ($key);
-unset ($val);
-unset ($c);
+	echo $edit_html_list;
+}
 
-?>
+echo "</div>\n";

+ 0 - 2
file_new.php

@@ -114,5 +114,3 @@
 
 		require_once "footer.php";
 	}
-
-?>

+ 0 - 2
file_options.php

@@ -85,5 +85,3 @@
 
 //include the footer
 	require_once "footer.php";
-
-?>

+ 0 - 2
file_options_list.php

@@ -268,5 +268,3 @@ if (!isset($_SESSION)) { session_start(); }
 echo "</div>\n";
 
 require_once "footer.php";
-
-?>

+ 0 - 2
file_read.php

@@ -138,5 +138,3 @@
 		*/
 
 	}
-
-?>

+ 0 - 2
file_rename.php

@@ -124,5 +124,3 @@
 		require_once "footer.php";
 	
 	}
-
-?>

+ 0 - 2
file_save.php

@@ -173,5 +173,3 @@
 			}
 		}
 	}
-
-?>

+ 0 - 2
folder_delete.php

@@ -93,5 +93,3 @@
 		//include the footer
 		require_once "footer.php";
 	}
-
-?>

+ 0 - 2
folder_new.php

@@ -113,5 +113,3 @@
 		//show the footer
 		require_once "footer.php";
 	}
-
-?>

+ 0 - 2
footer.php

@@ -27,5 +27,3 @@
 echo "<div>";
 echo "</body>";
 echo "</html>";
-
-?>

+ 2 - 4
header.php

@@ -71,7 +71,7 @@ echo "//-->\n";
 echo "</style>\n";
 
 
-echo "<SCRIPT language=\"JavaScript\">\n";
+echo "<script>\n";
 echo "<!--\n";
 echo "function confirmdelete(url)\n";
 echo "{\n";
@@ -81,9 +81,7 @@ echo "      window.location=url;\n";
 echo " }\n";
 echo "}\n";
 echo "//-->\n";
-echo "</SCRIPT>\n";
+echo "</script>\n";
 echo "</head>\n";
 echo "<body style='margin: 0; padding: 5px;'>\n";
 echo "<div align='center'>\n";
-
-?>

+ 446 - 171
index.php

@@ -43,54 +43,60 @@
 	$text = $language->get();
 
 //set the directory title and mode
-	$_SESSION["app"]["edit"]["dir"] = $_GET["dir"];
-	$title = escape($_GET["dir"]);
-	unset($mode);
 	switch ($_GET["dir"]) {
 		case 'xml':
 			$title = 'XML';
 			$mode = 'xml';
+			$dir = 'xml';
 			break;
 		case 'provision':
 			$title = 'Provision';
 			$mode = 'xml';
+			$dir = 'provision';
 			break;
 		case 'php':
 			$title = 'PHP';
 			$mode = 'php';
+			$dir = 'php';
 			break;
 		case 'scripts':
 			$title = 'Scripts';
 			$mode = 'lua';
+			$dir = 'scripts';
 			break;
 		case 'grammar':
 			$title = 'Grammar';
 			$mode = 'xml';
-		default: $mode = 'text';
+			$dir = 'grammar';
+			break;
+		default:
+			$mode = 'text';
+			$dir = '';
 	}
 
+//save the sanitized value
+	$_SESSION['app']['edit']['dir'] = $dir;
+
 //load editor preferences/defaults
-	$setting_size = !empty($_SESSION["editor"]["font_size"]["text"]) ? $_SESSION["editor"]["font_size"]["text"] : '12px';
-	$setting_theme = !empty($_SESSION["editor"]["theme"]["text"]) ? $_SESSION["editor"]["theme"]["text"] : 'cobalt';
-	$setting_invisibles = !empty($_SESSION["editor"]["invisibles"]["boolean"]) ? $_SESSION["editor"]["invisibles"]["boolean"] : 'false';
-	$setting_indenting = !empty($_SESSION["editor"]["indent_guides"]["boolean"]) ? $_SESSION["editor"]["indent_guides"]["boolean"] : 'false';
-	$setting_numbering = !empty($_SESSION["editor"]["line_numbers"]["boolean"]) ? $_SESSION["editor"]["line_numbers"]["boolean"] : 'true';
+	$setting_size       = $settings->get('editor', 'font_size', '12px');
+	$setting_theme      = $settings->get('editor', 'theme', 'cobalt');
+	$setting_invisibles = $settings->get('editor', 'invisibles','false');
+	$setting_indenting  = $settings->get('editor', 'indent_guides','false');
+	$setting_numbering  = $settings->get('editor', 'line_numbers','true');
 
 //get and then set the favicon
-	if (isset($_SESSION['theme']['favicon']['text'])){
-		$favicon = $_SESSION['theme']['favicon']['text'];
-	}
-	else {
-		$favicon = PROJECT_ROOT .'/themes/default/favicon.ico';
-	}
+	$favicon = $settings->get('theme', 'favicon', PROJECT_ROOT .'/themes/default/favicon.ico');
 
 //create a token
 	$key_name = '/app/edit/'.$mode;
 	$_SESSION['keys'][$key_name] = bin2hex(random_bytes(32));
 	$_SESSION['token'] = hash_hmac('sha256', $key_name, $_SESSION['keys'][$key_name]);
 
-?>
+	//The buffer must be empty
+	while(ob_get_level() > 0)
+		ob_get_clean();
 
+?><!doctype html>
 <html>
 <head>
 	<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
@@ -98,8 +104,6 @@
 	<link rel="icon" type="image/x-icon" href="<?php echo $favicon; ?>">
 	<link rel='stylesheet' type='text/css' href='<?php echo PROJECT_PATH; ?>/resources/fontawesome/css/all.min.css.php'>
 	<script language="JavaScript" type="text/javascript" src="<?php echo PROJECT_PATH; ?>/resources/jquery/jquery-3.6.1.min.js"></script>
-	<script src='https://code.jquery.com/jquery-migrate-3.1.0.js'></script>
-	<script language='JavaScript' type='text/javascript' src='<?php echo PROJECT_PATH; ?>/resources/fontawesome/js/solid.min.js.php' defer></script>
 	<script language="JavaScript" type="text/javascript">
 		function submit_check() {
 			if (document.getElementById('filepath').value != '') {
@@ -169,7 +173,6 @@
 			form_data.append('content', editor.getSession().getValue());
 			form_data.append('token',document.getElementById('token').value);
 			form_data.append('mode',"<?php echo $mode; ?>");
-			
 
 			http_request('file_save.php', form_data);
 		}
@@ -191,161 +194,433 @@
 			}
 	</style>
 </head>
-<body style='padding: 0; margin: 0; overflow: hidden;'>
-<table id='frame' cellpadding='0' cellspacing='0' border='0' style="height: 100%; width: 100%;">
-	<tr>
-		<td id='sidebar' valign='top' style="width: 300px; height: 100%;">
-			<iframe id='file_list' src='file_list.php' style='border: none; height: 65%; width: 100%;'></iframe><br>
-			<iframe id='clip_list' src='clip_list.php' style='border: none; border-top: 1px solid #ccc; height: calc(35% - 1px); width: 100%;'></iframe>
-		</td>
-		<td align='right' valign='top' style='height: 100%;'>
-			<form style='margin: 0;' name='frm_edit' id='frm_edit' method='post' action='file_save.php' onsubmit="return submit_check();">
-			<textarea name='content' id='editor_source' style='display: none;'></textarea>
-			<input type='hidden' name='filepath' id='filepath' value=''>
-			<input type='hidden' name='token' id='token' value='<?php echo $_SESSION['token']; ?>'>
-			<table cellpadding='0' cellspacing='0' border='0' style='width: 100%;'>
-				<tr>
-					<td valign='middle' id='td_save'><i class='fas fa-save fa-lg ace_control' title="<?php echo $text['label-save_changes']; ?>" onclick="save();"></i></td>
-					<td align='left' valign='middle' width='100%' style='padding: 0 15px 0 18px;'><input id='current_file' type='text' style='height: 23px; width: 100%;'></td>
-					<td style='padding: 0;'><img src='resources/images/blank.gif' style='width: 1px; height: 40px; border: none;'></td>
-					<td valign='middle' style='padding-left: 6px;'><i class='fas fa-window-maximize fa-lg fa-rotate-270 ace_control' title="<?php echo $text['label-toggle_side_bar']; ?>" onclick="toggle_sidebar();"></i></td>
-					<td valign='middle' style='padding-left: 6px;'><i class='fas fa-list-ul fa-lg ace_control' title="<?php echo $text['label-toggle_line_numbers']; ?>" onclick="toggle_option('numbering');"></i></td>
-					<td valign='middle' style='padding-left: 6px;'><i class='fas fa-eye-slash fa-lg ace_control' title="<?php echo $text['label-toggle_invisibles']; ?>" onclick="toggle_option('invisibles');"></i></td>
-					<td valign='middle' style='padding-left: 6px;'><i class='fas fa-indent fa-lg ace_control' title="<?php echo $text['label-toggle_indent_guides']; ?>" onclick="toggle_option('indenting');"></i></td>
-					<td valign='middle' style='padding-left: 6px;'><i class='fas fa-search fa-lg ace_control' title="<?php echo $text['label-find_replace']; ?>" onclick="editor.execCommand('replace');"></i></td>
-					<td valign='middle' style='padding-left: 6px;'><i class='fas fa-chevron-down fa-lg ace_control' title="<?php echo $text['label-go_to_line']; ?>" onclick="editor.execCommand('gotoline');"></i></td>
-					<td valign='middle' style='padding-left: 15px;'>
-						<select id='mode' style='height: 23px; max-width: 70px;' onchange="editor.getSession().setMode('ace/mode/' + this.options[this.selectedIndex].value); focus_editor();">
-							<?php
-							$modes['php'] = 'PHP';
-							$modes['css'] = 'CSS';
-							$modes['html'] = 'HTML';
-							$modes['javascript'] = 'JS';
-							$modes['json'] = 'JSON';
-							$modes['ini'] = 'Conf';
-							$modes['lua'] = 'Lua';
-							$modes['text'] = 'Text';
-							$modes['xml'] = 'XML';
-							$modes['sql'] = 'SQL';
-							$modes['sh'] = 'SH';
-							$modes['smarty'] = 'Smarty';
-							$modes['svg'] = 'SVG';
-							$modes['makefile'] = 'Makefile';
-							$modes['c_cpp'] = 'C';
-							$modes['c_cpp'] = 'CPP';
-							$modes['pgsql'] = 'PGSQL';
-							foreach ($modes as $value => $label) {
-								$selected = ($value == $mode) ? 'selected' : null;
-								echo "<option value='".$value."' ".$selected.">".$label."</option>\n";
-							}
-							?>
-						</select>
-					</td>
-					<td valign='middle' style='padding-left: 4px;'>
-						<select id='size' style='height: 23px;' onchange="document.getElementById('editor').style.fontSize = this.options[this.selectedIndex].value; focus_editor();">
-							<?php
-							$sizes = explode(',','9px,10px,11px,12px,14px,16px,18px,20px');
-							if (!in_array($setting_size, $sizes)) {
-								echo "<option value='".$setting_size."'>".$setting_size."</option>\n";
-								echo "<option value='' disabled='disabled'></option>\n";
-							}
-							foreach ($sizes as $size) {
-								$selected = ($size == $setting_size) ? 'selected' : null;
-								echo "<option value='".$size."' ".$selected.">".$size."</option>\n";
-							}
-							?>
-						</select>
-					</td>
-					<td valign='middle' style='padding-left: 4px; padding-right: 4px;'>
-						<select id='theme' style='height: 23px; max-width: 100px;' onchange="editor.setTheme('ace/theme/' + this.options[this.selectedIndex].value); focus_editor();">
-							<?php
-							$themes['Bright']['chrome']= 'Chrome';
-							$themes['Bright']['clouds']= 'Clouds';
-							$themes['Bright']['crimson_editor']= 'Crimson Editor';
-							$themes['Bright']['dawn']= 'Dawn';
-							$themes['Bright']['dreamweaver']= 'Dreamweaver';
-							$themes['Bright']['eclipse']= 'Eclipse';
-							$themes['Bright']['github']= 'GitHub';
-							$themes['Bright']['iplastic']= 'IPlastic';
-							$themes['Bright']['solarized_light']= 'Solarized Light';
-							$themes['Bright']['textmate']= 'TextMate';
-							$themes['Bright']['tomorrow']= 'Tomorrow';
-							$themes['Bright']['xcode']= 'XCode';
-							$themes['Bright']['kuroir']= 'Kuroir';
-							$themes['Bright']['katzenmilch']= 'KatzenMilch';
-							$themes['Bright']['sqlserver']= 'SQL Server';
-							$themes['Dark']['ambiance']= 'Ambiance';
-							$themes['Dark']['chaos']= 'Chaos';
-							$themes['Dark']['clouds_midnight']= 'Clouds Midnight';
-							$themes['Dark']['cobalt']= 'Cobalt';
-							$themes['Dark']['idle_fingers']= 'idle Fingers';
-							$themes['Dark']['kr_theme']= 'krTheme';
-							$themes['Dark']['merbivore']= 'Merbivore';
-							$themes['Dark']['merbivore_soft']= 'Merbivore Soft';
-							$themes['Dark']['mono_industrial']= 'Mono Industrial';
-							$themes['Dark']['monokai']= 'Monokai';
-							$themes['Dark']['pastel_on_dark']= 'Pastel on dark';
-							$themes['Dark']['solarized_dark']= 'Solarized Dark';
-							$themes['Dark']['terminal']= 'Terminal';
-							$themes['Dark']['tomorrow_night']= 'Tomorrow Night';
-							$themes['Dark']['tomorrow_night_blue']= 'Tomorrow Night Blue';
-							$themes['Dark']['tomorrow_night_bright']= 'Tomorrow Night Bright';
-							$themes['Dark']['tomorrow_night_eighties']= 'Tomorrow Night 80s';
-							$themes['Dark']['twilight']= 'Twilight';
-							$themes['Dark']['vibrant_ink']= 'Vibrant Ink';
-							foreach ($themes as $optgroup => $theme) {
-								echo "<optgroup label='".$optgroup."'>\n";
-								foreach ($theme as $value => $label) {
-									$selected = (strtolower($label) == strtolower($setting_theme)) ? 'selected' : null;
-									echo "<option value='".$value."' ".$selected.">".$label."</option>\n";
-								}
-								echo "</optgroup>\n";
-							}
-							?>
-						</select>
-					</td>
-				</tr>
-			</table>
-			</form>
-			<div id='editor' style="text-align: left; width: 100%; height: calc(100% - 30px); font-size: 12px;"></div>
-		</td>
-	</tr>
-</table>
-
-<script type="text/javascript" src="<?php echo PROJECT_PATH; ?>/resources/ace/ace.js" charset="utf-8"></script>
-<script type="text/javascript">
-	//load ace editor
-		var editor = ace.edit("editor");
-		editor.setOptions({
-			mode: 'ace/mode/<?php echo $mode;?>',
-			theme: 'ace/theme/'+document.getElementById('theme').options[document.getElementById('theme').selectedIndex].value,
-			selectionStyle: 'text',
-			cursorStyle: 'smooth',
-			showInvisibles: <?php echo $setting_invisibles;?>,
-			displayIndentGuides: <?php echo $setting_indenting;?>,
-			showLineNumbers: <?php echo $setting_numbering;?>,
-			showGutter: true,
-			scrollPastEnd: true,
-			fadeFoldWidgets: <?php echo $setting_numbering;?>,
-			showPrintMargin: false,
-			highlightGutterLine: false,
-			useSoftTabs: false
-			});
-		document.getElementById('editor').style.fontSize='<?php echo $setting_size;?>';
-		focus_editor();
+<body style="padding: 0; margin: 0; overflow: hidden;">
+  <div id="frame" style="display: flex; height: 100vh; width: 100vw;">
+    <!-- Sidebar -->
+    <div id="sidebar" style="width: 300px; height: 100%; display: flex; flex-direction: column;">
+		<div id="file_list" style="border: none; height: 65%; width: 100%; overflow: auto;">
+			Loading...
+		</div>
+		<div id="clip_list" style="border: none; border-top: 1px solid #ccc; height: calc(35% - 1px); width: 100%; overflow: auto;">
+			Loading...
+		</div>
+    </div>
 
-	//prevent form submit with enter key on file path input
-		<?php key_press('enter', 'down', '#current_file', null, null, 'return false;', false); ?>
+    <!-- Main Content -->
+    <div id="ace_content" style="flex: 1; height: 100%; display: flex; flex-direction: column;">
+      <!-- Editor Controls -->
+      <form style="margin: 0;" name="frm_edit" id="frm_edit" method="post" action="file_save.php" onsubmit="return submit_check();">
+        <textarea name="content" id="editor_source" style="display: none;"></textarea>
+        <input type="hidden" name="filepath" id="filepath" value="">
+        <input type="hidden" name="token" id="token" value="<?php echo $_SESSION['token']; ?>">
+        <div id="editor-controls" style="display: flex; align-items: center; width: 100%; height: 30px;">
+          <div id="td_save" style="display: inline-flex; align-items: center;">
+            <i class="fas fa-save fa-lg ace_control" title="<?php echo $text['label-save_changes']; ?>" onclick="save();"></i>
+          </div>
+          <div style="flex: 1; padding: 0 15px 0 18px;">
+            <input id="current_file" type="text" style="height: 23px; width: 100%;">
+          </div>
+          <div style="width: 1px; height: 40px;"></div>
+          <div style="padding-left: 6px;">
+            <i class="fas fa-window-maximize fa-lg fa-rotate-270 ace_control" title="<?php echo $text['label-toggle_side_bar']; ?>" onclick="toggle_sidebar();"></i>
+          </div>
+          <div style="padding-left: 6px;">
+            <i class="fas fa-list-ul fa-lg ace_control" title="<?php echo $text['label-toggle_line_numbers']; ?>" onclick="toggle_option('numbering');"></i>
+          </div>
+          <div style="padding-left: 6px;">
+            <i class="fas fa-eye-slash fa-lg ace_control" title="<?php echo $text['label-toggle_invisibles']; ?>" onclick="toggle_option('invisibles');"></i>
+          </div>
+          <div style="padding-left: 6px;">
+            <i class="fas fa-indent fa-lg ace_control" title="<?php echo $text['label-toggle_indent_guides']; ?>" onclick="toggle_option('indenting');"></i>
+          </div>
+          <div style="padding-left: 6px;">
+            <i class="fas fa-search fa-lg ace_control" title="<?php echo $text['label-find_replace']; ?>" onclick="editor.execCommand('replace');"></i>
+          </div>
+          <div style="padding-left: 6px;">
+            <i class="fas fa-chevron-down fa-lg ace_control" title="<?php echo $text['label-go_to_line']; ?>" onclick="editor.execCommand('gotoline');"></i>
+          </div>
+          <div style="padding-left: 15px;">
+            <select id="mode" style="height: 23px; max-width: 70px;" onchange="editor.getSession().setMode('ace/mode/' + this.options[this.selectedIndex].value); focus_editor();">
+              <?php
+              $modes['php'] = 'PHP';
+              $modes['css'] = 'CSS';
+              $modes['html'] = 'HTML';
+              $modes['javascript'] = 'JS';
+              $modes['json'] = 'JSON';
+              $modes['ini'] = 'Conf';
+              $modes['lua'] = 'Lua';
+              $modes['text'] = 'Text';
+              $modes['xml'] = 'XML';
+              $modes['sql'] = 'SQL';
+              $modes['sh'] = 'SH';
+              $modes['smarty'] = 'Smarty';
+              $modes['svg'] = 'SVG';
+              $modes['makefile'] = 'Makefile';
+              $modes['c_cpp'] = 'C';
+              $modes['c_cpp'] = 'CPP';
+              $modes['pgsql'] = 'PGSQL';
+              foreach ($modes as $value => $label) {
+                  $selected = ($value == $mode) ? 'selected' : null;
+                  echo "<option value='".$value."' ".$selected.">".$label."</option>\n";
+              }
+              ?>
+            </select>
+          </div>
+          <div style="padding-left: 4px;">
+            <select id="size" style="height: 23px;" onchange="document.getElementById('editor').style.fontSize = this.options[this.selectedIndex].value; focus_editor();">
+              <?php
+              $sizes = explode(',','9px,10px,11px,12px,14px,16px,18px,20px');
+              if (!in_array($setting_size, $sizes)) {
+                  echo "<option value='".$setting_size."'>".$setting_size."</option>\n";
+                  echo "<option value='' disabled='disabled'></option>\n";
+              }
+              foreach ($sizes as $size) {
+                  $selected = ($size == $setting_size) ? 'selected' : null;
+                  echo "<option value='".$size."' ".$selected.">".$size."</option>\n";
+              }
+              ?>
+            </select>
+          </div>
+          <div style="padding-left: 4px; padding-right: 4px;">
+            <select id="theme" style="height: 23px; max-width: 100px;" onchange="editor.setTheme('ace/theme/' + this.options[this.selectedIndex].value); focus_editor();">
+              <?php
+              $themes['Bright']['chrome']= 'Chrome';
+              $themes['Bright']['clouds']= 'Clouds';
+              $themes['Bright']['crimson_editor']= 'Crimson Editor';
+              $themes['Bright']['dawn']= 'Dawn';
+              $themes['Bright']['dreamweaver']= 'Dreamweaver';
+              $themes['Bright']['eclipse']= 'Eclipse';
+              $themes['Bright']['github']= 'GitHub';
+              $themes['Bright']['iplastic']= 'IPlastic';
+              $themes['Bright']['solarized_light']= 'Solarized Light';
+              $themes['Bright']['textmate']= 'TextMate';
+              $themes['Bright']['tomorrow']= 'Tomorrow';
+              $themes['Bright']['xcode']= 'XCode';
+              $themes['Bright']['kuroir']= 'Kuroir';
+              $themes['Bright']['katzenmilch']= 'KatzenMilch';
+              $themes['Bright']['sqlserver']= 'SQL Server';
+              $themes['Dark']['ambiance']= 'Ambiance';
+              $themes['Dark']['chaos']= 'Chaos';
+              $themes['Dark']['clouds_midnight']= 'Clouds Midnight';
+              $themes['Dark']['cobalt']= 'Cobalt';
+              $themes['Dark']['idle_fingers']= 'idle Fingers';
+              $themes['Dark']['kr_theme']= 'krTheme';
+              $themes['Dark']['merbivore']= 'Merbivore';
+              $themes['Dark']['merbivore_soft']= 'Merbivore Soft';
+              $themes['Dark']['mono_industrial']= 'Mono Industrial';
+              $themes['Dark']['monokai']= 'Monokai';
+              $themes['Dark']['pastel_on_dark']= 'Pastel on dark';
+              $themes['Dark']['solarized_dark']= 'Solarized Dark';
+              $themes['Dark']['terminal']= 'Terminal';
+              $themes['Dark']['tomorrow_night']= 'Tomorrow Night';
+              $themes['Dark']['tomorrow_night_blue']= 'Tomorrow Night Blue';
+              $themes['Dark']['tomorrow_night_bright']= 'Tomorrow Night Bright';
+              $themes['Dark']['tomorrow_night_eighties']= 'Tomorrow Night 80s';
+              $themes['Dark']['twilight']= 'Twilight';
+              $themes['Dark']['vibrant_ink']= 'Vibrant Ink';
+              foreach ($themes as $optgroup => $theme) {
+                  echo "<optgroup label='".$optgroup."'>\n";
+                  foreach ($theme as $value => $label) {
+                      $selected = (strtolower($label) == strtolower($setting_theme)) ? 'selected' : null;
+                      echo "<option value='".$value."' ".$selected.">".$label."</option>\n";
+                  }
+                  echo "</optgroup>\n";
+              }
+              ?>
+            </select>
+          </div>
+        </div>
+      </form>
+      <!-- Editor -->
+	  	<div id="editor" style="text-align: left; width: 100%; height: calc(100% - 30px); font-size: 12px;"></div>
+	  </div>
+    </div>
 
-	//save file
-		<?php key_press('ctrl+s', 'down', 'window', null, null, "save(); return false;", false); ?>
+  <script src="<?php echo PROJECT_PATH; ?>/resources/ace/ace.js" charset="utf-8"></script>
+  <script src="<?php echo PROJECT_PATH; ?>/resources/ace/ext-inline_autocomplete.js"></script>
+  <script>
+    // Load ACE extensions
+    ace.require("ace/ext/language_tools");
 
-	//open file manager/clip library pane
-		<?php key_press('ctrl+q', 'down', 'window', null, null, 'toggle_sidebar(); focus_editor(); return false;', false); ?>
+    // Initialize ACE Editor
+    var editor = ace.edit("editor");
+    editor.setOptions({
+      mode: 'ace/mode/<?=$mode?>',
+      theme: 'ace/theme/'+document.getElementById('theme').options[document.getElementById('theme').selectedIndex].value,
+      selectionStyle: 'text',
+      cursorStyle: 'smooth',
+      showInvisibles: <?=$setting_invisibles?>,
+      displayIndentGuides: <?=$setting_indenting?>,
+      showLineNumbers: <?=$setting_numbering?>,
+      showGutter: true,
+      scrollPastEnd: true,
+      fadeFoldWidgets: <?=$setting_numbering?>,
+      showPrintMargin: false,
+      highlightGutterLine: false,
+      useSoftTabs: false,
+      enableBasicAutocompletion: true,
+	  enableLiveAutocompletion: <?php echo ($mode === 'php') ? 'true' : 'false'; ?>,
+      enableSnippets: <?php echo 'true' ?>
+    });
 
-	//remove certain keyboard shortcuts
-		editor.commands.bindKey("Ctrl-T", null); //new browser tab
-</script>
+    // Prevent form submission with Enter key
+    <?php key_press('enter', 'down', '#current_file', null, null, 'return false;', false); ?>
+
+    // Save file with Ctrl+S
+    <?php key_press('ctrl+s', 'down', 'window', null, null, "save(); return false;", false); ?>
+
+    // Open file manager/clip library pane with Ctrl+Q
+    <?php key_press('ctrl+q', 'down', 'window', null, null, 'toggle_sidebar(); focus_editor(); return false;', false); ?>
+
+    // Remove unwanted shortcuts
+    editor.commands.bindKey("Ctrl-T", null); // Disable new browser tab shortcut
+
+	// Levenshtein distance algorithm
+	function levenshteinDistance(a, b) {
+		let m = a.length, n = b.length;
+		let dp = [];
+		for (let i = 0; i <= m; i++) {
+			dp[i] = [i];
+		}
+		for (let j = 0; j <= n; j++) {
+			dp[0][j] = j;
+		}
+		for (let i = 1; i <= m; i++) {
+			for (let j = 1; j <= n; j++) {
+				if (a[i - 1] === b[j - 1]) {
+					dp[i][j] = dp[i - 1][j - 1];
+				} else {
+					dp[i][j] = Math.min(
+						dp[i - 1][j] + 1,    // deletion
+						dp[i][j - 1] + 1,    // insertion
+						dp[i - 1][j - 1] + 1 // substitution
+					);
+				}
+			}
+		}
+		return dp[m][n];
+	}
+
+	// Example function to find the closest matching class key
+	function findClosestMatch(refName, phpMethods) {
+		let bestMatch = null;
+		let bestDistance = Infinity;
+		let lowerRef = refName.toLowerCase();
+
+		// Loop through all classes in phpMethods
+		for (let key in phpMethods) {
+			// Assume the simple class name is the last segment after a backslash
+			let parts = key.split("\\");
+			let simpleName = parts[parts.length - 1].toLowerCase();
+			let distance = levenshteinDistance(lowerRef, simpleName);
+			if (distance < bestDistance) {
+				bestDistance = distance;
+				bestMatch = key;
+			}
+		}
+		return bestMatch;
+	}
+
+	// Function to fetch PHP class methods using fetch() with promises
+	async function fetch_php_methods() {
+		try {
+			let response = await fetch('/app/edit/resources/get_php_methods.php');
+			if (!response.ok) throw new Error("Failed to load PHP methods.");
+			return await response.json();
+		} catch (error) {
+			console.error("Error fetching PHP methods:", error);
+			return {}; // Return empty object on failure
+		}
+	}
+
+	// Initialize ACE auto-completion after fetching PHP methods
+	async function init_ace_completion() {
+		const php_methods = await fetch_php_methods();
+
+		// Custom completer for PHP class methods
+		var php_class_completer = {
+			getCompletions: function(editor, session, pos, prefix, callback) {
+
+				// Define current_class_name by extracting the basename of the current file without extension.
+				var current_file_path = document.getElementById('current_file').value;
+				var current_file_name = current_file_path.split('/').pop();
+
+				// Remove the extension (everything after the last dot)
+				var current_class_name = current_file_name.replace(/\.[^/.]+$/, "");
+
+				// Get the current line text
+				var line = session.getLine(pos.row);
+
+				// Use regex to detect object (->) or static (::) access.
+				// This regex captures either "$this" or any other word.
+				const object_match = line.match(/(\$this|\w+)\s*->\s*\w*$/);
+				const static_match = line.match(/(self|static|\w+)::\w*$/);
+
+				// Extract the referenced name; if it's "$this", use the current_class_name.
+				var ref_name = object_match ? object_match[1] : (static_match ? static_match[1] : null);
+				if (ref_name === '$this' | ref_name === 'self' | ref_name === 'static') {
+					ref_name = current_class_name;
+				}
+
+				// If not a class, maybe a user function; use the prefix.
+				if (prefix.length > 0) ref_name = prefix;
+
+				if (!ref_name) return callback(null, []);
 
+				// Find the closest matching class using fuzzy matching.
+				var matched_class = findClosestMatch(ref_name, php_methods);
+				if (!matched_class) return callback(null, []);
+
+				// Map the methods of the matched class into completions.
+				var completions = php_methods[matched_class].map(function(method) {
+					// If static syntax is used but the method is not static, skip it.
+					if (static_match !== null && !method.static) {
+						return {};
+					}
+
+					// Map the item to the documentation
+					var item = {
+						caption: method.name + method.params,
+						snippet: method.name + method.params.replace(/\$/g, "\\$"),
+						meta: method.meta,
+						docHTML: method.doc ? method.doc : "No Documentation"
+					};
+
+					// TODO: fix italics
+					// Use italics in pop-up for static methods
+					if (method.static) {
+						item.className = "ace_static_method";
+					}
+
+					//return the mapped json object
+					return item;
+				});
+
+				callback(null, completions);
+			}
+		};
+
+		// Initialize ACE Editor
+		ace.require("ace/ext/language_tools");
+
+		// Replace the current list of completions with our custom one so we don't have every single word in the document listed as a completion option
+		editor.completers = [php_class_completer];
+
+		// Ensure font size is set
+		document.getElementById('editor').style.fontSize = '<?=$setting_size?>';
+		focus_editor();
+	}
+
+	if (<?php echo ($mode === 'php') ? 'true' : 'false'; ?>) {
+		// Run auto-completion setup
+		init_ace_completion();
+	}
+</script>
 </body>
-</html>
+<script>
+	fetch('clip_list.php')
+		.then(response => {
+			if (!response.ok) {
+				throw new Error('Network response was not ok');
+			}
+			return response.text();
+		})
+		.then(html => {
+			document.getElementById('clip_list').innerHTML = html;
+		})
+		.catch(error => {
+			console.error('Error fetching clip_list:', error);
+	});
+
+	async function loadFileList() {
+		try {
+			const response = await fetch('file_list.php');
+			if (!response.ok) {
+				throw new Error('Network response not okay');
+			}
+			const html = await response.text();
+			document.getElementById('file_list').innerHTML = html;
+		} catch (error) {
+			console.error('Error fetching files:', error);
+		}
+	}
+
+	function makeRequest(url, strpost) {
+        var http_request = false;
+
+        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
+            http_request = new XMLHttpRequest();
+            if (http_request.overrideMimeType) {
+                http_request.overrideMimeType('text/xml');
+                // See note below about this line
+            }
+        } else if (window.ActiveXObject) { // IE
+            try {
+                http_request = new ActiveXObject("Msxml2.XMLHTTP");
+            } catch (e) {
+                try {
+                    http_request = new ActiveXObject("Microsoft.XMLHTTP");
+                } catch (e) {}
+            }
+        }
+
+        if (!http_request) {
+            alert('<?=$text['message-give-up']?>');
+            return false;
+        }
+        http_request.onreadystatechange = function() { returnContent(http_request); };
+        if (http_request.overrideMimeType) {
+              http_request.overrideMimeType('text/html');
+        }
+        http_request.open('POST', url, true);
+
+
+        if (strpost.length == 0) {
+            //http_request.send(null);
+            http_request.send('name=value&foo=bar');
+        }
+        else {
+            http_request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
+            http_request.send(strpost);
+        }
+
+    }
+
+    function returnContent(http_request) {
+
+        if (http_request.readyState === 4) {
+            if (http_request.status === 200) {
+				document.getElementById('editor_source').value=http_request.responseText;
+				editor.getSession().setValue(document.getElementById('editor_source').value);
+				editor.gotoLine(1);
+				editor.scrollToLine(1, true, true, function() {});
+				editor.focus();
+            }
+            else {
+                alert('<?=$text['message-problem']?>');
+            }
+        }
+
+    }
+	// ---------------------------------------------
+	// --- http://www.codeproject.com/jscript/dhtml_treeview.asp
+	// --- Name:    Easy DHTML Treeview           --
+	// --- Author:  D.D. de Kerf                  --
+	// --- Version: 0.2          Date: 13-6-2001  --
+	// ---------------------------------------------
+	function Toggle(node) {
+		// Unfold the branch if it isn't visible
+		if (node.nextSibling.style.display == 'none') {
+			node.nextSibling.style.display = 'block';
+		}
+		// Collapse the branch if it IS visible
+		else {
+			node.nextSibling.style.display = 'none';
+		}
+	}
+
+// Load files from server
+	loadFileList();
+
+</script>
+</html>

+ 81 - 0
resources/get_php_methods.php

@@ -0,0 +1,81 @@
+<?php
+
+$project_root = dirname(__DIR__, 3);
+
+require_once  $project_root . '/resources/classes/auto_loader.php';
+global $autoload;
+$autoload = new auto_loader();
+
+$class_methods = [];
+$classes_to_scan = $autoload->get_class_list();
+$interfaces = array_keys($autoload->get_interfaces());
+
+foreach ($classes_to_scan as $class => $path) {
+	// Skip interfaces
+	if (in_array($class, $interfaces)) {
+		continue;
+	}
+
+	// Guard against removed classes
+	if (!class_exists($class)) {
+		continue;
+	}
+
+	// Create the RefectionClass for inspecting class
+	$ref = new ReflectionClass($class);
+
+	// Skip internal classes
+	if ($ref->isInternal()) {
+		continue;
+	}
+
+	$methods = [];
+	foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
+		// Skip __construct
+		if ($method->getName() === '__construct') {
+			continue;
+		}
+
+		// Get method parameters
+		$params = [];
+		foreach ($method->getParameters() as $param) {
+			$type = $param->hasType() ? $param->getType() . " " : "";
+			$default = "";
+			if ($param->isOptional() && $param->isDefaultValueAvailable()) {
+				$default = " = " . var_export($param->getDefaultValue(), true);
+			}
+			$params[] = $type . "$" . $param->getName() . $default;
+		}
+
+		// Get the doc comment and clean it up
+		$doc = $method->getDocComment();
+		if ($doc !== false) {
+			$doc = trim(preg_replace('/(^\/\*\*|\*\/$)/', '', $doc));
+			$doc = preg_replace('/^\s*\*\s?/m', '', $doc);
+		} else {
+			$doc = "";
+		}
+
+		// Get the return type, if any
+		$return_type = "";
+		if ($method->hasReturnType()) {
+			$rt = $method->getReturnType();
+			$return_type = $rt->getName();
+			if ($rt->allowsNull()) {
+				$return_type = "?" . $return_type;
+			}
+		}
+
+		$methods[] = [
+			"name"    => $method->getName(),
+			"params"  => "(" . implode(", ", $params) . ")",
+			"doc"     => $doc,
+			"static"  => $method->isStatic(),
+			"meta"    => $return_type
+		];
+	}
+	$class_methods[$class] = $methods;
+}
+
+header('Content-Type: application/json');
+echo json_encode($class_methods);