<?php

/*
+ ----------------------------------------------------------------------------+
|     e107 website system
|
|     ©Steve Dunstan 2001-2002
|     http://e107.org
|     jalist@e107.org
|
|     Released under the terms and conditions of the
|     GNU General Public License (http://gnu.org).
|
|     $Source: /cvsroot/e107/e107_0.7/e107_handlers/bbcode_handler.php,v $
|     $Revision: 1.52 $
|     $Date: 2007/02/16 22:44:25 $
|     $Author: e107steved $
+----------------------------------------------------------------------------+
*/

if (!defined('e107_INIT')) { exit; }

class e_bbcode
{
	var $bbList;			// Caches the file contents for each bbcode processed
	var $bbLocation;		// Location for each file - 'core' or a plugin name

	function e_bbcode()
	{
		global $pref;
		$core_bb = array(
		'blockquote', 'img', 'i', 'u', 'center',
		'_br', 'color', 'size', 'code',
		'html', 'flash', 'link', 'email',
		'url', 'quote', 'left', 'right',
		'b', 'justify', 'file', 'stream',
		'textarea', 'list', 'php', 'time',
		'spoiler', 'hide'
		);

		foreach($core_bb as $c)
		{
		  $this->bbLocation[$c] = 'core';
		}

		// grab list of plugin bbcodes.
		if(isset($pref['bbcode_list']) && $pref['bbcode_list'] != '')
		{
          foreach($pref['bbcode_list'] as $path=>$namearray)
		  {
			foreach($namearray as $code=>$uclass)
			{
           	  $this->bbLocation[$code] = $path;
			}
		  }
		}

		// Eliminate duplicates
		$this->bbLocation = array_diff($this->bbLocation, array(''));
		krsort($this->bbLocation);
	}



