A bare bones front-end for knockout designed for maximum compatibility with "obsolete" browsers
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

334 Zeilen
10KB

  1. <?php
  2. namespace App\Vendor\BBCode;
  3. use App\Vendor\BBCode\Tags\BoldTag;
  4. use App\Vendor\BBCode\Tags\NoParseTag;
  5. use App\Vendor\BBCode\Tokenizer\Token;
  6. use App\Vendor\BBCode\Tokenizer\Tokenizer;
  7. use Closure;
  8. /*
  9. * BBCode to HTML converter
  10. *
  11. * Inspired by Kai Mallea (kmallea@gmail.com)
  12. *
  13. * Licensed under the MIT license:
  14. * http://www.opensource.org/licenses/mit-license.php
  15. */
  16. class Bbcode {
  17. protected $tagTypes = [];
  18. /**
  19. * The text with BBCodes
  20. *
  21. * @var string|null
  22. */
  23. protected $text = null;
  24. /**
  25. * BBCode constructor.
  26. *
  27. * @param string|null $text The text - might include BBCode tags
  28. */
  29. public function __construct($text = null)
  30. {
  31. $this->setText($text);
  32. }
  33. /**
  34. * Set the raw text - might include BBCode tags
  35. *
  36. * @param string $text The text
  37. * @retun void
  38. */
  39. public function setText($text)
  40. {
  41. $this->text = $text;
  42. }
  43. /**
  44. * Renders only the text without any BBCode tags.
  45. *
  46. * @param string $text Optional: Render the passed BBCode string instead of the internally stored one
  47. * @return string
  48. */
  49. public function renderPlain($text = null)
  50. {
  51. if ($this->text !== null and $text === null) {
  52. $text = $this->text;
  53. }
  54. return preg_replace("/\[(.*?)\]/is", '', $text);
  55. }
  56. /**
  57. * Renders only the text without any BBCode tags.
  58. * Alias for renderRaw().
  59. *
  60. * @deprecated Deprecated since 1.1.0
  61. *
  62. * @param string $text Optional: Render the passed BBCode string instead of the internally stored one
  63. * @return string
  64. */
  65. public function renderRaw($text = null)
  66. {
  67. return $this->renderPlain($text);
  68. }
  69. /**
  70. * Renders BBCode to HTML
  71. *
  72. * @param string $text Optional: Render the passed BBCode string instead of the internally stored one
  73. * @param bool $escape Escape HTML entities? (Only "<" and ">"!)
  74. * @param bool $keepLines Keep line breaks by replacing them with <br>?
  75. * @return string
  76. */
  77. public function render($text = null, $escape = true, $keepLines = true)
  78. {
  79. if ($this->text !== null and $text === null) {
  80. $text = $this->text;
  81. }
  82. $tokenizer = new Tokenizer($this->tagTypes);
  83. $tokens = $tokenizer->tokenize($text, $escape, $keepLines);
  84. $html = '';
  85. $level = 0;
  86. foreach ($tokens as $index => $token) {
  87. switch ($token->getType()) {
  88. case Token::TYPE_LINEBREAK:
  89. $html .= '<br/>';
  90. break;
  91. case Token::TYPE_PLAIN_TEXT:
  92. $html .= $token->getValue();
  93. break;
  94. case Token::TYPE_TAG_OPENING:
  95. case Token::TYPE_TAG_CLOSING:
  96. $tagOpening = $token->getType() === Token::TYPE_TAG_OPENING;
  97. $tagName = $token->getValue();
  98. // If the tag is not known, just do not render it. We do not want to throw any exceptions.
  99. if (isset($this->tagTypes[$tagName])) {
  100. $tagType = $this->tagTypes[$tagName];
  101. /** @var AbstractTagType $tag */
  102. $tag = new $tagType;
  103. $tag->render($html, $tagOpening);
  104. }
  105. if ($tagOpening) {
  106. $level++;
  107. } else {
  108. $level--;
  109. }
  110. }
  111. }
  112. return $html;
  113. $html = '';
  114. $len = mb_strlen($text);
  115. $inTag = false; // True if current position is inside a tag
  116. $inName = false; // True if current pos is inside a tag name
  117. $inStr = false; // True if current pos is inside a string
  118. /** @var Tag|null $tag */
  119. $tag = null;
  120. $openTags = array();
  121. /*
  122. * Loop over each character of the text
  123. */
  124. for ($i = 0; $i < $len; $i++) {
  125. $char = mb_substr($text, $i, 1);
  126. if ($keepLines) {
  127. if ($char == "\n") {
  128. $html .= '<br/>';
  129. }
  130. if ($char == "\r") {
  131. continue;
  132. }
  133. }
  134. if (! $escape or ($char != '<' and $char != '>')) {
  135. /*
  136. * $inTag == true means the current position is inside a tag definition
  137. * (= inside the brackets of a tag)
  138. */
  139. if ($inTag) {
  140. if ($char == '"') {
  141. if ($inStr) {
  142. $inStr = false;
  143. } else {
  144. if ($inName) {
  145. $tag->valid = false;
  146. } else {
  147. $inStr = true;
  148. }
  149. }
  150. } else {
  151. /*
  152. * This closes a tag
  153. */
  154. if ($char == ']' and ! $inStr) {
  155. $inTag = false;
  156. $inName = false;
  157. if ($tag->valid) {
  158. $code = null;
  159. if ($tag->opening) {
  160. $code = $this->generateTag($tag, $html, null, $openTags);
  161. } else {
  162. $openingTag = $this->popTag($openTags, $tag);
  163. if ($openingTag) {
  164. $code = $this->generateTag($tag, $html, $openingTag, $openTags);
  165. }
  166. }
  167. if ($code !== null and $tag->opening) {
  168. $openTags[$tag->name][] = $tag;
  169. }
  170. $html .= $code;
  171. }
  172. continue;
  173. }
  174. if ($inName and ! $inStr) {
  175. /*
  176. * This makes the current tag a closing tag
  177. */
  178. if ($char == '/') {
  179. if ($tag->name) {
  180. $tag->valid = false;
  181. } else {
  182. $tag->opening = false;
  183. }
  184. } else {
  185. /*
  186. * This means a property starts
  187. */
  188. if ($char == '=') {
  189. if ($tag->name) {
  190. $inName = false;
  191. } else {
  192. $tag->valid = false;
  193. }
  194. } else {
  195. $tag->name .= mb_strtolower($char);
  196. }
  197. }
  198. } else { // If we are not inside the name we are inside a property
  199. $tag->property .= $char;
  200. }
  201. }
  202. } else {
  203. /*
  204. * This opens a tag
  205. */
  206. if ($char == '[') {
  207. $inTag = true;
  208. $inName = true;
  209. $tag = new Tag();
  210. $tag->position = mb_strlen($html);
  211. } else {
  212. $html .= $char;
  213. }
  214. }
  215. } else {
  216. $html .= htmlspecialchars($char);
  217. }
  218. }
  219. /*
  220. * Check for tags that are not closed and close them.
  221. */
  222. foreach ($openTags as $name => $openTagsByType) {
  223. $closingTag = new Tag($name, false);
  224. foreach ($openTagsByType as $openTag) {
  225. $html .= $this->generateTag($closingTag, $html, $openTag);
  226. }
  227. }
  228. return $html;
  229. }
  230. /**
  231. * Generates and returns the HTML code of the current tag
  232. *
  233. * @param Tag $tag The current tag
  234. * @param string $html The current HTML code passed by reference - might be altered!
  235. * @param Tag|null $openingTag The opening tag that is linked to the tag (or null)
  236. * @param Tag[] $openTags Array with tags that are opned but not closed
  237. * @return string
  238. */
  239. protected function generateTag(Tag $tag, &$html, Tag $openingTag = null, array $openTags = [])
  240. {
  241. $code = null;
  242. if (in_array($tag->name, $this->ignoredTags)) {
  243. return $code;
  244. }
  245. // Custom tags:
  246. foreach ($this->customTagClosures as $name => $closure) {
  247. if ($tag->name === $name) {
  248. exit($tag->name);
  249. $code .= $closure($tag, $html, $openingTag);
  250. }
  251. }
  252. return $code;
  253. }
  254. /**
  255. * Magic method __toString()
  256. *
  257. * @return string
  258. */
  259. public function __toString()
  260. {
  261. return $this->render();
  262. }
  263. /**
  264. * Returns the last tag of a given type and removes it from the array.
  265. *
  266. * @param Tag[] $tags Array of tags
  267. * @param Tag $tag Return the last tag of the type of this tag
  268. * @return Tag|null
  269. */
  270. protected function popTag(array &$tags, $tag)
  271. {
  272. if (! isset($tags[$tag->name])) {
  273. return null;
  274. }
  275. $size = sizeof($tags[$tag->name]);
  276. if ($size === 0) {
  277. return null;
  278. } else {
  279. return array_pop($tags[$tag->name]);
  280. }
  281. }
  282. /**
  283. * Returns true if $haystack ends with $needle
  284. *
  285. * @param string $haystack
  286. * @param string $needle
  287. * @return bool
  288. */
  289. protected function endsWith($haystack, $needle)
  290. {
  291. return ($needle === '' or mb_substr($haystack, -mb_strlen($needle)) === $needle);
  292. }
  293. }