Oppaitime's version of Gazelle
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

text.class.php 39KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. <?
  2. class Text {
  3. /**
  4. * Array of valid tags; tag => max number of attributes
  5. * @var array $ValidTags
  6. */
  7. private static $ValidTags = array('b'=>0, 'u'=>0, 'i'=>0, 's'=>0, '*'=>0, '#'=>0, 'ch'=>0, 'uch'=>0, 'artist'=>0, 'user'=>0, 'n'=>0, 'inlineurl'=>0, 'inlinesize'=>1, 'headline'=>1, 'align'=>1, 'color'=>1, 'colour'=>1, 'size'=>1, 'url'=>1, 'img'=>1, 'quote'=>1, 'pre'=>1, 'code'=>1, 'tex'=>0, 'hide'=>1, 'spoiler' => 1, 'plain'=>0, 'important'=>0, 'torrent'=>0, 'rule'=>0, 'mature'=>1, 'embed'=>0,
  8. );
  9. /**
  10. * Array of smilies; code => image file in STATIC_SERVER/common/smileys
  11. * @var array $Smileys
  12. */
  13. private static $Smileys = array(
  14. ':angry:' => 'angry.gif',
  15. ':-D' => 'biggrin.gif',
  16. ':D' => 'biggrin.gif',
  17. ':|' => 'blank.gif',
  18. ':-|' => 'blank.gif',
  19. ':blush:' => 'blush.gif',
  20. ':cool:' => 'cool.gif',
  21. ':&#39;(' => 'crying.gif',
  22. ':crying:' => 'crying.gif',
  23. '&gt;.&gt;' => 'eyesright.gif',
  24. ':frown:' => 'frown.gif',
  25. '&lt;3' => 'heart.gif',
  26. ':unsure:' => 'hmm.gif',
  27. //':\\' => 'hmm.gif',
  28. ':whatlove:' => 'ilu.gif',
  29. ':lol:' => 'laughing.gif',
  30. ':loveflac:' => 'loveflac.gif',
  31. ':flaclove:' => 'loveflac.gif',
  32. ':ninja:' => 'ninja.gif',
  33. ':no:' => 'no.gif',
  34. ':nod:' => 'nod.gif',
  35. ':ohno:' => 'ohnoes.gif',
  36. ':ohnoes:' => 'ohnoes.gif',
  37. ':omg:' => 'omg.gif',
  38. ':o' => 'ohshit.gif',
  39. ':O' => 'ohshit.gif',
  40. ':paddle:' => 'paddle.gif',
  41. ':(' => 'sad.gif',
  42. ':-(' => 'sad.gif',
  43. ':shifty:' => 'shifty.gif',
  44. ':sick:' => 'sick.gif',
  45. ':)' => 'smile.gif',
  46. ':-)' => 'smile.gif',
  47. ':sorry:' => 'sorry.gif',
  48. ':thanks:' => 'thanks.gif',
  49. ':P' => 'tongue.gif',
  50. ':p' => 'tongue.gif',
  51. ':-P' => 'tongue.gif',
  52. ':-p' => 'tongue.gif',
  53. ':wave:' => 'wave.gif',
  54. ';-)' => 'wink.gif',
  55. ':wink:' => 'wink.gif',
  56. ':creepy:' => 'creepy.gif',
  57. ':worried:' => 'worried.gif',
  58. ':wtf:' => 'wtf.gif',
  59. ':wub:' => 'wub.gif',
  60. ':ban:' => 'onion_ban.gif',
  61. ':oy:' => 'onion_oy.gif',
  62. ':laugh:' => 'onion_laugh.gif',
  63. ':snicker:' => 'onion_snicker.gif',
  64. ':barf:' => 'onion_barf.gif',
  65. ':dies:' => 'onion_dies.gif',
  66. ':shiver:' => 'onion_shiver.gif',
  67. ':frozen:' => 'onion_frozen.gif',
  68. ':relax:' => 'onion_relax.gif',
  69. ':hurry:' => 'onion_hurry.gif',
  70. ':whistle:' => 'onion_whistle.gif',
  71. ':negligent:' => 'onion_negligent.gif',
  72. ':nice:' => 'onion_nice.gif',
  73. ':giveup:' => 'onion_giveup.gif',
  74. ':hi:' => 'onion_hi.gif',
  75. ':bye:' => 'onion_bye.gif',
  76. ':dizzy:' => 'onion_dizzy.gif',
  77. ':evil:' => 'onion_evil.gif',
  78. ':distressed:' => 'onion_distressed.gif',
  79. ':dunno:' => 'onion_dunno.gif',
  80. ':sick:' => 'onion_sick.gif',
  81. ':why:' => 'onion_why.gif',
  82. ':full:' => 'onion_full.gif',
  83. ':zzz:' => 'onion_zzz.gif',
  84. ':whisper:' => 'onion_whisper.gif',
  85. ':love:' => 'onion_love.gif',
  86. ':nuts:' => 'onion_nuts.gif',
  87. ':please:' => 'onion_please.gif',
  88. ':unforgiven:' => 'onion_unforgiven.gif',
  89. ':miserable:' => 'onion_miserable.gif',
  90. ':donotwant:' => 'onion_donotwant.gif',
  91. ':furious:' => 'onion_furious.gif',
  92. ':bow:' => 'onion_bow.gif',
  93. ':perfect:' => 'onion_perfect.gif',
  94. ':puzzled:' => 'onion_puzzled.gif',
  95. ':tired:' => 'onion_tired.gif',
  96. ':angry:' => 'onion_angry.gif',
  97. ':inlove:' => 'onion_inlove.gif',
  98. ':fever:' => 'onion_fever.gif',
  99. ':warn:' => 'onion_warn.gif',
  100. ':shy:' => 'onion_shy.gif',
  101. ':flattered:' => 'onion_flattered.gif',
  102. ':attention:' => 'onion_attention.gif',
  103. ':payup:' => 'onion_payup.gif',
  104. ':exhausted:' => 'onion_exhausted.gif',
  105. ':nosepick:' => 'onion_nosepick.gif',
  106. ':blush:' => 'onion_blush.gif',
  107. ':pissed:' => 'onion_pissed.gif',
  108. ':omg:' => 'onion_omg.gif',
  109. ':xd:' => 'onion_xd.gif',
  110. ':petrified:' => 'onion_petrified.gif',
  111. ':innocence:' => 'onion_innocence.gif',
  112. ':tantrum:' => 'onion_tantrum.gif',
  113. ':cry:' => 'onion_cry.gif',
  114. ':hero:' => 'onion_hero.gif',
  115. ':abandoned:' => 'onion_abandoned.gif',
  116. ':notagain:' => 'onion_notagain.gif',
  117. ':wait:' => 'onion_wait.gif',
  118. ':runaway:' => 'onion_runaway.gif',
  119. ':surprised:' => 'onion_surprised.gif',
  120. ':moe:' => 'onion_moe.gif',
  121. );
  122. /**
  123. * Processed version of the $Smileys array, see {@link smileys}
  124. * @var array $ProcessedSmileys
  125. */
  126. private static $ProcessedSmileys = array();
  127. /**
  128. * Whether or not to turn images into URLs (used inside [quote] tags).
  129. * This is an integer reflecting the number of levels we're doing that
  130. * transition, i.e. images will only be displayed as images if $NoImg <= 0.
  131. * By setting this variable to a negative number you can delay the
  132. * transition to a deeper level of quotes.
  133. * @var int $NoImg
  134. */
  135. private static $NoImg = 0;
  136. /**
  137. * Internal counter for the level of recursion in to_html
  138. * @var int $Levels
  139. */
  140. private static $Levels = 0;
  141. /**
  142. * The maximum amount of nesting allowed (exclusive)
  143. * In reality n-1 nests are shown.
  144. * @var int $MaximumNests
  145. */
  146. private static $MaximumNests = 10;
  147. /**
  148. * Used to detect and disable parsing (e.g. TOC) within quotes
  149. * @var int $InQuotes
  150. */
  151. private static $InQuotes = 0;
  152. /**
  153. * Used to [hide] quote trains starting with the specified depth (inclusive)
  154. * @var int $NestsBeforeHide
  155. *
  156. * This defaulted to 5 but was raised to 10 to effectively "disable" it until
  157. * an optimal number of nested [quote] tags is chosen. The variable $MaximumNests
  158. * effectively overrides this variable, if $MaximumNests is less than the value
  159. * of $NestsBeforeHide.
  160. */
  161. private static $NestsBeforeHide = 10;
  162. /**
  163. * Array of headlines for Table Of Contents (TOC)
  164. * @var array $HeadLines
  165. */
  166. private static $Headlines;
  167. /**
  168. * Counter for making headline URLs unique
  169. * @var int $HeadLines
  170. */
  171. private static $HeadlineID = 0;
  172. /**
  173. * Depth
  174. * @var array $HeadlineLevels
  175. */
  176. private static $HeadlineLevels = array('1', '2', '3', '4');
  177. /**
  178. * TOC enabler
  179. * @var bool $TOC
  180. */
  181. public static $TOC = false;
  182. /**
  183. * Output BBCode as XHTML
  184. * @param string $Str BBCode text
  185. * @param bool $OutputTOC Ouput TOC near (above) text
  186. * @param int $Min See {@link parse_toc}
  187. * @return string
  188. */
  189. public static function full_format($Str, $OutputTOC = true, $Min = 3) {
  190. global $Debug;
  191. $Debug->set_flag('BBCode start');
  192. $Str = display_str($Str);
  193. self::$Headlines = array();
  194. $Str = preg_replace('/\[\\[(ch|uch)]\]/i', '', $Str);
  195. $Str = preg_replace('/\[ch\]/i', '[ch][/ch]', $Str);
  196. $Str = preg_replace('/\[uch\]/i', '[uch][/uch]', $Str);
  197. //Inline links
  198. $URLPrefix = '(\[url\]|\[url\=|\[img\=|\[img\])';
  199. $Str = preg_replace('/'.$URLPrefix.'\s+/i', '$1', $Str);
  200. $Str = preg_replace('/(?<!'.$URLPrefix.')http(s)?:\/\//i', '$1[inlineurl]http$2://', $Str);
  201. $Str = preg_replace('/\[embed\]\[inlineurl\]/', '[embed]', $Str);
  202. // For anonym.to and archive.org links, remove any [inlineurl] in the middle of the link
  203. $callback = create_function('$matches', 'return str_replace("[inlineurl]", "", $matches[0]);');
  204. $Str = preg_replace_callback('/(?<=\[inlineurl\]|'.$URLPrefix.')(\S*\[inlineurl\]\S*)/m', $callback, $Str);
  205. if (self::$TOC) {
  206. $Str = preg_replace('/(\={5})([^=].*)\1/i', '[headline=4]$2[/headline]', $Str);
  207. $Str = preg_replace('/(\={4})([^=].*)\1/i', '[headline=3]$2[/headline]', $Str);
  208. $Str = preg_replace('/(\={3})([^=].*)\1/i', '[headline=2]$2[/headline]', $Str);
  209. $Str = preg_replace('/(\={2})([^=].*)\1/i', '[headline=1]$2[/headline]', $Str);
  210. } else {
  211. $Str = preg_replace('/(\={4})([^=].*)\1/i', '[inlinesize=3]$2[/inlinesize]', $Str);
  212. $Str = preg_replace('/(\={3})([^=].*)\1/i', '[inlinesize=5]$2[/inlinesize]', $Str);
  213. $Str = preg_replace('/(\={2})([^=].*)\1/i', '[inlinesize=7]$2[/inlinesize]', $Str);
  214. }
  215. $HTML = nl2br(self::to_html(self::parse($Str)));
  216. if (self::$TOC && $OutputTOC) {
  217. $HTML = self::parse_toc($Min) . $HTML;
  218. }
  219. $Debug->set_flag('BBCode end');
  220. return $HTML;
  221. }
  222. public static function strip_bbcode($Str) {
  223. $Str = display_str($Str);
  224. //Inline links
  225. $Str = preg_replace('/(?<!(\[url\]|\[url\=|\[img\=|\[img\]))http(s)?:\/\//i', '$1[inlineurl]http$2://', $Str);
  226. return nl2br(self::raw_text(self::parse($Str)));
  227. }
  228. private static function valid_url($Str, $Extension = '', $Inline = false) {
  229. $Regex = '/^';
  230. $Regex .= '(https?|ftps?|irc):\/\/'; // protocol
  231. $Regex .= '(\w+(:\w+)?@)?'; // user:pass@
  232. $Regex .= '(';
  233. $Regex .= '(([0-9]{1,3}\.){3}[0-9]{1,3})|'; // IP or...
  234. $Regex .= '(([a-z0-9\-\_]+\.)+\w{2,6})'; // sub.sub.sub.host.com
  235. $Regex .= ')';
  236. $Regex .= '(:[0-9]{1,5})?'; // port
  237. $Regex .= '\/?'; // slash?
  238. $Regex .= '(\/?[0-9a-z\-_.,\?&=@~%\/:;()+|!#]+)*'; // /file
  239. if (!empty($Extension)) {
  240. $Regex.=$Extension;
  241. }
  242. // query string
  243. if ($Inline) {
  244. $Regex .= '(\?([0-9a-z\-_.,%\/\@~&=:;()+*\^$!#|?]|\[\d*\])*)?';
  245. } else {
  246. $Regex .= '(\?[0-9a-z\-_.,%\/\@[\]~&=:;()+*\^$!#|?]*)?';
  247. }
  248. $Regex .= '(#[a-z0-9\-_.,%\/\@[\]~&=:;()+*\^$!]*)?'; // #anchor
  249. $Regex .= '$/i';
  250. return preg_match($Regex, $Str, $Matches);
  251. }
  252. public static function local_url($Str) {
  253. $URLInfo = parse_url($Str);
  254. if (!$URLInfo) {
  255. return false;
  256. }
  257. $Host = $URLInfo['host'];
  258. // If for some reason your site does not require subdomains or contains a directory in the SITE_DOMAIN, revert to the line below.
  259. if ($Host == SITE_DOMAIN || $Host == 'www.'.SITE_DOMAIN) {
  260. if (empty($URLInfo['port']) && preg_match('/(\S+\.)*'.SITE_DOMAIN.'/', $Host)) {
  261. $URL = '';
  262. if (!empty($URLInfo['path'])) {
  263. $URL .= ltrim($URLInfo['path'], '/'); // Things break if the path starts with '//'
  264. }
  265. if (!empty($URLInfo['query'])) {
  266. $URL .= "?$URLInfo[query]";
  267. }
  268. if (!empty($URLInfo['fragment'])) {
  269. $URL .= "#$URLInfo[fragment]";
  270. }
  271. return $URL ? "/$URL" : false;
  272. } else {
  273. return false;
  274. }
  275. }
  276. }
  277. /*
  278. How parsing works
  279. Parsing takes $Str, breaks it into blocks, and builds it into $Array.
  280. Blocks start at the beginning of $Str, when the parser encounters a [, and after a tag has been closed.
  281. This is all done in a loop.
  282. EXPLANATION OF PARSER LOGIC
  283. 1) Find the next tag (regex)
  284. 1a) If there aren't any tags left, write everything remaining to a block and return (done parsing)
  285. 1b) If the next tag isn't where the pointer is, write everything up to there to a text block.
  286. 2) See if it's a [[wiki-link]] or an ordinary tag, and get the tag name
  287. 3) If it's not a wiki link:
  288. 3a) check it against the self::$ValidTags array to see if it's actually a tag and not [bullshit]
  289. If it's [not a tag], just leave it as plaintext and move on
  290. 3b) Get the attribute, if it exists [name=attribute]
  291. 4) Move the pointer past the end of the tag
  292. 5) Find out where the tag closes (beginning of [/tag])
  293. 5a) Different for different types of tag. Some tags don't close, others are weird like [*]
  294. 5b) If it's a normal tag, it may have versions of itself nested inside - e.g.:
  295. [quote=bob]*
  296. [quote=joe]I am a redneck!**[/quote]
  297. Me too!
  298. ***[/quote]
  299. If we're at the position *, the first [/quote] tag is denoted by **.
  300. However, our quote tag doesn't actually close there. We must perform
  301. a loop which checks the number of opening [quote] tags, and make sure
  302. they are all closed before we find our final [/quote] tag (***).
  303. 5c) Get the contents between [open] and [/close] and call it the block.
  304. In many cases, this will be parsed itself later on, in a new parse() call.
  305. 5d) Move the pointer past the end of the [/close] tag.
  306. 6) Depending on what type of tag we're dealing with, create an array with the attribute and block.
  307. In many cases, the block may be parsed here itself. Stick them in the $Array.
  308. 7) Increment array pointer, start again (past the end of the [/close] tag)
  309. */
  310. private static function parse($Str) {
  311. $i = 0; // Pointer to keep track of where we are in $Str
  312. $Len = strlen($Str);
  313. $Array = array();
  314. $ArrayPos = 0;
  315. $StrLC = strtolower($Str);
  316. while ($i < $Len) {
  317. $Block = '';
  318. // 1) Find the next tag (regex)
  319. // [name(=attribute)?]|[[wiki-link]]
  320. $IsTag = preg_match("/((\[[a-zA-Z*#]+)(=(?:[^\n'\"\[\]]|\[\d*\])+)?\])|(\[\[[^\n\"'\[\]]+\]\])/", $Str, $Tag, PREG_OFFSET_CAPTURE, $i);
  321. // 1a) If there aren't any tags left, write everything remaining to a block
  322. if (!$IsTag) {
  323. // No more tags
  324. $Array[$ArrayPos] = substr($Str, $i);
  325. break;
  326. }
  327. // 1b) If the next tag isn't where the pointer is, write everything up to there to a text block.
  328. $TagPos = $Tag[0][1];
  329. if ($TagPos > $i) {
  330. $Array[$ArrayPos] = substr($Str, $i, $TagPos - $i);
  331. ++$ArrayPos;
  332. $i = $TagPos;
  333. }
  334. // 2) See if it's a [[wiki-link]] or an ordinary tag, and get the tag name
  335. if (!empty($Tag[4][0])) { // Wiki-link
  336. $WikiLink = true;
  337. $TagName = substr($Tag[4][0], 2, -2);
  338. $Attrib = '';
  339. } else { // 3) If it's not a wiki link:
  340. $WikiLink = false;
  341. $TagName = strtolower(substr($Tag[2][0], 1));
  342. //3a) check it against the self::$ValidTags array to see if it's actually a tag and not [bullshit]
  343. if (!isset(self::$ValidTags[$TagName])) {
  344. $Array[$ArrayPos] = substr($Str, $i, ($TagPos - $i) + strlen($Tag[0][0]));
  345. $i = $TagPos + strlen($Tag[0][0]);
  346. ++$ArrayPos;
  347. continue;
  348. }
  349. $MaxAttribs = self::$ValidTags[$TagName];
  350. // 3b) Get the attribute, if it exists [name=attribute]
  351. if (!empty($Tag[3][0])) {
  352. $Attrib = substr($Tag[3][0], 1);
  353. } else {
  354. $Attrib = '';
  355. }
  356. }
  357. // 4) Move the pointer past the end of the tag
  358. $i = $TagPos + strlen($Tag[0][0]);
  359. // 5) Find out where the tag closes (beginning of [/tag])
  360. // Unfortunately, BBCode doesn't have nice standards like XHTML
  361. // [*], [img=...], and http:// follow different formats
  362. // Thus, we have to handle these before we handle the majority of tags
  363. //5a) Different for different types of tag. Some tags don't close, others are weird like [*]
  364. if ($TagName == 'img' && !empty($Tag[3][0])) { //[img=...]
  365. $Block = ''; // Nothing inside this tag
  366. // Don't need to touch $i
  367. } elseif ($TagName == 'inlineurl') { // We did a big replace early on to turn http:// into [inlineurl]http://
  368. // Let's say the block can stop at a newline or a space
  369. $CloseTag = strcspn($Str, " \n\r", $i);
  370. if ($CloseTag === false) { // block finishes with URL
  371. $CloseTag = $Len;
  372. }
  373. if (preg_match('/[!,.?:]+$/',substr($Str, $i, $CloseTag), $Match)) {
  374. $CloseTag -= strlen($Match[0]);
  375. }
  376. $URL = substr($Str, $i, $CloseTag);
  377. if (substr($URL, -1) == ')' && substr_count($URL, '(') < substr_count($URL, ')')) {
  378. $CloseTag--;
  379. $URL = substr($URL, 0, -1);
  380. }
  381. $Block = $URL; // Get the URL
  382. // strcspn returns the number of characters after the offset $i, not after the beginning of the string
  383. // Therefore, we use += instead of the = everywhere else
  384. $i += $CloseTag; // 5d) Move the pointer past the end of the [/close] tag.
  385. } elseif ($WikiLink == true || $TagName == 'n') {
  386. // Don't need to do anything - empty tag with no closing
  387. } elseif ($TagName === '*' || $TagName === '#') {
  388. // We're in a list. Find where it ends
  389. $NewLine = $i;
  390. do { // Look for \n[*]
  391. $NewLine = strpos($Str, "\n", $NewLine + 1);
  392. } while ($NewLine !== false && substr($Str, $NewLine + 1, 3) == "[$TagName]");
  393. $CloseTag = $NewLine;
  394. if ($CloseTag === false) { // block finishes with list
  395. $CloseTag = $Len;
  396. }
  397. $Block = substr($Str, $i, $CloseTag - $i); // Get the list
  398. $i = $CloseTag; // 5d) Move the pointer past the end of the [/close] tag.
  399. } else {
  400. //5b) If it's a normal tag, it may have versions of itself nested inside
  401. $CloseTag = $i - 1;
  402. $InTagPos = $i - 1;
  403. $NumInOpens = 0;
  404. $NumInCloses = -1;
  405. $InOpenRegex = '/\[('.$TagName.')';
  406. if ($MaxAttribs > 0) {
  407. $InOpenRegex .= "(=[^\n'\"\[\]]+)?";
  408. }
  409. $InOpenRegex .= '\]/i';
  410. // Every time we find an internal open tag of the same type, search for the next close tag
  411. // (as the first close tag won't do - it's been opened again)
  412. do {
  413. $CloseTag = strpos($StrLC, "[/$TagName]", $CloseTag + 1);
  414. if ($CloseTag === false) {
  415. $CloseTag = $Len;
  416. break;
  417. } else {
  418. $NumInCloses++; // Majority of cases
  419. }
  420. // Is there another open tag inside this one?
  421. $OpenTag = preg_match($InOpenRegex, $Str, $InTag, PREG_OFFSET_CAPTURE, $InTagPos + 1);
  422. if (!$OpenTag || $InTag[0][1] > $CloseTag) {
  423. break;
  424. } else {
  425. $InTagPos = $InTag[0][1];
  426. $NumInOpens++;
  427. }
  428. } while ($NumInOpens > $NumInCloses);
  429. // Find the internal block inside the tag
  430. $Block = substr($Str, $i, $CloseTag - $i); // 5c) Get the contents between [open] and [/close] and call it the block.
  431. $i = $CloseTag + strlen($TagName) + 3; // 5d) Move the pointer past the end of the [/close] tag.
  432. }
  433. // 6) Depending on what type of tag we're dealing with, create an array with the attribute and block.
  434. switch ($TagName) {
  435. case 'inlineurl':
  436. $Array[$ArrayPos] = array('Type'=>'inlineurl', 'Attr'=>$Block, 'Val'=>'');
  437. break;
  438. case 'url':
  439. $Array[$ArrayPos] = array('Type'=>'img', 'Attr'=>$Attrib, 'Val'=>$Block);
  440. if (empty($Attrib)) { // [url]http://...[/url] - always set URL to attribute
  441. $Array[$ArrayPos] = array('Type'=>'url', 'Attr'=>$Block, 'Val'=>'');
  442. } else {
  443. $Array[$ArrayPos] = array('Type'=>'url', 'Attr'=>$Attrib, 'Val'=>self::parse($Block));
  444. }
  445. break;
  446. case 'quote':
  447. $Array[$ArrayPos] = array('Type'=>'quote', 'Attr'=>self::parse($Attrib), 'Val'=>self::parse($Block));
  448. break;
  449. case 'img':
  450. case 'image':
  451. if (empty($Block)) {
  452. $Block = $Attrib;
  453. }
  454. $Array[$ArrayPos] = array('Type'=>'img', 'Val'=>$Block);
  455. break;
  456. case 'aud':
  457. case 'mp3':
  458. case 'audio':
  459. if (empty($Block)) {
  460. $Block = $Attrib;
  461. }
  462. $Array[$ArrayPos] = array('Type'=>'aud', 'Val'=>$Block);
  463. break;
  464. case 'user':
  465. $Array[$ArrayPos] = array('Type'=>'user', 'Val'=>$Block);
  466. break;
  467. case 'artist':
  468. $Array[$ArrayPos] = array('Type'=>'artist', 'Val'=>$Block);
  469. break;
  470. case 'torrent':
  471. $Array[$ArrayPos] = array('Type'=>'torrent', 'Val'=>$Block);
  472. break;
  473. case 'tex':
  474. $Array[$ArrayPos] = array('Type'=>'tex', 'Val'=>$Block);
  475. break;
  476. case 'rule':
  477. $Array[$ArrayPos] = array('Type'=>'rule', 'Val'=>$Block);
  478. break;
  479. case 'pre':
  480. case 'code':
  481. case 'plain':
  482. $Block = strtr($Block, array('[inlineurl]' => ''));
  483. $Callback = function ($matches) {
  484. $n = $matches[2];
  485. $text = '';
  486. if ($n < 5 && $n > 0) {
  487. $e = str_repeat('=', $matches[2] + 1);
  488. $text = $e . $matches[3] . $e;
  489. }
  490. return $text;
  491. };
  492. $Block = preg_replace_callback('/\[(headline)\=(\d)\](.*?)\[\/\1\]/i', $Callback, $Block);
  493. $Block = preg_replace('/\[inlinesize\=3\](.*?)\[\/inlinesize\]/i', '====$1====', $Block);
  494. $Block = preg_replace('/\[inlinesize\=5\](.*?)\[\/inlinesize\]/i', '===$1===', $Block);
  495. $Block = preg_replace('/\[inlinesize\=7\](.*?)\[\/inlinesize\]/i', '==$1==', $Block);
  496. $Array[$ArrayPos] = array('Type'=>$TagName, 'Val'=>$Block);
  497. break;
  498. case 'spoiler':
  499. case 'hide':
  500. $Array[$ArrayPos] = array('Type'=>'hide', 'Attr'=>$Attrib, 'Val'=>self::parse($Block));
  501. break;
  502. case 'mature':
  503. $Array[$ArrayPos] = array('Type'=>'mature', 'Attr'=>$Attrib, 'Val'=>self::parse($Block));
  504. break;
  505. case 'embed':
  506. $Array[$ArrayPos] = array('Type'=>'embed', 'Val'=>$Block);
  507. break;
  508. case '#':
  509. case '*':
  510. $Array[$ArrayPos] = array('Type'=>'list');
  511. $Array[$ArrayPos]['Val'] = explode("[$TagName]", $Block);
  512. $Array[$ArrayPos]['ListType'] = $TagName === '*' ? 'ul' : 'ol';
  513. $Array[$ArrayPos]['Tag'] = $TagName;
  514. foreach ($Array[$ArrayPos]['Val'] as $Key=>$Val) {
  515. $Array[$ArrayPos]['Val'][$Key] = self::parse(trim($Val));
  516. }
  517. break;
  518. case 'n':
  519. $ArrayPos--;
  520. break; // n serves only to disrupt bbcode (backwards compatibility - use [pre])
  521. default:
  522. if ($WikiLink == true) {
  523. $Array[$ArrayPos] = array('Type'=>'wiki','Val'=>$TagName);
  524. } else {
  525. // Basic tags, like [b] or [size=5]
  526. $Array[$ArrayPos] = array('Type'=>$TagName, 'Val'=>self::parse($Block));
  527. if (!empty($Attrib) && $MaxAttribs > 0) {
  528. $Array[$ArrayPos]['Attr'] = strtolower($Attrib);
  529. }
  530. }
  531. }
  532. $ArrayPos++; // 7) Increment array pointer, start again (past the end of the [/close] tag)
  533. }
  534. return $Array;
  535. }
  536. /**
  537. * Generates a navigation list for TOC
  538. * @param int $Min Minimum number of headlines required for a TOC list
  539. */
  540. public static function parse_toc ($Min = 3) {
  541. if (count(self::$Headlines) > $Min) {
  542. $list = '<ol class="navigation_list">';
  543. $i = 0;
  544. $level = 0;
  545. $off = 0;
  546. foreach (self::$Headlines as $t) {
  547. $n = (int)$t[0];
  548. if ($i === 0 && $n > 1) {
  549. $off = $n - $level;
  550. }
  551. self::headline_level($n, $level, $list, $i, $off);
  552. $list .= sprintf('<li><a href="#%2$s">%1$s</a>', $t[1], $t[2]);
  553. $level = $t[0];
  554. $off = 0;
  555. $i++;
  556. }
  557. $list .= str_repeat('</li></ol>', $level);
  558. $list .= "\n\n";
  559. return $list;
  560. }
  561. }
  562. /**
  563. * Generates the list items and proper depth
  564. *
  565. * First check if the item should be higher than the current level
  566. * - Close the list and previous lists
  567. *
  568. * Then check if the item should go lower than the current level
  569. * - If the list doesn't open on level one, use the Offset
  570. * - Open appropriate sub lists
  571. *
  572. * Otherwise the item is on the same as level as the previous item
  573. *
  574. * @param int $ItemLevel Current item level
  575. * @param int $Level Current list level
  576. * @param str $List reference to an XHTML string
  577. * @param int $i Iterator digit
  578. * @param int $Offset If the list doesn't start at level 1
  579. */
  580. private static function headline_level (&$ItemLevel, &$Level, &$List, $i, &$Offset) {
  581. if ($ItemLevel < $Level) {
  582. $diff = $Level - $ItemLevel;
  583. $List .= '</li>' . str_repeat('</ol></li>', $diff);
  584. } elseif ($ItemLevel > $Level) {
  585. $diff = $ItemLevel - $Level;
  586. if ($Offset > 0) $List .= str_repeat('<li><ol>', $Offset - 2);
  587. if ($ItemLevel > 1) {
  588. $List .= $i === 0 ? '<li>' : '';
  589. $List .= "\n<ol>\n";
  590. }
  591. } else {
  592. $List .= $i > 0 ? '</li>' : '<li>';
  593. }
  594. }
  595. private static function to_html ($Array) {
  596. self::$Levels++;
  597. /*
  598. * Hax prevention
  599. * That's the original comment on this.
  600. * Most likely this was implemented to avoid anyone nesting enough
  601. * elements to reach PHP's memory limit as nested elements are
  602. * solved recursively.
  603. * Original value of 10, it is now replaced in favor of
  604. * $MaximumNests.
  605. * If this line is ever executed then something is, infact
  606. * being haxed as the if before the block type switch for different
  607. * tags should always be limiting ahead of this line.
  608. * (Larger than vs. smaller than.)
  609. */
  610. if (self::$Levels > self::$MaximumNests) {
  611. return $Block['Val']; // Hax prevention, breaks upon exceeding nests.
  612. }
  613. $Str = '';
  614. foreach ($Array as $Block) {
  615. if (is_string($Block)) {
  616. $Str .= self::smileys($Block);
  617. continue;
  618. }
  619. if (self::$Levels < self::$MaximumNests) {
  620. switch ($Block['Type']) {
  621. case 'b':
  622. $Str .= '<strong>'.self::to_html($Block['Val']).'</strong>';
  623. break;
  624. case 'u':
  625. $Str .= '<span style="text-decoration: underline;">'.self::to_html($Block['Val']).'</span>';
  626. break;
  627. case 'i':
  628. $Str .= '<span style="font-style: italic;">'.self::to_html($Block['Val'])."</span>";
  629. break;
  630. case 's':
  631. $Str .= '<span style="text-decoration: line-through;">'.self::to_html($Block['Val']).'</span>';
  632. break;
  633. case 'important':
  634. $Str .= '<strong class="important_text">'.self::to_html($Block['Val']).'</strong>';
  635. break;
  636. case 'user':
  637. $Str .= '<a href="user.php?action=search&amp;search='.urlencode($Block['Val']).'">'.$Block['Val'].'</a>';
  638. break;
  639. case 'artist':
  640. $Str .= '<a href="artist.php?artistname='.urlencode(Format::undisplay_str($Block['Val'])).'">'.$Block['Val'].'</a>';
  641. break;
  642. case 'rule':
  643. $Rule = trim(strtolower($Block['Val']));
  644. if ($Rule[0] != 'r' && $Rule[0] != 'h') {
  645. $Rule = 'r'.$Rule;
  646. }
  647. $Str .= '<a href="rules.php?p=upload#'.urlencode(Format::undisplay_str($Rule)).'">'.preg_replace('/[aA-zZ]/', '', $Block['Val']).'</a>';
  648. break;
  649. case 'torrent':
  650. $Pattern = '/('.SITE_DOMAIN.'\/torrents\.php.*[\?&]id=)?(\d+)($|&|\#).*/i';
  651. $Matches = array();
  652. if (preg_match($Pattern, $Block['Val'], $Matches)) {
  653. if (isset($Matches[2])) {
  654. $GroupID = $Matches[2];
  655. $Groups = Torrents::get_groups(array($GroupID), true, true, false);
  656. if ($Groups[$GroupID]) {
  657. $Group = $Groups[$GroupID];
  658. $Str .= Artists::display_artists($Group['Artists']).'<a href="torrents.php?id='.$GroupID;
  659. if (preg_match('/torrentid=(\d+)/i', $Block['Val'], $Matches)) {
  660. $Str .= '&torrentid='.$Matches[1];
  661. }
  662. $Str .= '"';
  663. if (!isset($LoggedUser['CoverArt']) || $LoggedUser['CoverArt']) {
  664. $Str .= " onmouseover=\"getCover(event)\" cover=\"".ImageTools::process($Group['WikiImage'], true)."\" onmouseleave=\"ungetCover(event)\"";
  665. }
  666. $Str .= '>'.$Group['Name'].'</a>';
  667. } else {
  668. $Str .= '[torrent]'.str_replace('[inlineurl]', '', $Block['Val']).'[/torrent]';
  669. }
  670. }
  671. } else {
  672. $Str .= '[torrent]'.str_replace('[inlineurl]', '', $Block['Val']).'[/torrent]';
  673. }
  674. break;
  675. case 'wiki':
  676. $Str .= '<a href="wiki.php?action=article&amp;name='.urlencode($Block['Val']).'">'.$Block['Val'].'</a>';
  677. break;
  678. case 'tex':
  679. $Str .= '<img class="tex_img" style="vertical-align: middle;" src="'.STATIC_SERVER.'blank.gif" onload="if (this.src.substr(this.src.length - 9, this.src.length) == \'blank.gif\') { this.src = \''.ImageTools::process('https://chart.googleapis.com/chart?cht=tx&chf=bg,s,FFFFFF00&chl='.urlencode(mb_convert_encoding($Block['Val'], 'UTF-8', 'HTML-ENTITIES'))).'\'; }" alt="'.$Block['Val'].'" />';
  680. break;
  681. case 'plain':
  682. $Str .= $Block['Val'];
  683. break;
  684. case 'pre':
  685. $Str .= '<pre>'.$Block['Val'].'</pre>';
  686. break;
  687. case 'code':
  688. $Str .= '<code>'.$Block['Val'].'</code>';
  689. break;
  690. case 'ch':
  691. $Str .= '<input type="checkbox" checked="checked" disabled="disabled">';
  692. break;
  693. case 'uch':
  694. $Str .= '<input type="checkbox" disabled="disabled">';
  695. break;
  696. case 'list':
  697. $Str .= "<$Block[ListType] class=\"postlist\">";
  698. foreach ($Block['Val'] as $Line) {
  699. $Str .= '<li>'.self::to_html($Line).'</li>';
  700. }
  701. $Str .= '</'.$Block['ListType'].'>';
  702. break;
  703. case 'align':
  704. $ValidAttribs = array('left', 'center', 'right');
  705. if (!in_array($Block['Attr'], $ValidAttribs)) {
  706. $Str .= '[align='.$Block['Attr'].']'.self::to_html($Block['Val']).'[/align]';
  707. } else {
  708. $Str .= '<div style="text-align: '.$Block['Attr'].';">'.self::to_html($Block['Val']).'</div>';
  709. }
  710. break;
  711. case 'color':
  712. case 'colour':
  713. $ValidAttribs = array('aqua', 'black', 'blue', 'fuchsia', 'green', 'grey', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red', 'silver', 'teal', 'white', 'yellow');
  714. if (!in_array($Block['Attr'], $ValidAttribs) && !preg_match('/^#[0-9a-f]{6}$/', $Block['Attr'])) {
  715. $Str .= '[color='.$Block['Attr'].']'.self::to_html($Block['Val']).'[/color]';
  716. } else {
  717. $Str .= '<span style="color: '.$Block['Attr'].';">'.self::to_html($Block['Val']).'</span>';
  718. }
  719. break;
  720. case 'headline':
  721. $text = self::to_html($Block['Val']);
  722. $raw = self::raw_text($Block['Val']);
  723. if (!in_array($Block['Attr'], self::$HeadlineLevels)) {
  724. $Str .= sprintf('%1$s%2$s%1$s', str_repeat('=', $Block['Attr'] + 1), $text);
  725. } else {
  726. $id = '_' . crc32($raw . self::$HeadlineID);
  727. if (self::$InQuotes === 0) {
  728. self::$Headlines[] = array($Block['Attr'], $raw, $id);
  729. }
  730. $Str .= sprintf('<h%1$d id="%3$s">%2$s</h%1$d>', ($Block['Attr'] + 2), $text, $id);
  731. self::$HeadlineID++;
  732. }
  733. break;
  734. case 'inlinesize':
  735. case 'size':
  736. $ValidAttribs = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10');
  737. if (!in_array($Block['Attr'], $ValidAttribs)) {
  738. $Str .= '[size='.$Block['Attr'].']'.self::to_html($Block['Val']).'[/size]';
  739. } else {
  740. $Str .= '<span class="size'.$Block['Attr'].'">'.self::to_html($Block['Val']).'</span>';
  741. }
  742. break;
  743. case 'quote':
  744. self::$NoImg++; // No images inside quote tags
  745. self::$InQuotes++;
  746. if (self::$InQuotes == self::$NestsBeforeHide) { //Put quotes that are nested beyond the specified limit in [hide] tags.
  747. $Str .= '<strong>Older quotes</strong>: <a href="javascript:void(0);" onclick="BBCode.spoiler(this);">Show</a>';
  748. $Str .= '<blockquote class="hidden spoiler">';
  749. }
  750. if (!empty($Block['Attr'])) {
  751. $Exploded = explode('|', self::to_html($Block['Attr']));
  752. if (isset($Exploded[1]) && (is_numeric($Exploded[1]) || (in_array($Exploded[1][0], array('a', 't', 'c', 'r')) && is_numeric(substr($Exploded[1], 1))))) {
  753. // the part after | is either a number or starts with a, t, c or r, followed by a number (forum post, artist comment, torrent comment, collage comment or request comment, respectively)
  754. $PostID = trim($Exploded[1]);
  755. $Str .= '<a quote-jump="'.$PostID.'"><strong class="quoteheader">'.$Exploded[0].'</strong> wrote: </a>';
  756. }
  757. else {
  758. $Str .= '<strong class="quoteheader">'.$Exploded[0].'</strong> wrote: ';
  759. }
  760. }
  761. $Str .= '<blockquote>'.self::to_html($Block['Val']).'</blockquote>';
  762. if (self::$InQuotes == self::$NestsBeforeHide) { //Close quote the deeply nested quote [hide].
  763. $Str .= '</blockquote><br />'; // Ensure new line after quote train hiding
  764. }
  765. self::$NoImg--;
  766. self::$InQuotes--;
  767. break;
  768. case 'hide':
  769. $Str .= '<strong>'.(($Block['Attr']) ? $Block['Attr'] : 'Hidden text').'</strong>: <a href="javascript:void(0);" onclick="BBCode.spoiler(this);">Show</a>';
  770. $Str .= '<blockquote class="hidden spoiler">'.self::to_html($Block['Val']).'</blockquote>';
  771. break;
  772. case 'mature':
  773. if (!empty($Block['Attr'])) {
  774. $Str .= '<strong class="mature" style="font-size: 1.2em;">Mature content:</strong><strong> ' . $Block['Attr'] . '</strong><br /> <a href="javascript:void(0);" onclick="BBCode.spoiler(this);">Show</a>';
  775. $Str .= '<blockquote class="hidden spoiler">'.self::to_html($Block['Val']).'</blockquote>';
  776. }
  777. else {
  778. $Str .= '<strong>Use of the [mature] tag requires a description.</strong> The correct format is as follows: <strong>[mature=description] ...content... [/mature]</strong>, where "description" is a mandatory description of the post. Misleading descriptions will be penalized. For further information on our mature content policies, please refer to this <a href="wiki.php?action=article&amp;id=1063">wiki</a>.';
  779. }
  780. break;
  781. case 'img':
  782. if (self::$NoImg > 0 && self::valid_url($Block['Val'])) {
  783. $Str .= '<a rel="noreferrer" target="_blank" href="'.$Block['Val'].'">'.$Block['Val'].'</a> (image)';
  784. break;
  785. }
  786. if (!self::valid_url($Block['Val'], '\.(jpe?g|gif|png|bmp|tiff)')) {
  787. $Str .= '[img]'.$Block['Val'].'[/img]';
  788. } else {
  789. $LocalURL = self::local_url($Block['Val']);
  790. if ($LocalURL) {
  791. $Str .= '<img class="scale_image lightbox-init" alt="'.$Block['Val'].'" src="'.$LocalURL.'" />';
  792. } else {
  793. $Str .= '<img class="scale_image lightbox-init" alt="'.$Block['Val'].'" src="'.ImageTools::process($Block['Val']).'" />';
  794. }
  795. }
  796. break;
  797. case 'aud':
  798. if (self::$NoImg > 0 && self::valid_url($Block['Val'])) {
  799. $Str .= '<a rel="noreferrer" target="_blank" href="'.$Block['Val'].'">'.$Block['Val'].'</a> (audio)';
  800. break;
  801. }
  802. if (!self::valid_url($Block['Val'], '\.(mp3|ogg|wav)')) {
  803. $Str .= '[aud]'.$Block['Val'].'[/aud]';
  804. } else {
  805. //TODO: Proxy this for staff?
  806. $Str .= '<audio controls="controls" src="'.$Block['Val'].'"><a rel="noreferrer" target="_blank" href="'.$Block['Val'].'">'.$Block['Val'].'</a></audio>';
  807. }
  808. break;
  809. case 'url':
  810. // Make sure the URL has a label
  811. if (empty($Block['Val'])) {
  812. $Block['Val'] = $Block['Attr'];
  813. $NoName = true; // If there isn't a Val for this
  814. } else {
  815. $Block['Val'] = self::to_html($Block['Val']);
  816. $NoName = false;
  817. }
  818. if (!self::valid_url($Block['Attr'])) {
  819. $Str .= '[url='.$Block['Attr'].']'.$Block['Val'].'[/url]';
  820. } else {
  821. $LocalURL = self::local_url($Block['Attr']);
  822. if ($LocalURL) {
  823. if ($NoName) { $Block['Val'] = substr($LocalURL,1); }
  824. $Str .= '<a href="'.$LocalURL.'">'.$Block['Val'].'</a>';
  825. } else {
  826. $Str .= '<a rel="noreferrer" target="_blank" href="'.$Block['Attr'].'">'.$Block['Val'].'</a>';
  827. }
  828. }
  829. break;
  830. case 'inlineurl':
  831. if (!self::valid_url($Block['Attr'], '', true)) {
  832. $Array = self::parse($Block['Attr']);
  833. $Block['Attr'] = $Array;
  834. $Str .= self::to_html($Block['Attr']);
  835. }
  836. else {
  837. $LocalURL = self::local_url($Block['Attr']);
  838. if ($LocalURL) {
  839. $Str .= '<a href="'.$LocalURL.'">'.substr($LocalURL,1).'</a>';
  840. } else {
  841. $Str .= '<a rel="noreferrer" target="_blank" href="'.$Block['Attr'].'">'.$Block['Attr'].'</a>';
  842. }
  843. }
  844. break;
  845. case 'embed':
  846. $Val = str_replace(' ', '', $Block['Val']);
  847. if (self::valid_url($Val) && substr($Val, -4) == 'webm') {
  848. $Str .= '<video class="webm" preload controls><source src="'.ImageTools::process($Val).'" /></video>';
  849. }
  850. break;
  851. }
  852. }
  853. }
  854. self::$Levels--;
  855. return $Str;
  856. }
  857. private static function raw_text ($Array) {
  858. $Str = '';
  859. foreach ($Array as $Block) {
  860. if (is_string($Block)) {
  861. $Str .= $Block;
  862. continue;
  863. }
  864. switch ($Block['Type']) {
  865. case 'headline':
  866. break;
  867. case 'b':
  868. case 'u':
  869. case 'i':
  870. case 's':
  871. case 'color':
  872. case 'size':
  873. case 'quote':
  874. case 'align':
  875. $Str .= self::raw_text($Block['Val']);
  876. break;
  877. case 'tex': //since this will never strip cleanly, just remove it
  878. break;
  879. case 'artist':
  880. case 'user':
  881. case 'wiki':
  882. case 'pre':
  883. case 'code':
  884. case 'aud':
  885. case 'img':
  886. $Str .= $Block['Val'];
  887. break;
  888. case 'list':
  889. foreach ($Block['Val'] as $Line) {
  890. $Str .= $Block['Tag'].self::raw_text($Line);
  891. }
  892. break;
  893. case 'url':
  894. // Make sure the URL has a label
  895. if (empty($Block['Val'])) {
  896. $Block['Val'] = $Block['Attr'];
  897. } else {
  898. $Block['Val'] = self::raw_text($Block['Val']);
  899. }
  900. $Str .= $Block['Val'];
  901. break;
  902. case 'inlineurl':
  903. if (!self::valid_url($Block['Attr'], '', true)) {
  904. $Array = self::parse($Block['Attr']);
  905. $Block['Attr'] = $Array;
  906. $Str .= self::raw_text($Block['Attr']);
  907. }
  908. else {
  909. $Str .= $Block['Attr'];
  910. }
  911. break;
  912. }
  913. }
  914. return $Str;
  915. }
  916. private static function smileys($Str) {
  917. if (!empty(G::$LoggedUser['DisableSmileys'])) {
  918. return $Str;
  919. }
  920. if (count(self::$ProcessedSmileys) == 0 && count(self::$Smileys) > 0) {
  921. foreach (self::$Smileys as $Key => $Val) {
  922. self::$ProcessedSmileys[$Key] = '<img border="0" src="'.STATIC_SERVER.'common/smileys/'.$Val.'" alt="" />';
  923. }
  924. reset(self::$ProcessedSmileys);
  925. }
  926. $Str = strtr($Str, self::$ProcessedSmileys);
  927. return $Str;
  928. }
  929. }
  930. /*
  931. // Uncomment this part to test the class via command line:
  932. function display_str($Str) {
  933. return $Str;
  934. }
  935. function check_perms($Perm) {
  936. return true;
  937. }
  938. $Str = "hello
  939. [pre]http://anonym.to/?http://whatshirts.portmerch.com/
  940. ====hi====
  941. ===hi===
  942. ==hi==[/pre]
  943. ====hi====
  944. hi";
  945. echo Text::full_format($Str);
  946. echo "\n"
  947. */