2015-09-19 09:19:56 +00:00
< ? php
$start_time = time ( true );
{ settings }
///////////////////////////////////////////////////////////////////////////////////////////////
/////////////// Do not edit below this line unless you know what you are doing! ///////////////
///////////////////////////////////////////////////////////////////////////////////////////////
2015-10-10 09:24:04 +00:00
$version = " { version} " ;
2015-11-05 21:05:07 +00:00
/// Environment ///
2015-09-21 20:02:27 +00:00
$env = new stdClass ();
$env -> action = $settings -> defaultaction ;
$env -> page = " " ;
$env -> user = " Anonymous " ;
$env -> is_logged_in = false ;
$env -> is_admin = false ;
2015-11-06 20:11:41 +00:00
$env -> storage_prefix = $settings -> data_storage_dir . DIRECTORY_SEPARATOR ;
2015-11-05 21:05:07 +00:00
/// Paths ///
2015-11-05 10:00:04 +00:00
$paths = new stdClass ();
2015-11-05 21:05:07 +00:00
$paths -> pageindex = " pageindex.json " ; // The pageindex
2015-11-06 20:11:41 +00:00
$paths -> searchindex = " invindex.json " ; // The inverted index used for searching
2015-11-05 21:05:07 +00:00
$paths -> idindex = " idindex.json " ; // The index that converts ids to page names
// Prepend the storage data directory to all the defined paths.
foreach ( $paths as & $path ) {
2015-12-05 18:59:23 +00:00
$path = $env -> storage_prefix . $path ;
2015-11-05 21:05:07 +00:00
}
$paths -> upload_file_prefix = " Files/ " ; // The prefix to append to uploaded files
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
session_start ();
///////// Login System /////////
2015-09-21 20:02:27 +00:00
// Clear expired sessions
2015-09-19 09:19:56 +00:00
if ( isset ( $_SESSION [ " $settings->sessionprefix -expiretime " ]) and
$_SESSION [ " $settings->sessionprefix -expiretime " ] < time ())
{
2015-09-21 20:02:27 +00:00
// Clear the session variables
2015-09-19 09:19:56 +00:00
$_SESSION = [];
session_destroy ();
2015-09-21 20:02:27 +00:00
$env -> is_logged_in = false ;
$env -> user = " Anonymous " ;
2015-09-19 09:19:56 +00:00
}
if ( ! isset ( $_SESSION [ $settings -> sessionprefix . " -user " ]) and
! isset ( $_SESSION [ $settings -> sessionprefix . " -pass " ]))
{
2015-09-21 20:02:27 +00:00
// The user is not logged in
$env -> is_logged_in = false ;
2015-09-19 09:19:56 +00:00
}
else
{
2015-09-21 20:02:27 +00:00
$env -> user = $_SESSION [ $settings -> sessionprefix . " -user " ];
2015-09-22 13:22:34 +00:00
$env -> pass = $_SESSION [ $settings -> sessionprefix . " -pass " ];
if ( $settings -> users [ $env -> user ] == $env -> pass )
2015-09-19 09:19:56 +00:00
{
2015-09-21 20:02:27 +00:00
// The user is logged in
$env -> is_logged_in = true ;
2015-09-19 09:19:56 +00:00
}
else
{
2015-09-21 20:02:27 +00:00
// The user's login details are invalid (what is going on here?)
// Unset the session variables, treat them as an anonymous user,
// and get out of here
$env -> is_logged_in = false ;
$env -> user = " Anonymous " ;
2015-09-22 13:34:18 +00:00
$env -> pass = " " ;
2015-09-21 20:02:27 +00:00
// Clear the session data
2015-09-22 13:34:18 +00:00
$_SESSION = []; //delete all the variables
2015-09-19 09:19:56 +00:00
session_destroy (); //destroy the session
}
}
//check to see if the currently logged in user is an admin
2015-09-21 20:02:27 +00:00
$env -> is_admin = false ;
if ( $env -> is_logged_in )
2015-09-19 09:19:56 +00:00
{
foreach ( $settings -> admins as $admin_username )
{
2015-09-21 20:02:27 +00:00
if ( $admin_username == $env -> user )
2015-09-19 09:19:56 +00:00
{
2015-09-21 20:02:27 +00:00
$env -> is_admin = true ;
2015-09-19 09:19:56 +00:00
break ;
}
}
}
/////// Login System End ///////
2016-03-26 15:55:50 +00:00
///////////////////////////////////////////////////////////////////////////////
////////////////////////////////// Functions //////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
2015-09-19 09:19:56 +00:00
2016-03-26 15:55:50 +00:00
/**
* Converts a filesize into a human - readable string .
* From http :// php . net / manual / en / function . filesize . php #106569
* Edited by Starbeamrainbowlabs .
* @ param number $bytes The number of bytes to convert .
* @ param number $decimals The number of decimal places to preserve .
* @ return string A human - readable filesize .
2015-09-19 09:19:56 +00:00
*/
function human_filesize ( $bytes , $decimals = 2 )
{
2015-12-16 19:05:08 +00:00
$sz = [ " b " , " kb " , " mb " , " gb " , " tb " , " pb " , " eb " , " yb " , " zb " ];
2015-09-19 09:19:56 +00:00
$factor = floor (( strlen ( $bytes ) - 1 ) / 3 );
2015-12-16 19:05:08 +00:00
$result = round ( $bytes / pow ( 1024 , $factor ), $decimals );
return $result . @ $sz [ $factor ];
2015-09-19 09:19:56 +00:00
}
2015-12-21 14:39:56 +00:00
2016-03-26 15:55:50 +00:00
/**
* Calculates the time sincce a particular timestamp and returns a
* human - readable result .
* From http :// goo . gl / zpgLgq .
* @ param integer $time The timestamp to convert .
* @ return string The time since the given timestamp as
* a human - readable string .
2015-09-19 09:19:56 +00:00
*/
function human_time_since ( $time )
{
$timediff = time () - $time ;
$tokens = array (
31536000 => 'year' ,
2592000 => 'month' ,
604800 => 'week' ,
86400 => 'day' ,
3600 => 'hour' ,
60 => 'minute' ,
1 => 'second'
);
foreach ( $tokens as $unit => $text ) {
if ( $timediff < $unit ) continue ;
$numberOfUnits = floor ( $timediff / $unit );
return $numberOfUnits . ' ' . $text . (( $numberOfUnits > 1 ) ? 's' : '' ) . ' ago' ;
}
}
2016-03-26 15:55:50 +00:00
/**
* A recursive glob () function .
* From http :// in . php . net / manual / en / function . glob . php #106595
* @ param string $pattern The glob pattern to use to find filenames .
* @ p aram integer $flags The glob flags to use when finding filenames .
* @ return array An array of the filepaths that match the given glob .
2015-09-19 09:19:56 +00:00
*/
function glob_recursive ( $pattern , $flags = 0 )
{
$files = glob ( $pattern , $flags );
foreach ( glob ( dirname ( $pattern ) . '/*' , GLOB_ONLYDIR | GLOB_NOSORT ) as $dir )
{
$prefix = " $dir / " ;
// Remove the "./" from the beginning if it exists
if ( substr ( $prefix , 0 , 2 ) == " ./ " ) $prefix = substr ( $prefix , 2 );
$files = array_merge ( $files , glob_recursive ( $prefix . basename ( $pattern ), $flags ));
}
return $files ;
}
2016-03-26 15:55:50 +00:00
/**
* Gets a list of all the sub pages of the current page .
* @ param object $pageindex The pageindex to use to search .
* @ param string $pagename The name of the page to list the sub pages of .
* @ return object An object containing all the subpages and their
* respective distances from the given page name in the pageindex tree .
2015-09-19 09:19:56 +00:00
*/
function get_subpages ( $pageindex , $pagename )
{
$pagenames = get_object_vars ( $pageindex );
$result = new stdClass ();
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$stem = " $pagename / " ;
$stem_length = strlen ( $stem );
foreach ( $pagenames as $entry => $value )
{
if ( substr ( $entry , 0 , $stem_length ) == $stem )
{
// We found a subpage
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
// Extract the subpage's key relative to the page that we are searching for
$subpage_relative_key = substr ( $entry , $stem_length , - 3 );
// Calculate how many times removed the current subpage is from the current page. 0 = direct descendant.
$times_removed = substr_count ( $subpage_relative_key , " / " );
// Store the name of the subpage we found
$result -> $entry = $times_removed ;
}
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
unset ( $pagenames );
return $result ;
}
2016-03-26 15:55:50 +00:00
/**
* Makes sure that a subpage ' s parents exist .
* Note this doesn ' t check the pagename itself .
2015-09-19 09:19:56 +00:00
* @ param The pagename to check .
*/
function check_subpage_parents ( $pagename )
{
2015-11-12 10:01:21 +00:00
global $pageindex , $paths ;
2015-09-19 09:19:56 +00:00
// Save the new pageindex and return if there aren't any more parent pages to check
if ( strpos ( $pagename , " / " ) === false )
{
2015-11-12 10:01:21 +00:00
file_put_contents ( $paths -> pageindex , json_encode ( $pageindex , JSON_PRETTY_PRINT ));
2015-09-19 09:19:56 +00:00
return ;
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$parent_pagename = substr ( $pagename , 0 , strrpos ( $pagename , " / " ));
$parent_page_filename = " $parent_pagename .md " ;
if ( ! file_exists ( $parent_page_filename ))
{
// This parent page doesn't exist! Create it and add it to the page index.
touch ( $parent_page_filename , 0 );
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$newentry = new stdClass ();
$newentry -> filename = $parent_page_filename ;
$newentry -> size = 0 ;
$newentry -> lastmodified = 0 ;
$newentry -> lasteditor = " none " ;
$pageindex -> $parent_pagename = $newentry ;
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
check_subpage_parents ( $parent_pagename );
}
2016-03-26 15:55:50 +00:00
/**
* Makes a path safe .
* Paths may only contain alphanumeric characters , spaces , underscores , and
* dashes .
* @ param string $string The string to make safe .
* @ return string A safe version of the given string .
2015-09-19 09:19:56 +00:00
*/
function makepathsafe ( $string )
{
2015-10-22 07:54:59 +00:00
$string = preg_replace ( " /[^0-9a-zA-Z \ _ \ - \ \ / \ .]/i " , " " , $string );
$string = preg_replace ( " / \ .+/ " , " . " , $string );
return $string ;
2015-09-19 09:19:56 +00:00
}
2016-03-26 15:55:50 +00:00
/**
* Hides an email address from bots by adding random html entities .
* @ param string $str The original email address
* @ return string The mangled email address .
2015-09-19 09:19:56 +00:00
*/
function hide_email ( $str )
{
$hidden_email = " " ;
for ( $i = 0 ; $i < strlen ( $str ); $i ++ )
{
if ( $str [ $i ] == " @ " )
{
$hidden_email .= " &# " . ord ( " @ " ) . " ; " ;
continue ;
}
if ( rand ( 0 , 1 ) == 0 )
$hidden_email .= $str [ $i ];
else
$hidden_email .= " &# " . ord ( $str [ $i ]) . " ; " ;
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
return $hidden_email ;
}
2016-03-26 15:55:50 +00:00
/**
* Checks to see if $haystack starts with $needle .
* @ param string $haystack The string to search .
* @ param string $needle The string to search for at the beginning
* of $haystack .
* @ return boolean Whether $needle can be found at the beginning
* of $haystack .
2015-10-22 07:38:50 +00:00
*/
function starts_with ( $haystack , $needle )
{
2015-12-05 18:59:23 +00:00
$length = strlen ( $needle );
return ( substr ( $haystack , 0 , $length ) === $needle );
2015-10-22 07:38:50 +00:00
}
2016-03-26 15:55:50 +00:00
2015-11-09 07:30:00 +00:00
/**
2016-03-26 15:55:50 +00:00
* Case - insensitively finds all occurrences of $needle in $haystack . Handles
* UTF - 8 characters correctly .
* From http :// www . pontikis . net / tip / ? id = 16 , and
2015-11-09 07:30:00 +00:00
* based on http :// www . php . net / manual / en / function . strpos . php #87061
2016-03-26 15:55:50 +00:00
* @ param string $haystack The string to search .
* @ param string $needle The string to find .
* @ return array || false An array of match indices , or false if
* nothing was found .
2015-11-09 07:30:00 +00:00
*/
function mb_stripos_all ( $haystack , $needle ) {
$s = 0 ; $i = 0 ;
while ( is_integer ( $i )) {
$i = function_exists ( " mb_stripos " ) ? mb_stripos ( $haystack , $needle , $s ) : stripos ( $haystack , $needle , $s );
if ( is_integer ( $i )) {
$aStrPos [] = $i ;
$s = $i + ( function_exists ( " mb_strlen " ) ? mb_strlen ( $needle ) : strlen ( $needle ));
}
}
if ( isset ( $aStrPos ))
return $aStrPos ;
else
return false ;
}
2015-10-22 07:38:50 +00:00
2016-03-26 15:55:50 +00:00
/**
* Returns the system ' s mime type mappings , considering the first extension
* listed to be cacnonical .
* @ return array An array of mime type mappings .
*/
2015-10-22 07:51:43 +00:00
function system_mime_type_extensions () {
2015-10-22 07:38:50 +00:00
global $settings ;
2015-12-05 18:59:23 +00:00
$out = array ();
$file = fopen ( $settings -> mime_extension_mappings_location , 'r' );
while (( $line = fgets ( $file )) !== false ) {
$line = trim ( preg_replace ( '/#.*/' , '' , $line ));
if ( ! $line )
continue ;
$parts = preg_split ( '/\s+/' , $line );
if ( count ( $parts ) == 1 )
continue ;
$type = array_shift ( $parts );
if ( ! isset ( $out [ $type ]))
$out [ $type ] = array_shift ( $parts );
}
fclose ( $file );
return $out ;
2015-10-22 07:38:50 +00:00
}
2016-03-26 15:55:50 +00:00
/**
* Converts a given mime type to it ' s associated file extension .
* @ param string $type The mime type to convert .
* @ return string The extension for the given mime type .
*/
2015-10-22 07:38:50 +00:00
function system_mime_type_extension ( $type ) {
2015-12-05 18:59:23 +00:00
static $exts ;
if ( ! isset ( $exts ))
$exts = system_mime_type_extensions ();
return isset ( $exts [ $type ]) ? $exts [ $type ] : null ;
2015-10-22 07:38:50 +00:00
}
2015-09-19 09:19:56 +00:00
2016-03-26 15:55:50 +00:00
///////////////////////////////////////////////////////////////////////////////
2015-09-19 09:19:56 +00:00
2016-03-26 15:55:50 +00:00
///////////////////////////////////////////////////////////////////////////////
////////////////////// Security and Consistency Measures //////////////////////
///////////////////////////////////////////////////////////////////////////////
2015-09-19 09:19:56 +00:00
/*
2015-11-12 11:03:41 +00:00
* Sort out the pageindex . Create it if it doesn ' t exist , and load + parse it
* if it does .
2015-09-19 09:19:56 +00:00
*/
2015-11-06 20:11:41 +00:00
if ( ! file_exists ( $paths -> pageindex ))
2015-09-19 09:19:56 +00:00
{
2015-12-05 18:59:23 +00:00
$glob_str = $env -> storage_prefix . " *.md " ;
2015-11-14 21:24:57 +00:00
$existingpages = glob_recursive ( $glob_str );
2015-12-05 18:59:23 +00:00
// Debug statements. Uncomment when debugging the pageindex regenerator.
// var_dump($env->storage_prefix);
// var_dump($glob_str);
// var_dump($existingpages);
2015-09-19 09:19:56 +00:00
$pageindex = new stdClass ();
// We use a for loop here because foreach doesn't loop over new values inserted
// while we were looping
for ( $i = 0 ; $i < count ( $existingpages ); $i ++ )
{
$pagefilename = $existingpages [ $i ];
2015-12-05 18:59:23 +00:00
2015-09-19 09:19:56 +00:00
// Create a new entry
$newentry = new stdClass ();
2015-11-14 21:24:57 +00:00
$newentry -> filename = utf8_encode ( substr ( // Store the filename, whilst trimming the storage prefix
2015-12-05 18:59:23 +00:00
$pagefilename ,
strlen ( preg_replace ( " /^ \ . \ //i " , " " , $env -> storage_prefix )) // glob_recursive trim the ./ from returned filenames , so we need to as well
));
// Remove the `./` from the beginning if it's still hanging around
if ( substr ( $newentry -> filename , 0 , 2 ) == " ./ " )
$newentry -> filename = substr ( $newentry -> filename , 2 );
2015-09-19 09:19:56 +00:00
$newentry -> size = filesize ( $pagefilename ); // Store the page size
$newentry -> lastmodified = filemtime ( $pagefilename ); // Store the date last modified
// Todo find a way to keep the last editor independent of the page index
$newentry -> lasteditor = utf8_encode ( " unknown " ); // Set the editor to "unknown"
2015-11-14 21:24:57 +00:00
// Extract the name of the (sub)page without the ".md"
$pagekey = utf8_encode ( substr ( $newentry -> filename , 0 , - 3 ));
2015-12-05 18:59:23 +00:00
if ( file_exists ( $env -> storage_prefix . $pagekey ) && // If it exists...
! is_dir ( $env -> storage_prefix . $pagekey )) // ...and isn't a directory
{
// This page (potentially) has an associated file!
// Let's investigate.
// Blindly add the file to the pageindex for now.
// Future We might want to do a security check on the file later on.
// File a bug if you think we should do this.
$newentry -> uploadedfile = true ; // Yes this page does have an uploaded file associated with it
$newentry -> uploadedfilepath = $pagekey ; // It's stored here
// Work out what kind of file it really is
$mimechecker = finfo_open ( FILEINFO_MIME_TYPE );
$newentry -> uploadedfilemime = finfo_file ( $mimechecker , $env -> storage_prefix . $pagekey );
}
// Debug statements. Uncomment when debugging the pageindex regenerator.
// echo("pagekey: ");
// var_dump($pagekey);
// echo("newentry: ");
// var_dump($newentry);
2015-09-19 09:19:56 +00:00
// Subpage parent checker
if ( strpos ( $pagekey , " / " ) !== false )
{
// We have a sub page people
// Work out what our direct parent's key must be in order to check to
// make sure that it actually exists. If it doesn't, then we need to
// create it.
$subpage_parent_key = substr ( $pagekey , 0 , strrpos ( $pagekey , " / " ));
2015-11-06 20:11:41 +00:00
$subpage_parent_filename = " $env->storage_prefix $subpage_parent_key .md " ;
2015-09-19 09:19:56 +00:00
if ( array_search ( $subpage_parent_filename , $existingpages ) === false )
{
2015-10-25 21:02:20 +00:00
// Our parent page doesn't actually exist - create it
2015-09-19 09:19:56 +00:00
touch ( $subpage_parent_filename , 0 );
// Furthermore, we should add this page to the list of existing pages
// in order for it to be indexed
$existingpages [] = $subpage_parent_filename ;
}
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
// Store the new entry in the new page index
$pageindex -> $pagekey = $newentry ;
}
2015-11-06 20:11:41 +00:00
file_put_contents ( $paths -> pageindex , json_encode ( $pageindex , JSON_PRETTY_PRINT ));
2015-09-19 09:19:56 +00:00
unset ( $existingpages );
}
else
{
2015-10-25 20:59:21 +00:00
$pageindex_read_start = microtime ( true );
2015-11-06 20:11:41 +00:00
$pageindex = json_decode ( file_get_contents ( $paths -> pageindex ));
2015-10-25 20:59:21 +00:00
header ( " x-pageindex-decode-time: " . round ( microtime ( true ) - $pageindex_read_start , 6 ) . " ms " );
2015-09-19 09:19:56 +00:00
}
2015-10-28 14:31:27 +00:00
//////////////////////////
///// Page id system /////
//////////////////////////
2015-11-06 20:11:41 +00:00
if ( ! file_exists ( $paths -> idindex ))
file_put_contents ( $paths -> idindex , " { } " );
$idindex = json_decode ( file_get_contents ( $paths -> idindex ));
2015-10-28 14:31:27 +00:00
class ids
{
/*
* @ summary Gets the page id associated with the given pagename .
*/
public static function getid ( $pagename )
{
global $idindex ;
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
foreach ( $idindex as $id => $entry )
{
if ( $entry == $pagename )
return $id ;
}
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
// This pagename doesn't have an id - assign it one quick!
return self :: assign ( $pagename );
}
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
/*
* @ summary Gets the page name associated with the given page id .
*/
public static function getpagename ( $id )
{
global $idindex ;
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
if ( ! isset ( $idindex -> $id ))
return false ;
else
return $idindex -> $id ;
}
2015-12-05 18:59:23 +00:00
/*
* @ summary Moves a page in the id index from $oldpagename to $newpagename .
* Note that this function doesn ' t perform any special checks to
* make sure that the destination name doesn ' t already exist .
*/
public static function movepagename ( $oldpagename , $newpagename )
{
global $idindex , $paths ;
$pageid = self :: getid ( $oldpagename );
$idindex -> $pageid = $newpagename ;
file_put_contents ( $paths -> idindex , json_encode ( $idindex ));
}
/*
* @ summary Removes the given page name from the id index . Note that this
* function doesn ' t handle multiple entries with the same name .
*/
public static function deletepagename ( $pagename )
{
global $idindex , $paths ;
// Get the id of the specified page
$pageid = self :: getid ( $pagename );
// Remove it from the pageindex
unset ( $idindex -> $pageid );
// Save the id index
file_put_contents ( $paths -> idindex , json_encode ( $idindex ));
}
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
/*
* @ summary Assigns an id to a pagename . Doesn ' t check to make sure that
* pagename doesn ' t exist in the pageindex .
*/
protected static function assign ( $pagename )
{
2015-11-09 07:25:28 +00:00
global $idindex , $paths ;
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
$nextid = count ( array_keys ( get_object_vars ( $idindex )));
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
if ( isset ( $idindex -> $nextid ))
throw new Exception ( " The pageid is corrupt! Pepperminty Wiki generated the id $nextid , but that id is already in use. " );
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
// Update the id index
$idindex -> $nextid = utf8_encode ( $pagename );
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
// Save the id index
2015-11-06 20:11:41 +00:00
file_put_contents ( $paths -> idindex , json_encode ( $idindex ));
2015-11-05 10:00:04 +00:00
2015-10-28 14:31:27 +00:00
return $nextid ;
}
}
//////////////////////////
//////////////////////////
// Work around an Opera + Syntaxtic bug where there is no margin at the left
// hand side if there isn't a query string when accessing a .php file.
2015-09-19 09:19:56 +00:00
if ( ! isset ( $_GET [ " action " ]) and ! isset ( $_GET [ " page " ]))
{
http_response_code ( 302 );
2015-09-19 09:24:20 +00:00
header ( " location: index.php?action= $settings->defaultaction &page= $settings->defaultpage " );
2015-09-19 09:19:56 +00:00
exit ();
}
// Make sure that the action is set
if ( ! isset ( $_GET [ " action " ]))
$_GET [ " action " ] = $settings -> defaultaction ;
// Make sure that the page is set
if ( ! isset ( $_GET [ " page " ]) or strlen ( $_GET [ " page " ]) === 0 )
$_GET [ " page " ] = $settings -> defaultpage ;
// Redirect the user to the safe version of the path if they entered an unsafe character
if ( makepathsafe ( $_GET [ " page " ]) !== $_GET [ " page " ])
{
http_response_code ( 301 );
header ( " location: index.php?action= " . rawurlencode ( $_GET [ " action " ]) . " &page= " . makepathsafe ( $_GET [ " page " ]));
header ( " x-requested-page: " . $_GET [ " page " ]);
header ( " x-actual-page: " . makepathsafe ( $_GET [ " page " ]));
exit ();
}
2015-09-21 20:02:27 +00:00
2015-11-14 15:22:35 +00:00
// Finish setting up the environment object
2015-09-21 20:02:27 +00:00
$env -> page = $_GET [ " page " ];
$env -> action = strtolower ( $_GET [ " action " ]);
2015-09-19 09:19:56 +00:00
2016-03-26 15:55:50 +00:00
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
2015-09-19 09:19:56 +00:00
2016-03-26 15:55:50 +00:00
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////// HTML fragments ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
2015-09-19 09:19:56 +00:00
class page_renderer
{
public static $html_template = " <!DOCTYPE html>
< html >
< head >
< meta charset = 'utf-8' />
< title > { title } </ title >
< meta name = 'viewport' content = 'width=device-width, initial-scale=1' />
< link rel = 'shortcut-icon' href = '{favicon-url}' />
< link rel = 'icon' href = '{favicon-url}' />
{ header - html }
</ head >
< body >
{ body }
<!-- Took { generation - time - taken } seconds to generate -->
</ body >
</ html >
" ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
public static $main_content_template = " { navigation-bar}
< h1 class = 'sitename' > { sitename } </ h1 >
< main >
{ content }
</ main >
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
< footer >
2015-09-30 05:39:01 +00:00
< p > { footer - message } </ p >
2015-10-16 19:28:35 +00:00
< p > Powered by Pepperminty Wiki { version }, which was built by < a href = '//starbeamrainbowlabs.com/' > Starbeamrainbowlabs </ a >. Send bugs to 'bugs at starbeamrainbowlabs dot com' or < a href = '//github.com/sbrl/Pepperminty-Wiki' title = 'Github Issue Tracker' > open an issue </ a >.</ p >
< p > Your local friendly administrators are { admins - name - list } .</ p >
2015-09-19 09:19:56 +00:00
< p > This wiki is managed by < a href = 'mailto:{admin-details-email}' > { admin - details - name } </ a >.</ p >
</ footer >
{ navigation - bar - bottom }
{ all - pages - datalist } " ;
public static $minimal_content_template = " <main class='printable'> { content}</main>
< footer class = 'printable' >
< hr class = 'footerdivider' />
< p >< em > From { sitename }, which is managed by { admin - details - name } .</ em ></ p >
2015-09-30 05:39:01 +00:00
< p > { footer - message } </ p >
< p >< em > Timed at { generation - date } </ em ></ p >
2015-10-10 09:24:04 +00:00
< p >< em > Powered by Pepperminty Wiki { version } .</ em ></ p >
2015-09-19 09:19:56 +00:00
</ footer > " ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
// An array of functions that have been registered to process the
// find / replace array before the page is rendered. Note that the function
// should take a *reference* to an array as its only argument.
protected static $part_processors = [];
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
// Registers a function as a part post processor.
public static function register_part_preprocessor ( $function )
{
2015-09-21 20:02:27 +00:00
global $settings ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
// Make sure that the function we are about to register is valid
if ( ! is_callable ( $function ))
{
http_response_code ( 500 );
$admin_name = $settings -> admindetails [ " name " ];
$admin_email = hide_email ( $settings -> admindetails [ " email " ]);
exit ( page_renderer :: render ( " $settings->sitename - Module Error " , " <p> $settings->sitename has got a misbehaving module installed that tried to register an invalid HTML handler with the page renderer. Please contact $settings->sitename 's administrator $admin_name at <a href='mailto: $admin_email '> $admin_email </a>. " ));
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
self :: $part_processors [] = $function ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
return true ;
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
public static function render ( $title , $content , $body_template = false )
{
2015-10-10 09:24:04 +00:00
global $settings , $start_time , $version ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
if ( $body_template === false )
$body_template = self :: $main_content_template ;
2015-11-05 10:00:04 +00:00
2015-10-08 09:07:51 +00:00
if ( strlen ( $settings -> logo_url ) > 0 )
{
// A logo url has been specified
2016-01-23 14:21:05 +00:00
$logo_html = " <img class='logo " . ( isset ( $_GET [ " printable " ]) ? " small " : " " ) . " ' src=' $settings->logo_url ' /> " ;
2015-10-08 09:07:51 +00:00
switch ( $settings -> logo_position )
{
case " left " :
$logo_html = " $logo_html $settings->sitename " ;
break ;
case " right " :
$logo_html .= " $settings->sitename " ;
break ;
default :
throw new Exception ( " Invalid logo_position ' $settings->logo_position '. Valid values are either \" left \" or \" right \" and are case sensitive. " );
}
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$parts = [
" { body} " => $body_template ,
2015-11-05 10:00:04 +00:00
2015-10-08 09:07:51 +00:00
" { sitename} " => $logo_html ,
2015-10-10 09:24:04 +00:00
" { version} " => $version ,
2015-09-19 09:19:56 +00:00
" { favicon-url} " => $settings -> favicon ,
2016-04-03 16:49:38 +00:00
" { header-html} " => self :: get_header_html (),
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
" { navigation-bar} " => self :: render_navigation_bar ( $settings -> nav_links , $settings -> nav_links_extra , " top " ),
" { navigation-bar-bottom} " => self :: render_navigation_bar ( $settings -> nav_links_bottom , [], " bottom " ),
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
" { admin-details-name} " => $settings -> admindetails [ " name " ],
" { admin-details-email} " => $settings -> admindetails [ " email " ],
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
" { admins-name-list} " => implode ( " , " , $settings -> admins ),
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
" { generation-date} " => date ( " l jS \ of F Y \ a \\ t h:ia T " ),
2015-11-05 10:00:04 +00:00
2015-09-30 05:39:01 +00:00
" { all-pages-datalist} " => self :: generate_all_pages_datalist (),
2015-11-05 10:00:04 +00:00
2015-10-24 09:16:26 +00:00
" { footer-message} " => $settings -> footer_message ,
2015-11-05 10:00:04 +00:00
2015-10-24 09:16:26 +00:00
/// Secondary Parts ///
2015-11-05 10:00:04 +00:00
2015-10-24 09:16:26 +00:00
" { content} " => $content ,
" { title} " => $title ,
2015-09-19 09:19:56 +00:00
];
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
// Pass the parts through the part processors
foreach ( self :: $part_processors as $function )
{
$function ( $parts );
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$result = self :: $html_template ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$result = str_replace ( array_keys ( $parts ), array_values ( $parts ), $result );
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$result = str_replace ([
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
], [
], $result );
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$result = str_replace ( " { generation-time-taken} " , microtime ( true ) - $start_time , $result );
return $result ;
}
public static function render_main ( $title , $content )
{
return self :: render ( $title , $content , self :: $main_content_template );
}
public static function render_minimal ( $title , $content )
{
return self :: render ( $title , $content , self :: $minimal_content_template );
}
2015-12-05 18:59:23 +00:00
2016-04-03 16:49:38 +00:00
public static function get_header_html ()
{
global $settings ;
$result = self :: get_css_as_html ();
if ( ! empty ( $settings -> enable_math_rendering ))
$result .= " <script type='text/x-mathjax-config'>
MathJax . Hub . Config ({
tex2jax : {
inlineMath : [ [ '$' , '$' ], [ '\\\\(' , '\\\\)' ] ],
processEscapes : true ,
skipTags : [ 'script' , 'noscript' , 'style' , 'textarea' , 'pre' , 'code' ]
}
});
</ script >
< script async src = 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML' ></ script > " ;
return $result ;
}
2015-09-19 09:19:56 +00:00
public static function get_css_as_html ()
{
global $settings ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
if ( preg_match ( " /^[^ \ /]* \ / \ /|^ \ // " , $settings -> css ))
return " <link rel='stylesheet' href=' $settings->css ' /> " ;
else
return " <style> $settings->css </style> " ;
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
public static $nav_divider = " <span class='nav-divider inflexible'> | </span> " ;
2015-11-05 10:00:04 +00:00
2016-03-26 15:55:50 +00:00
/**
* Renders a navigation bar from an array of links . See
* $settings -> nav_links for format information .
* @ param array $nav_links The links to add to the navigation bar .
* @ param array $nav_links_extra The extra nav links to add to
* the " More... " menu .
2015-09-19 09:19:56 +00:00
*/
public static function render_navigation_bar ( $nav_links , $nav_links_extra , $class = " " )
{
2015-09-21 20:02:27 +00:00
global $settings , $env ;
2015-09-19 09:19:56 +00:00
$result = " <nav class=' $class '> \n " ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
// Loop over all the navigation links
foreach ( $nav_links as $item )
{
if ( is_string ( $item ))
{
// The item is a string
switch ( $item )
{
//keywords
case " user-status " :
2015-09-21 20:02:27 +00:00
if ( $env -> is_logged_in )
2015-09-19 09:19:56 +00:00
{
2015-09-22 13:54:03 +00:00
$result .= " <span class='inflexible'>Logged in as " . self :: render_username ( $env -> user ) . " .</span> " /* . page_renderer::$nav_divider*/ ;
2015-09-19 09:19:56 +00:00
$result .= " <span><a href='index.php?action=logout'>Logout</a></span> " ;
$result .= page_renderer :: $nav_divider ;
}
else
2015-11-14 15:48:30 +00:00
$result .= " <span class='inflexible'>Browsing as Anonymous.</span> " . /*page_renderer::$nav_divider . */ " <span><a href='index.php?action=login&returnto= " . rawurlencode ( $_SERVER [ " REQUEST_URI " ]) . " '>Login</a></span> " . page_renderer :: $nav_divider ;
2015-09-19 09:19:56 +00:00
break ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
case " search " : // Displays a search bar
2015-11-01 10:13:35 +00:00
$result .= " <span class='inflexible'><form method='get' action='index.php' style='display: inline;'><input type='search' name='page' list='allpages' placeholder='Type a page name here and hit enter' /><input type='hidden' name='search-redirect' value='true' /></form></span> " ;
2015-09-19 09:19:56 +00:00
break ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
case " divider " : // Displays a divider
$result .= page_renderer :: $nav_divider ;
break ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
case " menu " :
$result .= " <span class='inflexible nav-more'><label for='more-menu-toggler'>More...</label>
< input type = 'checkbox' class = 'off-screen' id = 'more-menu-toggler' /> " ;
$result .= page_renderer :: render_navigation_bar ( $nav_links_extra , [], " nav-more-menu " );
$result .= " </span> " ;
break ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
// It isn't a keyword, so just output it directly
default :
$result .= " <span> $item </span> " ;
}
}
else
{
// Output the item as a link to a url
2015-09-21 20:02:27 +00:00
$result .= " <span><a href=' " . str_replace ( " { page} " , $env -> page , $item [ 1 ]) . " '> $item[0] </a></span> " ;
2015-09-19 09:19:56 +00:00
}
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$result .= " </nav> " ;
return $result ;
}
public static function render_username ( $name )
{
global $settings ;
$result = " " ;
if ( in_array ( $name , $settings -> admins ))
$result .= $settings -> admindisplaychar ;
$result .= $name ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
return $result ;
}
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
public static function generate_all_pages_datalist ()
{
global $pageindex ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
$result = " <datalist id='allpages'> \n " ;
foreach ( $pageindex as $pagename => $pagedetails )
{
$result .= " \t \t \t <option value=' $pagename ' /> \n " ;
}
$result .= " \t \t </datalist> " ;
2015-11-05 10:00:04 +00:00
2015-09-19 09:19:56 +00:00
return $result ;
}
}
2015-11-01 10:13:35 +00:00
//////////////////////////////////////
///// Extra consistency measures /////
//////////////////////////////////////
2015-11-14 15:22:35 +00:00
// Redirect to the search page if there isn't a page with the requested name
2015-11-01 10:13:35 +00:00
if ( ! isset ( $pageindex -> { $env -> page }) and isset ( $_GET [ " search-redirect " ]))
{
http_response_code ( 307 );
$url = " ?action=search&query= " . rawurlencode ( $env -> page );
header ( " location: $url " );
2015-11-11 18:49:08 +00:00
exit ( page_renderer :: render ( " Non existent page - $settings->sitename " , " <p>There isn't a page on $settings->sitename with that name. However, you could <a href=' $url '>search for this page name</a> in other pages.</p>
2015-11-01 10:13:35 +00:00
< p > Alternatively , you could < a href = '?action=edit&page=" . rawurlencode($env->page) . "&create=true' > create this page </ a >.</ p > " ));
}
2015-11-14 15:22:35 +00:00
// Redirect the user to the login page if:
// - A login is required to view this wiki
// - The user isn't already requesting the login page
// Note we use $_GET here because $env->action isn't populated at this point
if ( $settings -> require_login_view === true && // If this site requires a login in order to view pages
! $env -> is_logged_in && // And the user isn't logged in
2015-11-14 15:41:28 +00:00
! in_array ( $_GET [ " action " ], [ " login " , " checklogin " ])) // And the user isn't trying to login
2015-11-14 15:22:35 +00:00
{
2015-12-05 18:59:23 +00:00
// Redirect the user to the login page
http_response_code ( 307 );
$url = " ?action=login&returnto= " . rawurlencode ( $_SERVER [ " REQUEST_URI " ]) . " &required=true " ;
header ( " location: $url " );
exit ( page_renderer :: render ( " Login required - $settings->sitename " , " <p> $settings->sitename requires that you login before you are able to access it.</p>
< p >< a href = '$url' > Login </ a >.</ p > " ));
2015-11-14 15:22:35 +00:00
}
2015-11-01 10:13:35 +00:00
//////////////////////////////////////
//////////////////////////////////////
2015-09-19 09:19:56 +00:00
//////////////////////////
/// Module functions ///
//////////////////////////
// These functions are //
// used by modules to //
// register themselves //
// or new pages. //
//////////////////////////
$modules = []; // List that contains all the loaded modules
// Function to register a module
function register_module ( $moduledata )
{
global $modules ;
//echo("registering module\n");
//var_dump($moduledata);
$modules [] = $moduledata ;
}
2015-12-05 18:59:23 +00:00
/**
* Checks to see whether a module with the given id exists .
* @ param string $id The id to search for .
* @ return bool Whether a module is currently loaded with the given id .
*/
function module_exists ( $id )
{
global $modules ;
foreach ( $modules as $module )
{
if ( $module [ " id " ] == $id )
return true ;
}
return false ;
}
2015-09-19 09:19:56 +00:00
// Function to register an action handler
$actions = new stdClass ();
function add_action ( $action_name , $func )
{
global $actions ;
//echo("adding $action_name\n");
$actions -> $action_name = $func ;
}
2015-10-04 12:08:59 +00:00
$parsers = [
" none " => function () {
throw new Exception ( " No parser registered! " );
}
];
2016-03-26 15:55:50 +00:00
/**
* Registers a new parser .
* @ param string $name The name of the new parser to register .
* @ param function $parser_code The function to register as a new parser .
*/
2015-10-04 08:10:14 +00:00
function add_parser ( $name , $parser_code )
2015-09-19 09:19:56 +00:00
{
2015-10-04 08:10:14 +00:00
global $parsers ;
2015-10-04 12:08:59 +00:00
if ( isset ( $parsers [ $name ]))
2015-10-04 08:10:14 +00:00
throw new Exception ( " Can't register parser with name ' $name ' because a parser with that name already exists. " );
2015-11-05 10:00:04 +00:00
2015-10-04 12:08:59 +00:00
$parsers [ $name ] = $parser_code ;
2015-10-04 08:10:14 +00:00
}
function parse_page_source ( $source )
{
global $settings , $parsers ;
2015-10-04 12:08:59 +00:00
if ( ! isset ( $parsers [ $settings -> parser ]))
exit ( page_renderer :: render_main ( " Parsing error - $settings->sitename " , " <p>Parsing some page source data failed. This is most likely because $settings->sitename has the parser setting set incorrectly. Please contact <a href='mailto: " . hide_email ( $settings -> admindetails [ " email " ]) . " '> " . $settings -> admindetails [ " name " ] . " </a>, your $settings->sitename Administrator. " ));
2015-11-05 10:00:04 +00:00
2015-10-04 11:42:53 +00:00
/* Not needed atm because escaping happens when saving , not when rendering *
if ( $settings -> clean_raw_html )
$source = htmlentities ( $source , ENT_QUOTES | ENT_HTML5 );
*/
2015-10-04 12:08:59 +00:00
return $parsers [ $settings -> parser ]( $source );
2015-09-19 09:19:56 +00:00
}
2016-03-26 15:55:50 +00:00
// Function to
2015-09-30 07:08:10 +00:00
$save_preprocessors = [];
2016-03-26 15:55:50 +00:00
/**
* Register a new proprocessor that will be executed just before
* an edit is saved .
* @ param function $func The function to register .
*/
2015-09-30 07:08:10 +00:00
function register_save_preprocessor ( $func )
{
global $save_preprocessors ;
2015-10-03 12:22:19 +00:00
$save_preprocessors [] = $func ;
2015-09-30 07:08:10 +00:00
}
2015-12-21 08:45:38 +00:00
$help_sections = [];
2016-03-26 15:55:50 +00:00
/**
* Adds a new help section to the help page .
* @ param string $index The string to index the new section under .
* @ param string $title The title to display above the section .
* @ param string $content The content to display .
*/
2015-12-21 08:45:38 +00:00
function add_help_section ( $index , $title , $content )
{
2015-12-21 14:35:27 +00:00
global $help_sections ;
$help_sections [ $index ] = [
2015-12-21 08:45:38 +00:00
" title " => $title ,
" content " => $content
];
}
2015-09-19 09:19:56 +00:00
2016-04-03 16:56:42 +00:00
if ( ! empty ( $settings -> enable_math_rendering ))
add_help_section ( " 22-Mathematical-Expressions " , " Methematical Expressions " , " <p> $settings->sitename supports rendering of mathematical expressions. Mathematical expressions can be included practically anywhere in your page. Expressions should be written in LaTeX and enclosed in dollar signs like this: <code>$x^2$</code>.</p>
< p > Note that expression parsing is done on the viewer ' s computer with javascript ( specifically MathJax ) and not by $settings -> sitename directly ( also called client side rendering ) .</ p > " );
2015-12-21 08:45:38 +00:00
//////////////////////////////////////////////////////////////////
2015-09-19 09:19:56 +00:00
// %next_module% //
2015-12-21 08:45:38 +00:00
//////////////////////////////////////////////////////////////////
2015-09-19 09:19:56 +00:00
// Execute each module's code
foreach ( $modules as $moduledata )
{
$moduledata [ " code " ]();
}
// Make sure that the credits page exists
if ( ! isset ( $actions -> credits ))
{
exit ( page_renderer :: render_main ( " Error - $settings -> $sitename " , " <p>No credits page detected. The credits page is a required module!</p> " ));
}
// Perform the appropriate action
2015-09-22 13:47:12 +00:00
$action_name = $env -> action ;
2015-09-19 09:19:56 +00:00
if ( isset ( $actions -> $action_name ))
{
$req_action_data = $actions -> $action_name ;
$req_action_data ();
}
else
{
exit ( page_renderer :: render_main ( " Error - $settings->sitename " , " <p>No action called " . strtolower ( $_GET [ " action " ]) . " has been registered. Perhaps you are missing a module?</p> " ));
}
2015-09-21 20:02:27 +00:00
2015-09-19 09:19:56 +00:00
?>