0
|
1
|
|
2 package nabble.view.web.template;
|
|
3
|
|
4 import fschmidt.util.java.HtmlUtils;
|
|
5 import fschmidt.util.servlet.JtpContext;
|
|
6 import nabble.model.DailyNumber;
|
|
7 import nabble.model.Node;
|
|
8 import nabble.model.Site;
|
|
9 import nabble.model.User;
|
|
10 import nabble.modules.ModuleManager;
|
|
11 import nabble.naml.compiler.CompileException;
|
|
12 import nabble.naml.compiler.Macro;
|
|
13 import nabble.naml.compiler.Meaning;
|
|
14 import nabble.naml.compiler.Source;
|
|
15 import nabble.view.lib.Jtp;
|
|
16 import nabble.view.lib.Permissions;
|
|
17 import nabble.view.lib.Shared;
|
|
18 import org.apache.commons.fileupload.FileItem;
|
|
19 import org.apache.commons.fileupload.FileUploadException;
|
|
20
|
|
21 import javax.servlet.ServletException;
|
|
22 import javax.servlet.http.HttpServlet;
|
|
23 import javax.servlet.http.HttpServletRequest;
|
|
24 import javax.servlet.http.HttpServletResponse;
|
|
25 import java.io.ByteArrayOutputStream;
|
|
26 import java.io.IOException;
|
|
27 import java.io.InputStream;
|
|
28 import java.io.PrintWriter;
|
|
29 import java.io.StringWriter;
|
|
30 import java.nio.charset.Charset;
|
|
31 import java.nio.charset.CharsetEncoder;
|
|
32 import java.util.HashMap;
|
|
33 import java.util.List;
|
|
34 import java.util.Map;
|
|
35 import java.util.TreeMap;
|
|
36 import java.util.zip.ZipEntry;
|
|
37 import java.util.zip.ZipInputStream;
|
|
38
|
|
39
|
|
40 public final class NamlEditor extends HttpServlet {
|
|
41
|
|
42 protected void service(HttpServletRequest request, HttpServletResponse response)
|
|
43 throws ServletException, IOException
|
|
44 {
|
|
45 User user = Jtp.getUser(request, response);
|
|
46 if (user == null) {
|
|
47 simpleLoginPage("You must login to edit the NAML code of this application.", request, response);
|
|
48 return;
|
|
49 }
|
|
50 boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
|
|
51 boolean isSysAdmin = Permissions.isSysAdmin(user);
|
|
52
|
|
53 if (!isSiteAdmin && !isSysAdmin) {
|
|
54 simpleLoginPage("You must login to edit the NAML code of this application.", request, response);
|
|
55 return;
|
|
56 }
|
|
57 buildPage(request, response);
|
|
58 }
|
|
59
|
|
60 public static void buildPage(HttpServletRequest request, HttpServletResponse response)
|
|
61 throws ServletException, IOException
|
|
62 {
|
|
63 Site site = Jtp.getSite(request);
|
|
64 Map<String,String> tweaks = new TreeMap<String,String>(site.getCustomTweaks());
|
|
65 Node rootNode = site.getRootNode();
|
|
66 Exception exception = site.getTweakException();
|
|
67
|
|
68 String macroDef = null;
|
|
69 String selectedFileName = null;
|
|
70 String overridenBody = null;
|
|
71 String meaningId = request.getParameter("id");
|
|
72 if (meaningId != null) {
|
|
73 Meaning meaning = site.getProgram().getMeaning(meaningId);
|
|
74 Macro macro = (Macro) meaning;
|
|
75
|
|
76 String macroType = macro.getType().toString().toLowerCase();
|
|
77 String macroBody = macro.element.toString();
|
|
78 int posOpen = macroBody.indexOf('<');
|
|
79 int posClose = macroBody.indexOf('>', posOpen+1);
|
|
80 macroDef = macroBody.substring(posOpen, posClose);
|
|
81 macroDef = macroDef.replaceFirst("<"+macroType, "<override_"+macroType);
|
|
82 int posClosingTag = macroBody.lastIndexOf("</");
|
|
83 String closingTag = "</override_" + macroType + '>';
|
|
84 overridenBody = macroDef + macroBody.substring(posClose, posClosingTag) + closingTag;
|
|
85 for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
|
|
86 String name = entry.getKey();
|
|
87 String content = entry.getValue();
|
|
88 if (content.contains(macroDef)) {
|
|
89 selectedFileName = name;
|
|
90 break;
|
|
91 }
|
|
92 }
|
|
93 }
|
|
94
|
|
95 String previousUrl = request.getParameter("prev");
|
|
96
|
|
97 PrintWriter out = response.getWriter();
|
|
98
|
|
99 out.print( "\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n <head>\n <META NAME=\"robots\" CONTENT=\"noindex,nofollow\"/>\n <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\n <title>Naml Editor - " );
|
|
100 out.print( (rootNode.getSubject()) );
|
|
101 out.print( "</title>\n " );
|
|
102 Shared.head(request, response, false);
|
|
103 out.print( "\n <style type=\"text/css\">\n div.main-content {\n position:absolute;\n top:5.2em;\n bottom:1.3em;\n right:.8em;\n left:.8em;\n }\n div.big-title {\n margin:.5em 0;\n }\n table.vertical-control {\n border-collapse:collapse;\n width:100%;\n height:100%;\n position:absolute;\n top:0;\n bottom:0;\n }\n table.vertical-control td {\n padding:0;\n vertical-align:top;\n }\n table.vertical-control td.options {\n width:13em;\n padding-top:.2em;\n }\n table.vertical-control td.details {\n padding: .2em;\n }\n ul.vertical-control {\n width:100%;\n list-style-type:none;\n padding:0;\n margin:.5em 0;\n }\n ul.vertical-control li {\n padding: .3em;\n white-space:nowrap;\n cursor: pointer;\n font-weight:normal;\n width:13em;\n overflow: hidden;\n border-top-left-radius:5px;\n border-bottom-left-radius:5px;\n -moz-border-radius-topleft:5px;\n -moz-border-radius-bottomleft:5px;\n }\n ul.vertical-control li.selected {\n cursor: default;\n font-weight:bold;\n }\n #options-ul {\n overflow-x:hidden !important;\n overflow-y:auto !important;\n }\n div.more {\n position:absolute;\n left:0;\n width:13em;\n bottom:0;\n border-top: 1px dotted #ddd;\n }\n img.toolbar-icon {\n vertical-align: -25%;\n }\n div.default {\n position: absolute;\n top: .2em;\n bottom: 0;\n margin-bottom: .2em;\n height: auto;\n overflow: auto;\n }\n div.advanced-option-body {\n padding: 0 1.5em 1.5em;\n }\n\n .CodeMirror-line-numbers {\n font-family: verdana, arial, sans-serif;\n font-size: 11pt;\n width: 2.2em;\n color: #aaa;\n background-color: #eee;\n text-align: right;\n padding-right: .3em;\n line-height: normal;\n }\n\n span.xml-tagname {color: #A0B;}\n span.xml-attribute {color: #281;}\n span.xml-punctuation {color: black;}\n span.xml-attname {color: #00F;}\n span.xml-comment {color: #A70;}\n span.xml-cdata {color: #48A;}\n span.xml-processing {color: #999;}\n span.xml-entity {color: #A22;}\n span.xml-error {color: #F00 !important;}\n span.xml-text {color: black;}\n\n div.status-label {\n float:right;\n font-weight:bold;\n width:6em;\n text-align:center;\n line-height:1.2em;\n margin:-.2em 0 0 .5em;\n padding:.35em;\n }\n div.no-error {\n color: #656565;\n cursor: text;\n }\n div.has-error {\n color: white;\n cursor: pointer;\n background: url('/gradients/v20_EE9999_EE5555') #EE5555 repeat-x;\n }\n #error-message {\n position:absolute;\n display:none;\n z-index:1000;\n padding:0 1em 1em;\n margin-top:.2em;\n top:5em;\n left:10%;\n bottom:10%;\n right:.7em;\n overflow:auto;\n display:none;\n }\n </style>\n\n <script src=\"/util/codemirror/js/codemirror.js\"></script>\n <script src=\"/util/codemirror/js/highlight.js\"></script>\n <script src=\"/util/codemirror/js/stringstream.js\"></script>\n <script src=\"/util/codemirror/js/tokenize.js\"></script>\n <script src=\"/util/codemirror/js/parsexml.js\"></script>\n\n <script type=\"text/javascript\">\n var configFileName = \"" );
|
|
104 out.print( (ModuleManager.CONFIGURATION_TWEAK) );
|
|
105 out.print( "\";\n function setVisible(name, show) {\n if (show) $('#'+name).show();\n else $('#'+name).hide();\n };\n var modified = [];\n function setModified(id) {\n if (modified.indexOf(id) >= 0)\n return;\n modified.push(id);\n if (id == 'removed')\n return;\n var $tab = $('#tab_'+id);\n var html = $tab.html();\n $tab.html(html.replace(/file\\.png/, 'file_modified.png'));\n setVisible('modified-warning',true);\n };\n function resetModified() {\n for (var i = 0; i < modified.length; i++) {\n if (modified[i] == 'removed')\n continue;\n var $tab = $('#tab_'+modified[i]);\n var html = $tab.html();\n $tab.html(html.replace(/file_modified\\.png/, 'file.png'));\n }\n setVisible('modified-warning',false);\n modified = [];\n }\n window.onbeforeunload = function() {\n if (modified.length > 0)\n return 'You have unsaved changes.\\nDo you really want to leave without saving?';\n };\n var fileImg = '<img src=\"/images/file.png\" style=\"vertical-align:-20%\"/> ';\n Array.prototype.remove = function(s){\n for(i=0; i<this.length; i++){\n if (s == this[i])\n this.splice(i, 1);\n }\n }\n var advancedOptions = {\n id: 'advanced_options',\n name: 'Advanced Options',\n content: null\n };\n function isDefaultTab(f) {\n return f == advancedOptions;\n };\n\n var files = [];\n " );
|
|
106
|
|
107 int i = 0;
|
|
108 for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
|
|
109 String name = entry.getKey();
|
|
110 String content = entry.getValue();
|
|
111
|
|
112 out.print( "\nfiles.push({\nid: " );
|
|
113 out.print( (i++) );
|
|
114 out.print( ",\nname: \"" );
|
|
115 out.print( (HtmlUtils.javascriptStringEncode(name)) );
|
|
116 out.print( "\",\ncontents: \"" );
|
|
117 out.print( (HtmlUtils.javascriptStringEncode(content)) );
|
|
118 out.print( "\"\n});\n" );
|
|
119 }
|
|
120 out.print( "\nfunction addFile(file) {\n$('#options-ul').append('<li id=\"tab_'+file.id+'\">'+fileImg+file.name+'</li>');\nclickableTab(file);\n\nvar textareaId = 'textarea_'+file.id;\n$('#details').append('<div id=\"div_'+file.id+'\" style=\"display:none\"><textarea id=\"'+textareaId+'\"></textarea></div>');\nvar $textarea = $('#'+textareaId);\n$textarea.val(file.contents);\n\nfile.editor = CodeMirror.fromTextArea(textareaId, {\n parserfile: \"parsexml.js\",\n stylesheet: \"/util/codemirror/css/xmlcolors.css\",\n path: \"/util/codemirror/js/\",\n lineNumbers: true,\n indentUnit: 4,\n onChange: function() { setModified(file.id); }\n});\n\nvar $wrapping = $textarea.next();\n$wrapping.css({\n position: 'absolute',\n top: '2.5em',\n bottom: 0,\n marginBottom: '.2em',\n height: 'auto'\n});\n$wrapping.attr('id', 'wrapping_'+file.id)\n$wrapping.addClass('no-bg-color');\n\n/* File Toolbar */\nvar toolbar = '<div id=\"toolbar_'+file.id+'\" class=\"shaded-bg-color\" style=\"padding:.2em .4em .3em;text-align:right\">';\ntoolbar += '<div class=\"float-left\" style=\"font-style:italic;padding:.3em\">'+file.name+'</div>';\ntoolbar += '<button class=\"toolbar\" style=\"padding:.1em .3em\" onclick=\"renameFile()\"><img class=\"toolbar-icon\" src=\"/images/edit_sm.png\"/> Rename</button> ';\ntoolbar += '<button class=\"toolbar\" style=\"padding:.1em .3em\" onclick=\"removeFile()\"><img class=\"toolbar-icon\" src=\"/images/remove_sm.png\"/> Remove</button>';\ntoolbar += '</div>';\n$textarea.after(toolbar);\n};\nfunction clickableTab(f) {\nvar $tab = $('#tab_'+f.id);\n$tab.click(function() {\n select(f);\n});\ntabHover($tab);\n};\nfunction tabHover($tab) {\n$tab.hover(function() {\n if (!$(this).hasClass('selected'))\n $(this).addClass('light-bg-color');\n },\n function() {\n $(this).removeClass('light-bg-color');\n }\n);\n};\n\nvar initialized = [];\nfunction showFile(file) {\n$('#div_'+file.id).show();\nif (initialized.indexOf(file.id) == -1) {\n var cc = Nabble.get('toolbar_'+file.id);\n $('#wrapping_'+file.id).css('top', $(cc).outerHeight());\n var frame = window.frames[window.frames.length-1];\n var doc = window.document;\n $(frame.document).click(function() { $(doc).click(); });\n initialized.push(file.id);\n}\n};\n\nvar selected = null;\nfunction select(f) {\nif (selected != f) {\n $('li').removeClass('selected dark-bg-color');\n var $tab = $('#tab_'+f.id);\n $tab.addClass('selected dark-bg-color');\n $('#details').children().hide();\n showFile(f);\n selected = f;\n layout();\n} else if (f == null || files.length == 0)\n setVisible('no-selection',true);\n};\nfunction layout() {\nif (selected) {\n var detailsWidth = $('#details').width();\n if (!isDefaultTab(selected)) {\n var $div = $('#div_'+selected.id);\n var $lineNumbers = $('div.CodeMirror-line-numbers', $div);\n var width = detailsWidth - $lineNumbers.width() - 5;\n $('div.CodeMirror-wrapping', $div).width(Math.round(width)+'px');\n } else {\n $('div.default').width(Math.round(detailsWidth - 30)+'px');\n }\n}\nvar $ul = $('#options-ul');\nvar uTop = $ul.position().top;\nvar mTop = $('#more').position().top;\n$ul.height(mTop-uTop-10);\n};\nfunction newFile() {\nvar name = prompt('Name of the file:');\nif (name == configFileName) {\n alert('You cannot create a file with this name.');\n return;\n}\nif (name) {\n var f = {\n id: new Date().getTime(),\n name: name,\n contents: ''\n };\n files.push(f);\n addFile(f);\n select(f);\n setModified(f.id);\n}\n};\nfunction renameFile() {\nif (selected) {\n name = prompt(\"Name of the file:\", selected.name);\n if (name && name !='null' && Nabble.trim(name).length > 0) {\n selected.name = Nabble.trim(name);\n $tab = $('#tab_'+selected.id).html(fileImg + name);\n $toolbar = $('#toolbar_'+selected.id+' div').html(name);\n setModified(selected.id);\n }\n}\n};\nfunction removeFile() {\nif (selected && confirm(\"Do you really want to delete \" + selected.name + \"?\")) {\n var i = files.indexOf(selected);\n files.remove(selected);\n setModified('removed');\n $('#tab_'+selected.id+',#div_'+selected.id).remove();\n i = i > 0? i-1 : files.length > 0? 0 : -1;\n if (i == -1)\n setVisible('no-selection',true);\n else\n select(files[i]);\n}\n};\nfunction saveChanges() {\nnotice('Saving data... please wait');\nvar params = {};\nparams['fileCount'] = files.length;\nvar c = 0;\nfor (var i=0; i < files.length; i++) {\n var name = files[i].name;\n params['name'+c] = name;\n params['contents'+c] = files[i].editor.getCode();\n c++;\n}\n$.post(\"./NamlEditor$Save.jtp\", params,\n function(data){\n if (data == null || data.length == 0) {\n setError(null);\n notice('Data successfully saved, no errors found', 2000, 2000);\n resetModified();\n } else {\n setError(data);\n notice('Data NOT saved, please fix errors', 5000, 2000);\n }\n }\n);\n};\n\nfunction getOrCreateFile(name) {\nfor (var i=0; i<files.length; i++) {\n if (files[i].name == name)\n return files[i];\n}\nfiles.push({\n id: new Date().getTime(),\n name: name,\n contents:''\n});\nreturn files[files.length-1];\n};\nfunction getLineNumber(s, token) {\nvar line = 1;\nvar pos = s.indexOf(token);\nfor (var i=0; i < pos; i++) {\n if (s.charAt(i) == '\\n')\n line++;\n}\nreturn line;\n};\nfunction startDefaultTabs() {\n/* Advanced options tab*/\nvar $tabs = $('#tab_advanced_options');\n$tabs.click(function() { select(advancedOptions); });\n};\nfunction setError(err) {\nvar $errorMsg = $('#error-message');\n$errorMsg.html(err? err : '');\nvar $status = $('#error-status');\nif (err) {\n var visible = false;\n function errorClick(e) {\n e.stopPropagation();\n if (visible) {\n $errorMsg.slideUp('fast', function() { $errorMsg.hide() });\n $status.html('View Error');\n } else {\n $errorMsg.slideDown('fast');\n $status.html('Hide Error');\n }\n visible = !visible;\n };\n $status.addClass('has-error').removeClass('no-error').html('View Error').click(errorClick).click();\n $(document).click(function(e) {\n if ($(e.target).attr('id') == 'error-message')\n return;\n var $parents = $(e.target).parents();\n if ($parents.hasClass('error-message'))\n return;\n visible = true;\n $status.click();\n });\n} else {\n $status.addClass('no-error').removeClass('has-error').html('No Errors').unbind('click');\n $(document).unbind('click');\n}\n};\nvar error = " );
|
|
121 out.print( (exception == null? "null" : "\"" + HtmlUtils.javascriptStringEncode(getErrorMessage(exception)) + "\"") );
|
|
122 out.print( ";\n\nfunction startEditors() {\nnotice('Starting editor... please wait');\nstartDefaultTabs();\nsetError(error);\nlayout();\nvar selectedFileName = " );
|
|
123 out.print( (selectedFileName == null? "null" : "\""+selectedFileName+"\"") );
|
|
124 out.print( ";\nvar selectedFile = files[0];\nvar macroDef = " );
|
|
125 out.print( (macroDef == null? "null" : "\""+HtmlUtils.javascriptStringEncode(macroDef)+"\"") );
|
|
126 out.print( ";\nvar overridenBody = " );
|
|
127 out.print( (overridenBody == null? "null" : "\""+HtmlUtils.javascriptStringEncode(overridenBody)+"\"") );
|
|
128 out.print( ";\nvar macroLineNumber = 0;\nif (selectedFileName != null) {\n var f = getOrCreateFile(selectedFileName);\n macroLineNumber = getLineNumber(f.contents,macroDef);\n} else if (overridenBody != null) {\n var f = getOrCreateFile('tweaks');\n selectedFileName = f.name;\n f.contents = Nabble.trim(f.contents);\n f.contents = (f.contents.length > 0? f.contents + '\\n\\n':'') + overridenBody;\n macroLineNumber = getLineNumber(f.contents,macroDef);\n} else {\n var f = files.length == 0? getOrCreateFile('tweaks') : files[0];\n selectedFileName = f.name;\n}\nfor (var i=0; i < files.length; i++) {\n addFile(files[i]);\n if (files[i].name == selectedFileName)\n selectedFile = files[i];\n}\nselect(selectedFile);\nif (selectedFile && macroLineNumber > 0) {\n function goToLine() {\n var $lines = $('#wrapping_'+selectedFile.id+' div.CodeMirror-line-numbers').children();\n if (selectedFile.editor.editor && $lines.size() >= macroLineNumber) {\n var c = 1;\n $lines.each(function() {\n var t = $(this).html();\n if (c == macroLineNumber) {\n $(this).css('font-weight','bold').addClass('highlight');\n setTimeout(function(){\n selectedFile.editor.jumpToLine(Number(macroLineNumber));\n notice('Ready', 2000, 1000);\n }, 150);\n return false;\n }\n if (t != \" \")\n c++;\n });\n } else\n setTimeout(goToLine, 50);\n };\n goToLine();\n} else\n notice('Ready', 2000, 1000);\n$(window).resize(layout);\n};\n\nvar started = false;\n$(document).ready(function() {\nif (!started) {\n started = true;\n startEditors();\n}\n});\n</script>\n</head>\n<body>\n<div id=\"notice\" class=\"notice rounded-bottom\"></div>\n" );
|
|
129 Shared.minHeader(request, response, site.getRootNode(), null, false);
|
|
130 out.print( "\n\n<div class=\"shaded-bg-color\" style=\"padding:.5em\">\n<div id=\"error-status\" class=\"status-label rounded no-error\">No Errors</div>\n<div id=\"error-message\"class=\"border2 error-message drop-shadow\"></div>\n<span class=\"big-title second-font\">NAML Editor</span>\n</div>\n\n<div class=\"main-content\">\n<table class=\"vertical-control\">\n<tr>\n <td class=\"options\">\n <button class=\"toolbar\" onclick=\"newFile()\">Add New File</button>\n <ul id=\"options-ul\" class=\"vertical-control\">\n </ul>\n <div id=\"more\" class=\"more\">\n <ul class=\"vertical-control\">\n <li id=\"tab_advanced_options\" class=\"weak-color\">\n <img src=\"/images/tool.png\" width=\"16\" height=\"17\" style=\"vertical-align:-15%\"/>\n Advanced Options\n </li>\n " );
|
|
131 if (previousUrl != null) {
|
|
132 out.print( "\n <li style=\"cursor:text\">\n <img src=\"/images/arrowleft.png\" style=\"width:15px;height:15px;vertical-align:-15%;margin-right:1px\"/>\n <a href=\"" );
|
|
133 out.print( (previousUrl) );
|
|
134 out.print( "\">Continue</a>\n </li>\n " );
|
|
135 }
|
|
136 out.print( "\n </ul>\n <button class=\"toolbar action-button\" onclick=\"saveChanges()\" style=\"width:100%;padding:.4em 0\">Save Changes</button>\n </div>\n </td>\n <td id=\"details\" class=\"details dark-bg-color\">\n <div id=\"no-selection\" class=\"no-bg-color\" style=\"display:none;padding:.2em 1em\">\n <div class=\"big-title second-font weak-color\">No Files</div>\n <div class=\"weak-color\">Use the button on the left to create a new file.</div>\n </div>\n <div id=\"div_advanced_options\" class=\"no-bg-color default\" style=\"display:none;padding:.2em 1em\">\n <div id=\"modified-warning\" class=\"info-message invisible\" style=\"padding: .2em .5em .5em\">\n <div class=\"big-title important\">Warning -- You have unsaved changes!</div>\n It is highly recommended that you save your changes before using the options below.\n The download option will NOT see your modified files and the upload option will override your last changes. Click on the\n \"Save Changes\" button first if you don't want to lose your modified files.\n </div>\n <div class=\"big-title second-font\">Download Changes</div>\n <div class=\"advanced-option-body\">\n Download a ZIP file with your custom NAML code and save a copy on your computer.\n <div style=\"padding:.3em 0\">\n <a href=\"" );
|
|
137 out.print( (NamlDownload.getFilePath(site)) );
|
|
138 out.print( "\">Download ZIP file</a>\n </div>\n </div>\n\n <div class=\"big-title second-font\">Upload Changes</div>\n <div class=\"advanced-option-body\">\n Import a ZIP file with custom NAML code.<br/>\n You may consider downloading a backup before uploading a new file.<br/>\n <iframe id=\"upload-frame\" src=\"./NamlEditor$UploadForm.jtp\" frameborder=\"0\" width=\"100%\" scrolling=\"0\"></iframe>\n </div>\n </div>\n </td>\n</tr>\n</table>\n</div>\n\n" );
|
|
139 Shared.noFooter(request,response);
|
|
140 out.print( "\n" );
|
|
141 Shared.analytics(request,response);
|
|
142 out.print( "\n</body>\n</html>\n" );
|
|
143
|
|
144 }
|
|
145
|
|
146 public static void save(HttpServletRequest request, HttpServletResponse response)
|
|
147 throws ServletException, IOException
|
|
148 {
|
|
149 Site site = Jtp.getSite(request);
|
|
150 int fileCount = Integer.valueOf(request.getParameter("fileCount"));
|
|
151 Map<String, String> tweaks = new HashMap<String, String>();
|
|
152 for (int i = 0; i < fileCount; i++) {
|
|
153 String name = request.getParameter("name"+i);
|
|
154 String contents = request.getParameter("contents"+i);
|
|
155 tweaks.put(name, contents);
|
|
156 }
|
|
157 Map<String, String> previousTweaks = site.getCustomTweaks();
|
|
158 site.setCustomTweaks(tweaks);
|
|
159 try {
|
|
160 site.getProgram().getTemplate("do_nothing");
|
|
161 } catch (CompileException e) {
|
|
162 response.getWriter().print(getErrorMessage(e));
|
|
163 // reverting changes
|
|
164 site.setCustomTweaks(previousTweaks);
|
|
165 }
|
|
166 }
|
|
167
|
|
168 public static void uploadForm(HttpServletRequest request, HttpServletResponse response)
|
|
169 throws ServletException, IOException
|
|
170 {
|
|
171 PrintWriter out = response.getWriter();
|
|
172
|
|
173 out.print( "\n<html>\n <head>\n " );
|
|
174 Shared.head(request, response, false);
|
|
175 out.print( "\n </head>\n <body style=\"margin-left:0\">\n <form action=\"./NamlEditor$Upload.jtp\" method=\"POST\" enctype=\"multipart/form-data\">\n <input name=\"file\" type=\"file\" size=\"20\" />\n <div style=\"padding:.5em 0\">\n <input type=\"radio\" name=\"type\" id=\"override\" value=\"override\" checked=\"true\"/> <label for=\"override\">Override my changes</label><br/>\n <input type=\"radio\" name=\"type\" id=\"append\" value=\"append\"/> <label for=\"append\">Merge with my changes</label><br/>\n </div>\n <button type=\"submit\" class=\"toolbar second-font\" style=\"padding:.25em\">Upload ZIP File</button>\n </form>\n </body>\n</html>\n" );
|
|
176
|
|
177 }
|
|
178
|
|
179 private static final int SIZE_LIMIT = 1024 * 1024 * 2; // 2 Mb
|
|
180 private static final CharsetEncoder CHARSET_ENCODER = Charset.forName("UTF-8").newEncoder();
|
|
181
|
|
182 public static void upload(HttpServletRequest request, HttpServletResponse response)
|
|
183 throws ServletException, IOException
|
|
184 {
|
|
185 final Map<String, FileItem> map;
|
|
186 try {
|
|
187 map = Jtp.getFileItems(request);
|
|
188 } catch (FileUploadException e) {
|
|
189 resultPage("Upload failed: " + e.getMessage(), request, response);
|
|
190 return;
|
|
191 }
|
|
192 FileItem fi = map.get("file");
|
|
193 if (fi == null) {
|
|
194 resultPage("Image upload failed, please try again.", request, response);
|
|
195 return;
|
|
196 } else if (fi.getSize() > SIZE_LIMIT) {
|
|
197 resultPage("The file you uploaded is too big. Please upload a smaller image (less than 2Mb).", request, response);
|
|
198 return;
|
|
199 }
|
|
200 Site site = Jtp.getSite(request);
|
|
201 Map<String,String> tweaks = new HashMap<String, String>(site.getCustomTweaks());
|
|
202
|
|
203 String type = map.get("type").getString();
|
|
204 boolean isOverride = "override".equals(type);
|
|
205 if (isOverride)
|
|
206 tweaks.clear();
|
|
207
|
|
208 InputStream in = fi.getInputStream();
|
|
209 ZipInputStream zis = new ZipInputStream(in);
|
|
210 ZipEntry entry;
|
|
211 int i = 0;
|
|
212 while ((entry = zis.getNextEntry()) != null) {
|
|
213 String name = entry.getName();
|
|
214 if (name.endsWith(".naml")) {
|
|
215 int size;
|
|
216 byte[] buffer = new byte[2048];
|
|
217 ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
218 while ((size = zis.read(buffer, 0, buffer.length)) != -1) {
|
|
219 baos.write(buffer, 0, size);
|
|
220 }
|
|
221 if (CHARSET_ENCODER.canEncode(baos.toString())) {
|
|
222 String contents = baos.toString("UTF-8");
|
|
223 name = name.replaceAll("\\.naml", ""); // remove ".naml" ext
|
|
224 if (tweaks.containsKey(name)) {
|
|
225 String original = tweaks.get(name);
|
|
226 tweaks.put(name, original + "\n\n----------- Merged -----------\n\n" + contents);
|
|
227 } else
|
|
228 tweaks.put(name, contents);
|
|
229 baos.flush();
|
|
230 baos.close();
|
|
231 i++;
|
|
232 }
|
|
233 }
|
|
234 }
|
|
235 if (i == 0) {
|
|
236 resultPage("Invalid ZIP file.", request, response);
|
|
237 return;
|
|
238 }
|
|
239 // Saves tweaks and recompiles XML
|
|
240 site.setCustomTweaks(tweaks);
|
|
241 site.getTemplate("do_nothing");
|
|
242 // Reloads the page (if there is any exception, it will be loaded and displayed)
|
|
243 resultPage(null, request, response);
|
|
244 }
|
|
245
|
|
246 private static void resultPage(String message, HttpServletRequest request, HttpServletResponse response)
|
|
247 throws IOException
|
|
248 {
|
|
249 PrintWriter out = response.getWriter();
|
|
250
|
|
251 out.print( "\n<html>\n <head>\n " );
|
|
252 Shared.head(request, response, false);
|
|
253 out.print( "\n </head>\n <body style=\"margin-left:0\">\n " );
|
|
254 if (message == null) {
|
|
255 out.print( "\n <script type=\"text/javascript\">\n parent.location = './NamlEditor.jtp';\n </script>\n " );
|
|
256 } else {
|
|
257 out.print( "\n Error: <span style=\"color:red\">" );
|
|
258 out.print( (message) );
|
|
259 out.print( "</span>\n <br/><br/>\n <a href=\"./NamlEditor$UploadForm.jtp\">Try again</a>\n " );
|
|
260 }
|
|
261 out.print( "\n </body>\n</html>\n" );
|
|
262
|
|
263 }
|
|
264
|
|
265 private static String getErrorMessage(Exception exception) {
|
|
266 StringBuilder errorMessage = new StringBuilder();
|
|
267 if (exception != null) {
|
|
268 errorMessage
|
|
269 .append("<div class=\"important big-title second-font\">Error</div>")
|
|
270 .append(exception.getMessage().replaceAll("\n","<br/>"))
|
|
271 .append("<div class=\"bold\" style=\"padding:.5em 0 .3em\">Full Stack Trace</div>");
|
|
272 StackTraceElement[] stack = exception.getStackTrace();
|
|
273 for (StackTraceElement e : stack)
|
|
274 errorMessage.append(e).append("<br/>");
|
|
275 }
|
|
276 return errorMessage.toString();
|
|
277 }
|
|
278
|
|
279 public static class Save extends HttpServlet {
|
|
280
|
|
281 protected void service(HttpServletRequest request, HttpServletResponse response)
|
|
282 throws ServletException, IOException
|
|
283 {
|
|
284 User user = Jtp.getUser(request, response);
|
|
285 boolean isSiteAdmin = user != null && Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
|
|
286 boolean isSysAdmin = user != null && Permissions.isSysAdmin(user);
|
|
287 if ((isSiteAdmin || isSysAdmin) && "POST".equals(request.getMethod())) {
|
|
288 save(request, response);
|
|
289 } else
|
|
290 response.getWriter().print("Please log in with your administrator account. Your may have logged out or your session has expired.");
|
|
291 }
|
|
292 }
|
|
293
|
|
294 public static class UploadForm extends HttpServlet {
|
|
295
|
|
296 protected void service(HttpServletRequest request, HttpServletResponse response)
|
|
297 throws ServletException, IOException
|
|
298 {
|
|
299 uploadForm(request, response);
|
|
300 }
|
|
301 }
|
|
302
|
|
303 public static class Upload extends HttpServlet {
|
|
304
|
|
305 protected void service(HttpServletRequest request, HttpServletResponse response)
|
|
306 throws ServletException, IOException
|
|
307 {
|
|
308 User user = Jtp.getUser(request, response);
|
|
309 if (user == null) {
|
|
310 resultPage("You are not logged in as an administrator.", request, response);
|
|
311 return;
|
|
312 }
|
|
313 boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
|
|
314 boolean isSysAdmin = Permissions.isSysAdmin(user);
|
|
315 if (!isSiteAdmin && !isSysAdmin) {
|
|
316 resultPage("You are not logged in as an administrator.", request, response);
|
|
317 return;
|
|
318 }
|
|
319
|
|
320 upload(request, response);
|
|
321 }
|
|
322 }
|
|
323
|
|
324 private static void simpleLoginPage(String message, HttpServletRequest request, HttpServletResponse response)
|
|
325 throws ServletException, IOException
|
|
326 {
|
|
327 PrintWriter out = response.getWriter();
|
|
328
|
|
329 out.print( "\n <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n <html>\n <head>\n " );
|
|
330 Shared.loadJavascript(request, out);
|
|
331 out.print( "\n <title>Naml Editor | Login</title>\n </head>\n <body style=\"text-align:center;font-family: Verdana,sans-serif;font-size:.84em;padding-top:2em\">\n <img src=\"/images/naml.png\"/>\n <p><b>" );
|
|
332 out.print( (message) );
|
|
333 out.print( "</b></p>\n <form action=\"./NamlEditor$SimpleLogin.jtp\" method=\"post\">\n <table style=\"margin:0 auto;font:inherit\">\n <tr>\n <td style=\"text-align:right\">Email</td>\n <td><input type=\"text\" name=\"email\"/></td>\n </tr>\n <tr>\n <td style=\"text-align:right\">Password</td>\n <td><input type=\"password\" name=\"password\"/></td>\n </tr>\n <tr>\n <td colspan=\"2\" style=\"padding: .5em 0 0;text-align:center\">\n <input type=\"submit\" value=\"Login\" style=\"font-weight:bold;padding:.3em .4em\"/>\n or <a href=\"/\">Cancel</a>\n </td>\n </tr>\n </table>\n </form>\n <div style=\"margin:2em 15%;text-align:center;background:#eee;padding:.3em 0\">\n Powered by <a href=\"" );
|
|
334 out.print( (Jtp.homePage()) );
|
|
335 out.print( "\">Nabble</a>\n </div>\n </body>\n </html>\n" );
|
|
336
|
|
337 }
|
|
338
|
|
339 public static class SimpleLogin extends HttpServlet {
|
|
340
|
|
341 protected void service(HttpServletRequest request, HttpServletResponse response)
|
|
342 throws ServletException, IOException
|
|
343 {
|
|
344 Site site = Jtp.getSite(request);
|
|
345 String email = request.getParameter("email");
|
|
346 String password = request.getParameter("password");
|
|
347 User user = site.getUserFromEmail(email);
|
|
348 if (user != null && user.isRegistered() && user.checkPassword(password) && "post".equalsIgnoreCase(request.getMethod())) {
|
|
349 Jtp.doLogin(request,response,user,true);
|
|
350 DailyNumber.logins.inc();
|
|
351
|
|
352 Shared.javascriptRedirect(request,response, "./NamlEditor.jtp");
|
|
353 } else {
|
|
354 simpleLoginPage("<span style=\"color:#C00\">Incorrect login!</span>", request, response);
|
|
355 }
|
|
356 }
|
|
357 }
|
|
358
|
|
359 public static class ViewFile extends HttpServlet {
|
|
360
|
|
361 protected void service(HttpServletRequest request, HttpServletResponse response)
|
|
362 throws ServletException, IOException
|
|
363 {
|
|
364 JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
|
|
365 jtpContext.setEtag(request,response);
|
|
366
|
|
367 Site site = Jtp.getSite(request);
|
|
368 String file = request.getParameter("file");
|
|
369 PrintWriter out = response.getWriter();
|
|
370 List<Source> sources = site.getProgram().getSources();
|
|
371 for (Source s : sources) {
|
|
372 if (s.id.equals(file)) {
|
|
373 file = file.replaceFirst("^.+:", "");
|
|
374 file = file.endsWith(".naml")? file : file + ".naml";
|
|
375 String code = HtmlUtils.htmlEncode(s.content);
|
|
376
|
|
377 out.print( "\n <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n <html>\n <head>\n <title>" );
|
|
378 out.print( (file) );
|
|
379 out.print( " | Naml File</title>\n " );
|
|
380 Shared.head(request, response, false);
|
|
381 out.print( "\n </head>\n <body>\n " );
|
|
382 Shared.minHeader(request, response, site.getRootNode(), null, false);
|
|
383 out.print( "\n <h1>" );
|
|
384 out.print( (file) );
|
|
385 out.print( "</h1>\n <pre>" );
|
|
386 out.print( (code) );
|
|
387 out.print( "</pre>\n " );
|
|
388 Shared.footer(request, response);
|
|
389 out.print( "\n </body>\n </html>\n" );
|
|
390
|
|
391 return;
|
|
392 }
|
|
393 }
|
|
394 response.sendError(HttpServletResponse.SC_NOT_FOUND, "Source file not found.");
|
|
395 }
|
|
396 }
|
|
397 }
|
|
398
|