Merging nighthawk and scribus branches
authorDan
Tue, 08 Apr 2008 20:32:30 -0400
changeset 525 3f2dfdb99be4
parent 524 26287ae2449d (diff)
parent 521 d264784355e5 (current diff)
child 526 b2fb50d572c7
Merging nighthawk and scribus branches
includes/sessions.php
plugins/SpecialUserFuncs.php
Binary file images/prompt-body.png has changed
Binary file images/prompt-bottom.png has changed
Binary file images/prompt-top.png has changed
--- a/includes/clientside/css/enano-shared.css	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/clientside/css/enano-shared.css	Tue Apr 08 20:32:30 2008 -0400
@@ -745,3 +745,41 @@
   background-image: url(../../../images/expander/open-prelight.gif);
 }
 
+/* Flown-in mini prompts */
+
+div.miniprompt {
+  position: absolute;
+}
+
+div.miniprompt div.mp-top, div.miniprompt div.mp-bottom {
+  width: 388px;
+  height: 57px;
+  background-image: url(../../../images/prompt-top.png);
+  background-repeat: no-repeat;
+  background-position: center center;
+}
+
+div.miniprompt div.mp-bottom {
+  height: 42px;
+  background-image: url(../../../images/prompt-bottom.png);
+}
+
+div.miniprompt div.mp-body {
+  padding: 0 44px 10px 44px;
+  width: 300px;
+  background-image: url(../../../images/prompt-body.png);
+  background-repeat: repeat-y;
+  background-position: center center;
+}
+
+div.miniprompt h3 {
+  /* fix padding issues on firefox */
+  margin: 0 0 10px 0;
+}
+
+/* for buttons */
+div.miniprompt div.mp-buttons {
+  text-align: right;
+  position: relative;
+  top: 10px;
+}
--- a/includes/clientside/static/ajax.js	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/clientside/static/ajax.js	Tue Apr 08 20:32:30 2008 -0400
@@ -239,15 +239,138 @@
   // IE <6 pseudo-compatibility
   if ( KILL_SWITCH )
     return true;
-  r = prompt($lang.get('ajax_rename_prompt'));
-  if(!r || r=='') return;
-  setAjaxLoading();
-  ajaxPost(stdAjaxPrefix+'&_mode=rename', 'newtitle='+ajaxEscape(r), function() {
-    if ( ajax.readyState == 4 && ajax.status == 200 ) {
-      unsetAjaxLoading();
-      alert(ajax.responseText);
-    }
-  }, true);
+  
+  // updated - 1.1.4 to use miniPrompt
+  miniPrompt(ajaxRenameConstructDialog);
+}
+
+var ajaxRenameConstructDialog = function(div)
+{
+  // title
+  var heading = document.createElement('h3');
+  heading.appendChild(document.createTextNode($lang.get('ajax_rename_prompt_short')));
+  div.appendChild(heading);
+  
+  // form
+  var form = document.createElement('form');
+  form.action = 'javascript:void(0);';
+  
+  // box
+  var box = document.createElement('input');
+  box.size = '43';
+  box.style.width = '100%';
+  form.appendChild(box);
+  div.appendChild(form);
+  
+  // notice
+  var notice = document.createElement('small');
+  notice.appendChild(document.createTextNode($lang.get('ajax_rename_notice')));
+  div.appendChild(notice);
+  
+  // button area
+  var btndiv = document.createElement('div');
+  btndiv.className = 'mp-buttons';
+  
+  // buttons
+  var btn_submit = document.createElement('a');
+  btn_submit.href = '#';
+  btn_submit.appendChild(document.createTextNode($lang.get('etc_go')));
+  btn_submit.className = 'fakebutton fakebutton-submit';
+  
+  var btn_cancel = document.createElement('a');
+  btn_cancel.href = '#';
+  btn_cancel.appendChild(document.createTextNode($lang.get('etc_cancel')));
+  btn_cancel.className = 'fakebutton';
+  
+  btndiv.appendChild(btn_submit);
+  btndiv.appendChild(document.createTextNode(' | '));
+  btndiv.appendChild(btn_cancel);
+  div.appendChild(btndiv);
+  
+  // events
+  btn_submit.onclick = function()
+  {
+    ajaxRenameSubmit(this);
+    return false;
+  }
+  btn_cancel.onclick = function()
+  {
+    miniPromptDestroy(this);
+    return false;
+  }
+  form.onsubmit = function()
+  {
+    ajaxRenameSubmit(this);
+    return false;
+  }
+  
+  setTimeout(function()
+    {
+      box.focus();
+    }, 200);
+}
+
+function ajaxRenameSubmit(obj)
+{
+  var box = miniPromptGetParent(obj);
+  if ( !box )
+    return false;
+  
+  var newname = ( obj.getElementsByTagName('input')[0] ).value;
+  newname = trim(newname);
+  
+  if ( newname.length < 1 )
+  {
+    alert($lang.get('ajax_rename_too_short'));
+    return false;
+  }
+  
+  if ( !newname )
+  {
+    return false;
+  }
+  
+  var innerBox = getElementsByClassName(box, 'div', 'mp-body')[0];
+  var whiteout = whiteOutElement(innerBox);
+  whiteout.style.width = ( $(whiteout).Width() - 78 ) + 'px';
+  whiteout.style.left = ( $(whiteout).Left() + 44 ) + 'px';
+  
+  ajaxPost(stdAjaxPrefix + '&_mode=rename', 'newtitle=' + ajaxEscape(newname), function()
+    {
+      if ( ajax.readyState == 4 && ajax.status == 200 )
+      {
+        whiteout.parentNode.removeChild(whiteout);
+        var response = String(ajax.responseText);
+        if ( response.substr(0, 1) != '{' )
+        {
+          handle_invalid_json(response);
+          return false;
+        }
+        response = parseJSON(response);
+        if ( response.success )
+        {
+          miniPromptDestroy(box, true);
+          ajaxRenameDoClientTransform(newname);
+          new messagebox( MB_OK|MB_ICONINFORMATION, $lang.get('ajax_rename_success_title'), $lang.get('ajax_rename_success_body', { page_name_new: newname }) );
+          mb_previously_had_darkener = false;
+        }
+        else
+        {
+          var errmsg = $lang.get('page_err_' + response.error);
+          alert(errmsg);
+        }
+      }
+    }, true);
+}
+
+function ajaxRenameDoClientTransform(newname)
+{
+  var obj = document.getElementById('h2PageName');
+  if ( obj )
+  {
+    obj.firstChild.nodeValue = newname;
+  }
+  document.title = newname;
 }
 
 function ajaxMakePage()
