Improved JSON validation and error interface when validation fails; made rank manager support custom CSS
authorDan
Tue, 22 Jul 2008 14:49:18 -0500
changeset 651 ce9d78d7251d
parent 650 e45183014778
child 652 26ce2694d43f
Improved JSON validation and error interface when validation fails; made rank manager support custom CSS
includes/clientside/static/acl.js
includes/clientside/static/admin-menu.js
includes/clientside/static/ajax.js
includes/clientside/static/editor.js
includes/clientside/static/functions.js
includes/clientside/static/login.js
includes/clientside/static/rank-manager.js
includes/clientside/static/theme-manager.js
language/english/admin.json
language/english/core.json
--- a/includes/clientside/static/acl.js	Sun Jul 20 13:32:04 2008 -0400
+++ b/includes/clientside/static/acl.js	Tue Jul 22 14:49:18 2008 -0500
@@ -31,7 +31,7 @@
       if ( ajax.readyState == 4 && ajax.status == 200 )
       {
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(ajax.responseText);
           return false;
@@ -66,7 +66,7 @@
       if ( ajax.readyState == 4 && ajax.status == 200 )
       {
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(ajax.responseText);
           return false;
@@ -388,7 +388,7 @@
       if ( ajax.readyState == 4 && ajax.status == 200 )
       {
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(ajax.responseText);
           return false;
--- a/includes/clientside/static/admin-menu.js	Sun Jul 20 13:32:04 2008 -0400
+++ b/includes/clientside/static/admin-menu.js	Tue Jul 22 14:49:18 2008 -0500
@@ -200,10 +200,11 @@
 		? '<a href="javascript: trees[' + this.o_root.n_id + '].toggle(' + this.n_id + ')" onmouseover="trees[' + this.o_root.n_id + '].mover(' + this.n_id + ')" onmouseout="trees[' + this.o_root.n_id + '].mout(' + this.n_id + ')"><img src="' + this.get_icon(true) + '" border="0" align="absbottom" name="j_img' + this.o_root.n_id + '_' + this.n_id + '"></a>'
 		: '<img src="' + this.get_icon(true) + '" border="0" align="absbottom">') : '')
   // CODE MODIFICATION
+  // [7/20/08: removed ondblclick property (unneeded)]
   // removed: 
 	//	+ '<a href="' + this.a_config[1] + '" target="' + this.o_root.a_tpl['target'] + '" onclick="return trees[' + this.o_root.n_id + '].select(' + this.n_id + ')" ondblclick="trees[' + this.o_root.n_id + '].toggle(' + this.n_id + ')" onmouseover="trees[' + this.o_root.n_id + '].mover(' + this.n_id + ')" onmouseout="trees[' + this.o_root.n_id + '].mout(' + this.n_id + ')" class="t' + this.o_root.n_id + 'i" id="i_txt' + this.o_root.n_id + '_' + this.n_id + '"><img src="' + this.get_icon() + '" border="0" align="absbottom" name="i_img' + this.o_root.n_id + '_' + this.n_id + '" class="t' + this.o_root.n_id + 'im">' + this.a_config[0] + '</a></td></tr></table>' + (this.a_children.length ? '<div id="i_div' + this.o_root.n_id + '_' + this.n_id + '" style="display:none"></div>' : '');
   // added:
-  + '<a href="' + this.a_config[1] + '" target="' + this.o_root.a_tpl['target'] + '" onclick="return trees[' + this.o_root.n_id + '].select(' + this.n_id + ')" ondblclick="trees[' + this.o_root.n_id + '].toggle(' + this.n_id + ')" onmouseover="trees[' + this.o_root.n_id + '].mover(' + this.n_id + ')" onmouseout="trees[' + this.o_root.n_id + '].mout(' + this.n_id + ')" class="t' + this.o_root.n_id + 'i" id="i_txt' + this.o_root.n_id + '_' + this.n_id + '">' + this.a_config[0] + '</a></td></tr></table>' + (this.a_children.length ? '<div id="i_div' + this.o_root.n_id + '_' + this.n_id + '" style="display:none"></div>' : '');
+  + '<a href="' + this.a_config[1] + '" target="' + this.o_root.a_tpl['target'] + '" onclick="return trees[' + this.o_root.n_id + '].select(' + this.n_id + ')" onmouseover="trees[' + this.o_root.n_id + '].mover(' + this.n_id + ')" onmouseout="trees[' + this.o_root.n_id + '].mout(' + this.n_id + ')" class="t' + this.o_root.n_id + 'i" id="i_txt' + this.o_root.n_id + '_' + this.n_id + '">' + this.a_config[0] + '</a></td></tr></table>' + (this.a_children.length ? '<div id="i_div' + this.o_root.n_id + '_' + this.n_id + '" style="display:none"></div>' : '');
   // END MODIFICATIONS
   alert('i_div' + this.o_root.n_id + '_' + this.n_id);
 }
