1889 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1889 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
| <?php
 | |
| /**
 | |
|  * BB code string parsing class
 | |
|  *
 | |
|  * Preview Version (20060220)
 | |
|  *
 | |
|  * @author Christian Seiler <spam@christian-seiler.de>
 | |
|  * @copyright Christian Seiler 2005
 | |
|  * @package stringparser
 | |
|  *
 | |
|  *  This program is free software; you can redistribute it and/or modify
 | |
|  *  it under the terms of either:
 | |
|  *
 | |
|  *  a) the GNU General Public License as published by the Free
 | |
|  *  Software Foundation; either version 1, or (at your option) any
 | |
|  *  later version, or
 | |
|  *
 | |
|  *  b) the Artistic License as published by Larry Wall, either version 2.0,
 | |
|  *     or (at your option) any later version.
 | |
|  *
 | |
|  *  This program is distributed in the hope that it will be useful,
 | |
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See either
 | |
|  *  the GNU General Public License or the Artistic License for more details.
 | |
|  *
 | |
|  *  You should have received a copy of the Artistic License with this Kit,
 | |
|  *  in the file named "Artistic.clarified".  If not, I'll be glad to provide
 | |
|  *  one.
 | |
|  *
 | |
|  *  You should also have received a copy of the GNU General Public License
 | |
|  *  along with this program in the file named "COPYING"; if not, write to
 | |
|  *  the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 | |
|  *  MA 02111-1307, USA.
 | |
|  */
 | |
|  
 | |
| require_once dirname(__FILE__).'/stringparser.class.php';
 | |
| 
 | |
| define ('BBCODE_CLOSETAG_FORBIDDEN', -1);
 | |
| define ('BBCODE_CLOSETAG_OPTIONAL', 0);
 | |
| define ('BBCODE_CLOSETAG_IMPLICIT', 1);
 | |
| define ('BBCODE_CLOSETAG_IMPLICIT_ON_CLOSE_ONLY', 2);
 | |
| define ('BBCODE_CLOSETAG_MUSTEXIST', 3);
 | |
| 
 | |
| define ('BBCODE_NEWLINE_PARSE', 0);
 | |
| define ('BBCODE_NEWLINE_IGNORE', 1);
 | |
| define ('BBCODE_NEWLINE_DROP', 2);
 | |
| 
 | |
| define ('BBCODE_PARAGRAPH_ALLOW_BREAKUP', 0);
 | |
| define ('BBCODE_PARAGRAPH_ALLOW_INSIDE', 1);
 | |
| define ('BBCODE_PARAGRAPH_BLOCK_ELEMENT', 2);
 | |
| 
 | |
| /**
 | |
|  * BB code string parser class
 | |
|  *
 | |
|  * @package stringparser
 | |
|  */
 | |
