. * Contact Vanilla Forums Inc. at support [at] vanillaforums [dot] com * * @package VanillaPorter */ if(defined('DEBUG')) error_reporting(E_ALL); else error_reporting(E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR); ini_set('display_errors', 'on'); ini_set('track_errors', 1); $e = new SMF(); $e->DoExport(); die ("OK"); function url_image_size($url) { global $sourcedir; $t = microtime(); // Get the host to pester... preg_match('~^\w+://(.+?)/(.*)$~', $url, $match); // Can't figure it out, just try the image size. if ($url == '' || $url == 'http://' || $url == 'https://') return false; elseif (!isset($match[1])) $size = @getimagesize($url); else { // Try to connect to the server... give it half a second. $temp = 0; $fp = @fsockopen($match[1], 80, $temp, $temp, 0.5); // Successful? Continue... if ($fp != false) { // Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.) fwrite($fp, 'HEAD /' . $match[2] . ' HTTP/1.1' . "\r\n" . 'Host: ' . $match[1] . "\r\n" . 'User-Agent: PHP/SMF' . "\r\n" . 'Connection: close' . "\r\n\r\n"); // Read in the HTTP/1.1 or whatever. $test = substr(fgets($fp, 11), -1); fclose($fp); // See if it returned a 404/403 or something. if ($test < 4) { $size = @getimagesize($url); // This probably means allow_url_fopen is off, let's try GD. if ($size === false && function_exists('imagecreatefromstring')) { include_once($sourcedir . '/Subs-Package.php'); // It's going to hate us for doing this, but another request... $image = @imagecreatefromstring(fetch_web_data($url)); if ($image !== false) { $size = array(imagesx($image), imagesy($image)); imagedestroy($image); } } } } } // If we didn't get it, we failed. if (!isset($size)) $size = false; // // Didn't work. return $size; } global $Supported; /** @var array Supported forum packages: classname => array(name, prefix) */ $Supported = array( 'vanilla1' => array('name'=> 'Vanilla 1.*', 'prefix'=>'LUM_'), 'vbulletin' => array('name'=>'vBulletin 3.*', 'prefix'=>'vb_'), 'phpbb' => array('name'=>'phpBB 3.*', 'prefix' => 'phpbb_'), 'bbPress' => array('name'=>'bbPress 1.*', 'prefx' => 'bb_'), 'SMF' => array('name'=>'SMF 1.*', 'prefx' => 'smf_') ); // Support Files /* Contents included from class.exportmodel.php */ ?> array( 'ActivityUserID' => 'int', 'RegardingUserID' => 'int', 'Story' => 'text', 'InsertUserID' => 'int', 'DateInserted' => 'datetime'), 'Category' => array( 'CategoryID' => 'int', 'Name' => 'varchar(30)', 'Description' => 'varchar(250)', 'ParentCategoryID' => 'int', 'DateInserted' => 'datetime', 'InsertUserID' => 'int', 'DateUpdated' => 'datetime', 'UpdateUserID' => 'int', 'Sort' => 'int'), 'Comment' => array( 'CommentID' => 'int', 'DiscussionID' => 'int', 'DateInserted' => 'datetime', 'InsertUserID' => 'int', 'DateUpdated' => 'datetime', 'UpdateUserID' => 'int', 'Format' => 'varchar(20)', 'Body' => 'text', 'Score' => 'float'), 'Conversation' => array( 'ConversationID' => 'int', 'FirstMessageID' => 'int', 'DateInserted' => 'datetime', 'InsertUserID' => 'int', 'DateUpdated' => 'datetime', 'UpdateUserID' => 'int'), 'ConversationMessage' => array( 'MessageID' => 'int', 'ConversationID' => 'int', 'Body' => 'text', 'InsertUserID' => 'int', 'DateInserted' => 'datetime'), 'Discussion' => array( 'DiscussionID' => 'int', 'Name' => 'varchar(100)', 'Body' => 'text', 'Format' => 'varchar(20)', 'CategoryID' => 'int', 'DateInserted' => 'datetime', 'InsertUserID' => 'int', 'DateUpdated' => 'datetime', 'UpdateUserID' => 'int', 'DateLastComment' => 'datetime', 'CountComments' => 'int', 'Score' => 'float', 'Closed' => 'tinyint', 'Announce' => 'tinyint', 'Sink' => 'tinyint'), 'Role' => array( 'RoleID' => 'int', 'Name' => 'varchar(100)', 'Description' => 'varchar(200)', 'CanSession' => 'tinyint'), 'User' => array( 'UserID' => 'int', 'Name' => 'varchar(20)', 'Email' => 'varchar(200)', 'Password' => 'varbinary(34)', //'Gender' => array('m', 'f'), 'Score' => 'float', 'InviteUserID' => 'int', 'HourOffset' => 'int', 'CountDiscussions' => 'int', 'CountComments' => 'int', 'Photo' => 'varchar(255)', 'DateOfBirth' => 'datetime', 'DateFirstVisit' => 'datetime', 'DateLastActive' => 'datetime', 'DateInserted' => 'datetime', 'DateUpdated' => 'datetime'), 'UserConversation' => array( 'UserID' => 'int', 'ConversationID' => 'int', 'LastMessageID' => 'int'), 'UserDiscussion' => array( 'UserID' => 'int', 'DiscussionID' => 'int', 'Bookmarked' => 'tinyint', 'DateLastViewed' => 'datetime', 'CountComments' => 'int'), 'UserMeta' => array( 'UserID' => 'int', 'Name' => 'varchar(255)', 'Value' => 'text'), 'UserRole' => array( 'UserID' => 'int', 'RoleID' => 'int') ); /** * @var bool Whether or not to use compression when creating the file. */ protected $_UseCompression = TRUE; protected $_Username; /** * * @var bool Whether or not to stream the export the the output rather than save a file. */ public $UseStreaming = FALSE; /** * Create the export file and begin the export. * @param string $Path The path to the export file. * @param string $Source The source program that created the export. This may be used by the import routine to do additional processing. */ public function BeginExport($Path = '', $Source = '', $Header = array()) { $this->Comments = array(); $this->BeginTime = microtime(TRUE); if($Path) $this->Path = $Path; if(!$this->Path) $this->Path = 'export_'.($this->FilenamePrefix ? $this->FilenamePrefix.'_' : '').date('Y-m-d_His').'.txt'.($this->UseCompression() ? '.gz' : ''); $fp = $this->_OpenFile(); fwrite($fp, 'Vanilla Export: '.$this->Version()); if($Source) fwrite($fp, self::DELIM.' Source: '.$Source); foreach ($Header as $Key => $Value) { fwrite($fp, self::DELIM." $Key: $Value"); } fwrite($fp, self::NEWLINE.self::NEWLINE); $this->Comment('Export Started: '.date('Y-m-d H:i:s')); } /** * Write a comment to the export file. * @param string $Message The message to write. * @param bool $Echo Whether or not to echo the message in addition to writing it to the file. */ public function Comment($Message, $Echo = TRUE) { fwrite($this->_File, self::COMMENT.' '.str_replace(self::NEWLINE, self::NEWLINE.self::COMMENT.' ', $Message).self::NEWLINE); if($Echo) $this->Comments[] = $Message; } /** * End the export and close the export file. This method must be called if BeginExport() has been called or else the export file will not be closed. */ public function EndExport() { $this->EndTime = microtime(TRUE); $this->TotalTime = $this->EndTime - $this->BeginTime; $this->Comment('Export Completed: '.date('Y-m-d H:i:s')); $this->Comment(sprintf('Elapsed Time: %s', self::FormatElapsed($this->TotalTime))); if($this->UseStreaming) { //ob_flush(); } else { if($this->UseCompression() && function_exists('gzopen')) gzclose($this->_File); else fclose($this->_File); } } /** * Export a table to the export file. * @param string $TableName the name of the table to export. This must correspond to one of the accepted Vanilla tables. * @param mixed $Query The query that will fetch the data for the export this can be one of the following: * - String: Represents a string of SQL to execute. * - PDOStatement: Represents an already executed query result set. * - Array: Represents an array of associative arrays or objects containing the data in the export. * @param array $Mappings Specifies mappings, if any, between the source and the export where the keys represent the source columns and the values represent Vanilla columns. * - If you specify a Vanilla column then it must be in the export structure contained in this class. * - If you specify a MySQL type then the column will be added. * - If you specify an array you can have the following keys: Column, and Type where Column represents the new column name and Type represents the MySQL type. * For a list of the export tables and columns see $this->Structure(). */ public function ExportTable($TableName, $Query, $Mappings = array()) { $BeginTime = microtime(TRUE); $RowCount = $this->_ExportTable($TableName, $Query, $Mappings); $EndTime = microtime(TRUE); $Elapsed = self::FormatElapsed($BeginTime, $EndTime); $this->Comment("Exported Table: $TableName ($RowCount rows, $Elapsed)"); fwrite($this->_File, self::NEWLINE); } protected function _ExportTable($TableName, $Query, $Mappings = array()) { $fp = $this->_File; // Make sure the table is valid for export. if (!array_key_exists($TableName, $this->_Structures)) { $this->Comment("Error: $TableName is not a valid export." ." The valid tables for export are ". implode(", ", array_keys($this->_Structures))); fwrite($fp, self::NEWLINE); return; } $Structure = $this->_Structures[$TableName]; // Set the search and replace to escape strings. $EscapeSearch = array(self::ESCAPE, self::DELIM, self::NEWLINE, self::QUOTE); // escape must go first $EscapeReplace = array(self::ESCAPE.self::ESCAPE, self::ESCAPE.self::DELIM, self::ESCAPE.self::NEWLINE, self::ESCAPE.self::QUOTE); $LastID = 0; $IDName = 'NOTSET'; $FirstQuery = TRUE; // Get the filters from the mappings. $Filters = array(); foreach ($Mappings as $Column => $Mapping) { if (is_array($Mapping) &&isset($Mapping['Column']) && isset($Mapping['Filter'])) { $Filters[$Mapping['Column']] = $Mapping['Filter']; } } $Data = $this->Query($Query, $IDName, $LastID, $this->_Limit); $Mb = function_exists('mb_detect_encoding'); // Loop through the data and write it to the file. $RowCount = 0; if ($Data !== FALSE) { while (($Row = mysql_fetch_assoc($Data)) !== FALSE) { $Row = (array)$Row; // export%202010-05-06%20210937.txt $RowCount++; if($FirstQuery) { // Start with the table name. fwrite($fp, 'Table: '.$TableName.self::NEWLINE); // Get the export structure. $ExportStructure = $this->GetExportStructure($Row, $Structure, $Mappings); // Build and write the table header. $TableHeader = $this->_GetTableHeader($ExportStructure, $Structure); fwrite($fp, $TableHeader.self::NEWLINE); $Mappings = array_flip($Mappings); $FirstQuery = FALSE; } $First = TRUE; // Loop through the columns in the export structure and grab their values from the row. $ExRow = array(); foreach ($ExportStructure as $Field => $Type) { // Get the value of the export. if (array_key_exists($Field, $Row)) { // The column has an exact match in the export. $Value = $Row[$Field]; } elseif (array_key_exists($Field, $Mappings)) { // The column is mapped. $Value = $Row[$Mappings[$Field]]; } else { $Value = NULL; } // 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)) { // Check to see if there is a callback filter. if (isset($Filters[$Field])) { $Value = call_user_func($Filters[$Field], $Value, $Field, $Row); } else { 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; } $ExRow[] = $Value; } // Write the data. fwrite($fp, implode(self::DELIM, $ExRow)); // End the record. fwrite($fp, self::NEWLINE); } } if($Data !== FALSE) mysql_free_result($Data); unset($Data); // Write an empty line to signify the end of the table. fwrite($fp, self::NEWLINE); mysql_close(); return $RowCount; } static function FormatElapsed($Start, $End = NULL) { if($End === NULL) $Elapsed = $Start; else $Elapsed = $End - $Start; $m = floor($Elapsed / 60); $s = $Elapsed - $m * 60; $Result = sprintf('%02d:%05.2f', $m, $s); return $Result; } public function GetCharacterSet($Table) { // First get the collation for the database. $Data = $this->Query("show table status like ':_{$Table}';"); if (!$Data) return FALSE; if ($StatusRow = mysql_fetch_assoc($Data)) $Collation = $StatusRow['Collation']; else return FALSE; // Grab the character set from the database. $Data = $this->Query("show collation like '$Collation'"); if (!$Data) return $False; if ($CollationRow = mysql_fetch_assoc($Data)) { $CharacterSet = $CollationRow['Charset']; return $CharacterSet; } return FALSE; } public function GetDatabasePrefixes() { // Grab all of the tables. $Data = $this->Query('show tables'); if ($Data === FALSE) return array(); // Get the names in an array for easier parsing. $Tables = array(); while (($Row = mysql_fetch_array($Data, MYSQL_NUM)) !== FALSE) { $Tables[] = $Row[0]; } sort($Tables); $Prefixes = array(); // Loop through each table and get it's prefixes. foreach ($Tables as $Table) { $PxFound = FALSE; foreach ($Prefixes as $PxIndex => $Px) { $NewPx = $this->_GetPrefix($Table, $Px); if (strlen($NewPx) > 0) { $PxFound = TRUE; if ($NewPx != $Px) { $Prefixes[$PxIndex] = $NewPx; } break; } } if (!$PxFound) { $Prefixes[] = $Table; } } return $Prefixes; } protected function _GetPrefix($A, $B) { $Length = min(strlen($A), strlen($B)); $Prefix = ''; for ($i = 0; $i < $Length; $i++) { if ($A[$i] == $B[$i]) $Prefix .= $A[$i]; else break; } return $Prefix; } public function GetExportStructure($Row, $Structure, &$Mappings) { $ExportStructure = array(); // See what columns from the structure are in // See what columns to add to the end of the structure. foreach($Row as $Column => $X) { if(array_key_exists($Column, $Mappings)) { $Mapping = $Mappings[$Column]; if(is_string($Mapping)) { if(array_key_exists($Mapping, $Structure)) { // This an existing column. $DestColumn = $Mapping; $DestType = $Structure[$DestColumn]; } else { // This is a created column. $DestColumn = $Column; $DestType = $Mapping; } } elseif(is_array($Mapping)) { $DestColumn = $Mapping['Column']; if (isset($Mapping['Type'])) $DestType = $Mapping['Type']; elseif(isset($Structure[$DestColumn])) $DestType = $Structure[$DestColumn]; else $DestType = 'varchar(255)'; $Mappings[$Column] = $DestColumn; } } elseif(array_key_exists($Column, $Structure)) { $DestColumn = $Column; $DestType = $Structure[$Column]; } else { $DestColumn = ''; $DestType = ''; } // Check to see if we have to add the column to the export structure. if($DestColumn && !array_key_exists($DestColumn, $ExportStructure)) { // TODO: Make sure $DestType is a valid MySQL type. $ExportStructure[$DestColumn] = $DestType; } } return $ExportStructure; } protected function _GetTableHeader($Structure, $GlobalStructure) { $TableHeader = ''; foreach($Structure as $Column => $Type) { if(strlen($TableHeader) > 0) $TableHeader .= self::DELIM; if(array_key_exists($Column, $GlobalStructure)) { $TableHeader .= $Column; } else { $TableHeader .= $Column.':'.$Type; } } return $TableHeader; } /** * Decode the HTML out of a value. */ public function HTMLDecoder($Value) { return html_entity_decode($Value, ENT_COMPAT, 'UTF-8'); } /** * vBulletin needs some fields decoded and it won't hurt the others. */ // public function HTMLDecoder($Table, $Field, $Value) { // if(($Table == 'Category' || $Table == 'Discussion') && $Field == 'Name') // return html_entity_decode($Value); // else // return $Value; // } protected function _OpenFile() { if($this->UseStreaming) { /** Setup the output to stream the file. */ // required for IE, otherwise Content-Disposition may be ignored if(ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off'); @ob_end_clean(); $fp = fopen('php://output', 'ab'); header("Content-Disposition: attachment; filename=\"{$this->Path}\""); header('Content-Type: text/plain'); header("Content-Transfer-Encoding: binary"); header('Accept-Ranges: bytes'); header("Cache-control: private"); header('Pragma: private'); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); } else { $this->Path = str_replace(' ', '_', $this->Path); if($this->UseCompression()) $fp = gzopen($this->Path, 'wb'); else $fp = fopen($this->Path, 'wb'); } $this->_File = $fp; return $fp; } /** Execute a SQL query on the current connection. * * @param string $Query The sql to execute. * @return resource The query cursor. */ public function Query($Query, $Buffer = FALSE) { if (isset($this->_LastResult) && is_resource($this->_LastResult)) mysql_free_result($this->_LastResult); $Query = str_replace(':_', $this->Prefix, $Query); // replace prefix. $Connection = mysql_connect($this->_Host, $this->_Username, $this->_Password); mysql_select_db($this->_DbName); mysql_query("set names {$this->CharacterSet}"); if ($Buffer) $Result = mysql_query($Query, $Connection); else { $Result = mysql_unbuffered_query($Query, $Connection); if (is_resource($Result)) $this->_LastResult = $Result; } if ($Result === FALSE) { trigger_error(mysql_error($Connection)); } return $Result; } public function SetConnection($Host = NULL, $Username = NULL, $Password = NULL, $DbName = NULL) { $this->_Host = $Host; $this->_Username = $Username; $this->_Password = $Password; $this->_DbName = $DbName; } /** * Returns an array of all the expected export tables and expected columns in the exports. * When exporting tables using ExportTable() all of the columns in this structure will always be exported in the order here, regardless of how their order in the query. * @return array * @see vnExport::ExportTable() */ public function Structures() { return $this->_Structures; } /** * Whether or not to use compression on the output file. * @param bool $Value The value to set or NULL to just return the value. * @return bool */ public function UseCompression($Value = NULL) { if($Value !== NULL) $this->_UseCompression = $Value; return $this->_UseCompression && !$this->UseStreaming && function_exists('gzopen'); } /** * Returns the version of export file that will be created with this export. * The version is used when importing to determine the format of this file. * @return string */ public function Version() { return VERSION; } /** * Checks whether or not a table and columns exist in the database. * * @param string $Table The name of the table to check. * @param array $Columns An array of column names to check. * @return bool|array The method will return one of the following * - true: If table and all of the columns exist. * - false: If the table does not exist. * - array: The names of the missing columns if one or more columns don't exist. */ public function Exists($Table, $Columns = array()) { $Desc = $this->Query('describe :_'.$Table); if ($Desc === false) return false; if (count($Columns) == 0) return true; $Cols = array(); $Missing = array(); while (($TD = mysql_fetch_assoc($Desc)) !== false) { $Cols[] = $TD['Field']; } foreach ($Columns as $Column) { if (!in_array($Column, $Cols)) $Missing[] = $Column; } mysql_free_result($Desc); return count($Missing) == 0 ? true : $Missing; } /** * Checks all required source tables are present */ public function VerifySource($RequiredTables) { $MissingTables = false; $CountMissingTables = 0; $MissingColumns = array(); foreach($RequiredTables as $ReqTable => $ReqColumns) { $TableDescriptions = $this->Query('describe :_'.$ReqTable); //echo 'describe '.$Prefix.$ReqTable; if($TableDescriptions === false) { // Table doesn't exist $CountMissingTables++; if($MissingTables !== false) $MissingTables .= ', '.$ReqTable; else $MissingTables = $ReqTable; } else { // Build array of columns in this table $PresentColumns = array(); while (($TD = mysql_fetch_assoc($TableDescriptions)) !== false) { $PresentColumns[] = $TD['Field']; } // Compare with required columns foreach($ReqColumns as $ReqCol) { if(!in_array($ReqCol, $PresentColumns)) $MissingColumns[$ReqTable][] = $ReqCol; } mysql_free_result($TableDescriptions); } } // Return results if($MissingTables === false) { if(count($MissingColumns) > 0) { $Result = array(); // Build a string of missing columns. foreach ($MissingColumns as $Table => $Columns) { $Result[] = "The $Table table is missing the following column(s): ".implode(', ', $Columns); } return implode("
\n", $Result); } else return true; // Nothing missing! } elseif($CountMissingTables == count($RequiredTables)) { $Result = 'The required tables are not present in the database. Make sure you entered the correct database name and prefix and try again.'; // Guess the prefixes to notify the user. $Prefixes = $this->GetDatabasePrefixes(); if (count($Prefixes) == 1) $Result .= ' Based on the database you provided, your database prefix is probably '.implode(', ', $Prefixes); elseif (count($Prefixes) > 0) $Result .= ' Based on the database you provided, your database prefix is probably one of the following: '.implode(', ', $Prefixes); return $Result; } else { return 'Missing required database tables: '.$MissingTables; } } } ?>'; ?> Vanilla Porter - Forum Export Tool

Vanilla

Vanilla Porter Version

Welcome to the Vanilla Porter. This application will export your existing forum data to the Vanilla 2 import format. If you want more information on how to use this application go to http://vanillaforums.com/blog/help-topics/importing-data.
"; foreach($Msgs as $Msg) { echo "

$Msg

\n"; } echo ""; if($Path) echo "Download $Path"; } PageFooter(); } function GetValue($Key, $Collection = NULL, $Default = '') { if(!$Collection) $Collection = $_POST; if(array_key_exists($Key, $Collection)) return $Collection[$Key]; return $Default; } ?>HandleInfoForm(); } /** * Logic for export process */ public function DoExport() { global $Supported; // Test connection $Msg = $this->TestDatabase(); if($Msg === true) { // Create db object $Ex = new ExportModel; $Ex->SetConnection($this->DbInfo['dbhost'], $this->DbInfo['dbuser'], $this->DbInfo['dbpass'], $this->DbInfo['dbname']); $Ex->Prefix = $this->DbInfo['prefix']; $Ex->UseStreaming = $this->UseStreaming; // Test src tables' existence structure $Msg = $Ex->VerifySource($this->SourceTables); if($Msg === true) { // Good src tables - Start dump $Ex->UseCompression(TRUE); $Ex->FilenamePrefix = $this->DbInfo['dbname']; set_time_limit(60*60); $this->ForumExport($Ex); // Write the results. if($Ex->UseStreaming) exit; else ViewExportResult($Ex->Comments, 'Info', $Ex->Path); } else ViewForm(array('Supported' => $Supported, 'Msg' => $Msg, 'Info' => $this->DbInfo)); // Back to form with error } else ViewForm(array('Supported' => $Supported, 'Msg' => $Msg, 'Info' => $this->DbInfo)); // Back to form with error } /** * User submitted db connection info */ public function HandleInfoForm() { $this->DbInfo = array( 'dbhost' => 'localhost', 'dbuser' => 'root', 'dbpass' => 'Id0nTkN0w', 'dbname' => 'rock', 'type' => 'SMF', 'prefix' => 'smf_'); $this->UseStreaming = FALSE; } /** * Test database connection info */ public function TestDatabase() { // Connection if($C = @mysql_connect($this->DbInfo['dbhost'], $this->DbInfo['dbuser'], $this->DbInfo['dbpass'])) { // Database if(mysql_select_db($this->DbInfo['dbname'], $C)) { mysql_close($C); return true; } else { mysql_close($C); return 'Could not find database “'.$this->DbInfo['dbname'].'”.'; } } else return 'Could not connect to '.$this->DbInfo['dbhost'].' as '.$this->DbInfo['dbuser'].' with given password.'; } } ?> columns */ public $SourceTables = array( 'User'=> array('UserID', 'Name', 'Password', 'Email', 'CountComments'), 'Role'=> array('RoleID', 'Name', 'Description'), 'Category'=> array('CategoryID', 'Name', 'Description'), 'Discussion'=> array('DiscussionID', 'Name', 'CategoryID', 'DateCreated', 'AuthUserID', 'DateLastActive', 'Closed', 'Sticky', 'CountComments', 'Sink', 'LastUserID'), 'Comment'=> array('CommentID', 'DiscussionID', 'AuthUserID', 'DateCreated', 'EditUserID', 'DateEdited', 'Body') ); /** * Forum-specific export format * @todo Project file size / export time and possibly break into multiple files * */ protected function ForumExport($Ex) { // Get the characterset for the comments. $CharacterSet = $Ex->GetCharacterSet('Comment'); if ($CharacterSet) $Ex->CharacterSet = $CharacterSet; // Begin $Ex->BeginExport('', 'Vanilla 1.*'); // Users $User_Map = array( 'UserID'=>'UserID', 'Name'=>'Name', 'Password'=>'Password', 'Email'=>'Email', 'Icon'=>'Photo', 'CountComments'=>'CountComments' ); $Ex->ExportTable('User', "SELECT * FROM :_User", $User_Map); // ":_" will be replaced by database prefix // Roles /* 'RoleID' => 'int', 'Name' => 'varchar(100)', 'Description' => 'varchar(200)' */ $Role_Map = array( 'RoleID'=>'RoleID', 'Name'=>'Name', 'Description'=>'Description' ); $Ex->ExportTable('Role', 'select * from :_Role', $Role_Map); // UserRoles /* 'UserID' => 'int', 'RoleID' => 'int' */ $UserRole_Map = array( 'UserID' => 'UserID', 'RoleID'=> 'RoleID' ); $Ex->ExportTable('UserRole', 'select UserID, RoleID from :_User', $UserRole_Map); // Categories /* 'CategoryID' => 'int', 'Name' => 'varchar(30)', 'Description' => 'varchar(250)', 'ParentCategoryID' => 'int', 'DateInserted' => 'datetime', 'InsertUserID' => 'int', 'DateUpdated' => 'datetime', 'UpdateUserID' => 'int' */ $Category_Map = array( 'CategoryID' => 'CategoryID', 'Name' => 'Name', 'Description'=> 'Description' ); $Ex->ExportTable('Category', "select CategoryID, Name, Description from :_Category", $Category_Map); // Discussions /* 'DiscussionID' => 'int', 'Name' => 'varchar(100)', 'CategoryID' => 'int', 'Body' => 'text', 'Format' => 'varchar(20)', 'DateInserted' => 'datetime', 'InsertUserID' => 'int', 'DateUpdated' => 'datetime', 'UpdateUserID' => 'int', 'Score' => 'float', 'Announce' => 'tinyint', 'Closed' => 'tinyint' */ $Discussion_Map = array( 'DiscussionID' => 'DiscussionID', 'Name' => 'Name', 'CategoryID'=> 'CategoryID', 'DateCreated'=>'DateInserted', 'DateCreated2'=>'DateUpdated', 'AuthUserID'=>'InsertUserID', 'DateLastActive'=>'DateLastComment', 'AuthUserID2'=>'UpdateUserID', 'Closed'=>'Closed', 'Sticky'=>'Announce', 'CountComments'=>'CountComments', 'Sink'=>'Sink', 'LastUserID'=>'LastCommentUserID' ); $Ex->ExportTable('Discussion', "SELECT d.*, d.LastUserID as LastCommentUserID, d.DateCreated as DateCreated2, d.AuthUserID as AuthUserID2 FROM :_Discussion d WHERE coalesce(d.WhisperUserID, 0) = 0", $Discussion_Map); // Comments /* 'CommentID' => 'int', 'DiscussionID' => 'int', 'DateInserted' => 'datetime', 'InsertUserID' => 'int', 'DateUpdated' => 'datetime', 'UpdateUserID' => 'int', 'Format' => 'varchar(20)', 'Body' => 'text', 'Score' => 'float' */ $Comment_Map = array( 'CommentID' => 'CommentID', 'DiscussionID' => 'DiscussionID', 'AuthUserID' => 'InsertUserID', 'DateCreated' => 'DateInserted', 'EditUserID' => 'UpdateUserID', 'DateEdited' => 'DateUpdated', 'Body' => 'Body', 'FormatType' => 'Format' ); $Ex->ExportTable('Comment', " SELECT c.* FROM :_Comment c JOIN :_Discussion d ON c.DiscussionID = d.DiscussionID WHERE coalesce(d.WhisperUserID, 0) = 0 AND coalesce(c.WhisperUserID, 0) = 0", $Comment_Map); $Ex->ExportTable('UserDiscussion', " SELECT w.UserID, w.DiscussionID, w.CountComments, w.LastViewed as DateLastViewed, case when b.UserID is not null then 1 else 0 end AS Bookmarked FROM :_UserDiscussionWatch w LEFT JOIN :_UserBookmark b ON w.DiscussionID = b.DiscussionID AND w.UserID = b.UserID"); // Conversations // Create a mapping table for conversations. // This cannot be a temporary table because of some of the union selects it is used in below. $Ex->Query("create table LUM_V1Conversation (ConversationID int auto_increment primary key, DiscussionID int, UserID1 int, UserID2 int, DateCreated datetime, EditUserID int, DateEdited datetime)"); $Ex->Query("insert LUM_V1Conversation (DiscussionID, UserID1, UserID2, DateCreated, EditUserID, DateEdited) select DiscussionID, AuthUserID as UserID1, WhisperUserID as UserID2, min(DateCreated), max(EditUserID), max(DateEdited) from LUM_Comment where coalesce(WhisperUserID, 0) <> 0 group by DiscussionID, AuthUserID, WhisperUserID union select DiscussionID, AuthUserID as UserID1, WhisperUserID as UserID2, DateCreated, WhisperFromLastUserID, DateLastWhisper from LUM_Discussion where coalesce(WhisperUserID, 0) <> 0"); // Delete redundant conversations. $Ex->Query("create index ix_V1UserID1 on LUM_V1Conversation (DiscussionID, UserID1)"); // for speed $Ex->Query("delete t.* from LUM_V1Conversation t inner join LUM_Comment c on c.DiscussionID = t.DiscussionID and c.AuthUserID = t.UserID2 and c.WhisperUserID = t.UserID1 and c.AuthUserID < c.WhisperUserID"); $Conversation_Map = array( 'UserID1' => 'InsertUserID', 'DateCreated' => 'DateInserted', 'EditUserID' => 'UpdateUserID', 'DateEdited' => 'DateUpdated' ); $Ex->ExportTable('Conversation', "select * from :_V1Conversation", $Conversation_Map); // ConversationMessage /* 'MessageID' => 'int', 'ConversationID' => 'int', 'Body' => 'text', 'InsertUserID' => 'int', 'DateInserted' => 'datetime' */ $ConversationMessage_Map = array( 'CommentID' => 'MessageID', 'DiscussionID' => 'ConversationID', 'Body' => 'Body', 'AuthUserID' => 'InsertUserID', 'DateCreated' => 'DateInserted' ); $Ex->ExportTable('ConversationMessage', " select c.CommentID, t.ConversationID, c.AuthUserID, c.DateCreated, c.Body from LUM_Comment c join LUM_V1Conversation t on t.DiscussionID = c.DiscussionID and c.WhisperUserID in (t.UserID1, t.UserID2) and c.AuthUserID in (t.UserID1, t.UserID2) where c.WhisperUserID > 0 union select c.CommentID, t.ConversationID, c.AuthUserID, c.DateCreated, c.Body from LUM_Comment c join LUM_Discussion d on c.DiscussionID = d.DiscussionID join LUM_V1Conversation t on t.DiscussionID = d.DiscussionID and d.WhisperUserID in (t.UserID1, t.UserID2) and d.AuthUserID in (t.UserID1, t.UserID2) where d.WhisperUserID > 0", $ConversationMessage_Map); // UserConversation /* 'UserID' => 'int', 'ConversationID' => 'int', 'LastMessageID' => 'int' */ $UserConversation_Map = array( 'UserID' => 'UserID', 'ConversationID' => 'ConversationID' ); $Ex->ExportTable('UserConversation', "select UserID1 as UserID, ConversationID from LUM_V1Conversation union select UserID2 as UserID, ConversationID from LUM_V1Conversation", $UserConversation_Map); $Ex->Query("drop table :_V1Conversation"); // End $Ex->EndExport(); } } ?> columns */ protected $SourceTables = array( 'user' => array('userid','username','password','email','referrerid','timezoneoffset','posts','salt', 'birthday_search','joindate','lastvisit','lastactivity','membergroupids','usergroupid', 'usertitle', 'homepage', 'aim', 'icq', 'yahoo', 'msn', 'skype', 'styleid', 'avatarid'), 'usergroup'=> array('usergroupid','title','description'), 'userfield' => array('userid'), 'phrase' => array('varname','text','product','fieldname','varname'), 'thread' => array('threadid','forumid','postuserid','title','open','sticky','dateline','lastpost'), 'deletionlog' => array('type','primaryid'), 'post' => array('postid','threadid','pagetext','userid','dateline'), 'forum' => array('forumid','description','displayorder','title','description','displayorder'), 'subscribethread' => array('userid','threadid') ); /** * Forum-specific export format * @todo Project file size / export time and possibly break into multiple files */ protected function ForumExport($Ex) { // Begin $Ex->BeginExport('', 'vBulletin 3.*'); // Users $User_Map = array( 'userid'=>'UserID', 'username'=>'Name', 'password2'=>'Password', 'email'=>'Email', 'referrerid'=>'InviteUserID', 'timezoneoffset'=>'HourOffset', //'posts'=>'CountComments', 'salt'=>'char(3)', 'photopath'=>'Photo' ); $Ex->ExportTable('User', "select *, concat(`password`, salt) as password2, concat('avatar', userid, '_', avatarid, '.gif') as photopath, DATE_FORMAT(birthday_search,GET_FORMAT(DATE,'ISO')) as DateOfBirth, FROM_UNIXTIME(joindate) as DateFirstVisit, FROM_UNIXTIME(lastvisit) as DateLastActive, FROM_UNIXTIME(joindate) as DateInserted, FROM_UNIXTIME(lastactivity) as DateUpdated from :_user", $User_Map); // ":_" will be replace by database prefix // Roles $Role_Map = array( 'usergroupid'=>'RoleID', 'title'=>'Name', 'description'=>'Description' ); $Ex->ExportTable('Role', 'select * from :_usergroup', $Role_Map); // UserRoles $UserRole_Map = array( 'userid'=>'UserID', 'usergroupid'=>'RoleID' ); $Ex->Query("CREATE TEMPORARY TABLE VbulletinRoles (userid INT UNSIGNED NOT NULL, usergroupid INT UNSIGNED NOT NULL)"); # Put primary groups into tmp table $Ex->Query("insert into VbulletinRoles (userid, usergroupid) select userid, usergroupid from :_user"); # Put stupid CSV column into tmp table $SecondaryRoles = $Ex->Query("select userid, usergroupid, membergroupids from :_user", TRUE); if (is_resource($SecondaryRoles)) { while (($Row = @mysql_fetch_assoc($SecondaryRoles)) !== false) { if($Row['membergroupids']!='') { $Groups = explode(',',$Row['membergroupids']); foreach($Groups as $GroupID) { $Ex->Query("insert into VbulletinRoles (userid, usergroupid) values({$Row['userid']},{$GroupID})", TRUE); } } } } # Export from our tmp table and drop $Ex->ExportTable('UserRole', 'select distinct userid, usergroupid from VbulletinRoles', $UserRole_Map); $Ex->Query("DROP TABLE IF EXISTS VbulletinRoles"); // UserMeta $Ex->Query("CREATE TEMPORARY TABLE VbulletinUserMeta (`UserID` INT NOT NULL ,`MetaKey` VARCHAR( 64 ) NOT NULL ,`MetaValue` VARCHAR( 255 ) NOT NULL)"); # Standard vB user data $UserFields = array('usertitle', 'homepage', 'aim', 'icq', 'yahoo', 'msn', 'skype', 'styleid'); foreach($UserFields as $Field) $Ex->Query("insert into VbulletinUserMeta (UserID, MetaKey, MetaValue) select userid, '$Field.', $Field from :_user where $Field !=''"); # Dynamic vB user data (userfield) $ProfileFields = $Ex->Query("select varname, text from :_phrase where product='vbulletin' and fieldname='cprofilefield' and varname like 'field%_title'"); if (is_resource($ProfileFields)) { while (($Field = @mysql_fetch_assoc($ProfileFields)) !== false) { //foreach ($ProfileFields as $Field) { $VbulletinField = str_replace('_title','',$Field['varname']); $MetaKey = preg_replace('/[^0-9a-z_-]/','',strtolower($Field['text'])); $Ex->Query("insert into VbulletinUserMeta (UserID, MetaKey, MetaValue) select userid, '".$MetaKey."', ".$VbulletinField." from :_userfield where ".$VbulletinField."!=''"); } } # Export from our tmp table and drop $Ex->ExportTable('UserMeta', 'select UserID, MetaKey as Name, MetaValue as Value from VbulletinUserMeta'); $Ex->Query("DROP TABLE IF EXISTS VbulletinUserMeta"); // Categories $Category_Map = array( 'forumid'=>'CategoryID', 'description'=>'Description', 'Name'=>array('Column'=>'Name','Filter'=>array($Ex, 'HTMLDecoder')), 'displayorder'=>array('Column'=>'Sort', 'Type'=>'int') ); $Ex->ExportTable('Category', "select forumid, left(title,30) as Name, description, displayorder from :_forum where threadcount > 0", $Category_Map); // Discussions $Discussion_Map = array( 'threadid'=>'DiscussionID', 'forumid'=>'CategoryID', 'postuserid'=>'InsertUserID', 'postuserid2'=>'UpdateUserID', 'title'=>array('Column'=>'Name','Filter'=>array($Ex, 'HTMLDecoder')), 'Format'=>'Format' ); $Ex->ExportTable('Discussion', "select t.*, t.postuserid as postuserid2, p.pagetext as Body, 'BBCode' as Format, replycount+1 as CountComments, convert(ABS(open-1),char(1)) as Closed, convert(sticky,char(1)) as Announce, FROM_UNIXTIME(t.dateline) as DateInserted, FROM_UNIXTIME(lastpost) as DateUpdated, FROM_UNIXTIME(lastpost) as DateLastComment from :_thread t left join :_deletionlog d ON (d.type='thread' AND d.primaryid=t.threadid) left join :_post p ON p.postid = t.firstpostid where d.primaryid IS NULL", $Discussion_Map); // Comments $Comment_Map = array( 'postid' => 'CommentID', 'threadid' => 'DiscussionID', 'pagetext' => 'Body', 'Format' => 'Format' ); $Ex->ExportTable('Comment', "select p.*, 'BBCode' as Format, p.userid as InsertUserID, p.userid as UpdateUserID, FROM_UNIXTIME(p.dateline) as DateInserted, FROM_UNIXTIME(p.dateline) as DateUpdated from :_post p inner join :_thread t ON p.threadid = t.threadid left join :_deletionlog d ON (d.type='post' AND d.primaryid=p.postid) where p.postid <> t.firstpostid and d.primaryid IS NULL", $Comment_Map); // UserDiscussion $UserDiscussion_Map = array( 'DateLastViewed' => 'datetime'); $Ex->ExportTable('UserDiscussion', "select tr.userid as UserID, tr.threadid as DiscussionID, FROM_UNIXTIME(tr.readtime) as DateLastViewed, case when st.threadid is not null then 1 else 0 end as Bookmarked from :_threadread tr left join :_subscribethread st on tr.userid = st.userid and tr.threadid = st.threadid"); // Activity (3.8+) $Activity_Map = array( 'postuserid'=>'ActivityUserID', 'userid'=>'RegardingUserID', 'pagetext'=>'Story', 'postuserid'=>'InsertUserID' ); $Tables = $Ex->Query("show tables like ':_visitormessage'"); if (mysql_fetch_assoc($Tables) !== FALSE) { # Table is present $Ex->ExportTable('Activity', "select *, FROM_UNIXTIME(dateline) as DateInserted from :_visitormessage where state='visible'", $Activity_Map); } // End $Ex->EndExport(); } } ?> columns */ protected $SourceTables = array( 'users' => array('user_id', 'username', 'user_password', 'user_email', 'user_timezone', 'user_posts', 'user_regdate', 'user_lastvisit', 'user_regdate'), 'groups' => array('group_id', 'group_name', 'group_desc'), 'user_group' => array('user_id', 'group_id'), 'forums' => array('forum_id', 'forum_name', 'forum_desc', 'left_id', 'parent_id'), 'topics' => array('topic_id', 'forum_id', 'topic_poster', 'topic_title', 'topic_views', 'topic_first_post_id', 'topic_replies', 'topic_status', 'topic_type', 'topic_time', 'topic_last_post_time', 'topic_last_post_time'), 'posts' => array('post_id', 'topic_id', 'post_text', 'poster_id', 'post_edit_user', 'post_time', 'post_edit_time'), 'bookmarks' => array('user_id', 'topic_id') ); /** * Forum-specific export format. * @param ExportModel $Ex */ protected function ForumExport($Ex) { // Begin $Ex->BeginExport('', 'phpBB 3.*', array('HashMethod' => 'phpBB')); // Users $User_Map = array( 'user_id'=>'UserID', 'username'=>'Name', 'user_password'=>'Password', 'user_email'=>'Email', 'user_timezone'=>'HourOffset', 'user_posts'=>array('Column' => 'CountComments', 'Type' => 'int') ); $Ex->ExportTable('User', "select *, FROM_UNIXTIME(nullif(user_regdate, 0)) as DateFirstVisit, FROM_UNIXTIME(nullif(user_lastvisit, 0)) as DateLastActive, FROM_UNIXTIME(nullif(user_regdate, 0)) as DateInserted from :_users", $User_Map); // ":_" will be replace by database prefix // Roles $Role_Map = array( 'group_id'=>'RoleID', 'group_name'=>'Name', 'group_desc'=>'Description' ); $Ex->ExportTable('Role', 'select * from :_groups', $Role_Map); // UserRoles $UserRole_Map = array( 'user_id'=>'UserID', 'group_id'=>'RoleID' ); $Ex->ExportTable('UserRole', 'select user_id, group_id from :_users union select user_id, group_id from :_user_group', $UserRole_Map); // Categories $Category_Map = array( 'forum_id'=>'CategoryID', 'forum_name'=>'Name', 'forum_desc'=>'Description', 'left_id'=>'Sort' ); $Ex->ExportTable('Category', "select *, nullif(parent_id,0) as ParentCategoryID from :_forums", $Category_Map); // Discussions $Discussion_Map = array( 'topic_id'=>'DiscussionID', 'forum_id'=>'CategoryID', 'topic_poster'=>'InsertUserID', 'topic_title'=>'Name', 'Format'=>'Format', 'topic_views'=>'CountViews', 'topic_first_post_id'=>array('Column'=>'FirstCommentID','Type'=>'int') ); $Ex->ExportTable('Discussion', "select t.*, 'BBCode' as Format, topic_replies+1 as CountComments, case t.topic_status when 1 then 1 else 0 end as Closed, case t.topic_type when 1 then 1 else 0 end as Announce, FROM_UNIXTIME(t.topic_time) as DateInserted, FROM_UNIXTIME(t.topic_last_post_time) as DateUpdated, FROM_UNIXTIME(t.topic_last_post_time) as DateLastComment from :_topics t", $Discussion_Map); // Comments $Comment_Map = array( 'post_id' => 'CommentID', 'topic_id' => 'DiscussionID', 'post_text' => array('Column'=>'Body','Filter'=>array($this, 'RemoveBBCodeUIDs')), 'Format' => 'Format', 'poster_id' => 'InsertUserID', 'post_edit_user' => 'UpdateUserID' ); $Ex->ExportTable('Comment', "select p.*, 'BBCode' as Format, FROM_UNIXTIME(p.post_time) as DateInserted, FROM_UNIXTIME(nullif(p.post_edit_time,0)) as DateUpdated from :_posts p", $Comment_Map); // UserDiscussion $UserDiscussion_Map = array( 'user_id' => 'UserID', 'topic_id' => 'DiscussionID'); $Ex->ExportTable('UserDiscussion', "select b.*, 1 as Bookmarked from :_bookmarks b", $UserDiscussion_Map); // End $Ex->EndExport(); } public function RemoveBBCodeUIDs($Value, $Field, $Row) { $UID = $Row['bbcode_uid']; return str_replace(':'.$UID, '', $Value); } } ?> columns */ protected $SourceTables = array( 'forums' => array(), 'posts' => array(), 'topics' => array(), 'users' => array('ID', 'user_nicename', 'user_pass', 'user_email', 'user_registered'), 'meta' => array() ); /** * Forum-specific export format. * @param ExportModel $Ex */ protected function ForumExport($Ex) { // Begin $Ex->BeginExport('', 'bbPress 1.*', array('HashMethod' => 'Vanilla')); // Users $User_Map = array( 'ID'=>'UserID', 'user_nicename'=>'Name', 'user_pass'=>'Password', 'user_email'=>'Email', 'user_registered'=>'DateInserted' ); $Ex->ExportTable('User', "select * from :_users", $User_Map); // ":_" will be replace by database prefix // Roles $Ex->ExportTable('Role', "select 1 as RoleID, 'Guest' as Name union select 2, 'Key Master' union select 3, 'Administrator' union select 4, 'Moderator' union select 5, 'Member' union select 6, 'Inactive' union select 7, 'Blocked'"); // UserRoles $UserRole_Map = array( 'user_id'=>'UserID' ); $Ex->ExportTable('UserRole', "select distinct user_id, case when locate('keymaster', meta_value) <> 0 then 2 when locate('administrator', meta_value) <> 0 then 3 when locate('moderator', meta_value) <> 0 then 4 when locate('member', meta_value) <> 0 then 5 when locate('inactive', meta_value) <> 0 then 6 when locate('blocked', meta_value) <> 0 then 7 else 1 end as RoleID from :_usermeta where meta_key = 'bb_capabilities'", $UserRole_Map); // Categories $Category_Map = array( 'forum_id'=>'CategoryID', 'forum_name'=>'Name', 'forum_desc'=>'Description', 'form_slug'=>'UrlCode', 'left_order'=>'Sort' ); $Ex->ExportTable('Category', "select *, nullif(forum_parent,0) as ParentCategoryID from :_forums", $Category_Map); // Discussions $Discussion_Map = array( 'topic_id'=>'DiscussionID', 'forum_id'=>'CategoryID', 'topic_poster'=>'InsertUserID', 'topic_title'=>'Name', 'Format'=>'Format', 'topic_start_time'=>'DateInserted', 'topic_sticky'=>'Announce' ); $Ex->ExportTable('Discussion', "select t.*, 'Html' as Format, case t.topic_open when 0 then 1 else 0 end as Closed from :_topics t", $Discussion_Map); // Comments $Comment_Map = array( 'post_id' => 'CommentID', 'topic_id' => 'DiscussionID', 'post_text' => 'Body', 'Format' => 'Format', 'Body' => array('Column'=>'Body','Filter'=>'bbPressTrim'), 'poster_id' => 'InsertUserID', 'post_time' => 'DateInserted' ); $Ex->ExportTable('Comment', "select p.*, 'Html' as Format from :_posts p", $Comment_Map); // Conversations. // The export is different depending on the table layout. $PM = $Ex->Exists('bbpm', array('ID', 'pm_title', 'pm_from', 'pm_to', 'pm_text', 'sent_on', 'pm_thread')); $ConversationVersion = ''; if ($PM === TRUE) { // This is from an old version of the plugin. $ConversationVersion = 'old'; } elseif (is_array($PM) && count(array_intersect(array('ID', 'pm_from', 'pm_text', 'sent_on', 'pm_thread'), $PM)) == 0) { // This is from a newer version of the plugin. $ConversationVersion = 'new'; } if ($ConversationVersion) { // Conversation. $Conv_Map = array( 'pm_thread' => 'ConversationID', 'pm_from' => 'InsertUserID' ); $Ex->ExportTable('Conversation', "select *, from_unixtime(sent_on) as DateInserted from :_bbpm where thread_depth = 0", $Conv_Map); // ConversationMessage. $ConvMessage_Map = array( 'ID' => 'MessageID', 'pm_thread' => 'ConversationID', 'pm_from' => 'InsertUserID', 'pm_text' => array('Column'=>'Body','Filter'=>'bbPressTrim') ); $Ex->ExportTable('ConversationMessage', 'select *, from_unixtime(sent_on) as DateInserted from :_bbpm', $ConvMessage_Map); // UserConversation. $Ex->Query("create temporary table bbpmto (UserID int, ConversationID int)"); if ($ConversationVersion == 'new') { $To = $Ex->Query("select object_id, meta_value from bb_meta where object_type = 'bbpm_thread' and meta_key = 'to'", TRUE); if (is_resource($To)) { while (($Row = @mysql_fetch_assoc($To)) !== false) { $Thread = $Row['object_id']; $Tos = explode(',', trim($Row['meta_value'], ',')); $ToIns = ''; foreach ($Tos as $ToID) { $ToIns .= "($ToID,$Thread),"; } $ToIns = trim($ToIns, ','); $Ex->Query("insert bbpmto (UserID, ConversationID) values $ToIns", TRUE); } mysql_free_result($To); $Ex->ExportTable('UserConversation', 'select * from bbpmto'); } } else { $ConUser_Map = array( 'pm_thread' => 'ConversationID', 'pm_from' => 'UserID' ); $Ex->ExportTable('UserConversation', 'select distinct pm_thread, pm_from, del_sender as Deleted from bb_bbpm union select distinct pm_thread, pm_to, del_reciever from bb_bbpm', $ConUser_Map); } } // End $Ex->EndExport(); } } function bbPressTrim($Text) { return rtrim(bb_code_trick_reverse($Text)); } function bb_code_trick_reverse( $text ) { $text = preg_replace_callback("!(
|)(.*?)(
|)!s", 'bb_decodeit', $text); $text = str_replace(array('

', '
'), '', $text); $text = str_replace('

', "\n", $text); $text = str_replace('', '
', $text); $text = str_replace('', '

', $text); $text = str_replace('', '

', $text); return $text; } function bb_decodeit( $matches ) { $text = $matches[2]; $trans_table = array_flip(get_html_translation_table(HTML_ENTITIES)); $text = strtr($text, $trans_table); $text = str_replace('
', '', $text); $text = str_replace('

', '', $text); $text = str_replace('

', '
', $text); $text = str_replace(array('&','&'), '&', $text); $text = str_replace(''', "'", $text); if ( '
' == $matches[1] )
		$text = "\n$text\n";
	return "`$text`";
}
?> columns */
   protected $SourceTables = array(
      'boards' => array(),
      'messages' => array(),
      'instant_messages' => array(),
	  'im_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' => 'reset'));

      // Users
      $User_Map = array(
         'ID_MEMBER'=>'UserID',
         'memberName'=>'Name',
         'password'=>'Password', // concat(`passwd`, `passwordSalt`) as `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(`passwd`, `passwordSalt`) 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
      $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
	  
	  ");
	  
      // Discussions
      $Discussion_Map = array(
         'ID_TOPIC' => 'DiscussionID', 
         'subject' => array('Column'=>'Name','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,
			 'Html' 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,
				'Html' 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 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' => '
      ', 'after' => '
    ', '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' => '
      ', 'after' => '
    ', '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' => '
    ', '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('\'' => ''', '' => '', '' => '')); } ?>DoExport(); } else { $CanWrite = TestWrite(); ViewForm(array('Supported' => $Supported, 'CanWrite' => $CanWrite)); } /** * Write out a value passed as bytes to its most readable format. */ function FormatMemorySize($Bytes, $Precision = 1) { $Units = array('B', 'K', 'M', 'G', 'T'); $Bytes = max((int)$Bytes, 0); $Pow = floor(($Bytes ? log($Bytes) : 0) / log(1024)); $Pow = min($Pow, count($Units) - 1); $Bytes /= pow(1024, $Pow); $Result = round($Bytes, $Precision).$Units[$Pow]; return $Result; } /** * Test filesystem permissions */ function TestWrite() { // Create file $file = 'vanilla2test.txt'; @touch($file); if(is_writable($file)) { @unlink($file); return true; } else return false; } ?>