diff --git a/class.exportmodel.php b/class.exportmodel.php index 4bf63b1e..2533bce2 100644 --- a/class.exportmodel.php +++ b/class.exportmodel.php @@ -47,6 +47,9 @@ class ExportModel { */ public $Prefix = ''; + /** @var string The path to the source of the export in the case where a file is being converted. */ + public $SourcePath = ''; + /** * @var array Strucutes that define the format of the export tables. */ @@ -205,6 +208,8 @@ public function BeginExport($Path = '', $Source = '', $Header = array()) { } fwrite($fp, self::NEWLINE.self::NEWLINE); $this->Comment('Export Started: '.date('Y-m-d H:i:s')); + + return $fp; } /** @@ -332,10 +337,6 @@ protected function _ExportTable($TableName, $Query, $Mappings = array()) { $Value = NULL; } - if ($TableName == 'Permission') { - $Foo = 'Bar'; - } - // Check to see if there is a callback filter. if (isset($Filters[$Field])) { $Callback = $Filters[$Field]; @@ -400,6 +401,32 @@ static function FormatElapsed($Start, $End = NULL) { return $Result; } + static function FormatValue($Value) { + static $EscapeSearch = NULL; if ($EscapeSearch === NULL) $EscapeSearch = array(self::ESCAPE, self::DELIM, self::NEWLINE, self::QUOTE); // escape must go first + static $EscapeReplace = NULL; if ($EscapeReplace === NULL) $EscapeReplace = array(self::ESCAPE.self::ESCAPE, self::ESCAPE.self::DELIM, self::ESCAPE.self::NEWLINE, self::ESCAPE.self::QUOTE); + + // Format the value for writing. + if (is_null($Value)) { + $Value = self::NULL; + } elseif (is_numeric($Value)) { + // Do nothing, formats as is. + } elseif (is_string($Value)) { + if($Mb && mb_detect_encoding($Value) != 'UTF-8') + $Value = utf8_encode($Value); + + $Value = str_replace(array("\r\n", "\r"), array(self::NEWLINE, self::NEWLINE), $Value); + $Value = self::QUOTE + .str_replace($EscapeSearch, $EscapeReplace, $Value) + .self::QUOTE; + } elseif (is_bool($Value)) { + $Value = $Value ? 1 : 0; + } else { + // Unknown format. + $Value = self::NULL; + } + return $Value; + } + public function GetCharacterSet($Table) { // First get the collation for the database. $Data = $this->Query("show table status like ':_{$Table}';"); @@ -756,5 +783,31 @@ public function VerifySource($RequiredTables) { return 'Missing required database tables: '.$MissingTables; } } + + public function WriteBeginTable($fp, $TableName, $Columns) { + $TableHeader = ''; + + foreach($Columns as $Key => $Value) { + if (is_numeric($Key)) { + $Column = $Value; + $Type = ''; + } else { + $Column = $Key; + $Type = $Value; + } + + if(strlen($TableHeader) > 0) + $TableHeader .= self::DELIM; + + if ($Type) + $TableHeader .= $Column.':'.$Type; + else + $TableHeader .= $Column; + } + + fwrite($fp, 'Table: '.$TableName.self::NEWLINE); + fwrite($fp, $TableHeader.self::NEWLINE); + + } } ?> \ No newline at end of file diff --git a/class.vanilla2.php b/class.vanilla2.php index eaf89b85..19e8456b 100644 --- a/class.vanilla2.php +++ b/class.vanilla2.php @@ -13,10 +13,46 @@ class Vanilla2 extends ExportController { protected $_SourceTables = array(); /** - * Forum-specific export format + * @param ExportModel $Ex */ protected function ForumExport($Ex) { - + $Tables = array( + 'Activity', + 'Category', + 'Comment', + 'Conversation', + 'ConversationMessage', + 'Discussion', + 'Media', + 'Permission', + 'Role', + 'User', + 'UserConversation', + 'UserDiscussion', + 'UserMeta', + 'UserRole'); + + $Ex->BeginExport('', 'Vanilla 2.*', array('HashMethod' => 'Vanilla')); + + foreach ($Tables as $TableName) { + $this->ExportTable($Ex, $TableName); + } + + $Ex->EndExport(); + } + + /** + * + * @param ExportModel $Ex + * @param string $TableName + */ + protected function ExportTable($Ex, $TableName) { + // Make sure the table exists. + if (!$Ex->Exists($TableName)) + return; + + $Ex->ExportTable($TableName, "select * from :_{$TableName}"); } -} \ No newline at end of file +} +?> \ No newline at end of file diff --git a/index.php b/index.php index b1b3edb9..2906ddf4 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,6 @@ array(name, prefix) */ $Supported = array( 'vanilla1' => array('name'=> 'Vanilla 1.*', 'prefix'=>'LUM_'), + 'vanilla2' => array('name'=> 'Vanilla 2.*', 'prefix'=>'GDN_'), 'vbulletin' => array('name'=>'vBulletin 3.* and 4.*', 'prefix'=>'vb_'), 'phpbb2' => array('name'=>'phpBB 2.*', 'prefix' => 'phpbb_'), 'phpbb3' => array('name'=>'phpBB 3.*', 'prefix' => 'phpbb_'), 'bbPress' => array('name'=>'bbPress 1.*', 'prefix' => 'bb_'), - 'SimplePress' => array('name'=>'SimpePress 1.*', 'prefix' => 'wp_') + 'SimplePress' => array('name'=>'SimpePress 1.*', 'prefix' => 'wp_'), + 'SMF' => array('name'=>'SMF (Simple Machines) 1.*', 'prefx' => 'smf_') ); // Support Files @@ -42,11 +44,13 @@ include('class.exportcontroller.php'); include('class.vanilla1.php'); +include('class.vanilla2.php'); include('class.vbulletin.php'); include('class.phpbb2.php'); include('class.phpbb3.php'); include('class.bbpress.php'); include('class.simplepress.php'); +include('class.smf.php'); // Make sure a default time zone is set if (ini_get('date.timezone') == '') diff --git a/vanilla2export.php b/vanilla2export.php index 6dbc2ddc..951fd60e 100644 --- a/vanilla2export.php +++ b/vanilla2export.php @@ -2,7 +2,7 @@ array(name, prefix) */ $Supported = array( 'vanilla1' => array('name'=> 'Vanilla 1.*', 'prefix'=>'LUM_'), + 'vanilla2' => array('name'=> 'Vanilla 2.*', 'prefix'=>'GDN_'), 'vbulletin' => array('name'=>'vBulletin 3.* and 4.*', 'prefix'=>'vb_'), 'phpbb2' => array('name'=>'phpBB 2.*', 'prefix' => 'phpbb_'), 'phpbb3' => array('name'=>'phpBB 3.*', 'prefix' => 'phpbb_'), 'bbPress' => array('name'=>'bbPress 1.*', 'prefix' => 'bb_'), - 'SimplePress' => array('name'=>'SimpePress 1.*', 'prefix' => 'wp_') + 'SimplePress' => array('name'=>'SimpePress 1.*', 'prefix' => 'wp_'), + 'SMF' => array('name'=>'SMF (Simple Machines) 1.*', 'prefx' => 'smf_') ); // Support Files @@ -90,6 +92,9 @@ class ExportModel { */ public $Prefix = ''; + /** @var string The path to the source of the export in the case where a file is being converted. */ + public $SourcePath = ''; + /** * @var array Strucutes that define the format of the export tables. */ @@ -248,6 +253,8 @@ public function BeginExport($Path = '', $Source = '', $Header = array()) { } fwrite($fp, self::NEWLINE.self::NEWLINE); $this->Comment('Export Started: '.date('Y-m-d H:i:s')); + + return $fp; } /** @@ -375,10 +382,6 @@ protected function _ExportTable($TableName, $Query, $Mappings = array()) { $Value = NULL; } - if ($TableName == 'Permission') { - $Foo = 'Bar'; - } - // Check to see if there is a callback filter. if (isset($Filters[$Field])) { $Callback = $Filters[$Field]; @@ -443,6 +446,32 @@ static function FormatElapsed($Start, $End = NULL) { return $Result; } + static function FormatValue($Value) { + static $EscapeSearch = NULL; if ($EscapeSearch === NULL) $EscapeSearch = array(self::ESCAPE, self::DELIM, self::NEWLINE, self::QUOTE); // escape must go first + static $EscapeReplace = NULL; if ($EscapeReplace === NULL) $EscapeReplace = array(self::ESCAPE.self::ESCAPE, self::ESCAPE.self::DELIM, self::ESCAPE.self::NEWLINE, self::ESCAPE.self::QUOTE); + + // Format the value for writing. + if (is_null($Value)) { + $Value = self::NULL; + } elseif (is_numeric($Value)) { + // Do nothing, formats as is. + } elseif (is_string($Value)) { + if($Mb && mb_detect_encoding($Value) != 'UTF-8') + $Value = utf8_encode($Value); + + $Value = str_replace(array("\r\n", "\r"), array(self::NEWLINE, self::NEWLINE), $Value); + $Value = self::QUOTE + .str_replace($EscapeSearch, $EscapeReplace, $Value) + .self::QUOTE; + } elseif (is_bool($Value)) { + $Value = $Value ? 1 : 0; + } else { + // Unknown format. + $Value = self::NULL; + } + return $Value; + } + public function GetCharacterSet($Table) { // First get the collation for the database. $Data = $this->Query("show table status like ':_{$Table}';"); @@ -799,6 +828,32 @@ public function VerifySource($RequiredTables) { return 'Missing required database tables: '.$MissingTables; } } + + public function WriteBeginTable($fp, $TableName, $Columns) { + $TableHeader = ''; + + foreach($Columns as $Key => $Value) { + if (is_numeric($Key)) { + $Column = $Value; + $Type = ''; + } else { + $Column = $Key; + $Type = $Value; + } + + if(strlen($TableHeader) > 0) + $TableHeader .= self::DELIM; + + if ($Type) + $TableHeader .= $Column.':'.$Type; + else + $TableHeader .= $Column; + } + + fwrite($fp, 'Table: '.$TableName.self::NEWLINE); + fwrite($fp, $TableHeader.self::NEWLINE); + + } } ?> columns */ + protected $_SourceTables = array(); + + /** + * @param ExportModel $Ex + */ + protected function ForumExport($Ex) { + $Tables = array( + 'Activity', + 'Category', + 'Comment', + 'Conversation', + 'ConversationMessage', + 'Discussion', + 'Media', + 'Permission', + 'Role', + 'User', + 'UserConversation', + 'UserDiscussion', + 'UserMeta', + 'UserRole'); + + $Ex->BeginExport('', 'Vanilla 2.*', array('HashMethod' => 'Vanilla')); + + foreach ($Tables as $TableName) { + $this->ExportTable($Ex, $TableName); + } + + $Ex->EndExport(); + } + + /** + * + * @param ExportModel $Ex + * @param string $TableName + */ + protected function ExportTable($Ex, $TableName) { + // Make sure the table exists. + if (!$Ex->Exists($TableName)) + return; + + $Ex->ExportTable($TableName, "select * from :_{$TableName}"); + } + +} +?> columns */ + protected $SourceTables = array( + 'boards' => array(), + 'messages' => array(), + 'personal_messages' => array(), + 'pm_recipients' => array(), + 'categories' => array('ID_CAT', 'name', 'catOrder'), + 'membergroups' => array(), + 'members' => array('ID_MEMBER', 'memberName', 'passwd', 'emailAddress', 'dateRegistered') + ); + + /** + * Forum-specific export format. + * @param ExportModel $Ex + */ + protected function ForumExport($Ex) { + // Begin + $Ex->BeginExport('', 'SMF 1.*', array('HashMethod' => 'Django')); + + // Users + $User_Map = array( + 'ID_MEMBER'=>'UserID', + 'memberName'=>'Name', + 'password'=>'Password', + 'emailAddress'=>'Email', + 'DateInserted'=>'DateInserted', + 'timeOffset'=>'HourOffset', + 'posts'=>'CountComments', + 'avatar'=>'Photo', + 'birthdate'=>'DateOfBirth', + 'DateFirstVisit'=>'DateFirstVisit', + 'DateLastActive'=>'DateLastActive', + 'DateUpdated'=>'DateUpdated' + ); + $Ex->ExportTable('User', " + select *, + from_unixtime(dateRegistered) as DateInserted, + from_unixtime(dateRegistered) as DateFirstVisit, + from_unixtime(lastLogin) as DateLastActive, + from_unixtime(lastLogin) as DateUpdated, + concat('sha1$', lower(memberName), '$', passwd) as `password` + from :_members", $User_Map); + + // Roles + $Role_Map = array( + 'ID_GROUP'=>'RoleID', + 'groupName'=>'Name' + ); + $Ex->ExportTable('Role', "select * from :_membergroups", $Role_Map); + + // UserRoles + $UserRole_Map = array( + 'ID_MEMBER'=>'UserID', + 'ID_GROUP'=>'RoleID' + ); + $Ex->ExportTable('UserRole', "select * from :_members", $UserRole_Map); + + // Categories + $Category_Map = array( + 'Name' => array('Column' => 'Name', 'Filter' => array($this, 'DecodeNumericEntity')) + ); + + $Ex->ExportTable('Category', + " + select + (`ID_CAT` + 1000000) as `CategoryID`, + `name` as `Name`, + '' as `Description`, + null as `ParentCategoryID`, + `catOrder` as `Sort` + from :_categories + + union + + select + `ID_BOARD` as `CategoryID`, + `name` as `Name`, + `description` as `Description`, + (CASE WHEN `ID_PARENT` = 0 THEN (`ID_CAT` + 1000000) ELSE `ID_PARENT` END) as `ParentCategoryID`, + `boardOrder` as `Sort` + from :_boards + + ", $Category_Map); + + // Discussions + $Discussion_Map = array( + 'ID_TOPIC' => 'DiscussionID', + 'subject' => array('Column'=>'Name', 'Filter' => array($this, 'DecodeNumericEntity')), //,'Filter'=>'bb2html'), + 'body' => array('Column'=>'Body'), //,'Filter'=>'bb2html'), + 'Format'=>'Format', + 'ID_BOARD'=> 'CategoryID', + 'DateInserted'=>'DateInserted', + 'DateUpdated'=>'DateUpdated', + 'ID_MEMBER'=>'InsertUserID', + 'DateLastComment'=>'DateLastComment', + 'UpdateUserID'=>'UpdateUserID', + 'locked'=>'Closed', + 'isSticky'=>'Announce', + 'CountComments'=>'CountComments', + 'numViews'=>'CountViews', + 'LastCommentUserID'=>'LastCommentUserID', + 'ID_LAST_MSG'=>'LastCommentID' + ); + $Ex->ExportTable('Discussion', " + select t.*, + (t.numReplies + 1) as CountComments, + m.subject, + m.body, + from_unixtime(m.posterTime) as DateInserted, + from_unixtime(m.modifiedTime) as DateUpdated, + m.ID_MEMBER, + from_unixtime(m_end.posterTime) AS DateLastComment, + m_end.ID_MEMBER AS UpdateUserID, + m_end.ID_MEMBER AS LastCommentUserID, + 'BBCode' as Format + from :_topics t + join :_messages as m on t.ID_FIRST_MSG = m.ID_MSG + join :_messages as m_end on t.ID_LAST_MSG = m_end.ID_MSG + + -- where t.spam = 0 AND m.spam = 0; + + ", $Discussion_Map); + + // Comments + $Comment_Map = array( + 'ID_MSG' => 'CommentID', + 'ID_TOPIC' => 'DiscussionID', + 'Format' => 'Format', + 'body' => array('Column'=>'Body'), //,'Filter'=>'bb2html'), + 'ID_MEMBER' => 'InsertUserID', + 'DateInserted' => 'DateInserted' + ); + $Ex->ExportTable('Comment', + "select m.*, + from_unixtime(m.posterTime) AS DateInserted, + 'BBCode' as Format + from :_messages m + join :_topics t on m.ID_TOPIC = t.ID_TOPIC + where m.ID_MSG <> t.ID_FIRST_MSG; + ", $Comment_Map); + + // Conversation. + $Conv_Map = array( + 'ID_PM' => 'ConversationID', + 'ID_MEMBER_FROM' => 'InsertUserID', + 'DateInserted' => 'DateInserted' + ); + $Ex->ExportTable('Conversation', + "select *, from_unixtime(msgtime) as DateInserted + from :_personal_messages + where deletedBySender = 0", $Conv_Map); + + // ConversationMessage. + $ConvMessage_Map = array( + 'MessageID' => 'MessageID', + 'ConversationID' => 'ConversationID', + 'DateInserted' => 'DateInserted', + 'ID_MEMBER_FROM' => 'InsertUserID', + 'body' => array('Column'=>'Body','Filter'=>'bb2html') + ); + $Ex->ExportTable('ConversationMessage', + "select *, ID_PM AS MessageID, ID_PM AS ConversationID, from_unixtime(msgtime) as DateInserted + from :_personal_messages + where deletedBySender = 0", $ConvMessage_Map); + + // UserConversation. + $UserConv_Map = array( + 'ID_MEMBER' => 'UserID', + 'ConversationID' => 'ConversationID', + 'LastMessageID' => 'LastMessageID' + ); + $Ex->ExportTable('UserConversation', + "select *, ID_PM AS LastMessageID, ID_PM AS ConversationID + from :_pm_recipients + where deletedBySender = 0", $UserConv_Map); + + // End + $Ex->EndExport(); + } + + function DecodeNumericEntity($Text) { + if (function_exists('mb_decode_numericentity')) { + $convmap = array(0x0, 0x2FFFF, 0, 0xFFFF); + return mb_decode_numericentity($Text, $convmap, 'UTF-8'); + } else { + return $Text; + } + } + + function _pcreEntityToUtf($matches) { + $char = intval(is_array($matches) ? $matches[1] : $matches); + + if ($char < 0x80) { + // to prevent insertion of control characters + if ($char >= 0x20) + return htmlspecialchars(chr($char)); + else + return "&#$char;"; + } else if ($char < 0x80000) { + return chr(0xc0 | (0x1f & ($char >> 6))) . chr(0x80 | (0x3f & $char)); + } else { + return chr(0xe0 | (0x0f & ($char >> 12))) . chr(0x80 | (0x3f & ($char >> 6))). chr(0x80 | (0x3f & $char)); + } + } +} + +function bb2html($text) +{ + global $txt, $scripturl, $context, $modSettings, $user_info; + + $user_info['time_format'] = '%Y-%m-%d'; + + // A bit of language stuff used by the parser + $txt['lang_character_set'] = 'utf8'; + $txt['smf238'] = 'Code'; + $txt['smf239'] = 'Quote from'; + $txt['smf240'] = 'Quote'; + $txt[176] = 'on'; + $txt['lang_locale'] = 'en'; + $txt[287] = 'Smiley'; + $txt[288] = 'Angry'; + $txt[289] = 'Cheesy'; + $txt[290] = 'Laugh'; + $txt[291] = 'Sad'; + $txt[292] = 'Wink'; + $txt[293] = 'Grin'; + $txt[294] = 'Shocked'; + $txt[295] = 'Cool'; + $txt[296] = 'Huh'; + $txt[450] = 'Roll Eyes'; + $txt[451] = 'Tongue'; + $txt[526] = 'Embarrassed'; + $txt[527] = 'Lips sealed'; + $txt[528] = 'Undecided'; + $txt[529] = 'Kiss'; + $txt[530] = 'Cry'; + + // URL stuff + $scripturl = dirname($_SERVER['REQUEST_URI']); // TODO: make sure this is right + + // some settings + $modSettings['enableBBC'] = true; + $modSettings['enablePostHTML'] = true; + $modSettings['max_image_width'] = 500; + $modSettings['max_image_height'] = 500; + $modSettings['autoLinkUrls'] = false; + $modSettings['fixLongWords'] = false; + + return parse_bbc($text, false); +} + +// Format a time to make it look purdy. +function timeformat($logTime, $show_today = true) +{ + global $user_info, $txt, $db_prefix, $modSettings, $func; + + // Offset the time. + $time = $logTime + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600; + + // We can't have a negative date (on Windows, at least.) + if ($time < 0) + $time = 0; + + // Today and Yesterday? + if ($modSettings['todayMod'] >= 1 && $show_today === true) + { + // Get the current time. + $nowtime = forum_time(); + + $then = @getdate($time); + $now = @getdate($nowtime); + + // Try to make something of a time format string... + $s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S'; + if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false) + $today_fmt = '%I:%M' . $s . ' %p'; + else + $today_fmt = '%H:%M' . $s; + + // Same day of the year, same year.... Today! + if ($then['yday'] == $now['yday'] && $then['year'] == $now['year']) + return $txt['smf10'] . timeformat($logTime, $today_fmt); + + // Day-of-year is one less and same year, or it's the first of the year and that's the last of the year... + if ($modSettings['todayMod'] == '2' && (($then['yday'] == $now['yday'] - 1 && $then['year'] == $now['year']) || ($now['yday'] == 0 && $then['year'] == $now['year'] - 1) && $then['mon'] == 12 && $then['mday'] == 31)) + return $txt['smf10b'] . timeformat($logTime, $today_fmt); + } + + $str = !is_bool($show_today) ? $show_today : $user_info['time_format']; + + if (setlocale(LC_TIME, $txt['lang_locale'])) + { + foreach (array('%a', '%A', '%b', '%B') as $token) + if (strpos($str, $token) !== false) + $str = str_replace($token, $func['ucwords'](strftime($token, $time)), $str); + } + else + { + // Do-it-yourself time localization. Fun. + foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label) + if (strpos($str, $token) !== false) + $str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str); + if (strpos($str, '%p')) + $str = str_replace('%p', (strftime('%H', $time) < 12 ? 'am' : 'pm'), $str); + } + + // Format any other characters.. + return strftime($str, $time); +} + +// Removes special entities from strings. Compatibility... +function un_htmlspecialchars($string) +{ + return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)) + array(''' => '\'', ' ' => ' ')); +} + +// This gets all possible permutations of an array. +function permute($array) +{ + $orders = array($array); + + $n = count($array); + $p = range(0, $n); + for ($i = 1; $i < $n; null) + { + $p[$i]--; + $j = $i % 2 != 0 ? $p[$i] : 0; + + $temp = $array[$i]; + $array[$i] = $array[$j]; + $array[$j] = $temp; + + for ($i = 1; $p[$i] == 0; $i++) + $p[$i] = 1; + + $orders[] = $array; + } + + return $orders; +} + +// Parse bulletin board code in a string, as well as smileys optionally. +function parse_bbc($message, $smileys = true, $cache_id = '') +{ + global $txt, $scripturl, $context, $modSettings, $user_info; + static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array(); + static $disabled; + + // Never show smileys for wireless clients. More bytes, can't see it anyway :P. + if (WIRELESS) + $smileys = false; + elseif ($smileys !== null && ($smileys == '1' || $smileys == '0')) + $smileys = (bool) $smileys; + + if (empty($modSettings['enableBBC']) && $message !== false) + { + if ($smileys === true) + parsesmileys($message); + + return $message; + } + + // Just in case it wasn't determined yet whether UTF-8 is enabled. + if (!isset($context['utf8'])) + $context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8'; + + // Sift out the bbc for a performance improvement. + if (empty($bbc_codes) || $message === false) + { + if (!empty($modSettings['disabledBBC'])) + { + $temp = explode(',', strtolower($modSettings['disabledBBC'])); + + foreach ($temp as $tag) + $disabled[trim($tag)] = true; + } + + if (empty($modSettings['enableEmbeddedFlash'])) + $disabled['flash'] = true; + + /* The following bbc are formatted as an array, with keys as follows: + + tag: the tag's name - should be lowercase! + + type: one of... + - (missing): [tag]parsed content[/tag] + - unparsed_equals: [tag=xyz]parsed content[/tag] + - parsed_equals: [tag=parsed data]parsed content[/tag] + - unparsed_content: [tag]unparsed content[/tag] + - closed: [tag], [tag/], [tag /] + - unparsed_commas: [tag=1,2,3]parsed content[/tag] + - unparsed_commas_content: [tag=1,2,3]unparsed content[/tag] + - unparsed_equals_content: [tag=...]unparsed content[/tag] + + parameters: an optional array of parameters, for the form + [tag abc=123]content[/tag]. The array is an associative array + where the keys are the parameter names, and the values are an + array which may contain the following: + - match: a regular expression to validate and match the value. + - quoted: true if the value should be quoted. + - validate: callback to evaluate on the data, which is $data. + - value: a string in which to replace $1 with the data. + either it or validate may be used, not both. + - optional: true if the parameter is optional. + + test: a regular expression to test immediately after the tag's + '=', ' ' or ']'. Typically, should have a \] at the end. + Optional. + + content: only available for unparsed_content, closed, + unparsed_commas_content, and unparsed_equals_content. + $1 is replaced with the content of the tag. Parameters + are repalced in the form {param}. For unparsed_commas_content, + $2, $3, ..., $n are replaced. + + before: only when content is not used, to go before any + content. For unparsed_equals, $1 is replaced with the value. + For unparsed_commas, $1, $2, ..., $n are replaced. + + after: similar to before in every way, except that it is used + when the tag is closed. + + disabled_content: used in place of content when the tag is + disabled. For closed, default is '', otherwise it is '$1' if + block_level is false, '
$1
' elsewise. + + disabled_before: used in place of before when disabled. Defaults + to '
' if block_level, '' if not. + + disabled_after: used in place of after when disabled. Defaults + to '
' if block_level, '' if not. + + block_level: set to true the tag is a "block level" tag, similar + to HTML. Block level tags cannot be nested inside tags that are + not block level, and will not be implicitly closed as easily. + One break following a block level tag may also be removed. + + trim: if set, and 'inside' whitespace after the begin tag will be + removed. If set to 'outside', whitespace after the end tag will + meet the same fate. + + validate: except when type is missing or 'closed', a callback to + validate the data as $data. Depending on the tag's type, $data + may be a string or an array of strings (corresponding to the + replacement.) + + quoted: when type is 'unparsed_equals' or 'parsed_equals' only, + may be not set, 'optional', or 'required' corresponding to if + the content may be quoted. This allows the parser to read + [tag="abc]def[esdf]"] properly. + + require_parents: an array of tag names, or not set. If set, the + enclosing tag *must* be one of the listed tags, or parsing won't + occur. + + require_children: similar to require_parents, if set children + won't be parsed if they are not in the list. + + disallow_children: similar to, but very different from, + require_children, if it is set the listed tags will not be + parsed inside the tag. + */ + + $codes = array( + array( + 'tag' => 'abbr', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'quoted' => 'optional', + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'acronym', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'quoted' => 'optional', + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'anchor', + 'type' => 'unparsed_equals', + 'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'b', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'black', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'blue', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'br', + 'type' => 'closed', + 'content' => '
', + ), + array( + 'tag' => 'code', + 'type' => 'unparsed_content', + 'content' => '
' . $txt['smf238'] . ':
' . ($context['browser']['is_gecko'] ? '
$1
' : '$1') . '
', + // !!! Maybe this can be simplified? + 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', ' + global $context; + + if (!isset($disabled[\'code\'])) + { + $php_parts = preg_split(\'~(<\?php|\?>)~\', $data, -1, PREG_SPLIT_DELIM_CAPTURE); + + for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) + { + // Do PHP code coloring? + if ($php_parts[$php_i] != \'<?php\') + continue; + + $php_string = \'\'; + while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?>\') + { + $php_string .= $php_parts[$php_i]; + $php_parts[$php_i++] = \'\'; + } + $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]); + } + + // Fix the PHP code stuff... + $data = str_replace("
\t
", "\t", implode(\'\', $php_parts)); + + // Older browsers are annoying, aren\'t they? + if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\']) + $data = str_replace("\t", "
\t
", $data); + elseif (!$context[\'browser\'][\'is_gecko\']) + $data = str_replace("\t", "\t", $data); + }'), + 'block_level' => true, + ), + array( + 'tag' => 'code', + 'type' => 'unparsed_equals_content', + 'content' => '
' . $txt['smf238'] . ': ($2)
' . ($context['browser']['is_gecko'] ? '
$1
' : '$1') . '
', + // !!! Maybe this can be simplified? + 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', ' + global $context; + + if (!isset($disabled[\'code\'])) + { + $php_parts = preg_split(\'~(<\?php|\?>)~\', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE); + + for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) + { + // Do PHP code coloring? + if ($php_parts[$php_i] != \'<?php\') + continue; + + $php_string = \'\'; + while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?>\') + { + $php_string .= $php_parts[$php_i]; + $php_parts[$php_i++] = \'\'; + } + $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]); + } + + // Fix the PHP code stuff... + $data[0] = str_replace("
\t
", "\t", implode(\'\', $php_parts)); + + // Older browsers are annoying, aren\'t they? + if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\']) + $data = str_replace("\t", "
\t
", $data); + elseif (!$context[\'browser\'][\'is_gecko\']) + $data = str_replace("\t", "\t", $data); + }'), + 'block_level' => true, + ), + array( + 'tag' => 'center', + 'before' => '
', + 'after' => '
', + 'block_level' => true, + ), + array( + 'tag' => 'color', + 'type' => 'unparsed_equals', + 'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,12})\]', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'email', + 'type' => 'unparsed_content', + 'content' => '$1', + // !!! Should this respect guest_hideContacts? + 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
\' => \'\'));'), + ), + array( + 'tag' => 'email', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + // !!! Should this respect guest_hideContacts? + 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'ftp', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
\' => \'\'));'), + ), + array( + 'tag' => 'ftp', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'font', + 'type' => 'unparsed_equals', + 'test' => '[A-Za-z0-9_,\-\s]+?\]', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'flash', + 'type' => 'unparsed_commas_content', + 'test' => '\d+,\d+\]', + 'content' => ($context['browser']['is_ie'] && !$context['browser']['is_mac_ie'] ? '<a href="$1" target="_blank">$1</a>' : '<a href="$1" target="_blank">$1</a>'), + 'validate' => create_function('&$tag, &$data, $disabled', ' + if (isset($disabled[\'url\'])) + $tag[\'content\'] = \'$1\';'), + 'disabled_content' => '$1', + ), + array( + 'tag' => 'green', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'glow', + 'type' => 'unparsed_commas', + 'test' => '[#0-9a-zA-Z\-]{3,12},([012]\d{1,2}|\d{1,2})(,[^]]+)?\]', + 'before' => $context['browser']['is_ie'] ? '
' : '', + 'after' => $context['browser']['is_ie'] ? '
' : '
', + ), + array( + 'tag' => 'hr', + 'type' => 'closed', + 'content' => '
', + 'block_level' => true, + ), + array( + 'tag' => 'html', + 'type' => 'unparsed_content', + 'content' => '$1', + 'block_level' => true, + 'disabled_content' => '$1', + ), + array( + 'tag' => 'img', + 'type' => 'unparsed_content', + 'parameters' => array( + 'alt' => array('optional' => true), + 'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'), + 'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'), + ), + 'content' => '{alt}', + 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
\' => \'\'));'), + 'disabled_content' => '($1)', + ), + array( + 'tag' => 'img', + 'type' => 'unparsed_content', + 'content' => '', + 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
\' => \'\'));'), + 'disabled_content' => '($1)', + ), + array( + 'tag' => 'i', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'iurl', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
\' => \'\'));'), + ), + array( + 'tag' => 'iurl', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'validate' => create_function('&$tag, &$data, $disabled', ' + if (substr($data, 0, 1) == \'#\') + $data = \'#post_\' . substr($data, 1);'), + 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'li', + 'before' => '
  • ', + 'after' => '
  • ', + 'trim' => 'outside', + 'require_parents' => array('list'), + 'block_level' => true, + 'disabled_before' => '', + 'disabled_after' => '
    ', + ), + array( + 'tag' => 'list', + 'before' => '', + 'trim' => 'inside', + 'require_children' => array('li'), + 'block_level' => true, + ), + array( + 'tag' => 'list', + 'parameters' => array( + 'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'), + ), + 'before' => '', + 'trim' => 'inside', + 'require_children' => array('li'), + 'block_level' => true, + ), + array( + 'tag' => 'left', + 'before' => '
    ', + 'after' => '
    ', + 'block_level' => true, + ), + array( + 'tag' => 'ltr', + 'before' => '
    ', + 'after' => '
    ', + 'block_level' => true, + ), + array( + 'tag' => 'me', + 'type' => 'unparsed_equals', + 'before' => '
    * $1 ', + 'after' => '
    ', + 'quoted' => 'optional', + 'block_level' => true, + 'disabled_before' => '/me ', + 'disabled_after' => '
    ', + ), + array( + 'tag' => 'move', + 'before' => '', + 'after' => '', + 'block_level' => true, + ), + array( + 'tag' => 'nobbc', + 'type' => 'unparsed_content', + 'content' => '$1', + ), + array( + 'tag' => 'pre', + 'before' => '
    ',
    +        'after' => '
    ', + ), + array( + 'tag' => 'php', + 'type' => 'unparsed_content', + 'content' => '
    $1
    ', + 'validate' => isset($disabled['php']) ? null : create_function('&$tag, &$data, $disabled', ' + if (!isset($disabled[\'php\'])) + { + $add_begin = substr(trim($data), 0, 5) != \'<?\'; + $data = highlight_php_code($add_begin ? \'<?php \' . $data . \'?>\' : $data); + if ($add_begin) + $data = preg_replace(array(\'~^(.+?)<\?.{0,40}?php( |\s)~\', \'~\?>((?:)*)$~\'), \'$1\', $data, 2); + }'), + 'block_level' => true, + 'disabled_content' => '$1', + ), + array( + 'tag' => 'quote', + 'before' => '
    ' . $txt['smf240'] . '
    ', + 'after' => '
    ', + 'block_level' => true, + ), + array( + 'tag' => 'quote', + 'parameters' => array( + 'author' => array('match' => '(.{1,192}?)', 'quoted' => true, 'validate' => 'parse_bbc'), + ), + 'before' => '
    ' . $txt['smf239'] . ': {author}
    ', + 'after' => '
    ', + 'block_level' => true, + ), + array( + 'tag' => 'quote', + 'type' => 'parsed_equals', + 'before' => '
    ' . $txt['smf239'] . ': $1
    ', + 'after' => '
    ', + 'quoted' => 'optional', + 'block_level' => true, + ), + array( + 'tag' => 'quote', + 'parameters' => array( + 'author' => array('match' => '([^<>]{1,192}?)'), + 'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|action=profile;u=\d+)'), + 'date' => array('match' => '(\d+)', 'validate' => 'timeformat'), + ), + 'before' => '
    ' . $txt['smf239'] . ': {author} ' . $txt[176] . ' {date}
    ', + 'after' => '
    ', + 'block_level' => true, + ), + array( + 'tag' => 'quote', + 'parameters' => array( + 'author' => array('match' => '(.{1,192}?)', 'validate' => 'parse_bbc'), + ), + 'before' => '
    ' . $txt['smf239'] . ': {author}
    ', + 'after' => '
    ', + 'block_level' => true, + ), + array( + 'tag' => 'right', + 'before' => '
    ', + 'after' => '
    ', + 'block_level' => true, + ), + array( + 'tag' => 'red', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'rtl', + 'before' => '
    ', + 'after' => '
    ', + 'block_level' => true, + ), + array( + 'tag' => 's', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'size', + 'type' => 'unparsed_equals', + 'test' => '([1-9][\d]?p[xt]|(?:x-)?small(?:er)?|(?:x-)?large[r]?)\]', + // !!! line-height + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'size', + 'type' => 'unparsed_equals', + 'test' => '[1-9]\]', + // !!! line-height + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'sub', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'sup', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'shadow', + 'type' => 'unparsed_commas', + 'test' => '[#0-9a-zA-Z\-]{3,12},(left|right|top|bottom|[0123]\d{0,2})\]', + 'before' => $context['browser']['is_ie'] ? '' : '', + 'after' => '', + 'validate' => $context['browser']['is_ie'] ? create_function('&$tag, &$data, $disabled', ' + if ($data[1] == \'left\') + $data[1] = 270; + elseif ($data[1] == \'right\') + $data[1] = 90; + elseif ($data[1] == \'top\') + $data[1] = 0; + elseif ($data[1] == \'bottom\') + $data[1] = 180; + else + $data[1] = (int) $data[1];') : create_function('&$tag, &$data, $disabled', ' + if ($data[1] == \'top\' || (is_numeric($data[1]) && $data[1] < 50)) + return \'0 -2px\'; + elseif ($data[1] == \'right\' || (is_numeric($data[1]) && $data[1] < 100)) + return \'2px 0\'; + elseif ($data[1] == \'bottom\' || (is_numeric($data[1]) && $data[1] < 190)) + return \'0 2px\'; + elseif ($data[1] == \'left\' || (is_numeric($data[1]) && $data[1] < 280)) + return \'-2px 0\'; + else + return \'0 0\';'), + ), + array( + 'tag' => 'time', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => create_function('&$tag, &$data, $disabled', ' + if (is_numeric($data)) + $data = timeformat($data); + else + $tag[\'content\'] = \'[time]$1[/time]\';'), + ), + array( + 'tag' => 'tt', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'table', + 'before' => '', + 'after' => '
    ', + 'trim' => 'inside', + 'require_children' => array('tr'), + 'block_level' => true, + ), + array( + 'tag' => 'tr', + 'before' => '', + 'after' => '', + 'require_parents' => array('table'), + 'require_children' => array('td'), + 'trim' => 'both', + 'block_level' => true, + 'disabled_before' => '', + 'disabled_after' => '', + ), + array( + 'tag' => 'td', + 'before' => '', + 'after' => '', + 'require_parents' => array('tr'), + 'trim' => 'outside', + 'block_level' => true, + 'disabled_before' => '', + 'disabled_after' => '', + ), + array( + 'tag' => 'url', + 'type' => 'unparsed_content', + 'content' => '$1', + 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'
    \' => \'\'));'), + ), + array( + 'tag' => 'url', + 'type' => 'unparsed_equals', + 'before' => '', + 'after' => '', + 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), + 'disabled_after' => ' ($1)', + ), + array( + 'tag' => 'u', + 'before' => '', + 'after' => '', + ), + array( + 'tag' => 'white', + 'before' => '', + 'after' => '', + ), + ); + + // This is mainly for the bbc manager, so it's easy to add tags above. Custom BBC should be added above this line. + if ($message === false) + return $codes; + + // So the parser won't skip them. + $itemcodes = array( + '*' => '', + '@' => 'disc', + '+' => 'square', + 'x' => 'square', + '#' => 'square', + 'o' => 'circle', + 'O' => 'circle', + '0' => 'circle', + ); + if (!isset($disabled['li']) && !isset($disabled['list'])) + { + foreach ($itemcodes as $c => $dummy) + $bbc_codes[$c] = array(); + } + + // Inside these tags autolink is not recommendable. + $no_autolink_tags = array( + 'url', + 'iurl', + 'ftp', + 'email', + ); + + // Shhhh! + if (!isset($disabled['color'])) + { + $codes[] = array( + 'tag' => 'chrissy', + 'before' => '', + 'after' => ' :-*', + ); + $codes[] = array( + 'tag' => 'kissy', + 'before' => '', + 'after' => ' :-*', + ); + } + + foreach ($codes as $c) + $bbc_codes[substr($c['tag'], 0, 1)][] = $c; + $codes = null; + } + + // Shall we take the time to cache this? + if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && strlen($message) > 1000) || strlen($message) > 2400)) + { + // It's likely this will change if the message is modified. + $cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . serialize($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']); + + if (($temp = cache_get_data($cache_key, 240)) != null) + return $temp; + + $cache_t = microtime(); + } + + if ($smileys === 'print') + { + // [glow], [shadow], and [move] can't really be printed. + $disabled['glow'] = true; + $disabled['shadow'] = true; + $disabled['move'] = true; + + // Colors can't well be displayed... supposed to be black and white. + $disabled['color'] = true; + $disabled['black'] = true; + $disabled['blue'] = true; + $disabled['white'] = true; + $disabled['red'] = true; + $disabled['green'] = true; + $disabled['me'] = true; + + // Color coding doesn't make sense. + $disabled['php'] = true; + + // Links are useless on paper... just show the link. + $disabled['ftp'] = true; + $disabled['url'] = true; + $disabled['iurl'] = true; + $disabled['email'] = true; + $disabled['flash'] = true; + + // !!! Change maybe? + if (!isset($_GET['images'])) + $disabled['img'] = true; + + // !!! Interface/setting to add more? + } + + $open_tags = array(); + $message = strtr($message, array("\n" => '
    ')); + + // The non-breaking-space looks a bit different each time. + $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{C2A0}' : chr(0xC2) . chr(0xA0)) : '\xA0'; + + $pos = -1; + while ($pos !== false) + { + $last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos; + $pos = strpos($message, '[', $pos + 1); + + // Failsafe. + if ($pos === false || $last_pos > $pos) + $pos = strlen($message) + 1; + + // Can't have a one letter smiley, URL, or email! (sorry.) + if ($last_pos < $pos - 1) + { + // We want to eat one less, and one more, character (for smileys.) + $last_pos = max($last_pos - 1, 0); + $data = substr($message, $last_pos, $pos - $last_pos + 1); + + // Take care of some HTML! + if (!empty($modSettings['enablePostHTML']) && strpos($data, '<') !== false) + { + $data = preg_replace('~<a\s+href=(?:")?((?:http://|ftp://|https://|ftps://|mailto:).+?)(?:")?>~i', '[url=$1]', $data); + $data = preg_replace('~</a>~i', '[/url]', $data); + + //
    should be empty. + $empty_tags = array('br', 'hr'); + foreach ($empty_tags as $tag) + $data = str_replace(array('<' . $tag . '>', '<' . $tag . '/>', '<' . $tag . ' />'), '[' . $tag . ' /]', $data); + + // b, u, i, s, pre... basic tags. + $closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote'); + foreach ($closable_tags as $tag) + { + $diff = substr_count($data, '<' . $tag . '>') - substr_count($data, '</' . $tag . '>'); + $data = strtr($data, array('<' . $tag . '>' => '<' . $tag . '>', '</' . $tag . '>' => '')); + + if ($diff > 0) + $data .= str_repeat('', $diff); + } + + // Do - with security... action= -> action-. + preg_match_all('~<img\s+src=(?:")?((?:http://|ftp://|https://|ftps://).+?)(?:")?(?:\s+alt=(?:")?(.*?)(?:")?)?(?:\s?/)?>~i', $data, $matches, PREG_PATTERN_ORDER); + if (!empty($matches[0])) + { + $replaces = array(); + foreach ($matches[1] as $match => $imgtag) + { + // No alt? + if (!isset($matches[2][$match])) + $matches[2][$match] = ''; + + // Remove action= from the URL - no funny business, now. + if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0) + $imgtag = preg_replace('~action(=|%3d)(?!dlattach)~i', 'action-', $imgtag); + + // Check if the image is larger than allowed. +// if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height'])) +// { +// list ($width, $height) = url_image_size($imgtag); +// +// if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width']) +// { +// $height = (int) (($modSettings['max_image_width'] * $height) / $width); +// $width = $modSettings['max_image_width']; +// } +// +// if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height']) +// { +// $width = (int) (($modSettings['max_image_height'] * $width) / $height); +// $height = $modSettings['max_image_height']; +// } +// +// // Set the new image tag. +// $replaces[$matches[0][$match]] = '' . $matches[2][$match] . ''; +// } +// else + $replaces[$matches[0][$match]] = '' . $matches[2][$match] . ''; + } + + $data = strtr($data, $replaces); + } + } + + if (!empty($modSettings['autoLinkUrls'])) + { + // Are we inside tags that should be auto linked? + $no_autolink_area = false; + if (!empty($open_tags)) + { + foreach ($open_tags as $open_tag) + if (in_array($open_tag['tag'], $no_autolink_tags)) + $no_autolink_area = true; + } + + // Don't go backwards. + //!!! Don't think is the real solution.... + $lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0; + if ($pos < $lastAutoPos) + $no_autolink_area = true; + $lastAutoPos = $pos; + + if (!$no_autolink_area) + { + // Parse any URLs.... have to get rid of the @ problems some things cause... stupid email addresses. + if (!isset($disabled['url']) && (strpos($data, '://') !== false || strpos($data, 'www.') !== false)) + { + // Switch out quotes really quick because they can cause problems. + $data = strtr($data, array(''' => '\'', ' ' => $context['utf8'] ? "\xC2\xA0" : "\xA0", '"' => '>">', '"' => '<"<', '<' => '\.(;\'"]|^)((?:http|https|ftp|ftps)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@,\?&;=#+:\'\\\\]*|[\(\{][\w\-_\~%\.@,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i', '~(?<=[\s>(\'<]|^)(www(?:\.[\w\-_]+)+(?::\d+)?(?:/[\w\-_\~%\.@,\?&;=#+:\'\\\\]*|[\(\{][\w\-_\~%\.@,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i'), array('[url]$1[/url]', '[url=http://$1]$1[/url]'), $data); + $data = strtr($data, array('\'' => ''', $context['utf8'] ? "\xC2\xA0" : "\xA0" => ' ', '>">' => '"', '<"<' => '"', ' '<')); + } + + // Next, emails... + if (!isset($disabled['email']) && strpos($data, '@') !== false) + { + $data = preg_replace('~(?<=[\?\s' . $non_breaking_space . '\[\]()*\\\;>]|^)([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?,\s' . $non_breaking_space . '\[\]()*\\\]|$|
    | |>|<|"|'|\.(?:\.|;| |\s|$|
    ))~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data); + $data = preg_replace('~(?<=
    )([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?\.,;\s' . $non_breaking_space . '\[\]()*\\\]|$|
    | |>|<|"|')~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data); + } + } + } + + $data = strtr($data, array("\t" => '   ')); + + if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) + { + // This is SADLY and INCREDIBLY browser dependent. + if ($context['browser']['is_gecko'] || $context['browser']['is_konqueror']) + $breaker = ' '; + // Opera... + elseif ($context['browser']['is_opera']) + $breaker = ' '; + // Internet Explorer... + else + $breaker = ' '; + + // PCRE will not be happy if we don't give it a short. + $modSettings['fixLongWords'] = (int) min(65535, $modSettings['fixLongWords']); + + // The idea is, find words xx long, and then replace them with xx + space + more. + if (strlen($data) > $modSettings['fixLongWords']) + { + // This is done in a roundabout way because $breaker has "long words" :P. + $data = strtr($data, array($breaker => '< >', ' ' => $context['utf8'] ? "\xC2\xA0" : "\xA0")); + $data = preg_replace( + '~(?<=[>;:!? ' . $non_breaking_space . '\]()]|^)([\w\.]{' . $modSettings['fixLongWords'] . ',})~e' . ($context['utf8'] ? 'u' : ''), + "preg_replace('/(.{" . ($modSettings['fixLongWords'] - 1) . '})/' . ($context['utf8'] ? 'u' : '') . "', '\\\$1< >', '\$1')", + $data); + $data = strtr($data, array('< >' => $breaker, $context['utf8'] ? "\xC2\xA0" : "\xA0" => ' ')); + } + } + + // Do any smileys! + if ($smileys === true) + parsesmileys($data); + + // If it wasn't changed, no copying or other boring stuff has to happen! + if ($data != substr($message, $last_pos, $pos - $last_pos + 1)) + { + $message = substr($message, 0, $last_pos) . $data . substr($message, $pos + 1); + + // Since we changed it, look again incase we added or removed a tag. But we don't want to skip any. + $old_pos = strlen($data) + $last_pos - 1; + $pos = strpos($message, '[', $last_pos); + $pos = $pos === false ? $old_pos : min($pos, $old_pos); + } + } + + // Are we there yet? Are we there yet? + if ($pos >= strlen($message) - 1) + break; + + $tags = strtolower(substr($message, $pos + 1, 1)); + + if ($tags == '/' && !empty($open_tags)) + { + $pos2 = strpos($message, ']', $pos + 1); + if ($pos2 == $pos + 2) + continue; + $look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2)); + + $to_close = array(); + $block_level = null; + do + { + $tag = array_pop($open_tags); + if (!$tag) + break; + + if (!empty($tag['block_level'])) + { + // Only find out if we need to. + if ($block_level === false) + { + array_push($open_tags, $tag); + break; + } + + // The idea is, if we are LOOKING for a block level tag, we can close them on the way. + if (strlen($look_for) > 0 && isset($bbc_codes[$look_for{0}])) + { + foreach ($bbc_codes[$look_for{0}] as $temp) + if ($temp['tag'] == $look_for) + { + $block_level = !empty($temp['block_level']); + break; + } + } + + if ($block_level !== true) + { + $block_level = false; + array_push($open_tags, $tag); + break; + } + } + + $to_close[] = $tag; + } + while ($tag['tag'] != $look_for); + + // Did we just eat through everything and not find it? + if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for))) + { + $open_tags = $to_close; + continue; + } + elseif (!empty($to_close) && $tag['tag'] != $look_for) + { + if ($block_level === null && isset($look_for{0}, $bbc_codes[$look_for{0}])) + { + foreach ($bbc_codes[$look_for{0}] as $temp) + if ($temp['tag'] == $look_for) + { + $block_level = !empty($temp['block_level']); + break; + } + } + + // We're not looking for a block level tag (or maybe even a tag that exists...) + if (!$block_level) + { + foreach ($to_close as $tag) + array_push($open_tags, $tag); + continue; + } + } + + foreach ($to_close as $tag) + { + $message = substr($message, 0, $pos) . $tag['after'] . substr($message, $pos2 + 1); + $pos += strlen($tag['after']); + $pos2 = $pos - 1; + + // See the comment at the end of the big loop - just eating whitespace ;). + if (!empty($tag['block_level']) && substr($message, $pos, 6) == '
    ') + $message = substr($message, 0, $pos) . substr($message, $pos + 6); + if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(
    | |\s)*~', substr($message, $pos), $matches) != 0) + $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); + } + + if (!empty($to_close)) + { + $to_close = array(); + $pos--; + } + + continue; + } + + // No tags for this character, so just keep going (fastest possible course.) + if (!isset($bbc_codes[$tags])) + continue; + + $inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1]; + $tag = null; + foreach ($bbc_codes[$tags] as $possible) + { + // Not a match? + if (strtolower(substr($message, $pos + 1, strlen($possible['tag']))) != $possible['tag']) + continue; + + $next_c = substr($message, $pos + 1 + strlen($possible['tag']), 1); + + // A test validation? + if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + strlen($possible['tag']) + 1)) == 0) + continue; + // Do we want parameters? + elseif (!empty($possible['parameters'])) + { + if ($next_c != ' ') + continue; + } + elseif (isset($possible['type'])) + { + // Do we need an equal sign? + if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=') + continue; + // Maybe we just want a /... + if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + strlen($possible['tag']), 2) != '/]' && substr($message, $pos + 1 + strlen($possible['tag']), 3) != ' /]') + continue; + // An immediate ]? + if ($possible['type'] == 'unparsed_content' && $next_c != ']') + continue; + } + // No type means 'parsed_content', which demands an immediate ] without parameters! + elseif ($next_c != ']') + continue; + + // Check allowed tree? + if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents']))) + continue; + elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children'])) + continue; + // If this is in the list of disallowed child tags, don't parse it. + elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children'])) + continue; + + $pos1 = $pos + 1 + strlen($possible['tag']) + 1; + + // This is long, but it makes things much easier and cleaner. + if (!empty($possible['parameters'])) + { + $preg = array(); + foreach ($possible['parameters'] as $p => $info) + $preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '"') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '"') . ')' . (empty($info['optional']) ? '' : '?'); + + // Okay, this may look ugly and it is, but it's not going to happen much and it is the best way of allowing any order of parameters but still parsing them right. + $match = false; + $orders = permute($preg); + foreach ($orders as $p) + if (preg_match('~^' . implode('', $p) . '\]~i', substr($message, $pos1 - 1), $matches) != 0) + { + $match = true; + break; + } + + // Didn't match our parameter list, try the next possible. + if (!$match) + continue; + + $params = array(); + for ($i = 1, $n = count($matches); $i < $n; $i += 2) + { + $key = strtok(ltrim($matches[$i]), '='); + if (isset($possible['parameters'][$key]['value'])) + $params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1])); + elseif (isset($possible['parameters'][$key]['validate'])) + $params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]); + else + $params['{' . $key . '}'] = $matches[$i + 1]; + + // Just to make sure: replace any $ or { so they can't interpolate wrongly. + $params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '$', '{' => '{')); + } + + foreach ($possible['parameters'] as $p => $info) + { + if (!isset($params['{' . $p . '}'])) + $params['{' . $p . '}'] = ''; + } + + $tag = $possible; + + // Put the parameters into the string. + if (isset($tag['before'])) + $tag['before'] = strtr($tag['before'], $params); + if (isset($tag['after'])) + $tag['after'] = strtr($tag['after'], $params); + if (isset($tag['content'])) + $tag['content'] = strtr($tag['content'], $params); + + $pos1 += strlen($matches[0]) - 1; + } + else + $tag = $possible; + break; + } + + // Item codes are complicated buggers... they are implicit [li]s and can make [list]s! + if ($smileys !== false && $tag === null && isset($itemcodes[substr($message, $pos + 1, 1)]) && substr($message, $pos + 2, 1) == ']' && !isset($disabled['list']) && !isset($disabled['li'])) + { + if (substr($message, $pos + 1, 1) == '0' && !in_array(substr($message, $pos - 1, 1), array(';', ' ', "\t", '>'))) + continue; + $tag = $itemcodes[substr($message, $pos + 1, 1)]; + + // First let's set up the tree: it needs to be in a list, or after an li. + if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li')) + { + $open_tags[] = array( + 'tag' => 'list', + 'after' => '', + 'block_level' => true, + 'require_children' => array('li'), + 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, + ); + $code = '
      '; + } + // We're in a list item already: another itemcode? Close it first. + elseif ($inside['tag'] == 'li') + { + array_pop($open_tags); + $code = ''; + } + else + $code = ''; + + // Now we open a new tag. + $open_tags[] = array( + 'tag' => 'li', + 'after' => '', + 'trim' => 'outside', + 'block_level' => true, + 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null, + ); + + // First, open the tag... + $code .= ''; + $message = substr($message, 0, $pos) . $code . substr($message, $pos + 3); + $pos += strlen($code) - 1; + + // Next, find the next break (if any.) If there's more itemcode after it, keep it going - otherwise close! + $pos2 = strpos($message, '
      ', $pos); + $pos3 = strpos($message, '[/', $pos); + if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false)) + { + preg_match('~^(
      | |\s|\[)+~', substr($message, $pos2 + 6), $matches); + $message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2); + + $open_tags[count($open_tags) - 2]['after'] = '
    '; + } + // Tell the [list] that it needs to close specially. + else + { + // Move the li over, because we're not sure what we'll hit. + $open_tags[count($open_tags) - 1]['after'] = ''; + $open_tags[count($open_tags) - 2]['after'] = ''; + } + + continue; + } + + // Implicitly close lists and tables if something other than what's required is in them. This is needed for itemcode. + if ($tag === null && $inside !== null && !empty($inside['require_children'])) + { + array_pop($open_tags); + + $message = substr($message, 0, $pos) . $inside['after'] . substr($message, $pos); + $pos += strlen($inside['after']) - 1; + } + + // No tag? Keep looking, then. Silly people using brackets without actual tags. + if ($tag === null) + continue; + + // Propagate the list to the child (so wrapping the disallowed tag won't work either.) + if (isset($inside['disallow_children'])) + $tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children']; + + // Is this tag disabled? + if (isset($disabled[$tag['tag']])) + { + if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content'])) + { + $tag['before'] = !empty($tag['block_level']) ? '
    ' : ''; + $tag['after'] = !empty($tag['block_level']) ? '
    ' : ''; + $tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '
    $1
    ' : '$1'); + } + elseif (isset($tag['disabled_before']) || isset($tag['disabled_after'])) + { + $tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '
    ' : ''); + $tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '
    ' : ''); + } + else + $tag['content'] = $tag['disabled_content']; + } + + // The only special case is 'html', which doesn't need to close things. + if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level'])) + { + $n = count($open_tags) - 1; + while (empty($open_tags[$n]['block_level']) && $n >= 0) + $n--; + + // Close all the non block level tags so this tag isn't surrounded by them. + for ($i = count($open_tags) - 1; $i > $n; $i--) + { + $message = substr($message, 0, $pos) . $open_tags[$i]['after'] . substr($message, $pos); + $pos += strlen($open_tags[$i]['after']); + $pos1 += strlen($open_tags[$i]['after']); + + // Trim or eat trailing stuff... see comment at the end of the big loop. + if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 6) == '
    ') + $message = substr($message, 0, $pos) . substr($message, $pos + 6); + if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(
    | |\s)*~', substr($message, $pos), $matches) != 0) + $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0])); + + array_pop($open_tags); + } + } + + // No type means 'parsed_content'. + if (!isset($tag['type'])) + { + // !!! Check for end tag first, so people can say "I like that [i] tag"? + $open_tags[] = $tag; + $message = substr($message, 0, $pos) . $tag['before'] . substr($message, $pos1); + $pos += strlen($tag['before']) - 1; + } + // Don't parse the content, just skip it. + elseif ($tag['type'] == 'unparsed_content') + { + $pos2 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos1); + if ($pos2 === false) + continue; + + $data = substr($message, $pos1, $pos2 - $pos1); + + if (!empty($tag['block_level']) && substr($data, 0, 6) == '
    ') + $data = substr($data, 6); + + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + $code = strtr($tag['content'], array('$1' => $data)); + $message = substr($message, 0, $pos) . $code . substr($message, $pos2 + 3 + strlen($tag['tag'])); + $pos += strlen($code) - 1; + } + // Don't parse the content, just skip it. + elseif ($tag['type'] == 'unparsed_equals_content') + { + // The value may be quoted for some tags - check. + if (isset($tag['quoted'])) + { + $quoted = substr($message, $pos1, 6) == '"'; + if ($tag['quoted'] != 'optional' && !$quoted) + continue; + + if ($quoted) + $pos1 += 6; + } + else + $quoted = false; + + $pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); + if ($pos2 === false) + continue; + $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2); + if ($pos3 === false) + continue; + + $data = array( + substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))), + substr($message, $pos1, $pos2 - $pos1) + ); + + if (!empty($tag['block_level']) && substr($data[0], 0, 6) == '
    ') + $data[0] = substr($data[0], 6); + + // Validation for my parking, please! + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + $code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1])); + $message = substr($message, 0, $pos) . $code . substr($message, $pos3 + 3 + strlen($tag['tag'])); + $pos += strlen($code) - 1; + } + // A closed tag, with no content or value. + elseif ($tag['type'] == 'closed') + { + $pos2 = strpos($message, ']', $pos); + $message = substr($message, 0, $pos) . $tag['content'] . substr($message, $pos2 + 1); + $pos += strlen($tag['content']) - 1; + } + // This one is sorta ugly... :/. Unforunately, it's needed for flash. + elseif ($tag['type'] == 'unparsed_commas_content') + { + $pos2 = strpos($message, ']', $pos1); + if ($pos2 === false) + continue; + $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2); + if ($pos3 === false) + continue; + + // We want $1 to be the content, and the rest to be csv. + $data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1)); + $data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1); + + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + $code = $tag['content']; + foreach ($data as $k => $d) + $code = strtr($code, array('$' . ($k + 1) => trim($d))); + $message = substr($message, 0, $pos) . $code . substr($message, $pos3 + 3 + strlen($tag['tag'])); + $pos += strlen($code) - 1; + } + // This has parsed content, and a csv value which is unparsed. + elseif ($tag['type'] == 'unparsed_commas') + { + $pos2 = strpos($message, ']', $pos1); + if ($pos2 === false) + continue; + + $data = explode(',', substr($message, $pos1, $pos2 - $pos1)); + + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + // Fix after, for disabled code mainly. + foreach ($data as $k => $d) + $tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d))); + + $open_tags[] = $tag; + + // Replace them out, $1, $2, $3, $4, etc. + $code = $tag['before']; + foreach ($data as $k => $d) + $code = strtr($code, array('$' . ($k + 1) => trim($d))); + $message = substr($message, 0, $pos) . $code . substr($message, $pos2 + 1); + $pos += strlen($code) - 1; + } + // A tag set to a value, parsed or not. + elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals') + { + // The value may be quoted for some tags - check. + if (isset($tag['quoted'])) + { + $quoted = substr($message, $pos1, 6) == '"'; + if ($tag['quoted'] != 'optional' && !$quoted) + continue; + + if ($quoted) + $pos1 += 6; + } + else + $quoted = false; + + $pos2 = strpos($message, $quoted == false ? ']' : '"]', $pos1); + if ($pos2 === false) + continue; + + $data = substr($message, $pos1, $pos2 - $pos1); + + // Validation for my parking, please! + if (isset($tag['validate'])) + $tag['validate']($tag, $data, $disabled); + + // For parsed content, we must recurse to avoid security problems. + if ($tag['type'] != 'unparsed_equals') + $data = parse_bbc($data); + + $tag['after'] = strtr($tag['after'], array('$1' => $data)); + + $open_tags[] = $tag; + + $code = strtr($tag['before'], array('$1' => $data)); + $message = substr($message, 0, $pos) . $code . substr($message, $pos2 + ($quoted == false ? 1 : 7)); + $pos += strlen($code) - 1; + } + + // If this is block level, eat any breaks after it. + if (!empty($tag['block_level']) && substr($message, $pos + 1, 6) == '
    ') + $message = substr($message, 0, $pos + 1) . substr($message, $pos + 7); + + // Are we trimming outside this tag? + if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(
    | |\s)*~', substr($message, $pos + 1), $matches) != 0) + $message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0])); + } + + // Close any remaining tags. + while ($tag = array_pop($open_tags)) + $message .= $tag['after']; + + if (substr($message, 0, 1) == ' ') + $message = ' ' . substr($message, 1); + + // Cleanup whitespace. + $message = strtr($message, array(' ' => '  ', "\r" => '', "\n" => '
    ', '
    ' => '
     ', ' ' => "\n")); + + // Cache the output if it took some time... + if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05) + cache_put_data($cache_key, $message, 240); + + return $message; +} + +// Parse smileys in the passed message. +function parsesmileys(&$message) +{ + global $modSettings, $db_prefix, $txt, $user_info, $context; + static $smileyfromcache = array(), $smileytocache = array(); + + // No smiley set at all?! + if ($user_info['smiley_set'] == 'none') + return; + + // If the smiley array hasn't been set, do it now. + if (empty($smileyfromcache)) + { + // Use the default smileys if it is disabled. (better for "portability" of smileys.) + if (empty($modSettings['smiley_enable'])) + { + $smileysfrom = array('>:D', ':D', '::)', '>:(', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)'); + $smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', 'smiley.gif', 'wink.gif', 'grin.gif', 'sad.gif', 'shocked.gif', 'cool.gif', 'tongue.gif', 'huh.gif', 'embarrassed.gif', 'lipsrsealed.gif', 'kiss.gif', 'cry.gif', 'undecided.gif', 'azn.gif', 'afro.gif', 'police.gif', 'angel.gif'); + $smileysdescs = array('', $txt[289], $txt[450], $txt[288], $txt[287], $txt[292], $txt[293], $txt[291], $txt[294], $txt[295], $txt[451], $txt[296], $txt[526], $txt[527], $txt[529], $txt[530], $txt[528], '', '', '', ''); + } + else + { + // Load the smileys in reverse order by length so they don't get parsed wrong. + if (($temp = cache_get_data('parsing_smileys', 480)) == null) + { + $result = db_query(" + SELECT code, filename, description + FROM {$db_prefix}smileys", __FILE__, __LINE__); + $smileysfrom = array(); + $smileysto = array(); + $smileysdescs = array(); + while ($row = mysql_fetch_assoc($result)) + { + $smileysfrom[] = $row['code']; + $smileysto[] = $row['filename']; + $smileysdescs[] = $row['description']; + } + mysql_free_result($result); + + cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480); + } + else + list ($smileysfrom, $smileysto, $smileysdescs) = $temp; + } + + // The non-breaking-space is a complex thing... + $non_breaking_space = $context['utf8'] ? ($context['server']['complex_preg_chars'] ? '\x{A0}' : pack('C*', 0xC2, 0xA0)) : '\xA0'; + + // This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:David@bla.com] doesn't parse the :D smiley) + for ($i = 0, $n = count($smileysfrom); $i < $n; $i++) + { + $smileyfromcache[] = '/(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|^)(' . preg_quote($smileysfrom[$i], '/') . '|' . preg_quote(htmlspecialchars($smileysfrom[$i], ENT_QUOTES), '/') . ')(?=[^[:alpha:]0-9]|$)/' . ($context['utf8'] ? 'u' : ''); + // Escape a bunch of smiley-related characters in the description so it doesn't get a double dose :P. + $smileytocache[] = '' . strtr(htmlspecialchars($smileysdescs[$i]), array(':' => ':', '(' => '(', ')' => ')', '$' => '$', '[' => '[')) . ''; + } + } + + // Replace away! + // !!! There must be a way to speed this up. + $message = preg_replace($smileyfromcache, $smileytocache, $message); +} + +// Highlight any code... +function highlight_php_code($code) +{ + global $context; + + // Remove special characters. + $code = un_htmlspecialchars(strtr($code, array('
    ' => "\n", "\t" => 'SMF_TAB();', '[' => '['))); + + $oldlevel = error_reporting(0); + + // It's easier in 4.2.x+. + if (@version_compare(PHP_VERSION, '4.2.0') == -1) + { + ob_start(); + @highlight_string($code); + $buffer = str_replace(array("\n", "\r"), '', ob_get_contents()); + ob_end_clean(); + } + else + $buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true)); + + error_reporting($oldlevel); + + // Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P. + $buffer = preg_replace('~SMF_TAB(<(font color|span style)="[^"]*?">)?\(\);~', "
    \t
    ", $buffer); + + return strtr($buffer, array('\'' => ''', '' => '', '' => '')); +} + + +?>