includes/paths.php
author Dan
Sun, 04 May 2008 21:57:48 -0400
changeset 541 acb7e23b6ffa
parent 540 1e4b759da336
child 555 ac4c6a7f01d8
permissions -rw-r--r--
Massive commit with various changes. Added user ranks system (no admin interface yet) and ability for users to have custom user titles. Made cron framework accept fractions of hours through floating-point intervals. Modifed ACL editor to use miniPrompt framework for close confirmation box. Made avatar system use a special page as opposed to fetching the files directly for caching reasons.

<?php

/**
 * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
 * Version 1.1.4 (Caoineag alpha 4)
 * Copyright (C) 2006-2008 Dan Fuhry
 * paths.php - The part of Enano that actually manages content. Everything related to page handling and namespaces is in here.
 *
 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
 *
 * @package Enano
 * @subpackage PathManager
 * @see http://enanocms.org/Help:API_Documentation
 */
 
class pathManager {
  var $pages, $custom_page, $cpage, $page, $fullpage, $page_exists, $page_id, $namespace, $nslist, $admin_tree, $wiki_mode, $page_protected, $template_cache, $anonymous_page;
  function __construct()
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    
    $GLOBALS['paths'] =& $this;
    $this->pages = Array();
    
    // DEFINE NAMESPACES HERE
    // The key names should NOT EVER be changed, or Enano will be very broken
    $this->nslist = Array(
      'Article' =>'',
      'User'    =>'User:',
      'File'    =>'File:',
      'Help'    =>'Help:',
      'Admin'   =>'Admin:',
      'Special' =>'Special:',
      'System'  =>'Enano:',
      'Template'=>'Template:',
      'Category'=>'Category:',
      'Anonymous'=>'PhysicalRedirect:',
      'Project' =>sanitize_page_id(getConfig('site_name')).':',
      );
    
    // ACL types
    // These can also be added from within plugins
    
