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('
  • %1$s', $t[1], $t[2]); $level = $t[0]; $off = 0; $i++; } $list .= str_repeat("
  • ", $level); $list .= "\n\n"; return $list; } } /** * Generates the list items and proper depth * * First check if the item should be higher than the current level * - Close the list and previous lists * * Then check if the item should go lower than the current level * - If the list doesn't open on level one, use the Offset * - Open appropriate sub lists * * Otherwise the item is on the same as level as the previous item * * @param int $ItemLevel Current item level * @param int $Level Current list level * @param str $List reference to an XHTML string * @param int $i Iterator digit * @param int $Offset If the list doesn't start at level 1 */ private static function headline_level (&$ItemLevel, &$Level, &$List, $i, &$Offset, $Tag) { if ($ItemLevel < $Level) { $diff = $Level - $ItemLevel; $List .= '' . str_repeat("", $diff); } elseif ($ItemLevel > $Level) { $diff = $ItemLevel - $Level; if ($Offset > 0) $List .= str_repeat("
  • <$Tag>", $Offset - 2); if ($ItemLevel > 1) { $List .= $i === 0 ? '
  • ' : ''; $List .= "\n<$Tag>\n"; } } else { $List .= $i > 0 ? '
  • ' : '
  • '; } } private static function to_html ($Array, $Rules) { global $SSL; self::$Levels++; /* * Hax prevention * That's the original comment on this. * Most likely this was implemented to avoid anyone nesting enough * elements to reach PHP's memory limit as nested elements are * solved recursively. * Original value of 10, it is now replaced in favor of * $MaximumNests. * If this line is ever executed then something is, infact * being haxed as the if before the block type switch for different * tags should always be limiting ahead of this line. * (Larger than vs. smaller than.) */ if (self::$Levels > self::$MaximumNests) { return $Array['Val']; // Hax prevention, breaks upon exceeding nests. } $Str = ''; if (array_key_exists('Id', $Array) && is_string($Array[0]) && count($Array) == 2) { self::$Levels--; return self::smileys($Array[0]); } foreach ($Array as $Key=>$Block) { if ($Key === 'Id') { continue; } if (is_string($Block)) { $Str .= self::smileys($Block); self::$Levels--; continue; } if (self::$Levels < self::$MaximumNests) { switch ($Block['Type']) { case 'b': $Str .= ''.self::to_html($Block['Val'], $Rules).''; break; case 'u': $Str .= ''.self::to_html($Block['Val'], $Rules).''; break; case 'i': $Str .= ''.self::to_html($Block['Val'], $Rules).""; break; case 's': $Str .= ''.self::to_html($Block['Val'], $Rules).''; break; case 'important': $Str .= ''.self::to_html($Block['Val'], $Rules).''; break; case 'user': $Str .= ''.$Block['Val'].''; break; case 'artist': $Str .= ''.$Block['Val'].''; break; case 'rule': $Rule = trim(strtolower($Block['Val'])); if ($Rule[0] != 'r' && $Rule[0] != 'h') { $Rule = 'r'.$Rule; } $Str .= ''.preg_replace('/[aA-zZ]/', '', $Block['Val']).''; break; case 'torrent': $Pattern = '/('.NONSSL_SITE_URL.'\/torrents\.php.*[\?&]id=)?(\d+)($|&|\#).*/i'; $Matches = []; if (preg_match($Pattern, $Block['Val'], $Matches)) { if (isset($Matches[2])) { $GroupID = $Matches[2]; $Groups = Torrents::get_groups([$GroupID], true, true, false); if ($Groups[$GroupID]) { $Group = $Groups[$GroupID]; $Str .= Artists::display_artists($Group['ExtendedArtists']).''.$Group['Name'].''; } else { $Str .= '[torrent]'.str_replace('[inlineurl]', '', $Block['Val']).'[/torrent]'; } } } else { $Str .= '[torrent]'.str_replace('[inlineurl]', '', $Block['Val']).'[/torrent]'; } break; case 'wiki': $Str .= ''.$Block['Val'].''; break; case 'tex': $Str .= ''.$Block['Val'].''; 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 .= ''.self::to_html($Line, $Rules).'
  • '; } $Str .= ''; break; case 'align': $ValidAttribs = ['left', 'center', 'right']; if (!in_array($Block['Attr'], $ValidAttribs)) { $Str .= '[align='.$Block['Attr'].']'.self::to_html($Block['Val']).'[/align]'; } else { $Str .= '
    '.self::to_html($Block['Val'], $Rules).'
    '; } break; case 'color': case 'colour': $ValidAttribs = ['aqua', 'black', 'blue', 'fuchsia', 'green', 'grey', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red', 'silver', 'teal', 'white', 'yellow']; if (!in_array($Block['Attr'], $ValidAttribs) && !preg_match('/^#[0-9a-f]{6}$/', $Block['Attr'])) { $Str .= '[color='.$Block['Attr'].']'.self::to_html($Block['Val'], $Rules).'[/color]'; } else { $Str .= ''.self::to_html($Block['Val'], $Rules).''; } break; case 'headline': $text = self::to_html($Block['Val'], $Rules); $raw = self::raw_text($Block['Val']); if (!in_array($Block['Attr'], self::$HeadlineLevels)) { $Str .= sprintf('%1$s%2$s%1$s', str_repeat('=', $Block['Attr'] + 1), $text); } else { $id = '_' . crc32($raw . self::$HeadlineID); if (self::$InQuotes === 0) { self::$Headlines[] = [$Block['Attr'], $raw, $id]; } $Str .= sprintf('%2$s', ($Block['Attr'] + 2), $text, $id); self::$HeadlineID++; } break; case 'inlinesize': case 'size': $ValidAttribs = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; if (!in_array($Block['Attr'], $ValidAttribs)) { $Str .= '[size='.$Block['Attr'].']'.self::to_html($Block['Val'], $Rules).'[/size]'; } else { $Str .= ''.self::to_html($Block['Val'], $Rules).''; } break; case 'quote': self::$NoImg++; // No images inside quote tags self::$InQuotes++; if (self::$InQuotes == self::$NestsBeforeHide) { //Put quotes that are nested beyond the specified limit in [hide] tags. $Str .= 'Older quotes: Show'; $Str .= '
    '; // Ensure new line after quote train hiding } self::$NoImg--; self::$InQuotes--; break; case 'box': $Str .= '
    '.self::to_html($Block['Val'], $Rules).'
    '; break; case 'hide': $Str .= ''.(($Block['Attr']) ? $Block['Attr'] : 'Hidden text').': Show'; $Str .= ''; break; case 'mature': if (G::$LoggedUser['EnableMatureContent']) { if (!empty($Block['Attr'])) { $Str .= 'Mature content: ' . $Block['Attr'] . '
    Show'; $Str .= ''; } 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 .= ''.$Block['Val'].''; } else { $Str .= ''.$Block['Val'].''; } } break; case 'aud': if (self::$NoImg > 0 && self::valid_url($Block['Val'])) { $Str .= ''.$Block['Val'].' (audio)'; break; } if (!self::valid_url($Block['Val'], '\.(mp3|ogg|wav)')) { $Str .= '[aud]'.$Block['Val'].'[/aud]'; } else { //TODO: Proxy this for staff? $Str .= ''; } break; case 'url': // Make sure the URL has a label if (empty($Block['Val'])) { $Block['Val'] = $Block['Attr']; $NoName = true; // If there isn't a Val for this } else { $Block['Val'] = self::to_html($Block['Val'], $Rules); $NoName = false; } if (!self::valid_url($Block['Attr'])) { $Str .= '[url='.$Block['Attr'].']'.$Block['Val'].'[/url]'; } else { $LocalURL = self::local_url($Block['Attr']); if ($LocalURL) { if ($NoName) { $Block['Val'] = substr($LocalURL,1); } $Str .= ''.$Block['Val'].''; } else { $Str .= ''.$Block['Val'].''; } } break; case 'inlineurl': if (!self::valid_url($Block['Attr'], '', true)) { $Array = self::parse($Block['Attr']); $Block['Attr'] = $Array; $Str .= self::to_html($Block['Attr'], $Rules); } else { $LocalURL = self::local_url($Block['Attr']); if ($LocalURL) { $Str .= ''.substr($LocalURL,1).''; } else { $Str .= ''.$Block['Attr'].''; } } break; } } } self::$Levels--; return $Str; } private static function raw_text ($Array) { $Str = ''; foreach ($Array as $Block) { if (is_string($Block)) { $Str .= $Block; continue; } switch ($Block['Type']) { case 'headline': break; case 'b': case 'u': case 'i': case 's': case 'color': case 'size': case 'quote': case 'align': $Str .= self::raw_text($Block['Val']); break; case 'tex': //since this will never strip cleanly, just remove it break; case 'artist': case 'user': case 'wiki': case 'pre': case 'code': case 'aud': case 'img': $Str .= $Block['Val']; break; case 'list': foreach ($Block['Val'] as $Line) { $Str .= $Block['Tag'].self::raw_text($Line); } break; case 'url': // Make sure the URL has a label if (empty($Block['Val'])) { $Block['Val'] = $Block['Attr']; } else { $Block['Val'] = self::raw_text($Block['Val']); } $Str .= $Block['Val']; break; case 'inlineurl': if (!self::valid_url($Block['Attr'], '', true)) { $Array = self::parse($Block['Attr']); $Block['Attr'] = $Array; $Str .= self::raw_text($Block['Attr']); } else { $Str .= $Block['Attr']; } break; } } return $Str; } private static function smileys($Str) { if (!empty(G::$LoggedUser['DisableSmileys'])) { return $Str; } if (count(self::$ProcessedSmileys) == 0 && count(self::$Smileys) > 0) { foreach (self::$Smileys as $Key => $Val) { self::$ProcessedSmileys[$Key] = ''; } reset(self::$ProcessedSmileys); } $Str = strtr($Str, self::$ProcessedSmileys); return $Str; } /** * Given a String that is composed of HTML, attempt to convert it back * into BBCode. Useful when we're trying to deal with the output from * some other site's metadata. This also should reverse the HTML encoding * that display_str does so we do not have to call reverse_display_str on the * result. * * @param String $Html * @return String */ public static function parse_html($Html) { $Document = new DOMDocument(); $Document->loadHtml(stripslashes($Html)); // For any manipulation that we do on the DOM tree, always go in reverse order or // else you end up with broken array pointers and missed elements $CopyNode = function ($OriginalElement, $NewElement) { for ($i = count($OriginalElement->childNodes) - 1; $i >= 0; $i--) { if (count($NewElement->childNodes) > 0) { $NewElement->insertBefore($OriginalElement->childNodes[$i], $NewElement->childNodes[0]); } else { $NewElement->appendChild($OriginalElement->childNodes[$i]); } } }; $Elements = $Document->getElementsByTagName('div'); for ($i = $Elements->length - 1; $i >= 0; $i--) { $Element = $Elements->item($i); if (strpos($Element->getAttribute('style'), 'text-align') !== false) { $NewElement = $Document->createElement('align'); $CopyNode($Element, $NewElement); $NewElement->setAttribute('align', str_replace('text-align: ', '', $Element->getAttribute('style'))); $Element->parentNode->replaceChild($NewElement, $Element); } } $Elements = $Document->getElementsByTagName('span'); for ($i = $Elements->length - 1; $i >= 0; $i--) { $Element = $Elements->item($i); if (strpos($Element->getAttribute('class'), 'size') !== false) { $NewElement = $Document->createElement('size'); $CopyNode($Element, $NewElement); $NewElement->setAttribute('size', str_replace('size', '', $Element->getAttribute('class'))); $Element->parentNode->replaceChild($NewElement, $Element); } elseif (strpos($Element->getAttribute('style'), 'font-style: italic') !== false) { $NewElement = $Document->createElement('italic'); $CopyNode($Element, $NewElement); $Element->parentNode->replaceChild($NewElement, $Element); } elseif (strpos($Element->getAttribute('style'), 'text-decoration: underline') !== false) { $NewElement = $Document->createElement('underline'); $CopyNode($Element, $NewElement); $Element->parentNode->replaceChild($NewElement, $Element); } elseif (strpos($Element->getAttribute('style'), 'color: ') !== false) { $NewElement = $Document->createElement('color'); $CopyNode($Element, $NewElement); $NewElement->setAttribute('color', str_replace(['color: ', ';'], '', $Element->getAttribute('style'))); $Element->parentNode->replaceChild($NewElement, $Element); } } $Elements = $Document->getElementsByTagName('ul'); for ($i = 0; $i < $Elements->length; $i++) { $InnerElements = $Elements->item($i)->getElementsByTagName('li'); for ($j = $InnerElements->length - 1; $j >= 0; $j--) { $Element = $InnerElements->item($j); $NewElement = $Document->createElement('bullet'); $CopyNode($Element, $NewElement); $Element->parentNode->replaceChild($NewElement, $Element); } } $Elements = $Document->getElementsByTagName('ol'); for ($i = 0; $i < $Elements->length; $i++) { $InnerElements = $Elements->item($i)->getElementsByTagName('li'); for ($j = $InnerElements->length - 1; $j >= 0; $j--) { $Element = $InnerElements->item($j); $NewElement = $Document->createElement('number'); $CopyNode($Element, $NewElement); $Element->parentNode->replaceChild($NewElement, $Element); } } $Elements = $Document->getElementsByTagName('strong'); for ($i = $Elements->length - 1; $i >= 0; $i--) { $Element = $Elements->item($i); if ($Element->hasAttribute('class') === 'important_text') { $NewElement = $Document->createElement('important'); $CopyNode($Element, $NewElement); $Element->parentNode->replaceChild($NewElement, $Element); } } $Elements = $Document->getElementsByTagName('a'); for ($i = $Elements->length - 1; $i >= 0; $i--) { $Element = $Elements->item($i); if ($Element->hasAttribute('href')) { $Element->removeAttribute('rel'); $Element->removeAttribute('target'); if ($Element->getAttribute('href') === $Element->nodeValue) { $Element->removeAttribute('href'); } elseif ($Element->getAttribute('href') === 'javascript:void(0);' && $Element->getAttribute('onclick') === 'BBCode.spoiler(this);') { $Spoilers = $Document->getElementsByTagName('blockquote'); for ($j = $Spoilers->length - 1; $j >= 0; $j--) { $Spoiler = $Spoilers->item($j); if ($Spoiler->hasAttribute('class') && $Spoiler->getAttribute('class') === 'hidden spoiler') { $NewElement = $Document->createElement('spoiler'); $CopyNode($Spoiler, $NewElement); $Element->parentNode->replaceChild($NewElement, $Element); $Spoiler->parentNode->removeChild($Spoiler); break; } } } elseif (substr($Element->getAttribute('href'), 0, 22) === 'artist.php?artistname=') { $NewElement = $Document->createElement('artist'); $CopyNode($Element, $NewElement); $Element->parentNode->replaceChild($NewElement, $Element); } elseif (substr($Element->getAttribute('href'), 0, 30) === 'user.php?action=search&search=') { $NewElement = $Document->createElement('user'); $CopyNode($Element, $NewElement); $Element->parentNode->replaceChild($NewElement, $Element); } } } $Str = str_replace(["\n", "\n", "", ""], "", $Document->saveHTML($Document->getElementsByTagName('body')->item(0))); $Str = str_replace(["\r\n", "\n"], "", $Str); $Str = preg_replace("/\([a-zA-Z0-9 ]+)\<\/strong\>\: \/", "[spoiler=\\1]", $Str); $Str = str_replace("", "[/spoiler]", $Str); $Str = preg_replace("/\(.*)\<\/strong\>(.*)wrote\:(.*)\/","[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
      \n
      Tracklist
      \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\n
      Notes
      \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"; */