<?php
/*
* Uberbbcode Parser
* Version 0.1
* 
* A bbCode class built on the Ubertemplate Parser
* 
* Notes:
* Overwrites addCode() and process(), with a few new blocks for bbCode use
* 
* Depends on:
* template.class.php (Ubertemplate Parser)
* geshi.php (GeSHi)
*/
class uberbbcode extends ubertemplate {
	var $postid=false;
	var $rss=false;
	var $comment=false;
	
	/*********************************************************
	* uberbbcode($geshi)
	* Should we be using geshi?
	**/
	function uberbbcode($geshi,$postid=false,$rss=false) {
		$geshi=($geshi===true);
		$this->postid=$postid;
		$this->rss=$rss===true;
		$this->comment=$postid===false;
		if($geshi&&!isset($GLOBALS['_geshi'])) {
			$GLOBALS['_geshi']=new geshi('','PHP');
			$GLOBALS['_geshi']->set_header_type(GESHI_HEADER_NONE);
			$GLOBALS['_geshi']->enable_keyword_links(false);
		}
	}
	
	function clean() {
		unset($GLOBALS['_geshi']);
	}
	
	/*********************************************************
	* addCode($string)
	* Processes $string in sequence, and adds component blocks to template
	**/
	function addCode($string) {
		//The start position and type of the first interesting block
		$start=strlen($string);
		$end=strlen($string);
		$name=false;
		$type=0; //0 - literal, 1 - code, 2 - html
		
		/*
		* Define our delimiters
		*/
		$delims=array(
			//Code
			1=>array(
				's'=>'[code]',
				'e'=>'[/code]',
			),
			//Customized Code
			2=>array(
				'ss'=>'[code=',
				'se'=>']',
				'es'=>'',
				'ee'=>'[/code]',
			),
			//HTML
			3=>array(
				's'=>'[html]',
				'e'=>'[/html]',
			),
			//Readmore
			4=>array(
				's'=>'[readmore]',
				'e'=>'[/readmore]',
			),
		);
		
		/*
		* Calculate critical lengths
		*/
		$delims[1]['frontpad']=strlen($delims[1]['s']);
		$delims[1]['totalpad']=$delims[1]['frontpad']+strlen($delims[1]['e']);
		
		$delims[2]['frontpad']=strlen($delims[2]['ss'])+strlen($delims[2]['se']);
		$delims[2]['totalpad']=$delims[2]['frontpad']+strlen($delims[2]['es'])+strlen($delims[2]['ee']);
		
		$delims[3]['frontpad']=strlen($delims[3]['s']);
		$delims[3]['totalpad']=$delims[3]['frontpad']+strlen($delims[3]['e']);
		
		$delims[4]['frontpad']=strlen($delims[4]['s']);
		$delims[4]['totalpad']=$delims[4]['frontpad']+strlen($delims[4]['e']);
		
		//Locate the next interesting block
		$code=$this->nextTag($string,$delims[1]['s'],$delims[1]['e'],false,0,true);
		if($code!==false&&$code['startpos']<$start) {
			$start=$code['startpos'];
			$end=$code['endpos'];
			$type=1;
		}
		$customcode=$this->nextBlock($string,$delims[2]['ss'],$delims[2]['se'],$delims[2]['es'],$delims[2]['ee'],true);
		if($customcode!==false&&$customcode['startpos']<$start) {
			$start=$customcode['startpos'];
			$end=$customcode['endpos'];
			$name=$customcode['tagname'];
			$type=2;
		}
		$html=$this->nextTag($string,$delims[3]['s'],$delims[3]['e'],false,0,true);
		if($html!==false&&$html['startpos']<$start) {
			$start=$html['startpos'];
			$end=$html['endpos'];
			$type=3;
		}
		$readmore=$this->nextTag($string,$delims[4]['s'],$delims[4]['e'],false,0,true);
		if($readmore!==false&&$readmore['startpos']<$start) {
			$start=$readmore['startpos'];
			$end=$readmore['endpos'];
			$type=4;
		}
		
		//Now we check if the entire string is just a literal
		if($type==0) {
			$this->addEscapedLiteral($string);
			return;
		}
		//Then we check if the first block of the string is a literal
		elseif($start>0) {
			//Add a literal block, since we know where the first interesting tag starts
			$this->addEscapedLiteral(substr($string,0,$start));
			$end=$start;
		}
		//If not, add the respective block, without the original tags
		else {
			switch($type) {
				case 1:
					$this->addCodeBlock(false,substr($string,$delims[1]['frontpad'],$end-$delims[1]['totalpad']));
				break;
				case 2:
					$this->addCodeBlock($name,substr($string,$delims[2]['frontpad']+strlen($name),$end-$delims[2]['totalpad']-strlen($name)));
				break;
				case 3:
					$this->addHTML(substr($string,$delims[3]['frontpad'],$end-$delims[3]['totalpad']));
				break;
				case 4:
					$this->addReadmore(substr($string,$delims[4]['frontpad'],$end-$delims[4]['totalpad']),$this->postid);
				break;
				default:
					trigger_error('Unknown bbcode block type ('.$type.')',E_USER_ERROR);
			}
		}
		
		//Is there still some more to add?
		if($end<strlen($string)-1) {
			//Add the rest of the string
			$this->addCode(substr($string,$end));
		}
	}	
	