    $session->register_acl_type('read',                   AUTH_ALLOW,    'perm_read');
    $session->register_acl_type('post_comments',          AUTH_ALLOW,    'perm_post_comments',          Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('edit_comments',          AUTH_ALLOW,    'perm_edit_comments',          Array('post_comments'),                                   'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('edit_page',              AUTH_WIKIMODE, 'perm_edit_page',              Array('view_source'),                                     'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('edit_wysiwyg',           AUTH_ALLOW,    'perm_edit_wysiwyg',           Array('edit_page'),                                       'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('view_source',            AUTH_WIKIMODE, 'perm_view_source',            Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category'); // Only used if the page is protected
    $session->register_acl_type('mod_comments',           AUTH_DISALLOW, 'perm_mod_comments',           Array('edit_comments'),                                   'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('history_view',           AUTH_WIKIMODE, 'perm_history_view',           Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('history_rollback',       AUTH_DISALLOW, 'perm_history_rollback',       Array('history_view'),                                    'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('history_rollback_extra', AUTH_DISALLOW, 'perm_history_rollback_extra', Array('history_rollback'),                                'Article|User|Project|Template|File|Help|System|Category|Special');
    $session->register_acl_type('protect',                AUTH_DISALLOW, 'perm_protect',                Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('rename',                 AUTH_WIKIMODE, 'perm_rename',                 Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('clear_logs',             AUTH_DISALLOW, 'perm_clear_logs',             Array('read', 'protect', 'even_when_protected'),          'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('vote_delete',            AUTH_ALLOW,    'perm_vote_delete',            Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('vote_reset',             AUTH_DISALLOW, 'perm_vote_reset',             Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('delete_page',            AUTH_DISALLOW, 'perm_delete_page',            Array(),                                                  'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('tag_create',             AUTH_ALLOW,    'perm_tag_create',             Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('tag_delete_own',         AUTH_ALLOW,    'perm_tag_delete_own',         Array('read', 'tag_create'),                              'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('tag_delete_other',       AUTH_DISALLOW, 'perm_tag_delete_other',       Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('set_wiki_mode',          AUTH_DISALLOW, 'perm_set_wiki_mode',          Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('password_set',           AUTH_DISALLOW, 'perm_password_set',           Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('password_reset',         AUTH_DISALLOW, 'perm_password_reset',         Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('mod_misc',               AUTH_DISALLOW, 'perm_mod_misc',               Array(),                                                  'All');
    $session->register_acl_type('edit_cat',               AUTH_WIKIMODE, 'perm_edit_cat',               Array('read'),                                            'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('even_when_protected',    AUTH_DISALLOW, 'perm_even_when_protected',    Array('edit_page', 'rename', 'mod_comments', 'edit_cat'), 'Article|User|Project|Template|File|Help|System|Category');
    $session->register_acl_type('upload_files',           AUTH_DISALLOW, 'perm_upload_files',           Array('create_page'),                                     'Article|User|Project|Template|File|Help|System|Category|Special');
    $session->register_acl_type('upload_new_version',     AUTH_WIKIMODE, 'perm_upload_new_version',     Array('upload_files'),                                    'Article|User|Project|Template|File|Help|System|Category|Special');
    $session->register_acl_type('create_page',            AUTH_WIKIMODE, 'perm_create_page',            Array(),                                                  'Article|User|Project|Template|File|Help|System|Category|Special');
    $session->register_acl_type('html_in_pages',          AUTH_DISALLOW, 'perm_html_in_pages',          Array('edit_page'),                                       'Article|User|Project|Template|File|Help|System|Category|Admin');
    $session->register_acl_type('php_in_pages',           AUTH_DISALLOW, 'perm_php_in_pages',           Array('edit_page', 'html_in_pages'),                      'Article|User|Project|Template|File|Help|System|Category|Admin');
    $session->register_acl_type('custom_user_title',      AUTH_DISALLOW, 'perm_custom_user_title',      Array(),                                                  'User|Special');
    $session->register_acl_type('edit_acl',               AUTH_DISALLOW, 'perm_edit_acl',               Array('read', 'post_comments', 'edit_comments', 'edit_page', 'view_source', 'mod_comments', 'history_view', 'history_rollback', 'history_rollback_extra', 'protect', 'rename', 'clear_logs', 'vote_delete', 'vote_reset', 'delete_page', 'set_wiki_mode', 'password_set', 'password_reset', 'mod_misc', 'edit_cat', 'even_when_protected', 'upload_files', 'upload_new_version', 'create_page', 'php_in_pages'));
    
    // DO NOT add new admin pages here! Use a plugin to call $paths->addAdminNode();
    $this->addAdminNode('adm_cat_general',    'adm_page_general_config', 'GeneralConfig', scriptPath . '/images/icons/applets/generalconfig.png');
    $this->addAdminNode('adm_cat_general',    'adm_page_file_uploads',   'UploadConfig', scriptPath . '/images/icons/applets/uploadconfig.png');
    $this->addAdminNode('adm_cat_general',    'adm_page_file_types',     'UploadAllowedMimeTypes', scriptPath . '/images/icons/applets/uploadallowedmimetypes.png');
    $this->addAdminNode('adm_cat_general',    'adm_page_plugins',        'PluginManager', scriptPath . '/images/icons/applets/pluginmanager.png');
    $this->addAdminNode('adm_cat_general',    'adm_page_db_backup',      'DBBackup', scriptPath . '/images/icons/applets/dbbackup.png');
    $this->addAdminNode('adm_cat_general',    'adm_page_lang_manager',   'LangManager', scriptPath . '/images/icons/applets/langmanager.png');
    $this->addAdminNode('adm_cat_content',    'adm_page_manager',        'PageManager', scriptPath . '/images/icons/applets/pagemanager.png');
    $this->addAdminNode('adm_cat_content',    'adm_page_editor',         'PageEditor', scriptPath . '/images/icons/applets/pageeditor.png');
    $this->addAdminNode('adm_cat_content',    'adm_page_pg_groups',      'PageGroups', scriptPath . '/images/icons/applets/pagegroups.png');
    $this->addAdminNode('adm_cat_appearance', 'adm_page_themes',         'ThemeManager', scriptPath . '/images/icons/applets/thememanager.png');
    $this->addAdminNode('adm_cat_users',      'adm_page_users',          'UserManager', scriptPath . '/images/icons/applets/usermanager.png');
    $this->addAdminNode('adm_cat_users',      'adm_page_user_groups',    'GroupManager', scriptPath . '/images/icons/applets/groupmanager.png');
    $this->addAdminNode('adm_cat_users',      'adm_page_coppa',          'COPPA', scriptPath . '/images/icons/applets/coppa.png');
    $this->addAdminNode('adm_cat_users',      'adm_page_mass_email',     'MassEmail', scriptPath . '/images/icons/applets/massemail.png');
    $this->addAdminNode('adm_cat_security',   'adm_page_security_log',   'SecurityLog', scriptPath . '/images/icons/applets/securitylog.png');
    $this->addAdminNode('adm_cat_security',   'adm_page_ban_control',    'BanControl', scriptPath . '/images/icons/applets/bancontrol.png');
    
    $code = $plugins->setHook('acl_rule_init');
    foreach ( $code as $cmd )
    {
      eval($cmd);
    }
    
    $this->wiki_mode = (int)getConfig('wiki_mode')=='1';
    $this->template_cache = Array();
  }
  function parse_url($sanitize = true)
  {
    $title = '';
    if ( isset($_GET['title']) )
    {
      $title = $_GET['title'];
    }
    else if ( isset($_SERVER['PATH_INFO']) )
    {
      // fix for apache + CGI (occurred on a GoDaddy server, thanks mm3)
      if ( @substr(@$_SERVER['GATEWAY_INTERFACE'], 0, 3) === 'CGI' && $_SERVER['PATH_INFO'] == scriptPath . '/index.php' )
      {
        // do nothing; ignore PATH_INFO
      }
      else
      {
        $title = substr($_SERVER['PATH_INFO'], ( strpos($_SERVER['PATH_INFO'], '/') ) + 1 );
      }
    }
    else
    {
      // This method really isn't supported because apache has a habit of passing dots as underscores, thus corrupting the request
      // If you really want to try it, the URI format is yoursite.com/?/Page_title
      if ( count($_GET) > 0 )
      {
        list($getkey) = array_keys($_GET);
        if ( substr($getkey, 0, 1) == '/' )
        {
          $title = substr($getkey, 1);
        }
      }
    }
    return ( $sanitize ) ? sanitize_page_id($title) : $title;
  }
  function init()
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    global $lang;
    
    $code = $plugins->setHook('paths_init_before');
    foreach ( $code as $cmd )
    {
      eval($cmd);
    }
    
    $e = $db->sql_query('SELECT name,urlname,namespace,special,visible,comments_on,protected,delvotes,' . "\n"
                        . '  delvote_ips,wiki_mode,password FROM '.table_prefix.'pages ORDER BY name;');
    if( !$e )
    {
      $db->_die('The error seems to have occured while selecting the page information. File: includes/paths.php; line: '.__LINE__);
    }
    while($r = $db->fetchrow())
    {
      
      $r['urlname_nons'] = $r['urlname'];
      if ( isset($this->nslist[$r['namespace']]) )
      {
        $r['urlname'] = $this->nslist[$r['namespace']] . $r['urlname']; // Applies the User:/File:/etc prefixes to the URL names
      }
      else
      {
        $ns_char = substr($this->nslist['Special'], -1);
        $r['urlname'] = $r['namespace'] . $ns_char . $r['urlname'];
      }
      
      if ( $r['delvotes'] == null)
      {
        $r['delvotes'] = 0;
      }
      if ( $r['protected'] == 0 || $r['protected'] == 1 )
      {
        $r['really_protected'] = (int)$r['protected'];
      }
      else if ( $r['protected'] == 2 && getConfig('wiki_mode') == '1')
      {
        $r['really_protected'] = 1;
      }
      else if ( $r['protected'] == 2 && getConfig('wiki_mode') == '0' )
      {
        $r['really_protected'] = 0;
      }
      
      $this->pages[$r['urlname']] = $r;
      $this->pages[] =& $this->pages[$r['urlname']];
      
    }
    $db->free_result();
    if ( defined('ENANO_INTERFACE_INDEX') || defined('ENANO_INTERFACE_AJAX') || defined('IN_ENANO_UPGRADE') )
    {
      $title = $this->parse_url(false);
      if ( empty($title) )
      {
        $this->main_page();
      }
      if ( strstr($title, ' ') || strstr($title, '+') || strstr($title, '%20') )
      {
        $title = sanitize_page_id($title);
        redirect(makeUrl($title), '', '', 0);
      }
      $title = sanitize_page_id($title);
      // We've got the title, pull the namespace from it
      $namespace = 'Article';
      $page_id = $title;
      foreach ( $this->nslist as $ns => $prefix )
      {
        $prefix_len = strlen($prefix);
        if ( substr($title, 0, $prefix_len) == $prefix )
        {
          $page_id = substr($title, $prefix_len);
          $namespace = $ns;
        }
      }
      $this->namespace = $namespace;
      $this->fullpage = $title;
      if ( $namespace == 'Special' || $namespace == 'Admin' )
      {
        list($page_id) = explode('/', $page_id);
      }
      $this->page = $this->nslist[$namespace] . $page_id;
      $this->page_id = $page_id;
      // die("All done setting parameters. What we've got:<br/>namespace: $namespace<br/>fullpage: $this->fullpage<br/>page: $this->page<br/>page_id: $this->page_id");
    }
    else
    {
      // Starting up Enano with the API from a page that wants to do its own thing. Generate
      // metadata for an anonymous page and avoid redirection at all costs.
      if ( isset($GLOBALS['title']) )
      {
        $title =& $GLOBALS['title'];
      }
      else
      {
        $title = basename($_SERVER['SCRIPT_NAME']);
      }
      $base_uri = str_replace( scriptPath . '/', '', $_SERVER['SCRIPT_NAME'] );
      $this->page = $this->nslist['Anonymous'] . sanitize_page_id($base_uri);
      $this->fullpage = $this->nslist['Anonymous'] . sanitize_page_id($base_uri);
      $this->namespace = 'Anonymous';
      $this->cpage = array(
          'name' => $title,
          'urlname' => sanitize_page_id($base_uri),
          'namespace' => 'Anonymous',
          'special' => 1,
          'visible' => 1,
          'comments_on' => 1,
          'protected' => 1,
          'delvotes' => 0,
          'delvote_ips' => ''
        );
      $this->anonymous_page = true;
      $code = $plugins->setHook('paths_anonymous_page');
      foreach ( $code as $cmd )
      {
        eval($cmd);
      }
    }
    
    $this->page = sanitize_page_id($this->page);
    $this->fullpage = sanitize_page_id($this->fullpage);
    
    if(isset($this->pages[$this->page]))
    {
      $this->page_exists = true;
      $this->cpage = $this->pages[$this->page];
      $this->page_id =& $this->cpage['urlname_nons'];
      $this->namespace = $this->cpage['namespace'];
      if(!isset($this->cpage['wiki_mode'])) $this->cpage['wiki_mode'] = 2;
      
      // Determine the wiki mode for this page, now that we have this->cpage established
      if($this->cpage['wiki_mode'] == 2)
      {
        $this->wiki_mode = (int)getConfig('wiki_mode');
      }
      else
      {
        $this->wiki_mode = $this->cpage['wiki_mode'];
      }
      // Allow the user to create/modify his user page uncondtionally (admins can still protect the page)
      if($this->page == $this->nslist['User'].str_replace(' ', '_', $session->username))
      {
        $this->wiki_mode = true;
      }
      // And above all, if the site requires wiki mode to be off for non-logged-in users, disable it now
      if(getConfig('wiki_mode_require_login')=='1' && !$session->user_logged_in)
      {
        $this->wiki_mode = false;
      }
      if($this->cpage['protected'] == 2)
      {
        // The page is semi-protected, determine permissions
        if($session->user_logged_in && $session->reg_time + 60*60*24*4 < time()) 
        {
          $this->page_protected = 0;
        }
        else
        {
          $this->page_protected = 1;
        }
      }
      else
      {
        $this->page_protected = $this->cpage['protected'];
      }
    }
    else
    {
      $this->page_exists = false;
      $page_name = dirtify_page_id($this->page);
      $page_name = str_replace('_', ' ', $page_name);
      
      $pid_cleaned = sanitize_page_id($this->page);
      if ( $pid_cleaned != $this->page )
      {
        redirect(makeUrl($pid_cleaned), 'Sanitizer message', 'page id sanitized', 0);
      }
      
      if ( !is_array($this->cpage) )
      {
        $this->cpage = Array(
          'name'=>$page_name,
          'urlname'=>$this->page,
          'namespace'=>'Article',
          'special'=>0,
          'visible'=>0,
          'comments_on'=>1,
          'protected'=>0,
          'delvotes'=>0,
          'delvote_ips'=>'',
          'wiki_mode'=>2,
          );
      }
      // Look for a namespace prefix in the urlname, and assign a different namespace, if necessary
      $k = array_keys($this->nslist);
      for($i=0;$i<sizeof($this->nslist);$i++)
      {
        $ln = strlen($this->nslist[$k[$i]]);
        if( substr($this->page, 0, $ln) == $this->nslist[$k[$i]] )
        {
          $this->cpage['namespace'] = $k[$i];
          $this->cpage['urlname_nons'] = substr($this->page, strlen($this->nslist[$this->cpage['namespace']]), strlen($this->page));
          if(!isset($this->cpage['wiki_mode'])) 
          {
            $this->cpage['wiki_mode'] = 2;
          }
        }
      }
      $this->namespace = $this->cpage['namespace'];
      $this->page_id =& $this->cpage['urlname_nons'];
      
      if($this->namespace=='System') 
      {
        $this->cpage['protected'] = 1;
      }
      if($this->namespace == 'Special' && !$this->anonymous_page)
      {
        // Can't load nonexistent pages
        if( is_string(getConfig('main_page')) )
        {
          $main_page = makeUrl(getConfig('main_page'));
        }
        else
        {
          $main_page = makeUrl($this->pages[0]['urlname']);
        }
        redirect($main_page, $lang->get('page_msg_special_404_title'), $lang->get('page_msg_special_404_body', array('sp_link' => makeUrlNS('Special', 'SpecialPages'))), 15);
        exit;
      }
      // Allow the user to create/modify his user page uncondtionally (admins can still protect the page)
      if($this->page == $this->nslist['User'].str_replace(' ', '_', $session->username)) 
      {
        $this->wiki_mode = true;
      }
    }
    // This is used in the admin panel to keep track of form submission targets
    $this->cpage['module'] = $this->cpage['urlname'];
    
    // Page is set up, call any hooks
    $code = $plugins->setHook('page_set');
    foreach ( $code as $cmd )
    {
      eval($cmd);
    }
    
    $session->init_permissions();
    profiler_log('Paths and CMS core initted');
  }
  
  function add_page($flags)
  {
    global $lang;
    $flags['urlname_nons'] = $flags['urlname'];
    $flags['urlname'] = $this->nslist[$flags['namespace']] . $flags['urlname']; // Applies the User:/File:/etc prefixes to the URL names
    
    if ( is_object($lang) )
    {
      if ( preg_match('/^[a-z0-9]+_[a-z0-9_]+$/', $flags['name']) )
        $flags['name'] = $lang->get($flags['name']);
    }
    
    $pages_len = sizeof($this->pages)/2;
    $this->pages[$pages_len] = $flags;
    $this->pages[$flags['urlname']] =& $this->pages[$pages_len];
  }
  
  function main_page()
  {
    if( is_string(getConfig('main_page')) )
    {
      $main_page = makeUrl(getConfig('main_page'));
    }
    else
    {
      $main_page = makeUrl($this->pages[0]['urlname']);
    }
    redirect($main_page, 'Redirecting...', 'Invalid request, redirecting to main page', 0);
    exit;
  }
  
  function sysmsg($n)
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    $q = $db->sql_query('SELECT page_text, char_tag FROM '.table_prefix.'page_text WHERE page_id=\''.$db->escape(sanitize_page_id($n)).'\' AND namespace=\'System\'');
    if( !$q )
    {
      $db->_die('Error during generic selection of system page data.');
    }
    if($db->numrows() < 1)
    {
      return false;
      //$db->_die('Error during generic selection of system page data: there were no rows in the text table that matched the page text query.');
    }
    $r = $db->fetchrow();
    $db->free_result();
    $message = $r['page_text'];
    
    $message = preg_replace('/<noinclude>(.*?)<\/noinclude>/is', '', $message);
    $message = preg_replace('/<nodisplay>(.*?)<\/nodisplay>/is', '\\1', $message);
    
    return $message;
  }
  function get_pageid_from_url()
  {
    $url = $this->parse_url();
    if ( substr($url, 0, strlen($this->nslist['Special'])) == $this->nslist['Special'] ||
         substr($url, 0, strlen($this->nslist['Admin']))   == $this->nslist['Admin'])
    {
      list(, $ns) = RenderMan::strToPageID($url);
      $upart = substr($url, strlen($this->nslist[$ns]));
      list($upart) = explode('/', $upart);
      $url = $this->nslist[$ns] . $upart;
    }
    return $url;
  }
  // Parses a (very carefully formed) array into Javascript code compatible with the Tigra Tree Menu used in the admin menu
  function parseAdminTree() 
  {
    global $lang;
    
    $k = array_keys($this->admin_tree);
    $i = 0;
    $ret = '';
    $icon = "<img alt=\"\" src=\"" . scriptPath . "/images/icons/applets/home.png\" style=\"border-width: 0; margin-right: 3px;\" /> ";
    $icon = addslashes($icon);
    $ret .= "var TREE_ITEMS = [\n  ['$icon" . $lang->get('adm_btn_home') . "', 'javascript:ajaxPage(\'".$this->nslist['Admin']."Home\');',\n    ";
    foreach($k as $key)
    {
      $i++;
      $name = ( preg_match('/^[a-z0-9_]+$/', $key) ) ? $lang->get($key) : $key;
      $ret .= "['".$name."', 'javascript:trees[0].toggle($i)', \n";
      foreach($this->admin_tree[$key] as $c)
      {
        $i++;
        $name = ( preg_match('/^[a-z0-9_]+$/', $key) ) ? $lang->get($c['name']) : $c['name'];
        if ( $c['icon'] && $c['icon'] != scriptPath . '/images/spacer.gif' )
        {
          $icon = "<img alt=\"\" src=\"{$c['icon']}\" style=\"border-width: 0; margin-right: 3px;\" /> ";
        }
        else
        {
          $icon = '';
        }
        $icon = addslashes($icon);
        $ret .= "        ['$icon$name', 'javascript:ajaxPage(\\'".$this->nslist['Admin'].$c['pageid']."\\');'],\n";
      }
      $ret .= "      ],\n";
    }
    $icon = "<img alt=\"\" src=\"" . scriptPath . "/images/icons/applets/adminlogout.png\" style=\"border-width: 0; margin-right: 3px;\" /> ";
    $icon = addslashes($icon);
    $ret .= "    ['$icon" . $lang->get('adm_btn_logout') . "', 'javascript:ajaxPage(\\'".$this->nslist['Admin']."AdminLogout\\');'],\n";
    $ret .= "    ['<span id=\\'keepalivestat\\'>" . $lang->get('adm_btn_keepalive_loading') . "</span>', 'javascript:ajaxToggleKeepalive();', 
                   ['" . $lang->get('adm_btn_keepalive_about') . "', 'javascript:aboutKeepAlive();']
                 ],\n";
    // I used this while I painstakingly wrote the Runt code that auto-expands certain nodes based on the value of a bitfield stored in a cookie. *shudders*
    // $ret .= "    ['(debug) Clear menu bitfield', 'javascript:createCookie(\\'admin_menu_state\\', \\'1\\', 365);'],\n";
    $ret .= "]\n];";
    return $ret;
  }
  
  /**
   * Creates a new entry in the administration panel's navigation tree.
   * @param string Section name - if this is a language string identifier, it will be sent through $lang->get()
   * @param string The title of the page, also may be a language string identifier
   * @param string The page ID of the admin page, the namespace Admin is assumed
   * @param string Optional. The path to a 16x16 image that will be displayed as the icon for this admin page
   */
  
  function addAdminNode($section, $page_title, $url, $icon = false)
  {
    if ( !$icon )
    {
      $icon = scriptPath . '/images/spacer.gif';
    }
    if(!isset($this->admin_tree[$section]))
    {
      $this->admin_tree[$section] = Array();
    }
    $this->admin_tree[$section][] = Array(
        'name' => $page_title,
        'pageid' => $url,
        'icon' => $icon
      );
  }
  function getParam($id = 0)
  {
    $title = $this->parse_url(false);
    list(, $ns) = RenderMan::strToPageID($title);
    $title = substr($title, strlen($this->nslist[$ns]));
    $regex = '/^' . str_replace('/', '\\/', preg_quote($this->nslist[$this->namespace])) . '\\/?/';
    $title = preg_replace($regex, '', $title);
    $title = explode('/', $title);
    $id = $id + 1;
    return ( isset($title[$id]) ) ? $title[$id] : false;
  }
  
  function getAllParams()
  {
    $title = $this->parse_url(false);
    $regex = '/^' . str_replace('/', '\\/', preg_quote($this->nslist[$this->namespace])) . '\\/?/';
    $title = preg_replace($regex, '', $title);
    $title = explode('/', $title);
    unset($title[0]);
    return implode('/', $title);
  }
  
  /**
   * Creates a new namespace in memory
   * @param string $id the namespace ID
   * @param string $prefix the URL prefix, must not be blank or already used
   * @return bool true on success false on failure
   */
  
  function create_namespace($id, $prefix)
  {
    if(in_array($prefix, $this->nslist))
    {
      // echo '<b>Warning:</b> pathManager::create_namespace: Prefix "'.$prefix.'" is already taken<br />';
      return false;
    }
    if( isset($this->nslist[$id]) )
    {
      // echo '<b>Warning:</b> pathManager::create_namespace: Namespace ID "'.$prefix.'" is already taken<br />';
      return false;
    }
    $this->nslist[$id] = $prefix;
  }
  
  /**
   * Fetches the page texts for searching
   */
   
  function fetch_page_search_texts()
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    $texts = Array();
    $q = $db->sql_query('SELECT t.page_id,t.namespace,t.page_text,t.char_tag FROM '.table_prefix.'page_text AS t
                           LEFT JOIN '.table_prefix.'pages AS p
                             ON t.page_id=p.urlname
                           WHERE p.namespace=t.namespace
                             AND ( p.password=\'\' OR p.password=\'da39a3ee5e6b4b0d3255bfef95601890afd80709\' )
                             AND p.visible=1;'); // Only indexes "visible" pages
    
    if( !$q )
    {
      return false;
    }
    while($row = $db->fetchrow())
    {
      $pid = $this->nslist[$row['namespace']] . $row['page_id'];
      $texts[$pid] = $row['page_text'];
    }
    $db->free_result();
    
    return $texts;
  }
  
  /**
   * Generates an SQL query to grab all of the text
   */
   
  function fetch_page_search_resource()
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    // sha1('') returns "da39a3ee5e6b4b0d3255bfef95601890afd80709"
    
    $concat_column = ( ENANO_DBLAYER == 'MYSQL' ) ?
      'CONCAT(\'ns=\',t.namespace,\';pid=\',t.page_id)' :
      "'ns=' || t.namespace || ';pid=' || t.page_id";
    
    $texts = 'SELECT t.page_text, ' . $concat_column . ' AS page_idstring, t.page_id, t.namespace FROM '.table_prefix.'page_text AS t
                           LEFT JOIN '.table_prefix.'pages AS p
                             ON ( t.page_id=p.urlname AND t.namespace=p.namespace )
                           WHERE p.namespace=t.namespace
                             AND ( p.password=\'\' OR p.password=\'da39a3ee5e6b4b0d3255bfef95601890afd80709\' )
                             AND p.visible=1;'; // Only indexes "visible" pages
    return $texts;
  }
  
  /**
   * Builds a word list for search indexing.
   * @param string Text to index
   * @param string Page ID of the page being indexed
   * @param string Title of the page being indexed
   * @return array List of words
   */
  
  function calculate_word_list($text, $page_id, $page_name)
  {
    $page_id = dirtify_page_id($page_id);
    $text = preg_replace('/[^a-z0-9\']/i', ' ', $text);
    $page_id = preg_replace('/[^a-z0-9\']/i', ' ', $page_id);
    $page_name = preg_replace('/[^a-z0-9\']/i', ' ', $page_name);
    $text .= " $page_id $page_name";
    $text = explode(' ', $text);
    foreach ( $text as $i => &$word )
    {
      if ( strstr($word, "''") )
        $word = preg_replace("/[']{2,}/", '', $word);
      if ( strlen($word) < 2 )
        unset($text[$i]);
    }
    $text = array_unique(array_values($text));
    // for debugging purposes (usually XSS safe because of character stripping)
    // echo ' ' . implode(' ', $text) . '<br />';
    return $text;
  }
  
  /**
   * Rebuilds the site's entire search index. Considerably more exciting if run from the command line.
   * @param bool If true, verbose output.
   * @param bool If true, verbose + debugging output.
   */
  
  function rebuild_search_index($verbose = false, $debug = false)
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    
    @set_time_limit(0);
    
    $q = $db->sql_query('DELETE FROM ' . table_prefix . 'search_index;');
    if ( !$q )
      $db->_die();
    
    $sha1_blank = sha1('');
    $query_func = ( ENANO_DBLAYER == 'MYSQL' ) ? 'mysql_query' : 'pg_query';
    
    //
    // Index $pages_in_batch pages at a time
    //
    $pages_in_batch = 15;
    
    // First find out how many pages there are
    $q = $db->sql_query('SELECT COUNT(p.urlname) AS num_pages FROM ' . table_prefix . "page_text AS t\n"
                      . "  LEFT JOIN " . table_prefix . "pages AS p\n"
                      . "    ON ( p.urlname = t.page_id AND p.namespace = t.namespace )\n"
                      . "  WHERE ( p.password = '' OR p.password = '$sha1_blank' )\n"
                      . "    AND ( p.visible = 1 );");
    if ( !$q )
      $db->_die();
    
    list($num_pages) = $db->fetchrow_num();
    $num_pages = intval($num_pages);
    $loops = ceil($num_pages / $pages_in_batch);
    $master_word_list = array();
    $stopwords = get_stopwords();
    
    for ( $j = 0; $j < $loops; )
    {
      $offset = $j * $pages_in_batch;
      
      $j++;
      
      if ( $verbose && $debug )
      {
        echo "Running indexing round $j of $loops (offset $offset)\n" . ( isset($_SERVER['REQUEST_URI']) ? '<br />' : '' );
      }
      
      $texts = $db->sql_query('SELECT p.name, t.page_id, t.namespace, t.page_text FROM ' . table_prefix . "page_text AS t\n"
                            . "  LEFT JOIN " . table_prefix . "pages AS p\n"
                            . "    ON ( p.urlname = t.page_id AND p.namespace = t.namespace )\n"
                            . "  WHERE ( p.password = '' OR p.password = '$sha1_blank' )\n"
                            . "    AND ( p.visible = 1 )\n"
                            . "  LIMIT $pages_in_batch OFFSET $offset;", false);
      if ( !$texts )
        $db->_die();
      
      $k = $offset;
      
      if ( $row = $db->fetchrow($texts) )
      {
        do
        {
          $k++;
          if ( $verbose )
          {
            $mu = memory_get_usage();
            echo "  Indexing page $k of $num_pages: {$row['namespace']}:{$row['page_id']}";
            if ( $debug )
              echo ", mem = $mu...";
            flush();
          }
          
          // Indexing identifier for the page in the DB
          $page_uniqid = "ns={$row['namespace']};pid=" . sanitize_page_id($row['page_id']);
          $page_uniqid = $db->escape($page_uniqid);
          
          // List of words on the page
          $wordlist = $this->calculate_word_list($row['page_text'], $row['page_id'], $row['name']);
          
          // Index calculation complete -- run inserts
          $inserts = array();
          foreach ( $wordlist as $word )
          {
            if ( in_array($word, $stopwords) || strval(intval($word)) === $word || strlen($word) < 3 )
              continue;
            $word_db = $db->escape($word);
            $word_db_lc = $db->escape(strtolower($word));
            if ( !in_array($word, $master_word_list) )
            {
              $inserts[] = "( '$word_db', '$word_db_lc', '$page_uniqid' )";
            }
            else
            {
              if ( $verbose && $debug )
                echo '.';
              $pid_col = ( ENANO_DBLAYER == 'MYSQL' ) ?
                          "CONCAT( page_names, ',$page_uniqid' )":
                          "page_names || ',$page_uniqid'";
              $q = $db->sql_query('UPDATE ' . table_prefix . "search_index SET page_names = $pid_col WHERE word = '$word_db';", false);
              if ( !$q )
                $db->_die();
            }
          }
          if ( count($inserts) > 0 )
          {
            if ( $verbose && $debug )
              echo 'i';
            $inserts = implode(",\n  ", $inserts);
            $q = $db->sql_query('INSERT INTO ' . table_prefix . "search_index(word, word_lcase, page_names) VALUES\n  $inserts;", false);
            if ( !$q )
              $db->_die();
          }
          
          $master_word_list = array_unique(array_merge($master_word_list, $wordlist));
          if ( $verbose )
          {
            if ( isset($_SERVER['REQUEST_URI']) )
              echo '<br />';
            echo "\n";
          }
          unset($inserts, $wordlist, $page_uniqid, $word_db, $q, $word, $row);
        }
        while ( $row = $db->fetchrow($texts) );
      }
      $db->free_result($texts);
    }
    if ( $verbose )
    {
      echo "Indexing complete.";
      if ( isset($_SERVER['REQUEST_URI']) )
        echo '<br />';
      echo "\n";
    }
    return true;
  }
  
  /**
   * Partially rebuilds the search index, removing/inserting entries only for the current page
   * @param string $page_id
   * @param string $namespace
   */
  
  function rebuild_page_index($page_id, $namespace)
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    if(!$db->sql_query('SELECT page_text FROM '.table_prefix.'page_text
      WHERE page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\';'))
    {
      return $db->get_error();
    }
    if ( $db->numrows() < 1 )
      return 'E: No rows';
    $idstring = $this->nslist[$namespace] . sanitize_page_id($page_id);
    if ( !isset($this->pages[$idstring]) )
    {
      return 'E: Can\'t find page metadata';
    }
    $row = $db->fetchrow();
    $db->free_result();
    $search = new Searcher();
    $search->buildIndex(Array("ns={$namespace};pid={$page_id}"=>$row['page_text'] . ' ' . $this->pages[$idstring]['name']));
    $new_index = $search->index;
    
    if ( ENANO_DBLAYER == 'MYSQL' )
    {
      $keys = array_keys($search->index);
      foreach($keys as $i => $k)
      {
        $c =& $keys[$i];
        $c = hexencode($c, '', '');
      }
      $keys = "word=0x" . implode ( " OR word=0x", $keys ) . "";
    }
    else
    {
      $keys = array_keys($search->index);
      foreach($keys as $i => $k)
      {
        $c =& $keys[$i];
        $c = $db->escape($c);
      }
      $keys = "word='" . implode ( "' OR word='", $keys ) . "'";
    }
    
    $query = $db->sql_query('SELECT word,page_names FROM '.table_prefix.'search_index WHERE '.$keys.';');
    
    while($row = $db->fetchrow())
    {
      $row['word'] = rtrim($row['word'], "\0");
      $new_index[ $row['word'] ] = $row['page_names'] . ',' . $search->index[ $row['word'] ];
    }
    $db->free_result();
    
    $db->sql_query('DELETE FROM '.table_prefix.'search_index WHERE '.$keys.';');
    
    $secs = Array();
    $q = 'INSERT INTO '.table_prefix.'search_index(word,word_lcase,page_names) VALUES';
    foreach($new_index as $word => $pages)
    {
      $secs[] = '(\''.$db->escape($word).'\', \'' . $db->escape(strtolower($word)) . '\', \''.$db->escape($pages).'\')';
    }
    $q .= implode(',', $secs);
    unset($secs);
    $q .= ';';
    if(!$db->check_query($q))
    {
      die('BUG: PathManager::rebuild_page_index: Query rejected by SQL parser:<pre>'.$q.'</pre>');
    }
    $result = $db->sql_query($q);
    if($result)
      return true;
    else
      $db->_die('The search index was trying to rebuild itself when the error occured.');
    
  }
  
  /**
   * Creates an instance of the Searcher class, including index info
   * @return object
   */
   
  function makeSearcher($match_case = false)
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    $search = new Searcher();
    $q = $db->sql_query('SELECT word,page_names FROM '.table_prefix.'search_index;');
    if(!$q)
    {
      echo $db->get_error();
      return false;
    }
    $idx = Array();
    while($row = $db->fetchrow($q))
    {
      $row['word'] = rtrim($row['word'], "\0");
      $idx[$row['word']] = $row['page_names'];
    }
    $db->free_result();
    $search->index = $idx;
    if($match_case)
      $search->match_case = true;
    return $search;
  }
  
  /**
   * Creates an associative array filled with the values of all the page titles
   * @return array
   */
   
  function get_page_titles()
  {
    $texts = Array();
    for ( $i = 0; $i < sizeof($this->pages) / 2; $i++ )
    {
      $texts[$this->pages[$i]['urlname']] = $this->pages[$i]['name'];
    }
    return $texts;
  }
  
  /**
   * Creates an instance of the Searcher class, including index info for page titles
   * @return object
   */
   
  function makeTitleSearcher($match_case = false)
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    $search = new Searcher();
    $texts = $this->get_page_titles();
    $search->buildIndex($texts);
    if($match_case)
      $search->match_case = true;
    return $search;
  }
  
  /**
   * Returns a list of groups that a given page is a member of.
   * @param string Page ID
   * @param string Namespace
   * @return array
   */
  
  function get_page_groups($page_id, $namespace)
  {
    global $db, $session, $paths, $template, $plugins; // Common objects
    
    static $cache = array();
    
    if ( count($cache) == 0 )
    {
      foreach ( $this->nslist as $key => $_ )
      {
        $cache[$key] = array();
      }
    }
    
    if ( !isset($this->nslist[$namespace]) )
      die('$paths->get_page_groups(): HACKING ATTEMPT: namespace "'. htmlspecialchars($namespace) .'" doesn\'t exist');
    
    $page_id_unescaped = $paths->nslist[$namespace] .
                         dirtify_page_id($page_id);
    $page_id_str       = $paths->nslist[$namespace] .
                         sanitize_page_id($page_id);
    
    $page_id = $db->escape(sanitize_page_id($page_id));
    
    if ( isset($cache[$namespace][$page_id]) )
    {
      return $cache[$namespace][$page_id];
    }
    
    $group_list = array();
    
    // What linked categories have this page?
    $q = $db->sql_unbuffered_query('SELECT g.pg_id, g.pg_type, g.pg_target FROM '.table_prefix.'page_groups AS g
  LEFT JOIN '.table_prefix.'categories AS c
    ON ( ( c.category_id = g.pg_target AND g.pg_type = ' . PAGE_GRP_CATLINK . ' ) OR c.category_id IS NULL )
  LEFT JOIN '.table_prefix.'page_group_members AS m
    ON ( ( g.pg_id = m.pg_id AND g.pg_type = ' . PAGE_GRP_NORMAL . ' ) OR ( m.pg_id IS NULL ) )
  LEFT JOIN '.table_prefix.'tags AS t
    ON ( ( t.tag_name = g.pg_target AND pg_type = ' . PAGE_GRP_TAGGED . ' ) OR t.tag_name IS NULL )
  WHERE
    ( c.page_id=\'' . $page_id . '\' AND c.namespace=\'' . $namespace . '\' ) OR
    ( t.page_id=\'' . $page_id . '\' AND t.namespace=\'' . $namespace . '\' ) OR
    ( m.page_id=\'' . $page_id . '\' AND m.namespace=\'' . $namespace . '\' ) OR
    ( g.pg_type = ' . PAGE_GRP_REGEX . ' );');
    if ( !$q )
      $db->_die();
    
    while ( $row = $db->fetchrow() )
    {
      if ( $row['pg_type'] == PAGE_GRP_REGEX )
      {
        //echo "&lt;debug&gt; matching page " . htmlspecialchars($page_id_unescaped) . " against regex <tt>" . htmlspecialchars($row['pg_target']) . "</tt>.";
        if ( @preg_match($row['pg_target'], $page_id_unescaped) || @preg_match($row['pg_target'], $page_id_str) )
        {
          //echo "..matched";
          $group_list[] = $row['pg_id'];
        }
        //echo "<br />";
      }
      else
      {
        $group_list[] = $row['pg_id'];
      }
    }
    
    $db->free_result();
    
    $cache[$namespace][$page_id] = $group_list;
    
    return $group_list;
    
  }
  
}

?>