<?php
if (MING != "merciless") require ('index.php'); // this keeps people from executing "core.php" directly
else {


/*
Is this code documented yet? No.

Is Planet Mongo done yet? No.

In other words, the documentation in this file is far from perfect. This is an alpha release. I tried my best.
Live with it.
My best tends to suck.


// NEW FEATURE ADD LIST:
0. Finish a simple, experimental web page editor <-- Done!
1. Write a simple picture gallery module
2. Finish a super simple CAPCHA system <-- Done!
3. Generalize file type system (ie output powers)
4. General code clean 
5. Make general 




// The following "Features to add list" is actually rather old. I have totally new priorities now.

Features to add list:
0. Add the "freeze" magic file feature, which writes it temporarily to disk, allowing for totally radical capabilities
1. Generalize output powers. Make BB, HTML, etc all be stored in an array and also be paired with functions. 
That way, people can easily add new types. Eventually allow multiple types, such that, for example, a bb file can be 
profanity filtered and interpretted into HTML before being written to a file.
2. Make modules a little more official, if the module writer so desires. Have all modules be listed in an array. Add 
optional module auto-detect system.
3. Make a user/login system
4. Make an admin page for creating new News articles and Content pages via nice HTML forms
5. Add CSV database module, serialization database module, and MySQL database module, and a PEAR DB module, 
for just about any other databasing system.
6. Make the $db's opperations fairly similar to the opperations of the PEAR equivalent.
*/

// The credits and version history are contained in the following magic file. The magic file 
// isn't added until the end of this file, as 'main_add_magic_file' needs to be defined, as do all 
// of the functions and classes that it might use. It's formatted for BBCode.
function magicfile_credits_and_version_history ()
{
	return
	'
Planet Mongo, version 0.04a
Version history:

> Version 0.04a
- Fixed many random bugs
- Internal construction improved
- Added (experimental) web administration module
- Added (experimental) user module
- Added (weak) captcha module

> Version 0.03a
- Fixed issue with outblurt getting choked up on Planet Mongo commands found in PHP code
- Added auto-execution module function
- Improved internal construction of content and theme modules
- Improved documentation
- Added module auto-detection
- Added folding feature to news module
- Added page title function to content module
- Added "random" module

> Version 0.02a
- Wrote FileWrapper class to replace the array-based file descriptors
- Rewrote outblurt() command to fix bugginess
- XML Database module added
- Comments module added
- BBCode support enhanced

> Version 0.01a
- First release
- Module system in place
- News, content, and theme modules somewhat finished
- Magic file system in place




Credits:
- Michael Bethencourt, http://michaelb.org/

Special thanks to...
- Justin Palmer, http://www.isolated-designs.net/core for writing the BBCode function, on which the main_convert_bbcode_to_html function is based (found in module core.php).
- james at mercstudio dot com, and bbellwfu at gmail dot com for writing the class on which the MainXmlToArray class is based (found in module xml_database.php).
- And all other frequenters of http://php.net/ for the the code snippets and helpful suggestions.
- You, for using Planet Mongo.
';
}




define('PLANET_MONGO_VERSION_STR', '0.04a');

define('PLANET_MONGO_VERSION', 4);
define('PLANET_MONGO_VERSION_IS_ALPHA', TRUE);
define('PLANET_MONGO_VERSION_IS_BETA', FALSE);
define('PLANET_MONGO_VERSION_RC', 0);

/*
Version numbering system:

1 -> 0.01
2 -> 0.02 
3 -> 0.03
...
101 -> 1.01
102 -> 1.02
etc.

The RC's are as follows:
0 -> is not a RC
1 -> RC1
2 -> RC2 etc.

*/


define('POWER_PHP', 4);
define('POWER_HTML', 3);
define('POWER_BB', 2);
define('POWER_TXT', 1);
define('POWER_DEBUG1', -1);


// The following two powers are for all the outblurted texts that aren't found directly in a file.
// These texts are usually contained in config files. Increasing the power of any of these defaults 
// may allow you to put PHP code in places that you before couldn't (e.g., in the theme template 
// for news posts, for example).
define('POWER_DEFAULT', POWER_HTML); 
define('POWER_DEFAULT2', POWER_PHP);


define('FILE_TITLE', 0);
define('FILE_TYPE', 1);
define('FILE_DONTLIST', 2);
define('FILE_FILENAME', 3);
define('FILE_ISMAGICAL', 4);
define('FILE_ISDIRECTORY', 5);
define('FILE_CAPABLE', 6);
define('FILE_DATE', 7);

define('TITLE_KEEP_LOWERCASE', 0);
define('TITLE_STANDARD_ENGLISH', 1);
define('TITLE_EVERY_FIRST_LETTER', 2);



// NOTE: the RESTRICT variables are only to plug up possible future exploits. Adjusting them should 
// not affect security, assuming that all security holes have already been discovered, which 
// of course is not a correct assumption. In other words, by altering them you aren't giving 
// an honest user free-reign of your computer.

// For example,  UnRESTRICTing php interpretation for 
// comments will not give the option of using php in comments. Instead, it will only allow 
// those with proper permission (at the time being, that would mean unfettered access to the 
// file system) to use PHP in comments.
// RESTRICTing php interpretation in comments will ensure that Planet Mongo will never 
// interpet text found in comments as PHP code, regardless of what power the comment 
// says it should have.
// These turn on or off PHP code interpretation restriction at two levels of user submission
// Level 1 turns off all PHP code interpret
define('RESTRICT1', TRUE);
define('RESTRICT2', TRUE);


// This should be turned on for testing and debugging,  turned off for normal usage.
define('BE_STRICT', FALSE);


// Alphanumeric and the underscore
define('REGEX_LEGAL_CHAR1', '/[^\\dA-Z_.a-z]/');


$temp = explode('/', $_SERVER['REQUEST_URI']);
$url_request = $temp[sizeof($temp) - 1];

// This is some handy constants which are simply slightly processed superglobals.
define('URL_REQUEST', $url_request);
define('URL_HAS_GET', strpos($url_request, '?') !== FALSE );
if(URL_HAS_GET) $prepared = URL_REQUEST . '&'; else $prepared = URL_REQUEST . '?';
define('URL_PREPARED', $prepared);


unset ($temp, $url_request, $prepared);




$main_set_stack = Array();
$main_magic_file_list = Array();
$main_module_descriptors = Array();
$main_outblurts = Array();

/**********************************************************************************/

/*
		These are the 'Never Used' configuration options. As their name implies, I doubt you'd 
		ever need to use any of these.

		WARNING: Take extra care when editing these options -- changing some of these in any way 
		will probably screw everything up.
*/

$main_module_folder = 'modules';

$main_should_start_with_php_when_eval = FALSE;
$main_command_start = '<!--[[';
$main_command_end = ']]-->';

$main_php_start = '<?';
$main_php_end = '?>';

$magic_function_prefix = 'magicfile_';
$magic_file_prefix = '__';

$main_module_divider = ':';

// This option will silence ALL output through the outblurt function.
$main_disable_all_output = FALSE;

// Used when generating small urls
$content_small_url_skeleton = '
<?php
// small url for %file_name%
$content_request = \'%file_name%\';
require (\'index.php\');
?>
';


/**********************************************************************************/



/**********************************************************************************/

/*
		These are the 'Insignificant' configuration options. As their name implies, I doubt you'd 
		ever need to use any of these.

		Note: Unlike the 'Never Used' functions, these are just worthless, not necessarily destructive.
*/


// This is only used if BOTH the magic file containing the 404 is not functioning (it's found in 
// content.php) AND no user 404 file is available. In other words, it is very, very unlikely it will ever be used,
$content_404_for_404 = '<b>404 - File Not Found</b> <br /> <br />Oops! Either the page you were looking for does not exist, or an error occured while trying to process your request. Hit the "back" button on your browser and try again. <!-- Also, ironically, a 404 error occurred when trying to find the 404 error page. The admin of this website needs some professional counciling. -->';


// This controls how titles are constructed.
// If the unformatted file name is "the_war_of_the_world.html" then the setting ...
// 'TITLE_KEEP_LOWERCASE' will yield "the war of the world" as the title and ...
// 'TITLE_STANDARD_ENGLISH' will yield "The War of the World" as the title, and ...
// 'TITLE_EVERY_FIRST_LETTER' will yield "The War Of The World" as the title.
$main_title_case_scheme = TITLE_STANDARD_ENGLISH;


// This is the list of words that the standard english title case will ignore, unless it is the first
// word in the title.
$main_standard_words_to_ignore = Array( 'of','a','the','and','an','or','nor','but','is','if','then','else','when', 'at','from','by','on','off','for','in','out','over','to','into','with' );


/**********************************************************************************/


/*
Entry represents one database entry.
*/
class Entry
{
	var $attributes = Array();
	var $type = 'entry';
	
	function setEntryType ($type ) {
		$this->type = $type;
	}
	
	function get($name) {
		return $this->getAttribute($name);
	}
	
	function set($name, $value) {
		$this->setAttribute($name, $value);
	}
	
	function setAttribute($name, $value) {
		$this->attributes[$name] = $value;
	}
	function getAttribute($name) {
		return $this->attributes[$name];
	}
	function toString() {
		$string = "---BEGIN " . strtoupper($type) . " DESCRIPTION---\n";
		$keys = array_keys($this->attributes);
		$values = array_values($this->attributes);
		for ( $i = 0; $i < sizeof($keys); $i++ ) {
			$string = $string . '+ ' . $keys[$i] . ' = ' . $values[$i] . " \n";
		}
		$string = $string . "---END " . strtoupper($type) . " DESCRIPTION---\n";
		return $string;
	}
	function toXML ( $base_tab_count = 1 ) {
		$tabs = str_repeat("\t", $base_tab_count);
		$string = $tabs . '<' . $this->type . "> \n";
		$keys = array_keys($this->attributes);
		$values = array_values($this->attributes);
		for ( $i = 0; $i < sizeof($keys); $i++ ) {
			$value = $values[$i];
			$value = main_replace($value, Array ('<' => '!`lt^', '>' => '!`gt^', '*/' => '!*`/') ); // removes <'s, >'s and other characters that could
			 // goof up the XML parser.
			$opener = '<' . $keys[$i] . '>';
			$closer = '</' . $keys[$i] . '>';
			$string = $string . $tabs . "\t" . $opener . $value . $closer . " \n"; // puts together an entire attribute
		}
		$string = $string . $tabs . '</' . $this->type . "> \n";
		return $string;
	}
}
class FileWrapper {
	var $title; // The properly formatted file title (ie "Example Page")
	var $file_type; // A number designating file type (ie 3)
	var $dont_list; // Boolean indicating if this file should be listed in a listing of links
	var $file_name; // Raw filename (ie "example_page.index.html")
	var $is_magical; // Boolean indicating if this file isn't really a real file in the file system
	var $is_directory; // Boolean. Is this actually just a directory, and not a file at all?
	var $is_capable; // Boolean. A file is capable if Planet Mongo commands are allowed. 
	var $category; // String. The category of this file (not yet implemented).
	var $unix_date; // Int. Unix timestamp for this file 
	var $related_database; // I have no clue what this was for. It's probably useless and should be delted. TODO
	var $contents; // String. The contetns of this file. Only set if loaded...
	var $magic_function_name; // If magical, this is set to the function that supplies with the magic contents
	var $directory; // the directory that contains this file
	var $has_been_parsed; // Has it already been parsed?
	var $type; // String. Type (ie "php")
	var $raw_title; // String. (ie "example_page")
	var $is_index; // IS this file the index? Not *always* accurate, but usually...
	var $is_locked; // Does this file have the locked keyword to disable online editing?
	var $is_news;
	
	function FileWrapper ( $file_name = FALSE ) {
		if ($file_name !== FALSE) {
			$this->file_name = $file_name;
			$this->parseFileName();
		}
	}
	
	function parseFileName(  ) {
		$file_name = $this->file_name;
		global $content_file_type_php, $content_file_type_txt, $content_file_type_html,  $content_file_dont_list;
		global $content_file_type_bb, $content_file_uncapable, $content_file_capable, $main_capable_directories;
		global $editor_allowed_filetypes;
		if ( in_array($this->directory, $main_capable_directories ) ) 
			$this->capable = TRUE; // Sets the default capability based on the $main_capable_directories setting
		else $this->capable = FALSE;
				
		$exploded = explode(  ".", $file_name );
		$name = $exploded[0];
		$dont_list = FALSE;
		$type = POWER_HTML; // TODO: Make this an option that can be set as a default
		$type_extension = 'HTML';
		$date = FALSE;
		$this->is_index = FALSE;
		$locked = FALSE;
		foreach ( $exploded as $chunk )
		{
			if ($chunk == $content_file_dont_list) $dont_list = TRUE;
			else if ( main_extension_to_int($chunk) !== FALSE) {
				$type = main_extension_to_int($chunk);
				$type_extension = $chunk;
			}
			else if ($chunk == $content_index_filename) $this->is_index = TRUE;
			else if ($chunk == $content_file_uncapable) $capable = FALSE;
			else if ($chunk == $content_file_capable) $capable = TRUE;
			else if ($chunk == $content_file_locked) $locked = TRUE;
			else if ( ctype_digit( $chunk ) === TRUE ) $date = intval($chunk);
			// TODO add a catch-all else that will add the no good text to the file_name + a period.
		}
		$this->file_name = $file_name;
		$this->type = $type;
		$this->type_extension = $type_extension;
		$this->dont_list = $dont_list;
		$this->file_name = $file_name;
		$this->unix_date = $date;
		$this->makeTitleFromName( $name );
		$this->raw_title = $name;
		$this->is_locked = $locked || !in_array($type_extension, $editor_allowed_filetypes);
	}
	
	function makeTitleFromName( $name ) {
		$fake_space = "_";	
		$str = trim($name);
		$str = str_replace ( $fake_space, ' ', $str ); 		
		if ( strtolower($str) == $str ) // if it is already all in lower case...
		{
			global $main_title_case_scheme, $main_standard_words_to_ignore;
			if ($main_title_case_scheme == TITLE_STANDARD_ENGLISH )
			{
				$exploded = explode(' ', $str);
				for ( $i = 0; $i < sizeof( $exploded ); $i++ ) // $exploded as $key => $word)
				{					
					if ( $i == 0 || !in_array( $exploded[$i] , $main_standard_words_to_ignore)  )
					{
						$exploded[$i] = ucwords($exploded[$i]);
					}
				}
				$str = implode(' ', $exploded);
			}
			else if ( $main_title_case_scheme == TITLE_EVERY_FIRST_LETTER)
				$str = ucwords($str);		
		}
		
		$this->title = $str;
	}
	function getVar ( $n ) {
		$vars = Array ($this->title, $this->type, $this->dont_list, $this->file_name, $file->is_magical, $file->is_directory, $file->is_capable );
		return $vars[$n];
	}
	function outblurtWithTemplate ($template, $capable = FALSE, $outblurt_id = FALSE) {
		$exp = explode('%content%', $template, 2);
		outblurt ($this->applyTemplate($exp[0]), POWER_DEFAULT, $capable, $outblurt_id);
		if (strpos($template, '%content%') !== FALSE) outblurt ($this->contents, $this->type, $this->is_capable, $outblurt_id);
		outblurt ($this->applyTemplate($exp[1]), POWER_DEFAULT, $capable, $outblurt_id);
	}
	function setContents ( $contents ) {
		$this->contents = $contents;
		$this->is_news = ereg('<!--\[\[\s*news\s*', $contents) === TRUE;
		// Sets is_news to be true if a call to the news main module can be found within the contents
	}
	function applyTemplate ($template) {
		$url_friendly_file_name = urlencode($this->file_name);
		global $main_date_format;
		$replace_array = Array (
			// Basic meta data
			'%title%' => $this->title, 
			'%file_name%' => $this->file_name,
			'%category%' => $this->category,
			'%date%' => $this->unix_date,
			'%type_extension%' => $this->type_extension,
			'%date_formatted%' => date($main_date_format, $this->unix_date),
			'%dY%' => date('Y', $this->unix_date),
			'%dm%' => date('m', $this->unix_date),
			'%dd%' => date('d', $this->unix_date),
			'%dH%' => date('H', $this->unix_date),
			'%dh%' => date('h', $this->unix_date),
			'%di%' => date('i', $this->unix_date),				
			'%file_name_url%' => $url_friendly_file_name,
			'%raw_title%' => $this->raw_tite,
			'%directory%' => $this->directory,
			'%is_index%' => $this->is_index,
			'%is_locked%' => $this->is_locked ? 'true' : 'false',
			// If the contents are loaded in this wrapper...
			'%raw_contents%' => $this->contents,
			'%is_capable%' => $this->capable ? 'true' : 'false',
			'%raw_contents_encoded%' => htmlspecialchars($this->contents),
			
			// Just some useful things that  aren't specific to this file...
			'%url_prepared%' => URL_PREPARED,
			'%url_request%' => URL_REQUEST );
		return main_replace($template, $replace_array);
	}
}

class MainInterpreter {
	var $text, $power, $capable, $continue_output;
	
	function MainInterpreter ($text, $power = POWER_HTML, $capable = FALSE)
	{
		//if ($power == '') $power = POWER_HTML;
		$this->text = $text;
		$this->power = $power;
		$this->capable = $capable;
		$this->continue_output = TRUE;
	}
	
	function controlledOutput () {
		main_controlled_output($this->text, $this->power);
	}
	
/*
This function parses out the <!--[[ news : main ]]--> style commands and executes the, while outputting 
the ordinary text.

If the $power is greater than POWER_PHP, then it will ignore the commands found between the php_start 
and php_end's.


*/
	
	function parseAndOutput()
	{
		while ( $this->parseOneChunk() === TRUE && ($this->continue_output === TRUE) )
		{
			// There really is no need for any code here--all that needs to be done is in parseOneChunk()
		}
		
	}
	
	function parseOneChunk ()
	{
		global $main_command_start, $main_command_end, $main_php_start, $main_php_end;
		
		$text = $this->text;		
		$split = explode($main_command_start, $text, 2); // This splits the text arround the first command start, 
		// and stores the before text in $split[0] and the after text in $split[1]
		
		// The following text is to ignore the <!--[[ and ]]--> found in php code.
		if ($this->power >= POWER_PHP && count($split) > 1)
		{
			$done = FALSE;
			while ( substr_count($split[0], $main_php_start) - substr_count($split[0], $main_php_end) !== 0 )
		// There are an odd number of opening PHP's--thus there is more to parse.
			{
				$second_split = explode($main_command_start, $split[1], 2);
				$split[0] = $split[0] . $main_command_start . $second_split[0];	
				$split[1] = $second_split[1];
			}
			
		}
		
		
		if ( sizeof($split) <= 1) 
		{
			// There are no more commands to worry about.
			main_controlled_output($text, $this->power);
			return FALSE; // Done with this!
		}
		else
		{
			main_controlled_output($split[0], $this->power); // outputs/executes the text up to the split.
			$split[0] = substr($split[0], strlen($main_command_start));
			$command_split = explode($main_command_end, $split[1], 2); // splits the remaining text by the Planet Mongo command ender.
			// Now, the text of the Planet Mongo command is included in $command_split[0] and the remaining text is included in $command_split[1]
			$this->text = $command_split[1]; // this assigns the remaining text to the var text, so that on the next iteration of this function 
			// it can be executed.
			$command = $command_split[0];
			
			///// Now that we've isolated the Planet Mongo module command, we should execute it.
			
			global $main_module_divider; 
			$exploded = explode($main_module_divider, $command , 3); // splits the text by the main_module_divider
			$mod_name = strtolower( trim($exploded[0]) ); // the module name is in the zeroeth part
			$function_name = strtolower( trim($exploded[1]) ); // and the function name in the first part
			if (sizeof($exploded) ==  3)
				$arg = trim($exploded[2]); // arguments can be found in the second part, if there is one
			else
				$arg = FALSE; // there isn't one, set $arg to FALSE.
				
			// TODO: make the underbar and _doaction customizable.
			$name_of_function = $mod_name . '_' . $function_name . '_doaction'; // constructs the name of the function
			main_execute_module_auto_once_if_exists($mod_name);
			if ( main_execute_function_if_exists ($name_of_function, $arg ) === FALSE )
			{
				global $main_admin_email;
				$error_message = "ERROR: Module function $name_of_function does not exist. The call to this function may be the result of a typo or a more serious configuration error. Please alert admin at $main_admin_email.";
				outblurt ( $error_message, POWER_HTML);
			}
			return TRUE; // True means there's might be more left to outblurt.
		}
		
		
	}
	
	function output ()	{
		if ($this->capable === FALSE) $this->controlledOutput();
		else $this->parseAndOutput();	
	}
	function stop_output() {
		$this->continue_output = FALSE;
	}
	
}

function main_execute_module_auto_once_if_exists ( $mod_name)
{
	global $main_module_autos;
	if(function_exists($mod_name . '_auto') && !is_bool($main_module_autos[$mod_name]))
	{
		main_execute_function_if_exists($mod_name . '_auto');
		$main_module_autos[$mod_name] = TRUE;
		return TRUE;
	}
	else return FALSE;	
}
function main_extension_to_int ($ext)
{
	global $content_file_type_php, $content_file_type_txt, $content_file_type_html,  $content_file_dont_list;
	global $content_file_type_bb;
	$ext = trim($ext);
	if ($ext == $content_file_type_bb) $type = POWER_BB;
	else if ($ext == $content_file_type_php) $type = POWER_PHP;
	else if ($ext == $content_file_type_html) $type = POWER_HTML;
	else if ($ext == $content_file_type_txt) $type = POWER_TXT;
	else return FALSE;
	return $type;
}

function main_turn_urls_into_links ( $text )
{		
	$text = preg_replace( "/(?<!<a href=\")((http|ftp)+(s)?:\/\/[^<>\s]+)/i", 
	"<a href=\"\\0\" target=\"_blank\">\\0</a>", $text );
	return $text;
}

function main_convert_bbcode_to_html ($string)
{
	

#  This BBode function is taken mostly from Justin Palmer's BBCode to HTML function, under the GNU Lesser General Public License
#  His website: HTTP://WWW.ISOLATED-DESIGNS.NET/CORE  ... http://www.gnu.org/copyleft/lesser.html
	global $main_bbcode_pattern_array, $main_bbcode_replaces_array;
	$string = nl2br(htmlspecialchars($string)); // clean up special characters
	//$string = main_turn_urls_into_links ( $string ); // DISABLED since it causes numerous issues
	if ( is_array($main_bbcode_pattern_array) ) $patterns = $main_bbcode_pattern_array;
	else	$patterns = array(		'`\[b\](.+?)\[/b\]`is',
										'`\[i\](.+?)\[/i\]`is',
										'`\[u\](.+?)\[/u\]`is',
										'`\[strike\](.+?)\[/strike\]`is',
										'`\[color=#([0-9]{6})\](.+?)\[/color\]`is',
										'`\[email\](.+?)\[/email\]`is',
										'`\[img\](.+?)\[/img\]`is',
										'`\[url=([a-z0-9]+://)([\w\-]+\.([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^ \"\n\r\t<]*?)?)\](.*?)\[/url\]`si',
										'`\[url\]([a-z0-9]+?://){1}([\w\-]+\.([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^ \"\n\r\t<]*)?)\[/url\]`si',
										'`\[url\]((www|ftp)\.([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^ \"\n\r\t<]*?)?)\[/url\]`si',
										//'`\[flash=([0-9]+),([0-9]+)\](.+?)\[/flash\]`is', // DISABLED since it is a security issue
										'`\[quote\](.+?)\[/quote\]`is',
										'`\[indent](.+?)\[/indent\]`is',
										'`\[size=([1-6]+)\](.+?)\[/size\]`is' );
	if ( is_array($main_bbcode_replaces_array) ) $replaces = $main_bbcode_replaces_array;
	else	$replaces =  array(		'<strong>\\1</strong>',
										'<em>\\1</em>',
										'<span style="border-bottom: 1px dotted">\\1</span>',
										'<strike>\\1</strike>',
										'<span style="color:#\1;">\2</span>',
										'<a href="mailto:\1">\1</a>',
										'<img src="\1" alt="" style="border:0px;" />',
										'<a href="\1\2">\6</a>',
										'<a href="\1\2">\1\2</a>',
										'<a href="http://\1">\1</a>',
										//'<object width="\1" height="\2"><param name="movie" value="\3" /><embed src="\3" width="\1" height="\2"></embed></object>',// DISABLED since it is a security issue
										'<br /><strong>Quote:</strong><div style="margin:0px 10px;padding:5px;background-color:#F7F7F7;border:1px dotted #CCCCCC;width:80%;"><em>\1</em></div>',
										'<pre>\\1</pre>',
										'<h\1>\2</h\1>' );
	
	
	$string = preg_replace($patterns, $replaces , $string);
	
	
	$other_translations = Array (
	'  ' => '&nbsp; ', 
	'(c)' => '&copy;',
	'(C)' => '&copy;',
	"<br /> * " => '<br /> &bull; ',
	"<br />* " => '<br />&bull; ',
	" * " => ' &bull; '
	
	);
	// TODO: implement, and make it CUSTOMIZABLE!
	
	//$text = htmlentities($text);
	$string = main_replace ($string, $other_translations);
	return $string;
}
function main_controlled_output ( $text, $power = POWER_HTML )
{
	global $main_should_start_with_php_when_eval;
	if ($power == POWER_TXT) echo "<pre>";
	if ($power == POWER_DEBUG1 ) echo "<!--" . $text . "-->";
	else if (($power == POWER_BB) || ($power == POWER_TXT) ) echo ( main_convert_bbcode_to_html($text) );
	else if ($power == POWER_HTML ) echo $text;
	else if ($power == POWER_PHP ) // it's time to eval.
	{
		// this eval's the text, assuming that it starts in HTML mode. 
		if ($main_should_start_with_php_when_eval === FALSE )  $text = '   ?>   ' . $text . '   <?php   ' ;
		$result = eval ($text);
		return $result;
	}
	if ($power == POWER_TXT) echo "</pre>";
}

function main_start_planet_mongo () {
	global $main_enable_benchmark;
	if ($main_enable_benchmark === TRUE) main_mark_doaction();
	theme_main_doaction(); // this essentially just outblurts the current theme. It is found in theme.php
	
}

/*

This function retrieves the contents of a magic file. The input is the full magic file file_name, including the 
magic file name prefix, but not the magic function prefix which is automatically inserted.
*/


function main_get_magic_contents ($file_name)
{
	global $magic_file_prefix, $magic_function_prefix;
	// The following line removes the magic file prefix.
	$function_name = substr($file_name, strlen($magic_file_prefix) , strlen($file_name) );
	// The following line adds the magic function prefix
	$function_name = $magic_function_prefix . $function_name;
	$result = main_execute_function_if_exists ($function_name, FALSE, TRUE );
	if ($result === FALSE)	{
		global $main_admin_email;
		$error_message = "ERROR: Magic file $file_name does not exist. The call to this function may be the result of a typographical error or a more serious configuration malfunction. Please alert admin at $main_admin_email.";
		outblurt ( $error_message, POWER_DEBUG1);
		return FALSE;
	} else return $result;
}

function main_add_magic_file ( $file)
{
	#print_r($file);
	global $main_magic_file_list, $magic_file_prefix;
	$file->file_name = $magic_file_prefix . $file->file_name;
	$file->is_magical = TRUE;
	$main_magic_file_list[] = $file;	
}

/*
This function retrieves the magic file list formatted to look like a normal file list. 
If no directory is specified, then all magic files are returned. 
*/
function main_retrieve_magic_file_list ($directory = ' $\ ') {
	global $main_magic_file_list;
	$file_list = Array();
	foreach ($main_magic_file_list as $file) {
		if ($file->directory == $directory || $directory === FALSE ) $file_list[] = $file;
	}
	return $file_list;
}


/*
This function returns a properly formatted directory listing.
*/
function main_dir_contents ($dir, $permit_magic = TRUE, $show_directories = FALSE )
{
	$directory_list = Array();
	// TODO: make the '/' flexible, and also make the '/' so that it doesn't HAVE to be specified. (make it add if not there)
	if (is_dir($dir . '/') !== FALSE)
	{
		// Ok, there is such a directory.
		
		if ( ($dir_open = @ opendir($dir . '/') ) !== FALSE)
		{
			// Ok, the directory has been successfully opened.
			
			// The following loop looks at each file in the directory, and stops when it reaches 
			// the end. It puts each of those file names in the variable $file.
			while ( ($file = readdir($dir_open) ) !== FALSE)
			{
				if (strpos($file, '.') !== 0) // this crops out the current dir and up-one dir, as well as hidden files and folders
				{
					$is_directory = is_dir($dir . '/' .$file); // stores whether or not this is a directory
					$entry_to_add = new FileWrapper( /*$file*/ ); // creates a file wrapper
					$entry_to_add->file_name = $file;
					$entry_to_add->directory = $dir;
					$entry_to_add->is_directory = $is_directory;
					$entry_to_add->parseFileName();
					
					if ($show_directories === FALSE) // Does the caller want directories to be listed too?
					{
						// No, the caller doesn't.
						if ($is_directory !== TRUE) // is this a directory? Because if is, I don't want to add it.
						{
							$directory_list[] = $entry_to_add; // Phew! it's not a directory. It's ok to add.
						}
					}
					else
					{
						// Looks like the caller wants directories to be listed too. That means we'll 
						// add this entry no matter what.
						$directory_list[] = $entry_to_add;
					}
				}
			}
			// There! we have  alist of all of the directories. Now we close 'er up.
			closedir($dir_open);
		}
		//else
		//{
			// TODO: Add errorsystem Maybe not here though, as magic files are yet to add. Or maybe not at all
		//}
		// OK, now we insert the magic files.
		if ($permit_magic === TRUE)
		{
			// Ooh boy! Magic is permitted. This is my type of town.
			$magic_file_list = main_retrieve_magic_file_list($dir);
			$directory_list = array_merge($directory_list, $magic_file_list);
		}
#		// sort($directory_list, SORT_STRING); // Now we sort er up.
		return $directory_list;
	}
	else
	{
		outblurt ("<b>Directory read error</b>", POWER_HTML);
		return FALSE;
	}
}


/*
This function gets the contents of a specific file. You give it a file specifier array, the dir, 
and it gives you the contents. It supports magic files.
*/
function main_get_file_contents ( $file, $dir = FALSE)
{
	if ($file->is_magical === TRUE)
	{
		$file_contents = main_get_magic_contents ( $file->file_name );
	}
	else
	{
		if ($dir === FALSE )
		{
			$file_path = $file->directory . '/' . $file->file_name;
		}
		else
		{
			$file_path = $dir . '/' . $file->file_name;
		}
		
		$handle = @fopen($file_path, 'r'); 
		$file_contents = fread($handle, filesize($file_path));
		fclose($handle);
		
	}
	return $file_contents;
}


/*
This function gets the contents of a file. You specify a directory and, if you want, a filelist 
(otherwise, it will construct a filelist for you using the "main_dir_contents" function).

Then, it will read the file and return the contents. This function supports magic files, which 
is totally cool.
*/
function main_get_file ( $needle, $dir = FALSE, $search_value = FILE_FILENAME, $filelist = FALSE ) {
	if ($dir === FALSE) {
		$dir = dirname($needle); // Get the directory name
		$needle = basename($needle);  // Get the file name
	}
	if ($filelist === FALSE ) {
		$filelist = main_dir_contents($dir);
	}
	
	$found = FALSE;
	foreach ($filelist as $file) {
		if (strpos($file->getVar($search_value), $needle) !== FALSE) {
			$found = $file;
			break;
		}
	}
	if ($found !== FALSE) {
		$found->setContents( main_get_file_contents ($found, $dir ));
		return $found;
	}
	else return FALSE;	
}

function main_replace ( $haystack, $needle_array) {
	return str_replace(array_keys($needle_array), array_values($needle_array), $haystack);
}


function outblurt ($text, $power = POWER_HTML, $capable = FALSE, $outblurt_handle = FALSE)
{
	// This is just a shortcut to save wasted motions, as most outblurts will be with those three settings. 
	// You can turn it off by uncommenting the && FALSE, or just by deleting it.
	if($power == POWER_HTML && $capable === FALSE && $outblurt_handle === FALSE /* && FALSE */) 
	{
		echo $text;
		return;
	}
	
	if ($power == '') $power = POWER_HTML;
	global $main_outblurts;
	$interpreter = new MainInterpreter($text, $power, $capable);
	// The outblurt handles can be used to stop particular outblurt calls midcall.
	// By specififying text along with your outblurt, PHP code that is called within the outblurt
	// can cause the outblurt to halt with a call to main_stop_outblurt().
	// This does not always work as one might expect, as it is impossible to really stop PHP code 
	// mid-execution. Thus, what it will do is stop outblurting ONLY IF it is in the middle of a 
	// Planet Mongo style command call from a capable outblurt. For example, in the style of
	// <!--[[ user : protect ]]-->. However, if the same function were to be called in a PHP 
	// file in the style of user_protect_doaction(), IT WILL NOT BE ABLE TO STOP EXECUTION OF 
	// THE REST OF THE PAGE UNTIL IT GETS TO A PLANET MONGO COMMAND.
	// For this reason, <!--[[ main : pass ]]--> can be used as a "dummy" command.
	$main_outblurts[$outblurt_handle] = $interpreter;
	$interpreter->output();
	unset($main_outblurts[$outblurt_handle]);
}




/*
This function executes other functions. For the first argument, specify the name of the function you want to execute. 
For the second argument, you may specify one string argument. For the third argument, specify if you want this 
function to return the return value of the other function. Otherwise, it will return TRUE if successful.

If unsuccessful, this function returns false.

*/
function main_execute_function_if_exists ( $name_of_function, $arg = FALSE, $have_return = FALSE )
{
		if ( function_exists($name_of_function)  ) {
			// ok there is a function like this, time for something crazy-go-nuts
			if ($have_return !== FALSE) $code_to_execute = "return ";
			$code_to_execute = $code_to_execute . $name_of_function . '(';
			if ($arg !== FALSE) $code_to_execute = $code_to_execute . "'" . str_replace("'", '\\\'', $arg ) . "'";
			$code_to_execute = $code_to_execute . ');';
			$product = eval($code_to_execute);
			
			if ($have_return !== FALSE) return $product;
			return TRUE;
		}
		else return FALSE;
}

// main_set_doaction wraps around main_set for Planet Mongo capable files.
function main_set_doaction ($arg){
	$exp = explode('=', $arg, 2);
	main_set ($exp[0], $exp[1]);
}
function main_set ($name, $value){
	global $main_set_stack;
	if( !is_array($main_set_stack[$name])) $main_set_stack[$name] = Array();
	$old_global = $GLOBAL[$name];
	array_push($main_set_stack[$name], $old_global);
	$GLOBAL[$name] = $value;	
	return $old_global;
}
function main_unset ( $name ){
	global $main_set_stack;
	if( !is_array($main_set_stack[$name])) return FALSE;
	$new_global = $GLOBAL[$name];
	$GLOBAL[$name] =  array_pop($main_set_stack[$name]);
	return $new_global;
}
/***************************************************
 * Here we have the two important functins "main_stop_outblurt"
 * and "main_stop_all". The first will atempt to stop an outblurt 
 * based on a string (the outblurt handle). Whether it will be 
 * successfull or not is slightly more complicated. It will only 
 * be successfull if that particular outblurt is capable (that is, 
 * has permissions to process Planet Mongo commands). Also, it will
 * only be completely succesfull if it was called from within a 
 * Planet Mongo command, indirectly or directly. If it was not 
 * called from within a Planet Mongo command, the outblurt function
 * will continue outblurting until it gets to another outblurt command, 
 * at which point it will stop. TODO: make it so that it will stop without executing that command
 *
 * The main_stop_all function is intended to be a fool-proof 
 * (although messy) way of stopping all outblurt. It in turn simply
 * calls the built-in exit() function, which will immediately exit
 * the PHP script. However, it will often leave unclosed HTML tags.
 ***************************************************/
function main_stop_outblurt_doaction($arg=''){ main_stop_outblurt($arg); }
function main_stop_outblurt($handle = ''){
	global $main_outblurts, $content_outblurt_handle;
	if (strlen($handle)<1) $handle = $content_outblurt_handle;
	if(array_key_exists($handle, $main_outblurts)) {
		$main_outblurts[$handle]->stop_output();
		return TRUE;
	} else return FALSE;
}
function main_stop_all_doaction($arg=''){ main_stop_all($arg); }
function main_stop_all ($arg='') { 
	exit($arg);
}



/***************************************************
 * These three functions are for an ultra-simple   *
 * benchmarking feature.                           *
 ***************************************************/
function microtime_float() { // this emulates the PHP5 style microtime.
	list($usec, $sec) = explode(" ", microtime());
	return ((float)$usec + (float)$sec);
}
function main_mark_doaction() {
	global $main_marktime;
	$main_marktime = microtime_float();
}
function main_twain_doaction() {
	global $main_marktime;
	$currenttime = microtime_float();
	$difference = $currenttime - $main_marktime;
	$text = substr( strval($difference), 0, 6);
	outblurt($text);
	return $difference;
}

/***************************************************
 * A simple utility function for obfuscating       *
 * email addresses                                 *
 ***************************************************/

function main_obfuscate_email ($email)
{
	$email = main_replace ($email, Array('@' => ' [at] ', '.' => ' [dot] ') );
	$len = strlen($email);
    for ($i=0;$i<$len;$i++) $obf_email .= "&#" . ord($email[$i]);
    return $obf_email;
	
}


/*
At this point, the Credits and Version History magic file is added.
It is added here simply because it must be added after the functions it
uses and the classes it uses are defined, and it wouldn't do to break 
up the code for a magic file.
*/

$file = new FileWrapper();
$file->title = 'Credits and Version History';
$file->directory = $content_directory;
$file->dont_list = TRUE;
$file->is_capable = FALSE;
$file->file_name = 'credits_and_version_history';
$file->type = POWER_BB;

main_add_magic_file ($file );







} // this closing brace is from the else clause in the bad_file checker above
?>
