diff --git a/pear/HTML/BBCodeParser.php b/pear/HTML/BBCodeParser.php new file mode 100644 index 000000000..68c7fe2bd --- /dev/null +++ b/pear/HTML/BBCodeParser.php @@ -0,0 +1,882 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: BBCodeParser.php,v 1.17 2007/07/02 18:46:30 cweiske Exp $ +// + +/** +* @package HTML_BBCodeParser +* @author Stijn de Reede +* +* +* This is a parser to replace UBB style tags with their html equivalents. It +* does not simply do some regex calls, but is complete stack based +* parse engine. This ensures that all tags are properly nested, if not, +* extra tags are added to maintain the nesting. This parser should only produce +* xhtml 1.0 compliant code. All tags are validated and so are all their attributes. +* It should be easy to extend this parser with your own tags, see the _definedTags +* format description below. +* +* +* Usage: +* $parser = new HTML_BBCodeParser(); +* $parser->setText('normal [b]bold[/b] and normal again'); +* $parser->parse(); +* echo $parser->getParsed(); +* or: +* $parser = new HTML_BBCodeParser(); +* echo $parser->qparse('normal [b]bold[/b] and normal again'); +* or: +* echo HTML_BBCodeParser::staticQparse('normal [b]bold[/b] and normal again'); +* +* +* Setting the options from the ini file: +* $config = parse_ini_file('BBCodeParser.ini', true); +* $options = &PEAR::getStaticProperty('HTML_BBCodeParser', '_options'); +* $options = $config['HTML_BBCodeParser']; +* unset($options); +* +* +* The _definedTags variables should be in this format: +* array('tag' // the actual tag used +* => array('htmlopen' => 'open', // the opening tag in html +* 'htmlclose' => 'close', // the closing tag in html, +* can be set to an empty string +* if no closing tag is present +* in html (like ) +* 'allowed' => 'allow', // tags that are allowed inside +* this tag. Values can be all +* or none, or either of these +* two, followed by a ^ and then +* followed by a comma seperated +* list of exceptions on this +* 'attributes' => array() // an associative array containing +* the tag attributes and their +* printf() html equivalents, to +* which the first argument is +* the value, and the second is +* the quote. Default would be +* something like this: +* 'attr' => 'attr=%2$s%1$s%2$s' +* ), +* 'etc' +* => (...) +* ) +*/ +require_once 'PEAR.php'; + +class HTML_BBCodeParser +{ + /** + * An array of tags parsed by the engine, should be overwritten by filters + * + * @access private + * @var array + */ + var $_definedTags = array(); + + /** + * A string containing the input + * + * @access private + * @var string + */ + var $_text = ''; + + /** + * A string containing the preparsed input + * + * @access private + * @var string + */ + var $_preparsed = ''; + + /** + * An array tags and texts build from the input text + * + * @access private + * @var array + */ + var $_tagArray = array(); + + /** + * A string containing the parsed version of the text + * + * @access private + * @var string + */ + var $_parsed = ''; + + /** + * An array of options, filled by an ini file or through the contructor + * + * @access private + * @var array + */ + var $_options = array( + 'quotestyle' => 'double', + 'quotewhat' => 'all', + 'open' => '[', + 'close' => ']', + 'xmlclose' => true, + 'filters' => 'Basic' + ); + + /** + * An array of filters used for parsing + * + * @access private + * @var array + */ + var $_filters = array(); + + /** + * Constructor, initialises the options and filters + * + * Sets the private variable _options with base options defined with + * &PEAR::getStaticProperty(), overwriting them with (if present) + * the argument to this method. + * Then it sets the extra options to properly escape the tag + * characters in preg_replace() etc. The set options are + * then stored back with &PEAR::getStaticProperty(), so that the filter + * classes can use them. + * All the filters in the options are initialised and their defined tags + * are copied into the private variable _definedTags. + * + * @param array options to use, can be left out + * @return none + * @access public + * @author Stijn de Reede + */ + function HTML_BBCodeParser($options = array()) + { + // set the already set options + $baseoptions = &PEAR::getStaticProperty('HTML_BBCodeParser', '_options'); + if (is_array($baseoptions)) { + foreach ($baseoptions as $k => $v) { + $this->_options[$k] = $v; + } + } + + // set the options passed as an argument + foreach ($options as $k => $v ) { + $this->_options[$k] = $v; + } + + // add escape open and close chars to the options for preg escaping + $preg_escape = '\^$.[]|()?*+{}'; + if ($this->_options['open'] != '' && strpos($preg_escape, $this->_options['open'])) { + $this->_options['open_esc'] = "\\".$this->_options['open']; + } else { + $this->_options['open_esc'] = $this->_options['open']; + } + if ($this->_options['close'] != '' && strpos($preg_escape, $this->_options['close'])) { + $this->_options['close_esc'] = "\\".$this->_options['close']; + } else { + $this->_options['close_esc'] = $this->_options['close']; + } + + // set the options back so that child classes can use them */ + $baseoptions = $this->_options; + unset($baseoptions); + + // return if this is a subclass + if (is_subclass_of($this, 'HTML_BBCodeParser_Filter')) { + return; + } + + // extract the definedTags from subclasses */ + $this->addFilters($this->_options['filters']); + } + + /** + * Option setter + * + * @param string option name + * @param mixed option value + * @author Lorenzo Alberton + */ + function setOption($name, $value) + { + $this->_options[$name] = $value; + } + + /** + * Add a new filter + * + * @param string filter + * @author Lorenzo Alberton + */ + function addFilter($filter) + { + $filter = ucfirst($filter); + if (!array_key_exists($filter, $this->_filters)) { + $class = 'HTML_BBCodeParser_Filter_'.$filter; + @include_once 'HTML/BBCodeParser/Filter/'.$filter.'.php'; + if (!class_exists($class)) { + PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE); + } + $this->_filters[$filter] = new $class; + $this->_definedTags = array_merge( + $this->_definedTags, + $this->_filters[$filter]->_definedTags + ); + } + } + + /** + * Remove an existing filter + * + * @param string $filter + * @author Lorenzo Alberton + */ + function removeFilter($filter) + { + $filter = ucfirst(trim($filter)); + if (!empty($filter) && array_key_exists($filter, $this->_filters)) { + unset($this->_filters[$filter]); + } + // also remove the related $this->_definedTags for this filter, + // preserving the others + $this->_definedTags = array(); + foreach (array_keys($this->_filters) as $filter) { + $this->_definedTags = array_merge( + $this->_definedTags, + $this->_filters[$filter]->_definedTags + ); + } + } + + /** + * Add new filters + * + * @param mixed (array or string) + * @return boolean true if all ok, false if not. + * @author Lorenzo Alberton + */ + function addFilters($filters) + { + if (is_string($filters)) { + //comma-separated list + if (strpos($filters, ',') !== false) { + $filters = explode(',', $filters); + } else { + $filters = array($filters); + } + } + if (!is_array($filters)) { + //invalid format + return false; + } + foreach ($filters as $filter) { + if (trim($filter)){ + $this->addFilter($filter); + } + } + return true; + } + + /** + * Executes statements before the actual array building starts + * + * This method should be overwritten in a filter if you want to do + * something before the parsing process starts. This can be useful to + * allow certain short alternative tags which then can be converted into + * proper tags with preg_replace() calls. + * The main class walks through all the filters and and calls this + * method. The filters should modify their private $_preparsed + * variable, with input from $_text. + * + * @return none + * @access private + * @see $_text + * @author Stijn de Reede + */ + function _preparse() + { + // default: assign _text to _preparsed, to be overwritten by filters + $this->_preparsed = $this->_text; + + // return if this is a subclass + if (is_subclass_of($this, 'HTML_BBCodeParser')) { + return; + } + + // walk through the filters and execute _preparse + foreach ($this->_filters as $filter) { + $filter->setText($this->_preparsed); + $filter->_preparse(); + $this->_preparsed = $filter->getPreparsed(); + } + } + + /** + * Builds the tag array from the input string $_text + * + * An array consisting of tag and text elements is contructed from the + * $_preparsed variable. The method uses _buildTag() to check if a tag is + * valid and to build the actual tag to be added to the tag array. + * + * TODO: - rewrite whole method, as this one is old and probably slow + * - see if a recursive method would be better than an iterative one + * + * @return none + * @access private + * @see _buildTag() + * @see $_text + * @see $_tagArray + * @author Stijn de Reede + */ + function _buildTagArray() + { + $this->_tagArray = array(); + $str = $this->_preparsed; + $strPos = 0; + $strLength = strlen($str); + + while (($strPos < $strLength)) { + $tag = array(); + $openPos = strpos($str, $this->_options['open'], $strPos); + if ($openPos === false) { + $openPos = $strLength; + $nextOpenPos = $strLength; + } + if ($openPos + 1 > $strLength) { + $nextOpenPos = $strLength; + } else { + $nextOpenPos = strpos($str, $this->_options['open'], $openPos + 1); + if ($nextOpenPos === false) { + $nextOpenPos = $strLength; + } + } + $closePos = strpos($str, $this->_options['close'], $strPos); + if ($closePos === false) { + $closePos = $strLength + 1; + } + + if ($openPos == $strPos) { + if (($nextOpenPos < $closePos)) { + // new open tag before closing tag: treat as text + $newPos = $nextOpenPos; + $tag['text'] = substr($str, $strPos, $nextOpenPos - $strPos); + $tag['type'] = 0; + } else { + // possible valid tag + $newPos = $closePos + 1; + $newTag = $this->_buildTag(substr($str, $strPos, $closePos - $strPos + 1)); + if (($newTag !== false)) { + $tag = $newTag; + } else { + // no valid tag after all + $tag['text'] = substr($str, $strPos, $closePos - $strPos + 1); + $tag['type'] = 0; + } + } + } else { + // just text + $newPos = $openPos; + $tag['text'] = substr($str, $strPos, $openPos - $strPos); + $tag['type'] = 0; + } + + // join 2 following text elements + if ($tag['type'] === 0 && isset($prev) && $prev['type'] === 0) { + $tag['text'] = $prev['text'].$tag['text']; + array_pop($this->_tagArray); + } + + $this->_tagArray[] = $tag; + $prev = $tag; + $strPos = $newPos; + } + } + + /** + * Builds a tag from the input string + * + * This method builds a tag array based on the string it got as an + * argument. If the tag is invalid, is returned. The tag + * attributes are extracted from the string and stored in the tag + * array as an associative array. + * + * @param string string to build tag from + * @return array tag in array format + * @access private + * @see _buildTagArray() + * @author Stijn de Reede + */ + function _buildTag($str) + { + $tag = array('text' => $str, 'attributes' => array()); + + if (substr($str, 1, 1) == '/') { // closing tag + + $tag['tag'] = strtolower(substr($str, 2, strlen($str) - 3)); + if (!in_array($tag['tag'], array_keys($this->_definedTags))) { + return false; // nope, it's not valid + } else { + $tag['type'] = 2; + return $tag; + } + } else { // opening tag + + $tag['type'] = 1; + if (strpos($str, ' ') && (strpos($str, '=') === false)) { + return false; // nope, it's not valid + } + + // tnx to Onno for the regex + // split the tag with arguments and all + $oe = $this->_options['open_esc']; + $ce = $this->_options['close_esc']; + $tagArray = array(); + if (preg_match("!$oe([a-z0-9]+)[^$ce]*$ce!i", $str, $tagArray) == 0) { + return false; + } + $tag['tag'] = strtolower($tagArray[1]); + if (!in_array($tag['tag'], array_keys($this->_definedTags))) { + return false; // nope, it's not valid + } + + // tnx to Onno for the regex + // validate the arguments + $attributeArray = array(); + $regex = "![\s$oe]([a-z0-9]+)=(\"[^\s$ce]+\"|[^\s$ce]"; + if ($tag['tag'] != 'url') { + $regex .= "[^=]"; + } + $regex .= "+)(?=[\s$ce])!i"; + preg_match_all($regex, $str, $attributeArray, PREG_SET_ORDER); + foreach ($attributeArray as $attribute) { + $attNam = strtolower($attribute[1]); + if (in_array($attNam, array_keys($this->_definedTags[$tag['tag']]['attributes']))) { + if ($attribute[2][0] == '"' && $attribute[2][strlen($attribute[2])-1] == '"') { + $tag['attributes'][$attNam] = substr($attribute[2], 1, -1); + } else { + $tag['attributes'][$attNam] = $attribute[2]; + } + } + } + return $tag; + } + } + + /** + * Validates the tag array, regarding the allowed tags + * + * While looping through the tag array, two following text tags are + * joined, and it is checked that the tag is allowed inside the + * last opened tag. + * By remembering what tags have been opened it is checked that + * there is correct (xml compliant) nesting. + * In the end all still opened tags are closed. + * + * @return none + * @access private + * @see _isAllowed() + * @see $_tagArray + * @author Stijn de Reede , Seth Price + */ + function _validateTagArray() + { + $newTagArray = array(); + $openTags = array(); + foreach ($this->_tagArray as $tag) { + $prevTag = end($newTagArray); + switch ($tag['type']) { + case 0: + if (($child = $this->_childNeeded(end($openTags), 'text')) && + $child !== false && + /* + * No idea what to do in this case: A child is needed, but + * no valid one is returned. We'll ignore it here and live + * with it until someone reports a valid bug. + */ + $child !== true ) + { + if (trim($tag['text']) == '') { + //just an empty indentation or newline without value? + continue; + } + $newTagArray[] = $child; + $openTags[] = $child['tag']; + } + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + $newTagArray[] = $tag; + break; + + case 1: + if (!$this->_isAllowed(end($openTags), $tag['tag']) || + ($parent = $this->_parentNeeded(end($openTags), $tag['tag'])) === true || + ($child = $this->_childNeeded(end($openTags), $tag['tag'])) === true) { + $tag['type'] = 0; + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + } else { + if ($parent) { + /* + * Avoid use of parent if we can help it. If we are + * trying to insert a new parent, but the current tag is + * the same as the previous tag, then assume that the + * previous tag structure is valid, and add this tag as + * a sibling. To add as a sibling, we need to close the + * current tag. + */ + if ($tag['tag'] == end($openTags)){ + $newTagArray[] = $this->_buildTag('[/'.$tag['tag'].']'); + array_pop($openTags); + } else { + $newTagArray[] = $parent; + $openTags[] = $parent['tag']; + } + } + if ($child) { + $newTagArray[] = $child; + $openTags[] = $child['tag']; + } + $openTags[] = $tag['tag']; + } + $newTagArray[] = $tag; + break; + + case 2: + if (($tag['tag'] == end($openTags) || $this->_isAllowed(end($openTags), $tag['tag']))) { + if (in_array($tag['tag'], $openTags)) { + $tmpOpenTags = array(); + while (end($openTags) != $tag['tag']) { + $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); + $tmpOpenTags[] = end($openTags); + array_pop($openTags); + } + $newTagArray[] = $tag; + array_pop($openTags); + /* why is this here? it just seems to break things + * (nested lists where closing tags need to be + * generated) + while (end($tmpOpenTags)) { + $tmpTag = $this->_buildTag('['.end($tmpOpenTags).']'); + $newTagArray[] = $tmpTag; + $openTags[] = $tmpTag['tag']; + array_pop($tmpOpenTags); + }*/ + } + } else { + $tag['type'] = 0; + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + $newTagArray[] = $tag; + } + break; + } + } + while (end($openTags)) { + $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); + array_pop($openTags); + } + $this->_tagArray = $newTagArray; + } + + /** + * Checks to see if a parent is needed + * + * Checks to see if the current $in tag has an appropriate parent. If it + * does, then it returns false. If a parent is needed, then it returns the + * first tag in the list to add to the stack. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean false if not needed, tag if needed, true if out + * of our minds + * @access private + * @see _validateTagArray() + * @author Seth Price + */ + function _parentNeeded($out, $in) + { + if (!isset($this->_definedTags[$in]['parent']) || + ($this->_definedTags[$in]['parent'] == 'all') + ) { + return false; + } + + $ar = explode('^', $this->_definedTags[$in]['parent']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none'){ + if ($out && in_array($out, $tags)) { + return false; + } + //Create a tag from the first one on the list + return $this->_buildTag('['.$tags[0].']'); + } + if ($ar[0] == 'all' && $out && !in_array($out, $tags)) { + return false; + } + // Tag is needed, we don't know which one. We could make something up, + // but it would be so random, I think that it would be worthless. + return true; + } + + /** + * Checks to see if a child is needed + * + * Checks to see if the current $out tag has an appropriate child. If it + * does, then it returns false. If a child is needed, then it returns the + * first tag in the list to add to the stack. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean false if not needed, tag if needed, true if out + * of our minds + * @access private + * @see _validateTagArray() + * @author Seth Price + */ + function _childNeeded($out, $in) + { + if (!isset($this->_definedTags[$out]['child']) || + ($this->_definedTags[$out]['child'] == 'all') + ) { + return false; + } + + $ar = explode('^', $this->_definedTags[$out]['child']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none'){ + if ($in && in_array($in, $tags)) { + return false; + } + //Create a tag from the first one on the list + return $this->_buildTag('['.$tags[0].']'); + } + if ($ar[0] == 'all' && $in && !in_array($in, $tags)) { + return false; + } + // Tag is needed, we don't know which one. We could make something up, + // but it would be so random, I think that it would be worthless. + return true; + } + + /** + * Checks to see if a tag is allowed inside another tag + * + * The allowed tags are extracted from the private _definedTags array. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean return true if the tag is allowed, false + * otherwise + * @access private + * @see _validateTagArray() + * @author Stijn de Reede + */ + function _isAllowed($out, $in) + { + if (!$out || ($this->_definedTags[$out]['allowed'] == 'all')) { + return true; + } + if ($this->_definedTags[$out]['allowed'] == 'none') { + return false; + } + + $ar = explode('^', $this->_definedTags[$out]['allowed']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none' && in_array($in, $tags)) { + return true; + } + if ($ar[0] == 'all' && in_array($in, $tags)) { + return false; + } + return false; + } + + /** + * Builds a parsed string based on the tag array + * + * The correct html and attribute values are extracted from the private + * _definedTags array. + * + * @return none + * @access private + * @see $_tagArray + * @see $_parsed + * @author Stijn de Reede + */ + function _buildParsedString() + { + $this->_parsed = ''; + foreach ($this->_tagArray as $tag) { + switch ($tag['type']) { + + // just text + case 0: + $this->_parsed .= $tag['text']; + break; + + // opening tag + case 1: + $this->_parsed .= '<'.$this->_definedTags[$tag['tag']]['htmlopen']; + if ($this->_options['quotestyle'] == 'single') $q = "'"; + if ($this->_options['quotestyle'] == 'double') $q = '"'; + foreach ($tag['attributes'] as $a => $v) { + //prevent XSS attacks. IMHO this is not enough, though... + //@see http://pear.php.net/bugs/bug.php?id=5609 + $v = preg_replace('#(script|about|applet|activex|chrome):#is', "\\1:", $v); + $v = htmlspecialchars($v); + $v = str_replace('&amp;', '&', $v); + + if (($this->_options['quotewhat'] == 'nothing') || + (($this->_options['quotewhat'] == 'strings') && is_numeric($v)) + ) { + $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, ''); + } else { + $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, $q); + } + } + if ($this->_definedTags[$tag['tag']]['htmlclose'] == '' && $this->_options['xmlclose']) { + $this->_parsed .= ' /'; + } + $this->_parsed .= '>'; + break; + + // closing tag + case 2: + if ($this->_definedTags[$tag['tag']]['htmlclose'] != '') { + $this->_parsed .= '_definedTags[$tag['tag']]['htmlclose'].'>'; + } + break; + } + } + } + + /** + * Sets text in the object to be parsed + * + * @param string the text to set in the object + * @return none + * @access public + * @see getText() + * @see $_text + * @author Stijn de Reede + */ + function setText($str) + { + $this->_text = $str; + } + + /** + * Gets the unparsed text from the object + * + * @return string the text set in the object + * @access public + * @see setText() + * @see $_text + * @author Stijn de Reede + */ + function getText() + { + return $this->_text; + } + + /** + * Gets the preparsed text from the object + * + * @return string the text set in the object + * @access public + * @see _preparse() + * @see $_preparsed + * @author Stijn de Reede + */ + function getPreparsed() + { + return $this->_preparsed; + } + + /** + * Gets the parsed text from the object + * + * @return string the parsed text set in the object + * @access public + * @see parse() + * @see $_parsed + * @author Stijn de Reede + */ + function getParsed() + { + return $this->_parsed; + } + + /** + * Parses the text set in the object + * + * @return none + * @access public + * @see _preparse() + * @see _buildTagArray() + * @see _validateTagArray() + * @see _buildParsedString() + * @author Stijn de Reede + */ + function parse() + { + $this->_preparse(); + $this->_buildTagArray(); + $this->_validateTagArray(); + $this->_buildParsedString(); + } + + /** + * Quick method to do setText(), parse() and getParsed at once + * + * @return none + * @access public + * @see parse() + * @see $_text + * @author Stijn de Reede + */ + function qparse($str) + { + $this->_text = $str; + $this->parse(); + return $this->_parsed; + } + + /** + * Quick static method to do setText(), parse() and getParsed at once + * + * @return none + * @access public + * @see parse() + * @see $_text + * @author Stijn de Reede + */ + function staticQparse($str) + { + $p = new HTML_BBCodeParser(); + $str = $p->qparse($str); + unset($p); + return $str; + } +} +?> \ No newline at end of file diff --git a/pear/HTML/BBCodeParser/Filter.php b/pear/HTML/BBCodeParser/Filter.php new file mode 100644 index 000000000..84924871a --- /dev/null +++ b/pear/HTML/BBCodeParser/Filter.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/pear/HTML/BBCodeParser/Filter/Basic.php b/pear/HTML/BBCodeParser/Filter/Basic.php new file mode 100644 index 000000000..58fb328cd --- /dev/null +++ b/pear/HTML/BBCodeParser/Filter/Basic.php @@ -0,0 +1,71 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Basic.php,v 1.6 2007/07/02 16:54:25 cweiske Exp $ +// + +/** +* @package HTML_BBCodeParser +* @author Stijn de Reede +*/ + + +require_once 'HTML/BBCodeParser/Filter.php'; + + + + +class HTML_BBCodeParser_Filter_Basic extends HTML_BBCodeParser_Filter +{ + + /** + * An array of tags parsed by the engine + * + * @access private + * @var array + */ + var $_definedTags = array( 'b' => array( 'htmlopen' => 'strong', + 'htmlclose' => 'strong', + 'allowed' => 'all', + 'attributes'=> array()), + 'i' => array( 'htmlopen' => 'em', + 'htmlclose' => 'em', + 'allowed' => 'all', + 'attributes'=> array()), + 'u' => array( 'htmlopen' => 'span style="text-decoration:underline;"', + 'htmlclose' => 'span', + 'allowed' => 'all', + 'attributes'=> array()), + 's' => array( 'htmlopen' => 'del', + 'htmlclose' => 'del', + 'allowed' => 'all', + 'attributes'=> array()), + 'sub' => array( 'htmlopen' => 'sub', + 'htmlclose' => 'sub', + 'allowed' => 'all', + 'attributes'=> array()), + 'sup' => array( 'htmlopen' => 'sup', + 'htmlclose' => 'sup', + 'allowed' => 'all', + 'attributes'=> array()) + ); + +} + + +?> \ No newline at end of file diff --git a/pear/HTML/BBCodeParser/Filter/Email.php b/pear/HTML/BBCodeParser/Filter/Email.php new file mode 100644 index 000000000..1182d4e4c --- /dev/null +++ b/pear/HTML/BBCodeParser/Filter/Email.php @@ -0,0 +1,85 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Email.php,v 1.5 2007/07/02 16:54:25 cweiske Exp $ +// + +/** +* @package HTML_BBCodeParser +* @author Stijn de Reede +*/ + + +require_once 'HTML/BBCodeParser/Filter.php'; + + + + +class HTML_BBCodeParser_Filter_Email extends HTML_BBCodeParser_Filter +{ + + /** + * An array of tags parsed by the engine + * + * @access private + * @var array + */ + var $_definedTags = array( 'email' => array( 'htmlopen' => 'a', + 'htmlclose' => 'a', + 'allowed' => 'none^img', + 'attributes'=> array('email' =>'href=%2$smailto:%1$s%2$s') + + ) + ); + + + /** + * Executes statements before the actual array building starts + * + * This method should be overwritten in a filter if you want to do + * something before the parsing process starts. This can be useful to + * allow certain short alternative tags which then can be converted into + * proper tags with preg_replace() calls. + * The main class walks through all the filters and and calls this + * method if it exists. The filters should modify their private $_text + * variable. + * + * @return none + * @access private + * @see $_text + * @author Stijn de Reede + */ + function _preparse() + { + $options = PEAR::getStaticProperty('HTML_BBCodeParser','_options'); + $o = $options['open']; + $c = $options['close']; + $oe = $options['open_esc']; + $ce = $options['close_esc']; + $pattern = array( "!(^|\s)([-a-z0-9_.]+@[-a-z0-9.]+\.[a-z]{2,4})!i", + "!".$oe."email(".$ce."|\s.*".$ce.")(.*)".$oe."/email".$ce."!Ui"); + $replace = array( "\\1".$o."email=\\2".$c."\\2".$o."/email".$c, + $o."email=\\2\\1\\2".$o."/email".$c); + $this->_preparsed = preg_replace($pattern, $replace, $this->_text); + } + + +} + + +?> diff --git a/pear/HTML/BBCodeParser/Filter/Extended.php b/pear/HTML/BBCodeParser/Filter/Extended.php new file mode 100644 index 000000000..ae9f39d23 --- /dev/null +++ b/pear/HTML/BBCodeParser/Filter/Extended.php @@ -0,0 +1,97 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Extended.php,v 1.3 2007/07/02 16:54:25 cweiske Exp $ +// + +/** +* @package HTML_BBCodeParser +* @author Stijn de Reede +*/ + + +require_once 'HTML/BBCodeParser/Filter.php'; + + + + +class HTML_BBCodeParser_Filter_Extended extends HTML_BBCodeParser_Filter +{ + + /** + * An array of tags parsed by the engine + * + * @access private + * @var array + */ + var $_definedTags = array( + 'color' => array( 'htmlopen' => 'span', + 'htmlclose' => 'span', + 'allowed' => 'all', + 'attributes'=> array('color' =>'style=%2$scolor:%1$s%2$s')), + 'size' => array( 'htmlopen' => 'span', + 'htmlclose' => 'span', + 'allowed' => 'all', + 'attributes'=> array('size' =>'style=%2$sfont-size:%1$spt%2$s')), + 'font' => array( 'htmlopen' => 'span', + 'htmlclose' => 'span', + 'allowed' => 'all', + 'attributes'=> array('font' =>'style=%2$sfont-family:%1$s%2$s')), + 'align' => array( 'htmlopen' => 'div', + 'htmlclose' => 'div', + 'allowed' => 'all', + 'attributes'=> array('align' =>'style=%2$stext-align:%1$s%2$s')), + 'quote' => array('htmlopen' => 'q', + 'htmlclose' => 'q', + 'allowed' => 'all', + 'attributes'=> array('quote' =>'cite=%2$s%1$s%2$s')), + 'code' => array('htmlopen' => 'code', + 'htmlclose' => 'code', + 'allowed' => 'all', + 'attributes'=> array()), + 'h1' => array('htmlopen' => 'h1', + 'htmlclose' => 'h1', + 'allowed' => 'all', + 'attributes'=> array()), + 'h2' => array('htmlopen' => 'h2', + 'htmlclose' => 'h2', + 'allowed' => 'all', + 'attributes'=> array()), + 'h3' => array('htmlopen' => 'h3', + 'htmlclose' => 'h3', + 'allowed' => 'all', + 'attributes'=> array()), + 'h4' => array('htmlopen' => 'h4', + 'htmlclose' => 'h4', + 'allowed' => 'all', + 'attributes'=> array()), + 'h5' => array('htmlopen' => 'h5', + 'htmlclose' => 'h5', + 'allowed' => 'all', + 'attributes'=> array()), + 'h6' => array('htmlopen' => 'h6', + 'htmlclose' => 'h6', + 'allowed' => 'all', + 'attributes'=> array()) + + ); + + +} + +?> \ No newline at end of file diff --git a/pear/HTML/BBCodeParser/Filter/Images.php b/pear/HTML/BBCodeParser/Filter/Images.php new file mode 100644 index 000000000..93152b09e --- /dev/null +++ b/pear/HTML/BBCodeParser/Filter/Images.php @@ -0,0 +1,79 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Images.php,v 1.8 2007/07/02 17:44:47 cweiske Exp $ +// + +/** +* @package HTML_BBCodeParser +* @author Stijn de Reede +*/ +require_once 'HTML/BBCodeParser/Filter.php'; + +class HTML_BBCodeParser_Filter_Images extends HTML_BBCodeParser_Filter +{ + + /** + * An array of tags parsed by the engine + * + * @access private + * @var array + */ + var $_definedTags = array( + 'img' => array( + 'htmlopen' => 'img', + 'htmlclose' => '', + 'allowed' => 'none', + 'attributes'=> array( + 'img' => 'src=%2$s%1$s%2$s', + 'w' => 'width=%2$s%1$d%2$s', + 'h' => 'height=%2$s%1$d%2$s', + 'alt' => 'alt=%2$s%1$s%2$s', + ) + ) + ); + + /** + * Executes statements before the actual array building starts + * + * This method should be overwritten in a filter if you want to do + * something before the parsing process starts. This can be useful to + * allow certain short alternative tags which then can be converted into + * proper tags with preg_replace() calls. + * The main class walks through all the filters and and calls this + * method if it exists. The filters should modify their private $_text + * variable. + * + * @return none + * @access private + * @see $_text + * @author Stijn de Reede + */ + function _preparse() + { + $options = PEAR::getStaticProperty('HTML_BBCodeParser','_options'); + $o = $options['open']; + $c = $options['close']; + $oe = $options['open_esc']; + $ce = $options['close_esc']; + $this->_preparsed = preg_replace( + "!".$oe."img(\s?.*)".$ce."(.*)".$oe."/img".$ce."!Ui", + $o."img=\"\$2\"\$1".$c.$o."/img".$c, + $this->_text); + } +} \ No newline at end of file diff --git a/pear/HTML/BBCodeParser/Filter/Links.php b/pear/HTML/BBCodeParser/Filter/Links.php new file mode 100644 index 000000000..58e096e96 --- /dev/null +++ b/pear/HTML/BBCodeParser/Filter/Links.php @@ -0,0 +1,200 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Links.php,v 1.12 2007/07/02 18:26:17 cweiske Exp $ +// + +/** +* @package HTML_BBCodeParser +* @author Stijn de Reede +*/ +require_once 'HTML/BBCodeParser/Filter.php'; + +/** + * + */ +class HTML_BBCodeParser_Filter_Links extends HTML_BBCodeParser_Filter +{ + /** + * List of allowed schemes + * + * @access private + * @var array + */ + var $_allowedSchemes = array('http', 'https', 'ftp'); + + /** + * Default scheme + * + * @access private + * @var string + */ + var $_defaultScheme = 'http'; + + /** + * An array of tags parsed by the engine + * + * @access private + * @var array + */ + var $_definedTags = array( + 'url' => array( + 'htmlopen' => 'a', + 'htmlclose' => 'a', + 'allowed' => 'none^img', + 'attributes'=> array('url' => 'href=%2$s%1$s%2$s') + ) + ); + + + /** + * Executes statements before the actual array building starts + * + * This method should be overwritten in a filter if you want to do + * something before the parsing process starts. This can be useful to + * allow certain short alternative tags which then can be converted into + * proper tags with preg_replace() calls. + * The main class walks through all the filters and and calls this + * method if it exists. The filters should modify their private $_text + * variable. + * + * @return none + * @access private + * @see $_text + * @author Stijn de Reede + * @author Seth Price + */ + function _preparse() + { + $options = PEAR::getStaticProperty('HTML_BBCodeParser', '_options'); + $o = $options['open']; + $c = $options['close']; + $oe = $options['open_esc']; + $ce = $options['close_esc']; + + $schemes = implode('|', $this->_allowedSchemes); + + $pattern = array( "/(?_text); + $pp = preg_replace($pattern[1], $o."url=\$2\$1\$2".$o."/url".$c, $pp); + $this->_preparsed = preg_replace_callback($pattern[2], array($this, 'smarterPPLink'), $pp); + + } + + /** + * Intelligently expand a URL into a link + * + * @return string + * @access private + * @author Seth Price + * @author Lorenzo Alberton + */ + function smarterPPLinkExpand($matches) + { + //echo '
';var_dump($matches);echo '

