PageRenderTime 57ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/parsers/bbcode_parser.php

http://github.com/CakeDC/markup_parsers
PHP | 931 lines | 829 code | 39 blank | 63 comment | 153 complexity | 04aa18eea8782cb29a9537ec529ca84b MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright 2010, Cake Development Corporation (http://cakedc.com)
  4. *
  5. * Licensed under The MIT License
  6. * Redistributions of files must retain the above copyright notice.
  7. *
  8. * @copyright Copyright 2010, Cake Development Corporation (http://cakedc.com)
  9. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  10. */
  11. /**
  12. * Bbcode Parser
  13. *
  14. * @package markup_parsers
  15. * @subpackage markup_parsers.libs
  16. */
  17. App::import('Lib', 'MarkupParsers.ParserInterface');
  18. class BbcodeParser implements ParserInterface {
  19. /**
  20. * Filters
  21. *
  22. * @var array
  23. */
  24. public $filters = array('cake', 'code', 'email', 'image', 'extended', 'link', 'list', 'table');
  25. /**
  26. * Quote
  27. *
  28. * @var string
  29. */
  30. public $quote = 'all';
  31. /**
  32. * Quote Style
  33. *
  34. * @var string
  35. */
  36. public $quoteStyle = 'double';
  37. /**
  38. * Strict
  39. *
  40. * @var boolean
  41. */
  42. public $strict = true;
  43. /**
  44. * Page separator pattern
  45. *
  46. * @var string
  47. */
  48. public static $pageSeparator = '[Page separator]';
  49. /**
  50. * Whether or not the code must be highlighted in the __highlightCode method
  51. *
  52. * @var boolean
  53. */
  54. protected $_codeHighlightingEnabled = true;
  55. protected $__charset = 'UTF-8';
  56. protected $__text = null;
  57. protected $__parsed = null;
  58. protected $__preparsed = null;
  59. protected $__tags = array();
  60. protected $__builtTags = array();
  61. protected $__basicTags = array(
  62. 'b' => array(
  63. 'open' => 'strong',
  64. 'close' => 'strong',
  65. 'allowed' => 'all',
  66. 'attributes' => array()
  67. ),
  68. 'i' => array(
  69. 'open' => 'span style="font-style:italic;"',
  70. 'close' => 'span',
  71. 'allowed' => 'all',
  72. 'attributes' => array()
  73. ),
  74. 'u' => array(
  75. 'open' => 'span style="text-decoration:underline;"',
  76. 'close' => 'span',
  77. 'allowed' => 'all',
  78. 'attributes' => array()
  79. ),
  80. 's' => array(
  81. 'open' => 'del',
  82. 'close' => 'del',
  83. 'allowed' => 'all',
  84. 'attributes' => array()
  85. ),
  86. 'sub' => array(
  87. 'open' => 'sub',
  88. 'close' => 'sub',
  89. 'allowed' => 'all',
  90. 'attributes' => array()
  91. ),
  92. 'sup' => array(
  93. 'open' => 'sup',
  94. 'close' => 'sup',
  95. 'allowed' => 'all',
  96. 'attributes' => array()
  97. ),
  98. 'indent' => array(
  99. 'open' => 'blockquote',
  100. 'close' => 'blockquote',
  101. 'allowed' => 'all',
  102. 'attributes' => array()
  103. ),
  104. 'p' => array(
  105. 'open' => 'p',
  106. 'close' => 'p',
  107. 'allowed' => 'all',
  108. 'attributes' => array()
  109. ),
  110. );
  111. protected $__emailTags = array(
  112. 'email' => array(
  113. 'open' => 'a',
  114. 'close' => 'a',
  115. 'allowed' => 'none^img',
  116. 'attributes' => array('email' => 'href=%2$smailto:%1$s%2$s')
  117. )
  118. );
  119. protected $__imageTags = array(
  120. 'img' => array(
  121. 'open' => 'img',
  122. 'close' => '',
  123. 'allowed' => 'none',
  124. 'attributes' => array(
  125. 'img' => 'src=%2$s%1$s%2$s',
  126. 'w' => 'width=%2$s%1$d%2$s',
  127. 'h' => 'height=%2$s%1$d%2$s'
  128. )
  129. )
  130. );
  131. protected $__extendedTags = array(
  132. 'color' => array(
  133. 'open' => 'span',
  134. 'close' => 'span',
  135. 'allowed' => 'all',
  136. 'attributes' => array('color' => 'style=%2$scolor:%1$s%2$s')
  137. ),
  138. 'code' => array(
  139. 'open' => 'code',
  140. 'close' => 'code',
  141. 'allowed' => 'all',
  142. 'attributes' => array()
  143. ),
  144. 'size' => array(
  145. 'open' => 'span',
  146. 'close' => 'span',
  147. 'allowed' => 'all',
  148. 'attributes' => array('size' => 'style=%2$sfont-size:%1$spt%2$s')
  149. ),
  150. 'font' => array(
  151. 'open' => 'span',
  152. 'close' => 'span',
  153. 'allowed' => 'all',
  154. 'attributes' => array('font' => 'style=%2$sfont-family:%1$s%2$s')
  155. ),
  156. 'align' => array(
  157. 'open' => 'div',
  158. 'close' => 'div',
  159. 'allowed' => 'all',
  160. 'attributes' => array('align' => 'style=%2$stext-align:%1$s%2$s')
  161. ),
  162. 'quote' => array(
  163. 'open' => 'q',
  164. 'close' => 'q',
  165. 'allowed' => 'all',
  166. 'attributes' => array('quote' => 'cite=%2$s%1$s%2$s')
  167. ),
  168. 'h1' => array(
  169. 'open' => 'h1',
  170. 'close' => 'h1',
  171. 'allowed' => 'all',
  172. 'attributes' => array()
  173. ),
  174. 'h2' => array(
  175. 'open' => 'h2',
  176. 'close' => 'h2',
  177. 'allowed' => 'all',
  178. 'attributes' => array()
  179. ),
  180. 'h3' => array(
  181. 'open' => 'h3',
  182. 'close' => 'h3',
  183. 'allowed' => 'all',
  184. 'attributes' => array()
  185. ),
  186. 'h4' => array(
  187. 'open' => 'h4',
  188. 'close' => 'h4',
  189. 'allowed' => 'all',
  190. 'attributes' => array()
  191. ),
  192. 'h5' => array(
  193. 'open' => 'h5',
  194. 'close' => 'h5',
  195. 'allowed' => 'all',
  196. 'attributes' => array()
  197. ),
  198. 'h6' => array(
  199. 'open' => 'h6',
  200. 'close' => 'h6',
  201. 'allowed' => 'all',
  202. 'attributes' => array()
  203. )
  204. );
  205. protected $__linkTags = array(
  206. 'url' => array(
  207. 'open' => 'a',
  208. 'close' => 'a',
  209. 'allowed' => 'none^img',
  210. 'attributes' => array('url' => 'href=%2$s%1$s%2$s')
  211. )
  212. );
  213. protected $__tableTags = array(
  214. 'table' => array(
  215. 'open' => 'table',
  216. 'close' => 'table',
  217. 'allowed' => 'all',
  218. 'child' => 'none^tr',
  219. 'attributes' => array('table' => 'border=%2$s%1$d%2$s')
  220. ),
  221. 'tr' => array(
  222. 'open' => 'tr',
  223. 'close' => 'tr',
  224. 'allowed' => 'all',
  225. 'child' => 'none^td,th',
  226. 'attributes' => array()
  227. ),
  228. 'td' => array(
  229. 'open' => 'td',
  230. 'close' => 'td',
  231. 'allowed' => 'all',
  232. 'attributes' => array()
  233. ),
  234. 'th' => array(
  235. 'open' => 'th',
  236. 'close' => 'th',
  237. 'allowed' => 'all',
  238. 'attributes' => array()
  239. )
  240. );
  241. protected $__listTags = array(
  242. 'list' => array(
  243. 'open' => 'ol',
  244. 'close' => 'ol',
  245. 'allowed' => 'all',
  246. 'child' => 'none^li',
  247. 'attributes' => array('list' => 'style=%2$slist-style-type:%1$s;%2$s')
  248. ),
  249. 'ulist' => array(
  250. 'open' => 'ul',
  251. 'close' => 'ul',
  252. 'allowed' => 'all',
  253. 'child' => 'none^li',
  254. 'attributes' => array('ulist' => 'style=%2$slist-style-type:%1$s;%2$s')
  255. ),
  256. 'li' => array(
  257. 'open' => 'li',
  258. 'close' => 'li',
  259. 'allowed' => 'all',
  260. 'parent' => 'none^ulist,list',
  261. 'attributes' => array()
  262. )
  263. );
  264. function __addParaTag($string) {
  265. $str = explode("\n", $string);
  266. $count = count($str);
  267. $newString = null;
  268. for ($i = 0; $i < $count; $i++) {
  269. if (preg_match('/[\\w]/', $str[$i])) {
  270. $newString[] = '[p]' . htmlentities($str[$i], ENT_COMPAT, $this->__charset) . '[/p]';
  271. }
  272. }
  273. $string = implode(null, $newString);
  274. return $string;
  275. }
  276. function __allowed($out, $in) {
  277. if (!$out || ($this->__tags[$out]['allowed'] == 'all')) {
  278. return true;
  279. }
  280. if ($this->__tags[$out]['allowed'] == 'none') {
  281. return false;
  282. }
  283. $ar = explode('^', $this->__tags[$out]['allowed']);
  284. $tags = explode(',', $ar[1]);
  285. if ($ar[0] == 'none' && in_array($in, $tags)) {
  286. return true;
  287. }
  288. if ($ar[0] == 'all' && in_array($in, $tags)) {
  289. return false;
  290. }
  291. return false;
  292. }
  293. function __basic() {
  294. preg_match_all('/(\\[indent])([\\s\\S]*?)(\\[\/indent])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  295. if (!empty($result[0])) {
  296. $count = count($result[0]);
  297. for ($i = 0; $i < $count; $i++) {
  298. $this->__text = str_replace($result[0][$i], $this->__encode(str_replace("\r\n", " ", '[indent]' . $this->__addParaTag($result[2][$i]) . '[/indent]')), $this->__text);
  299. }
  300. }
  301. $this->__preparsed = $this->__text;
  302. }
  303. function __buildOutput() {
  304. $this->__parsed = '';
  305. foreach ($this->__builtTags as $tag) {
  306. switch ($tag['type']) {
  307. case 0:
  308. $this->__parsed .= $tag['text'];
  309. break;
  310. case 1:
  311. $this->__parsed .= '<' . $this->__tags[$tag['tag']]['open'];
  312. if ($this->quoteStyle === 'single') {
  313. $quote = "'";
  314. } else {
  315. $quote = '"';
  316. }
  317. foreach ($tag['attributes'] as $key => $value) {
  318. $value = preg_replace('#(activex|applet|about|chrome|meta|xml|blink|link|style|script|embed|object|iframe|frame|frameset|ilayer|layer|bgsound|title|base):#is', "\\1&#058;", $value);
  319. if (($this->quote == 'nothing') || (($this->quote == 'strings') && is_numeric($v))) {
  320. $this->__parsed .= ' ' . sprintf($this->__tags[$tag['tag']]['attributes'][$key], $value, '');
  321. } else {
  322. $this->__parsed .= ' ' . sprintf($this->__tags[$tag['tag']]['attributes'][$key], $value, $quote);
  323. }
  324. }
  325. if ($this->__tags[$tag['tag']]['close'] == '' && $this->strict === true) {
  326. $this->__parsed .= ' /';
  327. }
  328. $this->__parsed .= '>';
  329. break;
  330. case 2:
  331. if ($this->__tags[$tag['tag']]['close'] != '') {
  332. $this->__parsed .= '</' . $this->__tags[$tag['tag']]['close'] . '>';
  333. }
  334. break;
  335. }
  336. }
  337. }
  338. function __buildTag($string) {
  339. $tag = array('text' => $string, 'attributes' => array());
  340. if (substr($string, 1, 1) == '/') {
  341. $tag['tag'] = strtolower(substr($string, 2, strlen($string) - 3));
  342. if (!in_array($tag['tag'], array_keys($this->__tags))) {
  343. return false;
  344. } else {
  345. $tag['type'] = 2;
  346. return $tag;
  347. }
  348. } else {
  349. $tag['type'] = 1;
  350. if (strpos($string, ' ') && (strpos($string, '=') === false)) {
  351. return false;
  352. }
  353. $tags = array();
  354. if (preg_match('/\\[([a-z0-9]+)[^\\]]*\\]/i', $string, $tags) == 0) {
  355. return false;
  356. }
  357. $tag['tag'] = strtolower($tags[1]);
  358. if (!in_array($tag['tag'], array_keys($this->__tags))) {
  359. return false;
  360. }
  361. $attributes = array();
  362. preg_match_all('/[\\s\\[]([a-z0-9]+)=([^\\s\\]]+)(?=[\\s\\]])/i', $string, $attributes, PREG_SET_ORDER);
  363. foreach ($attributes as $attribute) {
  364. $name = strtolower($attribute[1]);
  365. if (in_array($name, array_keys($this->__tags[$tag['tag']]['attributes']))) {
  366. $tag['attributes'][$name] = $attribute[2];
  367. }
  368. }
  369. return $tag;
  370. }
  371. }
  372. function __buildTags() {
  373. $string = $this->__preparsed;
  374. $position = 0;
  375. $length = strlen($string);
  376. $openedTags = array();
  377. while (($position < $length)) {
  378. $tag = array();
  379. $open = strpos($string, '[', $position);
  380. if ($open === false) {
  381. $open = $length;
  382. $nextPosition = $length;
  383. }
  384. if ($open + 1 > $length) {
  385. $nextPosition = $length;
  386. } else {
  387. $nextPosition = strpos($string, '[', $open + 1);
  388. if ($nextPosition === false) {
  389. $nextPosition = $length;
  390. }
  391. }
  392. $close = strpos($string, ']', $position);
  393. if ($close === false) {
  394. $close = $length + 1;
  395. }
  396. if ($open == $position) {
  397. if (($nextPosition < $close)) {
  398. $newPosition = $nextPosition;
  399. $tag['text'] = substr($string, $position, $nextPosition - $position);
  400. $tag['type'] = 0;
  401. } else {
  402. $newPosition = $close + 1;
  403. $newTag = $this->__buildTag(substr($string, $position, $close - $position + 1));
  404. if (($newTag !== false)) {
  405. $tag = $newTag;
  406. // Remove an opening tag if there is a no matching closing tag in the string
  407. if ($newTag['type'] == 1 && !empty($this->__tags[$newTag['tag']]['close'])) {
  408. $closingTag = '[/' . $newTag['tag'] . ']';
  409. $expectedOccurences = array_key_exists($newTag['tag'], $openedTags) ? $openedTags[$newTag['tag']] : 0;
  410. if (substr_count($string, $closingTag, $position) < $expectedOccurences + 1) {
  411. $tag = array();
  412. }
  413. }
  414. }
  415. if ($tag == array()) {
  416. $tag['text'] = substr($string, $position, $close - $position + 1);
  417. $tag['type'] = 0;
  418. } elseif ($tag['type'] == 1) {
  419. if (array_key_exists($tag['tag'], $openedTags)) {
  420. $openedTags[$tag['tag']]++;
  421. } else {
  422. $openedTags[$tag['tag']] = 1;
  423. }
  424. } elseif ($tag['type'] == 2 && array_key_exists($tag['tag'], $openedTags)) {
  425. $openedTags[$tag['tag']]--;
  426. }
  427. }
  428. } else {
  429. $newPosition = $open;
  430. $tag['text'] = substr($string, $position, $open - $position);
  431. $tag['type'] = 0;
  432. }
  433. if ($tag['type'] === 0 && isset($prev) && $prev['type'] === 0) {
  434. $tag['text'] = $prev['text'] . $tag['text'];
  435. array_pop($this->__builtTags);
  436. }
  437. $this->__builtTags[] = $tag;
  438. $prev = $tag;
  439. $position = $newPosition;
  440. }
  441. }
  442. function __cake() {
  443. preg_match_all('/(\\[model])([\\s\\S]*?)(\\[\/model])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  444. if (!empty($result[0])) {
  445. $count = count($result[0]);
  446. for ($i = 0; $i < $count; $i++) {
  447. $position = strpos('<?php', $result[2][$i]);
  448. if ($position === false) {
  449. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Model Class:[/b]</h4>' . $this->__highlightCode('<?php ' . $result[2][$i] . '?>', true)), $this->__text);
  450. } else {
  451. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Model Class:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  452. }
  453. }
  454. }
  455. preg_match_all('/(\\[view])([\\s\\S]*?)(\\[\/view])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  456. if (!empty($result[0])) {
  457. $count = count($result[0]);
  458. for ($i = 0; $i < $count; $i++) {
  459. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]View Template:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  460. }
  461. }
  462. preg_match_all('/(\\[controller])([\\s\\S]*?)(\\[\/controller])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  463. if (!empty($result[0])) {
  464. $count = count($result[0]);
  465. for ($i = 0; $i < $count; $i++) {
  466. $position = strpos('<?php', $result[2][$i]);
  467. if ($position === false) {
  468. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Controller Class:[/b]</h4>' . $this->__highlightCode('<?php ' . $result[2][$i] . '?>', true)), $this->__text);
  469. } else {
  470. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Controller Class:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  471. }
  472. }
  473. }
  474. preg_match_all('/(\\[helper])([\\s\\S]*?)(\\[\/helper])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  475. if (!empty($result[0])) {
  476. $count = count($result[0]);
  477. for ($i = 0; $i < $count; $i++) {
  478. $position = strpos('<?php', $result[2][$i]);
  479. if ($position === false) {
  480. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Helper Class:[/b]</h4>' . $this->__highlightCode('<?php ' . $result[2][$i] . '?>', true)), $this->__text);
  481. } else {
  482. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Helper Class:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  483. }
  484. }
  485. }
  486. preg_match_all('/(\\[component])([\\s\\S]*?)(\\[\/component])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  487. if (!empty($result[0])) {
  488. $count = count($result[0]);
  489. for ($i = 0; $i < $count; $i++) {
  490. $position = strpos('<?php', $result[2][$i]);
  491. if ($position === false) {
  492. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Component Class:[/b]</h4>' . $this->__highlightCode('<?php ' . $result[2][$i] . '?>', true)), $this->__text);
  493. } else {
  494. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Component Class:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  495. }
  496. }
  497. }
  498. preg_match_all('/(\\[datasource])([\\s\\S]*?)(\\[\/datasource])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  499. if (!empty($result[0])) {
  500. $count = count($result[0]);
  501. for ($i = 0; $i < $count; $i++) {
  502. $position = strpos('<?php', $result[2][$i]);
  503. if ($position === false) {
  504. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]DataSource Class:[/b]</h4>' . $this->__highlightCode('<?php ' . $result[2][$i] . '?>', true)), $this->__text);
  505. } else {
  506. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]DataSource Class:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  507. }
  508. }
  509. }
  510. preg_match_all('/(\\[behavior])([\\s\\S]*?)(\\[\/behavior])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  511. if (!empty($result[0])) {
  512. $count = count($result[0]);
  513. for ($i = 0; $i < $count; $i++) {
  514. $position = strpos('<?php', $result[2][$i]);
  515. if ($position === false) {
  516. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Behavior Class:[/b]</h4>' . $this->__highlightCode('<?php ' . $result[2][$i] . '?>', true)), $this->__text);
  517. } else {
  518. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]Behavior Class:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  519. }
  520. }
  521. }
  522. $this->__preparsed = $this->__text;
  523. }
  524. function __child($out, $in) {
  525. if (!isset($this->__tags[$out]['child']) || ($this->__tags[$out]['child'] == 'all')) {
  526. return false;
  527. }
  528. $ar = explode('^', $this->__tags[$out]['child']);
  529. $tags = explode(',', $ar[1]);
  530. if ($ar[0] == 'none') {
  531. if ($in && in_array($in, $tags)) {
  532. return false;
  533. }
  534. return $this->__buildTag('[' . $tags[0] . ']');
  535. }
  536. if ($ar[0] == 'all' && $in && !in_array($in, $tags)) {
  537. return false;
  538. }
  539. return true;
  540. }
  541. function __code() {
  542. preg_match_all('/(\\[code])([\\s\\S]*?)(\\[\/code])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  543. if (!empty($result[0])) {
  544. $count = count($result[0]);
  545. for ($i = 0; $i < $count; $i++) {
  546. $this->__text = str_replace($result[0][$i], $this->__encode($this->__highlightCode($result[2][$i])), $this->__text);
  547. }
  548. }
  549. preg_match_all('/(\\[php])([\\s\\S]*?)(\\[\/php])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  550. if (!empty($result[0])) {
  551. $count = count($result[0]);
  552. for ($i = 0; $i < $count; $i++) {
  553. $position = strpos('<?php', $result[2][$i]);
  554. if ($position === false) {
  555. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]PHP Snippet:[/b]</h4>' . $this->__highlightCode('<?php ' . $result[2][$i] . '?>', true)), $this->__text);
  556. } else {
  557. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]PHP Snippet:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  558. }
  559. }
  560. }
  561. preg_match_all('/(\\[sql])([\\s\\S]*?)(\\[\/sql])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  562. if (!empty($result[0])) {
  563. $count = count($result[0]);
  564. for ($i = 0; $i < $count; $i++) {
  565. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]SQL:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  566. }
  567. }
  568. preg_match_all('/(\\[html])([\\s\\S]*?)(\\[\/html])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  569. if (!empty($result[0])) {
  570. $count = count($result[0]);
  571. for ($i = 0; $i < $count; $i++) {
  572. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]HTML:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  573. }
  574. }
  575. preg_match_all('/(\\[xml])([\\s\\S]*?)(\\[\/xml])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  576. if (!empty($result[0])) {
  577. $count = count($result[0]);
  578. for ($i = 0; $i < $count; $i++) {
  579. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]XML:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  580. }
  581. }
  582. preg_match_all('/(\\[css])([\\s\\S]*?)(\\[\/css])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  583. if (!empty($result[0])) {
  584. $count = count($result[0]);
  585. for ($i = 0; $i < $count; $i++) {
  586. $this->__text = str_replace($result[0][$i], $this->__encode('<h4>[b]CSS:[/b]</h4>' . $this->__highlightCode($result[2][$i])), $this->__text);
  587. }
  588. }
  589. $this->__preparsed = $this->__text;
  590. }
  591. function __construct() {
  592. foreach ($this->filters as $filter) {
  593. $type = '__' . $filter . 'Tags';
  594. if (isset($this->$type)) {
  595. $this->__tags = array_merge($this->__tags, $this->$type);
  596. }
  597. }
  598. $this->__tags = array_merge($this->__tags, $this->__basicTags);
  599. }
  600. function __decode($value) {
  601. preg_match_all('/(\\[encoded])(.*)(\\[\/encoded])/', $value, $result, PREG_PATTERN_ORDER);
  602. if (!empty($result[0])) {
  603. $count = count($result[0]);
  604. for ($i = 0; $i < $count; $i++) {
  605. $value = str_replace($result[0][$i], base64_decode($result[2][$i]), $value);
  606. }
  607. }
  608. $this->__preparsed = $value;
  609. }
  610. function __encode($value) {
  611. return '[encoded]' . base64_encode($value) . '[/encoded]';
  612. }
  613. function __email() {
  614. $pattern = array('/(^|\\s)([-a-z0-9_.]+@[-a-z0-9.]+\\.[a-z]{2,4})/i', '/\\[email(\\]|.*\\])(.*)\\[\/email\\]/i');
  615. $replace = array('\\1[email=\\2]\\2[/email]', '[email=\\2\\1\\2[/email]');
  616. $this->__preparsed = preg_replace($pattern, $replace, $this->__text);
  617. }
  618. function __extended() {
  619. return true;
  620. }
  621. function __highlightCode($result) {
  622. if (!$this->_codeHighlightingEnabled) {
  623. $formated = '<code>' . $result . '</code>';
  624. } else {
  625. $formated = highlight_string($result, true);
  626. }
  627. return $formated;
  628. }
  629. function __image() {
  630. $this->__preparsed = preg_replace('/\\[img(\\s?.*)\\](.*)\\[\/img\\]/', "[img=\$2\$1][/img]", $this->__text);
  631. }
  632. function __link() {
  633. $pattern = array("/(?<![\"'=\]\/])(\[[^\]]*\])?(((http|https|ftp):\/\/|www)[@-a-z0-9.]+\.[a-z]{2,4}[^\s()\[\]]*)/i", "!\[url(\]|\s.*\])(.*)\[/url\]!iU", "!\[url=((([a-z]*:(//)?)|www)[@-a-z0-9.]+)([^\s\[\]]*)\](.*)\[/url\]!i");
  634. $pp = preg_replace_callback($pattern[0], array($this, '__linkExpand'), $this->__text);
  635. $pp = preg_replace($pattern[1], "[url=\$2\$1\$2[/url]", $pp);
  636. $this->__preparsed = preg_replace_callback($pattern[2], array($this, '__linkFinish'), $pp);
  637. }
  638. function __linkExpand($matches) {
  639. $pass = strpos($matches[1], '[url');
  640. if ($pass !== false) {
  641. return $matches[0];
  642. }
  643. $punctuation = '.,;:';
  644. $trailing = '';
  645. $last = substr($matches[2], -1);
  646. while (strpos($punctuation, $last) !== false) {
  647. $trailing = $last . $trailing;
  648. $matches[2] = substr($matches[2], 0, -1);
  649. $last = substr($matches[2], -1);
  650. }
  651. $off = strpos($matches[2], ':');
  652. if ($off === false) {
  653. return $matches[1] . '[url=http://' . $matches[2] . ']' . $matches[2] . '[' . '/url' . ']' . $trailing;
  654. }
  655. $scheme = substr($matches[2], 0, $off);
  656. if (in_array($scheme, array('http', 'https', 'ftp'))) {
  657. return $matches[1] . '[url]' . $matches[2] . '[/url]' . $trailing;
  658. } else {
  659. return $matches[0];
  660. }
  661. }
  662. function __linkFinish($matches) {
  663. $urlServ = $matches[1];
  664. $path = $matches[5];
  665. $off = strpos($urlServ, ':');
  666. if ($off === false) {
  667. $urlServ = 'http://' . $urlServ;
  668. $off = strpos($urlServ, ':');
  669. }
  670. if (!$path) {
  671. $path = '/';
  672. }
  673. $protocol = substr($urlServ, 0, $off);
  674. if (in_array($protocol, array('http', 'https', 'ftp'))) {
  675. return '[url=' . $urlServ . $path . ']' . $matches[6] . '[' . '/url' . ']';
  676. } else {
  677. return $matches[6];
  678. }
  679. }
  680. function __list() {
  681. $pattern = array('/\\[\\*\\]/i', '/\\[(u?)list=(?-i:A)(\\s*[^\\]]*)\\]/i', '/\\[(u?)list=(?-i:a)(\\s*[^\\]]*)\\]/i', '/\\[(u?)list=(?-i:I)(\\s*[^\\]]*)\\]/i', '/\\[(u?)list=(?-i:i)(\\s*[^\\]]*)\\]/i', '/\\[(u?)list=(?-i:1)(\\s*[^\\]]*)\\]/i', '/\\[(u?)list([^\\]]*)\\]/i');
  682. $replace = array('[li]', '[$1list=upper-alpha$2]', '[$1list=lower-alpha$2]', '[$1list=upper-roman$2]', '[$1list=lower-roman$2]', '[$1list=decimal$2]', '[$1list$2]');
  683. $text = preg_replace($pattern, $replace, $this->__text);
  684. preg_match_all('/(\\[nested]([\\s\\S]*)\\[\/nested])/', $text, $result, PREG_PATTERN_ORDER);
  685. if (!empty($result[0])) {
  686. $count = count($result[0]);
  687. for ($i = 0; $i < $count; $i++) {
  688. $text = str_replace($result[0][$i], str_replace("\r\n", " ", $result[2][$i]), $text);
  689. }
  690. }
  691. preg_match_all('/(\\[u?list=?[\\s\\S]*?]([\\s\\S])*?\\[\/u?list])/', $text, $result, PREG_PATTERN_ORDER);
  692. if (!empty($result[0])) {
  693. $count = count($result[0]);
  694. for ($i = 0; $i < $count; $i++) {
  695. $text = str_replace($result[0][$i], str_replace("\r\n", " ", $result[1][$i]), $text);
  696. }
  697. }
  698. $this->__preparsed = $text;
  699. }
  700. function __parent($out, $in) {
  701. if (!isset($this->__tags[$in]['parent']) || ($this->__tags[$in]['parent'] == 'all')) {
  702. return false;
  703. }
  704. $ar = explode('^', $this->__tags[$in]['parent']);
  705. $tags = explode(',', $ar[1]);
  706. if ($ar[0] == 'none') {
  707. if ($out && in_array($out, $tags)) {
  708. return false;
  709. }
  710. return $this->__buildTag('[' . $tags[0] . ']');
  711. }
  712. if ($ar[0] == 'all' && $out && !in_array($out, $tags)) {
  713. return false;
  714. }
  715. return true;
  716. }
  717. function __parse() {
  718. $this->__preparsed = $this->__text;
  719. $this->__basic();
  720. foreach ($this->filters as $filter) {
  721. $this->__text = $this->__preparsed;
  722. $method = '__' . $filter;
  723. $this->$method();
  724. }
  725. $this->__stripTags();
  726. $this->__decode($this->__preparsed);
  727. $this->__buildTags();
  728. $this->__validateTags();
  729. $this->__buildOutput();
  730. }
  731. function __reset() {
  732. $this->__text = null;
  733. $this->__parsed = null;
  734. $this->__preparsed = null;
  735. $this->__builtTags = array();
  736. }
  737. function __stripTags() {
  738. if (get_magic_quotes_gpc()) {
  739. $this->__preparsed = stripslashes($this->__preparsed);
  740. }
  741. $this->__preparsed = nl2br($this->__preparsed);
  742. $this->__preparsed = str_replace(array("&amp;", "&lt;", "&gt;", "><br />", "]<br />"), array("&amp;amp;", "&amp;lt;", "&amp;gt;", ">", "]"), $this->__preparsed);
  743. $this->__preparsed = preg_replace('#(&\#*\w+)[\x00-\x20]+;#u', "$1;", $this->__preparsed);
  744. $this->__preparsed = preg_replace('#(&\#x*)([0-9A-F]+);*#iu', "$1$2;", $this->__preparsed);
  745. $this->__preparsed = preg_replace('#(<[^>]+[\x00-\x20\"\'])(on|xmlns)[^>]*>#iUu', "$1>", $this->__preparsed);
  746. $this->__preparsed = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*)[\\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iUu', '$1=$2nojavascript...', $this->__preparsed);
  747. $this->__preparsed = preg_replace('#([a-z]*)[\x00-\x20]*=([\'\"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iUu', '$1=$2novbscript...', $this->__preparsed);
  748. $this->__preparsed = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*expression[\x00-\x20]*\([^>]*>#iU', "$1>", $this->__preparsed);
  749. $this->__preparsed = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*behaviour[\x00-\x20]*\([^>]*>#iU', "$1>", $this->__preparsed);
  750. $this->__preparsed = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*>#iUu', "$1>", $this->__preparsed);
  751. $this->__preparsed = preg_replace('#</*\w+:\w[^>]*>#i', "", $this->__preparsed);
  752. do {
  753. $oldstring = $this->__preparsed;
  754. $this->__preparsed = preg_replace('#</*(applet|meta|xml|blink|link|style|script|embed|object|iframe|frame|frameset|ilayer|layer|bgsound|title|base)[^>]*>#i', "", $this->__preparsed);
  755. } while ($oldstring != $this->__preparsed);
  756. }
  757. function __table() {
  758. $this->__preparsed = preg_replace('/\\[table(\\s?.*)\\](.*)\\[\/table\\]/', "[table=\$2\$1][/table]", $this->__text);
  759. }
  760. function __validateTags() {
  761. $newTags = array();
  762. $openTags = array();
  763. foreach ($this->__builtTags as $tag) {
  764. $previous = end($newTags);
  765. if (trim($tag['text']) !== '') {
  766. switch ($tag['type']) {
  767. case 0:
  768. if (($child = $this->__child(end($openTags), 'text')) && $child !== false && $child !== true) {
  769. $newTags[] = $child;
  770. $openTags[] = $child['tag'];
  771. }
  772. if ($previous['type'] === 0) {
  773. $tag['text'] = $previous['text'] . $tag['text'];
  774. array_pop($newTags);
  775. }
  776. $newTags[] = $tag;
  777. break;
  778. case 1:
  779. if (!$this->__allowed(end($openTags), $tag['tag']) || ($parent = $this->__parent(end($openTags), $tag['tag'])) === true || ($child = $this->__child(end($openTags), $tag['tag'])) === true) {
  780. $tag['type'] = 0;
  781. if ($previous['type'] === 0) {
  782. $tag['text'] = $previous['text'] . $tag['text'];
  783. array_pop($newTags);
  784. }
  785. } else {
  786. if ($parent) {
  787. if ($tag['tag'] == end($openTags)) {
  788. $newTags[] = $this->__buildTag('[/' . $tag['tag'] . ']');
  789. array_pop($openTags);
  790. } else {
  791. $newTags[] = $parent;
  792. $openTags[] = $parent['tag'];
  793. }
  794. }
  795. if ($child) {
  796. $newTags[] = $child;
  797. $openTags[] = $child['tag'];
  798. }
  799. $openTags[] = $tag['tag'];
  800. }
  801. $newTags[] = $tag;
  802. break;
  803. case 2:
  804. if (($tag['tag'] == end($openTags) || $this->__allowed(end($openTags), $tag['tag']))) {
  805. if (in_array($tag['tag'], $openTags)) {
  806. $tmpOpenTags = array();
  807. while (end($openTags) != $tag['tag']) {
  808. $newTags[] = $this->__buildTag('[/' . end($openTags) . ']');
  809. $tmpOpenTags[] = end($openTags);
  810. array_pop($openTags);
  811. }
  812. $newTags[] = $tag;
  813. array_pop($openTags);
  814. }
  815. } else {
  816. $tag['type'] = 0;
  817. if ($previous['type'] === 0) {
  818. $tag['text'] = $previous['text'] . $tag['text'];
  819. array_pop($newTags);
  820. }
  821. $newTags[] = $tag;
  822. }
  823. break;
  824. }
  825. } else {
  826. $newTags[] = $tag;
  827. unset($tag);
  828. }
  829. }
  830. while (end($openTags)) {
  831. $newTags[] = $this->__buildTag('[/' . end($openTags) . ']');
  832. array_pop($openTags);
  833. }
  834. $this->__builtTags = $newTags;
  835. }
  836. /**
  837. * Parse method used for parsing a BBCode text and generating Html
  838. *
  839. * @param string $string text to convert
  840. * @param array $options Valid keys are:
  841. * - charset
  842. * - highlight_code: whether or not the highlight_code() PHP function must be used for the code
  843. * It generates a messy markup adn can be disabled for users that want "classic" html <code> tags
  844. * @return string Parsed string
  845. */
  846. function parse($string, $options = array()) {
  847. $defaults = array(
  848. 'charset' => 'UTF-8',
  849. 'highlight_code' => true);
  850. extract(array_merge($defaults, $options));
  851. $this->_codeHighlightingEnabled = $highlight_code;
  852. $this->__charset = $charset;
  853. $this->__reset();
  854. $this->__text = $string;
  855. $this->__parse();
  856. $data = explode(self::$pageSeparator, $this->__parsed);
  857. if (count($data) == 1) {
  858. // For compatibility reasons
  859. $data = $data[0];
  860. }
  861. return $data;
  862. }
  863. /**
  864. * Strip
  865. *
  866. * @param string
  867. * @param string
  868. */
  869. function strip($string, $charset = 'UTF-8') {
  870. $this->__charset = $charset;
  871. $this->__reset();
  872. $this->__text = $string;
  873. preg_match_all('/(\\[.*])([\\s\\S]*?)(\\[\/.*])/i', $this->__text, $result, PREG_PATTERN_ORDER);
  874. if (!empty($result[0])) {
  875. $count = count($result[0]);
  876. for ($i = 0; $i < $count; $i++) {
  877. $this->__text = str_replace($result[0][$i], str_replace("\r\n", " ", $result[2][$i]), $this->__text);
  878. }
  879. }
  880. $this->__preparsed = $this->__text;
  881. $this->__stripTags();
  882. return $this->__preparsed;
  883. }
  884. }