Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @package Invision Community * @subpackage Converter * @since 21 Jan 2015 */ namespace IPS\convert\Software\Blog; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } class _Vbulletin extends \IPS\convert\Software { /** * @brief vBulletin 4 Stores all attachments under one table - this will store the content type for the blog app. */ protected static $entryContentType = NULL; /** * @brief The schematic for vB3 and vB4 is similar enough that we can make specific concessions in a single converter for either version. */ protected static $isLegacy = NULL; /** * Constructor * * @param \IPS\convert\App The application to reference for database and other information. * @param bool Establish a DB connection * @return void * @throws \InvalidArgumentException */ public function __construct( \IPS\convert\App $app, $needDB=TRUE ) { $return = parent::__construct( $app, $needDB ); /* Is this vB3 or vB4? */ try { if ( static::$isLegacy === NULL AND $needDB ) { $version = $this->db->select( 'value', 'setting', array( "varname=?", 'templateversion' ) )->first(); if ( mb_substr( $version, 0, 1 ) == '3' ) { static::$isLegacy = TRUE; } else { static::$isLegacy = FALSE; } } /* If this is vB4, what is the content type ID for posts? */ if ( static::$entryContentType === NULL AND ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) AND $needDB ) { static::$entryContentType = $this->db->select( 'contenttypeid', 'contenttype', array( "class=?", 'BlogEntry' ) )->first(); } } catch( \Exception $e ) {} return $return; } /** * Software Name * * @return string */ public static function softwareName() { /* Child classes must override this method */ return "vBulletin Blog (3.x/4.x)"; } /** * Software Key * * @return string */ public static function softwareKey() { /* Child classes must override this method */ return "vbulletin"; } /** * Content we can convert from this software. * * @return array */ public static function canConvert() { $attachmentTable = 'blog_attachment'; $attachmentWhere = NULL; if ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) { $attachmentTable = 'attachment'; $attachmentWhere = array( "contenttypeid=?", static::$entryContentType ); } return array( 'convertBlogs' => array( 'table' => 'blog_user', 'where' => NULL, ), 'convertBlogEntries' => array( 'table' => 'blog', 'where' => NULL ), 'convertBlogComments' => array( 'table' => 'blog_text', 'where' => NULL ), 'convertAttachments' => array( 'table' => $attachmentTable, 'where' => $attachmentWhere ) ); } /** * Count Source Rows for a specific step * * @param string $table The table containing the rows to count. * @param array|NULL $where WHERE clause to only count specific rows, or NULL to count all. * @param bool $recache Skip cache and pull directly (updating cache) * @return integer * @throws \IPS\convert\Exception */ public function countRows( $table, $where=NULL, $recache=FALSE ) { switch( $table ) { default: return parent::countRows( $table, $where ); break; case 'blog_text': return parent::countRows( 'blog_text' ) - parent::countRows( 'blog' ); break; } } /** * Requires Parent * * @return boolean */ public static function requiresParent() { return TRUE; } /** * Possible Parent Conversions * * @return array */ public static function parents() { return array( 'core' => array( 'vbulletin' ) ); } /** * Finish - Adds everything it needs to the queues and clears data store * * @return array Messages to display */ public function finish() { /* Content Rebuilds */ \IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'blog_entries', 'class' => 'IPS\blog\Entry' ), 2, array( 'app', 'link', 'class' ) ); \IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'blog_comments', 'class' => 'IPS\blog\Entry\Comment' ), 2, array( 'app', 'link', 'class' ) ); \IPS\Task::queue( 'convert', 'RebuildTagCache', array( 'app' => $this->app->app_id, 'link' => 'blog_entries', 'class' => 'IPS\blog\Entry' ), 3, array( 'app', 'link', 'class' ) ); return array( "f_blog_entries_rebuilding", "f_entry_comments_rebuilding", "f_blogs_recounting", "f_entry_tags_cache" ); } /** * Fix Post Data * * @param string Post * @return string Fixed Posts */ public static function fixPostData( $post ) { return \IPS\convert\Software\Core\Vbulletin::fixPostData( $post ); } /** * List of conversion methods that require additional information * * @return array */ public static function checkConf() { return array( 'convertAttachments' ); } /** * Get More Information * * @param string $method Conversion method * @return array */ public function getMoreInfo( $method ) { $return = array(); switch( $method ) { case 'convertAttachments': $return['convertAttachments'] = array( 'file_location' => array( 'field_class' => 'IPS\\Helpers\\Form\\Radio', 'field_default' => 'database', 'field_required' => TRUE, 'field_extra' => array( 'options' => array( 'database' => \IPS\Member::loggedIn()->language()->addToStack( 'database' ), 'file_system' => \IPS\Member::loggedIn()->language()->addToStack( 'file_system' ), ), 'userSuppliedInput' => 'file_system', ), 'field_hint' => NULL, 'field_validation' => function( $value ) { if ( $value != 'database' AND !@is_dir( $value ) ) { throw new \DomainException( 'path_invalid' ); } }, ) ); break; } return ( isset( $return[ $method ] ) ) ? $return[ $method ] : array(); } /** * Convert blogs * * @return void */ public function convertBlogs() { $libraryClass = $this->getLibrary(); $libraryClass::setKey( 'bloguserid' ); foreach( $this->fetch( 'blog_user', 'bloguserid' ) as $blog ) { try { $member = $this->db->select( '*', 'user', array( "userid=?", $blog['bloguserid'] ) )->first(); } catch( \UnderflowException $e ) { /* User no longer exists, so skip */ $libraryClass->setLastKeyValue( $blog['bloguserid'] ); continue; } $socialgroup = NULL; /* If this is a private blog, then we need to convert it to use a social group for invited users */ if ( $blog['options_member'] == 0 AND $blog['options_buddy'] > 0 ) { /* Fetch users friends */ $friends = array(); foreach( $this->db->select( 'relationid', 'userlist', array( "userid=?", $blog['bloguserid'] ) ) AS $friend ) { $friends[$friend] = $friend; } $socialgroup = array( 'members' => $friends ); } $info = array( 'blog_id' => $blog['bloguserid'], 'blog_member_id' => $blog['bloguserid'], 'blog_allowguests' => ( $blog['options_guest'] > 0 ) ? 1 : 0, # we don't care about specific options 'blog_rating_total' => $blog['ratingtotal'], 'blog_rating_count' => $blog['ratingnum'], 'blog_settings' => array( 'allowrss' => TRUE ), 'blog_name' => $blog['title'] ?: $member['username'] . "'s Blog", 'blog_description' => $blog['description'] ?: '', 'blog_last_edate' => $blog['lastblog'], 'blog_count_entries' => $blog['entries'], 'blog_count_comments' => $blog['comments'], 'blog_count_entries_hidden' => $blog['deleted'], 'blog_rating_average' => $blog['rating'], 'blog_count_entries_future' => $blog['pending'], ); $libraryClass->convertBlog( $info, $socialgroup ); foreach( $this->db->select( '*', 'blog_subscribeuser', array( "userid=?", $blog['bloguserid'] ) ) AS $follow ) { $frequency = 'none'; if ( $follow['type'] == 'email' ) { $frequency = 'immediate'; } $libraryClass->convertFollow( array( 'follow_app' => 'blog', 'follow_area' => 'blogs', 'follow_rel_id' => $blog['bloguserid'], 'follow_rel_id_type' => 'blog_blogs', 'follow_member_id' => $follow['userid'], 'follow_is_anon' => 0, 'follow_added' => time(), 'follow_notify_do' => 1, 'follow_notify_meta' => '', 'follow_notify_freq' => $frequency, 'follow_notify_sent' => 0, 'follow_visible' => 1, 'follow_index_id' => NULL ) ); } $libraryClass->setLastKeyValue( $blog['bloguserid'] ); } } /** * Convert blog entries * * @return void */ public function convertBlogEntries() { $libraryClass = $this->getLibrary(); $libraryClass::setKey( 'blogid' ); foreach( $this->fetch( 'blog', 'blogid' ) AS $entry ) { try { $text = $this->db->select( '*', 'blog_text', array( "blogtextid=?", $entry['firstblogtextid'] ) )->first(); } catch( \UnderflowException $e ) { $libraryClass->setLastKeyValue( $entry['blogid'] ); continue; } $lastcommenterid = 0; try { $lastcommenterid = $this->db->select( 'userid', 'blog_text', array( "blogtextid=?", $entry['lastblogtextid'] ) )->first(); } catch( \UnderflowException $e ) {} $status = 'published'; if ( $entry['state'] == 'draft' ) { $status = 'draft'; } $edittime = $editname = NULL; try { $editlog = $this->db->select( 'dateline, username', 'blog_editlog', array( "blogtextid=?", $entry['firstblogtextid'] ), "dateline DESC" )->first(); $edittime = $editlog['dateline']; $editname = $editlog['username']; } catch( \UnderflowException $e ) {} $info = array( 'entry_id' => $entry['blogid'], 'entry_blog_id' => $entry['userid'], 'entry_name' => $text['title'], 'entry_content' => $text['pagetext'], 'entry_author_id' => $entry['postedby_userid'], 'entry_author_name' => $entry['postedby_username'], 'entry_date' => $entry['dateline'], 'entry_status' => $status, 'entry_num_comments' => $entry['comments_visible'], 'entry_queued_comments' => $entry['comments_moderation'], 'entry_views' => $entry['views'], 'entry_hidden_comments' => $entry['comments_deleted'], 'entry_last_comment_mid' => $lastcommenterid, 'entry_edit_time' => $edittime, 'entry_edit_name' => $editname, 'entry_ip_address' => $text['ipaddress'] ); $libraryClass->convertBlogEntry( $info ); foreach( $this->db->select( '*', 'blog_rate', array( "blogid=?", $entry['blogid'] ) ) as $rating ) { $libraryClass->convertRating( array( 'id' => $rating['blograteid'], 'class' => 'IPS\blog\Entry', 'item_link' => 'blog_entries', 'item_id' => $entry['blogid'], 'rating' => $rating['vote'], 'ip' => $rating['ipaddress'], 'member' => $rating['userid'], ) ); } $categories = explode( ',', $entry['categories'] ); if ( count( $categories ) ) { foreach( $categories as $category ) { try { $data = $this->db->select( '*', 'blog_category', array( "blogcategoryid=?", $category ) )->first(); } catch( \UnderflowException $e ) { continue; } $libraryClass->convertTag( array( 'tag_meta_app' => 'blog', 'tag_meta_area' => 'blogs', 'tag_meta_parent_id' => $entry['userid'], 'tag_meta_id' => $entry['blogid'], 'tag_text' => $data['title'], 'tag_member_id' => $data['userid'], 'tag_prefix' => 0 ) ); } } foreach( $this->db->select( '*', 'blog_subscribeentry', array( "blogid=?", $entry['blogid'] ) ) AS $follow ) { $frequency = 'none'; if ( $follow['type'] == 'email' ) { $frequency = 'immediate'; } $libraryClass->convertFollow( array( 'follow_app' => 'blog', 'follow_area' => 'entries', 'follow_rel_id' => $entry['blogid'], 'follow_rel_id_type' => 'blog_entries', 'follow_member_id' => $follow['userid'], 'follow_is_anon' => 0, 'follow_added' => time(), 'follow_notify_do' => 1, 'follow_notify_meta' => '', 'follow_notify_freq' => $frequency, 'follow_notify_sent' => 0, 'follow_visible' => 1, 'follow_index_id' => NULL ) ); } $libraryClass->setLastKeyValue( $entry['blogid'] ); } } /** * Convert blog comments * * @return void */ public function convertBlogComments() { $libraryClass = $this->getLibrary(); $libraryClass::setKey( 'blogtextid' ); foreach( $this->fetch( 'blog_text', 'blogtextid' ) AS $comment ) { /* Is this actually the entry? */ try { $entry = $this->db->select( '*', 'blog', array( "firstblogtextid=?", $comment['blogtextid'] ) )->first(); /* If that didn't throw an exception, then yes this is - skip */ $libraryClass->setLastKeyValue( $comment['blogtextid'] ); continue; } catch( \UnderflowException $e ) {} # nope, carry on $edittime = $editname = NULL; try { $editlog = $this->db->select( 'dateline, username', 'blog_editlog', array( "blogtextid=?", $comment['blogtextid'] ), "dateline DESC" )->first(); $edittime = $editlog['dateline']; $editname = $editlog['username']; } catch( \UnderflowException $e ) {} switch( $comment['state'] ) { case 'moderation': $approved = 0; break; case 'deleted': $approved = -1; break; case 'visible': default: $approved = 1; break; } $libraryClass->convertBlogComment( array( 'comment_id' => $comment['blogtextid'], 'comment_entry_id' => $comment['blogid'], 'comment_text' => $comment['pagetext'], 'comment_member_id' => $comment['userid'], 'comment_member_name' => $comment['username'], 'comment_ip_address' => $comment['ipaddress'], 'comment_date' => $comment['dateline'], 'comment_edit_time' => $edittime, 'comment_edit_member_name' => $editname, 'comment_edit_show' => 1, 'comment_approved' => $approved ) ); $libraryClass->setLastKeyValue( $comment['blogtextid'] ); } } /** * Convert attachments * * @return void */ public function convertAttachments() { $libraryClass = $this->getLibrary(); $libraryClass::setKey( 'attachmentid' ); $where = NULL; $column = NULL; if ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) { $where = array( "contenttypeid=?", static::$entryContentType ); $column = 'contentid'; $table = 'attachment'; } else { $column = 'blogid'; $table = 'blog_attachment'; } foreach( $this->fetch( $table, 'attachmentid', $where ) as $attachment ) { if ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) { $filedata = $this->db->select( '*', 'filedata', array( "filedataid=?", $attachment['filedataid'] ) )->first(); } else { $filedata = $attachment; $filedata['filedataid'] = $attachment['attachmentid']; } $map = array( 'id1' => $attachment[$column], 'id2' => NULL ); $info = array( 'attach_id' => $attachment['attachmentid'], 'attach_file' => $attachment['filename'], 'attach_date' => $attachment['dateline'], 'attach_member_id' => $attachment['userid'], 'attach_hits' => $attachment['counter'], 'attach_ext' => $filedata['extension'], 'attach_filesize' => $filedata['filesize'], ); if ( $this->app->_session['more_info']['convertAttachments']['file_location'] == 'database' ) { /* Simples! */ $data = $filedata['filedata']; $path = NULL; } else { $data = NULL; $path = implode( '/', preg_split( '//', $filedata['userid'], -1, PREG_SPLIT_NO_EMPTY ) ); $path = rtrim( $this->app->_session['more_info']['convertAttachments']['file_location'], '/' ) . '/' . $path . '/' . $attachment['filedataid'] . '.attach'; } $attach_id = $libraryClass->convertAttachment( $info, $map, $path, $data ); /* Do some re-jiggery on the post itself to make sure attachment displays */ $entry_id = $this->app->getLink( $attachment[ $column ], 'blog_entries' ); $post = \IPS\Db::i()->select( 'entry_content', 'blog_entries', array( "entry_id=?", $entry_id ) )->first(); if ( preg_match( "/\[ATTACH(.+?)?\]".$attachment['attachmentid']."\[\/ATTACH\]/i", $post ) ) { $post = preg_replace( "/\[ATTACH=(.+?)?\]" . $attachment['attachmentid'] . "\[\/ATTACH\]/i", '[attachment=' . $attach_id . ':name]', $post ); \IPS\Db::i()->update( 'blog_entries', array( 'entry_content' => $post ), array( "entry_id=?", $entry_id ) ); } $libraryClass->setLastKeyValue( $attachment['attachmentid'] ); } } }