--- a/includes/clientside/static/dropdown.js	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/clientside/static/dropdown.js	Tue Apr 08 20:32:30 2008 -0400
@@ -92,6 +92,10 @@
                 obj.childNodes[i].onmouseover = function()  { jBoxOverHandler(this); };
                 obj.childNodes[i].onmouseout = function(e)  { jBoxOutHandler(this, e); };
                 obj.childNodes[i].nextSibling.onmouseout = function(e)  { jBoxOutHandler(this, e); };
+                if ( is_iPhone )
+                {
+                  obj.childNodes[i].onclick = function()  { jBoxOverHandler(this); return false; };
+                }
               }
             }
           }
--- a/includes/clientside/static/faders.js	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/clientside/static/faders.js	Tue Apr 08 20:32:30 2008 -0400
@@ -1,20 +1,39 @@
-// Message box system
+// Message box and visual effect system
 
-function darken(nofade)
+/**
+ * Darkens the browser screen. This will make the entire page un-clickable except for any floating divs created after this is called. Restore with enlighten().
+ * @param bool Controls whether the fade should be disabled or not. aclDisableTransitionFX will override this if set to true, and fades are never fired on IE.
+ * @param int When specified, represents the numeric opacity value to set the fade layer to. 1-100.
+ */
+
+function darken(nofade, opacVal)
 {
   if(IE)
     nofade = true;
+  if ( !opacVal )
+    opacVal = 70;
   if(document.getElementById('specialLayer_darkener'))
   {
     if(nofade)
     {
-      changeOpac(70, 'specialLayer_darkener');
+      changeOpac(opacVal, 'specialLayer_darkener');
       document.getElementById('specialLayer_darkener').style.display = 'block';
+      document.getElementById('specialLayer_darkener').myOpacVal = opacVal;
     }
     else
     {
-      document.getElementById('specialLayer_darkener').style.display = 'block';
-      opacity('specialLayer_darkener', 0, 70, 1000);
+      if ( document.getElementById('specialLayer_darkener').style.display != 'none' )
+      {
+        var currentOpac = document.getElementById('specialLayer_darkener').myOpacVal;
+        opacity('specialLayer_darkener', currentOpac, opacVal, 1000);
+        document.getElementById('specialLayer_darkener').myOpacVal = opacVal;
+      }
+      else
+      {
+        document.getElementById('specialLayer_darkener').style.display = 'block';
+        document.getElementById('specialLayer_darkener').myOpacVal = opacVal;
+        opacity('specialLayer_darkener', 0, opacVal, 1000);
+      }
     }
   } else {
     w = getWidth();
@@ -41,10 +60,11 @@
     thediv.style.height = '100%';
     thediv.zIndex = getHighestZ() + 5;
     thediv.id = 'specialLayer_darkener';
+    thediv.myOpacVal = opacVal;
     if(nofade)
     {
-      thediv.style.opacity = '0.7';
-      thediv.style.filter = 'alpha(opacity=70)';
+      thediv.style.opacity = ( parseFloat(opacVal) / 100 );
+      thediv.style.filter = 'alpha(opacity=' + opacVal + ')';
       body = document.getElementsByTagName('body');
       body = body[0];
       body.appendChild(thediv);
@@ -52,11 +72,16 @@
       body = document.getElementsByTagName('body');
       body = body[0];
       body.appendChild(thediv);
-      opacity('specialLayer_darkener', 0, 70, 1000);
+      opacity('specialLayer_darkener', 0, opacVal, 1000);
     }
   }
 }
 