	/*********************************************************
	* process()
	* Evaluates the post
	**/
	function process($readmore) {
		$this->generated='';
		$first_readmore=false;
		foreach($this->blocks as $blockname => $obj) {
			
			//Is this a readmore?
			if(is_a($obj,'readmore')) {
				//Are we supposed to stop here?
				if(!$readmore) {
					$this->generated.=$obj->get($this->values,false);
					break;
				}
				//Ok moving on, but is this the first readmore?
				if(!$first_readmore) {
					$first_readmore=true;
					$this->generated.=$obj->get($this->values,true); //Print out an empty anchor
				}
			}
			else {
				$this->generated.=$obj->get($this->values);
			}
		}
	}
	
	function addEscapedLiteral($string) {
		$this->blocks[]=new escapedliteral($string);
	}
	
	function addCodeBlock($language,$string) {
		$this->blocks[]=new code($language,$string,$this->rss);
	}
	
	function addHTML($string) {
		//Don't parse for comments!
		if(!$this->comment)
			$this->blocks[]=new literal($string);
		else
			$this->blocks[]=new escapedliteral($string);
	}
	
	function addReadmore($string,$post) {
		//Don't add for comments
		if(!$this->comment)
			$this->blocks[]=new readmore($string,$post,$this->rss);
	}
}

class readmore extends block {
	
	/*********************************************************
	* readmore($value)
	* Saves this value
	**/
	function readmore($value,$post,$rss=false) {
		$this->generated='';
		$this->value=$value;
		$this->post=$post;
		$this->rss=$rss===true;
	}
	
	/*********************************************************
	* get()
	* Get this block's value
	**/
	function get(&$values,$invisible=false) {
		$this->process($values,$invisible);
		return $this->generated;
	}
	
	/*********************************************************
	* process()
	* Processes this negative boolean with the given values
	**/
	function process(&$values,$invisible=false) {
		if($invisible) {
			if(!$this->rss)
				$this->generated='<a name="readmorepoint"></a>';
		}
		else
			$this->generated='<a href="index.php?post='.$this->post.'#readmorepoint">'.$this->value.'</a>';
	}
}

class code extends block {
	
	/*********************************************************
	* code($language=false$string)
	* Saves this bit of code
	**/
	function code($language,$string,$rss) {
		$this->language=$language;
		$this->code=$string;
		$this->rss=$rss===true;
	}
	
	/*********************************************************
	* process($value)
	* Processes this block
	**/
	function process(&$value) {
		$this->generated='
			<div class="code">
				<span class="codetitle">
					Code
		';
					
		//Is GESHI enabled, and can we highlight this language?
		if(isset($GLOBALS['_geshi'])&&$language!==false&&$GLOBALS['_geshi']->set_language($this->language)) {
			$GLOBALS['_geshi']->set_source($this->code);
			$parsedcode=$GLOBALS['_geshi']->parse_code();
			
			if($this->rss)
				$this->generated='<em>Code ('.$this->language.'):</em><br /><tt>'.$parsedcode.'</tt>';
			//If yes, specify the language being used
			else
				$this->generated.='
					 ('.$this->language.'):
				</span>
				<br />
				<tt>'.
					$parsedcode.
				'</tt>
				</div>';
		}
		else {
			//If not, do not highlight the post
			if($this->rss)
				$this->generated='<em>Code:</em><br /><tt>'.str_replace("\n",'<br />',htmlentities($this->code,ENT_QUOTES,"UTF-8")).'</tt>';
			else
				$this->generated.=':
					</span>
					<br />
					<tt>'.
						//Just replace with line breaks
						str_replace("\n",'<br />',htmlentities($this->code,ENT_QUOTES,"UTF-8")).
					'
					</tt>
				</div>';
		}
	}


}