'; + $options = PEAR::getStaticProperty('HTML_BBCodeParser','_options'); + $o = $options['open']; + $c = $options['close']; + + //If we have an intro tag that is [url], then skip this match + if ($matches[1] == $o.'url'.$c) { + return $matches[0]; + } + + $punctuation = '.,;:'; // Links can't end with these chars + $trailing = ''; + // Knock off ending punctuation + $last = substr($matches[2], -1); + while (strpos($punctuation, $last) !== false) { + // Last character is punctuation - remove it from the url + $trailing = $last.$trailing; + $matches[2] = substr($matches[2], 0, -1); + $last = substr($matches[2], -1); + } + + $off = strpos($matches[2], ':'); + + //Is a ":" (therefore a scheme) defined? + if ($off === false) { + /* + * Create a link with the default scheme of http. Notice that the + * text that is viewable to the user is unchanged, but the link + * itself contains the "http://". + */ + return $matches[1].$o.'url='.$this->_defaultScheme.'://'.$matches[2].$c.$matches[2].$o.'/url'.$c.$trailing; + } + + $scheme = substr($matches[2], 0, $off); + + /* + * If protocol is in the approved list than allow it. Note that this + * check isn't really needed, but the created link will just be deleted + * later in smarterPPLink() if we create it now and it isn't on the + * scheme list. + */ + if (in_array($scheme, $this->_allowedSchemes)) { + return $matches[1].$o.'url'.$c.$matches[2].$o.'/url'.$c.$trailing; + } + + return $matches[0]; + } + + /** + * Finish preparsing URL to clean it up + * + * @return string + * @access private + * @author Seth Price + */ + function smarterPPLink($matches) + { + $options = PEAR::getStaticProperty('HTML_BBCodeParser','_options'); + $o = $options['open']; + $c = $options['close']; + + $urlServ = $matches[1]; + $path = $matches[5]; + + $off = strpos($urlServ, ':'); + + if ($off === false) { + //Default to http + $urlServ = $this->_defaultScheme.'://'.$urlServ; + $off = strpos($urlServ, ':'); + } + + //Add trailing slash if missing (to create a valid URL) + if (!$path) { + $path = '/'; + } + + $protocol = substr($urlServ, 0, $off); + + if (in_array($protocol, $this->_allowedSchemes)) { + //If protocol is in the approved list than allow it + return $o.'url='.$urlServ.$path.$c.$matches[6].$o.'/url'.$c; + } + + //Else remove url tag + return $matches[6]; + } +} +?> \ No newline at end of file diff --git a/pear/HTML/BBCodeParser/Filter/Lists.php b/pear/HTML/BBCodeParser/Filter/Lists.php new file mode 100644 index 000000000..9a1994551 --- /dev/null +++ b/pear/HTML/BBCodeParser/Filter/Lists.php @@ -0,0 +1,108 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Lists.php,v 1.5 2007/07/02 16:54:25 cweiske Exp $ +// + + +/** +* @package HTML_BBCodeParser +* @author Stijn de Reede +*/ + +require_once 'HTML/BBCodeParser/Filter.php'; + +/** + * + */ +class HTML_BBCodeParser_Filter_Lists extends HTML_BBCodeParser_Filter +{ + + /** + * An array of tags parsed by the engine + * + * @access private + * @var array + */ + var $_definedTags = array( 'list' => array( 'htmlopen' => 'ol', + 'htmlclose' => 'ol', + 'allowed' => 'all', + 'child' => 'none^li', + 'attributes'=> array('list' => 'style=%2$slist-style-type:%1$s;%2$s') + ), + 'ulist' => array( 'htmlopen' => 'ul', + 'htmlclose' => 'ul', + 'allowed' => 'all', + 'child' => 'none^li', + 'attributes'=> array('list' => 'style=%2$slist-style-type:%1$s;%2$s') + ), + 'li' => array( 'htmlopen' => 'li', + 'htmlclose' => 'li', + 'allowed' => 'all', + 'parent' => 'none^ulist,list', + 'attributes'=> array() + ) + ); + + + /** + * Executes statements before the actual array building starts + * + * This method should be overwritten in a filter if you want to do + * something before the parsing process starts. This can be useful to + * allow certain short alternative tags which then can be converted into + * proper tags with preg_replace() calls. + * The main class walks through all the filters and and calls this + * method if it exists. The filters should modify their private $_text + * variable. + * + * @return none + * @access private + * @see $_text + * @author Stijn de Reede , Seth Price + */ + function _preparse() + { + $options = PEAR::getStaticProperty('HTML_BBCodeParser','_options'); + $o = $options['open']; + $c = $options['close']; + $oe = $options['open_esc']; + $ce = $options['close_esc']; + + $pattern = array( "!".$oe."\*".$ce."!", + "!".$oe."(u?)list=(?-i:A)(\s*[^".$ce."]*)".$ce."!i", + "!".$oe."(u?)list=(?-i:a)(\s*[^".$ce."]*)".$ce."!i", + "!".$oe."(u?)list=(?-i:I)(\s*[^".$ce."]*)".$ce."!i", + "!".$oe."(u?)list=(?-i:i)(\s*[^".$ce."]*)".$ce."!i", + "!".$oe."(u?)list=(?-i:1)(\s*[^".$ce."]*)".$ce."!i", + "!".$oe."(u?)list([^".$ce."]*)".$ce."!i"); + + $replace = array( $o."li".$c, + $o."\$1list=upper-alpha\$2".$c, + $o."\$1list=lower-alpha\$2".$c, + $o."\$1list=upper-roman\$2".$c, + $o."\$1list=lower-roman\$2".$c, + $o."\$1list=decimal\$2".$c, + $o."\$1list\$2".$c ); + + $this->_preparsed = preg_replace($pattern, $replace, $this->_text); + } +} + + +?> \ No newline at end of file diff --git a/pear/HTML/BBCodeParser/example/BBCodeParser.ini b/pear/HTML/BBCodeParser/example/BBCodeParser.ini new file mode 100644 index 000000000..01cbca6aa --- /dev/null +++ b/pear/HTML/BBCodeParser/example/BBCodeParser.ini @@ -0,0 +1,23 @@ +[HTML_BBCodeParser] + +; possible values: single|double +; use single or double quotes for attributes +quotestyle = single + +; possible values: all|nothing|strings +; quote all attribute values, none, or only the strings +quotewhat = all + +; the opening tag character +open = [ + +; the closing tag character +close = ] + +; possible values: true|false +; use xml style closing tags for single html tags ( or ) +xmlclose = true + +; possible values: a comma seperated list of filters +; comma seperated list of filters to use +filters = Basic,Extended,Links,Images,Lists,Email diff --git a/pear/HTML/BBCodeParser/example/parser.php b/pear/HTML/BBCodeParser/example/parser.php new file mode 100644 index 000000000..46e4ddf35 --- /dev/null +++ b/pear/HTML/BBCodeParser/example/parser.php @@ -0,0 +1,118 @@ +setText(@$_GET['string']); +$parser->parse(); +$parsed = $parser->getParsed(); + +?> + + + +HTML_BBCodeParser (by Stijn de Reede) + + +
+ + + + + + +
+input:
+
+
+ouput:
+
+
+
+
+ +
+possible codes: +
+[b]bold[/b]
+[i]italic[/i]
+[u]underline[/u]
+[s]strike[/s]
+[sub]subscript[/sub]
+[sup]superscript[/sup]
+
+[color=blue]blue text[/color]
+[size=18]the size of this text is 18pt[/size]
+[font=arial]different font type[/font]
+[align=right]yes, you're right, this isn't on the left[/align]
+he said: [quote=http://www.server.org/quote.html]i'm tony montana[/quote]
+[code]x + y = 6;[/code]
+
+http://www.server.org
+[url]http://www.server.org[/url]
+[url=http://www.server.org]server[/url]
+[url=http://www.server.org t=new]server[/url]
+
+guest@anonymous.org
+[email]guest@anonymous.org[/email]
+[email=guest@anonymous.org]mail me[/email]
+
+[img]http://www.server.org/image.jpg[/img]
+[img w=100 h=200]http://www.server.org/image.jpg[/img]
+
+[ulist]
+[*]unordered item 1
+[*]unordered item 2
+[/ulist]
+[list]
+[*]unordered item 1
+[*]unordered item 2
+[/list]
+
+[list=1]
+[*]ordered item 1
+[*]ordered item 2
+[/list]
+[list=i]
+[*]ordered item 1 type i
+[li=4]ordered item 4 type i[/li]
+[/list]
+[list=I]
+[*]ordered item 1 type I
+[/list]
+[list=a s=5]
+[li]ordered item 5 type a[/li]
+[*]ordered item 6 type a
+[/list]
+[list=A]
+[li]ordered item 1 type A[/li]
+[li=12]ordered item 12 type A[/li]
+[/list]
+
+[list=A s=3]
+[li]ordered item 1, nested list:
+    [list=I]
+    [li]nested item 1[/li]
+    [li]nested item 2[/li]
+    [/list][/li]
+[li]ordered item 2[/li]
+[/list]
+
+
+
+ diff --git a/pear/HTML/tests/AllTests.php b/pear/HTML/tests/AllTests.php new file mode 100644 index 000000000..bea283420 --- /dev/null +++ b/pear/HTML/tests/AllTests.php @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/pear/HTML/tests/HTML_BBCodeParserTest.php b/pear/HTML/tests/HTML_BBCodeParserTest.php new file mode 100644 index 000000000..50ea5ec71 --- /dev/null +++ b/pear/HTML/tests/HTML_BBCodeParserTest.php @@ -0,0 +1,244 @@ + '')); + $bbc->addFilter('Basic'); + $this->basicBBCode($bbc, 'qparse'); + $bbc->removeFilter('Basic'); + $this->assertEquals('[b]txt[/b]', $bbc->qparse('[b]txt[/b]'), 'Basic filters have been removed.'); + $bbc->addFilters('Basic,Email'); + $this->basicBBCode($bbc, 'qparse'); + $this->emailBBCode($bbc, 'qparse'); + } + + function testQparse() + { + $bbc = new HTML_BBCodeParser(array('filters' => 'Basic,Email,Extended,Images,Links,Lists')); + $this->basicBBCode($bbc, 'qparse'); + $this->listBBCode($bbc, 'qparse'); + $this->linkBBCode($bbc, 'qparse'); + $this->extBBCode($bbc, 'qparse'); + $this->imgBBCode($bbc, 'qparse'); + $this->emailBBCode($bbc, 'qparse'); + } + + function emailBBCode($bbc, $funcNam) + { + $this->assertEquals('guest@anonymous.org', $bbc->$funcNam('guest@anonymous.org')); + $this->assertEquals('mail me', $bbc->$funcNam('[email=guest@anonymous.org]mail me[/email]')); + $this->assertEquals('guest@anonymous.org', $bbc->$funcNam('[email]guest@anonymous.org[/email]')); + } + + function imgBBCode($bbc, $funcNam) + { + $this->assertEquals('Enthalpy Wheel', $bbc->$funcNam('[img w=100 h=99 alt=Enthalpy Wheel]/images/Enthalpy Wheel.png[/img]')); + $this->assertEquals('', $bbc->$funcNam('[img]img.jpg[/img]')); + $this->assertEquals('', $bbc->$funcNam('[img w=100 h=200]http://www.server.org/image.jpg[/img]')); + } + + function basicBBCode($bbc, $funcNam) + { + $this->assertEquals('txt', $bbc->$funcNam('[b]txt[/b]')); + $this->assertEquals('txt', $bbc->$funcNam('[b]txt')); + $this->assertEquals('txt', $bbc->$funcNam('[i]txt[/i]')); + $this->assertEquals('txt', $bbc->$funcNam('[i]txt[/I]')); + $this->assertEquals('txt', $bbc->$funcNam('[I]txt[/i]')); + $this->assertEquals('txt', $bbc->$funcNam('[I]txt[/I]')); + $this->assertEquals('txt', $bbc->$funcNam('[s]txt[/s]')); + $this->assertEquals('txt', $bbc->$funcNam('[u]txt[/u]')); + $this->assertEquals('txt', $bbc->$funcNam('[sub]txt[/sub]')); + $this->assertEquals('txt', $bbc->$funcNam('[sup]txt[/sup]')); + $this->assertEquals('txt', $bbc->$funcNam('[sup][sub]txt[/sup][/sub]')); + $this->assertEquals('txt', $bbc->$funcNam('[i][b]txt[/i][/b]')); + } + + function listBBCode($bbc, $funcNam) + { + $this->assertEquals('
  • txt
', $bbc->$funcNam('[*]txt')); + $this->assertEquals("
  • txt\n
", $bbc->$funcNam("[ulist][*]txt\n[/ulist]")); + $this->assertEquals('
  • txt
', $bbc->$funcNam('[ulist]txt[/ulist]')); + $this->assertEquals('
      • txt
', $bbc->$funcNam('[ulist][ulist][ulist]txt')); + $this->assertEquals('
  • [xxx]txt[/xxx]
', $bbc->$funcNam('[ulist][xxx]txt[/xxx][/ulist]')); + $this->assertEquals('
  • txt
', $bbc->$funcNam('[ulist][li]txt[/li][/ulist]')); + $this->assertEquals('
  • txt
  • txt
', $bbc->$funcNam('[ulist][li]txt[li]txt[/ulist]')); + $this->assertEquals('
  • txt
', $bbc->$funcNam('[ulist][*]txt[/ulist]')); + $this->assertEquals('
    1. txt
', $bbc->$funcNam('[ulist][*][list][*]txt[/ulist]')); + $this->assertEquals('
  1. txt
', $bbc->$funcNam('[list][li]txt[/li][/list]')); + $this->assertEquals('
    1. txt
', $bbc->$funcNam('[li][list][li]txt[/li][/list]')); + $this->assertEquals('
  • txt
    • txt
', $bbc->$funcNam('[*]txt[ulist]txt[/ulist]')); + $this->assertEquals('
        • txt
', $bbc->$funcNam('[li][ulist][ulist][ulist]txt')); + $this->assertEquals( + '
  1. ordered item 1, nested list:
    1. nested item 1
    2. nested item 2
  2. ordered item 2
', + $bbc->$funcNam('[list=A s=3][li]ordered item 1, nested list:[list=I][li]nested item 1[/li][li]nested item 2[/li][/list][/li][li]ordered item 2[/li][/list]')); + $this->assertEquals( + '
  1. ordered item 1 type A
  2. ordered item 12 type A
', + $bbc->$funcNam('[list=A][li]ordered item 1 type A[/li][li=12]ordered item 12 type A[/li][/list]')); + $this->assertEquals( + '
  1. ordered item 5 type a
  2. ordered item 6 type a
', + $bbc->$funcNam('[list=a s=5][li]ordered item 5 type a[/li][*]ordered item 6 type a[/list]')); + $this->assertEquals( + '
  1. ordered item 1 type I
', + $bbc->$funcNam('[list=I][*]ordered item 1 type I[/list]')); + $this->assertEquals( + '
  1. ordered item 1 type i
  2. ordered item 4 type i
', + $bbc->$funcNam('[list=i][*]ordered item 1 type i[li=4]ordered item 4 type i[/li][/list]')); + $this->assertEquals( + '
  1. ordered item 1
  2. ordered item 2
', + $bbc->$funcNam('[list=1][*]ordered item 1[*]ordered item 2[/list]')); + //Bug #512: [list] in a [list] breaks the first [list] + $this->assertEquals( + '
  1. Subject 1
    1. First
    2. Second
  2. Subject 2
', + $bbc->$funcNam('[list][*] Subject 1[list][*] First[*] Second[/list][*] Subject 2[/list]') + ); + //Bug #1201: [list] output adding extra
  • + $this->assertEquals( + '
    1. txt
    ', + $bbc->$funcNam('[list][*]txt[/list]') + ); + //Bug#6335 Empty item displayed + $this->assertEquals( + '
    1. Item one
    2. Item two
    3. Item three
    ', + $bbc->$funcNam('[list=1][*] Item one[*] Item two[*] Item three[/list]')); + } + + function linkBBCode($bbc, $funcNam) + { + $this->assertEquals( + 'http://www.test.com/', + $bbc->$funcNam('http://www.test.com/')); + $this->assertEquals( + 'www.test.com', + $bbc->$funcNam('[url]www.test.com[/url]')); + $this->assertEquals( + 'http://www.test.com/testurl', + $bbc->$funcNam('[url]http://www.test.com/testurl[/url]')); + $this->assertEquals( + 'testurl', + $bbc->$funcNam('[url=www.test.com/]testurl[/url]')); + $this->assertEquals( + 'server', + $bbc->$funcNam('[url=http://www.server.org t=new]server[/url]')); + $this->assertEquals( + 'txt www.test.com txt', + $bbc->$funcNam('txt www.test.com txt')); + $this->assertEquals( + 'txt (www.test.com) txt', + $bbc->$funcNam('txt (www.test.com) txt')); + $this->assertEquals( + 'txt www.test.com/test.php?a=1,2, txt', + $bbc->$funcNam('txt www.test.com/test.php?a=1,2, txt')); + $this->assertEquals( + 'txt www.test.com, txt', + $bbc->$funcNam('txt www.test.com, txt')); + $this->assertEquals( + 'txt http://www.test.com: txt', + $bbc->$funcNam('txt http://www.test.com: txt')); + $this->assertEquals( + 'txt www.test.com; txt', + $bbc->$funcNam('txt www.test.com; txt')); + //Bug #1755: tags around an url -> mess + $this->assertEquals( + 'txt www.test.com txt', + $bbc->$funcNam('txt [i]www.test.com[/i] txt')); + //Bug #1512: URL Tags Allow Javascript injection + $this->assertEquals( + 'Click here', + $bbc->$funcNam('[url=javascript:location.replace("bad_link");]Click here[/url]')); + $this->assertEquals( + 'linked text', + $bbc->$funcNam('[url=http://domain.com/index.php?i=1&j=2]linked text[/URL]')); + $this->assertEquals( + 'linked text', + $bbc->$funcNam('[url=http://domain.com/index.php?i=1&j=2]linked text[/URL]')); + //Bug #5609: BBCodeParser allows XSS + $this->assertEquals( + 'Alert box with "CouCou"', + $bbc->$funcNam('[url=javascript://%0ASh=alert(%22CouCou%22);window.close();]Alert box with "CouCou"[/url]') + ); + /* + //Request #4936: Nested URLs in quotes not handled + $this->assertEquals( + 'Quoted text', //?!?!? + $bbc->$funcNam('[quote="[url=http://somewhere.com]URL-Title[/url]"]Quoted text[/quote]') + ); + */ + } + + function extBBCode($bbc, $funcNam) + { + $this->assertEquals('

    txt

    ', $bbc->$funcNam('[h2]txt[/h2]')); + $this->assertEquals('blue text', $bbc->$funcNam('[color=blue]blue text[/color]')); + $this->assertEquals('the size of this text is 18pt', $bbc->$funcNam('[size=18]the size of this text is 18pt[/size]')); + $this->assertEquals('different font type', $bbc->$funcNam('[font=arial]different font type[/font]')); + $this->assertEquals('
    yes, you\'re right, this isn\'t on the left
    ', $bbc->$funcNam('[align=right]yes, you\'re right, this isn\'t on the left[/align]')); + $this->assertEquals('he said: i\'m tony montana', $bbc->$funcNam('he said: [quote=http://www.server.org/quote.html]i\'m tony montana[/quote]')); + $this->assertEquals('x + y = 6;', $bbc->$funcNam('[code]x + y = 6;[/code]')); + //Bug #1258: Extra tags rendered with faulty BBCode + $this->assertEquals( + 'my name NeverMind!', + $bbc->$funcNam('[font=Verdana][color=red]my name NeverMind![/font][/color]') + ); + //Bug #1979: Whitespaces in attribute are breaking it + $this->assertEquals( + 'txt', + $bbc->$funcNam('[font=Comic Sans MS]txt[/font]') + ); + //Bug #4844: Arbitrary HTML injection + $this->assertEquals( + '
    ', + $bbc->$funcNam('[align=foo">][/align]') + ); + } + + + + /** + * An empty
  • had been included for the first space + */ + function testBug11400() + { + $bbc = new HTML_BBCodeParser(array('filters' => '')); + $bbc->addFilter('Lists'); + + //this works + $this->assertEquals('
    • one
    • two
    ', + $bbc->qparse("[ulist][*]one[*]two[/ulist]") + ); + //this not + $this->assertEquals('
    • one
    • two
    ', + $bbc->qparse("[ulist] [*]one[*]two[/ulist]") + ); + //this not + $this->assertEquals('
    1. one
    2. two
    ', + $bbc->qparse("[list] [*]one[*]two[/list]") + ); + } + + + + /** + * img tags didn't like = in url + */ + function testBug11370() + { + $bbc = new HTML_BBCodeParser(array('filters' => '')); + $bbc->addFilter('Images'); + + $this->assertEquals('', + $bbc->qparse("[img]admin.php?fs=image[/img]") + ); + } +} + +//Run tests if run from the command line +if (realpath($_SERVER['PHP_SELF']) == __FILE__){ + $suite = new PHPUnit_TestSuite('BBCodeParser_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); +} +?> \ No newline at end of file diff --git a/pear/PEAR.php b/pear/PEAR.php new file mode 100644 index 000000000..fc879a0a7 --- /dev/null +++ b/pear/PEAR.php @@ -0,0 +1,1108 @@ + + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: PEAR.php,v 1.101 2006/04/25 02:41:03 cellog Exp $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +// instant backwards compatibility +if (!defined('PATH_SEPARATOR')) { + if (OS_WINDOWS) { + define('PATH_SEPARATOR', ';'); + } else { + define('PATH_SEPARATOR', ':'); + } +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.6.1 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + // {{{ properties + + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + // }}} + + // {{{ constructor + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + if ($error_class !== null) { + $this->_error_class = $error_class; + } + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + // }}} + // {{{ destructor + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + // }}} + // {{{ getStaticProperty() + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + return $properties[$class][$var]; + } + + // }}} + // {{{ registerShutdownFunc() + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + // }}} + // {{{ isError() + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + function isError($data, $code = null) + { + if (is_a($data, 'PEAR_Error')) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } else { + return $data->getCode() == $code; + } + } + return false; + } + + // }}} + // {{{ setErrorHandling() + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + // }}} + // {{{ expectError() + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return sizeof($this->_expected_errors); + } + + // }}} + // {{{ popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + // }}} + // {{{ _checkDelExpect() + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + + foreach ($this->_expected_errors AS $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + return $deleted; + } + + // }}} + // {{{ delExpect() + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; + // we walk through it trying to unset all + // values + foreach($error_code as $key => $error) { + if ($this->_checkDelExpect($error)) { + $deleted = true; + } else { + $deleted = false; + } + } + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } else { + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + } else { + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + } + + // }}} + // {{{ raiseError() + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + function &raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp))) { + $mode = PEAR_ERROR_RETURN; + } + } + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + if ($skipmsg) { + $a = &new $ec($code, $mode, $options, $userinfo); + return $a; + } else { + $a = &new $ec($message, $code, $mode, $options, $userinfo); + return $a; + } + } + + // }}} + // {{{ throwError() + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param string $message + * + */ + function &throwError($message = null, + $code = null, + $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } else { + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + } + + // }}} + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + // {{{ pushErrorHandling() + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + // }}} + // {{{ popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + // }}} + // {{{ loadExtension() + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + function loadExtension($ext) + { + if (!extension_loaded($ext)) { + // if either returns true dl() will produce a FATAL error, stop that + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + return false; + } + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } + return true; + } + + // }}} +} + +// {{{ _PEAR_call_destructors() + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (PEAR::getStaticProperty('PEAR', 'destructlifo')) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +// }}} +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Gregory Beaver + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.6.1 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + // {{{ properties + + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + // }}} + // {{{ constructor + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + if (!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + $this->level = $options; + $this->callback = null; + } + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + printf($format, $this->getMessage()); + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + } + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + // }}} + // {{{ getMode() + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() { + return $this->mode; + } + + // }}} + // {{{ getCallback() + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() { + return $this->callback; + } + + // }}} + // {{{ getMessage() + + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + + // }}} + // {{{ getCode() + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + // }}} + // {{{ getType() + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + // }}} + // {{{ getUserInfo() + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + // }}} + // {{{ getDebugInfo() + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + // }}} + // {{{ getBacktrace() + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + // }}} + // {{{ addUserInfo() + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + // }}} + // {{{ toString() + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } + + // }}} +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?>