--- a/includes/clientside/static/ajax.js	Sun Jul 20 13:32:04 2008 -0400
+++ b/includes/clientside/static/ajax.js	Tue Jul 22 14:49:18 2008 -0500
@@ -178,7 +178,7 @@
       {
         whiteout.parentNode.removeChild(whiteout);
         var response = String(ajax.responseText);
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(response);
           return false;
@@ -416,7 +416,7 @@
       unsetAjaxLoading();
       
       var response = String(ajax.responseText + '');
-      if ( response.substr(0, 1) != '{' )
+      if ( !check_json_response(response) )
       {
         handle_invalid_json(response);
         return false;
@@ -1042,6 +1042,7 @@
 
 window.aboutKeepAlive = function()
 {
+  load_component('messagebox');
   new MessageBox(MB_OK|MB_ICONINFORMATION, $lang.get('user_keepalive_info_title'), $lang.get('user_keepalive_info_body'));
 }
 
@@ -1282,7 +1283,7 @@
       if ( ajax.readyState == 4 && ajax.status == 200 )
       {
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(response);
           return false;
--- a/includes/clientside/static/editor.js	Sun Jul 20 13:32:04 2008 -0400
+++ b/includes/clientside/static/editor.js	Tue Jul 22 14:49:18 2008 -0500
@@ -26,7 +26,7 @@
         unsetAjaxLoading();
         
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(response);
           return false;
@@ -580,7 +580,7 @@
       {
         ajaxUnSetEditorLoading();
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(response);
           return false;
@@ -753,7 +753,7 @@
         ajaxUnSetEditorLoading();
         
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(response);
           return false;
@@ -972,7 +972,7 @@
         ajaxUnSetEditorLoading();
         
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(response);
           return false;
@@ -1001,12 +1001,3 @@
     }, true);
 }
 
-/**
- * Equivalent of PHP's time()
- * @return int
- */
-
-function unix_time()
-{
-  return parseInt((new Date()).getTime()/1000);
-}
--- a/includes/clientside/static/functions.js	Sun Jul 20 13:32:04 2008 -0400
+++ b/includes/clientside/static/functions.js	Tue Jul 22 14:49:18 2008 -0500
@@ -153,71 +153,211 @@
 
 function handle_invalid_json(response, customerror)
 {
-  var mainwin = $dynano('ajaxEditContainer').object;
-  mainwin.innerHTML = '';
+  load_component('messagebox');
+  load_component('SpryEffects');
+  load_component('fadefilter');
+  load_component('flyin');
+  load_component('l10n');
+  
+  darken();
   
-  // Title
-  var h3 = document.createElement('h3');
-  h3.appendChild(document.createTextNode('The site encountered an error while processing your request.'));
-  mainwin.appendChild(h3);
+  var box = document.createElement('div');
+  var mainwin = document.createElement('div');
+  var panel = document.createElement('div');
+  
+  //
+  // main window
+  //
+  
+    mainwin.style.padding = '10px';
+    mainwin.style.width = '580px';
+    mainwin.style.height = '360px';
+    mainwin.style.clip = 'rect(0px,auto,auto,0px)';
+    mainwin.style.overflow = 'auto';
+    mainwin.style.backgroundColor = '#ffffff';
   
-  if ( typeof(customerror) == 'string' )
-  {
-    var el = document.createElement('p');
-    el.appendChild(document.createTextNode(customerror));
-    mainwin.appendChild(el);
-  }
-  else
+    // Title
+    var h3 = document.createElement('h3');
+    var h3_text = ( $lang.placeholder ) ? 'The site encountered an error while processing your request.' : $lang.get('ajax_badjson_title');
+    h3.appendChild(document.createTextNode(h3_text));
+    mainwin.appendChild(h3);
+    
+    if ( typeof(customerror) == 'string' )
+    {
+      var el = document.createElement('p');
+      el.appendChild(document.createTextNode(customerror));
+      mainwin.appendChild(el);
+    }
+    else
+    {
+      var error = 'We unexpectedly received the following response from the server. The response should have been in the JSON ';
+      error += 'serialization format, but the response wasn\'t composed only of the JSON response. There are three possible triggers ';
+      error += 'for this problem:';
+      customerror = ( $lang.placeholder ) ? error : $lang.get('ajax_badjson_body');
+      var el = document.createElement('p');
+      el.appendChild(document.createTextNode(customerror));
+      mainwin.appendChild(el);
+      var ul = document.createElement('ul');
+      var li1 = document.createElement('li');
+      var li2 = document.createElement('li');
+      var li3 = document.createElement('li');
+      var li1_text = ( $lang.placeholder ) ? 'The server sent back a bad HTTP response code and thus sent an error page instead of running Enano. This indicates a possible problem with your server, and is not likely to be a bug with Enano.' : $lang.get('ajax_badjson_tip1');
+      var li2_text = ( $lang.placeholder ) ? 'The server sent back the expected JSON response, but also injected some code into the response that should not be there. Typically this consists of advertisement code. In this case, the administrator of this site will have to contact their web host to have advertisements disabled.' : $lang.get('ajax_badjson_tip2');
+      var li3_text = ( $lang.placeholder ) ? 'It\'s possible that Enano triggered a PHP error or warning. In this case, you may be looking at a bug in Enano.' : $lang.get('ajax_badjson_tip3');
+      var osc_ex_data = ( $lang.placeholder ) ? 'This is KNOWN to be the case with the OpenSourceCMS.com demo version of Enano.' : $lang.get('ajax_badjson_osc');
+      li1.appendChild(document.createTextNode(li1_text));
+      var osc_exception = ( window.location.hostname == 'demo.opensourcecms.com' ) ? ' ' + osc_ex_data : '';
+      li2.appendChild(document.createTextNode(li2_text + osc_exception));
+      li3.appendChild(document.createTextNode(li3_text));
+        
+      ul.appendChild(li1);
+      ul.appendChild(li2);
+      ul.appendChild(li3);
+      mainwin.appendChild(ul);
+    }
+    
+    var p2 = document.createElement('p');
+    var p2_text = ( $lang.placeholder ) ? 'The response received from the server is as follows:' : $lang.get('ajax_badjson_msg_response');
+    p2.appendChild(document.createTextNode(p2_text));
+    mainwin.appendChild(p2);
+    
+    var pre = document.createElement('pre');
+    pre.appendChild(document.createTextNode(response));
+    mainwin.appendChild(pre);
+    
+    var p3 = document.createElement('p');
+    var p3_text = $lang.placeholder ? 'You may also choose to view the response as HTML.' : $lang.get('ajax_badjson_msg_viewashtml');
+    p3.appendChild(document.createTextNode(p3_text + ' '));
+    var a = document.createElement('a');
+    var a_text = $lang.placeholder ? 'View as HTML' : $lang.get('ajax_badjson_btn_viewashtml');
+    a.appendChild(document.createTextNode(a_text + '...'));
+    a._resp = response;
+    a.onclick = function()
+    {
+      var vah_title = ( $lang.placeholder ) ? 'View the response as HTML?' : $lang.get('ajax_badjson_html_confirm_title');
+      var vah_body = ( $lang.placeholder ) ? 'If the server\'s response was modified by an attacker to include malicious code, viewing the response as HTML might allow that malicious code to run. Only continue if you have inspected the response text and verified that it is safe.' : $lang.get('ajax_badjson_html_confirm_body');
+      var btn_confirm = $lang.placeholder ? 'View as HTML' : $lang.get('ajax_badjson_btn_viewashtml');
+      var btn_cancel = $lang.placeholder ? 'Cancel' : $lang.get('etc_cancel');
+      var mp = miniPromptMessage({
+          title: vah_title,
+          message: vah_body,
+          buttons: [
+            {
+              text: btn_confirm,
+              color: 'blue',
+              style: {
+                fontWeight: 'bold'
+              },
+              onclick: function() {
+                var mp = miniPromptGetParent(this);
+                var win = window.open('about:blank', 'invalidjson_htmlwin', 'width=550,height=400,status=no,toolbars=no,toolbar=no,address=no,scroll=yes');
+                win.document.write(mp._response);
+                win.document.close();
+                miniPromptDestroy(this);
+              }
+            },
+            {
+              text: btn_cancel,
+              onclick: function() {
+                miniPromptDestroy(this);
+              }
+            }
+          ]
+        });
+      mp._response = this._resp;
+      return false;
+    }
+    a.href = '#';
+    p3.appendChild(a);
+    mainwin.appendChild(p3);
+  
+  //
+  // panel
+  //
+  
+    panel.style.backgroundColor = '#D0D0D0';
+    panel.style.textAlign = 'right';
+    panel.style.padding = '0 10px';
+    panel.style.lineHeight = '40px';
+    panel.style.width = '580px';
+    
+    var closer = document.createElement('input');
+    var btn_close = $lang.placeholder ? 'Close' : $lang.get('ajax_badjson_btn_close');
+    closer.type = 'button';
+    closer.value = btn_close;
+    closer.onclick = function()
+    {
+      var parentdiv = this.parentNode.parentNode;
+      var effect = new Spry.Effect.Blind(parentdiv, {
+          from: '100%',
+          to: '0%',
+          duration: '1000'
+        });
+      var observer = {
+        onPostEffect: function()
+          {
+            parentdiv.parentNode.removeChild(parentdiv);
+            enlighten();
+          }
+        };
+      effect.addObserver(observer);
+      effect.start();
+    }
+    panel.appendChild(closer);
+    
+  //
+  // put it together
+  //
+  
+    box.appendChild(mainwin);
+    box.appendChild(panel);
+    
+    // add it to the body to allow height/width calculation
+    
+    box.style.display = 'block';
+    box.style.position = 'absolute';
+    domObjChangeOpac(0, box);
+    
+    var body = document.getElementsByTagName('body')[0];
+    body.appendChild(box);
+    
+    
+    // calculate position of the box
+    // box should be exactly 640px high, 480px wide
+    var top = ( getHeight() / 2 ) - ( $(box).Height() / 2 ) + getScrollOffset();
+    var left = ( getWidth() / 2 ) - ( $(box).Width() / 2 );
+    console.debug('top = %d, left = %d', top, left);
+    box.style.top = top + 'px';
+    box.style.left = left + 'px';
+    
+    // we have width and height, set display to none and reset opacity
+    box.style.display = 'none';
+    domObjChangeOpac(100, box);
+    
+    setTimeout(function()
+      {
+        (new Spry.Effect.Blind(box, {
+            from: '0%',
+            to: '100%',
+            duration: 1000
+          })).start();
+      }, 1000);
+}
+
+/**
+ * Verify that a string is roughly a valid JSON object. Warning - this is only a very cheap syntax check.
+ * @param string
+ * @return bool true if JSON is valid
+ */
+
+function check_json_response(response)
+{
+  response = trim(response);
+  if ( response.substr(0, 1) == '{' && response.substr(response.length - 1, 1) == '}' )
   {
-    customerror  = 'We unexpectedly received the following response from the server. The response should have been in the JSON ';
-    customerror += 'serialization format, but the response wasn\'t composed only of the JSON response. There are three possible triggers ';
-    customerror += 'for this problem:';
-    var el = document.createElement('p');
-    el.appendChild(document.createTextNode(customerror));
-    mainwin.appendChild(el);
-    var ul = document.createElement('ul');
-    var li1 = document.createElement('li');
-    var li2 = document.createElement('li');
-    var li3 = document.createElement('li');
-    li1.appendChild(document.createTextNode('The server sent back a bad HTTP response code and thus sent an error page instead of running Enano. This indicates a possible problem with your server, and is not likely to be a bug with Enano.'));
-    var osc_exception = ( window.location.hostname == 'demo.opensourcecms.com' ) ? ' This is KNOWN to be the case with the OpenSourceCMS.com demo version of Enano.' : '';
-    li2.appendChild(document.createTextNode('The server sent back the expected JSON response, but also injected some code into the response that should not be there. Typically this consists of advertisement code. In this case, the administrator of this site will have to contact their web host to have advertisements disabled.' + osc_exception));
-    li3.appendChild(document.createTextNode('It\'s possible that Enano triggered a PHP error or warning. In this case, you may be looking at a bug in Enano.'));
-      
-    ul.appendChild(li1);
-    ul.appendChild(li2);
-    ul.appendChild(li3);
-    mainwin.appendChild(ul);
+    return true;
   }
-  
-  var p2 = document.createElement('p');
-  p2.appendChild(document.createTextNode('The response received from the server is as follows:'));
-  mainwin.appendChild(p2);
-  
-  var pre = document.createElement('pre');
-  pre.appendChild(document.createTextNode(response));
-  mainwin.appendChild(pre);
-  
-  var p3 = document.createElement('p');
-  p3.appendChild(document.createTextNode('You may also choose to view the response as HTML. '));
-  var a = document.createElement('a');
-  a.appendChild(document.createTextNode('View as HTML...'));
-  a._resp = response;
-  a.id = 'invalidjson_link';
-  a.onclick = function()
-  {
-    var mb = new MessageBox(MB_YESNO | MB_ICONEXCLAMATION, 'Do you really want to view this response as HTML?', 'If the response was changed during transmission to include malicious code, you may be allowing that malicious code to run by viewing the response as HTML. Only do this if you have reviewed the response text and have found no suspicious code in it.');
-    mb.onclick['Yes'] = function()
-    {
-      var html = $dynano('invalidjson_link').object._resp;
-      var win = window.open('about:blank', 'invalidjson_htmlwin', 'width=550,height=400,status=no,toolbars=no,toolbar=no,address=no,scroll=yes');
-      win.document.write(html);
-    }
-    return false;
-  }
-  a.href = '#';
-  p3.appendChild(a);
-  mainwin.appendChild(p3);
+  return false;
 }
 
 function ajaxEscape(text)
@@ -817,3 +957,13 @@
   }
   return false;
 }