+/**
+ * Un-darkens the screen and re-enables clicking of on-screen controls.
+ * @param bool If true, disables the fade effect. Fades are always disabled if aclDisableTransitionFX is true and on IE.
+ */
+
 function enlighten(nofade)
 {
   if(IE)
@@ -67,8 +92,13 @@
     {
       document.getElementById('specialLayer_darkener').style.display = 'none';
     }
-    opacity('specialLayer_darkener', 70, 0, 1000);
-    setTimeout("document.getElementById('specialLayer_darkener').style.display = 'none';", 1000);
+    else
+    {
+      var from = document.getElementById('specialLayer_darkener').myOpacVal;
+      // console.info('Fading from ' + from);
+      opacity('specialLayer_darkener', from, 0, 1000);
+      setTimeout("document.getElementById('specialLayer_darkener').style.display = 'none';", 1000);
+    }
   }
 }
 
@@ -388,6 +418,102 @@
     }
 }
 
+/**
+ * The miniPrompt function, for creating small prompts and dialogs. The window will be flown in and the window darkened with opac=0.4.
+ * @param function Will be passed an HTMLElement that is the body of the prompt window; the function can do with this as it pleases
+ */
+
+function miniPrompt(call_on_create)
+{
+  darken(false, 40);
+  
+  var wrapper = document.createElement('div');
+  wrapper.className = 'miniprompt';
+  var top = document.createElement('div');
+  top.className = 'mp-top';
+  var body = document.createElement('div');
+  body.className = 'mp-body';
+  var bottom = document.createElement('div');
+  bottom.className = 'mp-bottom';
+  if ( typeof(call_on_create) == 'function' )
+  {
+    call_on_create(body);
+  }
+  wrapper.appendChild(top);
+  wrapper.appendChild(body);
+  wrapper.appendChild(bottom);
+  var left = ( getWidth() / 2 ) - ( 388 / 2 );
+  wrapper.style.left = left + 'px';
+  var top = getScrollOffset() - 27;
+  wrapper.style.top = top + 'px';
+  domObjChangeOpac(0, wrapper);
+  var realbody = document.getElementsByTagName('body')[0];
+  realbody.appendChild(wrapper);
+  
+  fly_in_top(wrapper, true, true);
+  
+  setTimeout(function()
+    {
+      domObjChangeOpac(100, wrapper);
+    }, 40);
+}
+
+/**
+ * For a given element, loops through the element and all of its ancestors looking for a miniPrompt div, and returns it. Returns false on failure.
+ * @param object:HTMLElement Child node to scan
+ * @return object
+ */
+
+function miniPromptGetParent(obj)
+{
+  while ( true )
+  {
+    // prevent infinite loops
+    if ( !obj || obj.tagName == 'BODY' )
+      return false;
+    
+    if ( $dynano(obj).hasClass('miniprompt') )
+    {
+      return obj;
+    }
+    obj = obj.parentNode;
+  }
+  return false;
+}
+
+/**
+ * Destroys the first miniPrompt div encountered by recursively checking all parent nodes.
+ * Usage: <a href="javascript:miniPromptDestroy(this);">click</a>
+ * @param object:HTMLElement a child of the div.miniprompt
+ * @param bool If true, does not call enlighten().
+ */
+
+function miniPromptDestroy(obj, nofade)
+{
+  obj = miniPromptGetParent(obj);
+  if ( !obj )
+    return false;
+  
+  // found it
+  var parent = obj.parentNode;
+  if ( !nofade )
+    enlighten();
+  var timeout = fly_out_top(obj, true, true);
+  setTimeout(function()
+    {
+      parent.removeChild(obj);
+    }, timeout);
+}
+
+/**
+ * Simple test case
+ */
+
+function miniPromptTest()
+{
+  miniPrompt(function(div) { div.innerHTML = 'hello world! <a href="#" onclick="miniPromptDestroy(this); return false;">destroy me</a>'; });
+}
+
 // Function to fade classes info-box, warning-box, error-box, etc.
 
 function fadeInfoBoxes()
