max number of attributes * @var array $ValidTags */ private static $ValidTags = ['b'=>0, 'u'=>0, 'i'=>0, 's'=>0, '*'=>1, '#'=>1, '**'=>1, '##'=>1, '***'=>1, '###'=>1, '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, 'box'=>0 ]; /** * Array of smilies; code => image file in STATIC_SERVER/common/smileys * @var array $Smileys */ private static $Smileys = [ ':angry:' => 'angry.gif', ':-D' => 'biggrin.gif', ':D' => 'biggrin.gif', ':|' => 'blank.gif', ':-|' => 'blank.gif', ':blush:' => 'blush.gif', ':cool:' => 'cool.gif', ':'(' => 'crying.gif', ':crying:' => 'crying.gif', '>.>' => 'eyesright.gif', ':frown:' => 'frown.gif', '<3' => 'heart.gif', ':unsure:' => 'hmm.gif', //':\\' => 'hmm.gif', ':whatlove:' => 'ilu.gif', ':lol:' => 'laughing.gif', ':loveflac:' => 'loveflac.gif', ':flaclove:' => 'loveflac.gif', ':ninja:' => 'ninja.gif', ':no:' => 'no.gif', ':nod:' => 'nod.gif', ':ohno:' => 'ohnoes.gif', ':ohnoes:' => 'ohnoes.gif', ':omg:' => 'omg.gif', ':o' => 'ohshit.gif', ':O' => 'ohshit.gif', ':paddle:' => 'paddle.gif', ':(' => 'sad.gif', ':-(' => 'sad.gif', ':shifty:' => 'shifty.gif', ':sick:' => 'sick.gif', ':)' => 'smile.gif', ':-)' => 'smile.gif', ':sorry:' => 'sorry.gif', ':thanks:' => 'thanks.gif', ':P' => 'tongue.gif', ':p' => 'tongue.gif', ':-P' => 'tongue.gif', ':-p' => 'tongue.gif', ':wave:' => 'wave.gif', ';-)' => 'wink.gif', ':wink:' => 'wink.gif', ':creepy:' => 'creepy.gif', ':worried:' => 'worried.gif', ':wtf:' => 'wtf.gif', ':wub:' => 'wub.gif', ]; /** * Processed version of the $Smileys array, see {@link smileys} * @var array $ProcessedSmileys */ private static $ProcessedSmileys = []; /** * Whether or not to turn images into URLs (used inside [quote] tags). * This is an integer reflecting the number of levels we're doing that * transition, i.e. images will only be displayed as images if $NoImg <= 0. * By setting this variable to a negative number you can delay the * transition to a deeper level of quotes. * @var int $NoImg */ private static $NoImg = 0; /** * Internal counter for the level of recursion in to_html * @var int $Levels */ private static $Levels = 0; /** * The maximum amount of nesting allowed (exclusive) * In reality n-1 nests are shown. * @var int $MaximumNests */ private static $MaximumNests = 10; /** * Used to detect and disable parsing (e.g. TOC) within quotes * @var int $InQuotes */ private static $InQuotes = 0; /** * Used to [hide] quote trains starting with the specified depth (inclusive) * @var int $NestsBeforeHide * * This defaulted to 5 but was raised to 10 to effectively "disable" it until * an optimal number of nested [quote] tags is chosen. The variable $MaximumNests * effectively overrides this variable, if $MaximumNests is less than the value * of $NestsBeforeHide. */ private static $NestsBeforeHide = 10; /** * Array of headlines for Table Of Contents (TOC) * @var array $HeadLines */ private static $Headlines; /** * Counter for making headline URLs unique * @var int $HeadLines */ private static $HeadlineID = 0; /** * Depth * @var array $HeadlineLevels */ private static $HeadlineLevels = ['1', '2', '3', '4']; /** * TOC enabler * @var bool $TOC */ public static $TOC = false; /** * Output BBCode as XHTML * @param string $Str BBCode text * @param bool $OutputTOC Ouput TOC near (above) text * @param int $Min See {@link parse_toc} * @return string */ public static function full_format($Str, $OutputTOC = true, $Min = 3, $Rules = false) { global $Debug; $Debug->set_flag('BBCode start'); $Str = display_str($Str); self::$Headlines = []; // @mention $Str = preg_replace('/@('.USERNAME_REGEX_SHORT.')/i', '[user]@$1[/user]', $Str); //Inline links $URLPrefix = '(\[url\]|\[url\=|\[img\=|\[img\])'; $Str = preg_replace('/'.$URLPrefix.'\s+/i', '$1', $Str); $Str = preg_replace('/(?set_flag('BBCode end'); return $HTML; } public static function strip_bbcode($Str) { $Str = display_str($Str); //Inline links $Str = preg_replace('/(? $i) { $Array[$ArrayPos] = substr($Str, $i, $TagPos - $i); ++$ArrayPos; $i = $TagPos; } // 2) See if it's a [[wiki-link]] or an ordinary tag, and get the tag name if (!empty($Tag[4][0])) { // Wiki-link $WikiLink = true; $TagName = substr($Tag[4][0], 2, -2); $Attrib = ''; } else { // 3) If it's not a wiki link: $WikiLink = false; $TagName = strtolower(substr($Tag[2][0], 1)); //3a) check it against the self::$ValidTags array to see if it's actually a tag and not [bullshit] if (!isset(self::$ValidTags[$TagName])) { $Array[$ArrayPos] = substr($Str, $i, ($TagPos - $i) + strlen($Tag[0][0])); $i = $TagPos + strlen($Tag[0][0]); ++$ArrayPos; continue; } $MaxAttribs = self::$ValidTags[$TagName]; // 3b) Get the attribute, if it exists [name=attribute] if (!empty($Tag[3][0])) { $Attrib = substr($Tag[3][0], 1); } else { $Attrib = ''; } } // 4) Move the pointer past the end of the tag $i = $TagPos + strlen($Tag[0][0]); // 5) Find out where the tag closes (beginning of [/tag]) // Unfortunately, BBCode doesn't have nice standards like XHTML // [*], [img=...], and http:// follow different formats // Thus, we have to handle these before we handle the majority of tags //5a) Different for different types of tag. Some tags don't close, others are weird like [*] if ($TagName == 'img' && !empty($Tag[3][0])) { //[img=...] $Block = ''; // Nothing inside this tag // Don't need to touch $i } elseif ($TagName == 'inlineurl') { // We did a big replace early on to turn http:// into [inlineurl]http:// // Let's say the block can stop at a newline or a space $CloseTag = strcspn($Str, " \n\r", $i); if ($CloseTag === false) { // block finishes with URL $CloseTag = $Len; } if (preg_match('/[!,.?:]+$/',substr($Str, $i, $CloseTag), $Match)) { $CloseTag -= strlen($Match[0]); } $URL = substr($Str, $i, $CloseTag); if (substr($URL, -1) == ')' && substr_count($URL, '(') < substr_count($URL, ')')) { $CloseTag--; $URL = substr($URL, 0, -1); } $Block = $URL; // Get the URL // strcspn returns the number of characters after the offset $i, not after the beginning of the string // Therefore, we use += instead of the = everywhere else $i += $CloseTag; // 5d) Move the pointer past the end of the [/close] tag. } elseif ($WikiLink == true || $TagName == 'n') { // Don't need to do anything - empty tag with no closing } elseif ($TagName[0] === '*' || $TagName[0] === '#') { // We're in a list. Find where it ends $NewLine = $i; do { // Look for \n[*] $NewLine = strpos($Str, "\n", $NewLine + 1); } while ($NewLine !== false && substr($Str, $NewLine + 1, 1 + strlen($TagName)) == "[$TagName"); $CloseTag = $NewLine; if ($CloseTag === false) { // block finishes with list $CloseTag = $Len; } $Block = substr($Str, $i, $CloseTag - $i); // Get the list $i = $CloseTag; // 5d) Move the pointer past the end of the [/close] tag. } else { //5b) If it's a normal tag, it may have versions of itself nested inside $CloseTag = $i - 1; $InTagPos = $i - 1; $NumInOpens = 0; $NumInCloses = -1; $InOpenRegex = '/\[('.$TagName.')'; if ($MaxAttribs > 0) { $InOpenRegex .= "(=[^\n'\"\[\]]+)?"; } $InOpenRegex .= '\]/i'; // Every time we find an internal open tag of the same type, search for the next close tag // (as the first close tag won't do - it's been opened again) do { $CloseTag = strpos($StrLC, "[/$TagName]", $CloseTag + 1); if ($CloseTag === false) { $CloseTag = $Len; break; } else { $NumInCloses++; // Majority of cases } // Is there another open tag inside this one? $OpenTag = preg_match($InOpenRegex, $Str, $InTag, PREG_OFFSET_CAPTURE, $InTagPos + 1); if (!$OpenTag || $InTag[0][1] > $CloseTag) { break; } else { $InTagPos = $InTag[0][1]; $NumInOpens++; } } while ($NumInOpens > $NumInCloses); // Find the internal block inside the tag $Block = substr($Str, $i, $CloseTag - $i); // 5c) Get the contents between [open] and [/close] and call it the block. $i = $CloseTag + strlen($TagName) + 3; // 5d) Move the pointer past the end of the [/close] tag. } // 6) Depending on what type of tag we're dealing with, create an array with the attribute and block. switch ($TagName) { case 'inlineurl': $Array[$ArrayPos] = ['Type'=>'inlineurl', 'Attr'=>$Block, 'Val'=>'']; break; case 'url': $Array[$ArrayPos] = ['Type'=>'img', 'Attr'=>$Attrib, 'Val'=>$Block]; if (empty($Attrib)) { // [url]http://...[/url] - always set URL to attribute $Array[$ArrayPos] = ['Type'=>'url', 'Attr'=>$Block, 'Val'=>'']; } else { $Array[$ArrayPos] = ['Type'=>'url', 'Attr'=>$Attrib, 'Val'=>self::parse($Block)]; } break; case 'quote': $Array[$ArrayPos] = ['Type'=>'quote', 'Attr'=>self::parse($Attrib), 'Val'=>self::parse($Block)]; break; case 'box': $Array[$ArrayPos] = ['Type'=>'box', 'Val'=>self::parse($Block)]; break; case 'img': case 'image': if (empty($Block)) { $Block = $Attrib; } $Array[$ArrayPos] = ['Type'=>'img', 'Val'=>$Block]; break; case 'aud': case 'mp3': case 'audio': if (empty($Block)) { $Block = $Attrib; } $Array[$ArrayPos] = ['Type'=>'aud', 'Val'=>$Block]; break; case 'user': $Array[$ArrayPos] = ['Type'=>'user', 'Val'=>$Block]; break; case 'artist': $Array[$ArrayPos] = ['Type'=>'artist', 'Val'=>$Block]; break; case 'torrent': $Array[$ArrayPos] = ['Type'=>'torrent', 'Val'=>$Block]; break; case 'tex': $Array[$ArrayPos] = ['Type'=>'tex', 'Val'=>$Block]; break; case 'rule': $Array[$ArrayPos] = ['Type'=>'rule', 'Val'=>$Block]; break; case 'pre': case 'code': case 'plain': $Block = strtr($Block, ['[inlineurl]' => '']); $Callback = function ($matches) { $n = $matches[2]; $text = ''; if ($n < 5 && $n > 0) { $e = str_repeat('=', $matches[2] + 1); $text = $e . $matches[3] . $e; } return $text; }; $Block = preg_replace_callback('/\[(headline)\=(\d)\](.*?)\[\/\1\]/i', $Callback, $Block); $Block = preg_replace('/\[inlinesize\=3\](.*?)\[\/inlinesize\]/i', '====$1====', $Block); $Block = preg_replace('/\[inlinesize\=5\](.*?)\[\/inlinesize\]/i', '===$1===', $Block); $Block = preg_replace('/\[inlinesize\=7\](.*?)\[\/inlinesize\]/i', '==$1==', $Block); $Array[$ArrayPos] = ['Type'=>$TagName, 'Val'=>$Block]; break; case 'spoiler': case 'hide': $Array[$ArrayPos] = ['Type'=>'hide', 'Attr'=>$Attrib, 'Val'=>self::parse($Block)]; break; case 'mature': $Array[$ArrayPos] = ['Type'=>'mature', 'Attr'=>$Attrib, 'Val'=>self::parse($Block)]; break; case '#': case '*': case '##': case '**': case '###': case '***': $CurrentId = 1; $Array[$ArrayPos] = ['Type'=>'list']; $Array[$ArrayPos]['Val'] = explode("[$TagName]", $Block); $Array[$ArrayPos]['ListType'] = $TagName[0] === '*' ? 'ul' : 'ol'; $Array[$ArrayPos]['Tag'] = $TagName; $ChildPrefix = $ListPrefix === '' ? $ListId : $ListPrefix; if ($Attrib !== '') { $ChildPrefix = $Attrib; } foreach ($Array[$ArrayPos]['Val'] as $Key=>$Val) { $Id = $ChildPrefix.'.'.($CurrentId++); $Array[$ArrayPos]['Val'][$Key] = self::parse(trim($Val), $Id); $Array[$ArrayPos]['Val'][$Key]['Id'] = $Id; } $ListId++; break; case 'n': $ArrayPos--; break; // n serves only to disrupt bbcode (backwards compatibility - use [pre]) default: if ($WikiLink == true) { $Array[$ArrayPos] = ['Type'=>'wiki','Val'=>$TagName]; } else { // Basic tags, like [b] or [size=5] $Array[$ArrayPos] = ['Type'=>$TagName, 'Val'=>self::parse($Block)]; if (!empty($Attrib) && $MaxAttribs > 0) { $Array[$ArrayPos]['Attr'] = strtolower($Attrib); } } } $ArrayPos++; // 7) Increment array pointer, start again (past the end of the [/close] tag) } return $Array; } /** * Generates a navigation list for TOC * @param int $Min Minimum number of headlines required for a TOC list */ public static function parse_toc ($Min = 3, $RulesTOC = false) { if (count(self::$Headlines) > $Min) { $tag = $RulesTOC ? 'ul' : 'ol'; if ($RulesTOC) { $list = "<$tag>"; } else { $list = "<$tag class=\"navigation_list\">"; } $i = 0; $level = 0; $off = 0; foreach (self::$Headlines as $t) { $n = (int)$t[0]; if ($i === 0 && $n > 1) { $off = $n - $level; } self::headline_level($n, $level, $list, $i, $off, $tag); $list .= sprintf('
';
break;
case 'plain':
$Str .= $Block['Val'];
break;
case 'pre':
$Str .= ''.$Block['Val'].''; break; case 'code': $Str .= '
'.$Block['Val'].'';
break;
case 'list':
$Str .= "<$Block[ListType] class=\"postlist\">";
foreach ($Block['Val'] as $Line) {
$Str .= ''; } if (!empty($Block['Attr'])) { $Exploded = explode('|', self::to_html($Block['Attr'], $Rules)); if (isset($Exploded[1]) && (is_numeric($Exploded[1]) || (in_array($Exploded[1][0], ['a', 't', 'c', 'r']) && is_numeric(substr($Exploded[1], 1))))) { // 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) $PostID = trim($Exploded[1]); $Str .= ''.$Exploded[0].' wrote: '; } else { $Str .= ''.$Exploded[0].' wrote: '; } } $Str .= ''.self::to_html($Block['Val'], $Rules).''; if (self::$InQuotes == self::$NestsBeforeHide) { //Close quote the deeply nested quote [hide]. $Str .= '
'.self::to_html($Block['Val'], $Rules).''; break; case 'mature': if (G::$LoggedUser['EnableMatureContent']) { if (!empty($Block['Attr'])) { $Str .= 'Mature content: ' . $Block['Attr'] . '
'.self::to_html($Block['Val'], $Rules).''; } else { $Str .= 'Use of the [mature] tag requires a description. The correct format is as follows: [mature=description] ...content... [/mature], 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 wiki.'; } } else { $Str .= 'Mature content has been blocked. You can choose to view mature content by editing your settings.'; } break; case 'img': if (self::$NoImg > 0 && self::valid_url($Block['Val'])) { $Str .= ''.$Block['Val'].' (image)'; break; } if (!self::valid_url($Block['Val'], '\.(jpe?g|gif|png|bmp|tiff)')) { $Str .= '[img]'.$Block['Val'].'[/img]'; } else { $LocalURL = self::local_url($Block['Val']); if ($LocalURL) { $Str .= '
/","[quote=\\1]", $Str); $Str = preg_replace("/\<(\/*)blockquote\>/", "[\\1quote]", $Str); $Str = preg_replace("/\<(\/*)strong\>/", "[\\1b]", $Str); $Str = preg_replace("/\<(\/*)italic\>/", "[\\1i]", $Str); $Str = preg_replace("/\<(\/*)underline\>/", "[\\1u]", $Str); $Str = preg_replace("/\<(\/*)important\>/", "[\\1important]", $Str); $Str = preg_replace("/\<(\/*)code\>/", "[\\1code]", $Str); $Str = preg_replace("/\<(\/*)pre\>/", "[\\1pre]", $Str); $Str = preg_replace("/\/", "[color=\\1]", $Str); $Str = str_replace(" ", "[/color]", $Str); $Str = str_replace(['', ' ', ''], ''], ['[#]', '[*]'], $Str); $Str = str_replace(['
', $Str); $Str = str_replace(['', '
', ''], '', $Str); $Str = preg_replace("/\', '
/", "[align=\\1]", $Str); $Str = str_replace(" ", "[/align]", $Str); $Str = preg_replace("/\/", "[size=\\1]", $Str); $Str = str_replace(" ", "[/size]", $Str); //$Str = preg_replace("/\(.*)\<\/a\>/", "[rule]\\3[/rule]", $Str); //$Str = preg_replace("/\(.*)\<\/a>/", "[[\\1]]", $Str); $Str = preg_replace('#/torrents.php\?recordlabel="?(?:[^"]*)#', 'https://'.SITE_URL.'\\0', $Str); $Str = preg_replace('#/torrents.php\?taglist="?(?:[^"]*)#', 'https://'.SITE_URL.'\\0', $Str); $Str = preg_replace("/\<(\/*)artist\>/", "[\\1artist]", $Str); $Str = preg_replace("/\((\/*)user\>/", "[\\1user]", $Str); $Str = preg_replace("/\/", "[url=\\1]", $Str); $Str = preg_replace("/\<(\/*)a\>/", "[\\1url]", $Str); $Str = preg_replace("/\/", '[img]\\2[/img]', $Str); $Str = str_replace('
', '', $Str); $Str = str_replace('
', '
', $Str); //return $Str; return str_replace(["
", "
"], "\n", $Str); } } /* define("SITE_URL", 'https://orpheus.network'); $Str = "Album Information
\r\nInterstellar Rift Original Soundtrack<\/strong><\/span><\/span><\/div>
\r\n
\r\n
- Catalog Number:<\/strong>\tN\/A<\/li>
- Release Date:<\/strong>\tMay 29, 2017<\/li>
- Publish Format:<\/strong>\tCommercial<\/li>
- Release Price:<\/strong>\t13.00 AUD<\/li>
- Media Format:<\/strong>\tDigital<\/li>
- Classification:<\/strong>\tOriginal Soundtrack<\/li>
- Published by:<\/strong>\tBoss Battle Records (distributed by Bandcamp)<\/li>
- Composed by:<\/strong>\tCurtis Schweitzer<\/li>
- Arranged by:<\/strong><\/li><\/ul>
\n\r
\nTracklist
\r\n01\tInterstellar Rift\t\t2:38
\r\n02\tShipmaking\t\t\t4:16
\r\n03\tGalaxy Dawn\t\t\t2:55
\r\n04\tInterloper\t\t\t2:32
\r\n05\tShipbreaking\t\t\t2:06
\r\n06\tHorizon Wake\t\t\t4:24
\r\n07\tSky Harvest\t\t\t3:50
\r\n08\tTwilight Discord\t\t2:42
\r\n09\tLanguid Constellations\t\t2:45
\r\n10\tHurles Co.\t\t\t2:43
\r\n11\tGalactic Trade Federation\t2:46
\r\n12\tSentinel Security Systems\t4:08
\r\n13\tDrifters\t\t\t3:18<\/pre><\/div>
\r\n
\r\nNotes
\r\nComposed by Curtis Schweitzer
\r\nMastered by Aaron Edwards
\r\nSoundtrack curated by The Otherworld Agency<\/pre><\/div>"; //echo $Str; echo Text::parse_html($Str); echo "\n"; */