| class StringParser_BBCode extends StringParser {
 | |
| 	/**
 | |
| 	 * String parser mode
 | |
| 	 *
 | |
| 	 * The BBCode string parser works in search mode
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var int
 | |
| 	 * @see STRINGPARSER_MODE_SEARCH, STRINGPARSER_MODE_LOOP
 | |
| 	 */
 | |
| 	var $_parserMode = STRINGPARSER_MODE_SEARCH;
 | |
| 	
 | |
| 	/**
 | |
| 	 * Defined BB Codes
 | |
| 	 *
 | |
| 	 * The registered BB codes
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var array
 | |
| 	 */
 | |
| 	var $_codes = array ();
 | |
| 	
 | |
| 	/**
 | |
| 	 * Registered parsers
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var array
 | |
| 	 */
 | |
| 	var $_parsers = array ();
 | |
| 	
 | |
| 	/**
 | |
| 	 * Defined maximum occurrences
 | |
| 	 *
 | |
| 	 * @access protected
 | |
| 	 * @var array
 | |
| 	 */
 | |
| 	var $_maxOccurrences = array ();
 | |
| 	
 | |
| 	/**
 | |
| 	 * Root content type
 | |
| 	 *
 | |
| 	 * @access protected
 | |
| 	 * @var string
 | |
| 	 */
 | |
| 	var $_rootContentType = 'block';
 | |
| 	
 | |
| 	/**
 | |
| 	 * Do not output but return the tree
 | |
| 	 *
 | |
| 	 * @access protected
 | |
| 	 * @var bool
 | |
| 	 */
 | |
| 	var $_noOutput = false;
 | |
| 	
 | |
| 	/**
 | |
| 	 * Global setting: case sensitive
 | |
| 	 *
 | |
| 	 * @access protected
 | |
| 	 * @var bool
 | |
| 	 */
 | |
| 	var $_caseSensitive = true;
 | |
| 	
 | |
| 	/**
 | |
| 	 * Root paragraph handling enabled
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var bool
 | |
| 	 */
 | |
| 	var $_rootParagraphHandling = false;
 | |
| 	
 | |
| 	/**
 | |
| 	 * Paragraph handling parameters
 | |
| 	 * @access private
 | |
| 	 * @var array
 | |
| 	 */
 | |
| 	var $_paragraphHandling = array (
 | |
| 		'detect_string' => "\n\n",
 | |
| 		'start_tag' => '<p>',
 | |
| 		'end_tag' => "</p>\n"
 | |
| 	);
 | |
| 	
 | |
| 	/**
 | |
| 	 * Allow mixed attribute types (e.g. [code=bla attr=blub])
 | |
| 	 * @access private
 | |
| 	 * @var bool
 | |
| 	 */
 | |
| 	var $_mixedAttributeTypes = false;
 | |
| 	
 | |
| 	/**
 | |
| 	 * Add a code
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $name The name of the code
 | |
| 	 * @param string $callback_type See documentation
 | |
| 	 * @param string $callback_func The callback function to call
 | |
| 	 * @param array $callback_params The callback parameters
 | |
| 	 * @param string $content_type See documentation
 | |
| 	 * @param array $allowed_within See documentation
 | |
| 	 * @param array $not_allowed_within See documentation
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function addCode ($name, $callback_type, $callback_func, $callback_params, $content_type, $allowed_within, $not_allowed_within) {
 | |
| 		if (isset ($this->_codes[$name])) {
 | |
| 			return false; // already exists
 | |
| 		}
 | |
| 		if (!preg_match ('/^[a-zA-Z0-9*_!+-]+$/', $name, $code)) {
 | |
| 			return false; // invalid
 | |
| 		}
 | |
| 		$this->_codes[$name] = array (
 | |
| 			'name' => $name,
 | |
| 			'callback_type' => $callback_type,
 | |
| 			'callback_func' => $callback_func,
 | |
| 			'callback_params' => $callback_params,
 | |
| 			'content_type' => $content_type,
 | |
| 			'allowed_within' => $allowed_within,
 | |
| 			'not_allowed_within' => $not_allowed_within,
 | |
| 			'flags' => array ()
 | |
| 		);
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Remove a code
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param $name The code to remove
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function removeCode ($name) {
 | |
| 		if (isset ($this->_codes[$name])) {
 | |
| 			unset ($this->_codes[$name]);
 | |
| 			return true;
 | |
| 		}
 | |
| 		return false;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Remove all codes
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 */
 | |
| 	function removeAllCodes () {
 | |
| 		$this->_codes = array ();
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set a code flag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $name The name of the code
 | |
| 	 * @param string $flag The name of the flag to set
 | |
| 	 * @param mixed $value The value of the flag to set
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function setCodeFlag ($name, $flag, $value) {
 | |
| 		if (!isset ($this->_codes[$name])) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		$this->_codes[$name]['flags'][$flag] = $value;
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set occurrence type
 | |
| 	 *
 | |
| 	 * Example:
 | |
| 	 *   $bbcode->setOccurrenceType ('url', 'link');
 | |
| 	 *   $bbcode->setMaxOccurrences ('link', 4);
 | |
| 	 * Would create the situation where a link may only occur four
 | |
| 	 * times in the hole text.
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $code The name of the code
 | |
| 	 * @param string $type The name of the occurrence type to set
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function setOccurrenceType ($code, $type) {
 | |
| 		return $this->setCodeFlag ($code, 'occurrence_type', $type);
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set maximum number of occurrences
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $type The name of the occurrence type
 | |
| 	 * @param int $count The maximum number of occurrences
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function setMaxOccurrences ($type, $count) {
 | |
| 		settype ($count, 'integer');
 | |
| 		if ($count < 0) { // sorry, does not make any sense
 | |
| 			return false;
 | |
| 		}
 | |
| 		$this->_maxOccurrences[$type] = $count;
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Add a parser
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $type The content type for which the parser is to add
 | |
| 	 * @param mixed $parser The function to call
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function addParser ($type, $parser) {
 | |
| 		if (is_array ($type)) {
 | |
| 			foreach ($type as $t) {
 | |
| 				$this->addParser ($t, $parser);
 | |
| 			}
 | |
| 			return true;
 | |
| 		}
 | |
| 		if (!isset ($this->_parsers[$type])) {
 | |
| 			$this->_parsers[$type] = array ();
 | |
| 		}
 | |
| 		$this->_parsers[$type][] = $parser;
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set root content type
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $content_type The new root content type
 | |
| 	 */
 | |
| 	function setRootContentType ($content_type) {
 | |
| 		$this->_rootContentType = $content_type;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set paragraph handling on root element
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param bool $enabled The new status of paragraph handling on root element
 | |
| 	 */
 | |
| 	function setRootParagraphHandling ($enabled) {
 | |
| 		$this->_rootParagraphHandling = (bool)$enabled;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set paragraph handling parameters
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $detect_string The string to detect
 | |
| 	 * @param string $start_tag The replacement for the start tag (e.g. <p>)
 | |
| 	 * @param string $end_tag The replacement for the start tag (e.g. </p>)
 | |
| 	 */
 | |
| 	function setParagraphHandlingParameters ($detect_string, $start_tag, $end_tag) {
 | |
| 		$this->_paragraphHandling = array (
 | |
| 			'detect_string' => $detect_string,
 | |
| 			'start_tag' => $start_tag,
 | |
| 			'end_tag' => $end_tag
 | |
| 		);
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set global case sensitive flag
 | |
| 	 *
 | |
| 	 * If this is set to true, the class normally is case sensitive, but
 | |
| 	 * the case_sensitive code flag may override this for a single code.
 | |
| 	 *
 | |
| 	 * If this is set to false, all codes are case insensitive.
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param bool $caseSensitive
 | |
| 	 */
 | |
| 	function setGlobalCaseSensitive ($caseSensitive) {
 | |
| 		$this->_caseSensitive = (bool)$caseSensitive;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get global case sensitive flag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function globalCaseSensitive () {
 | |
| 		return $this->_caseSensitive;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set mixed attribute types flag
 | |
| 	 *
 | |
| 	 * If set, [code=val1 attr=val2] will cause 2 attributes to be parsed:
 | |
| 	 * 'default' will have value 'val1', 'attr' will have value 'val2'.
 | |
| 	 * If not set, only one attribute 'default' will have the value
 | |
| 	 * 'val1 attr=val2' (the default and original behaviour)
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param bool $mixedAttributeTypes
 | |
| 	 */
 | |
| 	function setMixedAttributeTypes ($mixedAttributeTypes) {
 | |
| 		$this->_mixedAttributeTypes = (bool)$mixedAttributeTypes;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get mixed attribute types flag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function mixedAttributeTypes () {
 | |
| 		return $this->_mixedAttributeTypes;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get a code flag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $name The name of the code
 | |
| 	 * @param string $flag The name of the flag to get
 | |
| 	 * @param string $type The type of the return value
 | |
| 	 * @param mixed $default The default return value
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function getCodeFlag ($name, $flag, $type = 'mixed', $default = null) {
 | |
| 		if (!isset ($this->_codes[$name])) {
 | |
| 			return $default;
 | |
| 		}
 | |
| 		if (!array_key_exists ($flag, $this->_codes[$name]['flags'])) {
 | |
| 			return $default;
 | |
| 		}
 | |
| 		$return = $this->_codes[$name]['flags'][$flag];
 | |
| 		if ($type != 'mixed') {
 | |
| 			settype ($return, $type);
 | |
| 		}
 | |
| 		return $return;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set a specific status
 | |
| 	 * @access private
 | |
| 	 */
 | |
| 	function _setStatus ($status) {
 | |
| 		switch ($status) {
 | |
| 			case 0:
 | |
| 				$this->_charactersSearch = array ('[/', '[');
 | |
| 				$this->_status = $status;
 | |
| 				break;
 | |
| 			case 1:
 | |
| 				$this->_charactersSearch = array (']', ' = "', '="', ' = \'', '=\'', ' = ', '=', ': ', ':', ' ');
 | |
| 				$this->_status = $status;
 | |
| 				break;
 | |
| 			case 2:
 | |
| 				$this->_charactersSearch = array (']');
 | |
| 				$this->_status = $status;
 | |
| 				$this->_savedName = '';
 | |
| 				break;
 | |
| 			case 3:
 | |
| 				if ($this->_quoting !== null) {
 | |
| 					if ($this->_mixedAttributeTypes) {
 | |
| 						$this->_charactersSearch = array ('\\\\', '\\'.$this->_quoting, $this->_quoting.' ', $this->_quoting.']', $this->_quoting);
 | |
| 					} else {
 | |
| 						$this->_charactersSearch = array ('\\\\', '\\'.$this->_quoting, $this->_quoting.']', $this->_quoting);
 | |
| 					}
 | |
| 					$this->_status = $status;
 | |
| 					break;
 | |
| 				}
 | |
| 				if ($this->_mixedAttributeTypes) {
 | |
| 					$this->_charactersSearch = array (' ', ']');
 | |
| 				} else {
 | |
| 					$this->_charactersSearch = array (']');
 | |
| 				}
 | |
| 				$this->_status = $status;
 | |
| 				break;
 | |
| 			case 4:
 | |
| 				$this->_charactersSearch = array (' ', ']', '="', '=\'', '=');
 | |
| 				$this->_status = $status;
 | |
| 				$this->_savedName = '';
 | |
| 				$this->_savedValue = '';
 | |
| 				break;
 | |
| 			case 5:
 | |
| 				if ($this->_quoting !== null) {
 | |
| 					$this->_charactersSearch = array ('\\\\', '\\'.$this->_quoting, $this->_quoting.' ', $this->_quoting.']', $this->_quoting);
 | |
| 				} else {
 | |
| 					$this->_charactersSearch = array (' ', ']');
 | |
| 				}
 | |
| 				$this->_status = $status;
 | |
| 				$this->_savedValue = '';
 | |
| 				break;
 | |
| 			case 7:
 | |
| 				$this->_charactersSearch = array ('[/'.$this->_topNode ('name').']');
 | |
| 				if (!$this->_topNode ('getFlag', 'case_sensitive', 'boolean', true) || !$this->_caseSensitive) {
 | |
| 					$this->_charactersSearch[] = '[/';
 | |
| 				}
 | |
| 				$this->_status = $status;
 | |
| 				break;
 | |
| 			default:
 | |
| 				return false;
 | |
| 		}
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Abstract method Append text depending on current status
 | |
| 	 * @access private
 | |
| 	 * @param string $text The text to append
 | |
| 	 * @return bool On success, the function returns true, else false
 | |
| 	 */
 | |
| 	function _appendText ($text) {
 | |
| 		if (!strlen ($text)) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		switch ($this->_status) {
 | |
| 			case 0:
 | |
| 			case 7:
 | |
| 				return $this->_appendToLastTextChild ($text);
 | |
| 			case 1:
 | |
| 				return $this->_topNode ('appendToName', $text);
 | |
| 			case 2:
 | |
| 			case 4:
 | |
| 				$this->_savedName .= $text;
 | |
| 				return true;
 | |
| 			case 3:
 | |
| 				return $this->_topNode ('appendToAttribute', 'default', $text);
 | |
| 			case 5:
 | |
| 				$this->_savedValue .= $text;
 | |
| 				return true;
 | |
| 			default:
 | |
| 				return false;
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Restart parsing after current block
 | |
| 	 *
 | |
| 	 * To achieve this the current top stack object is removed from the
 | |
| 	 * tree. Then the current item
 | |
| 	 *
 | |
| 	 * @access protected
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _reparseAfterCurrentBlock () {
 | |
| 		if ($this->_status == 2) {
 | |
| 			// this status will *never* call _reparseAfterCurrentBlock itself
 | |
| 			// so this is called if the loop ends
 | |
| 			// therefore, just add the [/ to the text
 | |
| 			
 | |
| 			// _savedName should be empty but just in case
 | |
| 			$this->_cpos -= strlen ($this->_savedName);
 | |
| 			$this->_savedName = '';
 | |
| 			$this->_status = 0;
 | |
| 			$this->_appendText ('[/');
 | |
| 			return true;
 | |
| 		} else {
 | |
| 			return parent::_reparseAfterCurrentBlock ();
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Apply parsers
 | |
| 	 */
 | |
| 	function _applyParsers ($type, $text) {
 | |
| 		if (!isset ($this->_parsers[$type])) {
 | |
| 			return $text;
 | |
| 		}
 | |
| 		foreach ($this->_parsers[$type] as $parser) {
 | |
| 			if (is_callable ($parser)) {
 | |
| 				$ntext = call_user_func ($parser, $text);
 | |
| 				if (is_string ($ntext)) {
 | |
| 					$text = $ntext;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return $text;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Handle status
 | |
| 	 * @access private
 | |
| 	 * @param int $status The current status
 | |
| 	 * @param string $needle The needle that was found
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _handleStatus ($status, $needle) {
 | |
| 		switch ($status) {
 | |
| 			case 0: // NORMAL TEXT
 | |
| 				if ($needle != '[' && $needle != '[/') {
 | |
| 					$this->_appendText ($needle);
 | |
| 					return true;
 | |
| 				}
 | |
| 				if ($needle == '[') {
 | |
| 					$node =& new StringParser_BBCode_Node_Element ($this->_cpos);
 | |
| 					$res = $this->_pushNode ($node);
 | |
| 					if (!$res) {
 | |
| 						return false;
 | |
| 					}
 | |
| 					$this->_setStatus (1);
 | |
| 				} else if ($needle == '[/') {
 | |
| 					if (count ($this->_stack) <= 1) {
 | |
| 						$this->_appendText ($needle);
 | |
| 						return true;
 | |
| 					}
 | |
| 					$this->_setStatus (2);
 | |
| 				}
 | |
| 				break;
 | |
| 			case 1: // OPEN TAG
 | |
| 				if ($needle == ']') {
 | |
| 					return $this->_openElement (0);
 | |
| 				} else if (trim ($needle) == ':' || trim ($needle) == '=') {
 | |
| 					$this->_quoting = null;
 | |
| 					$this->_setStatus (3); // default value parser
 | |
| 					break;
 | |
| 				} else if (trim ($needle) == '="' || trim ($needle) == '= "' || trim ($needle) == '=\'' || trim ($needle) == '= \'') {
 | |
| 					$this->_quoting = substr (trim ($needle), -1);
 | |
| 					$this->_setStatus (3); // default value parser with quotation
 | |
| 					break;
 | |
| 				} else if ($needle == ' ') {
 | |
| 					$this->_setStatus (4); // attribute parser
 | |
| 					break;
 | |
| 				} else {
 | |
| 					$this->_appendText ($needle);
 | |
| 					return true;
 | |
| 				}
 | |
| 				break;
 | |
| 			case 2: // CLOSE TAG
 | |
| 				if ($needle != ']') {
 | |
| 					$this->_appendText ($needle);
 | |
| 					return true;
 | |
| 				}
 | |
| 				$closecount = 0;
 | |
| 				if (!$this->_isCloseable ($this->_savedName, $closecount)) {
 | |
| 					$this->_setStatus (0);
 | |
| 					$this->_appendText ('[/'.$this->_savedName.$needle);
 | |
| 					return true;
 | |
| 				}
 | |
| 				$this->_setStatus (0);
 | |
| 				for ($i = 0; $i < $closecount; $i++) {
 | |
| 					if ($i == $closecount - 1) {
 | |
| 						$this->_topNode ('setHadCloseTag');
 | |
| 					}
 | |
| 					if (!$this->_popNode ()) {
 | |
| 						return false;
 | |
| 					}
 | |
| 				}
 | |
| 				break;
 | |
| 			case 3: // DEFAULT ATTRIBUTE
 | |
| 				if ($this->_quoting !== null) {
 | |
| 					if ($needle == '\\\\') {
 | |
| 						$this->_appendText ('\\');
 | |
| 						return true;
 | |
| 					} else if ($needle == '\\'.$this->_quoting) {
 | |
| 						$this->_appendText ($this->_quoting);
 | |
| 						return true;
 | |
| 					} else if ($needle == $this->_quoting.' ') {
 | |
| 						$this->_setStatus (4);
 | |
| 						return true;
 | |
| 					} else if ($needle == $this->_quoting.']') {
 | |
| 						return $this->_openElement (2);
 | |
| 					} else if ($needle == $this->_quoting) {
 | |
| 						// can't be, only ']' and ' ' allowed after quoting char
 | |
| 						return $this->_reparseAfterCurrentBlock ();
 | |
| 					} else {
 | |
| 						$this->_appendText ($needle);
 | |
| 						return true;
 | |
| 					}
 | |
| 				} else {
 | |
| 					if ($needle == ' ') {
 | |
| 						$this->_setStatus (4);
 | |
| 						return true;
 | |
| 					} else if ($needle == ']') {
 | |
| 						return $this->_openElement (2);
 | |
| 					} else {
 | |
| 						$this->_appendText ($needle);
 | |
| 						return true;
 | |
| 					}
 | |
| 				}
 | |
| 				break;
 | |
| 			case 4: // ATTRIBUTE NAME
 | |
| 				if ($needle == ' ') {
 | |
| 					if (strlen ($this->_savedName)) {
 | |
| 						$this->_topNode ('setAttribute', $this->_savedName, true);
 | |
| 					}
 | |
| 					// just ignore and continue in same mode
 | |
| 					$this->_setStatus (4); // reset parameters
 | |
| 					return true;
 | |
| 				} else if ($needle == ']') {
 | |
| 					if (strlen ($this->_savedName)) {
 | |
| 						$this->_topNode ('setAttribute', $this->_savedName, true);
 | |
| 					}
 | |
| 					return $this->_openElement (2);
 | |
| 				} else if ($needle == '=') {
 | |
| 					$this->_quoting = null;
 | |
| 					$this->_setStatus (5);
 | |
| 					return true;
 | |
| 				} else if ($needle == '="') {
 | |
| 					$this->_quoting = '"';
 | |
| 					$this->_setStatus (5);
 | |
| 					return true;
 | |
| 				} else if ($needle == '=\'') {
 | |
| 					$this->_quoting = '\'';
 | |
| 					$this->_setStatus (5);
 | |
| 					return true;
 | |
| 				} else {
 | |
| 					$this->_appendText ($needle);
 | |
| 					return true;
 | |
| 				}
 | |
| 				break;
 | |
| 			case 5: // ATTRIBUTE VALUE
 | |
| 				if ($this->_quoting !== null) {
 | |
| 					if ($needle == '\\\\') {
 | |
| 						$this->_appendText ('\\');
 | |
| 						return true;
 | |
| 					} else if ($needle == '\\'.$this->_quoting) {
 | |
| 						$this->_appendText ($this->_quoting);
 | |
| 						return true;
 | |
| 					} else if ($needle == $this->_quoting.' ') {
 | |
| 						$this->_topNode ('setAttribute', $this->_savedName, $this->_savedValue);
 | |
| 						$this->_setStatus (4);
 | |
| 						return true;
 | |
| 					} else if ($needle == $this->_quoting.']') {
 | |
| 						$this->_topNode ('setAttribute', $this->_savedName, $this->_savedValue);
 | |
| 						return $this->_openElement (2);
 | |
| 					} else if ($needle == $this->_quoting) {
 | |
| 						// can't be, only ']' and ' ' allowed after quoting char
 | |
| 						return $this->_reparseAfterCurrentBlock ();
 | |
| 					} else {
 | |
| 						$this->_appendText ($needle);
 | |
| 						return true;
 | |
| 					}
 | |
| 				} else {
 | |
| 					if ($needle == ' ') {
 | |
| 						$this->_topNode ('setAttribute', $this->_savedName, $this->_savedValue);
 | |
| 						$this->_setStatus (4);
 | |
| 						return true;
 | |
| 					} else if ($needle == ']') {
 | |
| 						$this->_topNode ('setAttribute', $this->_savedName, $this->_savedValue);
 | |
| 						return $this->_openElement (2);
 | |
| 					} else {
 | |
| 						$this->_appendText ($needle);
 | |
| 						return true;
 | |
| 					}
 | |
| 				}
 | |
| 				break;
 | |
| 			case 7:
 | |
| 				if ($needle == '[/') {
 | |
| 					// this was case insensitive match
 | |
| 					if (strtolower (substr ($this->_text, $this->_cpos + strlen ($needle), strlen ($this->_topNode ('name')) + 1)) == strtolower ($this->_topNode ('name').']')) {
 | |
| 						// this matched
 | |
| 						$this->_cpos += strlen ($this->_topNode ('name')) + 1;
 | |
| 					} else {
 | |
| 						// it didn't match
 | |
| 						$this->_appendText ($needle);
 | |
| 						return true;
 | |
| 					}
 | |
| 				}
 | |
| 				$closecount = $this->_savedCloseCount;
 | |
| 				if (!$this->_topNode ('validate')) {
 | |
| 					return $this->_reparseAfterCurrentBlock ();
 | |
| 				}
 | |
| 				// do we have to close subnodes?
 | |
| 				if ($closecount) {
 | |
| 					// get top node
 | |
| 					$mynode =& $this->_stack[count ($this->_stack)-1];
 | |
| 					// close necessary nodes
 | |
| 					for ($i = 0; $i <= $closecount; $i++) {
 | |
| 						if (!$this->_popNode ()) {
 | |
| 							return false;
 | |
| 						}
 | |
| 					}
 | |
| 					if (!$this->_pushNode ($mynode)) {
 | |
| 						return false;
 | |
| 					}
 | |
| 				}
 | |
| 				$this->_setStatus (0);
 | |
| 				$this->_popNode ();
 | |
| 				return true;
 | |
| 			default: 
 | |
| 				return false;
 | |
| 		}
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Open the next element
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _openElement ($type = 0) {
 | |
| 		$name = $this->_topNode ('name');
 | |
| 		if (!isset ($this->_codes[$name])) {
 | |
| 			if (isset ($this->_codes[strtolower ($name)]) && (!$this->getCodeFlag (strtolower ($name), 'case_sensitive', 'boolean', true) || !$this->_caseSensitive)) {
 | |
| 				$name = strtolower ($name);
 | |
| 			} else {
 | |
| 				return $this->_reparseAfterCurrentBlock ();
 | |
| 			}
 | |
| 		}
 | |
| 		$occ_type = $this->getCodeFlag ($name, 'occurrence_type', 'string');
 | |
| 		if ($occ_type !== null && isset ($this->_maxOccurrences[$occ_type])) {
 | |
| 			$max_occs = $this->_maxOccurrences[$occ_type];
 | |
| 			$occs = $this->_root->getNodeCountByCriterium ('flag:occurrence_type', $occ_type);
 | |
| 			if ($occs >= $max_occs) {
 | |
| 				return $this->_reparseAfterCurrentBlock ();
 | |
| 			}
 | |
| 		}
 | |
| 		$closecount = 0;
 | |
| 		$this->_topNode ('setCodeInfo', $this->_codes[$name]);
 | |
| 		if (!$this->_isOpenable ($name, $closecount)) {
 | |
| 			return $this->_reparseAfterCurrentBlock ();
 | |
| 		}
 | |
| 		$this->_setStatus (0);
 | |
| 		switch ($type) {
 | |
| 		case 0:
 | |
| 			$cond = $this->_isUseContent ($this->_stack[count($this->_stack)-1], false);
 | |
| 			break;
 | |
| 		case 1:
 | |
| 			$cond = $this->_isUseContent ($this->_stack[count($this->_stack)-1], true);
 | |
| 			break;
 | |
| 		case 2:
 | |
| 			$cond = $this->_isUseContent ($this->_stack[count($this->_stack)-1], true);
 | |
| 			break;
 | |
| 		default:
 | |
| 			$cond = false;
 | |
| 			break;
 | |
| 		}
 | |
| 		if ($cond) {
 | |
| 			$this->_savedCloseCount = $closecount;
 | |
| 			$this->_setStatus (7);
 | |
| 			return true;
 | |
| 		}
 | |
| 		if (!$this->_topNode ('validate')) {
 | |
| 			return $this->_reparseAfterCurrentBlock ();
 | |
| 		}
 | |
| 		// do we have to close subnodes?
 | |
| 		if ($closecount) {
 | |
| 			// get top node
 | |
| 			$mynode =& $this->_stack[count ($this->_stack)-1];
 | |
| 			// close necessary nodes
 | |
| 			for ($i = 0; $i <= $closecount; $i++) {
 | |
| 				if (!$this->_popNode ()) {
 | |
| 					return false;
 | |
| 				}
 | |
| 			}
 | |
| 			if (!$this->_pushNode ($mynode)) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		if ($this->_codes[$name]['callback_type'] == 'simple_replace_single' || $this->_codes[$name]['callback_type'] == 'callback_replace_single') {
 | |
| 			if (!$this->_popNode ())  {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Is a node closeable?
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _isCloseable ($name, &$closecount) {
 | |
| 		$node =& $this->_findNamedNode ($name, false);
 | |
| 		if ($node === false) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		$scount = count ($this->_stack);
 | |
| 		for ($i = $scount - 1; $i > 0; $i--) {
 | |
| 			$closecount++;
 | |
| 			if ($this->_stack[$i]->equals ($node)) {
 | |
| 				return true;
 | |
| 			}
 | |
| 			if ($this->_stack[$i]->getFlag ('closetag', 'integer', BBCODE_CLOSETAG_IMPLICIT) == BBCODE_CLOSETAG_MUSTEXIST) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		return false;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Is a node openable?
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _isOpenable ($name, &$closecount) {
 | |
| 		if (!isset ($this->_codes[$name])) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		
 | |
| 		$closecount = 0;
 | |
| 		
 | |
| 		$allowed_within = $this->_codes[$name]['allowed_within'];
 | |
| 		$not_allowed_within = $this->_codes[$name]['not_allowed_within'];
 | |
| 		
 | |
| 		$scount = count ($this->_stack);
 | |
| 		if ($scount == 2) { // top level element
 | |
| 			if (!in_array ($this->_rootContentType, $allowed_within)) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		} else {
 | |
| 			if (!in_array ($this->_stack[$scount-2]->_codeInfo['content_type'], $allowed_within)) {
 | |
| 				return $this->_isOpenableWithClose ($name, $closecount);
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		for ($i = 1; $i < $scount - 1; $i++) {
 | |
| 			if (in_array ($this->_stack[$i]->_codeInfo['content_type'], $not_allowed_within)) {
 | |
| 				return $this->_isOpenableWithClose ($name, $closecount);
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Is a node openable by closing other nodes?
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _isOpenableWithClose ($name, &$closecount) {
 | |
| 		$tnname = $this->_topNode ('name');
 | |
| 		if (isset ($this->_codes[strtolower($tnname)]) && (!$this->getCodeFlag (strtolower($tnname), 'case_sensitive', 'boolean', true) || !$this->_caseSensitive)) {
 | |
| 			$tnname = strtolower($tnname);
 | |
| 		}
 | |
| 		if (!in_array ($this->getCodeFlag ($tnname, 'closetag', 'integer', BBCODE_CLOSETAG_IMPLICIT), array (BBCODE_CLOSETAG_FORBIDDEN, BBCODE_CLOSETAG_OPTIONAL))) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		$node =& $this->_findNamedNode ($name, true);
 | |
| 		if ($node === false) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		$scount = count ($this->_stack);
 | |
| 		if ($scount < 3) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		for ($i = $scount - 2; $i > 0; $i--) {
 | |
| 			$closecount++;
 | |
| 			if ($this->_stack[$i]->equals ($node)) {
 | |
| 				return true;
 | |
| 			}
 | |
| 			if (in_array ($this->_stack[$i]->getFlag ('closetag', 'integer', BBCODE_CLOSETAG_IMPLICIT), array (BBCODE_CLOSETAG_IMPLICIT_ON_CLOSE_ONLY, BBCODE_CLOSETAG_MUSTEXIST))) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		return false;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Abstract method: Close remaining blocks
 | |
| 	 * @access private
 | |
| 	 */
 | |
| 	function _closeRemainingBlocks () {
 | |
| 		// everything closed
 | |
| 		if (count ($this->_stack) == 1) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		// not everything close
 | |
| 		if ($this->strict) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		while (count ($this->_stack) > 1) {
 | |
| 			if ($this->_topNode ('getFlag', 'closetag', 'integer', BBCODE_CLOSETAG_IMPLICIT) == BBCODE_CLOSETAG_MUSTEXIST) {
 | |
| 				return false; // sorry
 | |
| 			}
 | |
| 			$res = $this->_popNode ();
 | |
| 			if (!$res) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Find a node with a specific name in stack
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	function &_findNamedNode ($name, $searchdeeper = false) {
 | |
| 		$lname = strtolower ($name);
 | |
| 		if (isset ($this->_codes[$lname]) && (!$this->getCodeFlag ($lname, 'case_sensitive', 'boolean', true) || !$this->_caseSensitive)) {
 | |
| 			$name = $lname;
 | |
| 			$case_sensitive = false;
 | |
| 		} else {
 | |
| 			$case_sensitive = true;
 | |
| 		}
 | |
| 		$scount = count ($this->_stack);
 | |
| 		if ($searchdeeper) {
 | |
| 			$scount--;
 | |
| 		}
 | |
| 		for ($i = $scount - 1; $i > 0; $i--) {
 | |
| 			if (!$case_sensitive) {
 | |
| 				$cmp_name = strtolower ($this->_stack[$i]->name ());
 | |
| 			} else {
 | |
| 				$cmp_name = $this->_stack[$i]->name ();
 | |
| 			}
 | |
| 			if ($cmp_name == $name) {
 | |
| 				return $this->_stack[$i];
 | |
| 			}
 | |
| 		}
 | |
| 		return false;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Abstract method: Output tree
 | |
| 	 * @access private
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _outputTree () {
 | |
| 		if ($this->_noOutput) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		$output = $this->_outputNode ($this->_root);
 | |
| 		if (is_string ($output)) {
 | |
| 			$this->_output = $this->_applyPostfilters ($output);
 | |
| 			unset ($output);
 | |
| 			return true;
 | |
| 		}
 | |
| 		
 | |
| 		return false;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Output a node
 | |
| 	 * @access private
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _outputNode (&$node) {
 | |
| 		$output = '';
 | |
| 		if ($node->_type == STRINGPARSER_BBCODE_NODE_PARAGRAPH || $node->_type == STRINGPARSER_BBCODE_NODE_ELEMENT || $node->_type == STRINGPARSER_NODE_ROOT) {
 | |
| 			$ccount = count ($node->_children);
 | |
| 			for ($i = 0; $i < $ccount; $i++) {
 | |
| 				$suboutput = $this->_outputNode ($node->_children[$i]);
 | |
| 				if (!is_string ($suboutput)) {
 | |
| 					return false;
 | |
| 				}
 | |
| 				$output .= $suboutput;
 | |
| 			}
 | |
| 			if ($node->_type == STRINGPARSER_BBCODE_NODE_PARAGRAPH) {
 | |
| 				return $this->_paragraphHandling['start_tag'].$output.$this->_paragraphHandling['end_tag'];
 | |
| 			}
 | |
| 			if ($node->_type == STRINGPARSER_BBCODE_NODE_ELEMENT) {
 | |
| 				return $node->getReplacement ($output);
 | |
| 			}
 | |
| 			return $output;
 | |
| 		} else if ($node->_type == STRINGPARSER_NODE_TEXT) {
 | |
| 			$output = $node->content;
 | |
| 			$before = '';
 | |
| 			$after = '';
 | |
| 			$ol = strlen ($output);
 | |
| 			switch ($node->getFlag ('newlinemode.begin', 'integer', BBCODE_NEWLINE_PARSE)) {
 | |
| 			case BBCODE_NEWLINE_IGNORE:
 | |
| 				if ($ol && $output{0} == "\n") {
 | |
| 					$before = "\n";
 | |
| 				}
 | |
| 				// don't break!
 | |
| 			case BBCODE_NEWLINE_DROP:
 | |
| 				if ($ol && $output{0} == "\n") {
 | |
| 					$output = substr ($output, 1);
 | |
| 					$ol--;
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 			switch ($node->getFlag ('newlinemode.end', 'integer', BBCODE_NEWLINE_PARSE)) {
 | |
| 			case BBCODE_NEWLINE_IGNORE:
 | |
| 				if ($ol && $output{$ol-1} == "\n") {
 | |
| 					$after = "\n";
 | |
| 				}
 | |
| 				// don't break!
 | |
| 			case BBCODE_NEWLINE_DROP:
 | |
| 				if ($ol && $output{$ol-1} == "\n") {
 | |
| 					$output = substr ($output, 0, -1);
 | |
| 					$ol--;
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 			// can't do anything
 | |
| 			if ($node->_parent === null) {
 | |
| 				return $before.$output.$after;
 | |
| 			}
 | |
| 			if ($node->_parent->_type == STRINGPARSER_BBCODE_NODE_PARAGRAPH)  {
 | |
| 				$parent =& $node->_parent;
 | |
| 				unset ($node);
 | |
| 				$node =& $parent;
 | |
| 				unset ($parent);
 | |
| 				// if no parent for this paragraph
 | |
| 				if ($node->_parent === null) {
 | |
| 					return $before.$output.$after;
 | |
| 				}
 | |
| 			}
 | |
| 			if ($node->_parent->_type == STRINGPARSER_NODE_ROOT) {
 | |
| 				return $before.$this->_applyParsers ($this->_rootContentType, $output).$after;
 | |
| 			}
 | |
| 			if ($node->_parent->_type == STRINGPARSER_BBCODE_NODE_ELEMENT) {
 | |
| 				return $before.$this->_applyParsers ($node->_parent->_codeInfo['content_type'], $output).$after;
 | |
| 			}
 | |
| 			return $before.$output.$after;
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Abstract method: Manipulate the tree
 | |
| 	 * @access private
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _modifyTree () {
 | |
| 		// first pass: try to do newline handling
 | |
| 		$nodes =& $this->_root->getNodesByCriterium ('needsTextNodeModification', true);
 | |
| 		$nodes_count = count ($nodes);
 | |
| 		for ($i = 0; $i < $nodes_count; $i++) {
 | |
| 			$v = $nodes[$i]->getFlag ('opentag.before.newline', 'integer', BBCODE_NEWLINE_PARSE);
 | |
| 			if ($v != BBCODE_NEWLINE_PARSE) {
 | |
| 				$n =& $nodes[$i]->findPrevAdjentTextNode ();
 | |
| 				if (!is_null ($n)) {
 | |
| 					$n->setFlag ('newlinemode.end', $v);
 | |
| 				}
 | |
| 				unset ($n);
 | |
| 			}
 | |
| 			$v = $nodes[$i]->getFlag ('opentag.after.newline', 'integer', BBCODE_NEWLINE_PARSE);
 | |
| 			if ($v != BBCODE_NEWLINE_PARSE) {
 | |
| 				$n =& $nodes[$i]->firstChildIfText ();
 | |
| 				if (!is_null ($n)) {
 | |
| 					$n->setFlag ('newlinemode.begin', $v);
 | |
| 				}
 | |
| 				unset ($n);
 | |
| 			}
 | |
| 			$v = $nodes[$i]->getFlag ('closetag.before.newline', 'integer', BBCODE_NEWLINE_PARSE);
 | |
| 			if ($v != BBCODE_NEWLINE_PARSE) {
 | |
| 				$n =& $nodes[$i]->lastChildIfText ();
 | |
| 				if (!is_null ($n)) {
 | |
| 					$n->setFlag ('newlinemode.end', $v);
 | |
| 				}
 | |
| 				unset ($n);
 | |
| 			}
 | |
| 			$v = $nodes[$i]->getFlag ('closetag.after.newline', 'integer', BBCODE_NEWLINE_PARSE);
 | |
| 			if ($v != BBCODE_NEWLINE_PARSE) {
 | |
| 				$n =& $nodes[$i]->findNextAdjentTextNode ();
 | |
| 				if (!is_null ($n)) {
 | |
| 					$n->setFlag ('newlinemode.begin', $v);
 | |
| 				}
 | |
| 				unset ($n);
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		// second pass a: do paragraph handling on root element
 | |
| 		if ($this->_rootParagraphHandling) {
 | |
| 			$res = $this->_handleParagraphs ($this->_root);
 | |
| 			if (!$res) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		// second pass b: do paragraph handling on other elements
 | |
| 		unset ($nodes);
 | |
| 		$nodes =& $this->_root->getNodesByCriterium ('flag:paragraphs', true);
 | |
| 		$nodes_count = count ($nodes);
 | |
| 		for ($i = 0; $i < $nodes_count; $i++) {
 | |
| 			$res = $this->_handleParagraphs ($nodes[$i]);
 | |
| 			if (!$res) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 		// second pass c: search for empty paragraph nodes and remove them
 | |
| 		unset ($nodes);
 | |
| 		$nodes =& $this->_root->getNodesByCriterium ('empty', true);
 | |
| 		$nodes_count = count ($nodes);
 | |
| 		if (isset ($parent)) {
 | |
| 			unset ($parent); $parent = null;
 | |
| 		}
 | |
| 		for ($i = 0; $i < $nodes_count; $i++) {
 | |
| 			if ($nodes[$i]->_type != STRINGPARSER_BBCODE_NODE_PARAGRAPH) {
 | |
| 				continue;
 | |
| 			}
 | |
| 			unset ($parent);
 | |
| 			$parent =& $nodes[$i]->_parent;
 | |
| 			$parent->removeChild ($nodes[$i], true);
 | |
| 		}
 | |
| 		
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Handle paragraphs
 | |
| 	 * @access private
 | |
| 	 * @param object $node The node to handle
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _handleParagraphs (&$node) {
 | |
| 		// if this node is already a subnode of a paragraph node, do NOT 
 | |
| 		// do paragraph handling on this node!
 | |
| 		if ($this->_hasParagraphAncestor ($node)) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		$dest_nodes = array ();
 | |
| 		$last_node_was_paragraph = false;
 | |
| 		$prevtype = STRINGPARSER_NODE_TEXT;
 | |
| 		$paragraph = null;
 | |
| 		while (count ($node->_children)) {
 | |
| 			$mynode =& $node->_children[0];
 | |
| 			$node->removeChild ($mynode);
 | |
| 			$subprevtype = $prevtype;
 | |
| 			$sub_nodes =& $this->_breakupNodeByParagraphs ($mynode);
 | |
| 			for ($i = 0; $i < count ($sub_nodes); $i++) {
 | |
| 				if (!$last_node_was_paragraph ||  ($prevtype == $sub_nodes[$i]->_type && ($i != 0 || $prevtype != STRINGPARSER_BBCODE_NODE_ELEMENT))) {
 | |
| 					unset ($paragraph);
 | |
| 					$paragraph =& new StringParser_BBCode_Node_Paragraph ();
 | |
| 				}
 | |
| 				$prevtype = $sub_nodes[$i]->_type;
 | |
| 				if ($sub_nodes[$i]->_type != STRINGPARSER_BBCODE_NODE_ELEMENT || $sub_nodes[$i]->getFlag ('paragraph_type', 'integer', BBCODE_PARAGRAPH_ALLOW_BREAKUP) != BBCODE_PARAGRAPH_BLOCK_ELEMENT) {
 | |
| 					$paragraph->appendChild ($sub_nodes[$i]);
 | |
| 					$dest_nodes[] =& $paragraph;
 | |
| 					$last_node_was_paragraph = true;
 | |
| 				} else {
 | |
| 					$dest_nodes[] =& $sub_nodes[$i];
 | |
| 					$last_onde_was_paragraph = false;
 | |
| 					unset ($paragraph);
 | |
| 					$paragraph =& new StringParser_BBCode_Node_Paragraph ();
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		$count = count ($dest_nodes);
 | |
| 		for ($i = 0; $i < $count; $i++) {
 | |
| 			$node->appendChild ($dest_nodes[$i]);
 | |
| 		}
 | |
| 		unset ($dest_nodes);
 | |
| 		unset ($paragraph);
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Search for a paragraph node in tree in upward direction
 | |
| 	 * @access private
 | |
| 	 * @param object $node The node to analyze
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _hasParagraphAncestor (&$node) {
 | |
| 		if ($node->_parent === null) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		$parent =& $node->_parent;
 | |
| 		if ($parent->_type == STRINGPARSER_BBCODE_NODE_PARAGRAPH) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		return $this->_hasParagraphAncestor ($parent);
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Break up nodes
 | |
| 	 * @access private
 | |
| 	 * @param object $node The node to break up
 | |
| 	 * @return array
 | |
| 	 */
 | |
| 	function &_breakupNodeByParagraphs (&$node) {
 | |
| 		$detect_string = $this->_paragraphHandling['detect_string'];
 | |
| 		$dest_nodes = array ();
 | |
| 		// text node => no problem
 | |
| 		if ($node->_type == STRINGPARSER_NODE_TEXT) {
 | |
| 			$cpos = 0;
 | |
| 			while (($npos = strpos ($node->content, $detect_string, $cpos)) !== false) {
 | |
| 				$subnode =& new StringParser_Node_Text (substr ($node->content, $cpos, $npos - $cpos), $node->occurredAt + $cpos);
 | |
| 				// copy flags
 | |
| 				foreach ($node->_flags as $flag => $value) {
 | |
| 					if ($flag == 'newlinemode.begin') {
 | |
| 						if ($cpos == 0) {
 | |
| 							$subnode->setFlag ($flag, $value);
 | |
| 						}
 | |
| 					} else if ($flag == 'newlinemode.end') {
 | |
| 						// do nothing
 | |
| 					} else {
 | |
| 						$subnode->setFlag ($flag, $value);
 | |
| 					}
 | |
| 				}
 | |
| 				$dest_nodes[] =& $subnode;
 | |
| 				unset ($subnode);
 | |
| 				$cpos = $npos + strlen ($detect_string);
 | |
| 			}
 | |
| 			$subnode =& new StringParser_Node_Text (substr ($node->content, $cpos), $node->occurredAt + $cpos);
 | |
| 			if ($cpos == 0) {
 | |
| 				$value = $node->getFlag ('newlinemode.begin', 'integer', null);
 | |
| 				if ($value !== null) {
 | |
| 					$subnode->setFlag ('newlinemode.begin', $value);
 | |
| 				}
 | |
| 			}
 | |
| 			$value = $node->getFlag ('newlinemode.end', 'integer', null);
 | |
| 			if ($value !== null) {
 | |
| 				$subnode->setFlag ('newlinemode.end', $value);
 | |
| 			}
 | |
| 			$dest_nodes[] =& $subnode;
 | |
| 			unset ($subnode);
 | |
| 			return $dest_nodes;
 | |
| 		}
 | |
| 		// not a text node or an element node => no way
 | |
| 		if ($node->_type != STRINGPARSER_BBCODE_NODE_ELEMENT) {
 | |
| 			$dest_nodes[] =& $node;
 | |
| 			return $dest_nodes;
 | |
| 		}
 | |
| 		if ($node->getFlag ('paragraph_type', 'integer', BBCODE_PARAGRAPH_ALLOW_BREAKUP) != BBCODE_PARAGRAPH_ALLOW_BREAKUP || !count ($node->_children)) {
 | |
| 			$dest_nodes[] =& $node;
 | |
| 			return $dest_nodes;
 | |
| 		}
 | |
| 		$dest_node =& $node->duplicate ();
 | |
| 		$nodecount = count ($node->_children);
 | |
| 		// now this node allows breakup - do it
 | |
| 		for ($i = 0; $i < $nodecount; $i++) {
 | |
| 			$firstnode =& $node->_children[0];
 | |
| 			$node->removeChild ($firstnode);
 | |
| 			$sub_nodes =& $this->_breakupNodeByParagraphs ($firstnode);
 | |
| 			for ($j = 0; $j < count ($sub_nodes); $j++) {
 | |
| 				if ($j != 0) {
 | |
| 					$dest_nodes[] =& $dest_node;
 | |
| 					unset ($dest_node);
 | |
| 					$dest_node =& $node->duplicate ();
 | |
| 				}
 | |
| 				$dest_node->appendChild ($sub_nodes[$j]);
 | |
| 			}
 | |
| 			unset ($sub_nodes);
 | |
| 		}
 | |
| 		$dest_nodes[] =& $dest_node;
 | |
| 		return $dest_nodes;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Is this node a usecontent node
 | |
| 	 * @access private
 | |
| 	 * @param object $node The node to check
 | |
| 	 * @param bool $check_attrs Also check whether 'usecontent?'-attributes exist
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function _isUseContent (&$node, $check_attrs = false) {
 | |
| 		$name = strtolower($node->name ());
 | |
| 		if ($this->_codes[$name]['callback_type'] == 'usecontent') {
 | |
| 			return true;
 | |
| 		}
 | |
| 		if ($this->_codes[$name]['callback_type'] != 'usecontent?') {
 | |
| 			return false;
 | |
| 		}
 | |
| 		if ($check_attrs === false) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		$attributes = array_keys ($this->_topNodeVar ('_attributes'));
 | |
| 		$p = @$this->_codes[$name]['callback_params']['usecontent_param'];
 | |
| 		if (is_array ($p)) {
 | |
| 			foreach ($p as $param) {
 | |
| 				if (in_array ($param, $attributes)) {
 | |
| 					return false;
 | |
| 				}
 | |
| 			}
 | |
| 		} else {
 | |
| 			if (in_array ($p, $attributes)) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 		return true;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Node type: BBCode Element node
 | |
|  * @see StringParser_BBCode_Node_Element::_type
 | |
|  */
 | |
| define ('STRINGPARSER_BBCODE_NODE_ELEMENT', 32);
 | |
| 
 | |
| /**
 | |
|  * Node type: BBCode Paragraph node
 | |
|  * @see StringParser_BBCode_Node_Paragraph::_type
 | |
|  */
 | |
| define ('STRINGPARSER_BBCODE_NODE_PARAGRAPH', 33);
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * BBCode String parser paragraph node class
 | |
|  *
 | |
|  * @package stringparser
 | |
|  */
 | |
| class StringParser_BBCode_Node_Paragraph extends StringParser_Node {
 | |
| 	/**
 | |
| 	 * The type of this node.
 | |
| 	 * 
 | |
| 	 * This node is a bbcode paragraph node.
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var int
 | |
| 	 * @see STRINGPARSER_BBCODE_NODE_PARAGRAPH
 | |
| 	 */
 | |
| 	var $_type = STRINGPARSER_BBCODE_NODE_PARAGRAPH;
 | |
| 	
 | |
| 	/**
 | |
| 	 * Determines whether a criterium matches this node
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $criterium The criterium that is to be checked
 | |
| 	 * @param mixed $value The value that is to be compared
 | |
| 	 * @return bool True if this node matches that criterium
 | |
| 	 */
 | |
| 	function matchesCriterium ($criterium, $value) {
 | |
| 		if ($criterium == 'empty') {
 | |
| 			if (!count ($this->_children)) {
 | |
| 				return true;
 | |
| 			}
 | |
| 			if (count ($this->_children) > 1) {
 | |
| 				return false;
 | |
| 			}
 | |
| 			if ($this->_children[0]->_type != STRINGPARSER_NODE_TEXT) {
 | |
| 				return false;
 | |
| 			}
 | |
| 			if (!strlen ($this->_children[0]->content)) {
 | |
| 				return true;
 | |
| 			}
 | |
| 			if (strlen ($this->_children[0]->content) > 2) {
 | |
| 				return false;
 | |
| 			}
 | |
| 			$f_begin = $this->_children[0]->getFlag ('newlinemode.begin', 'integer', BBCODE_NEWLINE_PARSE);
 | |
| 			$f_end = $this->_children[0]->getFlag ('newlinemode.end', 'integer', BBCODE_NEWLINE_PARSE);
 | |
| 			$content = $this->_children[0]->content;
 | |
| 			if ($f_begin != BBCODE_NEWLINE_PARSE && $content{0} == "\n") {
 | |
| 				$content = substr ($content, 1);
 | |
| 			}
 | |
| 			if ($f_end != BBCODE_NEWLINE_PARSE && $content{strlen($content)-1} == "\n") {
 | |
| 				$content = substr ($content, 0, -1);
 | |
| 			}
 | |
| 			if (!strlen ($content)) {
 | |
| 				return true;
 | |
| 			}
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * BBCode String parser element node class
 | |
|  *
 | |
|  * @package stringparser
 | |
|  */
 | |
| class StringParser_BBCode_Node_Element extends StringParser_Node {
 | |
| 	/**
 | |
| 	 * The type of this node.
 | |
| 	 * 
 | |
| 	 * This node is a bbcode element node.
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var int
 | |
| 	 * @see STRINGPARSER_BBCODE_NODE_ELEMENT
 | |
| 	 */
 | |
| 	var $_type = STRINGPARSER_BBCODE_NODE_ELEMENT;
 | |
| 	
 | |
| 	/**
 | |
| 	 * Element name
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var string
 | |
| 	 * @see StringParser_BBCode_Node_Element::name
 | |
| 	 * @see StringParser_BBCode_Node_Element::setName
 | |
| 	 * @see StringParser_BBCode_Node_Element::appendToName
 | |
| 	 */
 | |
| 	var $_name = '';
 | |
| 	
 | |
| 	/**
 | |
| 	 * Element flags
 | |
| 	 * 
 | |
| 	 * @access private
 | |
| 	 * @var array
 | |
| 	 */
 | |
| 	var $_flags = array ();
 | |
| 	
 | |
| 	/**
 | |
| 	 * Element attributes
 | |
| 	 * 
 | |
| 	 * @access private
 | |
| 	 * @var array
 | |
| 	 */
 | |
| 	var $_attributes = array ();
 | |
| 	
 | |
| 	/**
 | |
| 	 * Had a close tag
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var bool
 | |
| 	 */
 | |
| 	var $_hadCloseTag = false;
 | |
| 	
 | |
| 	/**
 | |
| 	 * Was processed by paragraph handling
 | |
| 	 *
 | |
| 	 * @access private
 | |
| 	 * @var bool
 | |
| 	 */
 | |
| 	var $_paragraphHandled = false;
 | |
| 	
 | |
| 	//////////////////////////////////////////////////
 | |
| 	
 | |
| 	/**
 | |
| 	 * Duplicate this node (but without children / parents)
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return object
 | |
| 	 */
 | |
| 	function &duplicate () {
 | |
| 		$newnode =& new StringParser_BBCode_Node_Element ($this->occurredAt);
 | |
| 		$newnode->_name = $this->_name;
 | |
| 		$newnode->_flags = $this->_flags;
 | |
| 		$newnode->_attributes = $this->_attributes;
 | |
| 		$newnode->_hadCloseTag = $this->_hadCloseTag;
 | |
| 		$newnode->_paragraphHandled = $this->_paragraphHandled;
 | |
| 		$newnode->_codeInfo = $this->_codeInfo;
 | |
| 		return $newnode;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Retreive name of this element
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	function name () {
 | |
| 		return $this->_name;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set name of this element
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $name The new name of the element
 | |
| 	 */
 | |
| 	function setName ($name) {
 | |
| 		$this->_name = $name;
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Append to name of this element
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $chars The chars to append to the name of the element
 | |
| 	 */
 | |
| 	function appendToName ($chars) {
 | |
| 		$this->_name .= $chars;
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Append to attribute of this element
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $name The name of the attribute
 | |
| 	 * @param string $chars The chars to append to the attribute of the element
 | |
| 	 */
 | |
| 	function appendToAttribute ($name, $chars) {
 | |
| 		if (!isset ($this->_attributes[$name])) {
 | |
| 			$this->_attributes[$name] = $chars;
 | |
| 			return true;
 | |
| 		}
 | |
| 		$this->_attributes[$name] .= $chars;
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set attribute
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $name The name of the attribute
 | |
| 	 * @param string $value The new value of the attribute
 | |
| 	 */
 | |
| 	function setAttribute ($name, $value) {
 | |
| 		$this->_attributes[$name] = $value;
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set code info
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param array $info The code info array
 | |
| 	 */
 | |
| 	function setCodeInfo ($info) {
 | |
| 		$this->_codeInfo = $info;
 | |
| 		$this->_flags = $info['flags'];
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get attribute value
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $name The name of the attribute
 | |
| 	 */
 | |
| 	function attribute ($name) {
 | |
| 		if (!isset ($this->_attributes[$name])) {
 | |
| 			return null;
 | |
| 		}
 | |
| 		return $this->_attributes[$name];
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set flag that this element had a close tag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 */
 | |
| 	function setHadCloseTag () {
 | |
| 		$this->_hadCloseTag = true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set flag that this element was already processed by paragraph handling
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 */
 | |
| 	function setParagraphHandled () {
 | |
| 		$this->_paragraphHandled = true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get flag if this element was already processed by paragraph handling
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function paragraphHandled () {
 | |
| 		return $this->_paragraphHandled;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get flag if this element had a close tag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function hadCloseTag () {
 | |
| 		return $this->_hadCloseTag;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Determines whether a criterium matches this node
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $criterium The criterium that is to be checked
 | |
| 	 * @param mixed $value The value that is to be compared
 | |
| 	 * @return bool True if this node matches that criterium
 | |
| 	 */
 | |
| 	function matchesCriterium ($criterium, $value) {
 | |
| 		if ($criterium == 'tagName') {
 | |
| 			return ($value == $this->_name);
 | |
| 		}
 | |
| 		if ($criterium == 'needsTextNodeModification') {
 | |
| 			return (($this->getFlag ('opentag.before.newline', 'integer', BBCODE_NEWLINE_PARSE) != BBCODE_NEWLINE_PARSE || $this->getFlag ('opentag.after.newline', 'integer', BBCODE_NEWLINE_PARSE) != BBCODE_NEWLINE_PARSE || ($this->_hadCloseTag && ($this->getFlag ('closetag.before.newline', 'integer', BBCODE_NEWLINE_PARSE) != BBCODE_NEWLINE_PARSE || $this->getFlag ('closetag.after.newline', 'integer', BBCODE_NEWLINE_PARSE) != BBCODE_NEWLINE_PARSE))) == (bool)$value);
 | |
| 		}
 | |
| 		if (substr ($criterium, 0, 5) == 'flag:') {
 | |
| 			$criterium = substr ($criterium, 5);
 | |
| 			return ($this->getFlag ($criterium) == $value);
 | |
| 		}
 | |
| 		if (substr ($criterium, 0, 6) == '!flag:') {
 | |
| 			$criterium = substr ($criterium, 6);
 | |
| 			return ($this->getFlag ($criterium) != $value);
 | |
| 		}
 | |
| 		if (substr ($criterium, 0, 6) == 'flag=:') {
 | |
| 			$criterium = substr ($criterium, 6);
 | |
| 			return ($this->getFlag ($criterium) === $value);
 | |
| 		}
 | |
| 		if (substr ($criterium, 0, 7) == '!flag=:') {
 | |
| 			$criterium = substr ($criterium, 7);
 | |
| 			return ($this->getFlag ($criterium) !== $value);
 | |
| 		}
 | |
| 		return parent::matchesCriterium ($criterium, $value);
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get first child if it is a text node
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	function &firstChildIfText () {
 | |
| 		$ret =& $this->firstChild ();
 | |
| 		if (is_null ($ret)) {
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		if ($ret->_type != STRINGPARSER_NODE_TEXT) {
 | |
| 			// DON'T DO $ret = null WITHOUT unset BEFORE!
 | |
| 			// ELSE WE WILL ERASE THE NODE ITSELF! EVIL!
 | |
| 			unset ($ret);
 | |
| 			$ret = null;
 | |
| 		}
 | |
| 		return $ret;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get last child if it is a text node AND if this element had a close tag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	function &lastChildIfText () {
 | |
| 		$ret =& $this->lastChild ();
 | |
| 		if (is_null ($ret)) {
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		if ($ret->_type != STRINGPARSER_NODE_TEXT || !$this->_hadCloseTag) {
 | |
| 			// DON'T DO $ret = null WITHOUT unset BEFORE!
 | |
| 			// ELSE WE WILL ERASE THE NODE ITSELF! EVIL!
 | |
| 			if ($ret->_type != STRINGPARSER_NODE_TEXT && !$ret->hadCloseTag ()) {
 | |
| 				$ret2 =& $ret->_findPrevAdjentTextNodeHelper ();
 | |
| 				unset ($ret);
 | |
| 				$ret =& $ret2;
 | |
| 				unset ($ret2);
 | |
| 			} else {
 | |
| 				unset ($ret);
 | |
| 				$ret = null;
 | |
| 			}
 | |
| 		}
 | |
| 		return $ret;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Find next adjent text node after close tag
 | |
| 	 *
 | |
| 	 * returns the node or null if none exists
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	function &findNextAdjentTextNode () {
 | |
| 		$ret = null;
 | |
| 		if (is_null ($this->_parent)) {
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		if (!$this->_hadCloseTag) {
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		$ccount = count ($this->_parent->_children);
 | |
| 		$found = false;
 | |
| 		for ($i = 0; $i < $ccount; $i++) {
 | |
| 			if ($this->_parent->_children[$i]->equals ($this)) {
 | |
| 				$found = $i;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if ($found === false) {
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		if ($found < $ccount - 1) {
 | |
| 			if ($this->_parent->_children[$found+1]->_type == STRINGPARSER_NODE_TEXT) {
 | |
| 				return $this->_parent->_children[$found+1];
 | |
| 			}
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		if ($this->_parent->_type == STRINGPARSER_BBCODE_NODE_ELEMENT && !$this->_parent->hadCloseTag ()) {
 | |
| 			$ret =& $this->_parent->findNextAdjentTextNode ();
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		return $ret;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Find previous adjent text node before open tag
 | |
| 	 *
 | |
| 	 * returns the node or null if none exists
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	function &findPrevAdjentTextNode () {
 | |
| 		$ret = null;
 | |
| 		if (is_null ($this->_parent)) {
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		$ccount = count ($this->_parent->_children);
 | |
| 		$found = false;
 | |
| 		for ($i = 0; $i < $ccount; $i++) {
 | |
| 			if ($this->_parent->_children[$i]->equals ($this)) {
 | |
| 				$found = $i;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if ($found === false) {
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		if ($found > 0) {
 | |
| 			if ($this->_parent->_children[$found-1]->_type == STRINGPARSER_NODE_TEXT) {
 | |
| 				return $this->_parent->_children[$found-1];
 | |
| 			}
 | |
| 			if (!$this->_parent->_children[$found-1]->hadCloseTag ()) {
 | |
| 				$ret =& $this->_parent->_children[$found-1]->_findPrevAdjentTextNodeHelper ();
 | |
| 			}
 | |
| 			return $ret;
 | |
| 		}
 | |
| 		return $ret;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Helper function for findPrevAdjentTextNode
 | |
| 	 *
 | |
| 	 * Looks at the last child node; if it's a text node, it returns it,
 | |
| 	 * if the element node did not have an open tag, it calls itself
 | |
| 	 * recursively.
 | |
| 	 */
 | |
| 	function &_findPrevAdjentTextNodeHelper () {
 | |
| 		$lastnode =& $this->lastChild ();
 | |
| 		if ($lastnode->_type == STRINGPARSER_NODE_TEXT) {
 | |
| 			return $lastnode;
 | |
| 		}
 | |
| 		if (!$lastnode->hadCloseTag ()) {
 | |
| 			$ret =& $lastnode->_findPrevAdjentTextNodeHelper ();
 | |
| 		} else {
 | |
| 			$ret = null;
 | |
| 		}
 | |
| 		return $ret;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get Flag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $flag The requested flag
 | |
| 	 * @param string $type The requested type of the return value
 | |
| 	 * @param mixed $default The default return value
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	function getFlag ($flag, $type = 'mixed', $default = null) {
 | |
| 		if (!isset ($this->_flags[$flag])) {
 | |
| 			return $default;
 | |
| 		}
 | |
| 		$return = $this->_flags[$flag];
 | |
| 		if ($type != 'mixed') {
 | |
| 			settype ($return, $type);
 | |
| 		}
 | |
| 		return $return;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Set a flag
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $name The name of the flag
 | |
| 	 * @param mixed $value The value of the flag
 | |
| 	 */
 | |
| 	function setFlag ($name, $value) {
 | |
| 		$this->_flags[$name] = $value;
 | |
| 		return true;
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Validate code
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	function validate () {
 | |
| 		if ($this->_codeInfo['callback_type'] != 'simple_replace' && $this->_codeInfo['callback_type'] != 'simple_replace_single') {
 | |
| 			if (!is_callable ($this->_codeInfo['callback_func'])) {
 | |
| 				return false;
 | |
| 			}
 | |
| 			
 | |
| 			if (($this->_codeInfo['callback_type'] == 'usecontent' || $this->_codeInfo['callback_type'] == 'usecontent?') && count ($this->_children) == 1 && $this->_children[0]->_type == STRINGPARSER_NODE_TEXT) {
 | |
| 				// we have to make sure the object gets passed on as a reference
 | |
| 				// if we do call_user_func(..., &$this) this will clash with PHP5
 | |
| 				$callArray = array ('validate', $this->_attributes, $this->_children[0]->content, $this->_codeInfo['callback_params']);
 | |
| 				$callArray[] =& $this;
 | |
| 				$res = call_user_func_array ($this->_codeInfo['callback_func'], $callArray);
 | |
| 				if ($res) {
 | |
| 					// ok, now, if we've got a usecontent type, set a flag that
 | |
| 					// this may not be broken up by paragraph handling!
 | |
| 					// but PLEASE do NOT change if already set to any other setting
 | |
| 					// than BBCODE_PARAGRAPH_ALLOW_BREAKUP because we could
 | |
| 					// override e.g. BBCODE_PARAGRAPH_BLOCK_ELEMENT!
 | |
| 					$val = $this->getFlag ('paragraph_type', 'integer', BBCODE_PARAGRAPH_ALLOW_BREAKUP);
 | |
| 					if ($val == BBCODE_PARAGRAPH_ALLOW_BREAKUP) {
 | |
| 						$this->_flags['paragraph_type'] = BBCODE_PARAGRAPH_ALLOW_INSIDE;
 | |
| 					}
 | |
| 				}
 | |
| 				return $res;
 | |
| 			}
 | |
| 			
 | |
| 			// we have to make sure the object gets passed on as a reference
 | |
| 			// if we do call_user_func(..., &$this) this will clash with PHP5
 | |
| 			$callArray = array ('validate', $this->_attributes, null, $this->_codeInfo['callback_params']);
 | |
| 			$callArray[] =& $this;
 | |
| 			return call_user_func_array ($this->_codeInfo['callback_func'], $callArray);
 | |
| 		}
 | |
| 		return (bool)(!count ($this->_attributes));
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Get replacement for this code
 | |
| 	 *
 | |
| 	 * @access public
 | |
| 	 * @param string $subcontent The content of all sub-nodes
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	function getReplacement ($subcontent) {
 | |
| 		if ($this->_codeInfo['callback_type'] == 'simple_replace' || $this->_codeInfo['callback_type'] == 'simple_replace_single') {
 | |
| 			if ($this->_codeInfo['callback_type'] == 'simple_replace_single') {
 | |
| 				if (strlen ($subcontent)) { // can't be!
 | |
| 					return false;
 | |
| 				}
 | |
| 				return $this->_codeInfo['callback_params']['start_tag'];
 | |
| 			}
 | |
| 			return $this->_codeInfo['callback_params']['start_tag'].$subcontent.$this->_codeInfo['callback_params']['end_tag'];
 | |
| 		}
 | |
| 		// else usecontent, usecontent? or callback_replace or callback_replace_single
 | |
| 		// => call function (the function is callable, determined in validate()!)
 | |
| 		
 | |
| 		// we have to make sure the object gets passed on as a reference
 | |
| 		// if we do call_user_func(..., &$this) this will clash with PHP5
 | |
| 		$callArray = array ('output', $this->_attributes, $subcontent, $this->_codeInfo['callback_params']);
 | |
| 		$callArray[] =& $this;
 | |
| 		return call_user_func_array ($this->_codeInfo['callback_func'], $callArray);
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * Dump this node to a string
 | |
| 	 *
 | |
| 	 * @access protected
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	function _dumpToString () {
 | |
| 		$str = "bbcode \"".substr (preg_replace ('/\s+/', ' ', $this->_name), 0, 40)."\"";
 | |
| 		if (count ($this->_attributes)) {
 | |
| 			$attribs = array_keys ($this->_attributes);
 | |
| 			sort ($attribs);
 | |
| 			$str .= ' (';
 | |
| 			$i = 0;
 | |
| 			foreach ($attribs as $attrib) {
 | |
| 				if ($i != 0) {
 | |
| 					$str .= ', ';
 | |
| 				}
 | |
| 				$str .= $attrib.'="';
 | |
| 				$str .= substr (preg_replace ('/\s+/', ' ', $this->_attributes[$attrib]), 0, 10);
 | |
| 				$str .= '"';
 | |
| 				$i++;
 | |
| 			}
 | |
| 			$str .= ')';
 | |
| 		}
 | |
| 		return $str;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| ?>
 | 
