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' => '
';
+ }
+ // 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']) ? '