<?php $users = ['demo' => '123456']; $realm = 'Edit mode. User required'; ob_start(); ?> <!--aacmsTlbx:start--> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/regular.min.css" integrity="sha512-rVrbAp27ffQyFnzJ/aC5fZv9JgvL6cdB4zsL5HmM+DhJdzThc/F//62SJF+CaGiOZTP35e1p8JGcc+zRRVuhRw==" crossorigin="anonymous" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" integrity="sha512-aOG0c6nPNzGk+5zjwyJaoRUgCdOrfSDhmMID2u4+OIslr0GjpLKo7Xm0Ao3xmpM4T8AmIouRkqwj1nrdVsLKEQ==" crossorigin="anonymous" /> <style> #editor{ display: inline-block; background: white; box-shadow: #1b1e21 2px 2px 4px; position: fixed; top: 10px; left: 10px; padding: 2px; /*cursor: move;*/ } #editor button{ /*display: block;*/ /*width: 100%;*/ padding: 2px; font-size: 0.9em; border: solid silver 1px; } .hovering{ border: aqua dashed 3px; } #editor{ color: black; z-index: 1000; } .tabs{ border-bottom: solid silver 1px; } .tabs, .tabs li{ margin: 0; padding: 0; list-style: none; cursor: default; } .tabs li { display: inline; border: solid silver 1px; padding: 2px 5px 2px 5px; border-radius: 3px 3px 0px 0px; } .tabs li.active{ border-bottom-color: white; } .tab-content{ border: solid silver; border-width: 0 1px 1px 1px; padding: 5px; } .title{ background: silver; margin-bottom: 5px; font-weight: bold; padding-left: 5px; color: black; } .status{ background: #eee; border-width: 1px; border-style: solid; border-color: #222 #ddd #ddd #222; padding: 2px; margin-top: 5px; font-size: 12px; } .selected{ box-shadow: 0 0 0 9999px rgba(0, 0, 255, 0.2); z-index: 900; position: relative; } #editor button.nodisplay, .tabs .nodisplay{ display: none; } </style> <div id="editor" style="visibility: hidden"> <div class="title">Toolbox</div> <div class="body"> <ul class="tabs"> <li data-target=".fileMenu" class="active context" >File</li> <li data-target=".navMenu" class="context">Traverse</li> <li data-target=".editMenu" class="context">Edit</li> <li data-target=".textMenu" class="context">Text</li> <li data-target=".imageMenu" class="context">Image</li> <li data-target=".linkMenu" class="context">Link</li> <li data-target=".imgLinkMenu" class="context">Img Lnk</li> </ul> <div class="tab-content fileMenu"> <button data-role="save">Save</button> <button data-role="saveas">Save as</button> <button data-role="delete" disabled>Delete</button> <button data-role="reload">Reload</button> <button data-role="original">Original</button> </div> <div class="tab-content navMenu"> <button data-role="parent">Parent</button> <button data-role="prev">Previous</button> <button data-role="next">Next</button> <button data-role="down">Dive</button> </div> <div class="tab-content imageMenu"> <form> <table> <tr><td>URL</td><td><input type="text" name="url"> </td></tr> <tr><td>Title</td><td><input type="text" name="title"> </td></tr> <tr><td>Width</td><td><input type="text" name="width"> </td></tr> <tr><td>Height</td><td><input type="text" name="height"> </td></tr> </table> </form> </div> <div class="tab-content linkMenu"> <form> <table> <tr><td>Label</td><td><input type="text" name="label"> </td></tr> <tr><td>URL</td><td><input type="text" name="href"> </td></tr> <tr><td>Title</td><td><input type="text" name="title"> </td></tr> <tr><td>Target</td><td><input type="text" name="target"> </td></tr> </table> </form> </div> <div class="tab-content editMenu"> <button data-role="copy">Copy</button> <button data-role="cut">Cut</button> <button data-role="remove">Delete</button> <button data-role="before" disabled>Insert before</button> <button data-role="after" disabled>Insert after</button> </div> <div class="tab-content textMenu"> <button data-role="bold"><strong>B</strong></button> <button data-role="italic"><i>I</i></button> <button data-role="underline"><u>U</u></button> <button data-role="strike"><strike>S</strike></button> <button data-role="fontplus">F+</button> <button data-role="fontminus">F-</button> </div> <button class="context" style="margin-top: 10px; background: greenyellow" data-role="finish">Finish edit</button> </div> <div class="status"><a href="https://softaccel.net/aacms">AaCMS</a> by Softaccel.net</div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" integrity="sha512-uto9mlQzrs59VwILcLiRYeLKPPbS/bT71da/OEBYEwcdNUk8jYIy+D176RYoop1Da+f9mvkYrmj5MCLZWEtQuA==" crossorigin="anonymous"></script> <script> (function (editorSel) { let queryParas = (function () { let tmp = location.toString().split("?"); if (tmp.length < 2) return; tmp = tmp[1].split("&"); let paras = {}; tmp.forEach(function (item) { let t = item.indexOf("="); if (t === -1) paras[item] = null; else paras[item.substr(0,t)] = item.substr(t+1); }); return paras; })(); function tabs(sel) { let $menu = $(sel); let active = null; $menu.children("li").each(function (){ if($(this).on("click",function () {activate(this);}).hasClass("active")) active = this; }); function reset() { $menu.children("li").each( function(){ $($(this).removeClass("active").data("target")).css("display","none"); }); } function activate(el) { reset(); $($(el).addClass("active").data("target")).css("display","block") } activate(active); } $(document).ready(function () { toolBox(editorSel); tabs(".tabs"); $(".tabs [data-target='.fileMenu']").removeClass("nodisplay"); }); let activeMode = null; let editorLbl = "editor"; let activeElement = null; let clipBoard = null; function toolBox() { $(editorSel).find("[data-role]").each(function () { let $el = $(this); switch ($el.data("role")) { case "save": $el.on("click",saveFile); break; case "saveas": $el.on("click",()=>saveFileAs()); break; case "delete": // $el.on("click",dete); break; case "reload": $el.on("click",function (){location.reload()}); break; case "original": $el.on("click",function(){location = queryParas.file}); break; case "logout": $el.on("click",function(){ $.get({ type: "GET", url: location.pathname, async: false, username: "fake", password: "123456", headers: { "Authorization": "Basic xxx" } }) .done(function(){ // If we don't get an error, we actually got an error as we expect an 401! }) .fail(function(){ // We expect to get an 401 Unauthorized error! In this case we are successfully // logged out and we redirect the user. window.location = queryParas.file; }); return false; }); break; case "parent": $el.on("click",function(){ console.log("parent",activeElement.parent()); if(activeElement.parent().prop("tagName")==="BODY") return; setActive(activeElement.parent()); }); break; case "prev": $el.on("click",function(){ console.log("prev",activeElement.prev()); if(activeElement.prev().length) setActive(activeElement.prev()); }); break; case "next": $el.on("click",function(){ console.log("next",activeElement.next()); if(activeElement.next().length) setActive(activeElement.next()); }); break; case "down": $el.on("click",function(){ console.log("down",activeElement.children()); if(activeElement.children().length) setActive(activeElement.children()[0]); }); break; case "copy": $el.on("click",function(){ console.log($(editorSel + " [data-role=before]")); clipBoard = activeElement.clone(true).removeClass("selected"); $(editorSel + " [data-role=after]").attr("disabled",null); $(editorSel + " [data-role=before]").attr("disabled",null); }); break; case "cut": $el.on("click",function(){ clipBoard = activeElement.remove().removeClass("selected"); $(editorSel + " [data-role=after]").attr("disabled",null); $(editorSel + " [data-role=before]").attr("disabled",null); setInactive(); }); break; case "before": $el.on("click",function(){ clipBoard.insertBefore(activeElement); $(editorSel + " [data-role=after]").attr("disabled",true); $(editorSel + " [data-role=before]").attr("disabled",true); }); break; case "remove": $el.on("click",function(){ activeElement.remove(); setInactive(); }); break; case "after": $el.on("click",function(){ clipBoard.insertAfter(activeElement); $(editorSel + " [data-role=after]").attr("disabled",true); $(editorSel + " [data-role=before]").attr("disabled",true); }); break; case "finish": $el.on("click",function(){ console.log("finish",activeElement); if(!activeElement) return; setInactive(); }); break; case "bold": $el.on("click",function(){ document.execCommand('bold',false,null); }); break; case "bold": $el.on("click",function(){ document.execCommand('bold',false,null); }); break; case "italic": $el.on("click",function(){ document.execCommand('italic',false,null); }); break; case "underline": $el.on("click",function(){ document.execCommand('underline',false,null); }); break; case "strike": $el.on("click",function(){ document.execCommand('strikeThrough',false,null); }); break; case "fontplus": $el.on("click",function(){ document.execCommand('increaseFontSize',false,null); }); break; case "fontminus": $el.on("click",function(){ document.execCommand('decreaseFontSize',false,null); }); break; } }); hideTabs(); $(editorSel) .draggable({ handle: ".title", stop: function (ev,ui) { localStorage.setItem("aacmsTlbxTop",ui.position.top); localStorage.setItem("aacmsTlbxLeft",ui.position.left); } }) .css({ top:localStorage.getItem("aacmsTlbxTop")?localStorage.getItem("aacmsTlbxTop"):100, left:localStorage.getItem("aacmsTlbxLeft")?localStorage.getItem("aacmsTlbxLeft"):100, visibility: "visible" }); $("*").on("mouseover",function (e) { e.originalEvent.stopPropagation(); removeHovering(); if(activeElement!==null) return ; if( $(e.currentTarget)[0]===$(editorSel)[0]) return ; if($(e.currentTarget).parents("#"+$(editorSel).attr("id")).length>0) return ; if(["BODY","HTML"].indexOf(e.currentTarget.tagName)!==-1) return; addHovering($(e.currentTarget)); }); } function hideTabs() { $(editorSel).find(".context").addClass("nodisplay"); } function removeHovering() { $(".hovering").filter("[contenteditable]").attr("contenteditable",null); $(".hovering").removeClass("hovering"); } let onSetInactive = new Function(); function setInactive() { onSetInactive(); $(editorSel+" .context").addClass("nodisplay"); $(".tabs [data-target='.fileMenu']").removeClass("nodisplay").click(); $(activeElement).removeClass("selected"); activeElement = null; } function setActive(el) { let frm; activeElement = $(el); $(".selected").removeClass("selected"); activeElement.addClass("selected"); console.log("set active",activeElement); hideTabs(); $(editorSel+" [data-target='.editMenu']").removeClass("nodisplay"); $(editorSel +" [data-target='.navMenu']").removeClass("nodisplay"); $(editorSel+" [data-target='.navMenu']").click(); $(editorSel+" [data-role='finish']").removeClass("nodisplay"); switch(activeElement.prop("tagName").toLowerCase()) { case "img": $(editorSel+" [data-target='.imageMenu']").removeClass("nodisplay").click(); frm = $(editorSel +" .imageMenu").find("form")[0]; frm.url.value = activeElement.attr("src"); frm.title.value = activeElement.attr("title")?activeElement.attr("title"):""; frm.width.value = activeElement.attr("width")?activeElement.attr("width"):""; frm.height.value = activeElement.attr("height")?activeElement.attr("height"):""; onSetInactive = function () { if(frm.url.value) activeElement.attr("src",frm.url.value); activeElement.attr("title",frm.title.value?frm.title.value:null); activeElement.attr("width",frm.width.value?frm.width.value:null); activeElement.attr("height",frm.height.value?frm.height.value:null); onSetInactive = new Function(); }; break; case "a": $(editorSel+" [data-target='.linkMenu']").removeClass("nodisplay").click(); frm = $(editorSel +" .linkMenu").find("form")[0]; frm.label.value = activeElement.html(); frm.href.value = activeElement.attr("href"); frm.title.value = activeElement.attr("title")?activeElement.attr("title"):""; frm.target.value = activeElement.attr("target")?activeElement.attr("target"):""; onSetInactive = function () { if(frm.href.value) activeElement.attr("href",frm.href.value); if(frm.label.value) activeElement.html(frm.label.value); activeElement.attr("title",frm.title.value?frm.title.value:null); activeElement.attr("target",frm.target.value?frm.target.value:null); onSetInactive = new Function(); }; break; case "table": break; case "form": break; case "fieldset": break; case "input": break; case "button": break; default: $(editorSel+" [data-target='.textMenu']").removeClass("nodisplay").click(); activeElement.attr("contentEditable",true); onSetInactive = function () { activeElement.attr("contentEditable",false); onSetInactive = new Function(); } } } function addHovering($el) { $el.off("click"); if(activeElement!==null) return; // console.log("hover",$el); // if(["FORM","FIELDSET","IMG","UL","OL","DL"].indexOf($el.prop("tagName"))!==-1) { // $el.removeClass("hovering"); // console.log("hover"); // return; // } // $el.addClass("hovering"); $el.on("click",function (e) { e.originalEvent.stopPropagation(); e.stopPropagation(); setActive($el); return false; }); } function saveFile() { saveFileAs(queryParas.file,true); } function saveSwap() { saveFileAs("."+queryParas.file+".swap",()=>console.log("swap file saved")); } function saveFileAs(fname,overwrite,handleOk) { if(fname===undefined) { // fname = prompt('New file name'); let $form = $("<form>").append("<label>File name</label>").append("<input name='fname' type=text style='width: 100%'>"); $form.dialog({ title: "Save as", buttons:{ OK: function () { if($form[0].fname.value) { saveFileAs($form[0].fname.value); } $(this).dialog("close").remove(); }, Cancel: function () { $(this).dialog("close").remove(); } } }); return; } if(fname===null) return; overwrite = overwrite?"confirm":""; let html = document.documentElement.outerHTML; let start = html.indexOf("<\!--aacmsTlbx:start-->"); let stop = html.indexOf("<\!--aacmsTlbx:stop-->"); html = html.substr(0,start)+html.substr(stop+21); $.ajax({ url:location.pathname+"?file="+fname+"&"+ overwrite, method: "put", data: html }).done(function (data,xhr) { if(handleOk) return handleOk(data); if(data!==queryParas.file) { // alert(); $("<div>") .attr("title","Confirm") .html("File succesfully saved as "+data+".<br>Do you want to edit the new file?") .dialog({ modal: true, buttons: { No: function() { $(this ).dialog( "close" ).remove(); }, Yes: function() { location.href = location.pathname+"?file=" + data; } } }); } }).fail(function (xhr) { console.log(xhr); switch(xhr.status) { case 409: $("<div title='Confirm overwrite'>File "+fname+" already exists. Do you want to overwrite it?</div>") .dialog({ modal: true, buttons: { No: function() { $( this ).dialog( "close" ).remove(); }, Yes: function() { $( this ).dialog( "close" ).remove(); saveFileAs(fname,true); } } }); break; case 406: $("<div title='Error'>"+fname+" already exists and is a directory. You are not allowed to use its name.</div>") .dialog({modal:true,close:function () {$(this).remove();}}); break; case 503: $("<div title='Error'>"+fname+" is not writable. Please update settings on server.</div>") .dialog({modal:true,close:function () {$(this).remove();}}); } }); } function removeFile() { if(!confirm('Are you sure you want to remove this file?')) return; $.ajax({ url:"editor.php?file="+fname, method: "delete", data: html }).done(function (data,xhr) { console.log(data,xhr); }).fail(function (xhr) { console.log(xhr); }); } })("#editor"); </script> <!--aacmsTlbx:stop--> <?php $editorTxt = ob_get_contents(); ob_end_clean(); //$editorTxt = file_get_contents("editor.html"); if (empty($_SERVER['PHP_AUTH_DIGEST'])) { header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Digest realm="'.$realm. '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); die(sprintf('<html><head><title></title></head><body><script>window.location="%s"</script></body></html>',$_GET["file"])); } if (isset($_GET["logout"])) { header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Digest realm="'.$realm. '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); die(sprintf('<html><head><title></title></head><body><script>window.location="%s"</script></body></html>',$_GET["file"]?$_GET["file"]:"/")); } if(!isset($_GET["file"]) || empty($_GET["file"])) { http_response_code(400); die("Invalid request: filename missing"); } //print_r($_SERVER['PHP_AUTH_DIGEST']); // analyze the PHP_AUTH_DIGEST variable if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || !isset($users[$data['username']])) {# header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Digest realm="'.$realm. '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); die('Wrong Credentials!'); } // generate the valid response $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]); $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']); $valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2); if ($data['response'] != $valid_response) die('Wrong Credentials!'); function http_digest_parse($txt) { // protect against missing data $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1); $data = array(); $keys = implode('|', array_keys($needed_parts)); preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER); foreach ($matches as $m) { $data[$m[1]] = $m[3] ? $m[3] : $m[4]; unset($needed_parts[$m[1]]); } return $needed_parts ? false : $data; } switch($_SERVER["REQUEST_METHOD"]) { case "PUT": if (is_dir($_GET["file"])) { http_response_code("406"); die($_GET["file"]." is a directory"); } $exists = file_exists($_GET["file"]); if(!isset($_GET["confirm"]) && $exists) { http_response_code("409"); die("File already exists"); } $dir = dirname($_GET["file"]); if(!is_dir($dir)) mkdir($dir)."\n"; $postdata = file_get_contents("php://input"); if(($exists && !is_writable($_GET["file"])) || !is_writable($dir)) { http_response_code(503); } file_put_contents($_GET["file"], $postdata); echo $_GET["file"]; break; case "DELETE": if (is_file($_GET["file"])) { unlink($_GET["file"]); } break; case "GET": if (!$_GET["file"] || !is_file($_GET["file"])) http_response_code(404); $res = file_get_contents($_GET["file"]); $editor = $editorTxt; $res = preg_replace("/\<\/body\>/i", "$editor</body>", $res); echo $res; }