includes/pageutils.php
changeset 1227 bdac73ed481e
parent 1216 4125e19d3b27
child 1252 e34c23a35dc9
--- a/includes/pageutils.php	Sun Mar 28 21:49:26 2010 -0400
+++ b/includes/pageutils.php	Sun Mar 28 23:10:46 2010 -0400
@@ -13,2446 +13,2446 @@
  */
  
 class PageUtils {
-  
-  /**
-   * Tell if a username is used or not.
-   * @param $name the name to check for
-   * @return string
-   */
-  
-  public static function checkusername($name)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    $name = str_replace('_', ' ', $name);
-    $q = $db->sql_query('SELECT username FROM ' . table_prefix.'users WHERE username=\'' . $db->escape(rawurldecode($name)) . '\'');
-    if ( !$q )
-    {
-      die($db->get_error());
-    }
-    if ( $db->numrows() < 1)
-    {
-      $db->free_result(); return('good');
-    }
-    else
-    {
-      $db->free_result(); return('bad');
-    }
-  }
-  
-  /**
-   * Get the wiki formatting source for a page
-   * @param $page the full page id (Namespace:Pagename)
-   * @return string
-   * @todo (DONE) Make it require a password (just for security purposes)
-   */
-   
-  public static function getsource($page, $password = false)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    if ( !isPage($page) )
-    {
-      return '';
-    }
-    
-    list($page_id, $namespace) = RenderMan::strToPageID($page);
-    $ns = namespace_factory($page_id, $namespace);
-    $cdata = $ns->get_cdata();
-    
-    if ( strlen($cdata['password']) == 40 )
-    {
-      if(!$password || ( $password != $cdata['password']))
-      {
-        return 'invalid_password';
-      }
-    }
-    
-    if(!$session->get_permissions('view_source')) // Dependencies handle this for us - this also checks for read privileges
-      return 'access_denied';
-    $pid = RenderMan::strToPageID($page);
-    if($pid[1] == 'Special' || $pid[1] == 'Admin')
-    {
-      die('This type of page (' . $paths->nslist[$pid[1]] . ') cannot be edited because the page source code is not stored in the database.');
-    }
-    
-    $e = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix.'page_text WHERE page_id=\'' . $pid[0] . '\' AND namespace=\'' . $pid[1] . '\'');
-    if ( !$e )
-    {
-      $db->_die('The page text could not be selected.');
-    }
-    if( $db->numrows() < 1 )
-    {
-      return ''; //$db->_die('There were no rows in the text table that matched the page text query.');
-    }
-    
-    $r = $db->fetchrow();
-    $db->free_result();
-    $message = $r['page_text'];
-    
-    return htmlspecialchars($message);
-  }
-  
-  /**
-   * DEPRECATED. Previously returned the full rendered contents of a page.
-   * @param $page the full page id (Namespace:Pagename)
-   * @param $send_headers true if the theme headers should be sent (still dependent on current page settings), false otherwise
-   * @return string
-   */
-  
-  public static function getpage($page, $send_headers = false, $hist_id = false)
-  {
-    die('PageUtils->getpage is deprecated.');
-  }
-  
-  /**
-   * Writes page data to the database, after verifying permissions and running the XSS filter
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $message the text to save
-   * @return string
-   */
-   
-  public static function savepage($page_id, $namespace, $message, $summary = 'No edit summary given', $minor = false)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    
-    $page = new PageProcessor($page_id, $namespace);
-    $cdata = $page->ns->get_cdata();
-    return $page->update_page($message, $summary, $minor, $cdata['page_format']);
-  }
-  
-  /**
-   * Creates a page, both in memory and in the database.
-   * @param string $page_id
-   * @param string $namespace
-   * @return bool true on success, false on failure
-   */
-  
-  public static function createPage($page_id, $namespace, $name = false, $visible = 1)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    if(in_array($namespace, Array('Special', 'Admin')))
-    {
-      // echo '<b>Notice:</b> PageUtils::createPage: You can\'t create a special page in the database<br />';
-      return 'You can\'t create a special page in the database';
-    }
-    
-    if(!isset($paths->nslist[$namespace]))
-    {
-      // echo '<b>Notice:</b> PageUtils::createPage: Couldn\'t look up the namespace<br />';
-      return 'Couldn\'t look up the namespace';
-    }
-    
-    $pname = $paths->nslist[$namespace] . $page_id;
-    if(isPage($pname))
-    {
-      // echo '<b>Notice:</b> PageUtils::createPage: Page already exists<br />';
-      return 'Page already exists';
-    }
-    
-    if(!$session->get_permissions('create_page'))
-    {
-      // echo '<b>Notice:</b> PageUtils::createPage: Not authorized to create pages<br />';
-      return 'Not authorized to create pages';
-    }
-    
-    if($session->user_level < USER_LEVEL_ADMIN && $namespace == 'System')
-    {
-      // echo '<b>Notice:</b> PageUtils::createPage: Not authorized to create system messages<br />';
-      return 'Not authorized to create system messages';
-    }
-    
-    if ( substr($page_id, 0, 8) == 'Project:' )
-    {
-      // echo '<b>Notice:</b> PageUtils::createPage: Prefix "Project:" is reserved<br />';
-      return 'The prefix "Project:" is reserved for a parser shortcut; if a page was created using this prefix, it would not be possible to link to it.';
-    }
-    
-    /*
-    // Dunno why this was here. Enano can handle more flexible names than this...
-    $regex = '#^([A-z0-9 _\-\.\/\!\@\(\)]*)$#is';
-    if(!preg_match($regex, $name))
-    {
-      //echo '<b>Notice:</b> PageUtils::createPage: Name contains invalid characters<br />';
-      return 'Name contains invalid characters';
-    }
-    */
-    
-    $page_id = dirtify_page_id($page_id);
-    
-    if ( !$name )
-      $name = str_replace('_', ' ', $page_id);
-    
-    $page_id = sanitize_page_id( $page_id );
-    
-    $prot = ( $namespace == 'System' ) ? 1 : 0;
-    
-    $ips = array(
-      'ip' => array(),
-      'u' => array()
-      );
-    
-    $page_data = Array(
-      'name'=>$name,
-      'urlname'=>$page_id,
-      'namespace'=>$namespace,
-      'special'=>0,'visible'=>1,'comments_on'=>0,'protected'=>$prot,'delvotes'=>0,'delvote_ips'=>serialize($ips),'wiki_mode'=>2,
-    );
-    
-    // die('PageUtils::createpage: Creating page with this data:<pre>' . print_r($page_data, true) . '</pre>');
-    
-    $paths->add_page($page_data);
-    
-    $qa = $db->sql_query('INSERT INTO ' . table_prefix.'pages(name,urlname,namespace,visible,protected,delvote_ips) VALUES(\'' . $db->escape($name) . '\', \'' . $db->escape($page_id) . '\', \'' . $namespace . '\', '. ( $visible ? '1' : '0' ) .', ' . $prot . ', \'' . $db->escape(serialize($ips)) . '\');');
-    $qb = $db->sql_query('INSERT INTO ' . table_prefix.'page_text(page_id,namespace) VALUES(\'' . $db->escape($page_id) . '\', \'' . $namespace . '\');');
-    $qc = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,author_uid,page_id,namespace) VALUES('.time().', \'DEPRECATED\', \'page\', \'create\', \'' . $session->username . '\', ' . $session->user_id . ', \'' . $db->escape($page_id) . '\', \'' . $namespace . '\');');
-    
-    if($qa && $qb && $qc)
-      return 'good';
-    else
-    {
-      return $db->get_error();
-    }
-  }
-  
-  /**
-   * Sets the protection level on a page.
-   * @param $page_id string the page ID
-   * @param $namespace string the namespace
-   * @param $level int level of protection - 0 is off, 1 is full, 2 is semi
-   * @param $reason string why the page is being (un)protected
-   * @return string - "good" on success, in all other cases, an error string (on query failure, calls $db->_die() )
-   */
-  public static function protect($page_id, $namespace, $level, $reason)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    
-    $page = new PageProcessor($page_id, $namespace);
-    return $page->protect_page($level, $reason);
-  }
-  
-  /**
-   * Generates an HTML table with history information in it.
-   * @param string the page ID
-   * @param string the namespace
-   * @param string page password
-   * @return string
-   */
-  
-  public static function histlist($page_id, $namespace, $password = false)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    if(!$session->get_permissions('history_view'))
-      return 'Access denied';
-    
-    ob_start();
-    
-    $pname = $paths->get_pathskey($page_id, $namespace);
-    $ns = namespace_factory($page_id, $namespace);
-    $cdata = $ns->get_cdata();
-    
-    if ( !isPage($pname) )
-    {
-      return 'DNE';
-    }
-    
-    if ( isPage($pname) )
-    {
-      $password_exists = ( !empty($cdata['password']) && $cdata['password'] !== sha1('') );
-      if ( $password_exists && $password !== $cdata['password'] )
-      {
-        return '<p>' . $lang->get('history_err_wrong_password') . '</p>';
-      }
-    }
-    
-    $wiki = ( ( $cdata['wiki_mode'] == 2 && getConfig('wiki_mode') == '1') || $cdata['wiki_mode'] == 1) ? true : false;
-    $prot = ( ( $cdata['protected'] == 2 && $session->user_logged_in && $session->reg_time + 60*60*24*4 < time() ) || $cdata['protected'] == 1) ? true : false;
-    
-    $q = 'SELECT log_id,time_id,date_string,page_id,namespace,author,author_uid,u.username,edit_summary,minor_edit FROM ' . table_prefix . "logs AS l\n"
-       . "  LEFT JOIN " . table_prefix . "users AS u\n"
-       . "    ON ( u.user_id = l.author_uid OR u.user_id IS NULL )\n"
-       . "  WHERE log_type='page' AND action='edit' AND page_id='$page_id' AND namespace='$namespace' AND is_draft != 1 ORDER BY time_id DESC;";
-    
-    if ( !($q = $db->sql_query($q)) )
-      $db->_die('The history data for the page "' . $paths->cpage['name'] . '" could not be selected.');
-    
-    echo $lang->get('history_page_subtitle') . '
-          <h3>' . $lang->get('history_heading_edits') . '</h3>';
-    $numrows = $db->numrows();
-    if ( $numrows < 1 )
-    {
-      echo $lang->get('history_no_entries');
-    }
-    else
-    {
-      echo '<form action="'.makeUrlNS($namespace, $page_id, 'do=diff').'" onsubmit="ajaxHistDiff(); return false;" method="get">
-            <input type="submit" value="' . $lang->get('history_btn_compare') . '" />
-            ' . ( urlSeparator == '&' ? '<input type="hidden" name="title" value="' . htmlspecialchars($paths->nslist[$namespace] . $page_id) . '" />' : '' ) . '
-            ' . ( $session->sid_super ? '<input type="hidden" name="auth"  value="' . $session->sid_super . '" />' : '') . '
-            <input type="hidden" name="do" value="diff" />
-            <br /><span>&nbsp;</span>
-            <div class="tblholder">
-            <table border="0" width="100%" cellspacing="1" cellpadding="4">
-            <tr>
-              <th colspan="2">' . $lang->get('history_col_diff') . '</th>
-              <th>' . $lang->get('history_col_datetime') . '</th>
-              <th>' . $lang->get('history_col_user') . '</th>
-              <th>' . $lang->get('history_col_summary') . '</th>
-              <th>' . $lang->get('history_col_minor') . '</th>
-              <th colspan="3">' . $lang->get('history_col_actions') . '</th>
-            </tr>'."\n"."\n";
-      $cls = 'row2';
-      $ticker = 0;
-      
-      while ( $r = $db->fetchrow($q) )
-      {
-        
-        $ticker++;
-        
-        if($cls == 'row2') $cls = 'row1';
-        else $cls = 'row2';
-        
-        echo '<tr>'."\n";
-        
-        // Diff selection
-        if($ticker == 1)
-        {
-          $s1 = '';
-          $s2 = 'checked="checked" ';
-        }
-        elseif($ticker == 2)
-        {
-          $s1 = 'checked="checked" ';
-          $s2 = '';
-        }
-        else
-        {
-          $s1 = '';
-          $s2 = '';
-        }
-        if($ticker > 1)        echo '<td class="' . $cls . '" style="padding: 0;"><input ' . $s1 . 'name="diff1" type="radio" value="' . $r['time_id'] . '" id="diff1_' . $r['time_id'] . '" class="clsDiff1Radio" onclick="selectDiff1Button(this);" /></td>'."\n"; else echo '<td class="' . $cls . '"></td>';
-        if($ticker < $numrows) echo '<td class="' . $cls . '" style="padding: 0;"><input ' . $s2 . 'name="diff2" type="radio" value="' . $r['time_id'] . '" id="diff2_' . $r['time_id'] . '" class="clsDiff2Radio" onclick="selectDiff2Button(this);" /></td>'."\n"; else echo '<td class="' . $cls . '"></td>';
-        
-        // Date and time
-        echo '<td class="' . $cls . '" style="white-space: nowrap;">' . enano_date(ED_DATE | ED_TIME, intval($r['time_id'])) . '</td class="' . $cls . '">'."\n";
-        
-        // User
-        $real_username = $r['author_uid'] > 1 && !empty($r['username']) ? $r['username'] : $r['author'];
-        $rank_info = $session->get_user_rank($r['author_uid']);
-        if ( $session->get_permissions('mod_misc') && is_valid_ip($r['author']) && $r['author_uid'] == 1 )
-        {
-          $rc = ' style="cursor: pointer;" title="' . $lang->get('history_tip_rdns') . '" onclick="ajaxReverseDNS(this, \'' . $r['author'] . '\');"';
-        }
-        else
-        {
-          $rc = '';
-        }
-        echo '<td class="' . $cls . '"' . $rc . '><a href="'.makeUrlNS('User', sanitize_page_id($real_username)).'" ';
-        if ( !isPage($paths->nslist['User'] . sanitize_page_id($real_username)) )
-        {
-          echo 'class="wikilink-nonexistent"';
-        }
-        echo 'style="' . $rank_info['rank_style'] . '">' . htmlspecialchars($real_username) . '</a></td class="' . $cls . '">'."\n";
-        
-        // Edit summary
-        if ( $r['edit_summary'] == 'Automatic backup created when logs were purged' )
-        {
-          $r['edit_summary'] = $lang->get('history_summary_clearlogs');
-        }
-        echo '<td class="' . $cls . '">' . $r['edit_summary'] . '</td>'."\n";
-        
-        // Minor edit
-        echo '<td class="' . $cls . '" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>'."\n";
-        
-        // Actions!
-        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'oldid=' . $r['log_id']) . '" onclick="ajaxHistView(\'' . $r['log_id'] . '\'); return false;">' . $lang->get('history_action_view') . '</a></td>'."\n";
-        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">' . $lang->get('history_action_contrib') . '</a></td>'."\n";
-        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'do=edit&amp;revid=' . $r['log_id']) . '" onclick="ajaxEditor(\'' . $r['log_id'] . '\'); return false;">' . $lang->get('history_action_restore') . '</a></td>'."\n";
-        
-        echo '</tr>'."\n"."\n";
-        
-      }
-      echo '</table>
-            </div>
-            <br />
-            <input type="hidden" name="do" value="diff" />
-            <input type="submit" value="' . $lang->get('history_btn_compare') . '" />
-            </form>
-            <script type="text/javascript">if ( !KILL_SWITCH ) { buildDiffList(); }</script>';
-    }
-    $db->free_result();
-    echo '<h3>' . $lang->get('history_heading_other') . '</h3>';
-    
-    $sql = 'SELECT log_id,action,time_id,date_string,page_id,namespace,author,author_uid,u.username,edit_summary,minor_edit FROM ' . table_prefix . "logs AS l\n"
-         . "  LEFT JOIN " . table_prefix . "users AS u\n"
-         . "    ON ( u.user_id = l.author_uid OR u.user_id IS NULL )\n"
-         . "  WHERE log_type='page' AND action!='edit' AND page_id='$page_id' AND namespace='$namespace' AND is_draft != 1 ORDER BY time_id DESC;";
-    
-    if ( !( $q = $db->sql_query($sql)) )
-    {
-      $db->_die('The history data for the page "' . htmlspecialchars($paths->cpage['name']) . '" could not be selected.');
-    }
-    if ( $db->numrows() < 1 )
-    {
-      echo $lang->get('history_no_entries');
-    }
-    else
-    {
-      
-      echo '<div class="tblholder">
-              <table border="0" width="100%" cellspacing="1" cellpadding="4"><tr>
-                <th>' . $lang->get('history_col_datetime') . '</th>
-                <th>' . $lang->get('history_col_user') . '</th>
-                <th>' . $lang->get('history_col_minor') . '</th>
-                <th>' . $lang->get('history_col_action_taken') . '</th>
-                <th>' . $lang->get('history_col_extra') . '</th>
-                <th colspan="2"></th>
-              </tr>';
-      $cls = 'row2';
-      while($r = $db->fetchrow($q)) {
-        
-        if($cls == 'row2') $cls = 'row1';
-        else $cls = 'row2';
-        
-        echo '<tr>';
-        
-        // Date and time
-        echo '<td class="' . $cls . '">' . enano_date(ED_DATE | ED_TIME, intval($r['time_id'])) . '</td class="' . $cls . '">';
-        
-        // User
-        $real_username = $r['author_uid'] > 1 && !empty($r['username']) ? $r['username'] : $r['author'];
-        $rank_info = $session->get_user_rank($r['author_uid']);
-        if ( $session->get_permissions('mod_misc') && is_valid_ip($r['author']) && $r['author_uid'] == 1 )
-        {
-          $rc = ' style="cursor: pointer;" title="' . $lang->get('history_tip_rdns') . '" onclick="ajaxReverseDNS(this, \'' . $r['author'] . '\');"';
-        }
-        else
-        {
-          $rc = '';
-        }
-        echo '<td class="' . $cls . '"' . $rc . '><a href="'.makeUrlNS('User', sanitize_page_id($real_username)).'" ';
-        if ( !isPage($paths->nslist['User'] . sanitize_page_id($real_username)) )
-        {
-          echo 'class="wikilink-nonexistent"';
-        }
-        echo 'style="' . $rank_info['rank_style'] . '">' . htmlspecialchars($real_username) . '</a></td class="' . $cls . '">'."\n";
-        
-        
-        // Minor edit
-        echo '<td class="' . $cls . '" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>';
-        
-        // Action taken
-        echo '<td class="' . $cls . '">';
-        // Some of these are sanitized at insert-time. Others follow the newer Enano policy of stripping HTML at runtime.
-        if    ($r['action']=='prot')     echo $lang->get('history_log_protect') . '</td><td class="' . $cls . '">'     . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__REVERSION__' ? $lang->get('history_extra_protection_reversion') : htmlspecialchars($r['edit_summary']) );
-        elseif($r['action']=='unprot')   echo $lang->get('history_log_unprotect') . '</td><td class="' . $cls . '">'   . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__REVERSION__' ? $lang->get('history_extra_protection_reversion') : htmlspecialchars($r['edit_summary']) );
-        elseif($r['action']=='semiprot') echo $lang->get('history_log_semiprotect') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__REVERSION__' ? $lang->get('history_extra_protection_reversion') : htmlspecialchars($r['edit_summary']) );
-        elseif($r['action']=='rename')   echo $lang->get('history_log_rename') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_oldtitle') . ' '.htmlspecialchars($r['edit_summary']);
-        elseif($r['action']=='create')   echo $lang->get('history_log_create') . '</td><td class="' . $cls . '">';
-        elseif($r['action']=='delete')   echo $lang->get('history_log_delete') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' ' . $r['edit_summary'];
-        elseif($r['action']=='reupload') echo $lang->get('history_log_uploadnew') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__ROLLBACK__' ? $lang->get('history_extra_upload_reversion') : htmlspecialchars($r['edit_summary']) );
-        elseif($r['action']=='votereset')echo $lang->get('history_log_votereset') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_numvotes') . ' ' . $r['edit_summary'];
-        echo '</td>';
-        
-        // Actions!
-        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">' . $lang->get('history_action_contrib') . '</a></td>';
-        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'do=rollback&amp;id=' . $r['log_id']) . '" onclick="ajaxRollback(\'' . $r['log_id'] . '\'); return false;">' . $lang->get('history_action_revert') . '</a></td>';
-        
-        echo '</tr>';
-      }
-      echo '</table></div>';
-    }
-    $db->free_result();
-    $ret = ob_get_contents();
-    ob_end_clean();
-    return $ret;
-  }
-  
-  /**
-   * Rolls back a logged action
-   * @param $id the time ID, a.k.a. the primary key in the logs table
-   * @return string
-   */
-   
-  public static function rollback($id)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    // placeholder
-    return 'PageUtils->rollback() is deprecated - use PageProcessor instead.';
-  }
-  
-  /**
-   * Posts a comment.
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $name the name of the person posting, defaults to current username/IP
-   * @param $subject the subject line of the comment
-   * @param $text the comment text
-   * @return string javascript code
-   */
-   
-  public static function addcomment($page_id, $namespace, $name, $subject, $text, $captcha_code = false, $captcha_id = false)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    $_ob = '';
-    if(!$session->get_permissions('post_comments'))
-      return 'Access denied';
-    if(getConfig('comments_need_login') == '2' && !$session->user_logged_in) _die('Access denied to post comments: you need to be logged in first.');
-    if(getConfig('comments_need_login') == '1' && !$session->user_logged_in)
-    {
-      if(!$captcha_code || !$captcha_id) _die('BUG: PageUtils::addcomment: no CAPTCHA data passed to method');
-      $result = $session->get_captcha($captcha_id);
-      if(strtolower($captcha_code) != strtolower($result)) _die('The confirmation code you entered was incorrect.');
-    }
-    $text = RenderMan::preprocess_text($text);
-    $name = $session->user_logged_in ? RenderMan::preprocess_text($session->username) : RenderMan::preprocess_text($name);
-    $subj = RenderMan::preprocess_text($subject);
-    if(getConfig('approve_comments', '0')=='1') $appr = '0'; else $appr = '1';
-    $q = 'INSERT INTO ' . table_prefix.'comments(page_id,namespace,subject,comment_data,name,user_id,approved,time) VALUES(\'' . $page_id . '\',\'' . $namespace . '\',\'' . $subj . '\',\'' . $text . '\',\'' . $name . '\',' . $session->user_id . ',' . $appr . ','.time().')';
-    $e = $db->sql_query($q);
-    if(!$e) die('alert(unescape(\''.rawurlencode('Error inserting comment data: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'))');
-    else $_ob .= '<div class="info-box">Your comment has been posted.</div>';
-    return PageUtils::comments($page_id, $namespace, false, Array(), $_ob);
-  }
-  
-  /**
-   * Generates partly-compiled HTML/Javascript code to be eval'ed by the user's browser to display comments
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $action administrative action to perform, default is false
-   * @param $flags additional info for $action, shouldn't be used except when deleting/approving comments, etc.
-   * @param $_ob text to prepend to output, used by PageUtils::addcomment
-   * @return array
-   * @access private
-   */
-   
-  public static function comments_raw($page_id, $namespace, $action = false, $flags = Array(), $_ob = '')
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    $pname = $paths->nslist[$namespace] . $page_id;
-    $template->init_vars();
-    
-    ob_start();
-    
-    if($action && $session->get_permissions('mod_comments')) // Nip hacking attempts in the bud
-    {
-      switch($action) {
-      case "delete":
-        if(isset($flags['id']))
-        {
-          $q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND comment_id='.intval($flags['id']).' LIMIT 1;';
-        } else {
-          $n = $db->escape($flags['name']);
-          $s = $db->escape($flags['subj']);
-          $t = $db->escape($flags['text']);
-          $q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\' LIMIT 1;';
-        }
-        $e=$db->sql_query($q);
-        if(!$e) die('alert(unesape(\''.rawurlencode('Error during query: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
-        break;
-      case "approve":
-        if(isset($flags['id']))
-        {
-          $where = 'comment_id='.intval($flags['id']);
-        } else {
-          $n = $db->escape($flags['name']);
-          $s = $db->escape($flags['subj']);
-          $t = $db->escape($flags['text']);
-          $where = 'name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\'';
-        }
-        $q = 'SELECT approved FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND ' . $where . ' LIMIT 1;';
-        $e = $db->sql_query($q);
-        if(!$e) die('alert(unesape(\''.rawurlencode('Error selecting approval status: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
-        $r = $db->fetchrow();
-        $db->free_result();
-        $a = ( $r['approved'] ) ? '0' : '1';
-        $q = 'UPDATE ' . table_prefix.'comments SET approved=' . $a . ' WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND ' . $where . ';';
-        $e=$db->sql_query($q);
-        if(!$e) die('alert(unesape(\''.rawurlencode('Error during query: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
-        if($a=='1') $v = $lang->get('comment_btn_mod_unapprove');
-        else $v = $lang->get('comment_btn_mod_approve');
-        echo 'document.getElementById("mdgApproveLink'.intval($_GET['id']).'").innerHTML="' . $v . '";';
-        break;
-      }
-    }
-    
-    if(!defined('ENANO_TEMPLATE_LOADED'))
-    {
-      $template->load_theme($session->theme, $session->style);
-    }
-    
-    $tpl = $template->makeParser('comment.tpl');
-    
-    $e = $db->sql_query('SELECT * FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND approved=0;');
-    if(!$e) $db->_die('The comment text data could not be selected.');
-    $num_unapp = $db->numrows();
-    $db->free_result();
-    $e = $db->sql_query('SELECT * FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND approved=1;');
-    if(!$e) $db->_die('The comment text data could not be selected.');
-    $num_app = $db->numrows();
-    $db->free_result();
-    $lq = $db->sql_query('SELECT c.comment_id,c.subject,c.name,c.comment_data,c.approved,c.time,c.user_id,c.ip_address,u.user_level,u.email,u.signature,u.user_has_avatar,u.avatar_type
-                  FROM ' . table_prefix.'comments AS c
-                  LEFT JOIN ' . table_prefix.'users AS u
-                    ON c.user_id=u.user_id
-                  WHERE page_id=\'' . $page_id . '\'
-                  AND namespace=\'' . $namespace . '\' ORDER BY c.time ASC;');
-    if(!$lq) _die('The comment text data could not be selected. '.$db->get_error());
-    $_ob .= '<h3>' . $lang->get('comment_heading') . '</h3>';
-    
-    $n = ( $session->get_permissions('mod_comments')) ? $db->numrows() : $num_app;
-    
-    $subst = array(
-        'num_comments' => $n,
-        'page_type' => $template->namespace_string
-      );
-    
-    $_ob .= '<p>';
-    $_ob .= ( $n == 0 ) ? $lang->get('comment_msg_count_zero', $subst) : ( $n == 1 ? $lang->get('comment_msg_count_one', $subst) : $lang->get('comment_msg_count_plural', $subst) );
-    
-    if ( $session->get_permissions('mod_comments') && $num_unapp > 0 )
-    {
-      $_ob .= ' <span style="color: #D84308">' . $lang->get('comment_msg_count_unapp_mod', array( 'num_unapp' => $num_unapp )) . '</span>';
-    }
-    else if ( !$session->get_permissions('mod_comments') && $num_unapp > 0 )
-    {
-      $ls = ( $num_unapp == 1 ) ? 'comment_msg_count_unapp_one' : 'comment_msg_count_unapp_plural';
-      $_ob .= ' <span>' . $lang->get($ls, array( 'num_unapp' => $num_unapp )) . '</span>';
-    }
-    $_ob .= '</p>';
-    $list = 'list = { ';
-    // _die(htmlspecialchars($ttext));
-    $i = -1;
-    while ( $row = $db->fetchrow($lq) )
-    {
-      $i++;
-      $strings = Array();
-      $bool = Array();
-      if ( $session->get_permissions('mod_comments') || $row['approved'] == COMMENT_APPROVED )
-      {
-        $list .= $i . ' : { \'comment\' : unescape(\''.rawurlencode($row['comment_data']).'\'), \'name\' : unescape(\''.rawurlencode($row['name']).'\'), \'subject\' : unescape(\''.rawurlencode($row['subject']).'\'), }, ';
-        
-        // Comment ID (used in the Javascript apps)
-        $strings['ID'] = (string)$i;
-        
-        // Determine the name, and whether to link to the user page or not
-        $name = '';
-        if($row['user_id'] > 1) $name .= '<a href="'.makeUrlNS('User', sanitize_page_id(' ', '_', $row['name'])).'">';
-        $name .= $row['name'];
-        if($row['user_id'] > 1) $name .= '</a>';
-        $strings['NAME'] = $name; unset($name);
-        
-        // Subject
-        $s = $row['subject'];
-        if(!$row['approved']) $s .= ' <span style="color: #D84308">' . $lang->get('comment_msg_note_unapp') . '</span>';
-        $strings['SUBJECT'] = $s;
-        
-        // Date and time
-        $strings['DATETIME'] = enano_date(ED_DATE | ED_TIME, $row['time']);
-        
-        // User level
-        switch($row['user_level'])
-        {
-          default:
-          case USER_LEVEL_GUEST:
-            $l = $lang->get('user_type_guest');
-            break;
-          case USER_LEVEL_MEMBER:
-          case USER_LEVEL_CHPREF:
-            $l = $lang->get('user_type_member');
-            break;
-          case USER_LEVEL_MOD:
-            $l = $lang->get('user_type_mod');
-            break;
-          case USER_LEVEL_ADMIN:
-            $l = $lang->get('user_type_admin');
-            break;
-        }
-        $strings['USER_LEVEL'] = $l; unset($l);
-        
-        // The actual comment data
-        $strings['DATA'] = RenderMan::render($row['comment_data']);
-        
-        if($session->get_permissions('edit_comments'))
-        {
-          // Edit link
-          $strings['EDIT_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=editcomment&amp;id=' . $row['comment_id']) . '" id="editbtn_' . $i . '">' . $lang->get('comment_btn_edit') . '</a>';
-        
-          // Delete link
-          $strings['DELETE_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=deletecomment&amp;id=' . $row['comment_id']) . '">' . $lang->get('comment_btn_delete') . '</a>';
-        }
-        else
-        {
-          // Edit link
-          $strings['EDIT_LINK'] = '';
-        
-          // Delete link
-          $strings['DELETE_LINK'] = '';
-        }
-        
-        // Send PM link
-        $strings['SEND_PM_LINK'] = ( $session->user_logged_in && $row['user_id'] > 1 ) ? '<a href="'.makeUrlNS('Special', 'PrivateMessages/Compose/To/' . $row['name']) . '">' . $lang->get('comment_btn_send_privmsg') . '</a><br />' : '';
-        
-        // Add Buddy link
-        $strings['ADD_BUDDY_LINK'] = ( $session->user_logged_in && $row['user_id'] > 1 ) ? '<a href="'.makeUrlNS('Special', 'PrivateMessages/FriendList/Add/' . $row['name']) . '">' . $lang->get('comment_btn_add_buddy') . '</a>' : '';
-        
-        // Mod links
-        $applink = '';
-        $applink .= '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=admin&amp;action=approve&amp;id=' . $row['comment_id']) . '" id="mdgApproveLink' . $i . '">';
-        if($row['approved']) $applink .= $lang->get('comment_btn_mod_unapprove');
-        else $applink .= $lang->get('comment_btn_mod_approve');
-        $applink .= '</a>';
-        $strings['MOD_APPROVE_LINK'] = $applink; unset($applink);
-        $strings['MOD_DELETE_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=admin&amp;action=delete&amp;id=' . $row['comment_id']) . '">' . $lang->get('comment_btn_mod_delete') . '</a>';
-        $strings['MOD_IP_LINK'] = '<span style="opacity: 0.5; filter: alpha(opacity=50);">' . ( ( empty($row['ip_address']) ) ? $lang->get('comment_btn_mod_ip_missing') : $lang->get('comment_btn_mod_ip_notimplemented') ) . '</span>';
-        
-        // Signature
-        $strings['SIGNATURE'] = '';
-        if($row['signature'] != '') $strings['SIGNATURE'] = RenderMan::render($row['signature']);
-        
-        // Avatar
-        if ( $row['user_has_avatar'] == 1 )
-        {
-          $bool['user_has_avatar'] = true;
-          $strings['AVATAR_ALT'] = $lang->get('usercp_avatar_image_alt', array('username' => $row['name']));
-          $strings['AVATAR_URL'] = make_avatar_url(intval($row['user_id']), $row['avatar_type'], $row['email']);
-          $strings['USERPAGE_LINK'] = makeUrlNS('User', $row['name']);
-        }
-        
-        $bool['auth_mod'] = ($session->get_permissions('mod_comments')) ? true : false;
-        $bool['can_edit'] = ( ( $session->user_logged_in && $row['name'] == $session->username && $session->get_permissions('edit_comments') ) || $session->get_permissions('mod_comments') ) ? true : false;
-        $bool['signature'] = ( $strings['SIGNATURE'] == '' ) ? false : true;
-        
-        // Done processing and compiling, now let's cook it into HTML
-        $tpl->assign_vars($strings);
-        $tpl->assign_bool($bool);
-        $_ob .= $tpl->run();
-      }
-    }
-    if(getConfig('comments_need_login') != '2' || $session->user_logged_in)
-    {
-      if($session->get_permissions('post_comments'))
-      {
-        $_ob .= '<h3>' . $lang->get('comment_postform_title') . '</h3>';
-        $_ob .= $lang->get('comment_postform_blurb');
-        if(getConfig('approve_comments', '0')=='1') $_ob .= ' ' . $lang->get('comment_postform_blurb_unapp');
-        if(getConfig('comments_need_login') == '1' && !$session->user_logged_in)
-        {
-          $_ob .= ' ' . $lang->get('comment_postform_blurb_captcha');
-        }
-        $sn = $session->user_logged_in ? $session->username . '<input name="name" id="mdgScreenName" type="hidden" value="' . $session->username . '" />' : '<input name="name" id="mdgScreenName" type="text" size="35" />';
-        $_ob .= '  <a href="#" id="mdgCommentFormLink" style="display: none;" onclick="document.getElementById(\'mdgCommentForm\').style.display=\'block\';this.style.display=\'none\';return false;">' . $lang->get('comment_postform_blurb_link') . '</a>
-        <div id="mdgCommentForm">
-        <form action="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=postcomment').'" method="post" style="margin-left: 1em">
-        <table border="0">
-        <tr><td>' . $lang->get('comment_postform_field_name') . '</td><td>' . $sn . '</td></tr>
-        <tr><td>' . $lang->get('comment_postform_field_subject') . '</td><td><input name="subj" id="mdgSubject" type="text" size="35" /></td></tr>';
-        if(getConfig('comments_need_login') == '1' && !$session->user_logged_in)
-        {
-          $session->kill_captcha();
-          $captcha = $session->make_captcha();
-          $_ob .= '<tr><td>' . $lang->get('comment_postform_field_captcha_title') . '<br /><small>' . $lang->get('comment_postform_field_captcha_blurb') . '</small></td><td><img src="'.makeUrlNS('Special', 'Captcha/' . $captcha) . '" alt="Visual confirmation" style="cursor: pointer;" onclick="this.src = \''.makeUrlNS("Special", "Captcha/".$captcha).'/\'+Math.floor(Math.random() * 100000);" /><input name="captcha_id" id="mdgCaptchaID" type="hidden" value="' . $captcha . '" /><br />' . $lang->get('comment_postform_field_captcha_label') . ' <input name="captcha_input" id="mdgCaptchaInput" type="text" size="10" /><br /><small><script type="text/javascript">document.write("' . $lang->get('comment_postform_field_captcha_cantread_js') . '");</script><noscript>' . $lang->get('comment_postform_field_captcha_cantread_nojs') . '</noscript></small></td></tr>';
-        }
-        $_ob .= '
-        <tr><td valign="top">' . $lang->get('comment_postform_field_comment') . '</td><td><textarea name="text" id="mdgCommentArea" rows="10" cols="40"></textarea></td></tr>
-        <tr><td colspan="2" style="text-align: center;"><input type="submit" value="' . $lang->get('comment_postform_btn_submit') . '" /></td></tr>
-        </table>
-        </form>
-        </div>';
-      }
-    } else {
-      // FIXME: l10n
-      $_ob .= '<h3>' . $lang->get('comment_postform_title') . '</h3><p>You need to be logged in to post comments. <a href="'.makeUrlNS('Special', 'Login/' . $pname . '%2523comments').'">Log in</a></p>';
-    }
-    $list .= '};';
-    echo 'document.getElementById(\'ajaxEditContainer\').innerHTML = unescape(\''. rawurlencode($_ob) .'\');
-    ' . $list;
-    echo 'Fat.fade_all(); document.getElementById(\'mdgCommentForm\').style.display = \'none\'; document.getElementById(\'mdgCommentFormLink\').style.display="inline";';
-    
-    $ret = ob_get_contents();
-    ob_end_clean();
-    return Array($ret, $_ob);
-    
-  }
-  
-  /**
-   * Generates ready-to-execute Javascript code to be eval'ed by the user's browser to display comments
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $action administrative action to perform, default is false
-   * @param $flags additional info for $action, shouldn't be used except when deleting/approving comments, etc.
-   * @param $_ob text to prepend to output, used by PageUtils::addcomment
-   * @return string
-   */
-   
-  public static function comments($page_id, $namespace, $action = false, $id = -1, $_ob = '')
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    $r = PageUtils::comments_raw($page_id, $namespace, $action, $id, $_ob);
-    return $r[0];
-  }
-  
-  /**
-   * Generates HTML code for comments - used in browser compatibility mode
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $action administrative action to perform, default is false
-   * @param $flags additional info for $action, shouldn't be used except when deleting/approving comments, etc.
-   * @param $_ob text to prepend to output, used by PageUtils::addcomment
-   * @return string
-   */
-  
-  public static function comments_html($page_id, $namespace, $action = false, $id = -1, $_ob = '')
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    $r = PageUtils::comments_raw($page_id, $namespace, $action, $id, $_ob);
-    return $r[1];
-  }
-  
-  /**
-   * Updates comment data.
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $subject new subject
-   * @param $text new text
-   * @param $old_subject the old subject, unprocessed and identical to the value in the DB
-   * @param $old_text the old text, unprocessed and identical to the value in the DB
-   * @param $id the javascript list ID, used internally by the client-side app
-   * @return string
-   */
-  
-  public static function savecomment($page_id, $namespace, $subject, $text, $old_subject, $old_text, $id = -1)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!$session->get_permissions('edit_comments'))
-      return 'result="BAD";error="Access denied"';
-    // Avoid SQL injection
-    $old_text    = $db->escape($old_text);
-    $old_subject = $db->escape($old_subject);
-    // Safety check - username/login
-    if(!$session->get_permissions('mod_comments')) // allow mods to edit comments
-    {
-      if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
-      $q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_data=\'' . $old_text . '\' AND subject=\'' . $old_subject . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
-      $s = $db->sql_query($q);
-      if(!$s) _die('SQL error during safety check: '.$db->get_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
-      $r = $db->fetchrow($s);
-      $db->free_result();
-      if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
-    }
-    $s = RenderMan::preprocess_text($subject);
-    $t = RenderMan::preprocess_text($text);
-    $sql  = 'UPDATE ' . table_prefix.'comments SET subject=\'' . $s . '\',comment_data=\'' . $t . '\' WHERE comment_data=\'' . $old_text . '\' AND subject=\'' . $old_subject . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
-    $result = $db->sql_query($sql);
-    if($result)
-    {
-      return 'result="GOOD";
-                      list[' . $id . '][\'subject\'] = unescape(\''.str_replace('%5Cn', '%0A', rawurlencode(str_replace('{{EnAnO:Newline}}', '\\n', stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $s))))).'\');
-                      list[' . $id . '][\'comment\'] = unescape(\''.str_replace('%5Cn', '%0A', rawurlencode(str_replace('{{EnAnO:Newline}}', '\\n', stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $t))))).'\'); id = ' . $id . ';
-      s = unescape(\''.rawurlencode($s).'\');
-      t = unescape(\''.str_replace('%5Cn', '<br \\/>', rawurlencode(RenderMan::render(str_replace('{{EnAnO:Newline}}', "\n", stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $t)))))).'\');';
-    }
-    else
-    {
-      return 'result="BAD"; error=unescape("'.rawurlencode('Enano encountered a problem whilst saving the comment.
-      Performed SQL:
-      ' . $sql . '
-    
-      Error returned by MySQL: '.$db->get_error()).'");';
-    }
-  }
-  
-  /**
-   * Updates comment data using the comment_id column instead of the old, messy way
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $subject new subject
-   * @param $text new text
-   * @param $id the comment ID (primary key in enano_comments table)
-   * @return string
-   */
-  
-  public static function savecomment_neater($page_id, $namespace, $subject, $text, $id)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!is_int($id)) die('PageUtils::savecomment: $id is not an integer, aborting for safety');
-    if(!$session->get_permissions('edit_comments'))
-      return 'Access denied';
-    // Safety check - username/login
-    if(!$session->get_permissions('mod_comments')) // allow mods to edit comments
-    {
-      if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
-      $q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
-      $s = $db->sql_query($q);
-      if(!$s) _die('SQL error during safety check: '.$db->get_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
-      $r = $db->fetchrow($s);
-      if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
-      $db->free_result();
-    }
-    $s = RenderMan::preprocess_text($subject);
-    $t = RenderMan::preprocess_text($text);
-    $sql  = 'UPDATE ' . table_prefix.'comments SET subject=\'' . $s . '\',comment_data=\'' . $t . '\' WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
-    $result = $db->sql_query($sql);
-    if($result)
-    return 'good';
-    else return 'Enano encountered a problem whilst saving the comment.
-    Performed SQL:
-    ' . $sql . '
-    
-    Error returned by MySQL: '.$db->get_error();
-  }
-  
-  /**
-   * Deletes a comment.
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $name the name the user posted under
-   * @param $subj the subject of the comment to be deleted
-   * @param $text the text of the comment to be deleted
-   * @param $id the javascript list ID, used internally by the client-side app
-   * @return string
-   */
-  
-  public static function deletecomment($page_id, $namespace, $name, $subj, $text, $id)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    
-    if(!$session->get_permissions('edit_comments'))
-      return 'alert("Access to delete/edit comments is denied");';
-    
-    if(!preg_match('#^([0-9]+)$#', (string)$id)) die('$_GET[id] is improperly formed.');
-    $n = $db->escape($name);
-    $s = $db->escape($subj);
-    $t = $db->escape($text);
-    
-    // Safety check - username/login
-    if(!$session->get_permissions('mod_comments')) // allows mods to delete comments
-    {
-      if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
-      $q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_data=\'' . $t . '\' AND subject=\'' . $s . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
-      $s = $db->sql_query($q);
-      if(!$s) _die('SQL error during safety check: '.$db->get_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
-      $r = $db->fetchrow($s);
-      if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
-      $db->free_result();
-    }
-    $q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\' LIMIT 1;';
-    $e=$db->sql_query($q);
-    if(!$e) return('alert(unesape(\''.rawurlencode('Error during query: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
-    return('good');
-  }
-  
-  /**
-   * Deletes a comment in a cleaner fashion.
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $id the comment ID (primary key)
-   * @return string
-   */
-  
-  public static function deletecomment_neater($page_id, $namespace, $id)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    
-    if(!preg_match('#^([0-9]+)$#', (string)$id)) die('$_GET[id] is improperly formed.');
-    
-    if(!$session->get_permissions('edit_comments'))
-      return 'alert("Access to delete/edit comments is denied");';
-    
-    // Safety check - username/login
-    if(!$session->get_permissions('mod_comments')) // allows mods to delete comments
-    {
-      if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
-      $q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
-      $s = $db->sql_query($q);
-      if(!$s) _die('SQL error during safety check: '.$db->get_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
-      $r = $db->fetchrow($s);
-      if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
-      $db->free_result();
-    }
-    $q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND comment_id=' . $id . ' LIMIT 1;';
-    $e=$db->sql_query($q);
-    if(!$e) return('alert(unesape(\''.rawurlencode('Error during query: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
-    return('good');
-  }
-  
-  /**
-   * Renames a page.
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $name the new name for the page
-   * @return string error string or success message
-   */
-   
-  public static function rename($page_id, $namespace, $name)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    $page = new PageProcessor($page_id, $namespace);
-    return $page->rename_page($name);
-  }
-  
-  /**
-   * Flushes (clears) the action logs for a given page
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @return string error/success string
-   */
-   
-  public static function flushlogs($page_id, $namespace)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    if ( !is_object($lang) && defined('IN_ENANO_INSTALL') )
-    {
-      // This is a special exception for the Enano installer, which doesn't init languages yet.
-      $lang = new Language('eng');
-    }
-    if(!$session->get_permissions('clear_logs') && !defined('IN_ENANO_INSTALL'))
-    {
-      return $lang->get('etc_access_denied');
-    }
-    if ( !$session->sid_super )
-    {
-      return $lang->get('etc_access_denied_need_reauth');
-    }
-    
-    $page_id_db = $db->escape($page_id);
-    $namespace_db = $db->escape($namespace);
-    
-    // If we're flushing a file, also clear all revisions before the current
-    if ( $namespace == 'File' )
-    {
-      $q = $db->sql_query('SELECT file_id FROM ' . table_prefix . "files WHERE page_id='$page_id_db' ORDER BY time_id DESC;");
-      if ( !$q )
-        $db->_die();
-      // discard first row (current revision)
-      $db->fetchrow();
-      $id_list = array();
-      while ( $row = $db->fetchrow() )
-        $id_list[] = $row['file_id'];
-      
-      require_once(ENANO_ROOT . '/includes/namespaces/file.php');
-      
-      // clear out each file
-      foreach ( $id_list as $id )
-        Namespace_File::delete_file($id);
-    }
-    
-    $q = $db->sql_query('DELETE FROM ' . table_prefix . "logs WHERE page_id='$page_id_db' AND namespace='$namespace';");
-    if ( !$q )
-      $db->_die('The log entries could not be deleted.');
-    
-    // If the page exists, make a backup of it in case it gets spammed/vandalized
-    // If not, the admin's probably deleting a trash page
-    if ( isPage($paths->get_pathskey($page_id, $namespace)) )
-    {
-      $q = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix . "page_text WHERE page_id='$page_id_db' AND namespace='$namespace_db';");
-      if ( !$q )
-        $db->_die('The current page text could not be selected; as a result, creating the backup of the page failed. Please make a backup copy of the page by clicking Edit this page and then clicking Save Changes.');
-      $row = $db->fetchrow();
-      $db->free_result();
-      $minor_edit = ( ENANO_DBLAYER == 'MYSQL' ) ? 'false' : '0';
-      $username = $db->escape($session->username);
-      $q = 'INSERT INTO ' . table_prefix . "logs ( log_type, action, time_id, date_string, page_id, namespace, page_text, char_tag, author, author_uid, edit_summary, minor_edit ) VALUES\n"
-         . "  ('page', 'edit', " . time() . ", 'DEPRECATED', '$page_id', '$namespace', '" . $db->escape($row['page_text']) . "', '', '{$username}', $session->user_id, '" . $lang->get('page_flushlogs_backup_summary') . "', $minor_edit);";
-      if ( !$db->sql_query($q) )
-        $db->_die('The history (log) entry could not be inserted into the logs table.');
-    }
-    
-    return $lang->get('ajax_clearlogs_success');
-  }
-  
-  /**
-   * Deletes a page.
-   * @param string $page_id the condemned page ID
-   * @param string $namespace the condemned namespace
-   * @param string The reason for deleting the page in question
-   * @return string
-   */
-   
-  public static function deletepage($page_id, $namespace, $reason)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    global $cache;
-    $perms = $session->fetch_page_acl($page_id, $namespace);
-    $x = trim($reason);
-    if ( empty($x) )
-    {
-      return $lang->get('ajax_delete_need_reason');
-    }
-    if(!$perms->get_permissions('delete_page')) return('Administrative privileges are required to delete pages, you loser.');
-    
-    if ( !$session->sid_super )
-    {
-      return $lang->get('etc_access_denied_need_reauth');
-    }
-    
-    $e = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,author_uid,edit_summary) VALUES('.time().', \''.enano_date(ED_DATE | ED_TIME).'\', \'page\', \'delete\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $session->username . '\', ' . $session->user_id . ', \'' . $db->escape(htmlspecialchars($reason)) . '\')');
-    if(!$e) $db->_die('The page log entry could not be inserted.');
-    $e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
-    if(!$e) $db->_die('The page categorization entries could not be deleted.');
-    $e = $db->sql_query('DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
-    if(!$e) $db->_die('The page comments could not be deleted.');
-    $e = $db->sql_query('DELETE FROM ' . table_prefix.'page_text WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
-    if(!$e) $db->_die('The page text entry could not be deleted.');
-    $e = $db->sql_query('DELETE FROM ' . table_prefix.'pages WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
-    if(!$e) $db->_die('The page entry could not be deleted.');
-    if ( $namespace == 'File' )
-    {
-      $e = $db->sql_query('DELETE FROM ' . table_prefix.'files WHERE page_id=\'' . $page_id . '\'');
-      if(!$e) $db->_die('The file entry could not be deleted.');
-    }
-    $cache->purge('page_meta');
-    return $lang->get('ajax_delete_success');
-  }
-  
-  /**
-   * Deletes files associated with a File page.
-   * @param string Page ID
-   */
-  
-  public static function delete_page_files($page_id)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    
-    $q = $db->sql_query('SELECT file_id, filename, file_key, time_id, file_extension FROM ' . table_prefix . "files WHERE page_id = '{$db->escape($page_id)}';");
-    if ( !$q )
-      $db->_die();
-    
-    while ( $row = $db->fetchrow() )
-    {
-      // wipe original file
-      foreach ( array(
-          ENANO_ROOT . "/files/{$row['file_key']}_{$row['time_id']}{$row['file_extension']}",
-          ENANO_ROOT . "/files/{$row['file_key']}{$row['file_extension']}"
-        ) as $orig_file )
-      {
-        if ( file_exists($orig_file) )
-          @unlink($orig_file);
-      }
-      
-      // wipe cached files
-      if ( $dr = @opendir(ENANO_ROOT . '/cache/') )
-      {
-        // lol404.jpg-1217958283-200x320.jpg
-        while ( $dh = @readdir($dr) )
-        {
-          $regexp = ':^' . preg_quote("{$row['filename']}-{$row['time_id']}-") . '[0-9]+x[0-9]+\.' . ltrim($row['file_extension'], '.') . '$:';
-          if ( preg_match($regexp, $dh) )
-          {
-            @unlink(ENANO_ROOT . "/cache/$dh");
-          }
-        }
-        @closedir($dr);
-      }
-    }
-    
-    $q = $db->sql_query('DELETE FROM ' . table_prefix . "files WHERE page_id = '{$db->escape($page_id)}';");
-    if ( !$q )
-      $db->die();
-    
-    return true;
-  }
-  
-  /**
-   * Increments the deletion votes for a page by 1, and adds the current username/IP to the list of users that have voted for the page to prevent dual-voting
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @return string
-   */
-   
-  public static function delvote($page_id, $namespace)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    global $cache;
-    
-    if ( !$session->get_permissions('vote_delete') )
-    {
-      return $lang->get('etc_access_denied');
-    }
-    
-    if ( $namespace == 'Admin' || $namespace == 'Special' || $namespace == 'System' )
-    {
-      return 'Special pages and system messages can\'t be voted for deletion.';
-    }
-    
-    $pname = $paths->nslist[$namespace] . sanitize_page_id($page_id);
-    
-    if ( !isPage($pname) )
-    {
-      return 'The page does not exist.';
-    }
-    
-    $ns = namespace_factory($page_id, $namespace);
-    $cdata = $ns->get_cdata();
-    
-    $cv  =& $cdata['delvotes'];
-    $ips =& $cdata['delvote_ips'];
-    
-    if ( empty($ips) )
-    {
-      $ips = array(
-        'ip' => array(),
-        'u' => array()
-        );
-    }
-    else
-    {
-      $ips = @unserialize($ips);
-      if ( !$ips )
-      {
-        $ips = array(
-        'ip' => array(),
-        'u' => array()
-        );
-      }
-    }
-    
-    if ( in_array($session->username, $ips['u']) || in_array($_SERVER['REMOTE_ADDR'], $ips['ip']) )
-    {
-      return $lang->get('ajax_delvote_already_voted');
-    }
-    
-    $ips['u'][] = $session->username;
-    $ips['ip'][] = $_SERVER['REMOTE_ADDR'];
-    $ips = $db->escape( serialize($ips) );
-    
-    $cv++;
-    
-    $q = 'UPDATE ' . table_prefix.'pages SET delvotes=' . $cv . ',delvote_ips=\'' . $ips . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
-    $w = $db->sql_query($q);
-    if ( !$w )
-      $db->_die();
-    
-    // all done, flush page cache to mark it up
-    $cache->purge('page_meta');
-    
-    return $lang->get('ajax_delvote_success');
-  }
-  
-  /**
-   * Resets the number of votes against a page to 0.
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @return string
-   */
-  
-  public static function resetdelvotes($page_id, $namespace)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    global $cache;
-    
-    if ( !$session->get_permissions('vote_reset') )
-    {
-      return $lang->get('etc_access_denied');
-    }
-    
-    $page_id = $db->escape($page_id);
-    $namespace = $db->escape($namespace);
-    
-    // pull existing info
-    $q = $db->sql_query('SELECT delvotes, delvote_ips FROM ' . table_prefix . "pages WHERE urlname = '$page_id' AND namespace = '$namespace'");
-    if ( !$q )
-      $db->_die();
-    if ( $db->numrows() < 1 )
-      return $lang->get('page_err_page_not_exist');
-    
-    list($delvotes, $delvote_ips) = $db->fetchrow_num();
-    $db->free_result();
-    $delvote_ips = $db->escape($delvote_ips);
-    $username = $db->escape($session->username);
-    
-    // log action
-    $time = time();
-    $q = $db->sql_query('INSERT INTO ' . table_prefix . "logs (time_id, log_type, action, edit_summary, page_text, author, author_uid, page_id, namespace) VALUES\n"
-                      . "  ( $time, 'page', 'votereset', '$delvotes', '$delvote_ips', '$username', $session->user_id, '$page_id', '$namespace' )");
-    if ( !$q )
-      $db->_die();
-    
-    // reset votes
-    $empty_vote_record = $db->escape(serialize(array('ip'=>array(),'u'=>array())));
-    $q = 'UPDATE ' . table_prefix.'pages SET delvotes=0,delvote_ips=\'' . $empty_vote_record . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
-    $e = $db->sql_query($q);
-    if ( !$e )
-    {
-      $db->_die('The number of delete votes was not reset.');
-    }
-    else
-    {
-      $cache->purge('page_meta');
-      return $lang->get('ajax_delvote_reset_success');
-    }
-  }
-  
-  /**
-   * Gets a list of styles for a given theme name. As of Banshee, this returns JSON.
-   * @param $id the name of the directory for the theme
-   * @return string JSON string with an array containing a list of themes
-   */
-   
-  public static function getstyles()
-  {
-    
-    if ( !preg_match('/^([a-z0-9_-]+)$/', $_GET['id']) )
-      return enano_json_encode(false);
-    
-    $dir = './themes/' . $_GET['id'] . '/css/';
-    $list = Array();
-    // Open a known directory, and proceed to read its contents
-    if (is_dir($dir)) {
-      if ($dh = opendir($dir)) {
-        while (($file = readdir($dh)) !== false) {
-          if ( preg_match('#^(.*?)\.css$#is', $file) && $file != '_printable.css' ) // _printable.css should be included with every theme
-          {                                                                         // it should be a copy of the original style, but
-                                                                                    // mostly black and white
-                                                                                    // Note to self: document this
-            $list[] = substr($file, 0, strlen($file)-4);
-          }
-        }
-        closedir($dh);
-      }
-    }
-    else
-    {
-      return(enano_json_encode(Array('mode' => 'error', 'error' => $dir.' is not a dir')));
-    }
-    
-    return enano_json_encode($list);
-  }
-  
-  /**
-   * Assembles a Javascript app with category information
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @return string Javascript code
-   */
-   
-  public static function catedit($page_id, $namespace)
-  {
-    $d = PageUtils::catedit_raw($page_id, $namespace);
-    return $d[0] . ' /* BEGIN CONTENT */ document.getElementById("ajaxEditContainer").innerHTML = unescape(\''.rawurlencode($d[1]).'\');';
-  }
-  
-  /**
-   * Does the actual HTML/javascript generation for cat editing, but returns an array
-   * @access private
-   */
-   
-  public static function catedit_raw($page_id, $namespace)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    ob_start();
-    $_ob = '';
-    $e = $db->sql_query('SELECT category_id FROM ' . table_prefix.'categories WHERE page_id=\'' . $paths->page_id . '\' AND namespace=\'' . $paths->namespace . '\'');
-    if(!$e) jsdie('Error selecting category information for current page: '.$db->get_error());
-    $cat_current = Array();
-    while($r = $db->fetchrow())
-    {
-      $cat_current[] = $r;
-    }
-    $db->free_result();
-    
-    $cat_all = array();
-    $q = $db->sql_query('SELECT * FROM ' . table_prefix . 'pages WHERE namespace = \'Category\';');
-    if ( !$q )
-      $db->_die();
-    
-    while ( $row = $db->fetchrow() )
-    {
-      $cat_all[] = Namespace_Default::bake_cdata($row);
-    }
-    
-    // Make $cat_all an associative array, like $paths->pages
-    $sz = sizeof($cat_all);
-    for($i=0;$i<$sz;$i++)
-    {
-      $cat_all[$cat_all[$i]['urlname_nons']] = $cat_all[$i];
-    }
-    // Now, the "zipper" function - join the list of categories with the list of cats that this page is a part of
-    $cat_info = $cat_all;
-    for($i=0;$i<sizeof($cat_current);$i++)
-    {
-      $un = $cat_current[$i]['category_id'];
-      $cat_info[$un]['member'] = true;
-    }
-    // Now copy the information we just set into the numerically named keys
-    for($i=0;$i<sizeof($cat_info)/2;$i++)
-    {
-      $un = $cat_info[$i]['urlname_nons'];
-      $cat_info[$i] = $cat_info[$un];
-    }
-    
-    echo 'catlist = new Array();'; // Initialize the client-side category list
-    $_ob .= '<h3>' . $lang->get('catedit_title') . '</h3>
-             <form name="mdgCatForm" action="'.makeUrlNS($namespace, $page_id, 'do=catedit').'" method="post">';
-    if ( sizeof($cat_info) < 1 )
-    {
-      $_ob .= '<p>' . $lang->get('catedit_no_categories') . '</p>';
-    }
-    for ( $i = 0; $i < sizeof($cat_info) / 2; $i++ )
-    {
-      // Protection code added 1/3/07
-      // Updated 3/4/07
-      $is_prot = false;
-      $perms = $session->fetch_page_acl($cat_info[$i]['urlname_nons'], 'Category');
-      if ( !$session->get_permissions('edit_cat') || !$perms->get_permissions('edit_cat') ||
-         ( $cat_info[$i]['really_protected'] && !$perms->get_permissions('even_when_protected') ) )
-         $is_prot = true;
-      $prot = ( $is_prot ) ? ' disabled="disabled" ' : '';
-      $prottext = ( $is_prot ) ? ' <img alt="(protected)" width="16" height="16" src="'.scriptPath.'/images/lock16.png" />' : '';
-      echo 'catlist[' . $i . '] = \'' . $cat_info[$i]['urlname_nons'] . '\';';
-      $_ob .= '<span class="catCheck"><input ' . $prot . ' name="' . $cat_info[$i]['urlname_nons'] . '" id="mdgCat_' . $cat_info[$i]['urlname_nons'] . '" type="checkbox"';
-      if(isset($cat_info[$i]['member'])) $_ob .= ' checked="checked"';
-      $_ob .= '/>  <label for="mdgCat_' . $cat_info[$i]['urlname_nons'] . '">' . $cat_info[$i]['name'].$prottext.'</label></span><br />';
-    }
-    
-    $disabled = ( sizeof($cat_info) < 1 ) ? 'disabled="disabled"' : '';
-      
-    $_ob .= '<div style="border-top: 1px solid #CCC; padding-top: 5px; margin-top: 10px;"><input name="__enanoSaveButton" ' . $disabled . ' style="font-weight: bold;" type="submit" onclick="ajaxCatSave(); return false;" value="' . $lang->get('etc_save_changes') . '" /> <input name="__enanoCatCancel" type="submit" onclick="ajaxReset(); return false;" value="' . $lang->get('etc_cancel') . '" /></div></form>';
-    
-    $cont = ob_get_contents();
-    ob_end_clean();
-    return Array($cont, $_ob);
-  }
-  
-  /**
-   * Saves category information
-   * WARNING: If $which_cats is empty, all the category information for the selected page will be nuked!
-   * @param $page_id string the page ID
-   * @param $namespace string the namespace
-   * @param $which_cats array associative array of categories to put the page in
-   * @return string "GOOD" on success, error string on failure
-   */
-  
-  public static function catsave($page_id, $namespace, $which_cats)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    if(!$session->get_permissions('edit_cat')) return('Insufficient privileges to change category information');
-    
-    $page_perms = $session->fetch_page_acl($page_id, $namespace);
-    $ns = namespace_factory($page_id, $namespace);
-    $page_data = $ns->get_cdata();
-    
-    $cat_all = array();
-    $q = $db->sql_query('SELECT * FROM ' . table_prefix . 'pages WHERE namespace = \'Category\';');
-    if ( !$q )
-      $db->_die();
-    
-    while ( $row = $db->fetchrow() )
-    {
-      $cat_all[] = Namespace_Default::bake_cdata($row);
-    }
-    
-    // Make $cat_all an associative array, like $paths->pages
-    $sz = sizeof($cat_all);
-    for($i=0;$i<$sz;$i++)
-    {
-      $cat_all[$cat_all[$i]['urlname_nons']] = $cat_all[$i];
-    }
-    
-    $rowlist = Array();
-    
-    for($i=0;$i<sizeof($cat_all)/2;$i++)
-    {
-      $auth = true;
-      $perms = $session->fetch_page_acl($cat_all[$i]['urlname_nons'], 'Category');
-      if ( !$session->get_permissions('edit_cat') || !$perms->get_permissions('edit_cat') ||
-         ( $cat_all[$i]['really_protected'] && !$perms->get_permissions('even_when_protected') ) ||
-         ( !$page_perms->get_permissions('even_when_protected') && $page_data['protected'] == '1' ) )
-         $auth = false;
-      if(!$auth)
-      {
-        // Find out if the page is currently in the category
-        $q = $db->sql_query('SELECT * FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
-        if(!$q)
-          return 'MySQL error: ' . $db->get_error();
-        if($db->numrows() > 0)
-        {
-          $auth = true;
-          $which_cats[$cat_all[$i]['urlname_nons']] = true; // Force the category to stay in its current state
-        }
-        $db->free_result();
-      }
-      if(isset($which_cats[$cat_all[$i]['urlname_nons']]) && $which_cats[$cat_all[$i]['urlname_nons']] == true /* for clarity ;-) */ && $auth ) $rowlist[] = '(\'' . $page_id . '\', \'' . $namespace . '\', \'' . $cat_all[$i]['urlname_nons'] . '\')';
-    }
-    if(sizeof($rowlist) > 0)
-    {
-      $val = implode(',', $rowlist);
-      $q = 'INSERT INTO ' . table_prefix.'categories(page_id,namespace,category_id) VALUES' . $val . ';';
-      $e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
-      if(!$e) $db->_die('The old category data could not be deleted.');
-      $e = $db->sql_query($q);
-      if(!$e) $db->_die('The new category data could not be inserted.');
-      return('GOOD');
-    }
-    else
-    {
-      $e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
-      if(!$e) $db->_die('The old category data could not be deleted.');
-      return('GOOD');
-    }
-  }
-  
-  /**
-   * Sets the wiki mode level for a page.
-   * @param $page_id string the page ID
-   * @param $namespace string the namespace
-   * @param $level int 0 for off, 1 for on, 2 for use global setting
-   * @return string "GOOD" on success, error string on failure
-   */
-  
-  public static function setwikimode($page_id, $namespace, $level)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $cache;
-    
-    if(!$session->get_permissions('set_wiki_mode')) return('Insufficient access rights');
-    if ( !isset($level) || ( isset($level) && !preg_match('#^([0-2]){1}$#', (string)$level) ) )
-    {
-      return('Invalid mode string');
-    }
-    $q = $db->sql_query('UPDATE ' . table_prefix.'pages SET wiki_mode=' . $level . ' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
-    if ( !$q )
-    {
-      return('Error during update query: '.$db->get_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
-    }
-    
-    $cache->purge('page_meta');
-    return('GOOD');
-  }
-  
-  /**
-   * Sets the access password for a page.
-   * @param $page_id string the page ID
-   * @param $namespace string the namespace
-   * @param $pass string the SHA1 hash of the password - if the password doesn't match the regex ^([0-9a-f]*){40,40}$ it will be sha1'ed
-   * @return string
-   */
-  
-  public static function setpass($page_id, $namespace, $pass)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang, $cache;
-    // Determine permissions
-    $ns = namespace_factory($page_id, $namespace);
-    $cdata = $ns->get_cdata();
-    if ( $cdata['password'] != '' )
-      $a = $session->get_permissions('password_reset');
-    else
-      $a = $session->get_permissions('password_set');
-    if ( !$a )
-      return $lang->get('etc_access_denied');
-    if ( !isset($pass) )
-      return('Password was not set on URL');
-    $p = $pass;
-    if ( !preg_match('#([0-9a-f]){40,40}#', $p) )
-    {
-      $p = sha1($p);
-    }
-    if ( $p == 'da39a3ee5e6b4b0d3255bfef95601890afd80709' )
-      // sha1('') = da39a3ee5e6b4b0d3255bfef95601890afd80709
-      $p = '';
-    $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET password=\'' . $p . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
-    if ( !$e )
-    {
-      die('PageUtils::setpass(): Error during update query: '.$db->get_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
-    }
-    // Is the new password blank?
-    if ( $p == '' )
-    {
-      return $lang->get('ajax_password_disable_success');
-    }
-    else
-    {
-      return $lang->get('ajax_password_success');
-    }
-  }
-  
-  /**
-   * Generates some preview HTML
-   * @param $text string the wikitext to use
-   * @return string
-   */
-   
-  public static function genPreview($text)
-  {
-    global $lang;
-    $ret = '<div class="info-box">' . $lang->get('editor_preview_blurb') . '</div><div style="background-color: #F8F8F8; padding: 10px; border: 1px dashed #406080; max-height: 250px; overflow: auto; margin: 10px 0;">';
-    $text = RenderMan::render(RenderMan::preprocess_text($text, false, false));
-    ob_start();
-    eval('?>' . $text);
-    $text = ob_get_contents();
-    ob_end_clean();
-    $ret .= $text;
-    $ret .= '</div>';
-    return $ret;
-  }
-  
-  /**
-   * Makes a scrollable box
-   * @param string $text the inner HTML
-   * @param int $height Optional - the maximum height. Defaults to 250.
-   * @return string
-   */
-   
-  public static function scrollBox($text, $height = 250)
-  {
-    return '<div style="background-color: #F8F8F8; padding: 10px; border: 1px dashed #406080; max-height: '.(string)intval($height).'px; overflow: auto; margin: 1em 0 1em 1em;">' . $text . '</div>';
-  }
-  
-  /**
-   * Generates a diff summary between two page revisions.
-   * @param $page_id the page ID
-   * @param $namespace the namespace
-   * @param $id1 the time ID of the first revision
-   * @param $id2 the time ID of the second revision
-   * @return string XHTML-formatted diff
-   */
-   
-  public static function pagediff($page_id, $namespace, $id1, $id2)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    if ( !$session->get_permissions('history_view') )
-      return $lang->get('etc_access_denied');
-    
-    if(!preg_match('#^([0-9]+)$#', (string)$id1) ||
-       !preg_match('#^([0-9]+)$#', (string)$id2  )) return 'SQL injection attempt';
-    // OK we made it through security
-    // Safest way to make sure we don't end up with the revisions in wrong columns is to make 2 queries
-    if ( !$q1 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE log_id = ' . $id1 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error();
-    if ( !$q2 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE log_id = ' . $id2 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error();
-    $row1 = $db->fetchrow($q1);
-    $db->free_result($q1);
-    $row2 = $db->fetchrow($q2);
-    $db->free_result($q2);
-    if(sizeof($row1) < 1 || sizeof($row2) < 2)
-    {
-      if ( !$q1 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id = ' . $id1 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error();
-      if ( !$q2 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id = ' . $id2 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error();
-      $row1 = $db->fetchrow($q1);
-      $db->free_result($q1);
-      $row2 = $db->fetchrow($q2);
-      $db->free_result($q2);
-      if(sizeof($row1) < 1 || sizeof($row2) < 2)
-        return 'Couldn\'t find any rows that matched the query. The time ID probably doesn\'t exist in the logs table.';
-    }
-    $text1 = $row1['page_text'];
-    $text2 = $row2['page_text'];
-    $time1 = enano_date(ED_DATE | ED_TIME, $row1['time_id']);
-    $time2 = enano_date(ED_DATE | ED_TIME, $row2['time_id']);
-    $_ob = "
-    <p>" . $lang->get('history_lbl_comparingrevisions') . " {$time1} &rarr; {$time2}</p>
-    ";
-    // Free some memory
-    unset($row1, $row2, $q1, $q2);
-    
-    $_ob .= RenderMan::diff($text1, $text2);
-    return $_ob;
-  }
-  
-  /**
-   * Gets ACL information about the selected page for target type X and target ID Y.
-   * @param array $parms What to select. This is an array purely for JSON compatibility. It should be an associative array with keys target_type and target_id.
-   * @return array
-   */
-   
-  public static function acl_editor($parms = Array())
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    
-    if(!$session->get_permissions('edit_acl') && ( $session->user_level < USER_LEVEL_ADMIN || !defined('ACL_ALWAYS_ALLOW_ADMIN_EDIT_ACL')) )
-    {
-      return Array(
-        'mode' => 'error',
-        'error' => $lang->get('acl_err_access_denied')
-        );
-    }
-    if ( !$session->sid_super )
-    {
-      return Array(
-        'mode' => 'error',
-        'error' => $lang->get('etc_access_denied_need_reauth')
-        );
-    }
-    $parms['page_id'] = ( isset($parms['page_id']) ) ? $parms['page_id'] : false;
-    $parms['namespace'] = ( isset($parms['namespace']) ) ? $parms['namespace'] : false;
-    $page_id =& $parms['page_id'];
-    $namespace =& $parms['namespace'];
-    $page_where_clause      = ( empty($page_id) || empty($namespace) ) ? 'AND a.page_id IS NULL AND a.namespace IS NULL' : 'AND a.page_id=\'' . $db->escape($page_id) . '\' AND a.namespace=\'' . $db->escape($namespace) . '\'';
-    $page_where_clause_lite = ( empty($page_id) || empty($namespace) ) ? 'AND page_id IS NULL AND namespace IS NULL' : 'AND page_id=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\'';
-    //die(print_r($page_id,true));
-    $template->load_theme();
-    // $perms_obj = $session->fetch_page_acl($page_id, $namespace);
-    $perms_obj =& $session;
-    $return = Array();
-    if ( !file_exists(ENANO_ROOT . '/themes/' . $session->theme . '/acledit.tpl') )
-    {
-      return Array(
-        'mode' => 'error',
-        'error' => $lang->get('acl_err_missing_template'),
-      );
-    }
-    $return['template'] = $template->extract_vars('acledit.tpl');
-    $return['page_id'] = $page_id;
-    $return['namespace'] = $namespace;
-    if(isset($parms['mode']))
-    {
-      switch($parms['mode'])
-      {
-        case 'listgroups':
-          $return['groups'] = Array();
-          $q = $db->sql_query('SELECT group_id,group_name FROM ' . table_prefix.'groups ORDER BY group_name ASC;');
-          while($row = $db->fetchrow())
-          {
-            $return['groups'][] = Array(
-              'id' => $row['group_id'],
-              'name' => $row['group_name'],
-              );
-          }
-          $db->free_result();
-          $return['page_groups'] = Array();
-          $q = $db->sql_query('SELECT pg_id,pg_name FROM ' . table_prefix.'page_groups ORDER BY pg_name ASC;');
-          if ( !$q )
-            return Array(
-              'mode' => 'error',
-              'error' => $db->get_error()
-              );
-          while ( $row = $db->fetchrow() )
-          {
-            $return['page_groups'][] = Array(
-                'id' => $row['pg_id'],
-                'name' => $row['pg_name']
-              );
-          }
-          break;
-        case 'seltarget_id':
-          if ( !is_int($parms['target_id']) )
-          {
-            return Array(
-              'mode' => 'error',
-              'error' => 'Expected parameter target_id type int'
-              );
-          }
-          $q = $db->sql_query('SELECT target_id, target_type, page_id, namespace, rules FROM ' . table_prefix . "acl WHERE rule_id = {$parms['target_id']};");
-          if ( !$q )
-            return Array(
-              'mode' => 'error',
-              'error' => $db->get_error()
-              );
-          if ( $db->numrows() < 1 )
-            return Array(
-              'mode' => 'error',
-              'error' => "No rule with ID {$parms['target_id']} found"
-              );
-            $parms = $db->fetchrow();
-            $db->free_result();
-            
-            // regenerate page selection
-            $parms['page_id'] = ( isset($parms['page_id']) ) ? $parms['page_id'] : false;
-            $parms['namespace'] = ( isset($parms['namespace']) ) ? $parms['namespace'] : false;
-            $parms['mode'] = 'seltarget_id';
-            $page_id =& $parms['page_id'];
-            $namespace =& $parms['namespace'];
-            $page_where_clause      = ( empty($page_id) || empty($namespace) ) ? 'AND a.page_id IS NULL AND a.namespace IS NULL' : 'AND a.page_id=\'' . $db->escape($page_id) . '\' AND a.namespace=\'' . $db->escape($namespace) . '\'';
-            $page_where_clause_lite = ( empty($page_id) || empty($namespace) ) ? 'AND page_id IS NULL AND namespace IS NULL' : 'AND page_id=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\'';
-            
-            $return['page_id'] = $parms['page_id'];
-            $return['namespace'] = $parms['namespace'];
-            
-            // From here, let the seltarget handler take over
-        case 'seltarget':
-          $return['mode'] = 'seltarget';
-          $return['acl_types'] = $perms_obj->acl_types;
-          $return['acl_deps'] = $perms_obj->acl_deps;
-          $return['acl_descs'] = $perms_obj->acl_descs;
-          $return['target_type'] = $parms['target_type'];
-          $return['target_id'] = $parms['target_id'];
-          switch($parms['target_type'])
-          {
-            case ACL_TYPE_USER:
-              $user_col = ( $parms['mode'] == 'seltarget_id' ) ? 'user_id' : 'username';
-              $q = $db->sql_query('SELECT a.rules,u.user_id,u.username FROM ' . table_prefix.'users AS u
-                  LEFT JOIN ' . table_prefix.'acl AS a
-                    ON a.target_id=u.user_id
-                  WHERE a.target_type='.ACL_TYPE_USER.'
-                    AND u.' . $user_col . ' = \'' . $db->escape($parms['target_id']) . '\'
-                    ' . $page_where_clause . ';');
-              if(!$q)
-                return(Array('mode'=>'error','error'=>$db->get_error()));
-              if($db->numrows() < 1)
-              {
-                $return['type'] = 'new';
-                $q = $db->sql_query('SELECT user_id,username FROM ' . table_prefix.'users WHERE username=\'' . $db->escape($parms['target_id']) . '\';');
-                if(!$q)
-                  return(Array('mode'=>'error','error'=>$db->get_error()));
-                if($db->numrows() < 1)
-                  return Array('mode'=>'error','error'=>$lang->get('acl_err_user_not_found'),'debug' => $db->sql_backtrace());
-                $row = $db->fetchrow();
-                $return['target_name'] = $row['username'];
-                $return['target_id'] = intval($row['user_id']);
-                $return['current_perms'] = array();
-              }
-              else
-              {
-                $return['type'] = 'edit';
-                $row = $db->fetchrow();
-                $return['target_name'] = $row['username'];
-                $return['target_id'] = intval($row['user_id']);
-                $return['current_perms'] = $session->string_to_perm($row['rules']);
-              }
-              $db->free_result();
-              // Eliminate types that don't apply to this namespace
-              if ( $namespace && $namespace != '__PageGroup' )
-              {
-                foreach ( $return['current_perms'] AS $i => $perm )
-                {
-                  if ( ( $page_id != null && $namespace != null ) && ( !in_array ( $namespace, $session->acl_scope[$i] ) && !in_array('All', $session->acl_scope[$i]) ) )
-                  {
-                    // echo "// SCOPE CONTROL: eliminating: $i\n";
-                    unset($return['current_perms'][$i]);
-                    unset($return['acl_types'][$i]);
-                    unset($return['acl_descs'][$i]);
-                    unset($return['acl_deps'][$i]);
-                  }
-                }
-              }
-              break;
-            case ACL_TYPE_GROUP:
-              $q = $db->sql_query('SELECT a.rules,g.group_name,g.group_id FROM ' . table_prefix.'groups AS g
-                  LEFT JOIN ' . table_prefix.'acl AS a
-                    ON a.target_id=g.group_id
-                  WHERE a.target_type='.ACL_TYPE_GROUP.'
-                    AND g.group_id=\''.intval($parms['target_id']).'\'
-                    ' . $page_where_clause . ';');
-              if(!$q)
-                return(Array('mode'=>'error','error'=>$db->get_error()));
-              if($db->numrows() < 1)
-              {
-                $return['type'] = 'new';
-                $q = $db->sql_query('SELECT group_id,group_name FROM ' . table_prefix.'groups WHERE group_id=\''.intval($parms['target_id']).'\';');
-                if(!$q)
-                  return(Array('mode'=>'error','error'=>$db->get_error()));
-                if($db->numrows() < 1)
-                  return Array('mode'=>'error','error'=>$lang->get('acl_err_bad_group_id'));
-                $row = $db->fetchrow();
-                $return['target_name'] = $row['group_name'];
-                $return['target_id'] = intval($row['group_id']);
-                $return['current_perms'] = array();
-              }
-              else
-              {
-                $return['type'] = 'edit';
-                $row = $db->fetchrow();
-                $return['target_name'] = $row['group_name'];
-                $return['target_id'] = intval($row['group_id']);
-                $return['current_perms'] = $session->string_to_perm($row['rules']);
-              }
-              $db->free_result();
-              // Eliminate types that don't apply to this namespace
-              if ( $namespace && $namespace != '__PageGroup' )
-              {
-                foreach ( $return['current_perms'] AS $i => $perm )
-                {
-                  if ( ( $page_id != false && $namespace != false ) && ( !in_array ( $namespace, $session->acl_scope[$i] ) && !in_array('All', $session->acl_scope[$i]) ) )
-                  {
-                    // echo "// SCOPE CONTROL: eliminating: $i\n"; //; ".print_r($namespace,true).":".print_r($page_id,true)."\n";
-                    unset($return['current_perms'][$i]);
-                    unset($return['acl_types'][$i]);
-                    unset($return['acl_descs'][$i]);
-                    unset($return['acl_deps'][$i]);
-                  }
-                }
-              }
-              //return Array('mode'=>'debug','text'=>print_r($return, true));
-              break;
-            default:
-              return Array('mode'=>'error','error','Invalid ACL type ID');
-              break;
-          }
-          return $return;
-          break;
-        case 'save_new':
-        case 'save_edit':
-          if ( defined('ENANO_DEMO_MODE') )
-          {
-            return Array('mode'=>'error','error'=>$lang->get('acl_err_demo'));
-          }
-          $q = $db->sql_query('DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).'
-            ' . $page_where_clause_lite . ';');
-          if(!$q)
-            return Array('mode'=>'error','error'=>$db->get_error());
-          if ( sizeof ( $parms['perms'] ) < 1 )
-          {
-            // As of 1.1.x, this returns success because the rule length is zero if the user selected "inherit" in all columns
-            return Array(
-              'mode' => 'success',
-              'target_type' => $parms['target_type'],
-              'target_id' => $parms['target_id'],
-              'target_name' => $parms['target_name'],
-              'page_id' => $page_id,
-              'namespace' => $namespace,
-            );
-          }
-          $rules = $session->perm_to_string($parms['perms']);
-          $q = ($page_id && $namespace) ? 'INSERT INTO ' . table_prefix.'acl ( target_type, target_id, page_id, namespace, rules )
-                                             VALUES( '.intval($parms['target_type']).', '.intval($parms['target_id']).', \'' . $db->escape($page_id) . '\', \'' . $db->escape($namespace) . '\', \'' . $db->escape($rules) . '\' )' :
-                                          'INSERT INTO ' . table_prefix.'acl ( target_type, target_id, rules )
-                                             VALUES( '.intval($parms['target_type']).', '.intval($parms['target_id']).', \'' . $db->escape($rules) . '\' )';
-          if(!$db->sql_query($q)) return Array('mode'=>'error','error'=>$db->get_error());
-          return Array(
-              'mode' => 'success',
-              'target_type' => $parms['target_type'],
-              'target_id' => $parms['target_id'],
-              'target_name' => $parms['target_name'],
-              'page_id' => $page_id,
-              'namespace' => $namespace,
-            );
-          break;
-        case 'delete':
-          if ( defined('ENANO_DEMO_MODE') )
-          {
-            return Array('mode'=>'error','error'=>$lang->get('acl_err_demo'));
-          }
-          $sql = 'DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).'
-            ' . $page_where_clause_lite . ';';
-          $q = $db->sql_query($sql);
-          if(!$q)
-            return Array('mode'=>'error','error'=>$db->get_error());
-          return Array(
-              'mode' => 'delete',
-              'target_type' => $parms['target_type'],
-              'target_id' => $parms['target_id'],
-              'target_name' => $parms['target_name'],
-              'page_id' => $page_id,
-              'namespace' => $namespace,
-            );
-          break;
-        case 'list_existing':
-          
-          $return = array(
-              'mode'  => 'list_existing',
-              'key'   => acl_list_draw_key(),
-              'rules' => array()
-            );
-          
-          $acl_columns = 'a.' . implode(', a.', $db->columns_in(table_prefix . 'acl'));
-          $users_columns = 'u.' . implode(', u.', $db->columns_in(table_prefix . 'users'));
-          $groups_columns = 'g.' . implode(', g.', $db->columns_in(table_prefix . 'groups'));
-          $pg_columns = 'p.' . implode(', p.', array('pg_id', 'pg_type', 'pg_name', 'pg_target'));
-          
-          $q = $db->sql_query("SELECT a.rule_id, u.username, g.group_name, a.target_type, a.target_id, a.page_id, a.namespace, a.rules, p.pg_name\n"
-                  . "  FROM " . table_prefix . "acl AS a\n"
-                  . "  LEFT JOIN " . table_prefix . "users AS u\n"
-                  . "    ON ( (a.target_type = " . ACL_TYPE_USER . " AND a.target_id = u.user_id) OR (u.user_id IS NULL) )\n"
-                  . "  LEFT JOIN " . table_prefix . "groups AS g\n"
-                  . "    ON ( (a.target_type = " . ACL_TYPE_GROUP . " AND a.target_id = g.group_id) OR (g.group_id IS NULL) )\n"
-                  . "  LEFT JOIN " . table_prefix . "page_groups as p\n"
-                  . "    ON ( (a.namespace = '__PageGroup' AND a.page_id = CAST(p.pg_id AS CHAR)) OR (p.pg_id IS NULL) )\n"
-                  . "  WHERE ( a.target_type = " . ACL_TYPE_USER . " OR a.target_type = " . ACL_TYPE_GROUP . " )\n"
-                  . "  GROUP BY a.rule_id, $acl_columns, $users_columns, $groups_columns, $pg_columns\n"
-                  . "  ORDER BY a.target_type ASC, a.rule_id ASC;"
-                );
-          
-          if ( !$q )
-            $db->_die();
-          
-          while ( $row = $db->fetchrow($q) )
-          {
-            if ( $row['target_type'] == ACL_TYPE_USER && empty($row['username']) )
-            {
-              // This is only done if we have an ACL affecting a user that doesn't exist.
-              // Nice little bit of maintenance to have.
-              if ( !$db->sql_query("DELETE FROM " . table_prefix . "acl WHERE rule_id = {$row['rule_id']};") )
-                $db->_die();
-              continue;
-            }
-            $score = get_acl_rule_score($row['rules']);
-            $deep_limit = ACL_SCALE_MINIMAL_SHADE;
-            // Determine background color of cell by score
-            if ( $score > 5 )
-            {
-              // high score, show in green
-              $color = 2.5 * $score;
-              if ( $color > 255 )
-                $color = 255;
-              $color = round($color);
-              // blend with the colordepth limit
-              $color = $deep_limit + ( ( 0xFF - $deep_limit ) - ( ( $color / 0xFF ) * ( 0xFF - $deep_limit ) ) );
-              $color = dechex($color);
-              $color = "{$color}ff{$color}";
-            }
-            else if ( $score < -5 )
-            {
-              // low score, show in red
-              $color = 0 - $score;
-              $color = 2.5 * $color;
-              if ( $color > 255 )
-                $color = 255;
-              $color = round($color);
-              // blend with the colordepth limit
-              $color = $deep_limit + ( ( 0xFF - $deep_limit ) - ( ( $color / 0xFF ) * ( 0xFF - $deep_limit ) ) );
-              $color = dechex($color);
-              $color = "ff{$color}{$color}";
-            }
-            else
-            {
-              $color = 'efefef';
-            }
-            
-            // Rate rule textually based on its score
-            if ( $score >= 70 )
-              $desc = $lang->get('acl_msg_scale_allow');
-            else if ( $score >= 50 )
-              $desc = $lang->get('acl_msg_scale_mostly_allow');
-            else if ( $score >= 25 )
-              $desc = $lang->get('acl_msg_scale_some_allow');
-            else if ( $score >= -25 )
-              $desc = $lang->get('acl_msg_scale_mixed');
-            else if ( $score <= -70 )
-              $desc = $lang->get('acl_msg_scale_deny');
-            else if ( $score <= -50 )
-              $desc = $lang->get('acl_msg_scale_mostly_deny');
-            else if ( $score <= -25 )
-              $desc = $lang->get('acl_msg_scale_some_deny');
-            
-            // group and user target info
-            $info = '';
-            if ( $row['target_type'] == ACL_TYPE_USER )
-              $info = $lang->get('acl_msg_list_user', array( 'username' => $row['username'] )); // "(User: {$row['username']})";
-            else if ( $row['target_type'] == ACL_TYPE_GROUP )
-              $info = $lang->get('acl_msg_list_group', array( 'group' => $row['group_name'] ));
-            
-            // affected pages info
-            if ( $row['page_id'] && $row['namespace'] && $row['namespace'] != '__PageGroup' )
-              $info .= $lang->get('acl_msg_list_on_page', array( 'page_name' => "{$row['namespace']}:{$row['page_id']}" ));
-            else if ( $row['page_id'] && $row['namespace'] && $row['namespace'] == '__PageGroup' )
-              $info .= $lang->get('acl_msg_list_on_page_group', array( 'page_group' => $row['pg_name'] ));
-            else
-              $info .= $lang->get('acl_msg_list_entire_site');
-              
-            $score_string = $lang->get('acl_msg_list_score', array
-              (
-                'score' => $score,
-                'desc'  => $desc,
-                'info'  => $info
-                ));
-            $return['rules'][] = array(
-              'score_string' => $score_string,
-              'rule_id'      => $row['rule_id'],
-              'color'        => $color
-              );
-          }
-          
-          break;
-        case 'list_presets':
-          $presets = array();
-          $q = $db->sql_query('SELECT page_id AS preset_name, rule_id, rules FROM ' . table_prefix . "acl WHERE target_type = " . ACL_TYPE_PRESET . ";");
-          if ( !$q )
-            $db->die_json();
-          
-          while ( $row = $db->fetchrow() )
-          {
-            $row['rules'] = $session->string_to_perm($row['rules']);
-            $presets[] = $row;
-          }
-          
-          return array(
-            'mode' => 'list_existing',
-            'presets' => $presets
-          );
-          break;
-        case 'save_preset':
-          if ( empty($parms['preset_name']) )
-          {
-            return array(
-              'mode' => 'error',
-              'error' => $lang->get('acl_err_preset_name_empty')
-            );
-          }
-          $preset_name = $db->escape($parms['preset_name']);
-          $q = $db->sql_query('DELETE FROM ' . table_prefix . "acl WHERE target_type = " . ACL_TYPE_PRESET . " AND page_id = '$preset_name';");
-          if ( !$q )
-            $db->die_json();
-          
-          $perms = $session->perm_to_string($parms['perms']);
-          if ( !$perms )
-          {
-            return array(
-              'mode' => 'error',
-              'error' => $lang->get('acl_err_preset_is_blank')
-            );
-          }
-          
-          $perms = $db->escape($perms);
-          $q = $db->sql_query('INSERT INTO ' . table_prefix . "acl(page_id, target_type, rules) VALUES\n"
-                            . "  ( '$preset_name', " . ACL_TYPE_PRESET . ", '$perms' );");
-          if ( !$q )
-            $db->die_json();
-          
-          return array(
-              'mode' => 'success'
-            );
-          break;
-        case 'trace':
-          list($targetpid, $targetns) = RenderMan::strToPageID($parms['page']);
-          try
-          {
-            $perms = $session->fetch_page_acl_user($parms['user'], $targetpid, $targetns);
-            $perm_table = array(
-                AUTH_ALLOW => 'acl_lbl_field_allow',
-                AUTH_WIKIMODE => 'acl_lbl_field_wikimode',
-                AUTH_DISALLOW => 'acl_lbl_field_disallow',
-                AUTH_DENY => 'acl_lbl_field_deny'
-              );
-            
-            $return = array(
-              'mode' => 'trace',
-              'perms' => array()
-            );
-            
-            foreach ( $perms->perm_resolve_table as $perm_type => $lookup_data )
-            {
-              if ( !$session->check_acl_scope($perm_type, $targetns) )
-                continue;
-              
-              $src_l10n = $lang->get($session->acl_inherit_lang_table[$lookup_data['src']], $lookup_data);
-              $divclass = preg_replace('/^acl_inherit_/', '', $session->acl_inherit_lang_table[$lookup_data['src']]);
-              $perm_string = $lang->get($perm_table[$perms->perms[$perm_type]]);
-              $perm_name = $lang->get($session->acl_descs[$perm_type]);
-              
-              $return['perms'][$perm_type] = array(
-                  'divclass' => "acl_inherit acl_$divclass",
-                  'perm_type' => $perm_type,
-                  'perm_name' => $perm_name,
-                  'perm_value' => $perm_string,
-                  'perm_src' => $src_l10n,
-                  'rule_id' => intval($lookup_data['rule_id']),
-                  'bad_deps' => $perms->acl_check_deps($perm_type, true)
-                );
-            }
-            
-            // group rules if possible
-            $return['groups'] = array();
-            foreach ( $return['perms'] as $rule )
-            {
-              if ( !isset($return['groups'][$rule['rule_id']]) )
-              {
-                $return['groups'][$rule['rule_id']] = array();
-              }
-              $return['groups'][$rule['rule_id']][] = $rule['perm_type'];
-            }
-          }
-          catch ( Exception $e )
-          {
-            $return = array(
-                'mode' => 'error',
-                'error' => $e->getMessage()
-              );
-          }
-          
-          break;
-        default:
-          return Array('mode'=>'error','error'=>'Hacking attempt');
-          break;
-      }
-    }
-    return $return;
-  }
-  
-  /**
-   * Same as PageUtils::acl_editor(), but the parms are a JSON string instead of an array. This also returns a JSON string.
-   * @param string $parms Same as PageUtils::acl_editor/$parms, but should be a valid JSON string.
-   * @return string
-   */
-   
-  public static function acl_json($parms = '{ }')
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    try
-    {
-      $parms = enano_json_decode($parms);
-    }
-    catch ( Zend_Json_Exception $e )
-    {
-      $parms = array();
-    }
-    $ret = PageUtils::acl_editor($parms);
-    $ret = enano_json_encode($ret);
-    return $ret;
-  }
-  
-  /**
-   * A non-Javascript frontend for the ACL API.
-   * @param array The request data, if any, this should be in the format required by PageUtils::acl_editor()
-   */
-   
-  public static function aclmanager($parms)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    global $lang;
-    ob_start();
-    // Convenience
-    $formstart = '<form 
-                    action="' . makeUrl($paths->page, 'do=aclmanager', true) . '"
-                    method="post" enctype="multipart/form-data"
-                    onsubmit="if(!submitAuthorized) return false;"
-                    >';
-    $formend   = '</form>';
-    $parms    = PageUtils::acl_preprocess($parms);
-    $response = PageUtils::acl_editor($parms);
-    $response = PageUtils::acl_postprocess($response);
-    
-    //die('<pre>' . htmlspecialchars(print_r($response, true)) . '</pre>');
-    
-    switch($response['mode'])
-    {
-      case 'debug':
-        echo '<pre>' . htmlspecialchars($response['text']) . '</pre>';
-        break;
-      case 'stage1':
-        echo '<h3>' . $lang->get('acl_lbl_welcome_title') . '</h3>
-              <p>' . $lang->get('acl_lbl_welcome_body') . '</p>';
-        echo $formstart;
-        echo '<p><label><input type="radio" name="data[target_type]" value="' . ACL_TYPE_GROUP . '" checked="checked" /> ' . $lang->get('acl_radio_usergroup') . '</label></p>
-              <p><select name="data[target_id_grp]">';
-        foreach ( $response['groups'] as $group )
-        {
-          echo '<option value="' . $group['id'] . '">' . $group['name'] . '</option>';
-        }
-        
-        // page group selector
-        $groupsel = '';
-        if ( count($response['page_groups']) > 0 )
-        {
-          $groupsel = '<p><label><input type="radio" name="data[scope]" value="page_group" /> ' . $lang->get('acl_radio_scope_pagegroup') . '</label></p>
-                       <p><select name="data[pg_id]">';
-          foreach ( $response['page_groups'] as $grp )
-          {
-            $groupsel .= '<option value="' . $grp['id'] . '">' . htmlspecialchars($grp['name']) . '</option>';
-          }
-          $groupsel .= '</select></p>';
-        }
-        
-        echo '</select></p>
-              <p><label><input type="radio" name="data[target_type]" value="' . ACL_TYPE_USER . '" /> ' . $lang->get('acl_radio_user') . '</label></p>
-              <p>' . $template->username_field('data[target_id_user]') . '</p>
-              <p>' . $lang->get('acl_lbl_scope') . '</p>
-              <p><label><input name="data[scope]" value="only_this" type="radio" checked="checked" /> ' . $lang->get('acl_radio_scope_thispage') . '</p>
-              ' . $groupsel . '
-              <p><label><input name="data[scope]" value="entire_site" type="radio" /> ' . $lang->get('acl_radio_scope_wholesite') . '</p>
-              <div style="margin: 0 auto 0 0; text-align: right;">
-                <input name="data[mode]" value="seltarget" type="hidden" />
-                <input type="hidden" name="data[page_id]" value="' . $paths->page_id . '" />
-                <input type="hidden" name="data[namespace]" value="' . $paths->namespace . '" />
-                <input type="submit" value="' . htmlspecialchars($lang->get('etc_wizard_next')) . '" />
-              </div>';
-        echo $formend;
-        break;
-      case 'success':
-        echo '<div class="info-box">
-                <b>' . $lang->get('acl_lbl_save_success_title') . '</b><br />
-                ' . $lang->get('acl_lbl_save_success_body', array( 'target_name' => $response['target_name'] )) . '<br />
-                ' . $formstart . '
-                <input type="hidden" name="data[mode]" value="seltarget" />
-                <input type="hidden" name="data[target_type]" value="' . $response['target_type'] . '" />
-                <input type="hidden" name="data[target_id_user]" value="' . ( ( intval($response['target_type']) == ACL_TYPE_USER ) ? $response['target_name'] : $response['target_id'] ) .'" />
-                <input type="hidden" name="data[target_id_grp]"  value="' . ( ( intval($response['target_type']) == ACL_TYPE_USER ) ? $response['target_name'] : $response['target_id'] ) .'" />
-                <input type="hidden" name="data[scope]" value="' . ( ( $response['page_id'] ) ? 'only_this' : 'entire_site' ) . '" />
-                <input type="hidden" name="data[page_id]" value="' . ( ( $response['page_id'] ) ? $response['page_id'] : 'false' ) . '" />
-                <input type="hidden" name="data[namespace]" value="' . ( ( $response['namespace'] ) ? $response['namespace'] : 'false' ) . '" />
-                <input type="submit" value="' . $lang->get('acl_btn_returnto_editor') . '" /> <input type="submit" name="data[act_go_stage1]" value="' . $lang->get('acl_btn_returnto_userscope') . '" />
-                ' . $formend . '
-              </div>';
-        break;
-      case 'delete':
-        echo '<div class="info-box">
-                <b>' . $lang->get('acl_lbl_delete_success_title') . '</b><br />
-                ' . $lang->get('acl_lbl_delete_success_body', array('target_name' => $response['target_name'])) . '<br />
-                ' . $formstart . '
-                <input type="hidden" name="data[mode]" value="seltarget" />
-                <input type="hidden" name="data[target_type]" value="' . $response['target_type'] . '" />
-                <input type="hidden" name="data[target_id_user]" value="' . ( ( intval($response['target_type']) == ACL_TYPE_USER ) ? $response['target_name'] : $response['target_id'] ) .'" />
-                <input type="hidden" name="data[target_id_grp]"  value="' . ( ( intval($response['target_type']) == ACL_TYPE_USER ) ? $response['target_name'] : $response['target_id'] ) .'" />
-                <input type="hidden" name="data[scope]" value="' . ( ( $response['page_id'] ) ? 'only_this' : 'entire_site' ) . '" />
-                <input type="hidden" name="data[page_id]" value="' . ( ( $response['page_id'] ) ? $response['page_id'] : 'false' ) . '" />
-                <input type="hidden" name="data[namespace]" value="' . ( ( $response['namespace'] ) ? $response['namespace'] : 'false' ) . '" />
-                <input type="submit" value="' . $lang->get('acl_btn_returnto_editor') . '" /> <input type="submit" name="data[act_go_stage1]" value="' . $lang->get('acl_btn_returnto_userscope') . '" />
-                ' . $formend . '
-              </div>';
-        break;
-      case 'seltarget':
-        if ( $response['type'] == 'edit' )
-        {
-          echo '<h3>' . $lang->get('acl_lbl_editwin_title_edit') . '</h3>';
-        }
-        else
-        {
-          echo '<h3>' . $lang->get('acl_lbl_editwin_title_create') . '</h3>';
-        }
-        $type  = ( $response['target_type'] == ACL_TYPE_GROUP ) ? $lang->get('acl_target_type_group') : $lang->get('acl_target_type_user');
-        $scope = ( $response['page_id'] ) ? ( $response['namespace'] == '__PageGroup' ? $lang->get('acl_scope_type_pagegroup') : $lang->get('acl_scope_type_thispage') ) : $lang->get('acl_scope_type_wholesite');
-        $subs = array(
-            'target_type' => $type,
-            'target' => $response['target_name'],
-            'scope_type' => $scope
-          );
-        echo $lang->get('acl_lbl_editwin_body', $subs);
-        echo $formstart;
-        $parser = $template->makeParserText( $response['template']['acl_field_begin'] );
-        echo $parser->run();
-        $parser = $template->makeParserText( $response['template']['acl_field_item'] );
-        $cls = 'row2';
-        foreach ( $response['acl_types'] as $acl_type => $value )
-        {
-          $vars = Array(
-              'FIELD_INHERIT_CHECKED' => '',
-              'FIELD_DENY_CHECKED' => '',
-              'FIELD_DISALLOW_CHECKED' => '',
-              'FIELD_WIKIMODE_CHECKED' => '',
-              'FIELD_ALLOW_CHECKED' => '',
-            );
-          $cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
-          $vars['ROW_CLASS'] = $cls;
-          
-          switch ( $response['current_perms'][$acl_type] )
-          {
-            case 'i':
-            default:
-              $vars['FIELD_INHERIT_CHECKED'] = 'checked="checked"';
-              break;
-            case AUTH_ALLOW:
-              $vars['FIELD_ALLOW_CHECKED'] = 'checked="checked"';
-              break;
-            case AUTH_WIKIMODE:
-              $vars['FIELD_WIKIMODE_CHECKED'] = 'checked="checked"';
-              break;
-            case AUTH_DISALLOW:
-              $vars['FIELD_DISALLOW_CHECKED'] = 'checked="checked"';
-              break;
-             case AUTH_DENY:
-              $vars['FIELD_DENY_CHECKED'] = 'checked="checked"';
-              break;
-          }
-          $vars['FIELD_NAME'] = 'data[perms][' . $acl_type . ']';
-          if ( preg_match('/^([a-z0-9_]+)$/', $response['acl_descs'][$acl_type]) )
-          {
-            $vars['FIELD_DESC'] = $lang->get($response['acl_descs'][$acl_type]);
-          }
-          else
-          {
-            $vars['FIELD_DESC'] = $response['acl_descs'][$acl_type];
-          }
-          $parser->assign_vars($vars);
-          echo $parser->run();
-        }
-        $parser = $template->makeParserText( $response['template']['acl_field_end'] );
-        echo $parser->run();
-        echo '<div style="margin: 10px auto 0 0; text-align: right;">
-                <input name="data[mode]" value="save_' . $response['type'] . '" type="hidden" />
-                <input type="hidden" name="data[page_id]" value="'   . (( $response['page_id']   ) ? $response['page_id']   : 'false') . '" />
-                <input type="hidden" name="data[namespace]" value="' . (( $response['namespace'] ) ? $response['namespace'] : 'false') . '" />
-                <input type="hidden" name="data[target_type]" value="' . $response['target_type'] . '" />
-                <input type="hidden" name="data[target_id]" value="' . $response['target_id'] . '" />
-                <input type="hidden" name="data[target_name]" value="' . $response['target_name'] . '" />
-                ' . ( ( $response['type'] == 'edit' ) ? '<input type="submit" value="' . $lang->get('etc_save_changes') . '" />&nbsp;&nbsp;<input type="submit" name="data[act_delete_rule]" value="' . $lang->get('acl_btn_deleterule') . '" style="color: #AA0000;" onclick="return confirm(\'' . addslashes($lang->get('acl_msg_deleterule_confirm')) . '\');" />' : '<input type="submit" value="' . $lang->get('acl_btn_createrule') . '" />' ) . '
-              </div>';
-        echo $formend;
-        break;
-      case 'error':
-        ob_end_clean();
-        die_friendly('Error occurred', '<p>Error returned by permissions API:</p><pre>' . htmlspecialchars($response['error']) . '</pre>');
-        break;
-    }
-    $ret = ob_get_contents();
-    ob_end_clean();
-    echo
-      $template->getHeader() .
-      $ret .
-      $template->getFooter();
-  }
-  
-  /**
-   * Preprocessor to turn the form-submitted data from the ACL editor into something the backend can handle
-   * @param array The posted data
-   * @return array
-   * @access private
-   */
-   
-  public static function acl_preprocess($parms)
-  {
-    if ( !isset($parms['mode']) )
-      // Nothing to do
-      return $parms;
-    switch ( $parms['mode'] )
-    {
-      case 'seltarget':
-        
-        // Who's affected?
-        $parms['target_type'] = intval( $parms['target_type'] );
-        $parms['target_id'] = ( $parms['target_type'] == ACL_TYPE_GROUP ) ? $parms['target_id_grp'] : $parms['target_id_user'];
-        
-      case 'save_edit':
-      case 'save_new':
-        if ( isset($parms['act_delete_rule']) )
-        {
-          $parms['mode'] = 'delete';
-        }
-        
-        // Scope (just this page or entire site?)
-        if ( $parms['scope'] == 'entire_site' || ( $parms['page_id'] == 'false' && $parms['namespace'] == 'false' ) )
-        {
-          $parms['page_id']   = false;
-          $parms['namespace'] = false;
-        }
-        else if ( $parms['scope'] == 'page_group' )
-        {
-          $parms['page_id'] = $parms['pg_id'];
-          $parms['namespace'] = '__PageGroup';
-        }
-        
-        break;
-    }
-    
-    if ( isset($parms['act_go_stage1']) )
-    {
-      $parms = array(
-          'mode' => 'listgroups'
-        );
-    }
-    
-    return $parms;
-  }
-  
-  public static function acl_postprocess($response)
-  {
-    if(!isset($response['mode']))
-    {
-      if ( isset($response['groups']) )
-        $response['mode'] = 'stage1';
-      else
-        $response = Array(
-            'mode' => 'error',
-            'error' => 'Invalid action passed by API backend.',
-          );
-    }
-    return $response;
-  }
-   
+	
+	/**
+ 	* Tell if a username is used or not.
+ 	* @param $name the name to check for
+ 	* @return string
+ 	*/
+	
+	public static function checkusername($name)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		$name = str_replace('_', ' ', $name);
+		$q = $db->sql_query('SELECT username FROM ' . table_prefix.'users WHERE username=\'' . $db->escape(rawurldecode($name)) . '\'');
+		if ( !$q )
+		{
+			die($db->get_error());
+		}
+		if ( $db->numrows() < 1)
+		{
+			$db->free_result(); return('good');
+		}
+		else
+		{
+			$db->free_result(); return('bad');
+		}
+	}
+	
+	/**
+ 	* Get the wiki formatting source for a page
+ 	* @param $page the full page id (Namespace:Pagename)
+ 	* @return string
+ 	* @todo (DONE) Make it require a password (just for security purposes)
+ 	*/
+ 	
+	public static function getsource($page, $password = false)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		if ( !isPage($page) )
+		{
+			return '';
+		}
+		
+		list($page_id, $namespace) = RenderMan::strToPageID($page);
+		$ns = namespace_factory($page_id, $namespace);
+		$cdata = $ns->get_cdata();
+		
+		if ( strlen($cdata['password']) == 40 )
+		{
+			if(!$password || ( $password != $cdata['password']))
+			{
+				return 'invalid_password';
+			}
+		}
+		
+		if(!$session->get_permissions('view_source')) // Dependencies handle this for us - this also checks for read privileges
+			return 'access_denied';
+		$pid = RenderMan::strToPageID($page);
+		if($pid[1] == 'Special' || $pid[1] == 'Admin')
+		{
+			die('This type of page (' . $paths->nslist[$pid[1]] . ') cannot be edited because the page source code is not stored in the database.');
+		}
+		
+		$e = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix.'page_text WHERE page_id=\'' . $pid[0] . '\' AND namespace=\'' . $pid[1] . '\'');
+		if ( !$e )
+		{
+			$db->_die('The page text could not be selected.');
+		}
+		if( $db->numrows() < 1 )
+		{
+			return ''; //$db->_die('There were no rows in the text table that matched the page text query.');
+		}
+		
+		$r = $db->fetchrow();
+		$db->free_result();
+		$message = $r['page_text'];
+		
+		return htmlspecialchars($message);
+	}
+	
+	/**
+ 	* DEPRECATED. Previously returned the full rendered contents of a page.
+ 	* @param $page the full page id (Namespace:Pagename)
+ 	* @param $send_headers true if the theme headers should be sent (still dependent on current page settings), false otherwise
+ 	* @return string
+ 	*/
+	
+	public static function getpage($page, $send_headers = false, $hist_id = false)
+	{
+		die('PageUtils->getpage is deprecated.');
+	}
+	
+	/**
+ 	* Writes page data to the database, after verifying permissions and running the XSS filter
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $message the text to save
+ 	* @return string
+ 	*/
+ 	
+	public static function savepage($page_id, $namespace, $message, $summary = 'No edit summary given', $minor = false)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		
+		$page = new PageProcessor($page_id, $namespace);
+		$cdata = $page->ns->get_cdata();
+		return $page->update_page($message, $summary, $minor, $cdata['page_format']);
+	}
+	
+	/**
+ 	* Creates a page, both in memory and in the database.
+ 	* @param string $page_id
+ 	* @param string $namespace
+ 	* @return bool true on success, false on failure
+ 	*/
+	
+	public static function createPage($page_id, $namespace, $name = false, $visible = 1)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		if(in_array($namespace, Array('Special', 'Admin')))
+		{
+			// echo '<b>Notice:</b> PageUtils::createPage: You can\'t create a special page in the database<br />';
+			return 'You can\'t create a special page in the database';
+		}
+		
+		if(!isset($paths->nslist[$namespace]))
+		{
+			// echo '<b>Notice:</b> PageUtils::createPage: Couldn\'t look up the namespace<br />';
+			return 'Couldn\'t look up the namespace';
+		}
+		
+		$pname = $paths->nslist[$namespace] . $page_id;
+		if(isPage($pname))
+		{
+			// echo '<b>Notice:</b> PageUtils::createPage: Page already exists<br />';
+			return 'Page already exists';
+		}
+		
+		if(!$session->get_permissions('create_page'))
+		{
+			// echo '<b>Notice:</b> PageUtils::createPage: Not authorized to create pages<br />';
+			return 'Not authorized to create pages';
+		}
+		
+		if($session->user_level < USER_LEVEL_ADMIN && $namespace == 'System')
+		{
+			// echo '<b>Notice:</b> PageUtils::createPage: Not authorized to create system messages<br />';
+			return 'Not authorized to create system messages';
+		}
+		
+		if ( substr($page_id, 0, 8) == 'Project:' )
+		{
+			// echo '<b>Notice:</b> PageUtils::createPage: Prefix "Project:" is reserved<br />';
+			return 'The prefix "Project:" is reserved for a parser shortcut; if a page was created using this prefix, it would not be possible to link to it.';
+		}
+		
+		/*
+		// Dunno why this was here. Enano can handle more flexible names than this...
+		$regex = '#^([A-z0-9 _\-\.\/\!\@\(\)]*)$#is';
+		if(!preg_match($regex, $name))
+		{
+			//echo '<b>Notice:</b> PageUtils::createPage: Name contains invalid characters<br />';
+			return 'Name contains invalid characters';
+		}
+		*/
+		
+		$page_id = dirtify_page_id($page_id);
+		
+		if ( !$name )
+			$name = str_replace('_', ' ', $page_id);
+		
+		$page_id = sanitize_page_id( $page_id );
+		
+		$prot = ( $namespace == 'System' ) ? 1 : 0;
+		
+		$ips = array(
+			'ip' => array(),
+			'u' => array()
+			);
+		
+		$page_data = Array(
+			'name'=>$name,
+			'urlname'=>$page_id,
+			'namespace'=>$namespace,
+			'special'=>0,'visible'=>1,'comments_on'=>0,'protected'=>$prot,'delvotes'=>0,'delvote_ips'=>serialize($ips),'wiki_mode'=>2,
+		);
+		
+		// die('PageUtils::createpage: Creating page with this data:<pre>' . print_r($page_data, true) . '</pre>');
+		
+		$paths->add_page($page_data);
+		
+		$qa = $db->sql_query('INSERT INTO ' . table_prefix.'pages(name,urlname,namespace,visible,protected,delvote_ips) VALUES(\'' . $db->escape($name) . '\', \'' . $db->escape($page_id) . '\', \'' . $namespace . '\', '. ( $visible ? '1' : '0' ) .', ' . $prot . ', \'' . $db->escape(serialize($ips)) . '\');');
+		$qb = $db->sql_query('INSERT INTO ' . table_prefix.'page_text(page_id,namespace) VALUES(\'' . $db->escape($page_id) . '\', \'' . $namespace . '\');');
+		$qc = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,author,author_uid,page_id,namespace) VALUES('.time().', \'DEPRECATED\', \'page\', \'create\', \'' . $session->username . '\', ' . $session->user_id . ', \'' . $db->escape($page_id) . '\', \'' . $namespace . '\');');
+		
+		if($qa && $qb && $qc)
+			return 'good';
+		else
+		{
+			return $db->get_error();
+		}
+	}
+	
+	/**
+ 	* Sets the protection level on a page.
+ 	* @param $page_id string the page ID
+ 	* @param $namespace string the namespace
+ 	* @param $level int level of protection - 0 is off, 1 is full, 2 is semi
+ 	* @param $reason string why the page is being (un)protected
+ 	* @return string - "good" on success, in all other cases, an error string (on query failure, calls $db->_die() )
+ 	*/
+	public static function protect($page_id, $namespace, $level, $reason)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		
+		$page = new PageProcessor($page_id, $namespace);
+		return $page->protect_page($level, $reason);
+	}
+	
+	/**
+ 	* Generates an HTML table with history information in it.
+ 	* @param string the page ID
+ 	* @param string the namespace
+ 	* @param string page password
+ 	* @return string
+ 	*/
+	
+	public static function histlist($page_id, $namespace, $password = false)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		
+		if(!$session->get_permissions('history_view'))
+			return 'Access denied';
+		
+		ob_start();
+		
+		$pname = $paths->get_pathskey($page_id, $namespace);
+		$ns = namespace_factory($page_id, $namespace);
+		$cdata = $ns->get_cdata();
+		
+		if ( !isPage($pname) )
+		{
+			return 'DNE';
+		}
+		
+		if ( isPage($pname) )
+		{
+			$password_exists = ( !empty($cdata['password']) && $cdata['password'] !== sha1('') );
+			if ( $password_exists && $password !== $cdata['password'] )
+			{
+				return '<p>' . $lang->get('history_err_wrong_password') . '</p>';
+			}
+		}
+		
+		$wiki = ( ( $cdata['wiki_mode'] == 2 && getConfig('wiki_mode') == '1') || $cdata['wiki_mode'] == 1) ? true : false;
+		$prot = ( ( $cdata['protected'] == 2 && $session->user_logged_in && $session->reg_time + 60*60*24*4 < time() ) || $cdata['protected'] == 1) ? true : false;
+		
+		$q = 'SELECT log_id,time_id,date_string,page_id,namespace,author,author_uid,u.username,edit_summary,minor_edit FROM ' . table_prefix . "logs AS l\n"
+ 			. "  LEFT JOIN " . table_prefix . "users AS u\n"
+ 			. "    ON ( u.user_id = l.author_uid OR u.user_id IS NULL )\n"
+ 			. "  WHERE log_type='page' AND action='edit' AND page_id='$page_id' AND namespace='$namespace' AND is_draft != 1 ORDER BY time_id DESC;";
+		
+		if ( !($q = $db->sql_query($q)) )
+			$db->_die('The history data for the page "' . $paths->cpage['name'] . '" could not be selected.');
+		
+		echo $lang->get('history_page_subtitle') . '
+					<h3>' . $lang->get('history_heading_edits') . '</h3>';
+		$numrows = $db->numrows();
+		if ( $numrows < 1 )
+		{
+			echo $lang->get('history_no_entries');
+		}
+		else
+		{
+			echo '<form action="'.makeUrlNS($namespace, $page_id, 'do=diff').'" onsubmit="ajaxHistDiff(); return false;" method="get">
+						<input type="submit" value="' . $lang->get('history_btn_compare') . '" />
+						' . ( urlSeparator == '&' ? '<input type="hidden" name="title" value="' . htmlspecialchars($paths->nslist[$namespace] . $page_id) . '" />' : '' ) . '
+						' . ( $session->sid_super ? '<input type="hidden" name="auth"  value="' . $session->sid_super . '" />' : '') . '
+						<input type="hidden" name="do" value="diff" />
+						<br /><span>&nbsp;</span>
+						<div class="tblholder">
+						<table border="0" width="100%" cellspacing="1" cellpadding="4">
+						<tr>
+							<th colspan="2">' . $lang->get('history_col_diff') . '</th>
+							<th>' . $lang->get('history_col_datetime') . '</th>
+							<th>' . $lang->get('history_col_user') . '</th>
+							<th>' . $lang->get('history_col_summary') . '</th>
+							<th>' . $lang->get('history_col_minor') . '</th>
+							<th colspan="3">' . $lang->get('history_col_actions') . '</th>
+						</tr>'."\n"."\n";
+			$cls = 'row2';
+			$ticker = 0;
+			
+			while ( $r = $db->fetchrow($q) )
+			{
+				
+				$ticker++;
+				
+				if($cls == 'row2') $cls = 'row1';
+				else $cls = 'row2';
+				
+				echo '<tr>'."\n";
+				
+				// Diff selection
+				if($ticker == 1)
+				{
+					$s1 = '';
+					$s2 = 'checked="checked" ';
+				}
+				elseif($ticker == 2)
+				{
+					$s1 = 'checked="checked" ';
+					$s2 = '';
+				}
+				else
+				{
+					$s1 = '';
+					$s2 = '';
+				}
+				if($ticker > 1)        echo '<td class="' . $cls . '" style="padding: 0;"><input ' . $s1 . 'name="diff1" type="radio" value="' . $r['time_id'] . '" id="diff1_' . $r['time_id'] . '" class="clsDiff1Radio" onclick="selectDiff1Button(this);" /></td>'."\n"; else echo '<td class="' . $cls . '"></td>';
+				if($ticker < $numrows) echo '<td class="' . $cls . '" style="padding: 0;"><input ' . $s2 . 'name="diff2" type="radio" value="' . $r['time_id'] . '" id="diff2_' . $r['time_id'] . '" class="clsDiff2Radio" onclick="selectDiff2Button(this);" /></td>'."\n"; else echo '<td class="' . $cls . '"></td>';
+				
+				// Date and time
+				echo '<td class="' . $cls . '" style="white-space: nowrap;">' . enano_date(ED_DATE | ED_TIME, intval($r['time_id'])) . '</td class="' . $cls . '">'."\n";
+				
+				// User
+				$real_username = $r['author_uid'] > 1 && !empty($r['username']) ? $r['username'] : $r['author'];
+				$rank_info = $session->get_user_rank($r['author_uid']);
+				if ( $session->get_permissions('mod_misc') && is_valid_ip($r['author']) && $r['author_uid'] == 1 )
+				{
+					$rc = ' style="cursor: pointer;" title="' . $lang->get('history_tip_rdns') . '" onclick="ajaxReverseDNS(this, \'' . $r['author'] . '\');"';
+				}
+				else
+				{
+					$rc = '';
+				}
+				echo '<td class="' . $cls . '"' . $rc . '><a href="'.makeUrlNS('User', sanitize_page_id($real_username)).'" ';
+				if ( !isPage($paths->nslist['User'] . sanitize_page_id($real_username)) )
+				{
+					echo 'class="wikilink-nonexistent"';
+				}
+				echo 'style="' . $rank_info['rank_style'] . '">' . htmlspecialchars($real_username) . '</a></td class="' . $cls . '">'."\n";
+				
+				// Edit summary
+				if ( $r['edit_summary'] == 'Automatic backup created when logs were purged' )
+				{
+					$r['edit_summary'] = $lang->get('history_summary_clearlogs');
+				}
+				echo '<td class="' . $cls . '">' . $r['edit_summary'] . '</td>'."\n";
+				
+				// Minor edit
+				echo '<td class="' . $cls . '" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>'."\n";
+				
+				// Actions!
+				echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'oldid=' . $r['log_id']) . '" onclick="ajaxHistView(\'' . $r['log_id'] . '\'); return false;">' . $lang->get('history_action_view') . '</a></td>'."\n";
+				echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">' . $lang->get('history_action_contrib') . '</a></td>'."\n";
+				echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'do=edit&amp;revid=' . $r['log_id']) . '" onclick="ajaxEditor(\'' . $r['log_id'] . '\'); return false;">' . $lang->get('history_action_restore') . '</a></td>'."\n";
+				
+				echo '</tr>'."\n"."\n";
+				
+			}
+			echo '</table>
+						</div>
+						<br />
+						<input type="hidden" name="do" value="diff" />
+						<input type="submit" value="' . $lang->get('history_btn_compare') . '" />
+						</form>
+						<script type="text/javascript">if ( !KILL_SWITCH ) { buildDiffList(); }</script>';
+		}
+		$db->free_result();
+		echo '<h3>' . $lang->get('history_heading_other') . '</h3>';
+		
+		$sql = 'SELECT log_id,action,time_id,date_string,page_id,namespace,author,author_uid,u.username,edit_summary,minor_edit FROM ' . table_prefix . "logs AS l\n"
+ 				. "  LEFT JOIN " . table_prefix . "users AS u\n"
+ 				. "    ON ( u.user_id = l.author_uid OR u.user_id IS NULL )\n"
+ 				. "  WHERE log_type='page' AND action!='edit' AND page_id='$page_id' AND namespace='$namespace' AND is_draft != 1 ORDER BY time_id DESC;";
+		
+		if ( !( $q = $db->sql_query($sql)) )
+		{
+			$db->_die('The history data for the page "' . htmlspecialchars($paths->cpage['name']) . '" could not be selected.');
+		}
+		if ( $db->numrows() < 1 )
+		{
+			echo $lang->get('history_no_entries');
+		}
+		else
+		{
+			
+			echo '<div class="tblholder">
+							<table border="0" width="100%" cellspacing="1" cellpadding="4"><tr>
+								<th>' . $lang->get('history_col_datetime') . '</th>
+								<th>' . $lang->get('history_col_user') . '</th>
+								<th>' . $lang->get('history_col_minor') . '</th>
+								<th>' . $lang->get('history_col_action_taken') . '</th>
+								<th>' . $lang->get('history_col_extra') . '</th>
+								<th colspan="2"></th>
+							</tr>';
+			$cls = 'row2';
+			while($r = $db->fetchrow($q)) {
+				
+				if($cls == 'row2') $cls = 'row1';
+				else $cls = 'row2';
+				
+				echo '<tr>';
+				
+				// Date and time
+				echo '<td class="' . $cls . '">' . enano_date(ED_DATE | ED_TIME, intval($r['time_id'])) . '</td class="' . $cls . '">';
+				
+				// User
+				$real_username = $r['author_uid'] > 1 && !empty($r['username']) ? $r['username'] : $r['author'];
+				$rank_info = $session->get_user_rank($r['author_uid']);
+				if ( $session->get_permissions('mod_misc') && is_valid_ip($r['author']) && $r['author_uid'] == 1 )
+				{
+					$rc = ' style="cursor: pointer;" title="' . $lang->get('history_tip_rdns') . '" onclick="ajaxReverseDNS(this, \'' . $r['author'] . '\');"';
+				}
+				else
+				{
+					$rc = '';
+				}
+				echo '<td class="' . $cls . '"' . $rc . '><a href="'.makeUrlNS('User', sanitize_page_id($real_username)).'" ';
+				if ( !isPage($paths->nslist['User'] . sanitize_page_id($real_username)) )
+				{
+					echo 'class="wikilink-nonexistent"';
+				}
+				echo 'style="' . $rank_info['rank_style'] . '">' . htmlspecialchars($real_username) . '</a></td class="' . $cls . '">'."\n";
+				
+				
+				// Minor edit
+				echo '<td class="' . $cls . '" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>';
+				
+				// Action taken
+				echo '<td class="' . $cls . '">';
+				// Some of these are sanitized at insert-time. Others follow the newer Enano policy of stripping HTML at runtime.
+				if    ($r['action']=='prot')     echo $lang->get('history_log_protect') . '</td><td class="' . $cls . '">'     . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__REVERSION__' ? $lang->get('history_extra_protection_reversion') : htmlspecialchars($r['edit_summary']) );
+				elseif($r['action']=='unprot')   echo $lang->get('history_log_unprotect') . '</td><td class="' . $cls . '">'   . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__REVERSION__' ? $lang->get('history_extra_protection_reversion') : htmlspecialchars($r['edit_summary']) );
+				elseif($r['action']=='semiprot') echo $lang->get('history_log_semiprotect') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__REVERSION__' ? $lang->get('history_extra_protection_reversion') : htmlspecialchars($r['edit_summary']) );
+				elseif($r['action']=='rename')   echo $lang->get('history_log_rename') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_oldtitle') . ' '.htmlspecialchars($r['edit_summary']);
+				elseif($r['action']=='create')   echo $lang->get('history_log_create') . '</td><td class="' . $cls . '">';
+				elseif($r['action']=='delete')   echo $lang->get('history_log_delete') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' ' . $r['edit_summary'];
+				elseif($r['action']=='reupload') echo $lang->get('history_log_uploadnew') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__ROLLBACK__' ? $lang->get('history_extra_upload_reversion') : htmlspecialchars($r['edit_summary']) );
+				elseif($r['action']=='votereset')echo $lang->get('history_log_votereset') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_numvotes') . ' ' . $r['edit_summary'];
+				echo '</td>';
+				
+				// Actions!
+				echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">' . $lang->get('history_action_contrib') . '</a></td>';
+				echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'do=rollback&amp;id=' . $r['log_id']) . '" onclick="ajaxRollback(\'' . $r['log_id'] . '\'); return false;">' . $lang->get('history_action_revert') . '</a></td>';
+				
+				echo '</tr>';
+			}
+			echo '</table></div>';
+		}
+		$db->free_result();
+		$ret = ob_get_contents();
+		ob_end_clean();
+		return $ret;
+	}
+	
+	/**
+ 	* Rolls back a logged action
+ 	* @param $id the time ID, a.k.a. the primary key in the logs table
+ 	* @return string
+ 	*/
+ 	
+	public static function rollback($id)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		
+		// placeholder
+		return 'PageUtils->rollback() is deprecated - use PageProcessor instead.';
+	}
+	
+	/**
+ 	* Posts a comment.
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $name the name of the person posting, defaults to current username/IP
+ 	* @param $subject the subject line of the comment
+ 	* @param $text the comment text
+ 	* @return string javascript code
+ 	*/
+ 	
+	public static function addcomment($page_id, $namespace, $name, $subject, $text, $captcha_code = false, $captcha_id = false)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		$_ob = '';
+		if(!$session->get_permissions('post_comments'))
+			return 'Access denied';
+		if(getConfig('comments_need_login') == '2' && !$session->user_logged_in) _die('Access denied to post comments: you need to be logged in first.');
+		if(getConfig('comments_need_login') == '1' && !$session->user_logged_in)
+		{
+			if(!$captcha_code || !$captcha_id) _die('BUG: PageUtils::addcomment: no CAPTCHA data passed to method');
+			$result = $session->get_captcha($captcha_id);
+			if(strtolower($captcha_code) != strtolower($result)) _die('The confirmation code you entered was incorrect.');
+		}
+		$text = RenderMan::preprocess_text($text);
+		$name = $session->user_logged_in ? RenderMan::preprocess_text($session->username) : RenderMan::preprocess_text($name);
+		$subj = RenderMan::preprocess_text($subject);
+		if(getConfig('approve_comments', '0')=='1') $appr = '0'; else $appr = '1';
+		$q = 'INSERT INTO ' . table_prefix.'comments(page_id,namespace,subject,comment_data,name,user_id,approved,time) VALUES(\'' . $page_id . '\',\'' . $namespace . '\',\'' . $subj . '\',\'' . $text . '\',\'' . $name . '\',' . $session->user_id . ',' . $appr . ','.time().')';
+		$e = $db->sql_query($q);
+		if(!$e) die('alert(unescape(\''.rawurlencode('Error inserting comment data: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'))');
+		else $_ob .= '<div class="info-box">Your comment has been posted.</div>';
+		return PageUtils::comments($page_id, $namespace, false, Array(), $_ob);
+	}
+	
+	/**
+ 	* Generates partly-compiled HTML/Javascript code to be eval'ed by the user's browser to display comments
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $action administrative action to perform, default is false
+ 	* @param $flags additional info for $action, shouldn't be used except when deleting/approving comments, etc.
+ 	* @param $_ob text to prepend to output, used by PageUtils::addcomment
+ 	* @return array
+ 	* @access private
+ 	*/
+ 	
+	public static function comments_raw($page_id, $namespace, $action = false, $flags = Array(), $_ob = '')
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		
+		$pname = $paths->nslist[$namespace] . $page_id;
+		$template->init_vars();
+		
+		ob_start();
+		
+		if($action && $session->get_permissions('mod_comments')) // Nip hacking attempts in the bud
+		{
+			switch($action) {
+			case "delete":
+				if(isset($flags['id']))
+				{
+					$q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND comment_id='.intval($flags['id']).' LIMIT 1;';
+				} else {
+					$n = $db->escape($flags['name']);
+					$s = $db->escape($flags['subj']);
+					$t = $db->escape($flags['text']);
+					$q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\' LIMIT 1;';
+				}
+				$e=$db->sql_query($q);
+				if(!$e) die('alert(unesape(\''.rawurlencode('Error during query: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
+				break;
+			case "approve":
+				if(isset($flags['id']))
+				{
+					$where = 'comment_id='.intval($flags['id']);
+				} else {
+					$n = $db->escape($flags['name']);
+					$s = $db->escape($flags['subj']);
+					$t = $db->escape($flags['text']);
+					$where = 'name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\'';
+				}
+				$q = 'SELECT approved FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND ' . $where . ' LIMIT 1;';
+				$e = $db->sql_query($q);
+				if(!$e) die('alert(unesape(\''.rawurlencode('Error selecting approval status: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
+				$r = $db->fetchrow();
+				$db->free_result();
+				$a = ( $r['approved'] ) ? '0' : '1';
+				$q = 'UPDATE ' . table_prefix.'comments SET approved=' . $a . ' WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND ' . $where . ';';
+				$e=$db->sql_query($q);
+				if(!$e) die('alert(unesape(\''.rawurlencode('Error during query: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
+				if($a=='1') $v = $lang->get('comment_btn_mod_unapprove');
+				else $v = $lang->get('comment_btn_mod_approve');
+				echo 'document.getElementById("mdgApproveLink'.intval($_GET['id']).'").innerHTML="' . $v . '";';
+				break;
+			}
+		}
+		
+		if(!defined('ENANO_TEMPLATE_LOADED'))
+		{
+			$template->load_theme($session->theme, $session->style);
+		}
+		
+		$tpl = $template->makeParser('comment.tpl');
+		
+		$e = $db->sql_query('SELECT * FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND approved=0;');
+		if(!$e) $db->_die('The comment text data could not be selected.');
+		$num_unapp = $db->numrows();
+		$db->free_result();
+		$e = $db->sql_query('SELECT * FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND approved=1;');
+		if(!$e) $db->_die('The comment text data could not be selected.');
+		$num_app = $db->numrows();
+		$db->free_result();
+		$lq = $db->sql_query('SELECT c.comment_id,c.subject,c.name,c.comment_data,c.approved,c.time,c.user_id,c.ip_address,u.user_level,u.email,u.signature,u.user_has_avatar,u.avatar_type
+									FROM ' . table_prefix.'comments AS c
+									LEFT JOIN ' . table_prefix.'users AS u
+										ON c.user_id=u.user_id
+									WHERE page_id=\'' . $page_id . '\'
+									AND namespace=\'' . $namespace . '\' ORDER BY c.time ASC;');
+		if(!$lq) _die('The comment text data could not be selected. '.$db->get_error());
+		$_ob .= '<h3>' . $lang->get('comment_heading') . '</h3>';
+		
+		$n = ( $session->get_permissions('mod_comments')) ? $db->numrows() : $num_app;
+		
+		$subst = array(
+				'num_comments' => $n,
+				'page_type' => $template->namespace_string
+			);
+		
+		$_ob .= '<p>';
+		$_ob .= ( $n == 0 ) ? $lang->get('comment_msg_count_zero', $subst) : ( $n == 1 ? $lang->get('comment_msg_count_one', $subst) : $lang->get('comment_msg_count_plural', $subst) );
+		
+		if ( $session->get_permissions('mod_comments') && $num_unapp > 0 )
+		{
+			$_ob .= ' <span style="color: #D84308">' . $lang->get('comment_msg_count_unapp_mod', array( 'num_unapp' => $num_unapp )) . '</span>';
+		}
+		else if ( !$session->get_permissions('mod_comments') && $num_unapp > 0 )
+		{
+			$ls = ( $num_unapp == 1 ) ? 'comment_msg_count_unapp_one' : 'comment_msg_count_unapp_plural';
+			$_ob .= ' <span>' . $lang->get($ls, array( 'num_unapp' => $num_unapp )) . '</span>';
+		}
+		$_ob .= '</p>';
+		$list = 'list = { ';
+		// _die(htmlspecialchars($ttext));
+		$i = -1;
+		while ( $row = $db->fetchrow($lq) )
+		{
+			$i++;
+			$strings = Array();
+			$bool = Array();
+			if ( $session->get_permissions('mod_comments') || $row['approved'] == COMMENT_APPROVED )
+			{
+				$list .= $i . ' : { \'comment\' : unescape(\''.rawurlencode($row['comment_data']).'\'), \'name\' : unescape(\''.rawurlencode($row['name']).'\'), \'subject\' : unescape(\''.rawurlencode($row['subject']).'\'), }, ';
+				
+				// Comment ID (used in the Javascript apps)
+				$strings['ID'] = (string)$i;
+				
+				// Determine the name, and whether to link to the user page or not
+				$name = '';
+				if($row['user_id'] > 1) $name .= '<a href="'.makeUrlNS('User', sanitize_page_id(' ', '_', $row['name'])).'">';
+				$name .= $row['name'];
+				if($row['user_id'] > 1) $name .= '</a>';
+				$strings['NAME'] = $name; unset($name);
+				
+				// Subject
+				$s = $row['subject'];
+				if(!$row['approved']) $s .= ' <span style="color: #D84308">' . $lang->get('comment_msg_note_unapp') . '</span>';
+				$strings['SUBJECT'] = $s;
+				
+				// Date and time
+				$strings['DATETIME'] = enano_date(ED_DATE | ED_TIME, $row['time']);
+				
+				// User level
+				switch($row['user_level'])
+				{
+					default:
+					case USER_LEVEL_GUEST:
+						$l = $lang->get('user_type_guest');
+						break;
+					case USER_LEVEL_MEMBER:
+					case USER_LEVEL_CHPREF:
+						$l = $lang->get('user_type_member');
+						break;
+					case USER_LEVEL_MOD:
+						$l = $lang->get('user_type_mod');
+						break;
+					case USER_LEVEL_ADMIN:
+						$l = $lang->get('user_type_admin');
+						break;
+				}
+				$strings['USER_LEVEL'] = $l; unset($l);
+				
+				// The actual comment data
+				$strings['DATA'] = RenderMan::render($row['comment_data']);
+				
+				if($session->get_permissions('edit_comments'))
+				{
+					// Edit link
+					$strings['EDIT_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=editcomment&amp;id=' . $row['comment_id']) . '" id="editbtn_' . $i . '">' . $lang->get('comment_btn_edit') . '</a>';
+				
+					// Delete link
+					$strings['DELETE_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=deletecomment&amp;id=' . $row['comment_id']) . '">' . $lang->get('comment_btn_delete') . '</a>';
+				}
+				else
+				{
+					// Edit link
+					$strings['EDIT_LINK'] = '';
+				
+					// Delete link
+					$strings['DELETE_LINK'] = '';
+				}
+				
+				// Send PM link
+				$strings['SEND_PM_LINK'] = ( $session->user_logged_in && $row['user_id'] > 1 ) ? '<a href="'.makeUrlNS('Special', 'PrivateMessages/Compose/To/' . $row['name']) . '">' . $lang->get('comment_btn_send_privmsg') . '</a><br />' : '';
+				
+				// Add Buddy link
+				$strings['ADD_BUDDY_LINK'] = ( $session->user_logged_in && $row['user_id'] > 1 ) ? '<a href="'.makeUrlNS('Special', 'PrivateMessages/FriendList/Add/' . $row['name']) . '">' . $lang->get('comment_btn_add_buddy') . '</a>' : '';
+				
+				// Mod links
+				$applink = '';
+				$applink .= '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=admin&amp;action=approve&amp;id=' . $row['comment_id']) . '" id="mdgApproveLink' . $i . '">';
+				if($row['approved']) $applink .= $lang->get('comment_btn_mod_unapprove');
+				else $applink .= $lang->get('comment_btn_mod_approve');
+				$applink .= '</a>';
+				$strings['MOD_APPROVE_LINK'] = $applink; unset($applink);
+				$strings['MOD_DELETE_LINK'] = '<a href="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=admin&amp;action=delete&amp;id=' . $row['comment_id']) . '">' . $lang->get('comment_btn_mod_delete') . '</a>';
+				$strings['MOD_IP_LINK'] = '<span style="opacity: 0.5; filter: alpha(opacity=50);">' . ( ( empty($row['ip_address']) ) ? $lang->get('comment_btn_mod_ip_missing') : $lang->get('comment_btn_mod_ip_notimplemented') ) . '</span>';
+				
+				// Signature
+				$strings['SIGNATURE'] = '';
+				if($row['signature'] != '') $strings['SIGNATURE'] = RenderMan::render($row['signature']);
+				
+				// Avatar
+				if ( $row['user_has_avatar'] == 1 )
+				{
+					$bool['user_has_avatar'] = true;
+					$strings['AVATAR_ALT'] = $lang->get('usercp_avatar_image_alt', array('username' => $row['name']));
+					$strings['AVATAR_URL'] = make_avatar_url(intval($row['user_id']), $row['avatar_type'], $row['email']);
+					$strings['USERPAGE_LINK'] = makeUrlNS('User', $row['name']);
+				}
+				
+				$bool['auth_mod'] = ($session->get_permissions('mod_comments')) ? true : false;
+				$bool['can_edit'] = ( ( $session->user_logged_in && $row['name'] == $session->username && $session->get_permissions('edit_comments') ) || $session->get_permissions('mod_comments') ) ? true : false;
+				$bool['signature'] = ( $strings['SIGNATURE'] == '' ) ? false : true;
+				
+				// Done processing and compiling, now let's cook it into HTML
+				$tpl->assign_vars($strings);
+				$tpl->assign_bool($bool);
+				$_ob .= $tpl->run();
+			}
+		}
+		if(getConfig('comments_need_login') != '2' || $session->user_logged_in)
+		{
+			if($session->get_permissions('post_comments'))
+			{
+				$_ob .= '<h3>' . $lang->get('comment_postform_title') . '</h3>';
+				$_ob .= $lang->get('comment_postform_blurb');
+				if(getConfig('approve_comments', '0')=='1') $_ob .= ' ' . $lang->get('comment_postform_blurb_unapp');
+				if(getConfig('comments_need_login') == '1' && !$session->user_logged_in)
+				{
+					$_ob .= ' ' . $lang->get('comment_postform_blurb_captcha');
+				}
+				$sn = $session->user_logged_in ? $session->username . '<input name="name" id="mdgScreenName" type="hidden" value="' . $session->username . '" />' : '<input name="name" id="mdgScreenName" type="text" size="35" />';
+				$_ob .= '  <a href="#" id="mdgCommentFormLink" style="display: none;" onclick="document.getElementById(\'mdgCommentForm\').style.display=\'block\';this.style.display=\'none\';return false;">' . $lang->get('comment_postform_blurb_link') . '</a>
+				<div id="mdgCommentForm">
+				<form action="'.makeUrlNS($namespace, $page_id, 'do=comments&amp;sub=postcomment').'" method="post" style="margin-left: 1em">
+				<table border="0">
+				<tr><td>' . $lang->get('comment_postform_field_name') . '</td><td>' . $sn . '</td></tr>
+				<tr><td>' . $lang->get('comment_postform_field_subject') . '</td><td><input name="subj" id="mdgSubject" type="text" size="35" /></td></tr>';
+				if(getConfig('comments_need_login') == '1' && !$session->user_logged_in)
+				{
+					$session->kill_captcha();
+					$captcha = $session->make_captcha();
+					$_ob .= '<tr><td>' . $lang->get('comment_postform_field_captcha_title') . '<br /><small>' . $lang->get('comment_postform_field_captcha_blurb') . '</small></td><td><img src="'.makeUrlNS('Special', 'Captcha/' . $captcha) . '" alt="Visual confirmation" style="cursor: pointer;" onclick="this.src = \''.makeUrlNS("Special", "Captcha/".$captcha).'/\'+Math.floor(Math.random() * 100000);" /><input name="captcha_id" id="mdgCaptchaID" type="hidden" value="' . $captcha . '" /><br />' . $lang->get('comment_postform_field_captcha_label') . ' <input name="captcha_input" id="mdgCaptchaInput" type="text" size="10" /><br /><small><script type="text/javascript">document.write("' . $lang->get('comment_postform_field_captcha_cantread_js') . '");</script><noscript>' . $lang->get('comment_postform_field_captcha_cantread_nojs') . '</noscript></small></td></tr>';
+				}
+				$_ob .= '
+				<tr><td valign="top">' . $lang->get('comment_postform_field_comment') . '</td><td><textarea name="text" id="mdgCommentArea" rows="10" cols="40"></textarea></td></tr>
+				<tr><td colspan="2" style="text-align: center;"><input type="submit" value="' . $lang->get('comment_postform_btn_submit') . '" /></td></tr>
+				</table>
+				</form>
+				</div>';
+			}
+		} else {
+			// FIXME: l10n
+			$_ob .= '<h3>' . $lang->get('comment_postform_title') . '</h3><p>You need to be logged in to post comments. <a href="'.makeUrlNS('Special', 'Login/' . $pname . '%2523comments').'">Log in</a></p>';
+		}
+		$list .= '};';
+		echo 'document.getElementById(\'ajaxEditContainer\').innerHTML = unescape(\''. rawurlencode($_ob) .'\');
+		' . $list;
+		echo 'Fat.fade_all(); document.getElementById(\'mdgCommentForm\').style.display = \'none\'; document.getElementById(\'mdgCommentFormLink\').style.display="inline";';
+		
+		$ret = ob_get_contents();
+		ob_end_clean();
+		return Array($ret, $_ob);
+		
+	}
+	
+	/**
+ 	* Generates ready-to-execute Javascript code to be eval'ed by the user's browser to display comments
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $action administrative action to perform, default is false
+ 	* @param $flags additional info for $action, shouldn't be used except when deleting/approving comments, etc.
+ 	* @param $_ob text to prepend to output, used by PageUtils::addcomment
+ 	* @return string
+ 	*/
+ 	
+	public static function comments($page_id, $namespace, $action = false, $id = -1, $_ob = '')
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		$r = PageUtils::comments_raw($page_id, $namespace, $action, $id, $_ob);
+		return $r[0];
+	}
+	
+	/**
+ 	* Generates HTML code for comments - used in browser compatibility mode
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $action administrative action to perform, default is false
+ 	* @param $flags additional info for $action, shouldn't be used except when deleting/approving comments, etc.
+ 	* @param $_ob text to prepend to output, used by PageUtils::addcomment
+ 	* @return string
+ 	*/
+	
+	public static function comments_html($page_id, $namespace, $action = false, $id = -1, $_ob = '')
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		$r = PageUtils::comments_raw($page_id, $namespace, $action, $id, $_ob);
+		return $r[1];
+	}
+	
+	/**
+ 	* Updates comment data.
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $subject new subject
+ 	* @param $text new text
+ 	* @param $old_subject the old subject, unprocessed and identical to the value in the DB
+ 	* @param $old_text the old text, unprocessed and identical to the value in the DB
+ 	* @param $id the javascript list ID, used internally by the client-side app
+ 	* @return string
+ 	*/
+	
+	public static function savecomment($page_id, $namespace, $subject, $text, $old_subject, $old_text, $id = -1)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		if(!$session->get_permissions('edit_comments'))
+			return 'result="BAD";error="Access denied"';
+		// Avoid SQL injection
+		$old_text    = $db->escape($old_text);
+		$old_subject = $db->escape($old_subject);
+		// Safety check - username/login
+		if(!$session->get_permissions('mod_comments')) // allow mods to edit comments
+		{
+			if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
+			$q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_data=\'' . $old_text . '\' AND subject=\'' . $old_subject . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
+			$s = $db->sql_query($q);
+			if(!$s) _die('SQL error during safety check: '.$db->get_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
+			$r = $db->fetchrow($s);
+			$db->free_result();
+			if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
+		}
+		$s = RenderMan::preprocess_text($subject);
+		$t = RenderMan::preprocess_text($text);
+		$sql  = 'UPDATE ' . table_prefix.'comments SET subject=\'' . $s . '\',comment_data=\'' . $t . '\' WHERE comment_data=\'' . $old_text . '\' AND subject=\'' . $old_subject . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
+		$result = $db->sql_query($sql);
+		if($result)
+		{
+			return 'result="GOOD";
+											list[' . $id . '][\'subject\'] = unescape(\''.str_replace('%5Cn', '%0A', rawurlencode(str_replace('{{EnAnO:Newline}}', '\\n', stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $s))))).'\');
+											list[' . $id . '][\'comment\'] = unescape(\''.str_replace('%5Cn', '%0A', rawurlencode(str_replace('{{EnAnO:Newline}}', '\\n', stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $t))))).'\'); id = ' . $id . ';
+			s = unescape(\''.rawurlencode($s).'\');
+			t = unescape(\''.str_replace('%5Cn', '<br \\/>', rawurlencode(RenderMan::render(str_replace('{{EnAnO:Newline}}', "\n", stripslashes(str_replace('\\n', '{{EnAnO:Newline}}', $t)))))).'\');';
+		}
+		else
+		{
+			return 'result="BAD"; error=unescape("'.rawurlencode('Enano encountered a problem whilst saving the comment.
+			Performed SQL:
+			' . $sql . '
+		
+			Error returned by MySQL: '.$db->get_error()).'");';
+		}
+	}
+	
+	/**
+ 	* Updates comment data using the comment_id column instead of the old, messy way
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $subject new subject
+ 	* @param $text new text
+ 	* @param $id the comment ID (primary key in enano_comments table)
+ 	* @return string
+ 	*/
+	
+	public static function savecomment_neater($page_id, $namespace, $subject, $text, $id)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		if(!is_int($id)) die('PageUtils::savecomment: $id is not an integer, aborting for safety');
+		if(!$session->get_permissions('edit_comments'))
+			return 'Access denied';
+		// Safety check - username/login
+		if(!$session->get_permissions('mod_comments')) // allow mods to edit comments
+		{
+			if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
+			$q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
+			$s = $db->sql_query($q);
+			if(!$s) _die('SQL error during safety check: '.$db->get_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
+			$r = $db->fetchrow($s);
+			if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
+			$db->free_result();
+		}
+		$s = RenderMan::preprocess_text($subject);
+		$t = RenderMan::preprocess_text($text);
+		$sql  = 'UPDATE ' . table_prefix.'comments SET subject=\'' . $s . '\',comment_data=\'' . $t . '\' WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
+		$result = $db->sql_query($sql);
+		if($result)
+		return 'good';
+		else return 'Enano encountered a problem whilst saving the comment.
+		Performed SQL:
+		' . $sql . '
+		
+		Error returned by MySQL: '.$db->get_error();
+	}
+	
+	/**
+ 	* Deletes a comment.
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $name the name the user posted under
+ 	* @param $subj the subject of the comment to be deleted
+ 	* @param $text the text of the comment to be deleted
+ 	* @param $id the javascript list ID, used internally by the client-side app
+ 	* @return string
+ 	*/
+	
+	public static function deletecomment($page_id, $namespace, $name, $subj, $text, $id)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		
+		if(!$session->get_permissions('edit_comments'))
+			return 'alert("Access to delete/edit comments is denied");';
+		
+		if(!preg_match('#^([0-9]+)$#', (string)$id)) die('$_GET[id] is improperly formed.');
+		$n = $db->escape($name);
+		$s = $db->escape($subj);
+		$t = $db->escape($text);
+		
+		// Safety check - username/login
+		if(!$session->get_permissions('mod_comments')) // allows mods to delete comments
+		{
+			if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
+			$q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_data=\'' . $t . '\' AND subject=\'' . $s . '\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
+			$s = $db->sql_query($q);
+			if(!$s) _die('SQL error during safety check: '.$db->get_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
+			$r = $db->fetchrow($s);
+			if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
+			$db->free_result();
+		}
+		$q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND name=\'' . $n . '\' AND subject=\'' . $s . '\' AND comment_data=\'' . $t . '\' LIMIT 1;';
+		$e=$db->sql_query($q);
+		if(!$e) return('alert(unesape(\''.rawurlencode('Error during query: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
+		return('good');
+	}
+	
+	/**
+ 	* Deletes a comment in a cleaner fashion.
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $id the comment ID (primary key)
+ 	* @return string
+ 	*/
+	
+	public static function deletecomment_neater($page_id, $namespace, $id)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		
+		if(!preg_match('#^([0-9]+)$#', (string)$id)) die('$_GET[id] is improperly formed.');
+		
+		if(!$session->get_permissions('edit_comments'))
+			return 'alert("Access to delete/edit comments is denied");';
+		
+		// Safety check - username/login
+		if(!$session->get_permissions('mod_comments')) // allows mods to delete comments
+		{
+			if(!$session->user_logged_in) _die('AJAX comment save safety check failed because you are not logged in. Sometimes this can happen because you are using a browser that does not send cookies as part of AJAX requests.<br /><br />Please log in and try again.');
+			$q = 'SELECT c.name FROM ' . table_prefix.'comments c, ' . table_prefix.'users u WHERE comment_id=' . $id . ' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND u.user_id=c.user_id;';
+			$s = $db->sql_query($q);
+			if(!$s) _die('SQL error during safety check: '.$db->get_error().'<br /><br />Attempted SQL:<br /><pre>'.htmlspecialchars($q).'</pre>');
+			$r = $db->fetchrow($s);
+			if($db->numrows() < 1 || $r['name'] != $session->username) _die('Safety check failed, probably due to a hacking attempt.');
+			$db->free_result();
+		}
+		$q = 'DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\' AND comment_id=' . $id . ' LIMIT 1;';
+		$e=$db->sql_query($q);
+		if(!$e) return('alert(unesape(\''.rawurlencode('Error during query: '.$db->get_error().'\n\nQuery:\n' . $q) . '\'));');
+		return('good');
+	}
+	
+	/**
+ 	* Renames a page.
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $name the new name for the page
+ 	* @return string error string or success message
+ 	*/
+ 	
+	public static function rename($page_id, $namespace, $name)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		
+		$page = new PageProcessor($page_id, $namespace);
+		return $page->rename_page($name);
+	}
+	
+	/**
+ 	* Flushes (clears) the action logs for a given page
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @return string error/success string
+ 	*/
+ 	
+	public static function flushlogs($page_id, $namespace)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		if ( !is_object($lang) && defined('IN_ENANO_INSTALL') )
+		{
+			// This is a special exception for the Enano installer, which doesn't init languages yet.
+			$lang = new Language('eng');
+		}
+		if(!$session->get_permissions('clear_logs') && !defined('IN_ENANO_INSTALL'))
+		{
+			return $lang->get('etc_access_denied');
+		}
+		if ( !$session->sid_super )
+		{
+			return $lang->get('etc_access_denied_need_reauth');
+		}
+		
+		$page_id_db = $db->escape($page_id);
+		$namespace_db = $db->escape($namespace);
+		
+		// If we're flushing a file, also clear all revisions before the current
+		if ( $namespace == 'File' )
+		{
+			$q = $db->sql_query('SELECT file_id FROM ' . table_prefix . "files WHERE page_id='$page_id_db' ORDER BY time_id DESC;");
+			if ( !$q )
+				$db->_die();
+			// discard first row (current revision)
+			$db->fetchrow();
+			$id_list = array();
+			while ( $row = $db->fetchrow() )
+				$id_list[] = $row['file_id'];
+			
+			require_once(ENANO_ROOT . '/includes/namespaces/file.php');
+			
+			// clear out each file
+			foreach ( $id_list as $id )
+				Namespace_File::delete_file($id);
+		}
+		
+		$q = $db->sql_query('DELETE FROM ' . table_prefix . "logs WHERE page_id='$page_id_db' AND namespace='$namespace';");
+		if ( !$q )
+			$db->_die('The log entries could not be deleted.');
+		
+		// If the page exists, make a backup of it in case it gets spammed/vandalized
+		// If not, the admin's probably deleting a trash page
+		if ( isPage($paths->get_pathskey($page_id, $namespace)) )
+		{
+			$q = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix . "page_text WHERE page_id='$page_id_db' AND namespace='$namespace_db';");
+			if ( !$q )
+				$db->_die('The current page text could not be selected; as a result, creating the backup of the page failed. Please make a backup copy of the page by clicking Edit this page and then clicking Save Changes.');
+			$row = $db->fetchrow();
+			$db->free_result();
+			$minor_edit = ( ENANO_DBLAYER == 'MYSQL' ) ? 'false' : '0';
+			$username = $db->escape($session->username);
+			$q = 'INSERT INTO ' . table_prefix . "logs ( log_type, action, time_id, date_string, page_id, namespace, page_text, char_tag, author, author_uid, edit_summary, minor_edit ) VALUES\n"
+ 				. "  ('page', 'edit', " . time() . ", 'DEPRECATED', '$page_id', '$namespace', '" . $db->escape($row['page_text']) . "', '', '{$username}', $session->user_id, '" . $lang->get('page_flushlogs_backup_summary') . "', $minor_edit);";
+			if ( !$db->sql_query($q) )
+				$db->_die('The history (log) entry could not be inserted into the logs table.');
+		}
+		
+		return $lang->get('ajax_clearlogs_success');
+	}
+	
+	/**
+ 	* Deletes a page.
+ 	* @param string $page_id the condemned page ID
+ 	* @param string $namespace the condemned namespace
+ 	* @param string The reason for deleting the page in question
+ 	* @return string
+ 	*/
+ 	
+	public static function deletepage($page_id, $namespace, $reason)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		global $cache;
+		$perms = $session->fetch_page_acl($page_id, $namespace);
+		$x = trim($reason);
+		if ( empty($x) )
+		{
+			return $lang->get('ajax_delete_need_reason');
+		}
+		if(!$perms->get_permissions('delete_page')) return('Administrative privileges are required to delete pages, you loser.');
+		
+		if ( !$session->sid_super )
+		{
+			return $lang->get('etc_access_denied_need_reauth');
+		}
+		
+		$e = $db->sql_query('INSERT INTO ' . table_prefix.'logs(time_id,date_string,log_type,action,page_id,namespace,author,author_uid,edit_summary) VALUES('.time().', \''.enano_date(ED_DATE | ED_TIME).'\', \'page\', \'delete\', \'' . $page_id . '\', \'' . $namespace . '\', \'' . $session->username . '\', ' . $session->user_id . ', \'' . $db->escape(htmlspecialchars($reason)) . '\')');
+		if(!$e) $db->_die('The page log entry could not be inserted.');
+		$e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
+		if(!$e) $db->_die('The page categorization entries could not be deleted.');
+		$e = $db->sql_query('DELETE FROM ' . table_prefix.'comments WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
+		if(!$e) $db->_die('The page comments could not be deleted.');
+		$e = $db->sql_query('DELETE FROM ' . table_prefix.'page_text WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
+		if(!$e) $db->_die('The page text entry could not be deleted.');
+		$e = $db->sql_query('DELETE FROM ' . table_prefix.'pages WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'');
+		if(!$e) $db->_die('The page entry could not be deleted.');
+		if ( $namespace == 'File' )
+		{
+			$e = $db->sql_query('DELETE FROM ' . table_prefix.'files WHERE page_id=\'' . $page_id . '\'');
+			if(!$e) $db->_die('The file entry could not be deleted.');
+		}
+		$cache->purge('page_meta');
+		return $lang->get('ajax_delete_success');
+	}
+	
+	/**
+ 	* Deletes files associated with a File page.
+ 	* @param string Page ID
+ 	*/
+	
+	public static function delete_page_files($page_id)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		
+		$q = $db->sql_query('SELECT file_id, filename, file_key, time_id, file_extension FROM ' . table_prefix . "files WHERE page_id = '{$db->escape($page_id)}';");
+		if ( !$q )
+			$db->_die();
+		
+		while ( $row = $db->fetchrow() )
+		{
+			// wipe original file
+			foreach ( array(
+					ENANO_ROOT . "/files/{$row['file_key']}_{$row['time_id']}{$row['file_extension']}",
+					ENANO_ROOT . "/files/{$row['file_key']}{$row['file_extension']}"
+				) as $orig_file )
+			{
+				if ( file_exists($orig_file) )
+					@unlink($orig_file);
+			}
+			
+			// wipe cached files
+			if ( $dr = @opendir(ENANO_ROOT . '/cache/') )
+			{
+				// lol404.jpg-1217958283-200x320.jpg
+				while ( $dh = @readdir($dr) )
+				{
+					$regexp = ':^' . preg_quote("{$row['filename']}-{$row['time_id']}-") . '[0-9]+x[0-9]+\.' . ltrim($row['file_extension'], '.') . '$:';
+					if ( preg_match($regexp, $dh) )
+					{
+						@unlink(ENANO_ROOT . "/cache/$dh");
+					}
+				}
+				@closedir($dr);
+			}
+		}
+		
+		$q = $db->sql_query('DELETE FROM ' . table_prefix . "files WHERE page_id = '{$db->escape($page_id)}';");
+		if ( !$q )
+			$db->die();
+		
+		return true;
+	}
+	
+	/**
+ 	* Increments the deletion votes for a page by 1, and adds the current username/IP to the list of users that have voted for the page to prevent dual-voting
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @return string
+ 	*/
+ 	
+	public static function delvote($page_id, $namespace)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		global $cache;
+		
+		if ( !$session->get_permissions('vote_delete') )
+		{
+			return $lang->get('etc_access_denied');
+		}
+		
+		if ( $namespace == 'Admin' || $namespace == 'Special' || $namespace == 'System' )
+		{
+			return 'Special pages and system messages can\'t be voted for deletion.';
+		}
+		
+		$pname = $paths->nslist[$namespace] . sanitize_page_id($page_id);
+		
+		if ( !isPage($pname) )
+		{
+			return 'The page does not exist.';
+		}
+		
+		$ns = namespace_factory($page_id, $namespace);
+		$cdata = $ns->get_cdata();
+		
+		$cv  =& $cdata['delvotes'];
+		$ips =& $cdata['delvote_ips'];
+		
+		if ( empty($ips) )
+		{
+			$ips = array(
+				'ip' => array(),
+				'u' => array()
+				);
+		}
+		else
+		{
+			$ips = @unserialize($ips);
+			if ( !$ips )
+			{
+				$ips = array(
+				'ip' => array(),
+				'u' => array()
+				);
+			}
+		}
+		
+		if ( in_array($session->username, $ips['u']) || in_array($_SERVER['REMOTE_ADDR'], $ips['ip']) )
+		{
+			return $lang->get('ajax_delvote_already_voted');
+		}
+		
+		$ips['u'][] = $session->username;
+		$ips['ip'][] = $_SERVER['REMOTE_ADDR'];
+		$ips = $db->escape( serialize($ips) );
+		
+		$cv++;
+		
+		$q = 'UPDATE ' . table_prefix.'pages SET delvotes=' . $cv . ',delvote_ips=\'' . $ips . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
+		$w = $db->sql_query($q);
+		if ( !$w )
+			$db->_die();
+		
+		// all done, flush page cache to mark it up
+		$cache->purge('page_meta');
+		
+		return $lang->get('ajax_delvote_success');
+	}
+	
+	/**
+ 	* Resets the number of votes against a page to 0.
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @return string
+ 	*/
+	
+	public static function resetdelvotes($page_id, $namespace)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		global $cache;
+		
+		if ( !$session->get_permissions('vote_reset') )
+		{
+			return $lang->get('etc_access_denied');
+		}
+		
+		$page_id = $db->escape($page_id);
+		$namespace = $db->escape($namespace);
+		
+		// pull existing info
+		$q = $db->sql_query('SELECT delvotes, delvote_ips FROM ' . table_prefix . "pages WHERE urlname = '$page_id' AND namespace = '$namespace'");
+		if ( !$q )
+			$db->_die();
+		if ( $db->numrows() < 1 )
+			return $lang->get('page_err_page_not_exist');
+		
+		list($delvotes, $delvote_ips) = $db->fetchrow_num();
+		$db->free_result();
+		$delvote_ips = $db->escape($delvote_ips);
+		$username = $db->escape($session->username);
+		
+		// log action
+		$time = time();
+		$q = $db->sql_query('INSERT INTO ' . table_prefix . "logs (time_id, log_type, action, edit_summary, page_text, author, author_uid, page_id, namespace) VALUES\n"
+											. "  ( $time, 'page', 'votereset', '$delvotes', '$delvote_ips', '$username', $session->user_id, '$page_id', '$namespace' )");
+		if ( !$q )
+			$db->_die();
+		
+		// reset votes
+		$empty_vote_record = $db->escape(serialize(array('ip'=>array(),'u'=>array())));
+		$q = 'UPDATE ' . table_prefix.'pages SET delvotes=0,delvote_ips=\'' . $empty_vote_record . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\'';
+		$e = $db->sql_query($q);
+		if ( !$e )
+		{
+			$db->_die('The number of delete votes was not reset.');
+		}
+		else
+		{
+			$cache->purge('page_meta');
+			return $lang->get('ajax_delvote_reset_success');
+		}
+	}
+	
+	/**
+ 	* Gets a list of styles for a given theme name. As of Banshee, this returns JSON.
+ 	* @param $id the name of the directory for the theme
+ 	* @return string JSON string with an array containing a list of themes
+ 	*/
+ 	
+	public static function getstyles()
+	{
+		
+		if ( !preg_match('/^([a-z0-9_-]+)$/', $_GET['id']) )
+			return enano_json_encode(false);
+		
+		$dir = './themes/' . $_GET['id'] . '/css/';
+		$list = Array();
+		// Open a known directory, and proceed to read its contents
+		if (is_dir($dir)) {
+			if ($dh = opendir($dir)) {
+				while (($file = readdir($dh)) !== false) {
+					if ( preg_match('#^(.*?)\.css$#is', $file) && $file != '_printable.css' ) // _printable.css should be included with every theme
+					{                                                                         // it should be a copy of the original style, but
+																																										// mostly black and white
+																																										// Note to self: document this
+						$list[] = substr($file, 0, strlen($file)-4);
+					}
+				}
+				closedir($dh);
+			}
+		}
+		else
+		{
+			return(enano_json_encode(Array('mode' => 'error', 'error' => $dir.' is not a dir')));
+		}
+		
+		return enano_json_encode($list);
+	}
+	
+	/**
+ 	* Assembles a Javascript app with category information
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @return string Javascript code
+ 	*/
+ 	
+	public static function catedit($page_id, $namespace)
+	{
+		$d = PageUtils::catedit_raw($page_id, $namespace);
+		return $d[0] . ' /* BEGIN CONTENT */ document.getElementById("ajaxEditContainer").innerHTML = unescape(\''.rawurlencode($d[1]).'\');';
+	}
+	
+	/**
+ 	* Does the actual HTML/javascript generation for cat editing, but returns an array
+ 	* @access private
+ 	*/
+ 	
+	public static function catedit_raw($page_id, $namespace)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		
+		ob_start();
+		$_ob = '';
+		$e = $db->sql_query('SELECT category_id FROM ' . table_prefix.'categories WHERE page_id=\'' . $paths->page_id . '\' AND namespace=\'' . $paths->namespace . '\'');
+		if(!$e) jsdie('Error selecting category information for current page: '.$db->get_error());
+		$cat_current = Array();
+		while($r = $db->fetchrow())
+		{
+			$cat_current[] = $r;
+		}
+		$db->free_result();
+		
+		$cat_all = array();
+		$q = $db->sql_query('SELECT * FROM ' . table_prefix . 'pages WHERE namespace = \'Category\';');
+		if ( !$q )
+			$db->_die();
+		
+		while ( $row = $db->fetchrow() )
+		{
+			$cat_all[] = Namespace_Default::bake_cdata($row);
+		}
+		
+		// Make $cat_all an associative array, like $paths->pages
+		$sz = sizeof($cat_all);
+		for($i=0;$i<$sz;$i++)
+		{
+			$cat_all[$cat_all[$i]['urlname_nons']] = $cat_all[$i];
+		}
+		// Now, the "zipper" function - join the list of categories with the list of cats that this page is a part of
+		$cat_info = $cat_all;
+		for($i=0;$i<sizeof($cat_current);$i++)
+		{
+			$un = $cat_current[$i]['category_id'];
+			$cat_info[$un]['member'] = true;
+		}
+		// Now copy the information we just set into the numerically named keys
+		for($i=0;$i<sizeof($cat_info)/2;$i++)
+		{
+			$un = $cat_info[$i]['urlname_nons'];
+			$cat_info[$i] = $cat_info[$un];
+		}
+		
+		echo 'catlist = new Array();'; // Initialize the client-side category list
+		$_ob .= '<h3>' . $lang->get('catedit_title') . '</h3>
+ 						<form name="mdgCatForm" action="'.makeUrlNS($namespace, $page_id, 'do=catedit').'" method="post">';
+		if ( sizeof($cat_info) < 1 )
+		{
+			$_ob .= '<p>' . $lang->get('catedit_no_categories') . '</p>';
+		}
+		for ( $i = 0; $i < sizeof($cat_info) / 2; $i++ )
+		{
+			// Protection code added 1/3/07
+			// Updated 3/4/07
+			$is_prot = false;
+			$perms = $session->fetch_page_acl($cat_info[$i]['urlname_nons'], 'Category');
+			if ( !$session->get_permissions('edit_cat') || !$perms->get_permissions('edit_cat') ||
+ 				( $cat_info[$i]['really_protected'] && !$perms->get_permissions('even_when_protected') ) )
+ 				$is_prot = true;
+			$prot = ( $is_prot ) ? ' disabled="disabled" ' : '';
+			$prottext = ( $is_prot ) ? ' <img alt="(protected)" width="16" height="16" src="'.scriptPath.'/images/lock16.png" />' : '';
+			echo 'catlist[' . $i . '] = \'' . $cat_info[$i]['urlname_nons'] . '\';';
+			$_ob .= '<span class="catCheck"><input ' . $prot . ' name="' . $cat_info[$i]['urlname_nons'] . '" id="mdgCat_' . $cat_info[$i]['urlname_nons'] . '" type="checkbox"';
+			if(isset($cat_info[$i]['member'])) $_ob .= ' checked="checked"';
+			$_ob .= '/>  <label for="mdgCat_' . $cat_info[$i]['urlname_nons'] . '">' . $cat_info[$i]['name'].$prottext.'</label></span><br />';
+		}
+		
+		$disabled = ( sizeof($cat_info) < 1 ) ? 'disabled="disabled"' : '';
+			
+		$_ob .= '<div style="border-top: 1px solid #CCC; padding-top: 5px; margin-top: 10px;"><input name="__enanoSaveButton" ' . $disabled . ' style="font-weight: bold;" type="submit" onclick="ajaxCatSave(); return false;" value="' . $lang->get('etc_save_changes') . '" /> <input name="__enanoCatCancel" type="submit" onclick="ajaxReset(); return false;" value="' . $lang->get('etc_cancel') . '" /></div></form>';
+		
+		$cont = ob_get_contents();
+		ob_end_clean();
+		return Array($cont, $_ob);
+	}
+	
+	/**
+ 	* Saves category information
+ 	* WARNING: If $which_cats is empty, all the category information for the selected page will be nuked!
+ 	* @param $page_id string the page ID
+ 	* @param $namespace string the namespace
+ 	* @param $which_cats array associative array of categories to put the page in
+ 	* @return string "GOOD" on success, error string on failure
+ 	*/
+	
+	public static function catsave($page_id, $namespace, $which_cats)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		if(!$session->get_permissions('edit_cat')) return('Insufficient privileges to change category information');
+		
+		$page_perms = $session->fetch_page_acl($page_id, $namespace);
+		$ns = namespace_factory($page_id, $namespace);
+		$page_data = $ns->get_cdata();
+		
+		$cat_all = array();
+		$q = $db->sql_query('SELECT * FROM ' . table_prefix . 'pages WHERE namespace = \'Category\';');
+		if ( !$q )
+			$db->_die();
+		
+		while ( $row = $db->fetchrow() )
+		{
+			$cat_all[] = Namespace_Default::bake_cdata($row);
+		}
+		
+		// Make $cat_all an associative array, like $paths->pages
+		$sz = sizeof($cat_all);
+		for($i=0;$i<$sz;$i++)
+		{
+			$cat_all[$cat_all[$i]['urlname_nons']] = $cat_all[$i];
+		}
+		
+		$rowlist = Array();
+		
+		for($i=0;$i<sizeof($cat_all)/2;$i++)
+		{
+			$auth = true;
+			$perms = $session->fetch_page_acl($cat_all[$i]['urlname_nons'], 'Category');
+			if ( !$session->get_permissions('edit_cat') || !$perms->get_permissions('edit_cat') ||
+ 				( $cat_all[$i]['really_protected'] && !$perms->get_permissions('even_when_protected') ) ||
+ 				( !$page_perms->get_permissions('even_when_protected') && $page_data['protected'] == '1' ) )
+ 				$auth = false;
+			if(!$auth)
+			{
+				// Find out if the page is currently in the category
+				$q = $db->sql_query('SELECT * FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
+				if(!$q)
+					return 'MySQL error: ' . $db->get_error();
+				if($db->numrows() > 0)
+				{
+					$auth = true;
+					$which_cats[$cat_all[$i]['urlname_nons']] = true; // Force the category to stay in its current state
+				}
+				$db->free_result();
+			}
+			if(isset($which_cats[$cat_all[$i]['urlname_nons']]) && $which_cats[$cat_all[$i]['urlname_nons']] == true /* for clarity ;-) */ && $auth ) $rowlist[] = '(\'' . $page_id . '\', \'' . $namespace . '\', \'' . $cat_all[$i]['urlname_nons'] . '\')';
+		}
+		if(sizeof($rowlist) > 0)
+		{
+			$val = implode(',', $rowlist);
+			$q = 'INSERT INTO ' . table_prefix.'categories(page_id,namespace,category_id) VALUES' . $val . ';';
+			$e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
+			if(!$e) $db->_die('The old category data could not be deleted.');
+			$e = $db->sql_query($q);
+			if(!$e) $db->_die('The new category data could not be inserted.');
+			return('GOOD');
+		}
+		else
+		{
+			$e = $db->sql_query('DELETE FROM ' . table_prefix.'categories WHERE page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
+			if(!$e) $db->_die('The old category data could not be deleted.');
+			return('GOOD');
+		}
+	}
+	
+	/**
+ 	* Sets the wiki mode level for a page.
+ 	* @param $page_id string the page ID
+ 	* @param $namespace string the namespace
+ 	* @param $level int 0 for off, 1 for on, 2 for use global setting
+ 	* @return string "GOOD" on success, error string on failure
+ 	*/
+	
+	public static function setwikimode($page_id, $namespace, $level)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $cache;
+		
+		if(!$session->get_permissions('set_wiki_mode')) return('Insufficient access rights');
+		if ( !isset($level) || ( isset($level) && !preg_match('#^([0-2]){1}$#', (string)$level) ) )
+		{
+			return('Invalid mode string');
+		}
+		$q = $db->sql_query('UPDATE ' . table_prefix.'pages SET wiki_mode=' . $level . ' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
+		if ( !$q )
+		{
+			return('Error during update query: '.$db->get_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
+		}
+		
+		$cache->purge('page_meta');
+		return('GOOD');
+	}
+	
+	/**
+ 	* Sets the access password for a page.
+ 	* @param $page_id string the page ID
+ 	* @param $namespace string the namespace
+ 	* @param $pass string the SHA1 hash of the password - if the password doesn't match the regex ^([0-9a-f]*){40,40}$ it will be sha1'ed
+ 	* @return string
+ 	*/
+	
+	public static function setpass($page_id, $namespace, $pass)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang, $cache;
+		// Determine permissions
+		$ns = namespace_factory($page_id, $namespace);
+		$cdata = $ns->get_cdata();
+		if ( $cdata['password'] != '' )
+			$a = $session->get_permissions('password_reset');
+		else
+			$a = $session->get_permissions('password_set');
+		if ( !$a )
+			return $lang->get('etc_access_denied');
+		if ( !isset($pass) )
+			return('Password was not set on URL');
+		$p = $pass;
+		if ( !preg_match('#([0-9a-f]){40,40}#', $p) )
+		{
+			$p = sha1($p);
+		}
+		if ( $p == 'da39a3ee5e6b4b0d3255bfef95601890afd80709' )
+			// sha1('') = da39a3ee5e6b4b0d3255bfef95601890afd80709
+			$p = '';
+		$e = $db->sql_query('UPDATE ' . table_prefix.'pages SET password=\'' . $p . '\' WHERE urlname=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';');
+		if ( !$e )
+		{
+			die('PageUtils::setpass(): Error during update query: '.$db->get_error()."\n\nSQL Backtrace:\n".$db->sql_backtrace());
+		}
+		// Is the new password blank?
+		if ( $p == '' )
+		{
+			return $lang->get('ajax_password_disable_success');
+		}
+		else
+		{
+			return $lang->get('ajax_password_success');
+		}
+	}
+	
+	/**
+ 	* Generates some preview HTML
+ 	* @param $text string the wikitext to use
+ 	* @return string
+ 	*/
+ 	
+	public static function genPreview($text)
+	{
+		global $lang;
+		$ret = '<div class="info-box">' . $lang->get('editor_preview_blurb') . '</div><div style="background-color: #F8F8F8; padding: 10px; border: 1px dashed #406080; max-height: 250px; overflow: auto; margin: 10px 0;">';
+		$text = RenderMan::render(RenderMan::preprocess_text($text, false, false));
+		ob_start();
+		eval('?>' . $text);
+		$text = ob_get_contents();
+		ob_end_clean();
+		$ret .= $text;
+		$ret .= '</div>';
+		return $ret;
+	}
+	
+	/**
+ 	* Makes a scrollable box
+ 	* @param string $text the inner HTML
+ 	* @param int $height Optional - the maximum height. Defaults to 250.
+ 	* @return string
+ 	*/
+ 	
+	public static function scrollBox($text, $height = 250)
+	{
+		return '<div style="background-color: #F8F8F8; padding: 10px; border: 1px dashed #406080; max-height: '.(string)intval($height).'px; overflow: auto; margin: 1em 0 1em 1em;">' . $text . '</div>';
+	}
+	
+	/**
+ 	* Generates a diff summary between two page revisions.
+ 	* @param $page_id the page ID
+ 	* @param $namespace the namespace
+ 	* @param $id1 the time ID of the first revision
+ 	* @param $id2 the time ID of the second revision
+ 	* @return string XHTML-formatted diff
+ 	*/
+ 	
+	public static function pagediff($page_id, $namespace, $id1, $id2)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		
+		if ( !$session->get_permissions('history_view') )
+			return $lang->get('etc_access_denied');
+		
+		if(!preg_match('#^([0-9]+)$#', (string)$id1) ||
+ 			!preg_match('#^([0-9]+)$#', (string)$id2  )) return 'SQL injection attempt';
+		// OK we made it through security
+		// Safest way to make sure we don't end up with the revisions in wrong columns is to make 2 queries
+		if ( !$q1 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE log_id = ' . $id1 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error();
+		if ( !$q2 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE log_id = ' . $id2 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error();
+		$row1 = $db->fetchrow($q1);
+		$db->free_result($q1);
+		$row2 = $db->fetchrow($q2);
+		$db->free_result($q2);
+		if(sizeof($row1) < 1 || sizeof($row2) < 2)
+		{
+			if ( !$q1 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id = ' . $id1 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error();
+			if ( !$q2 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id = ' . $id2 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error();
+			$row1 = $db->fetchrow($q1);
+			$db->free_result($q1);
+			$row2 = $db->fetchrow($q2);
+			$db->free_result($q2);
+			if(sizeof($row1) < 1 || sizeof($row2) < 2)
+				return 'Couldn\'t find any rows that matched the query. The time ID probably doesn\'t exist in the logs table.';
+		}
+		$text1 = $row1['page_text'];
+		$text2 = $row2['page_text'];
+		$time1 = enano_date(ED_DATE | ED_TIME, $row1['time_id']);
+		$time2 = enano_date(ED_DATE | ED_TIME, $row2['time_id']);
+		$_ob = "
+		<p>" . $lang->get('history_lbl_comparingrevisions') . " {$time1} &rarr; {$time2}</p>
+		";
+		// Free some memory
+		unset($row1, $row2, $q1, $q2);
+		
+		$_ob .= RenderMan::diff($text1, $text2);
+		return $_ob;
+	}
+	
+	/**
+ 	* Gets ACL information about the selected page for target type X and target ID Y.
+ 	* @param array $parms What to select. This is an array purely for JSON compatibility. It should be an associative array with keys target_type and target_id.
+ 	* @return array
+ 	*/
+ 	
+	public static function acl_editor($parms = Array())
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		
+		if(!$session->get_permissions('edit_acl') && ( $session->user_level < USER_LEVEL_ADMIN || !defined('ACL_ALWAYS_ALLOW_ADMIN_EDIT_ACL')) )
+		{
+			return Array(
+				'mode' => 'error',
+				'error' => $lang->get('acl_err_access_denied')
+				);
+		}
+		if ( !$session->sid_super )
+		{
+			return Array(
+				'mode' => 'error',
+				'error' => $lang->get('etc_access_denied_need_reauth')
+				);
+		}
+		$parms['page_id'] = ( isset($parms['page_id']) ) ? $parms['page_id'] : false;
+		$parms['namespace'] = ( isset($parms['namespace']) ) ? $parms['namespace'] : false;
+		$page_id =& $parms['page_id'];
+		$namespace =& $parms['namespace'];
+		$page_where_clause      = ( empty($page_id) || empty($namespace) ) ? 'AND a.page_id IS NULL AND a.namespace IS NULL' : 'AND a.page_id=\'' . $db->escape($page_id) . '\' AND a.namespace=\'' . $db->escape($namespace) . '\'';
+		$page_where_clause_lite = ( empty($page_id) || empty($namespace) ) ? 'AND page_id IS NULL AND namespace IS NULL' : 'AND page_id=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\'';
+		//die(print_r($page_id,true));
+		$template->load_theme();
+		// $perms_obj = $session->fetch_page_acl($page_id, $namespace);
+		$perms_obj =& $session;
+		$return = Array();
+		if ( !file_exists(ENANO_ROOT . '/themes/' . $session->theme . '/acledit.tpl') )
+		{
+			return Array(
+				'mode' => 'error',
+				'error' => $lang->get('acl_err_missing_template'),
+			);
+		}
+		$return['template'] = $template->extract_vars('acledit.tpl');
+		$return['page_id'] = $page_id;
+		$return['namespace'] = $namespace;
+		if(isset($parms['mode']))
+		{
+			switch($parms['mode'])
+			{
+				case 'listgroups':
+					$return['groups'] = Array();
+					$q = $db->sql_query('SELECT group_id,group_name FROM ' . table_prefix.'groups ORDER BY group_name ASC;');
+					while($row = $db->fetchrow())
+					{
+						$return['groups'][] = Array(
+							'id' => $row['group_id'],
+							'name' => $row['group_name'],
+							);
+					}
+					$db->free_result();
+					$return['page_groups'] = Array();
+					$q = $db->sql_query('SELECT pg_id,pg_name FROM ' . table_prefix.'page_groups ORDER BY pg_name ASC;');
+					if ( !$q )
+						return Array(
+							'mode' => 'error',
+							'error' => $db->get_error()
+							);
+					while ( $row = $db->fetchrow() )
+					{
+						$return['page_groups'][] = Array(
+								'id' => $row['pg_id'],
+								'name' => $row['pg_name']
+							);
+					}
+					break;
+				case 'seltarget_id':
+					if ( !is_int($parms['target_id']) )
+					{
+						return Array(
+							'mode' => 'error',
+							'error' => 'Expected parameter target_id type int'
+							);
+					}
+					$q = $db->sql_query('SELECT target_id, target_type, page_id, namespace, rules FROM ' . table_prefix . "acl WHERE rule_id = {$parms['target_id']};");
+					if ( !$q )
+						return Array(
+							'mode' => 'error',
+							'error' => $db->get_error()
+							);
+					if ( $db->numrows() < 1 )
+						return Array(
+							'mode' => 'error',
+							'error' => "No rule with ID {$parms['target_id']} found"
+							);
+						$parms = $db->fetchrow();
+						$db->free_result();
+						
+						// regenerate page selection
+						$parms['page_id'] = ( isset($parms['page_id']) ) ? $parms['page_id'] : false;
+						$parms['namespace'] = ( isset($parms['namespace']) ) ? $parms['namespace'] : false;
+						$parms['mode'] = 'seltarget_id';
+						$page_id =& $parms['page_id'];
+						$namespace =& $parms['namespace'];
+						$page_where_clause      = ( empty($page_id) || empty($namespace) ) ? 'AND a.page_id IS NULL AND a.namespace IS NULL' : 'AND a.page_id=\'' . $db->escape($page_id) . '\' AND a.namespace=\'' . $db->escape($namespace) . '\'';
+						$page_where_clause_lite = ( empty($page_id) || empty($namespace) ) ? 'AND page_id IS NULL AND namespace IS NULL' : 'AND page_id=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\'';
+						
+						$return['page_id'] = $parms['page_id'];
+						$return['namespace'] = $parms['namespace'];
+						
+						// From here, let the seltarget handler take over
+				case 'seltarget':
+					$return['mode'] = 'seltarget';
+					$return['acl_types'] = $perms_obj->acl_types;
+					$return['acl_deps'] = $perms_obj->acl_deps;
+					$return['acl_descs'] = $perms_obj->acl_descs;
+					$return['target_type'] = $parms['target_type'];
+					$return['target_id'] = $parms['target_id'];
+					switch($parms['target_type'])
+					{
+						case ACL_TYPE_USER:
+							$user_col = ( $parms['mode'] == 'seltarget_id' ) ? 'user_id' : 'username';
+							$q = $db->sql_query('SELECT a.rules,u.user_id,u.username FROM ' . table_prefix.'users AS u
+									LEFT JOIN ' . table_prefix.'acl AS a
+										ON a.target_id=u.user_id
+									WHERE a.target_type='.ACL_TYPE_USER.'
+										AND u.' . $user_col . ' = \'' . $db->escape($parms['target_id']) . '\'
+										' . $page_where_clause . ';');
+							if(!$q)
+								return(Array('mode'=>'error','error'=>$db->get_error()));
+							if($db->numrows() < 1)
+							{
+								$return['type'] = 'new';
+								$q = $db->sql_query('SELECT user_id,username FROM ' . table_prefix.'users WHERE username=\'' . $db->escape($parms['target_id']) . '\';');
+								if(!$q)
+									return(Array('mode'=>'error','error'=>$db->get_error()));
+								if($db->numrows() < 1)
+									return Array('mode'=>'error','error'=>$lang->get('acl_err_user_not_found'),'debug' => $db->sql_backtrace());
+								$row = $db->fetchrow();
+								$return['target_name'] = $row['username'];
+								$return['target_id'] = intval($row['user_id']);
+								$return['current_perms'] = array();
+							}
+							else
+							{
+								$return['type'] = 'edit';
+								$row = $db->fetchrow();
+								$return['target_name'] = $row['username'];
+								$return['target_id'] = intval($row['user_id']);
+								$return['current_perms'] = $session->string_to_perm($row['rules']);
+							}
+							$db->free_result();
+							// Eliminate types that don't apply to this namespace
+							if ( $namespace && $namespace != '__PageGroup' )
+							{
+								foreach ( $return['current_perms'] AS $i => $perm )
+								{
+									if ( ( $page_id != null && $namespace != null ) && ( !in_array ( $namespace, $session->acl_scope[$i] ) && !in_array('All', $session->acl_scope[$i]) ) )
+									{
+										// echo "// SCOPE CONTROL: eliminating: $i\n";
+										unset($return['current_perms'][$i]);
+										unset($return['acl_types'][$i]);
+										unset($return['acl_descs'][$i]);
+										unset($return['acl_deps'][$i]);
+									}
+								}
+							}
+							break;
+						case ACL_TYPE_GROUP:
+							$q = $db->sql_query('SELECT a.rules,g.group_name,g.group_id FROM ' . table_prefix.'groups AS g
+									LEFT JOIN ' . table_prefix.'acl AS a
+										ON a.target_id=g.group_id
+									WHERE a.target_type='.ACL_TYPE_GROUP.'
+										AND g.group_id=\''.intval($parms['target_id']).'\'
+										' . $page_where_clause . ';');
+							if(!$q)
+								return(Array('mode'=>'error','error'=>$db->get_error()));
+							if($db->numrows() < 1)
+							{
+								$return['type'] = 'new';
+								$q = $db->sql_query('SELECT group_id,group_name FROM ' . table_prefix.'groups WHERE group_id=\''.intval($parms['target_id']).'\';');
+								if(!$q)
+									return(Array('mode'=>'error','error'=>$db->get_error()));
+								if($db->numrows() < 1)
+									return Array('mode'=>'error','error'=>$lang->get('acl_err_bad_group_id'));
+								$row = $db->fetchrow();
+								$return['target_name'] = $row['group_name'];
+								$return['target_id'] = intval($row['group_id']);
+								$return['current_perms'] = array();
+							}
+							else
+							{
+								$return['type'] = 'edit';
+								$row = $db->fetchrow();
+								$return['target_name'] = $row['group_name'];
+								$return['target_id'] = intval($row['group_id']);
+								$return['current_perms'] = $session->string_to_perm($row['rules']);
+							}
+							$db->free_result();
+							// Eliminate types that don't apply to this namespace
+							if ( $namespace && $namespace != '__PageGroup' )
+							{
+								foreach ( $return['current_perms'] AS $i => $perm )
+								{
+									if ( ( $page_id != false && $namespace != false ) && ( !in_array ( $namespace, $session->acl_scope[$i] ) && !in_array('All', $session->acl_scope[$i]) ) )
+									{
+										// echo "// SCOPE CONTROL: eliminating: $i\n"; //; ".print_r($namespace,true).":".print_r($page_id,true)."\n";
+										unset($return['current_perms'][$i]);
+										unset($return['acl_types'][$i]);
+										unset($return['acl_descs'][$i]);
+										unset($return['acl_deps'][$i]);
+									}
+								}
+							}
+							//return Array('mode'=>'debug','text'=>print_r($return, true));
+							break;
+						default:
+							return Array('mode'=>'error','error','Invalid ACL type ID');
+							break;
+					}
+					return $return;
+					break;
+				case 'save_new':
+				case 'save_edit':
+					if ( defined('ENANO_DEMO_MODE') )
+					{
+						return Array('mode'=>'error','error'=>$lang->get('acl_err_demo'));
+					}
+					$q = $db->sql_query('DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).'
+						' . $page_where_clause_lite . ';');
+					if(!$q)
+						return Array('mode'=>'error','error'=>$db->get_error());
+					if ( sizeof ( $parms['perms'] ) < 1 )
+					{
+						// As of 1.1.x, this returns success because the rule length is zero if the user selected "inherit" in all columns
+						return Array(
+							'mode' => 'success',
+							'target_type' => $parms['target_type'],
+							'target_id' => $parms['target_id'],
+							'target_name' => $parms['target_name'],
+							'page_id' => $page_id,
+							'namespace' => $namespace,
+						);
+					}
+					$rules = $session->perm_to_string($parms['perms']);
+					$q = ($page_id && $namespace) ? 'INSERT INTO ' . table_prefix.'acl ( target_type, target_id, page_id, namespace, rules )
+ 																						VALUES( '.intval($parms['target_type']).', '.intval($parms['target_id']).', \'' . $db->escape($page_id) . '\', \'' . $db->escape($namespace) . '\', \'' . $db->escape($rules) . '\' )' :
+																					'INSERT INTO ' . table_prefix.'acl ( target_type, target_id, rules )
+ 																						VALUES( '.intval($parms['target_type']).', '.intval($parms['target_id']).', \'' . $db->escape($rules) . '\' )';
+					if(!$db->sql_query($q)) return Array('mode'=>'error','error'=>$db->get_error());
+					return Array(
+							'mode' => 'success',
+							'target_type' => $parms['target_type'],
+							'target_id' => $parms['target_id'],
+							'target_name' => $parms['target_name'],
+							'page_id' => $page_id,
+							'namespace' => $namespace,
+						);
+					break;
+				case 'delete':
+					if ( defined('ENANO_DEMO_MODE') )
+					{
+						return Array('mode'=>'error','error'=>$lang->get('acl_err_demo'));
+					}
+					$sql = 'DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).'
+						' . $page_where_clause_lite . ';';
+					$q = $db->sql_query($sql);
+					if(!$q)
+						return Array('mode'=>'error','error'=>$db->get_error());
+					return Array(
+							'mode' => 'delete',
+							'target_type' => $parms['target_type'],
+							'target_id' => $parms['target_id'],
+							'target_name' => $parms['target_name'],
+							'page_id' => $page_id,
+							'namespace' => $namespace,
+						);
+					break;
+				case 'list_existing':
+					
+					$return = array(
+							'mode'  => 'list_existing',
+							'key'   => acl_list_draw_key(),
+							'rules' => array()
+						);
+					
+					$acl_columns = 'a.' . implode(', a.', $db->columns_in(table_prefix . 'acl'));
+					$users_columns = 'u.' . implode(', u.', $db->columns_in(table_prefix . 'users'));
+					$groups_columns = 'g.' . implode(', g.', $db->columns_in(table_prefix . 'groups'));
+					$pg_columns = 'p.' . implode(', p.', array('pg_id', 'pg_type', 'pg_name', 'pg_target'));
+					
+					$q = $db->sql_query("SELECT a.rule_id, u.username, g.group_name, a.target_type, a.target_id, a.page_id, a.namespace, a.rules, p.pg_name\n"
+									. "  FROM " . table_prefix . "acl AS a\n"
+									. "  LEFT JOIN " . table_prefix . "users AS u\n"
+									. "    ON ( (a.target_type = " . ACL_TYPE_USER . " AND a.target_id = u.user_id) OR (u.user_id IS NULL) )\n"
+									. "  LEFT JOIN " . table_prefix . "groups AS g\n"
+									. "    ON ( (a.target_type = " . ACL_TYPE_GROUP . " AND a.target_id = g.group_id) OR (g.group_id IS NULL) )\n"
+									. "  LEFT JOIN " . table_prefix . "page_groups as p\n"
+									. "    ON ( (a.namespace = '__PageGroup' AND a.page_id = CAST(p.pg_id AS CHAR)) OR (p.pg_id IS NULL) )\n"
+									. "  WHERE ( a.target_type = " . ACL_TYPE_USER . " OR a.target_type = " . ACL_TYPE_GROUP . " )\n"
+									. "  GROUP BY a.rule_id, $acl_columns, $users_columns, $groups_columns, $pg_columns\n"
+									. "  ORDER BY a.target_type ASC, a.rule_id ASC;"
+								);
+					
+					if ( !$q )
+						$db->_die();
+					
+					while ( $row = $db->fetchrow($q) )
+					{
+						if ( $row['target_type'] == ACL_TYPE_USER && empty($row['username']) )
+						{
+							// This is only done if we have an ACL affecting a user that doesn't exist.
+							// Nice little bit of maintenance to have.
+							if ( !$db->sql_query("DELETE FROM " . table_prefix . "acl WHERE rule_id = {$row['rule_id']};") )
+								$db->_die();
+							continue;
+						}
+						$score = get_acl_rule_score($row['rules']);
+						$deep_limit = ACL_SCALE_MINIMAL_SHADE;
+						// Determine background color of cell by score
+						if ( $score > 5 )
+						{
+							// high score, show in green
+							$color = 2.5 * $score;
+							if ( $color > 255 )
+								$color = 255;
+							$color = round($color);
+							// blend with the colordepth limit
+							$color = $deep_limit + ( ( 0xFF - $deep_limit ) - ( ( $color / 0xFF ) * ( 0xFF - $deep_limit ) ) );
+							$color = dechex($color);
+							$color = "{$color}ff{$color}";
+						}
+						else if ( $score < -5 )
+						{
+							// low score, show in red
+							$color = 0 - $score;
+							$color = 2.5 * $color;
+							if ( $color > 255 )
+								$color = 255;
+							$color = round($color);
+							// blend with the colordepth limit
+							$color = $deep_limit + ( ( 0xFF - $deep_limit ) - ( ( $color / 0xFF ) * ( 0xFF - $deep_limit ) ) );
+							$color = dechex($color);
+							$color = "ff{$color}{$color}";
+						}
+						else
+						{
+							$color = 'efefef';
+						}
+						
+						// Rate rule textually based on its score
+						if ( $score >= 70 )
+							$desc = $lang->get('acl_msg_scale_allow');
+						else if ( $score >= 50 )
+							$desc = $lang->get('acl_msg_scale_mostly_allow');
+						else if ( $score >= 25 )
+							$desc = $lang->get('acl_msg_scale_some_allow');
+						else if ( $score >= -25 )
+							$desc = $lang->get('acl_msg_scale_mixed');
+						else if ( $score <= -70 )
+							$desc = $lang->get('acl_msg_scale_deny');
+						else if ( $score <= -50 )
+							$desc = $lang->get('acl_msg_scale_mostly_deny');
+						else if ( $score <= -25 )
+							$desc = $lang->get('acl_msg_scale_some_deny');
+						
+						// group and user target info
+						$info = '';
+						if ( $row['target_type'] == ACL_TYPE_USER )
+							$info = $lang->get('acl_msg_list_user', array( 'username' => $row['username'] )); // "(User: {$row['username']})";
+						else if ( $row['target_type'] == ACL_TYPE_GROUP )
+							$info = $lang->get('acl_msg_list_group', array( 'group' => $row['group_name'] ));
+						
+						// affected pages info
+						if ( $row['page_id'] && $row['namespace'] && $row['namespace'] != '__PageGroup' )
+							$info .= $lang->get('acl_msg_list_on_page', array( 'page_name' => "{$row['namespace']}:{$row['page_id']}" ));
+						else if ( $row['page_id'] && $row['namespace'] && $row['namespace'] == '__PageGroup' )
+							$info .= $lang->get('acl_msg_list_on_page_group', array( 'page_group' => $row['pg_name'] ));
+						else
+							$info .= $lang->get('acl_msg_list_entire_site');
+							
+						$score_string = $lang->get('acl_msg_list_score', array
+							(
+								'score' => $score,
+								'desc'  => $desc,
+								'info'  => $info
+								));
+						$return['rules'][] = array(
+							'score_string' => $score_string,
+							'rule_id'      => $row['rule_id'],
+							'color'        => $color
+							);
+					}
+					
+					break;
+				case 'list_presets':
+					$presets = array();
+					$q = $db->sql_query('SELECT page_id AS preset_name, rule_id, rules FROM ' . table_prefix . "acl WHERE target_type = " . ACL_TYPE_PRESET . ";");
+					if ( !$q )
+						$db->die_json();
+					
+					while ( $row = $db->fetchrow() )
+					{
+						$row['rules'] = $session->string_to_perm($row['rules']);
+						$presets[] = $row;
+					}
+					
+					return array(
+						'mode' => 'list_existing',
+						'presets' => $presets
+					);
+					break;
+				case 'save_preset':
+					if ( empty($parms['preset_name']) )
+					{
+						return array(
+							'mode' => 'error',
+							'error' => $lang->get('acl_err_preset_name_empty')
+						);
+					}
+					$preset_name = $db->escape($parms['preset_name']);
+					$q = $db->sql_query('DELETE FROM ' . table_prefix . "acl WHERE target_type = " . ACL_TYPE_PRESET . " AND page_id = '$preset_name';");
+					if ( !$q )
+						$db->die_json();
+					
+					$perms = $session->perm_to_string($parms['perms']);
+					if ( !$perms )
+					{
+						return array(
+							'mode' => 'error',
+							'error' => $lang->get('acl_err_preset_is_blank')
+						);
+					}
+					
+					$perms = $db->escape($perms);
+					$q = $db->sql_query('INSERT INTO ' . table_prefix . "acl(page_id, target_type, rules) VALUES\n"
+														. "  ( '$preset_name', " . ACL_TYPE_PRESET . ", '$perms' );");
+					if ( !$q )
+						$db->die_json();
+					
+					return array(
+							'mode' => 'success'
+						);
+					break;
+				case 'trace':
+					list($targetpid, $targetns) = RenderMan::strToPageID($parms['page']);
+					try
+					{
+						$perms = $session->fetch_page_acl_user($parms['user'], $targetpid, $targetns);
+						$perm_table = array(
+								AUTH_ALLOW => 'acl_lbl_field_allow',
+								AUTH_WIKIMODE => 'acl_lbl_field_wikimode',
+								AUTH_DISALLOW => 'acl_lbl_field_disallow',
+								AUTH_DENY => 'acl_lbl_field_deny'
+							);
+						
+						$return = array(
+							'mode' => 'trace',
+							'perms' => array()
+						);
+						
+						foreach ( $perms->perm_resolve_table as $perm_type => $lookup_data )
+						{
+							if ( !$session->check_acl_scope($perm_type, $targetns) )
+								continue;
+							
+							$src_l10n = $lang->get($session->acl_inherit_lang_table[$lookup_data['src']], $lookup_data);
+							$divclass = preg_replace('/^acl_inherit_/', '', $session->acl_inherit_lang_table[$lookup_data['src']]);
+							$perm_string = $lang->get($perm_table[$perms->perms[$perm_type]]);
+							$perm_name = $lang->get($session->acl_descs[$perm_type]);
+							
+							$return['perms'][$perm_type] = array(
+									'divclass' => "acl_inherit acl_$divclass",
+									'perm_type' => $perm_type,
+									'perm_name' => $perm_name,
+									'perm_value' => $perm_string,
+									'perm_src' => $src_l10n,
+									'rule_id' => intval($lookup_data['rule_id']),
+									'bad_deps' => $perms->acl_check_deps($perm_type, true)
+								);
+						}
+						
+						// group rules if possible
+						$return['groups'] = array();
+						foreach ( $return['perms'] as $rule )
+						{
+							if ( !isset($return['groups'][$rule['rule_id']]) )
+							{
+								$return['groups'][$rule['rule_id']] = array();
+							}
+							$return['groups'][$rule['rule_id']][] = $rule['perm_type'];
+						}
+					}
+					catch ( Exception $e )
+					{
+						$return = array(
+								'mode' => 'error',
+								'error' => $e->getMessage()
+							);
+					}
+					
+					break;
+				default:
+					return Array('mode'=>'error','error'=>'Hacking attempt');
+					break;
+			}
+		}
+		return $return;
+	}
+	
+	/**
+ 	* Same as PageUtils::acl_editor(), but the parms are a JSON string instead of an array. This also returns a JSON string.
+ 	* @param string $parms Same as PageUtils::acl_editor/$parms, but should be a valid JSON string.
+ 	* @return string
+ 	*/
+ 	
+	public static function acl_json($parms = '{ }')
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		try
+		{
+			$parms = enano_json_decode($parms);
+		}
+		catch ( Zend_Json_Exception $e )
+		{
+			$parms = array();
+		}
+		$ret = PageUtils::acl_editor($parms);
+		$ret = enano_json_encode($ret);
+		return $ret;
+	}
+	
+	/**
+ 	* A non-Javascript frontend for the ACL API.
+ 	* @param array The request data, if any, this should be in the format required by PageUtils::acl_editor()
+ 	*/
+ 	
+	public static function aclmanager($parms)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		global $lang;
+		ob_start();
+		// Convenience
+		$formstart = '<form 
+										action="' . makeUrl($paths->page, 'do=aclmanager', true) . '"
+										method="post" enctype="multipart/form-data"
+										onsubmit="if(!submitAuthorized) return false;"
+										>';
+		$formend   = '</form>';
+		$parms    = PageUtils::acl_preprocess($parms);
+		$response = PageUtils::acl_editor($parms);
+		$response = PageUtils::acl_postprocess($response);
+		
+		//die('<pre>' . htmlspecialchars(print_r($response, true)) . '</pre>');
+		
+		switch($response['mode'])
+		{
+			case 'debug':
+				echo '<pre>' . htmlspecialchars($response['text']) . '</pre>';
+				break;
+			case 'stage1':
+				echo '<h3>' . $lang->get('acl_lbl_welcome_title') . '</h3>
+							<p>' . $lang->get('acl_lbl_welcome_body') . '</p>';
+				echo $formstart;
+				echo '<p><label><input type="radio" name="data[target_type]" value="' . ACL_TYPE_GROUP . '" checked="checked" /> ' . $lang->get('acl_radio_usergroup') . '</label></p>
+							<p><select name="data[target_id_grp]">';
+				foreach ( $response['groups'] as $group )
+				{
+					echo '<option value="' . $group['id'] . '">' . $group['name'] . '</option>';
+				}
+				
+				// page group selector
+				$groupsel = '';
+				if ( count($response['page_groups']) > 0 )
+				{
+					$groupsel = '<p><label><input type="radio" name="data[scope]" value="page_group" /> ' . $lang->get('acl_radio_scope_pagegroup') . '</label></p>
+ 											<p><select name="data[pg_id]">';
+					foreach ( $response['page_groups'] as $grp )
+					{
+						$groupsel .= '<option value="' . $grp['id'] . '">' . htmlspecialchars($grp['name']) . '</option>';
+					}
+					$groupsel .= '</select></p>';
+				}
+				
+				echo '</select></p>
+							<p><label><input type="radio" name="data[target_type]" value="' . ACL_TYPE_USER . '" /> ' . $lang->get('acl_radio_user') . '</label></p>
+							<p>' . $template->username_field('data[target_id_user]') . '</p>
+							<p>' . $lang->get('acl_lbl_scope') . '</p>
+							<p><label><input name="data[scope]" value="only_this" type="radio" checked="checked" /> ' . $lang->get('acl_radio_scope_thispage') . '</p>
+							' . $groupsel . '
+							<p><label><input name="data[scope]" value="entire_site" type="radio" /> ' . $lang->get('acl_radio_scope_wholesite') . '</p>
+							<div style="margin: 0 auto 0 0; text-align: right;">
+								<input name="data[mode]" value="seltarget" type="hidden" />
+								<input type="hidden" name="data[page_id]" value="' . $paths->page_id . '" />
+								<input type="hidden" name="data[namespace]" value="' . $paths->namespace . '" />
+								<input type="submit" value="' . htmlspecialchars($lang->get('etc_wizard_next')) . '" />
+							</div>';
+				echo $formend;
+				break;
+			case 'success':
+				echo '<div class="info-box">
+								<b>' . $lang->get('acl_lbl_save_success_title') . '</b><br />
+								' . $lang->get('acl_lbl_save_success_body', array( 'target_name' => $response['target_name'] )) . '<br />
+								' . $formstart . '
+								<input type="hidden" name="data[mode]" value="seltarget" />
+								<input type="hidden" name="data[target_type]" value="' . $response['target_type'] . '" />
+								<input type="hidden" name="data[target_id_user]" value="' . ( ( intval($response['target_type']) == ACL_TYPE_USER ) ? $response['target_name'] : $response['target_id'] ) .'" />
+								<input type="hidden" name="data[target_id_grp]"  value="' . ( ( intval($response['target_type']) == ACL_TYPE_USER ) ? $response['target_name'] : $response['target_id'] ) .'" />
+								<input type="hidden" name="data[scope]" value="' . ( ( $response['page_id'] ) ? 'only_this' : 'entire_site' ) . '" />
+								<input type="hidden" name="data[page_id]" value="' . ( ( $response['page_id'] ) ? $response['page_id'] : 'false' ) . '" />
+								<input type="hidden" name="data[namespace]" value="' . ( ( $response['namespace'] ) ? $response['namespace'] : 'false' ) . '" />
+								<input type="submit" value="' . $lang->get('acl_btn_returnto_editor') . '" /> <input type="submit" name="data[act_go_stage1]" value="' . $lang->get('acl_btn_returnto_userscope') . '" />
+								' . $formend . '
+							</div>';
+				break;
+			case 'delete':
+				echo '<div class="info-box">
+								<b>' . $lang->get('acl_lbl_delete_success_title') . '</b><br />
+								' . $lang->get('acl_lbl_delete_success_body', array('target_name' => $response['target_name'])) . '<br />
+								' . $formstart . '
+								<input type="hidden" name="data[mode]" value="seltarget" />
+								<input type="hidden" name="data[target_type]" value="' . $response['target_type'] . '" />
+								<input type="hidden" name="data[target_id_user]" value="' . ( ( intval($response['target_type']) == ACL_TYPE_USER ) ? $response['target_name'] : $response['target_id'] ) .'" />
+								<input type="hidden" name="data[target_id_grp]"  value="' . ( ( intval($response['target_type']) == ACL_TYPE_USER ) ? $response['target_name'] : $response['target_id'] ) .'" />
+								<input type="hidden" name="data[scope]" value="' . ( ( $response['page_id'] ) ? 'only_this' : 'entire_site' ) . '" />
+								<input type="hidden" name="data[page_id]" value="' . ( ( $response['page_id'] ) ? $response['page_id'] : 'false' ) . '" />
+								<input type="hidden" name="data[namespace]" value="' . ( ( $response['namespace'] ) ? $response['namespace'] : 'false' ) . '" />
+								<input type="submit" value="' . $lang->get('acl_btn_returnto_editor') . '" /> <input type="submit" name="data[act_go_stage1]" value="' . $lang->get('acl_btn_returnto_userscope') . '" />
+								' . $formend . '
+							</div>';
+				break;
+			case 'seltarget':
+				if ( $response['type'] == 'edit' )
+				{
+					echo '<h3>' . $lang->get('acl_lbl_editwin_title_edit') . '</h3>';
+				}
+				else
+				{
+					echo '<h3>' . $lang->get('acl_lbl_editwin_title_create') . '</h3>';
+				}
+				$type  = ( $response['target_type'] == ACL_TYPE_GROUP ) ? $lang->get('acl_target_type_group') : $lang->get('acl_target_type_user');
+				$scope = ( $response['page_id'] ) ? ( $response['namespace'] == '__PageGroup' ? $lang->get('acl_scope_type_pagegroup') : $lang->get('acl_scope_type_thispage') ) : $lang->get('acl_scope_type_wholesite');
+				$subs = array(
+						'target_type' => $type,
+						'target' => $response['target_name'],
+						'scope_type' => $scope
+					);
+				echo $lang->get('acl_lbl_editwin_body', $subs);
+				echo $formstart;
+				$parser = $template->makeParserText( $response['template']['acl_field_begin'] );
+				echo $parser->run();
+				$parser = $template->makeParserText( $response['template']['acl_field_item'] );
+				$cls = 'row2';
+				foreach ( $response['acl_types'] as $acl_type => $value )
+				{
+					$vars = Array(
+							'FIELD_INHERIT_CHECKED' => '',
+							'FIELD_DENY_CHECKED' => '',
+							'FIELD_DISALLOW_CHECKED' => '',
+							'FIELD_WIKIMODE_CHECKED' => '',
+							'FIELD_ALLOW_CHECKED' => '',
+						);
+					$cls = ( $cls == 'row1' ) ? 'row2' : 'row1';
+					$vars['ROW_CLASS'] = $cls;
+					
+					switch ( $response['current_perms'][$acl_type] )
+					{
+						case 'i':
+						default:
+							$vars['FIELD_INHERIT_CHECKED'] = 'checked="checked"';
+							break;
+						case AUTH_ALLOW:
+							$vars['FIELD_ALLOW_CHECKED'] = 'checked="checked"';
+							break;
+						case AUTH_WIKIMODE:
+							$vars['FIELD_WIKIMODE_CHECKED'] = 'checked="checked"';
+							break;
+						case AUTH_DISALLOW:
+							$vars['FIELD_DISALLOW_CHECKED'] = 'checked="checked"';
+							break;
+ 						case AUTH_DENY:
+							$vars['FIELD_DENY_CHECKED'] = 'checked="checked"';
+							break;
+					}
+					$vars['FIELD_NAME'] = 'data[perms][' . $acl_type . ']';
+					if ( preg_match('/^([a-z0-9_]+)$/', $response['acl_descs'][$acl_type]) )
+					{
+						$vars['FIELD_DESC'] = $lang->get($response['acl_descs'][$acl_type]);
+					}
+					else
+					{
+						$vars['FIELD_DESC'] = $response['acl_descs'][$acl_type];
+					}
+					$parser->assign_vars($vars);
+					echo $parser->run();
+				}
+				$parser = $template->makeParserText( $response['template']['acl_field_end'] );
+				echo $parser->run();
+				echo '<div style="margin: 10px auto 0 0; text-align: right;">
+								<input name="data[mode]" value="save_' . $response['type'] . '" type="hidden" />
+								<input type="hidden" name="data[page_id]" value="'   . (( $response['page_id']   ) ? $response['page_id']   : 'false') . '" />
+								<input type="hidden" name="data[namespace]" value="' . (( $response['namespace'] ) ? $response['namespace'] : 'false') . '" />
+								<input type="hidden" name="data[target_type]" value="' . $response['target_type'] . '" />
+								<input type="hidden" name="data[target_id]" value="' . $response['target_id'] . '" />
+								<input type="hidden" name="data[target_name]" value="' . $response['target_name'] . '" />
+								' . ( ( $response['type'] == 'edit' ) ? '<input type="submit" value="' . $lang->get('etc_save_changes') . '" />&nbsp;&nbsp;<input type="submit" name="data[act_delete_rule]" value="' . $lang->get('acl_btn_deleterule') . '" style="color: #AA0000;" onclick="return confirm(\'' . addslashes($lang->get('acl_msg_deleterule_confirm')) . '\');" />' : '<input type="submit" value="' . $lang->get('acl_btn_createrule') . '" />' ) . '
+							</div>';
+				echo $formend;
+				break;
+			case 'error':
+				ob_end_clean();
+				die_friendly('Error occurred', '<p>Error returned by permissions API:</p><pre>' . htmlspecialchars($response['error']) . '</pre>');
+				break;
+		}
+		$ret = ob_get_contents();
+		ob_end_clean();
+		echo
+			$template->getHeader() .
+			$ret .
+			$template->getFooter();
+	}
+	
+	/**
+ 	* Preprocessor to turn the form-submitted data from the ACL editor into something the backend can handle
+ 	* @param array The posted data
+ 	* @return array
+ 	* @access private
+ 	*/
+ 	
+	public static function acl_preprocess($parms)
+	{
+		if ( !isset($parms['mode']) )
+			// Nothing to do
+			return $parms;
+		switch ( $parms['mode'] )
+		{
+			case 'seltarget':
+				
+				// Who's affected?
+				$parms['target_type'] = intval( $parms['target_type'] );
+				$parms['target_id'] = ( $parms['target_type'] == ACL_TYPE_GROUP ) ? $parms['target_id_grp'] : $parms['target_id_user'];
+				
+			case 'save_edit':
+			case 'save_new':
+				if ( isset($parms['act_delete_rule']) )
+				{
+					$parms['mode'] = 'delete';
+				}
+				
+				// Scope (just this page or entire site?)
+				if ( $parms['scope'] == 'entire_site' || ( $parms['page_id'] == 'false' && $parms['namespace'] == 'false' ) )
+				{
+					$parms['page_id']   = false;
+					$parms['namespace'] = false;
+				}
+				else if ( $parms['scope'] == 'page_group' )
+				{
+					$parms['page_id'] = $parms['pg_id'];
+					$parms['namespace'] = '__PageGroup';
+				}
+				
+				break;
+		}
+		
+		if ( isset($parms['act_go_stage1']) )
+		{
+			$parms = array(
+					'mode' => 'listgroups'
+				);
+		}
+		
+		return $parms;
+	}
+	
+	public static function acl_postprocess($response)
+	{
+		if(!isset($response['mode']))
+		{
+			if ( isset($response['groups']) )
+				$response['mode'] = 'stage1';
+			else
+				$response = Array(
+						'mode' => 'error',
+						'error' => 'Invalid action passed by API backend.',
+					);
+		}
+		return $response;
+	}
+ 	
 }
 
 /**
@@ -2462,32 +2462,32 @@
 
 function acl_list_draw_key()
 {
-  $out  = '<div style="width: 460px; margin: 0 auto; text-align: center; margin-bottom: 10px;">';
-  $out .= '<div style="float: left;">&larr; Deny</div>';
-  $out .= '<div style="float: right;">Allow &rarr;</div>';
-  $out .= 'Neutral';
-  $out .= '<div style="clear: both;"></div>';
-  // 11 boxes on each side of the center
-  $inc = ceil ( ( 0xFF - ACL_SCALE_MINIMAL_SHADE ) / 11 );
-  for ( $i = ACL_SCALE_MINIMAL_SHADE; $i <= 0xFF; $i+= $inc )
-  {
-    $octet = dechex($i);
-    $color = "ff$octet$octet";
-    $out .= '<div style="background-color: #' . $color . '; float: left; width: 20px;">&nbsp;</div>';
-  }
-  $out .= '<div style="background-color: #efefef; float: left; width: 20px;">&nbsp;</div>';
-  for ( $i = 0xFF; $i >= ACL_SCALE_MINIMAL_SHADE; $i-= $inc )
-  {
-    $octet = dechex($i);
-    $color = "{$octet}ff{$octet}";
-    $out .= '<div style="background-color: #' . $color . '; float: left; width: 20px;">&nbsp;</div>';
-  }
-  $out .= '<div style="clear: both;"></div>';
-  $out .= '<div style="float: left;">-100</div>';
-  $out .= '<div style="float: right;">+100</div>';
-  $out .= '0';
-  $out .= '</div>';
-  return $out;
+	$out  = '<div style="width: 460px; margin: 0 auto; text-align: center; margin-bottom: 10px;">';
+	$out .= '<div style="float: left;">&larr; Deny</div>';
+	$out .= '<div style="float: right;">Allow &rarr;</div>';
+	$out .= 'Neutral';
+	$out .= '<div style="clear: both;"></div>';
+	// 11 boxes on each side of the center
+	$inc = ceil ( ( 0xFF - ACL_SCALE_MINIMAL_SHADE ) / 11 );
+	for ( $i = ACL_SCALE_MINIMAL_SHADE; $i <= 0xFF; $i+= $inc )
+	{
+		$octet = dechex($i);
+		$color = "ff$octet$octet";
+		$out .= '<div style="background-color: #' . $color . '; float: left; width: 20px;">&nbsp;</div>';
+	}
+	$out .= '<div style="background-color: #efefef; float: left; width: 20px;">&nbsp;</div>';
+	for ( $i = 0xFF; $i >= ACL_SCALE_MINIMAL_SHADE; $i-= $inc )
+	{
+		$octet = dechex($i);
+		$color = "{$octet}ff{$octet}";
+		$out .= '<div style="background-color: #' . $color . '; float: left; width: 20px;">&nbsp;</div>';
+	}
+	$out .= '<div style="clear: both;"></div>';
+	$out .= '<div style="float: left;">-100</div>';
+	$out .= '<div style="float: right;">+100</div>';
+	$out .= '0';
+	$out .= '</div>';
+	return $out;
 }
 
 /**
@@ -2496,43 +2496,43 @@
 
 function get_acl_rule_score($perms)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  if ( is_string($perms) )
-    $perms = $session->string_to_perm($perms);
-  else if ( !is_array($perms) )
-    return false;
-  $score = 0;
-  foreach ( $perms as $item )
-  {
-    switch ( $item )
-    {
-      case AUTH_ALLOW :
-        $inc = 2;
-        break;
-      case AUTH_WIKIMODE:
-        $inc = 1;
-        break;
-      case AUTH_DISALLOW:
-        $inc = -1;
-        break;
-      case AUTH_DENY:
-        $inc = -2;
-        break;
-      default:
-        $inc = 0;
-        break;
-    }
-    $score += $inc;
-  }
-  // this is different from the beta; calculate highest score and
-  // get percentage to be fairer to smaller/less broad rules
-  $divisor = count($perms) * 2;
-  if ( $divisor == 0 )
-  {
-    return 0;
-  }
-  $score = 100 * ( $score / $divisor );
-  return round($score);
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	if ( is_string($perms) )
+		$perms = $session->string_to_perm($perms);
+	else if ( !is_array($perms) )
+		return false;
+	$score = 0;
+	foreach ( $perms as $item )
+	{
+		switch ( $item )
+		{
+			case AUTH_ALLOW :
+				$inc = 2;
+				break;
+			case AUTH_WIKIMODE:
+				$inc = 1;
+				break;
+			case AUTH_DISALLOW:
+				$inc = -1;
+				break;
+			case AUTH_DENY:
+				$inc = -2;
+				break;
+			default:
+				$inc = 0;
+				break;
+		}
+		$score += $inc;
+	}
+	// this is different from the beta; calculate highest score and
+	// get percentage to be fairer to smaller/less broad rules
+	$divisor = count($perms) * 2;
+	if ( $divisor == 0 )
+	{
+		return 0;
+	}
+	$score = 100 * ( $score / $divisor );
+	return round($score);
 }
 
 ?>