resizePrefs = varset($pref['resize_dimensions']);
$this->core_bb = array(
'alert',
'blockquote', 'img', 'i', 'u', 'center',
'_br', 'color', 'size', 'code',
'flash', 'link', 'email',
'url', 'quote', 'left', 'right',
'b', 'justify', 'file', 'stream',
'textarea', 'list', 'time',
'spoiler', 'hide', 'youtube', 'sanitised',
'p', 'h', 'nobr', 'block', 'table', 'th', 'tr', 'tbody', 'td', 'video', 'glyph'
);
foreach($this->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);
}
/**
* Parse a string for bbcodes.
* Process using the 'pre-save' or 'display' routines as appropriate
*
* @var string $value - the string to be processed
* @var int $p_ID - ID of a user (the 'post ID') needed by some bbcodes in display mode
* @var string|boolean $force_lower - determines whether bbcode detection is case-insensitive
* TRUE - case-insensitive
* 'default' - case-insensitive
* FALSE - case-sensitive (only lower case bbcodes processed)
* @var string|boolean $bbStrip - determines action when a bbcode is encountered.
* TRUE (boolean or word), all bbcodes are stripped.
* FALSE - normal display processing of all bbcodes
* comma separated (lower case) list - only the listed codes are stripped (and the rest are processed)
* If the first word is 'PRE', sets pre-save mode. Any other parameters follow, comma separated
*
* @return string processed data
*
* Code uses a crude stack-based syntax analyser to handle nested bbcodes (including nested 'size' bbcodes, for example)
*/
function parseBBCodes($value, $p_ID='', $force_lower = 'default', $bbStrip = FALSE)
{
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
$nopro = FALSE; // Blocks processing within [code]...[/code] tags
$this->preProcess = FALSE;
$strip_array = array();
if (!is_bool($bbStrip))
{
$strip_array = explode(',',$bbStrip);
if ($strip_array[0] == 'PRE')
{
$this->preProcess = "toDB";
unset($strip_array[0]);
if (count($strip_array) == 0)
{
$bbStrip = FALSE;
}
elseif (in_array('TRUE', $strip_array))
{
$bbStrip = TRUE;
}
}
}
$pattern = '#^\[(/?)([A-Za-z_]+)(\d*)([=:]?)(.*?)]$#i'; // Pattern to split up bbcodes
// $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
$content = preg_split('#(\[(?:\w|/\w).*?\])#ms', $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
$match_count = preg_match($pattern,$cont,$matches);
$bbparam = (isset($matches[5])) ? $matches[5] : '';
$bbword = (isset($matches[2])) ? $matches[2] : '';
if($cont[1] != '/')
{
$bbsep = varset($matches[4]);
}
if ($force_lower) $bbword = strtolower($bbword);
if ($nopro && ($bbword == 'code') && ($matches[1] == '/')) $nopro = FALSE; // End of code block
if (($bbword) && ($bbword == trim($bbword)) && !$nopro)
{ // Got a code to process here
if (($bbStrip === TRUE) || in_array($bbword,$strip_array))
{
$is_proc = TRUE; // Just discard this bbcode
}
else
{
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, $code_stack[0]['bbsep'], $code_stack[0]['block'].$stacktext.$cont);
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
if (count($code_stack) == 0)
{
$result .= $this->proc_bbcode('_'.$bbword,$bbparam,'','','',$cont);
}
else
{
$stacktext .= $this->proc_bbcode('_'.$bbword,$bbparam,'','','',$cont);
}
$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, 'bbsep' => $bbsep, 'block' => $cont));
if ($bbword == 'code') $nopro = TRUE;
$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;
}
/**
* Process a bbcode
*
* @var string $code - textual value of the bbcode (already begins with '_' if a single code)
* @var string $param1 - any text after '=' in the opening code
* @var string $code_text_par - text between the opening and closing codes
* @var string $param2 - any text after '=' for the closing code
* @var char $sep - character separating bbcode name and any parameters
* @var string $full_text - the 'raw' text between, and including, the opening and closing bbcode tags
* @return string
*/
private function proc_bbcode($code, $param1='', $code_text_par='', $param2='', $sep='', $full_text='')
{
global $tp, $postID, $code_text, $parm;
$parm = $param1;
$code_text = $code_text_par;
$className = null;
$debugFile = null;
if (is_array($this->bbList) && array_key_exists($code, $this->bbList))
{ // Check the bbcode 'cache'
$bbcode = $this->bbList[$code];
$debugFile = "(cached)";
}
else
{ // Find the file
if ($this->bbLocation[$code] == 'core')
{
$bbPath = e_CORE.'bbcodes/';
$bbFile = strtolower(str_replace('_', '', $code));
$debugFile = $bbFile;
}
else
{ // Add code to check for plugin bbcode addition
$bbPath = e_PLUGIN.$this->bbLocation[$code].'/';
$bbFile = strtolower($code);
$debugFile = $bbFile;
}
if (file_exists($bbPath.'bb_'.$bbFile.'.php'))
{ // Its a bbcode class file
require_once($bbPath.'bb_'.$bbFile.'.php');
//echo "Load: {$bbFile}.php
";
$className = 'bb_'.$code;
$this->bbList[$code] = new $className();
$debugFile = $bbPath.'bb_'.$bbFile.'.php';
}
elseif (file_exists($bbPath.$bbFile.'.bb'))
{
$bbcode = file_get_contents($bbPath.$bbFile.'.bb');
$this->bbList[$code] = $bbcode;
$debugFile = $bbPath.$bbFile.'.bb';
}
else
{
$this->bbList[$code] = '';
//echo "
File not found: {$bbFile}.php
";
return false;
}
}
if (E107_DEBUG_LEVEL)
{
$info = array(
'class' =>$className,
'path' => $debugFile,
// 'text' => $full_text
);
e107::getDebug()->logCode(1, $code, $parm, print_a($info,true));
}
if (is_object($this->bbList[$code]))
{
if ($this->preProcess == 'toDB')
{
//echo "Preprocess: ".htmlspecialchars($code_text).", params: {$param1}
";
return $this->bbList[$code]->bbPreSave($code_text, $param1);
}
// if($this->preProcess == 'toWYSIWYG')//XXX FixMe NOT working - messes with default toHTML behavior.
// {
// return $this->bbList[$code]->bbWYSIWYG($code_text, $param1);
// }
return $this->bbList[$code]->bbPreDisplay($code_text, $param1);
}
if ($this->preProcess == 'toDB') return $full_text; // No change
/**
* @todo - capturing output deprecated
*/
ob_start();
try
{
$bbcode = isset($bbcode) && is_string($bbcode) ? $bbcode : '';
$bbcode_return = eval($bbcode); //FIXME notice removal
}
catch (ParseError $e)
{
$error = $debugFile." -- ".$e->getMessage();
}
$bbcode_output = ob_get_clean();
if(!empty($error))
{
trigger_error($error, E_USER_NOTICE);
}
$bbcode_return = isset($bbcode_return) ? $bbcode_return : '';
/* added to remove possibility of nested bbcode exploits ... */
if(strpos($bbcode_return, "[") !== FALSE)
{
$exp_search = array("eval", "expression");
$exp_replace = array("eval", "expression");
$bbcode_return = str_replace($exp_search, $exp_replace, $bbcode_return);
}
return $bbcode_output.$bbcode_return;
}
/** Grab a list of bbcode content . ie. all [img]xxxx[/img] within a block of text.
* @var string $type - bbcode eg. 'img' or 'youtube'
* @var string $text - text to be processed for bbcode content
* @var string $path - optional path to prepend to output if http or {e_xxxx} is not found.
* @return array|null
*/
function getContent($type,$text,$path='')
{
if(!in_array($type,$this->core_bb))
{
return null;
}
if(strpos(ltrim($text), '[html]') === 0 && $type == 'img') // support for html img tags inside [html] bbcode.
{
$tmp = e107::getParser()->getTags($text,'img');
if(!empty($tmp['img']))
{
$mtch = array();
foreach($tmp['img'] as $k)
{
$mtch[1][] = str_replace('"','',trim($k['src']));
// echo $k['src']."
";
}
}
}
else // regular bbcode;
{
preg_match_all("/\[".$type."(?:[^\]]*)?]([^\[]*)(?:\[\/".$type."])/im",$text,$mtch);
}
$ret = array();
if(!empty($mtch) && is_array($mtch[1]))
{
$tp = e107::getParser();
foreach($mtch[1] as $i)
{
if(strpos($i,'http') === 0)
{
$ret[] = $i;
}
elseif(strpos($i,"{e_") === 0)
{
$ret[] = $tp->replaceConstants($i,'full');
}
elseif(strpos($i,'thumb.php')!==false || strpos($i,'media/img/')!==false || strpos($i,'theme/img/')!==false) // absolute path.
{
$ret[] = SITEURLBASE.$i;
}
else
{
$ret[] = $path.$i;
}
}
}
return $ret;
}
//Set the class type for a bbcode eg. news | page | user | {plugin-folder}
/**
* @param $mode
* @return void
*/
function setClass($mode=false)
{
$this->class = $mode;
}
// return the Mode used by the class. eg. news | page | user | {plugin-folder}
/**
* @return bool
*/
function getMode()
{
return $this->class;
}
/**
* @return false|int
*/
function resizeWidth()
{
if($this->class && !empty($this->resizePrefs[$this->class.'-bbcode']['w']))
{
return (int) $this->resizePrefs[$this->class.'-bbcode']['w'];
}
return false;
}
/**
* @return false|int
*/
function resizeHeight()
{
if($this->class && !empty($this->resizePrefs[$this->class.'-bbcode']['h']))
{
return (int) $this->resizePrefs[$this->class.'-bbcode']['h'];
}
return false;
}
// return the class for a bbcode
/**
* @param $type
* @return string
*/
function getClass($type='')
{
$ret = "bbcode-".$type;
if($this->class)
{
$ret .= " bbcode-".$type."-".$this->class;
}
return $ret;
}
/**
* @return void
*/
function clearClass()
{
$this->setClass();
}
//
/**
* NEW bbcode button rendering function. replacing displayHelp();
* @param string (optional) $template eg. news, submitnews, extended, admin, mailout, page, comment, signature
* @param string $id
* @param array $options
* @return string
*/
function renderButtons($template='', $id='', $options=array())
{
$template = (string) $template;
$tp = e107::getParser();
// Notice Removal
$BBCODE_TEMPLATE_SUBMITNEWS = '';
$BBCODE_TEMPLATE_NEWSPOST = '';
$BBCODE_TEMPLATE_MAILOUT = '';
$BBCODE_TEMPLATE_CPAGE = '';
$BBCODE_TEMPLATE_ADMIN = '';
$BBCODE_TEMPLATE_COMMENT = '';
$BBCODE_TEMPLATE_SIGNATURE = '';
require(e107::coreTemplatePath('bbcode')); //correct way to load a core template.
// $pref = e107::getPref('e_bb_list');
//
// if (!empty($pref)) // Load the Plugin bbcode AFTER the templates, so they can modify or replace.
// {
// foreach($pref as $val)
// {
// if(is_readable(e_PLUGIN.$val."/e_bb.php"))
// {
// require(e_PLUGIN.$val."/e_bb.php");
// }
// }
// }
$temp = array();
$temp['news'] = $BBCODE_TEMPLATE_NEWSPOST;
$temp['submitnews'] = $BBCODE_TEMPLATE_SUBMITNEWS;
$temp['extended'] = $BBCODE_TEMPLATE_NEWSPOST;
$temp['admin'] = $BBCODE_TEMPLATE_ADMIN;
$temp['mailout'] = $BBCODE_TEMPLATE_MAILOUT;
$temp['page'] = $BBCODE_TEMPLATE_CPAGE;
$temp['maintenance']= $BBCODE_TEMPLATE_ADMIN;
$temp['comment'] = $BBCODE_TEMPLATE_COMMENT;
$temp['signature'] = $BBCODE_TEMPLATE_SIGNATURE;
if(!isset($temp[$template]))
{
// if template not yet defined, assume that $template is the name of a plugin
// and load the specific bbcode template from the plugin
// see forum plugin "templates/bbcode_template.php" for an example of the definition
$tpl = e107::getTemplate($template, 'bbcode', $template);
if (!empty($tpl))
{
// If the plugin has a template defined for bbcode, add it to the list
$temp[$template] = $tpl;
}
unset($tpl);
}
if(isset($temp[$template]))
{
$BBCODE_TEMPLATE = $temp[$template];
}
elseif(strpos($template,"{")!==false) // custom template provided manually. eg. $template = "
/i', "\t[td]",$text);
$text = preg_replace('//i', "[blockquote]",$text); $text = preg_replace('/ |