  function parseBBCodes($value, $p_ID, $force_lower = 'default')
  {
	global $postID;
	$postID = $p_ID;

  if (strlen($value) <= 6) return $value;     // Don't waste time on trivia!
  if ($force_lower == 'default') $force_lower = TRUE;	// Set the default behaviour if not overridden
  $code_stack = array();				// Stack for unprocessed bbcodes and text
  $unmatch_stack = array();				// Stack for unmatched bbcodes
  $result = '';							// Accumulates fully processed text
  $stacktext = '';						// Accumulates text which might be subject to one or more bbcodes

  $content = preg_split('#(\[(?:\w|/\w).*?\])#mis', $value, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
  
  foreach ($content as $cont)
  {  // Each chunk is either a bbcode or a piece of text
    $is_proc = FALSE;
    while (!$is_proc)
	{
	  $oddtext = '';
      if ($cont[0] == '[')
	  {  // We've got a bbcode - split it up and process it
		$pattern = '#^\[(/?)([A-Za-z]+)(\d*)([=:]?)(.*?)]$#i';
		// $matches[0] - same as the input text
		// $matches[1] - '/' for a closing tag. Otherwise empty string
		// $matches[2] - the bbcode word
		// $matches[3] - any digits immediately following the bbcode word
		// $matches[4] - '=' or ':' according to the separator used
		// $matches[5] - any parameter
		$match_count = preg_match($pattern,$cont,$matches);
	    $bbparam = $matches[5];
        $bbword = $matches[2];
	    if (($bbword) && ($bbword == trim($bbword)))
	    {  // Got a code to process here
		  if ($force_lower) $bbword = strtolower($bbword);
		  if ($matches[1] == '/')
		  {  // Closing code to process
		    $found = FALSE;
		    $i = 0;
		    while ($i < count($code_stack))
		    {     // See if code is anywhere on the stack. 
		      if (($code_stack[$i]['type'] == 'bbcode') && ($code_stack[$i]['code'] == $bbword) && ($code_stack[0]['numbers'] == $matches[3]))
		      {
		        $found = TRUE;
			    break;
		      }
		      $i++;
		    }
		
		    if ($found)
		    {
		      $found = FALSE;   // Use as 'done' variable now
			  // Code is on stack - $i has index number. Process text, discard unmatched open codes, process 'our' code
			  while ($i > 0) { $unmatch_stack[] = array_shift($code_stack); $i--; }    // Get required code to top of stack
			  
			  // Pull it off using array_shift - this keeps it as a zero-based array, newest first.
			  while (!$found && (count($code_stack) != 0))
			  {
		        switch ($code_stack[0]['type'])
			    {
			      case 'text' :
			        $stacktext = $code_stack[0]['code'].$stacktext;   // Need to insert text at front
				    array_shift($code_stack);
			        break;
			      case 'bbcode' :
			        if (($code_stack[0]['code'] == $bbword) && ($code_stack[0]['numbers'] == $matches[3]))
				    {
				      $stacktext = $this->proc_bbcode($bbword,$code_stack[0]['param'],$stacktext,$bbparam);
				      array_shift($code_stack);
				      // Intentionally don't terminate here - may be some text we can clean up
				      $bbword='';    // Necessary to make sure we don't double process if several instances on stack
					  while (count($unmatch_stack) != 0) { array_unshift($code_stack,array_pop($unmatch_stack));  }
				    }
				    else
				    {
					  {
				        $found = TRUE;  // Terminate on unmatched bbcode
					  }
				    }
			        break;
			    }
			    if (count($code_stack) == 0)
			    {
			      $result .= $stacktext;
			      $stacktext = '';
			      $found = TRUE;
			    }
		      }
	          $is_proc = TRUE;
	        }
		  }
		  else
		  {  // Opening code to process
		     // If its a single code, we can process it now. Otherwise just stack the value
		    if (array_key_exists('_'.$bbword,$this->bbLocation))
		    {  // Single code to process
	          $stacktext .= $this->proc_bbcode('_'.$bbword);
			  $is_proc = TRUE;
		    }
		    elseif (array_key_exists($bbword,$this->bbLocation))
		    {
		      if ($stacktext != '')
			  { // Stack the text we've accumulated so far
			    array_unshift($code_stack,array('type' => 'text','code' => $stacktext));
			    $stacktext = '';
			  }
	          array_unshift($code_stack,array('type' => 'bbcode','code' => $bbword, 'numbers'=> $matches[3], 'param'=>$bbparam));
	          $is_proc = TRUE;
		    }
	      }
	    }
		// Next lines could be deleted - but gives better rejection of 'stray' opening brackets
		if ((!$is_proc) && (($temp = strrpos($cont,"[")) !== 0)) 
		{
		  $oddtext = substr($cont,0,$temp);
		  $cont = substr($cont,$temp);
		}
	  }
	
	  if (!$is_proc)
	  {  // We've got some text between bbcodes (or possibly text in front of a bbcode)
	    if ($oddtext == '') { $oddtext = $cont; $is_proc = TRUE; }
	    if (count($code_stack) == 0)
	    {  // Can just add text to answer
	      $result .= $oddtext;
	    }
	    else
	    {  // Add to accumulator at this level
	      $stacktext .= $oddtext;
	    }
      }
	}
  }

// Basically done - just tidy up now  
  // If there's still anything on the stack, we need to process it
  while (count($code_stack) != 0)
  {
	switch ($code_stack[0]['type'])
	{
	  case 'text' :
	    $stacktext = $code_stack[0]['code'].$stacktext;   // Need to insert text at front
		array_shift($code_stack);
	    break;
	  case 'bbcode' :
	    $stacktext = '['.$code_stack[0]['code'].']'.$stacktext;   // To discard unmatched codes, delete this line
		array_shift($code_stack);  		// Just discard any unmatched bbcodes
		break;
	}
  }
  $result .= $stacktext; 
  return $result;
  }



function proc_bbcode($code, $param1='',$code_text_par='', $param2='')
// Invoke an actual bbcode handler
// $code - textual value of the bbcode (already begins with '_' if a single code)
// $param1 - any text after '=' in the opening code
// $code_text_par - text between the opening and closing codes
// $param2 - any text after '=' for the closing code
	{
		global $tp, $postID, $code_text, $parm;
		$parm = $param1;
		$code_text = $tp->replaceConstants($code_text_par);

		if (E107_DEBUG_LEVEL)
		{
			global $db_debug;
			$db_debug->logCode(1, $code, $parm, $postID);
		}

		if (is_array($this->bbList) && array_key_exists($code, $this->bbList))
		{	// Check the bbcode 'cache'
			$bbcode = $this->bbList[$code];
		}
		else
		{	// Find the file
			if ($this->bbLocation[$code] == 'core')
			{
				$bbFile = e_FILE.'bbcode/'.strtolower(str_replace('_', '', $code)).'.bb';
			}
			else
			{	// Add code to check for plugin bbcode addition
				$bbFile = e_PLUGIN.$this->bbLocation[$code].'/'.strtolower($code).'.bb';
			}
			if (file_exists($bbFile))
			{
				$bbcode = file_get_contents($bbFile);
				$this->bbList[$code] = $bbcode;
			}
			else
			{
				$this->bbList[$code] = '';
				return false;
			}
		}
		global $e107_debug;
		if(E107_DBG_BBSC)
		{
			trigger_error("starting bbcode [$code]", E_USER_ERROR);
		}
		ob_start();
		$bbcode_return = eval($bbcode);
		$bbcode_output = ob_get_contents();
		ob_end_clean();

		/* added to remove possibility of nested bbcode exploits ... */
		if(strpos($bbcode_return, "[") !== FALSE)
		{
			$exp_search = array("eval", "expression");
			$exp_replace = array("ev<b></b>al", "expres<b></b>sion");
			$bbcode_return = str_replace($exp_search, $exp_replace, $bbcode_return);
		}
		return $bbcode_output.$bbcode_return;
	}
}
?>