--- a/includes/functions.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/functions.php	Tue Apr 08 20:32:30 2008 -0400
@@ -4078,6 +4078,17 @@
 }
 
 /**
+ * Trims a snippet of text to the first and last curly braces. Useful for JSON.
+ * @param string Text to trim
+ * @return string
+ */
+
+function enano_trim_json($json)
+{
+  return preg_replace('/^([^{]+)\{/', '{', preg_replace('/\}([^}]+)$/', '}', $json));
+}
+
+/**
  * Starts the profiler.
  */
 
--- a/includes/lang.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/lang.php	Tue Apr 08 20:32:30 2008 -0400
@@ -389,8 +389,7 @@
     $contents =& $block[0]['value'];
     
     // Trim off all text before and after the starting and ending braces
-    $contents = preg_replace('/^([^{]+)\{/', '{', $contents);
-    $contents = preg_replace('/\}([^}]+)$/', '}', $contents);
+    $contents = enano_trim_json($contents);
     
     // Correct syntax to be nice to the json parser
     $contents = enano_clean_json($contents);
--- a/includes/plugins.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/plugins.php	Tue Apr 08 20:32:30 2008 -0400
@@ -224,9 +224,22 @@
       if ( is_string($type) && $blocks[1][$i] !== $type )
         continue;
       
+      $value =& $blocks[4][$i];
+      // parse includes
+      preg_match_all('/^!include [\'"]?(.+?)[\'"]?$/m', $value, $includes);
+      foreach ( $includes[0] as $i => $replace )
+      {
+        $filename = ENANO_ROOT . '/' . $includes[1][$i];
+        if ( @file_exists( $filename ) && @is_readable( $filename ) )
+        {
+          $contents = @file_get_contents($filename);
+          $value = str_replace_once($replace, $contents, $value);
+        }
+      }
+      
       $el = self::parse_vars($blocks[2][$i]);
       $el['block'] = $blocks[1][$i];
-      $el['value'] = $blocks[4][$i];
+      $el['value'] = $value;
       $return[] = $el;
     }
     
--- a/includes/rijndael.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/rijndael.php	Tue Apr 08 20:32:30 2008 -0400
@@ -807,10 +807,11 @@
    * @param string $text the encrypted text
    * @param string $key the raw binary key used to encrypt the text
    * @param int $input_encoding the encoding used for the encrypted string. Can be ENC_BINARY, ENC_HEX, or ENC_BASE64.
+   * @param bool $no_cache If true, will not cache the decrypted string on disk.
    * @return string
    */
    
-  function decrypt($text, $key, $input_encoding = ENC_HEX)
+  function decrypt($text, $key, $input_encoding = ENC_HEX, $no_cache = false)
   {
     if ( $text == '' )
       return '';
@@ -871,7 +872,8 @@
     
     $this->decrypt_cache[$key_bin][$text_bin] = $dypt;
     
-    aes_decrypt_cache_store($text_bin, $dypt, $key_bin);
+    if ( !$no_cache )
+      aes_decrypt_cache_store($text_bin, $dypt, $key_bin);
     
     return $dypt;
   }
--- a/includes/sessions.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/includes/sessions.php	Tue Apr 08 20:32:30 2008 -0400
@@ -516,7 +516,7 @@
               $super = $this->validate_session($key);
             }
           }