+
+/**
+ * Equivalent of PHP's time()
+ * @return int
+ */
+
+function unix_time()
+{
+  return parseInt((new Date()).getTime()/1000);
+}
--- a/includes/clientside/static/login.js	Sun Jul 20 13:32:04 2008 -0400
+++ b/includes/clientside/static/login.js	Tue Jul 22 14:49:18 2008 -0500
@@ -321,7 +321,7 @@
       {
         // parse response
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(response);
           return false;
--- a/includes/clientside/static/rank-manager.js	Sun Jul 20 13:32:04 2008 -0400
+++ b/includes/clientside/static/rank-manager.js	Tue Jul 22 14:49:18 2008 -0500
@@ -5,6 +5,10 @@
 var RankEditorControl = function(rankdata)
 {
   this.rankdata = ( typeof(rankdata) == 'object' ) ? rankdata : {};
+  if ( !this.rankdata.rank_style )
+  {
+    this.rankdata.rank_style = '';
+  }
   
   // have the browser parse CSS for us and use an anchor to be as close
   // as possible in calculating CSS
@@ -197,15 +201,43 @@
     tr_color.appendChild(td_color_f);
     table.appendChild(tr_color);
     
+    // field: additional CSS
+    var tr_css = document.createElement('tr');
+    
+    var td_css_l = document.createElement('td');
+    td_css_l.className = 'row2';
+    td_css_l.appendChild(document.createTextNode($lang.get('acpur_field_style_css')));
+    tr_css.appendChild(td_css_l);
+    
+    var td_css_f = document.createElement('td');
+    td_css_f.className = 'row2';
+    var f_css = document.createElement('input');
+    f_css.type = 'text';
+    f_css.value = this.stripBasicCSSAttributes(this.rankdata.rank_style);
+    f_css.style.width = '98%';
+    f_css.editor = this;
+    f_css.onkeyup = function()
+    {
+      if ( !(trim(this.value)).match(/^((([a-z-]+):(.+?);)+)?$/) )
+        return;
+      var newcss = this.editor.stripExtendedCSSAttributes(String(this.editor.style_sim_obj.getAttribute('style'))) + ' ' + this.value;
+      this.editor.preview_div.setAttribute('style', 'font-size: x-large; ' + newcss);
+      this.editor.style_sim_obj.setAttribute('style', newcss);
+    }
+    this.f_css = f_css;
+    td_css_f.appendChild(f_css);
+    tr_css.appendChild(td_css_f);
+    table.appendChild(tr_css);
+    
     // "field": preview
     var tr_preview = document.createElement('tr');
     var td_preview_l = document.createElement('td');
-    td_preview_l.className = 'row2';
+    td_preview_l.className = 'row1';
     td_preview_l.appendChild(document.createTextNode($lang.get('acpur_field_preview')));
     tr_preview.appendChild(td_preview_l);
     
     var td_preview_f = document.createElement('td');
-    td_preview_f.className = 'row2';
+    td_preview_f.className = 'row1';
     var div_preview = document.createElement('a');
     this.preview_div = div_preview;
     div_preview.style.fontSize = 'x-large';
@@ -321,9 +353,38 @@
     return ( lumin > 60 ) ? '000000' : 'ffffff';
   }
   
-  this.getJSONDataset = function()
+  /**
+   * Strips out basic CSS attributes (color, font-weight, font-style, text-decoration) from a snippet of CSS.
+   * @param string
+   * @return string
+   */
+  
+  this.stripBasicCSSAttributes = function(css)
   {
-    
+    return trim(css.replace(/(color|font-weight|font-style|text-decoration): ?([A-z0-9# ,\(\)]+);/g, ''));
+  }
+  
+  /**
+   * Strips out all but basic CSS attributes.
+   * @param string
+   * @return string
+   */
+  
+  this.stripExtendedCSSAttributes = function(css)
+  {
+    var match;
+    var final_css = '';
+    var basics = ['color', 'font-weight', 'font-style', 'text-decoration'];
+    while ( match = css.match(/([a-z-]+):(.+?);/) )
+    {
+      if ( in_array(match[1], basics) )
+      {
+        final_css += ' ' + match[0] + ' ';
+      }
+      css = css.replace(match[0], '');
+    }
+    final_css = trim(final_css);
+    return final_css;
   }
   
   this.getCSS = function()
@@ -433,7 +494,7 @@
       if ( ajax.readyState == 4 && ajax.status == 200 )
       {
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(ajax.responseText);
           return false;
@@ -488,7 +549,7 @@
       if ( ajax.readyState == 4 && ajax.status == 200 )
       {
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(ajax.responseText);
           return false;
@@ -634,7 +695,7 @@
       if ( ajax.readyState == 4 && ajax.status == 200 )
       {
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           handle_invalid_json(ajax.responseText);
           return false;
--- a/includes/clientside/static/theme-manager.js	Sun Jul 20 13:32:04 2008 -0400
+++ b/includes/clientside/static/theme-manager.js	Tue Jul 22 14:49:18 2008 -0500
@@ -127,7 +127,7 @@
           {
             theme_list.innerHTML = '';
             var response = String(ajax.responseText + '');
-            if ( response.substr(0, 1) != '{' )
+            if ( !check_json_response(response) )
             {
               alert(response);
               return false;
@@ -495,7 +495,7 @@
         
         // process response
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           alert(response);
           return false;
@@ -609,7 +609,7 @@
       {
         // process response
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           // For this we actually *expect* an HTML response.
           parent.innerHTML = response;
@@ -643,7 +643,7 @@
       {
         // process response
         var response = String(ajax.responseText + '');
-        if ( response.substr(0, 1) != '{' )
+        if ( !check_json_response(response) )
         {
           // For this we actually *expect* an HTML response.
           parent.innerHTML = response;
--- a/language/english/admin.json	Sun Jul 20 13:32:04 2008 -0400
+++ b/language/english/admin.json	Tue Jul 22 14:49:18 2008 -0500
@@ -186,7 +186,7 @@
       welcome_line2: 'Using the links on the left you can control every aspect of your website\'s look and feel, plus you can manage users, work with pages, and install plugins to make your Enano installation even better.',
       msg_demo_title: 'Enano is running in demo mode.',
       msg_demo_body: 'If you borked something up, or if you\'re done testing, you can <a href="%reset_url%">reset this site</a>. The site is reset automatically once every two hours. When a reset is performed, all custom modifications to the site are lost and replaced with default values.',
-      msg_install_files: '<b>NOTE:</b> It appears that your install.php and/or schema.sql files still exist. It is HIGHLY RECOMMENDED that you delete or rename these files, to prevent getting your server hacked.',
+      msg_install_files: '<b>NOTE:</b> It appears that the install/ directory still exists in your Enano installation root. Even though sensitive tools try to avoid hacking attempts, it is highly recommended that you remove the entire install directory from your server for security reasons.',
       heading_updates: 'Check for updates',
       msg_updates_info: 'Periodically, new releases of Enano will be made available. Click the button below to check for updates to Enano. During this process, a request will be sent to the Enano CMS server (germantown.enanocms.org) over HTTP for an <a href="%updates_url%">XML file</a> containing a list of the latest releases. No information about your Enano installation will be transmitted.',
       btn_check_updates: 'Check for updates',
@@ -948,6 +948,7 @@
       field_style_basic_italic: 'Italic',
       field_style_basic_underline: 'Underline',
       field_style_color: 'Username color:',
+      field_style_css: 'Additional CSS:',
       field_preview: 'Preview:',
       
       btn_save: 'Save rank',
--- a/language/english/core.json	Sun Jul 20 13:32:04 2008 -0400
+++ b/language/english/core.json	Tue Jul 22 14:49:18 2008 -0500
@@ -458,6 +458,19 @@
       lbl_moreoptions_nojs: 'More options for this page',
       msg_loading_component: 'Loading %component%...',
       
+      badjson_title: 'The site encountered an error while processing your request.',
+      badjson_body: 'We unexpectedly received the following response from the server. The response should have been in the JSON serialization format, but the response wasn\'t composed only of the JSON response. There are three possible triggers for this problem:',
+      badjson_tip1: 'The server sent back a bad HTTP response code and thus sent an error page instead of running Enano. This indicates a possible problem with your server, and is not likely to be a bug with Enano.',
+      badjson_tip2: 'The server sent back the expected JSON response, but also injected some code into the response that should not be there. Typically this consists of advertisement code. In this case, the administrator of this site will have to contact their web host to have advertisements disabled.',
+      badjson_tip3: 'It\'s possible that Enano triggered a PHP error or warning. In this case, you may be looking at a bug in Enano.',
+      badjson_osc: 'This is KNOWN to be the case with the OpenSourceCMS.com demo version of Enano.',
+      badjson_msg_response: 'The response received from the server is as follows:',
+      badjson_msg_viewashtml: 'You may also choose to view the response as HTML.',
+      badjson_btn_viewashtml: 'View as HTML',
+      badjson_html_confirm_title: 'View the response as HTML?',
+      badjson_html_confirm_body: 'If the server\'s response was modified by an attacker to include malicious code, viewing the response as HTML might allow that malicious code to run. Only continue if you have inspected the response text and verified that it is safe.',
+      badjson_btn_close: 'Close',
+      
       // Server-side responses
       rename_too_short: 'The name you entered is too short. Please enter a longer name for this page.',
       rename_success_title: 'Page renamed',