setText($text); } /** * Set the raw text - might include BBCode tags * * @param string $text The text * @retun void */ public function setText($text) { $this->text = $text; } /** * Renders only the text without any BBCode tags. * * @param string $text Optional: Render the passed BBCode string instead of the internally stored one * @return string */ public function renderPlain($text = null) { if ($this->text !== null and $text === null) { $text = $this->text; } return preg_replace("/\[(.*?)\]/is", '', $text); } /** * Renders only the text without any BBCode tags. * Alias for renderRaw(). * * @deprecated Deprecated since 1.1.0 * * @param string $text Optional: Render the passed BBCode string instead of the internally stored one * @return string */ public function renderRaw($text = null) { return $this->renderPlain($text); } /** * Renders BBCode to HTML * * @param string $text Optional: Render the passed BBCode string instead of the internally stored one * @param bool $escape Escape HTML entities? (Only "<" and ">"!) * @param bool $keepLines Keep line breaks by replacing them with
? * @return string */ public function render($text = null, $escape = true, $keepLines = true) { if ($this->text !== null and $text === null) { $text = $this->text; } $tokenizer = new Tokenizer($this->tagTypes); $tokens = $tokenizer->tokenize($text, $escape, $keepLines); $html = ''; $level = 0; foreach ($tokens as $index => $token) { switch ($token->getType()) { case Token::TYPE_LINEBREAK: $html .= '
'; break; case Token::TYPE_PLAIN_TEXT: $html .= $token->getValue(); break; case Token::TYPE_TAG_OPENING: case Token::TYPE_TAG_CLOSING: $tagOpening = $token->getType() === Token::TYPE_TAG_OPENING; $tagName = $token->getValue(); // If the tag is not known, just do not render it. We do not want to throw any exceptions. if (isset($this->tagTypes[$tagName])) { $tagType = $this->tagTypes[$tagName]; /** @var AbstractTagType $tag */ $tag = new $tagType; $tag->render($html, $tagOpening); } if ($tagOpening) { $level++; } else { $level--; } } } return $html; $html = ''; $len = mb_strlen($text); $inTag = false; // True if current position is inside a tag $inName = false; // True if current pos is inside a tag name $inStr = false; // True if current pos is inside a string /** @var Tag|null $tag */ $tag = null; $openTags = array(); /* * Loop over each character of the text */ for ($i = 0; $i < $len; $i++) { $char = mb_substr($text, $i, 1); if ($keepLines) { if ($char == "\n") { $html .= '
'; } if ($char == "\r") { continue; } } if (! $escape or ($char != '<' and $char != '>')) { /* * $inTag == true means the current position is inside a tag definition * (= inside the brackets of a tag) */ if ($inTag) { if ($char == '"') { if ($inStr) { $inStr = false; } else { if ($inName) { $tag->valid = false; } else { $inStr = true; } } } else { /* * This closes a tag */ if ($char == ']' and ! $inStr) { $inTag = false; $inName = false; if ($tag->valid) { $code = null; if ($tag->opening) { $code = $this->generateTag($tag, $html, null, $openTags); } else { $openingTag = $this->popTag($openTags, $tag); if ($openingTag) { $code = $this->generateTag($tag, $html, $openingTag, $openTags); } } if ($code !== null and $tag->opening) { $openTags[$tag->name][] = $tag; } $html .= $code; } continue; } if ($inName and ! $inStr) { /* * This makes the current tag a closing tag */ if ($char == '/') { if ($tag->name) { $tag->valid = false; } else { $tag->opening = false; } } else { /* * This means a property starts */ if ($char == '=') { if ($tag->name) { $inName = false; } else { $tag->valid = false; } } else { $tag->name .= mb_strtolower($char); } } } else { // If we are not inside the name we are inside a property $tag->property .= $char; } } } else { /* * This opens a tag */ if ($char == '[') { $inTag = true; $inName = true; $tag = new Tag(); $tag->position = mb_strlen($html); } else { $html .= $char; } } } else { $html .= htmlspecialchars($char); } } /* * Check for tags that are not closed and close them. */ foreach ($openTags as $name => $openTagsByType) { $closingTag = new Tag($name, false); foreach ($openTagsByType as $openTag) { $html .= $this->generateTag($closingTag, $html, $openTag); } } return $html; } /** * Generates and returns the HTML code of the current tag * * @param Tag $tag The current tag * @param string $html The current HTML code passed by reference - might be altered! * @param Tag|null $openingTag The opening tag that is linked to the tag (or null) * @param Tag[] $openTags Array with tags that are opned but not closed * @return string */ protected function generateTag(Tag $tag, &$html, Tag $openingTag = null, array $openTags = []) { $code = null; if (in_array($tag->name, $this->ignoredTags)) { return $code; } // Custom tags: foreach ($this->customTagClosures as $name => $closure) { if ($tag->name === $name) { exit($tag->name); $code .= $closure($tag, $html, $openingTag); } } return $code; } /** * Magic method __toString() * * @return string */ public function __toString() { return $this->render(); } /** * Returns the last tag of a given type and removes it from the array. * * @param Tag[] $tags Array of tags * @param Tag $tag Return the last tag of the type of this tag * @return Tag|null */ protected function popTag(array &$tags, $tag) { if (! isset($tags[$tag->name])) { return null; } $size = sizeof($tags[$tag->name]); if ($size === 0) { return null; } else { return array_pop($tags[$tag->name]); } } /** * Returns true if $haystack ends with $needle * * @param string $haystack * @param string $needle * @return bool */ protected function endsWith($haystack, $needle) { return ($needle === '' or mb_substr($haystack, -mb_strlen($needle)) === $needle); } }