-          if(is_array($super))
+          if(is_array(@$super))
           {
             $this->auth_level = intval($super['auth_level']);
             $this->sid_super = $_REQUEST['auth'];
@@ -3233,7 +3233,8 @@
         // decrypt user info
         $aes_key = hexdecode($aes_key);
         $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
-        $userinfo_json = $aes->decrypt($userinfo_crypt, $aes_key, ENC_HEX);
+        // using "true" here disables caching of the decrypted login info (which includes the password)
+        $userinfo_json = $aes->decrypt($userinfo_crypt, $aes_key, ENC_HEX, true);
         if ( !$userinfo_json )
         {
           return array(
--- a/install/schemas/mysql_stage2.sql	Tue Apr 08 20:30:05 2008 -0400
+++ b/install/schemas/mysql_stage2.sql	Tue Apr 08 20:32:30 2008 -0400
@@ -318,6 +318,17 @@
   PRIMARY KEY ( key_id )
 ) CHARACTER SET `utf8` COLLATE `utf8_bin`;
 
+-- Added in 1.1.4
+-- This is really honestly a better way to handle plugins.
+
+CREATE TABLE {{TABLE_PREFIX}}plugins (
+  plugin_id int(12) NOT NULL auto_increment,
+  plugin_filename varchar(63),
+  plugin_flags int(12),
+  plugin_version varchar(16),
+  PRIMARY KEY ( plugin_id )
+) ENGINE `MyISAM` CHARACTER SET `utf8` COLLATE `utf8_bin`;
+
 INSERT INTO {{TABLE_PREFIX}}config(config_name, config_value) VALUES
   ('site_name', '{{SITE_NAME}}'),
   ('main_page', 'Main_Page'),
@@ -335,10 +346,6 @@
   ('w3c_vcss', '0'),
   ('approve_comments', '0'),
   ('enable_comments', '1'),
-  ('plugin_SpecialAdmin.php', '1'),
-  ('plugin_SpecialPageFuncs.php', '1'),
-  ('plugin_SpecialUserFuncs.php', '1'),
-  ('plugin_SpecialCSS.php', '1'),
   ('copyright_notice', '{{COPYRIGHT}}'),
   ('wiki_edit_notice_text', '== Why can I edit this page? ==\n\nEveryone can edit almost any page in this website. This concept is called a wiki. It gives everyone the opportunity to make a change for the best. While some spam and vandalism may occur, it is believed that most contributions will be legitimate and helpful.\n\nFor security purposes, a history of all page edits is kept, and administrators are able to restore vandalized or spammed pages with just a few clicks.'),
   ('cache_thumbs', '{{ENABLE_CACHE}}'),
--- a/install/schemas/postgresql_stage2.sql	Tue Apr 08 20:30:05 2008 -0400
+++ b/install/schemas/postgresql_stage2.sql	Tue Apr 08 20:32:30 2008 -0400
@@ -315,6 +315,17 @@
   PRIMARY KEY ( key_id )
 );
 
+-- Added in 1.1.4
+-- This is really honestly a better way to handle plugins.
+
+CREATE TABLE {{TABLE_PREFIX}}plugins (
+  plugin_id SERIAL,
+  plugin_filename varchar(63),
+  plugin_flags int,
+  plugin_version varchar(16),
+  PRIMARY KEY ( plugin_id )
+);
+
 INSERT INTO {{TABLE_PREFIX}}config(config_name, config_value) VALUES
   ('site_name', '{{SITE_NAME}}'),
   ('main_page', 'Main_Page'),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/install/schemas/upgrade/1.1.3-1.1.4-mysql.sql	Tue Apr 08 20:32:30 2008 -0400
@@ -0,0 +1,10 @@
+-- This is really honestly a better way to handle plugins.
+
+CREATE TABLE {{TABLE_PREFIX}}plugins (
+  plugin_id int(12) NOT NULL auto_increment,
+  plugin_filename varchar(63),
+  plugin_flags int(12),
+  plugin_version varchar(16),
+  PRIMARY KEY ( plugin_id )
+) ENGINE `MyISAM` CHARACTER SET `utf8` COLLATE `utf8_bin`;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/install/schemas/upgrade/1.1.3-1.1.4-postgresql.sql	Tue Apr 08 20:32:30 2008 -0400
@@ -0,0 +1,9 @@
+-- This is really honestly a better way to handle plugins.
+
+CREATE TABLE {{TABLE_PREFIX}}plugins (
+  plugin_id SERIAL,
+  plugin_filename varchar(63),
+  plugin_flags int,
+  plugin_version varchar(16),
+  PRIMARY KEY ( plugin_id )
+);
--- a/language/english/admin.json	Tue Apr 08 20:30:05 2008 -0400
+++ b/language/english/admin.json	Tue Apr 08 20:32:30 2008 -0400
@@ -382,24 +382,7 @@
       msg_demo_mode: 'Hmm, enabling executables, are we? Tsk tsk. I\'d love to know what\'s in that EXE file you want to upload. OK, maybe you didn\'t enable EXEs. But nevertheless, changing allowed filetypes is disabled in the demo.',
     },
     acppl: {
-      err_heading: 'Error disabling plugin',
-      err_demo_plugin: 'The demo lockdown plugin cannot be disabled in demo mode.',
-      err_system_plugin: 'The plugin you selected cannot be disabled because it is a system plugin.',
-      err_open_dir: 'The plugins/ directory could not be opened.',
-      err_missing_dir: 'The plugins/ directory is missing from your Enano installation.',
-      col_filename: 'Plugin filename',
-      col_name: 'Plugin name',
-      col_description: 'Description',
-      col_author: 'Author',
-      col_version: 'Version',
-      btn_enable: 'Enable',
-      btn_disable: 'Disable',
-      btn_reimport: 'Re-import',
-      btn_reimport_tip: 'Reloads all the language and meta data from this plugin file.',
-      msg_reimport_success: 'All language strings from this plugin have been re-imported.',
-      btn_hide_system: 'Hide system plugins',
-      btn_show_system: 'Show system plugins',
-      lbl_system_plugin: '[System]',
+      
     },
     acppm: {
       heading_main: 'Edit page properties',
--- a/language/english/core.json	Tue Apr 08 20:30:05 2008 -0400
+++ b/language/english/core.json	Tue Apr 08 20:32:30 2008 -0400
@@ -430,7 +430,9 @@
     ajax: {
       // Client-side messages
       protect_prompt_reason: 'Reason for (un)protecting:',
-      rename_prompt: 'What title should this page be renamed to?\nNote: This does not and will never change the URL of this page, that must be done from the admin panel.',
+      rename_prompt: 'What title should this page be renamed to?\n%this.ajax_rename_notice%',
+      rename_prompt_short: 'Enter a new name for this page',
+      rename_notice: 'This won\'t change the URL to this page. To change the URL to this page, use Page Manager in the administration panel.',
       delete_prompt_reason: 'Please enter your reason for deleting this page.',
       delete_confirm: 'You are about to REVERSIBLY delete this page. Do you REALLY want to do this?\n\n(Comments and categorization data, as well as any attached files, will be permanently lost)',
       delvote_confirm: 'Are you sure that you want to vote that this page be deleted?',
@@ -449,7 +451,8 @@
       
       // Server-side responses
       rename_too_short: 'The name you entered is too short. Please enter a longer name for this page.',
-      rename_success: 'The page "%page_name_old%" has been renamed to "%page_name_new%". You are encouraged to leave a comment explaining your action.\n\nYou will see the change take effect the next time you reload this page.',
+      rename_success_title: 'Page renamed',
+      rename_success_body: 'This page has been renamed to "%page_name_new%". You are encouraged to leave a comment explaining your action.<br /><br />Please note that you might not see this change take effect until you reload the page.',
       clearlogs_success: 'The logs for this page have been cleared. A backup of this page has been added to the logs table so that this page can be restored in case of vandalism or spam later.',
       delete_need_reason: 'Invalid reason for deletion passed. Please enter a reason for deleting this page.',
       delete_success: 'This page has been deleted. Note that there is still a log of edits and actions in the database, and anyone with admin rights can raise this page from the dead unless the log is cleared. If the deleted file is an image, there may still be cached thumbnails of it in the cache/ directory, which is inaccessible to users.',
@@ -458,7 +461,6 @@
       delvote_reset_success: 'The number of votes for having this page deleted has been reset to zero.',
       password_success: 'The password for this page has been set.',
       password_disable_success: 'The password for this page has been disabled.',
-      
     },
     sidebar: {
       title_navigation: 'Navigation',
@@ -691,6 +693,7 @@
       cancel: 'Cancel',
       yes: 'Yes',
       no: 'No',
+      go: 'Go',
       unit_bytes: 'bytes',
       unit_kilobytes: 'kilobytes',
       unit_megabytes: 'megabytes',
--- a/plugins/PrivateMessages.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/PrivateMessages.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_privatemessages_title
-Plugin URI: http://enanocms.org/
-Description: plugin_privatemessages_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_privatemessages_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_privatemessages_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- a/plugins/SpecialAdmin.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialAdmin.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialadmin_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialadmin_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialadmin_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialadmin_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
@@ -48,6 +50,7 @@
 require(ENANO_ROOT . '/plugins/admin/UserManager.php');
 require(ENANO_ROOT . '/plugins/admin/LangManager.php');
 require(ENANO_ROOT . '/plugins/admin/ThemeManager.php');
+require(ENANO_ROOT . '/plugins/admin/PluginManager.php');
 
 // For convenience and nothing more.
 function acp_start_form()
@@ -1146,6 +1149,7 @@
   <?php
 }
 
+/*
 function page_Admin_PluginManager()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
@@ -1328,6 +1332,7 @@
     echo '<tr><th colspan="6" class="subhead">'.$showhide_link.'</th></tr>';
     echo '</table></div>';
 }
+*/
 
 function page_Admin_DBBackup()
 {
--- a/plugins/SpecialCSS.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialCSS.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialcss_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialcss_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialcss_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialcss_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- a/plugins/SpecialGroups.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialGroups.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialgroups_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialgroups_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialgroups_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialgroups_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- a/plugins/SpecialPageFuncs.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialPageFuncs.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialpagefuncs_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialpagefuncs_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialpagefuncs_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialpagefuncs_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- a/plugins/SpecialRecentChanges.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialRecentChanges.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialrecentchanges_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialrecentchanges_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialrecentchanges_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialrecentchanges_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- a/plugins/SpecialSearch.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialSearch.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialsearch_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialsearch_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialsearch_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialsearch_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- a/plugins/SpecialUpdownload.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialUpdownload.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialupdownload_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialupdownload_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialupdownload_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialupdownload_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- a/plugins/SpecialUserFuncs.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialUserFuncs.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialuserfuncs_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialuserfuncs_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialuserfuncs_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialuserfuncs_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- a/plugins/SpecialUserPrefs.php	Tue Apr 08 20:30:05 2008 -0400
+++ b/plugins/SpecialUserPrefs.php	Tue Apr 08 20:32:30 2008 -0400
@@ -1,12 +1,14 @@
 <?php
-/*
-Plugin Name: plugin_specialuserprefs_title
-Plugin URI: http://enanocms.org/
-Description: plugin_specialuserprefs_desc
-Author: Dan Fuhry
-Version: 1.1.3
-Author URI: http://enanocms.org/
-*/
+/**!info**
+{
+  "Plugin Name"  : "plugin_specialuserprefs_title",
+  "Plugin URI"   : "http://enanocms.org/",
+  "Description"  : "plugin_specialuserprefs_desc",
+  "Author"       : "Dan Fuhry",
+  "Version"      : "1.1.3",
+  "Author URI"   : "http://enanocms.org/"
+}
+**!*/
 
 /*
  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/admin/PluginManager.php	Tue Apr 08 20:32:30 2008 -0400
@@ -0,0 +1,263 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.3 (Caoineag alpha 3)
+ * Copyright (C) 2006-2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+/**
+ * SYNOPSIS OF PLUGIN FRAMEWORK
+ *
+ * The new plugin manager is making an alternative approach to managing plugin files by allowing metadata to be embedded in them
+ * or optionally included from external files. This method is API- and format-compatible with old plugins. The change is being
+ * made because we believe this will provide greater flexibility within plugin files.
+ * 
+ * Plugin files can contain one or more specially formatted comment blocks with metadata, language strings, and installation or
+ * upgrade SQL schemas. For this to work, plugins need to define their version numbers in an Enano-readable and standardized
+ * format, and we think the best way to do this is with JSON. It is important that plugins define both the current version and
+ * a list of all past versions, and then have upgrade sections telling which version they go from and which one they go to.
+ * 
+ * The format for the special comment blocks is:
+ <code>
+ /**!blocktype( param1 = "value1" [ param2 = "value2" ... ] )**
+ 
+ ... block content ...
+ 
+ **!* / (remove that last space)
+ </code>
+ * The format inside blocks varies. Metadata and language strings will be in JSON; installation and upgrade schemas will be in
+ * SQL. You can include an external file into a block using the following syntax inside of a block:
+ <code>
+ !include "path/to/file"
+ </code>
+ * The file will always be relative to the Enano root. So if your plugin has a language file in ENANO_ROOT/plugins/fooplugin/,
+ * you would use "plugins/fooplugin/language.json".
+ *
+ * The format for plugin metadata is as follows:
+ <code>
+ /**!info**
+ {
+   "Plugin Name" : "Foo plugin",
+   "Plugin URI" : "http://fooplugin.enanocms.org/",
+   "Description" : "Some short descriptive text",
+   "Author" : "John Doe",
+   "Version" : "0.1",
+   "Author URI" : "http://yourdomain.com/",
+   "Version list" : [ "0.1-alpha1", "0.1-alpha2", "0.1-beta1", "0.1" ]
+ }
+ **!* /
+ </code>
+ * This is the format for language data:
+ <code>
+ /**!language**
+ {
+   eng: {
+     categories: [ 'meta', 'foo', 'bar' ],
+     strings: {
+       meta: {
+         foo: "Foo strings",
+         bar: "Bar strings"
+       },
+       foo: {
+         string_name: "string value",
+         string_name_2: "string value 2"
+       }
+     }
+   }
+ }
+ **!* / (once more, remove the space in there)
+ </code>
+ * Here is the format for installation schemas:
+ <code>
+ /**!install**
+ 
+ CREATE TABLE {{TABLE_PREFIX}}foo_table(
+   ...
+ )
+ 
+ **!* /
+ </code>
+ * And finally, the format for upgrade schemas:
+ <code>
+ /**!upgrade from = "0.1-alpha1" to = "0.1-alpha2" **
+ 
+ **!* /
+ </code>
+ * Remember that upgrades will always be done incrementally, so if the user is upgrading 0.1-alpha2 to 0.1, Enano's plugin
+ * engine will run the 0.1-alpha2 to 0.1-beta1 upgrader, then the 0.1-beta1 to 0.1 upgrader, going by the versions listed in
+ * the example metadata block above.
+ * 
+ * All of this information is effective as of Enano 1.1.4.
+ */
+
+// Plugin manager "2.0"
+
+function page_Admin_PluginManager()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  global $lang;
+  if ( $session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN )
+  {
+    $login_link = makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true);
+    echo '<h3>' . $lang->get('adm_err_not_auth_title') . '</h3>';
+    echo '<p>' . $lang->get('adm_err_not_auth_body', array( 'login_link' => $login_link )) . '</p>';
+    return;
+  }
+  
+  // Are we processing an AJAX request from the smartform?
+  if ( $paths->getParam(0) == 'action.json' )
+  {
+    // Set to application/json to discourage advertisement scripts
+    header('Content-Type: application/json');
+    
+    // Init return data
+    $return = array('mode' => 'error', 'error' => 'undefined');
+    
+    // Start parsing process
+    try
+    {
+      // Is the request properly sent on POST?
+      if ( isset($_POST['r']) )
+      {
+        // Try to decode the request
+        $request = enano_json_decode($_POST['r']);
+        // Is the action to perform specified?
+        if ( isset($request['mode']) )
+        {
+          switch ( $request['mode'] )
+          {
+            default:
+              // The requested action isn't something this script knows how to do
+              $return = array(
+                'mode' => 'error',
+                'error' => 'Unknown mode "' . $request['mode'] . '" sent in request'
+              );
+              break;
+          }
+        }
+        else
+        {
+          // Didn't specify action
+          $return = array(
+            'mode' => 'error',
+            'error' => 'Missing key "mode" in request'
+          );
+        }
+      }
+      else
+      {
+        // Didn't send a request
+        $return = array(
+          'mode' => 'error',
+          'error' => 'No request specified'
+        );
+      }
+    }
+    catch ( Exception $e )
+    {
+      // Sent a request but it's not valid JSON
+      $return = array(
+          'mode' => 'error',
+          'error' => 'Invalid request - JSON parsing failed'
+        );
+    }
+    
+    echo enano_json_encode($return);
+    
+    return true;
+  }
+  
+  //
+  // Not a JSON request, output normal HTML interface
+  //
+  
+  // Scan all plugins
+  $plugin_list = array();
+  
+  if ( $dirh = @opendir( ENANO_ROOT . '/plugins' ) )
+  {
+    while ( $dh = @readdir($dirh) )
+    {
+      if ( !preg_match('/\.php$/i', $dh) )
+        continue;
+      $fullpath = ENANO_ROOT . "/plugins/$dh";
+      // it's a PHP file, attempt to read metadata
+      // pass 1: try to read a !info block
+      $blockdata = $plugins->parse_plugin_blocks($fullpath, 'info');
+      if ( empty($blockdata) )
+      {
+        // no !info block, check for old header
+        $fh = @fopen($fullpath, 'r');
+        if ( !$fh )
+          // can't read, bail out
+          continue;
+        $plugin_data = array();
+        for ( $i = 0; $i < 8; $i++ )
+        {
+          $plugin_data[] = @fgets($fh, 8096);
+        }
+        // close our file handle
+        fclose($fh);
+        // is the header correct?
+        if ( trim($plugin_data[0]) != '<?php' || trim($plugin_data[1]) != '/*' )
+        {
+          // nope. get out.
+          continue;
+        }
+        // parse all the variables
+        $plugin_meta = array();
+        for ( $i = 2; $i <= 7; $i++ )
+        {
+          if ( !preg_match('/^([A-z0-9 ]+?): (.+?)$/', trim($plugin_data[$i]), $match) )
+            continue 2;
+          $plugin_meta[ strtolower($match[1]) ] = $match[2];
+        }
+      }
+      else
+      {
+        // parse JSON block
+        $plugin_data =& $blockdata[0]['value'];
+        $plugin_data = enano_clean_json(enano_trim_json($plugin_data));
+        try
+        {
+          $plugin_meta_uc = enano_json_decode($plugin_data);
+        }
+        catch ( Exception $e )
+        {
+          continue;
+        }
+        // convert all the keys to lowercase
+        $plugin_meta = array();
+        foreach ( $plugin_meta_uc as $key => $value )
+        {
+          $plugin_meta[ strtolower($key) ] = $value;
+        }
+      }
+      if ( !isset($plugin_meta) || !is_array(@$plugin_meta) )
+      {
+        // parsing didn't work.
+        continue;
+      }
+      // check for required keys
+      $required_keys = array('plugin name', 'plugin uri', 'description', 'author', 'version', 'author uri');
+      foreach ( $required_keys as $key )
+      {
+        if ( !isset($plugin_meta[$key]) )
+          // not set, skip this plugin
+          continue 2;
+      }
+      // decide if it's a system plugin
+      $plugin_meta['system plugin'] = in_array($dh, $plugins->system_plugins);
+      // all checks passed
+      $plugin_list[$dh] = $plugin_meta;
+    }
+  }
+  echo '<pre>' . print_r($plugin_list, true) . '</pre>';
+}