Просмотр файла app/classes/BBCodeParser.php

Размер файла: 9.08Kb
  1. <?php
  2. /**
  3. * Класс обработки BB-кодов
  4. * @license Code and contributions have MIT License
  5. * @link http://visavi.net
  6. * @author Alexander Grigorev <visavi.net@mail.ru>
  7. */
  8. class BBCodeParser {
  9.  
  10. /**
  11. * @var array
  12. */
  13. protected $setting;
  14.  
  15. /**
  16. * @var array
  17. */
  18. protected $parsers = [
  19. 'code' => [
  20. 'pattern' => '/\[code\](.*?)\[\/code\]/s',
  21. 'callback' => 'highlightCode'
  22. ],
  23. 'bold' => [
  24. 'pattern' => '/\[b\](.*?)\[\/b\]/s',
  25. 'replace' => '<strong>$1</strong>',
  26. ],
  27. 'italic' => [
  28. 'pattern' => '/\[i\](.*?)\[\/i\]/s',
  29. 'replace' => '<em>$1</em>',
  30. ],
  31. 'underLine' => [
  32. 'pattern' => '/\[u\](.*?)\[\/u\]/s',
  33. 'replace' => '<u>$1</u>',
  34. ],
  35. 'lineThrough' => [
  36. 'pattern' => '/\[s\](.*?)\[\/s\]/s',
  37. 'replace' => '<strike>$1</strike>',
  38. ],
  39. 'fontSize' => [
  40. 'pattern' => '/\[size\=([1-5])\](.*?)\[\/size\]/s',
  41. 'replace' => '<font size="$1">$2</font>',
  42. ],
  43. 'fontColor' => [
  44. 'pattern' => '/\[color\=(#[A-f0-9]{6}|#[A-f0-9]{3})\](.*?)\[\/color\]/s',
  45. 'replace' => '<font color="$1">$2</font>',
  46. 'iterate' => 5,
  47. ],
  48. 'center' => [
  49. 'pattern' => '/\[center\](.*?)\[\/center\]/s',
  50. 'replace' => '<div style="text-align:center;">$1</div>',
  51. ],
  52. 'quote' => [
  53. 'pattern' => '/\[quote\](.*?)\[\/quote\]/s',
  54. 'replace' => '<blockquote>$1</blockquote>',
  55. 'iterate' => 3,
  56. ],
  57. 'namedQuote' => [
  58. 'pattern' => '/\[quote\=(.*?)\](.*?)\[\/quote\]/s',
  59. 'replace' => '<blockquote>$2<small>$1</small></blockquote>',
  60. 'iterate' => 3,
  61. ],
  62. 'http' => [
  63. 'pattern' => '%\b((?<!(=|]))[\w-]+://[^\s()<>\[\]]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))%s',
  64. 'callback' => 'urlReplace',
  65. ],
  66. 'link' => [
  67. 'pattern' => '%\[url\](\b([\w-]+://[^\s()<>\[\]]+))\[/url\]%s',
  68. 'callback' => 'urlReplace',
  69. ],
  70. 'namedLink' => [
  71. 'pattern' => '%\[url\=\b([\w-]+://[^\s()<>\[\]]+)\](.*?)\[/url\]%s',
  72. 'callback' => 'urlReplace',
  73. ],
  74. 'image' => [
  75. 'pattern' => '%\[img\]\b([\w-]+://[^\s()<>\[\]]+\.(jpg|png|gif|jpeg))\[/img\]%s',
  76. 'replace' => '<img src="$1" class="img-responsive" alt="image">',
  77. ],
  78. 'orderedList' => [
  79. 'pattern' => '/\[list=1\](.*?)\[\/list\]/s',
  80. 'callback' => 'listReplace',
  81. ],
  82. 'unorderedList' => [
  83. 'pattern' => '/\[list\](.*?)\[\/list\]/s',
  84. 'callback' => 'listReplace',
  85. ],
  86. 'spoiler' => [
  87. 'pattern' => '/\[spoiler\](.*?)\[\/spoiler\]/s',
  88. 'callback' => 'spoilerText',
  89. 'iterate' => 1,
  90. ],
  91. 'shortSpoiler' => [
  92. 'pattern' => '/\[spoiler\=(.*?)\](.*?)\[\/spoiler\]/s',
  93. 'callback' => 'spoilerText',
  94. 'iterate' => 1,
  95. ],
  96. 'hide' => [
  97. 'pattern' => '/\[hide\](.*?)\[\/hide\]/s',
  98. 'callback' => 'hiddenText',
  99. ],
  100. 'youtube' => [
  101. 'pattern' => '/\[youtube\]([\w-]{11})\[\/youtube\]/s',
  102. 'replace' => '<div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item" src="//www.youtube.com/embed/$1"></iframe></div>',
  103. ],
  104. ];
  105.  
  106. /**
  107. * Конструктор
  108. * @param string $setting настройки
  109. */
  110. public function __construct($setting)
  111. {
  112. $this->setting = $setting;
  113. }
  114.  
  115. /**
  116. * Обрабатывает текст
  117. * @param string $source текст содержаший BBCode
  118. * @return string распарсенный текст
  119. */
  120. public function parse($source)
  121. {
  122. $source = nl2br($source);
  123.  
  124. foreach ($this->parsers as $parser) {
  125.  
  126. $iterate = isset($parser['iterate']) ? $parser['iterate'] : 1;
  127.  
  128. for ($i = 0; $i < $iterate; $i++) {
  129. if (isset($parser['callback'])) {
  130. $source = preg_replace_callback($parser['pattern'], [$this, $parser['callback']], $source);
  131. } else {
  132. $source = preg_replace($parser['pattern'], $parser['replace'], $source);
  133. }
  134. }
  135. }
  136. return $source;
  137. }
  138.  
  139. /**
  140. * Очищает текст от BB-кодов
  141. * @param string $source неочищенный текст
  142. * @return string очищенный текст
  143. */
  144. public function clear($source)
  145. {
  146. return $source = preg_replace('/\[(.*?)\]/', '', $source);
  147. }
  148.  
  149. /**
  150. * Обработка ссылок
  151. * @param array $match ссылка
  152. * @return string обработанная ссылка
  153. */
  154. public function urlReplace($match)
  155. {
  156. $name = (isset($match[3]) || empty($match[2])) ? $match[1] : $match[2];
  157. $target = (strpos($match[1], $this->setting['home']) === false) ? ' target="_blank" rel="nofollow"' : '';
  158.  
  159. return '<a href="'.$match[1].'"'.$target.'>'.rawurldecode($name).'</a>';
  160. }
  161.  
  162. /**
  163. * Обработка списков
  164. * @param array $match список
  165. * @return string обработанный список
  166. */
  167. public function listReplace($match)
  168. {
  169. $li = preg_split('/<br[^>]*>\R/', $match[1], -1, PREG_SPLIT_NO_EMPTY);
  170. if (empty($li)) return $match[0];
  171.  
  172. $list = [];
  173. foreach($li as $l){
  174. $list[] = '<li>'.$l.'</li>';
  175. }
  176.  
  177. $tag = strpos($match[0], '[list]') === false ? 'ol' : 'ul';
  178.  
  179. return '<'.$tag.'>'.implode($list).'</'.$tag.'>';
  180. }
  181.  
  182. /**
  183. * Подсветка кода
  184. * @param callable $match массив элементов
  185. * @return string текст с подсветкой
  186. */
  187. public function highlightCode($match)
  188. {
  189. //Чтобы bb-код и смайлы не работали внутри тега [code]
  190. $match[1] = strtr($match[1], [':' => '&#58;', '[' => '&#91;']);
  191.  
  192. return '<pre class="prettyprint linenums">'.$match[1].'</pre>';
  193. }
  194.  
  195. /**
  196. * Скрытие текста под спойлер
  197. * @param callable $match массив элементов
  198. * @return string код спойлера
  199. */
  200. public function spoilerText($match)
  201. {
  202. $title = (empty($match[1]) || !isset($match[2])) ? 'Развернуть для просмотра' : $match[1];
  203. $text = (empty($match[2])) ? !isset($match[2]) ? $match[1] : 'Текст отсутствует' : $match[2];
  204.  
  205. return '<div class="spoiler">
  206. <b class="spoiler-title">'.$title.'</b>
  207. <div class="spoiler-text" style="display: none;">'.$text.'</div>
  208. </div>';
  209. }
  210.  
  211. /**
  212. * Скрытие текста от неавторизованных пользователей
  213. * @param callable $match массив элементов
  214. * @return string скрытый код
  215. */
  216. public function hiddenText($match)
  217. {
  218. if (empty($match[1])) $match[1] = 'Текст отсутствует';
  219.  
  220. return '<div class="hiding">
  221. <span class="strong">Скрытый контент:</span> '.(is_user() ? $match[1] : 'Для просмотра необходимо авторизоваться!').
  222. '</div>';
  223. }
  224.  
  225. /**
  226. * Обработка смайлов
  227. * @param $source
  228. * @return string Обработанный текст
  229. * @internal param string $text Необработанный текст
  230. */
  231. public function parseSmiles($source)
  232. {
  233. static $list_smiles;
  234.  
  235. if (empty($list_smiles)) {
  236. if (! file_exists(STORAGE.'/temp/smiles.dat')) {
  237.  
  238. $smiles = DBM::run()->query("SELECT code, name FROM smiles ORDER BY CHAR_LENGTH(code) DESC;");
  239. file_put_contents(STORAGE.'/temp/smiles.dat', serialize($smiles));
  240. }
  241.  
  242. $list_smiles = unserialize(file_get_contents(STORAGE.'/temp/smiles.dat'));
  243. }
  244.  
  245. $count = 0;
  246. foreach($list_smiles as $smile) {
  247. $source = preg_replace('|'.preg_quote($smile['code']).'|', '<img src="/uploads/smiles/'.$smile['name'].'" alt="'.$smile['code'].'" /> ', $source, $this->setting['resmiles'] - $count, $cnt);
  248. $count += $cnt;
  249. if ($count >= $this->setting['resmiles']) break;
  250. }
  251.  
  252. return $source;
  253. }
  254.  
  255. /**
  256. * Добавляет или переопределяет парсер.
  257. * @param string $name Parser name
  258. * @param string $pattern Pattern
  259. * @param string $replace Replace pattern
  260. * @return void
  261. */
  262. public function setParser($name, $pattern, $replace)
  263. {
  264. $this->parsers[$name] = [
  265. 'pattern' => $pattern,
  266. 'replace' => $replace
  267. ];
  268. }
  269.  
  270. /**
  271. * Устанавливает список доступных парсеров
  272. * @param mixed $only parsers
  273. * @return object BBCodeParser object
  274. */
  275. public function only($only = null)
  276. {
  277. $only = (is_array($only)) ? $only : func_get_args();
  278. $this->parsers = $this->arrayOnly($only);
  279. return $this;
  280. }
  281.  
  282. /**
  283. * Исключает парсеры из набора
  284. * @param mixed $except parsers
  285. * @return object BBCodeParser object
  286. */
  287. public function except($except = null)
  288. {
  289. $except = (is_array($except)) ? $except : func_get_args();
  290. $this->parsers = $this->arrayExcept($except);
  291. return $this;
  292. }
  293.  
  294. /**
  295. * Возвращает список всех парсеров
  296. * @return array array of parsers
  297. */
  298. public function getParsers()
  299. {
  300. return $this->parsers;
  301. }
  302.  
  303. /**
  304. * Filters all parsers that you don´t want
  305. * @param array $only chosen parsers
  306. * @return array parsers
  307. */
  308. private function arrayOnly(array $only)
  309. {
  310. return array_intersect_key($this->parsers, array_flip($only));
  311. }
  312.  
  313. /**
  314. * Removes the parsers that you don´t want
  315. * @param array $excepts
  316. * @return array parsers
  317. * @internal param array $except parsers to exclude
  318. */
  319. private function arrayExcept(array $excepts)
  320. {
  321. return array_diff_key($this->parsers, array_flip($excepts));
  322. }
  323. }