class escapedliteral extends block {
	
	/*********************************************************
	* literal($value)
	* Saves this value
	**/
	function escapedliteral($value='') {
		$this->generated=$this->bbcode(htmlentities($value,ENT_QUOTES,'UTF-8'));
	}
	
	function bbcode($string,$rss=false) {
			$bb_find = array(
			'/\[attach\](.*?)\[\/attach\]/is',
			'/\[attach\=(.*?)\](.*?)\[\/attach\]/is',
			
			'/\[h1\](.*?)\[\/h1\]/is',
			'/\[h2\](.*?)\[\/h2\]/is',
			'/\[h3\](.*?)\[\/h3\]/is',
			'/\[h4\](.*?)\[\/h4\]/is',
			'/\[h5\](.*?)\[\/h5\]/is',
			
			'/\[hr]/',
			
			'/\[quote\](.*?)\[\/quote\]/is',
			'/\[quote=(.*?)\](.*?)\[\/quote\]/is',
			
			'/\[b\](.*?)\[\/b\]/is',
			'/\[i\](.*?)\[\/i\]/is',
			'/\[u\](.*?)\[\/u\]/is',
			
			'/\[sub\](.*?)\[\/sub\]/is',
			'/\[sup\](.*?)\[\/sup\]/is',
			'/\[s\](.*?)\[\/s\]/is',
			
			'/\[post\=(.*?)\](.*?)\[\/post\]/is',
			'/\[page\=(.*?)\](.*?)\[\/page\]/is',
			'/\[blog\](.*?)\[\/blog\]/is',
			
			'/\[url\=(.*?)\](.*?)\[\/url\]/is',
			'/\[url\](.*?)\[\/url\]/is',
			'/\[img\](.*?)\[\/img\]/is',
			
			'/\[right\](.*?)\[\/right\]/is',
			'/\[left\](.*?)\[\/left\]/is',
			'/\[clear\](.*?)\[\/clear\]/is',
			
			'/\n/',
		);
		
		$bb_replace = array(
			$rss?SITE_URL.'/'.UPLOAD_PATH.'/$1':SITE_URL.'/download.php?attach=$1',
			'<a target="_blank" href="'.($rss?SITE_URL.'/'.UPLOAD_PATH.'/':SITE_URL.'/download.php?attach=').'$1" class="content">$2</a>',
			
			'<h1>$1</h1>',
			'<h2>$1</h2>',
			'<h3>$1</h3>',
			'<h4>$1</h4>',
			'<h5>$1</h5>',
			
			$rss?'<hr>':'<hr />',
			
			$rss?'"$1"':'<div class="quote"><span class="quotetitle">Quote:</span><br />$1</div>',
			$rss?'"$2"':'<div class="quote"><span class="quotetitle">Quote ($1):</span><br />$2</div>',
			
			'<strong>$1</strong>',
			'<em>$1</em>',
			'<u>$1</u>',
			
			'<sub>$1</sub>',
			'<sup>$1</sup>',
			'<s>$1</s>',
			
			'<a href="index.php?post=$1" class="content">$2</a>',
			'<a href="index.php?page=$1" class="content">$2</a>',
			'<a href="index.php?blog=true" class="content">$1</a>',
			
			'<a href="$1" class="content" target="_blank">$2</a>',
			'<a href="$1" class="content" target="_blank">$1</a>',
			$rss?'<img alt="user posted image" src="$1"></img>':
			'<img alt="user posted image" src="$1" />',
			
			$rss?'$1':'<span style="float:right;">$1</span>',
			$rss?'$1':'<span style="float:left;">$1</span>',
			$rss?'$1':'<span style="clear:both;">$1</span>',
			
			$rss?'<br>':'<br />',
		);
		
		return preg_replace($bb_find, $bb_replace,$string);
	}
}
?>