Fixed sanitization of full page IDs with accidental parse of escaped hex character in dirtify_page_id(). Thanks Asterion; see http://forum.enanocms.org/post/20/
<?php
/**
* Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
* Version 1.0.5 (Ferrishyn)
* Copyright (C) 2006-2007 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, 'Read page(s)');
$session->register_acl_type('post_comments', AUTH_ALLOW, 'Post comments', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('edit_comments', AUTH_ALLOW, 'Edit own comments', Array('post_comments'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('edit_page', AUTH_WIKIMODE, 'Edit page', Array('view_source'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('view_source', AUTH_WIKIMODE, '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, 'Moderate comments', Array('edit_comments'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('history_view', AUTH_WIKIMODE, 'View history/diffs', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('history_rollback', AUTH_DISALLOW, 'Rollback history', Array('history_view'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('history_rollback_extra', AUTH_DISALLOW, 'Undelete page(s)', Array('history_rollback'), 'Article|User|Project|Template|File|Help|System|Category|Special');
$session->register_acl_type('protect', AUTH_DISALLOW, 'Protect page(s)', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('rename', AUTH_WIKIMODE, 'Rename page(s)', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('clear_logs', AUTH_DISALLOW, 'Clear page logs (dangerous)', Array('read', 'protect', 'even_when_protected'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('vote_delete', AUTH_ALLOW, 'Vote to delete', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('vote_reset', AUTH_DISALLOW, 'Reset delete votes', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('delete_page', AUTH_DISALLOW, 'Delete page(s)', Array(), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('tag_create', AUTH_ALLOW, 'Tag page(s)', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('tag_delete_own', AUTH_ALLOW, 'Remove own page tags', Array('read', 'tag_create'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('tag_delete_other', AUTH_DISALLOW, 'Remove others\' page tags', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('set_wiki_mode', AUTH_DISALLOW, 'Set per-page wiki mode', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('password_set', AUTH_DISALLOW, 'Set password', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('password_reset', AUTH_DISALLOW, 'Disable/reset password', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('mod_misc', AUTH_DISALLOW, 'Super moderator (generate SQL backtraces, view IP addresses, and send large numbers of private messages)', Array(), 'All');
$session->register_acl_type('edit_cat', AUTH_WIKIMODE, 'Edit categorization', Array('read'), 'Article|User|Project|Template|File|Help|System|Category');
$session->register_acl_type('even_when_protected', AUTH_DISALLOW, 'Allow editing, renaming, and categorization 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, 'Upload files', Array('create_page'), 'Article|User|Project|Template|File|Help|System|Category|Special');
$session->register_acl_type('upload_new_version', AUTH_WIKIMODE, 'Upload new versions of files', Array('upload_files'), 'Article|User|Project|Template|File|Help|System|Category|Special');
$session->register_acl_type('create_page', AUTH_WIKIMODE, 'Create pages', Array(), 'Article|User|Project|Template|File|Help|System|Category|Special');
$session->register_acl_type('php_in_pages', AUTH_DISALLOW, 'Embed PHP code in pages', Array('edit_page'), 'Article|User|Project|Template|File|Help|System|Category|Admin');
$session->register_acl_type('edit_acl', AUTH_DISALLOW, 'Edit access control lists', 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('General', 'General Configuration', 'GeneralConfig');
$this->addAdminNode('General', 'File uploads', 'UploadConfig');
$this->addAdminNode('General', 'Allowed file types', 'UploadAllowedMimeTypes');
$this->addAdminNode('General', 'Manage Plugins', 'PluginManager');
$this->addAdminNode('General', 'Backup database', 'DBBackup');
$this->addAdminNode('Content', 'Manage Pages', 'PageManager');
$this->addAdminNode('Content', 'Edit page content', 'PageEditor');
$this->addAdminNode('Content', 'Manage page groups', 'PageGroups');
$this->addAdminNode('Appearance', 'Manage themes', 'ThemeManager');
$this->addAdminNode('Users', 'Manage users', 'UserManager');
$this->addAdminNode('Users', 'Edit groups', 'GroupManager');
$this->addAdminNode('Users', 'COPPA support', 'COPPA');
$this->addAdminNode('Users', 'Mass e-mail', 'MassEmail');
$this->addAdminNode('Security', 'Security log', 'SecurityLog');
$this->addAdminNode('Security', 'Ban control', 'BanControl');
$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 pathManager()
{
$this->__construct();
}
function init()
{
global $db, $session, $paths, $template, $plugins; // Common objects
$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'];
$r['urlname'] = $this->nslist[$r['namespace']] . $r['urlname']; // Applies the User:/File:/etc prefixes to the URL names
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_INSTALL') || defined('IN_ENANO_UPGRADE') )
{
if( isset($_GET['title']) )
{
if ( $_GET['title'] == '' && getConfig('main_page') != '' )
{
$this->main_page();
}
if(strstr($_GET['title'], ' '))
{
$loc = urldecode(rawurldecode($_SERVER['REQUEST_URI']));
$loc = str_replace(' ', '_', $loc);
$loc = str_replace('+', '_', $loc);
$loc = str_replace('%20', '_', $loc);
redirect($loc, 'Redirecting...', 'Space detected in the URL, please wait whilst you are redirected', 0);
exit;
}
$url_namespace_special = substr($_GET['title'], 0, strlen($this->nslist['Special']) );
$url_namespace_template = substr($_GET['title'], 0, strlen($this->nslist['Template']) );
if($url_namespace_special == $this->nslist['Special'] || $url_namespace_template == $this->nslist['Template'] )
{
$ex = explode('/', $_GET['title']);
$this->page = $ex[0];
}
else
{
$this->page = $_GET['title'];
}
$this->fullpage = $_GET['title'];
}
elseif( isset($_SERVER['PATH_INFO']) )
{
$pi = explode('/', $_SERVER['PATH_INFO']);
if( !isset($pi[1]) || (isset($pi[1]) && $pi[1] == '' && getConfig('main_page') != '') )
{
$this->main_page();
}
if( strstr($pi[1], ' ') )
{
$loc = str_replace(' ', '_', urldecode(rawurldecode($_SERVER['REQUEST_URI'])));
$loc = str_replace('+', '_', $loc);
$loc = str_replace('%20', '_', $loc);
redirect($loc, 'Redirecting...', 'Please wait whilst you are redirected', 3);
exit;
}
unset($pi[0]);
if( substr($pi[1], 0, strlen($this->nslist['Special'])) == $this->nslist['Special'] || substr($pi[1], 0, strlen($this->nslist['Template'])) == $this->nslist['Template'] )
{
$pi2 = $pi[1];
}
else
{
$pi2 = implode('/', $pi);
}
$this->page = $pi2;
$this->fullpage = implode('/', $pi);
}
else
{
$k = array_keys($_GET);
foreach($k as $c)
{
if(substr($c, 0, 1) == '/')
{
$this->page = substr($c, 1, strlen($c));
// Bugfix for apache somehow passing dots as underscores
global $mime_types;
$exts = array_keys($mime_types);
$exts = '(' . implode('|', $exts) . ')';
if ( preg_match( '#_'.$exts.'#i', $this->page ) )
{
$this->page = preg_replace( '#_'.$exts.'#i', '.\\1', $this->page );
}
$this->fullpage = $this->page;
if(substr($this->page, 0, strlen($this->nslist['Special']))==$this->nslist['Special'] || substr($this->page, 0, strlen($this->nslist['Template']))==$this->nslist['Template'])
{
$ex = explode('/', $this->page);
$this->page = $ex[0];
}
if(strstr($this->page, ' '))
{
$loc = str_replace(' ', '_', urldecode(rawurldecode($_SERVER['REQUEST_URI'])));
$loc = str_replace('+', '_', $loc);
$loc = str_replace('%20', '_', $loc);
redirect($loc, 'Redirecting...', 'Space in the URL detected, please wait whilst you are redirected', 0);
exit;
}
break;
}
}
if(!$this->page && !($this->page == '' && getConfig('main_page') == ''))
{
$this->main_page();
}
}
}
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']);
}
$sp_link = '<a href="' . makeUrlNS('Special', 'SpecialPages') . '">here</a>';
redirect($main_page, 'Can\'t load special page', 'The special page you requested could not be found. This may be due to a plugin failing to load. A list of all special pages on this website can be viewed '.$sp_link.'. You will be redirected to the main page in 15 seconds.', 14);
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();
}
function add_page($flags)
{
$flags['urlname_nons'] = $flags['urlname'];
$flags['urlname'] = $this->nslist[$flags['namespace']] . $flags['urlname']; // Applies the User:/File:/etc prefixes to the URL names
$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($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);
return $message;
}
function get_pageid_from_url()
{
if(isset($_GET['title']))
{
if( $_GET['title'] == '' && getConfig('main_page') != '' )
{
$this->main_page();
}
if(strstr($_GET['title'], ' '))
{
$loc = urldecode(rawurldecode($_SERVER['REQUEST_URI']));
$loc = str_replace(' ', '_', $loc);
$loc = str_replace('+', '_', $loc);
header('Location: '.$loc);
exit;
}
$ret = $_GET['title'];
}
elseif(isset($_SERVER['PATH_INFO']))
{
$pi = explode('/', $_SERVER['PATH_INFO']);
if(!isset($pi[1]) || (isset($pi[1]) && $pi[1] == ''))
{
return false;
}
if(strstr($pi[1], ' '))
{
$loc = urldecode(rawurldecode($_SERVER['REQUEST_URI']));
$loc = str_replace(' ', '_', $loc);
$loc = str_replace('+', '_', $loc);
header('Location: '.$loc);
exit;
}
unset($pi[0]);
$pi[1] = implode('/', $pi);
$ret = $pi[1];
}
else
{
$k = array_keys($_GET);
foreach($k as $c)
{
if(substr($c, 0, 1) == '/')
{
$ret = substr($c, 1, strlen($c));
if(substr($ret, 0, strlen($this->nslist['Special'])) == $this->nslist['Special'] ||
substr($ret, 0, strlen($this->nslist['Admin'])) == $this->nslist['Admin'])
{
$ret = explode('/', $ret);
$ret = $ret[0];
}
break;
}
}
}
if ( isset($ret) )
{
if ( substr($ret, 0, ( strlen($this->nslist['Special']) )) == $this->nslist['Special'] && strstr($ret, '/') )
{
list ( $ret ) = explode('/', $ret);
}
}
return ( isset($ret) ) ? $ret : false;
}
// Parses a (very carefully formed) array into Javascript code compatible with the Tigra Tree Menu used in the admin menu
function parseAdminTree()
{
$k = array_keys($this->admin_tree);
$i = 0;
$ret = '';
$ret .= "var TREE_ITEMS = [\n ['Administration panel home', 'javascript:ajaxPage(\'".$this->nslist['Admin']."Home\');',\n ";
foreach($k as $key)
{
$i++;
$ret .= "['".$key."', 'javascript:trees[0].toggle($i)', \n";
foreach($this->admin_tree[$key] as $c)
{
$i++;
$ret .= " ['".$c['name']."', 'javascript:ajaxPage(\\'".$this->nslist['Admin'].$c['pageid']."\\');'],\n";
}
$ret .= " ],\n";
}
$ret .= " ['Log out of admin panel', 'javascript:ajaxPage(\\'".$this->nslist['Admin']."AdminLogout\\');'],\n";
$ret .= " ['<span id=\\'keepalivestat\\'>Loading keep-alive control...</span>', 'javascript:ajaxToggleKeepalive();',
['About keep-alive', '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;
}
function addAdminNode($section, $page_title, $url)
{
if(!isset($this->admin_tree[$section]))
{
$this->admin_tree[$section] = Array();
}
$this->admin_tree[$section][] = Array(
'name'=>$page_title,
'pageid'=>$url
);
}
function getParam($id = 0)
{
// using !empty here is a bugfix for IIS 5.x on Windows 2000 Server
// It may affect other IIS versions as well
if(isset($_SERVER['PATH_INFO']) && !empty($_SERVER['PATH_INFO']))
{
$pi = explode('/', $_SERVER['PATH_INFO']);
$id = $id + 2;
return isset($pi[$id]) ? $pi[$id] : false;
}
else if( isset($_GET['title']) )
{
$pi = explode('/', $_GET['title']);
$id = $id + 1;
return isset($pi[$id]) ? $pi[$id] : false;
}
else
{
$k = array_keys($_GET);
foreach($k as $c)
{
if(substr($c, 0, 1) == '/')
{
// Bugfix for apache somehow passing dots as underscores
global $mime_types;
$exts = array_keys($mime_types);
$exts = '(' . implode('|', $exts) . ')';
if ( preg_match( '#_'.$exts.'#i', $c ) )
$c = preg_replace( '#_'.$exts.'#i', '.\\1', $c );
$pi = explode('/', $c);
$id = $id + 2;
return isset($pi[$id]) ? $pi[$id] : false;
}
}
return false;
}
}
function getAllParams()
{
// using !empty here is a bugfix for IIS 5.x on Windows 2000 Server
// It may affect other IIS versions as well
if(isset($_SERVER['PATH_INFO']) && !empty($_SERVER['PATH_INFO']))
{
$pi = explode('/', $_SERVER['PATH_INFO']);
unset($pi[0], $pi[1]);
return implode('/', $pi);
}
else if( isset($_GET['title']) )
{
$pi = explode('/', $_GET['title']);
unset($pi[0]);
return implode('/', $pi);
}
else
{
$k = array_keys($_GET);
foreach($k as $c)
{
if(substr($c, 0, 1) == '/')
{
// Bugfix for apache somehow passing dots as underscores
global $mime_types;
$exts = array_keys($mime_types);
$exts = '(' . implode('|', $exts) . ')';
if ( preg_match( '#_'.$exts.'#i', $c ) )
$c = preg_replace( '#_'.$exts.'#i', '.\\1', $c );
$pi = explode('/', $c);
unset($pi[0], $pi[1]);
return implode('/', $pi);
}
}
return false;
}
}
/**
* 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;
}
/**
* Rebuilds the search index
* @param bool If true, prints out status messages
*/
function rebuild_search_index($verbose = false)
{
global $db, $session, $paths, $template, $plugins; // Common objects
$search = new Searcher();
if ( $verbose )
{
echo '<p>';
}
$texts = Array();
$textq = $db->sql_unbuffered_query($this->fetch_page_search_resource());
if(!$textq) $db->_die('');
while($row = $db->fetchrow())
{
if ( $verbose )
{
ob_start();
echo "Indexing page " . $this->nslist[$row['namespace']] . sanitize_page_id($row['page_id']) . "<br />";
ob_flush();
while (@ob_end_flush());
flush();
}
if ( isset($this->nslist[$row['namespace']]) )
{
$idstring = $this->nslist[$row['namespace']] . sanitize_page_id($row['page_id']);
if ( isset($this->pages[$idstring]) )
{
$page = $this->pages[$idstring];
}
else
{
$page = array('name' => dirtify_page_id($row['page_id']));
}
}
else
{
$page = array('name' => dirtify_page_id($row['page_id']));
}
// make sure the page is indexable
if ( isset($page['visible']) )
{
if ( $page['visible'] == 0 )
{
// not indexable, just continue
continue;
}
}
$texts[(string)$row['page_idstring']] = $row['page_text'] . ' ' . $page['name'];
}
if ( $verbose )
{
ob_start();
echo "Calculating word list...";
ob_flush();
while (@ob_end_flush());
flush();
}
$search->buildIndex($texts);
if ( $verbose )
{
echo '</p>';
}
// echo '<pre>'.print_r($search->index, true).'</pre>';
// return;
$q = $db->sql_query('DELETE FROM '.table_prefix.'search_index');
if(!$q) return false;
$secs = Array();
$q = 'INSERT INTO '.table_prefix.'search_index(word,page_names) VALUES';
foreach($search->index as $word => $pages)
{
$secs[] = '(\''.$db->escape($word).'\', \''.$db->escape($pages).'\')';
}
$q .= implode(',', $secs);
unset($secs);
$q .= ';';
$result = $db->sql_query($q);
$db->free_result();
if($result)
return true;
else
$db->_die('The search index was trying to rebuild itself when the error occured.');
}
/**
* 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();
// if the page shouldn't be indexed, send a blank set of strings to the indexing engine
if ( $this->pages[$idstring]['visible'] == 0 )
{
$search->buildIndex(Array("ns={$namespace};pid={$page_id}"=>''));
}
else
{
$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,page_names) VALUES';
foreach($new_index as $word => $pages)
{
$secs[] = '(\''.$db->escape($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 "<debug> 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;
}
}
?>