Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

@@ -5,21 +5,58 @@
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @package Invision Community
* @subpackage Converter
* @subpackage convert
* @since 21 Jan 2015
*/
namespace IPS\convert\Software\Core;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* vBulletin 5 Core Converter
*/
class _Vbulletin5 extends \IPS\convert\Software
{
/**
* @brief is vB 5.5.3 or newer
*/
protected static $skipCustomProfilePic = NULL;
/**
* Constructor
*
* @param \IPS\convert\App $app The application to reference for database and other information.
* @param bool $needDB Establish a DB connection
* @return void
* @throws \InvalidArgumentException
*/
public function __construct( \IPS\convert\App $app, $needDB=TRUE )
{
$return = parent::__construct( $app, $needDB );
if ( $needDB )
{
try
{
/* Is this vB 5.5.3 or newer? */
if ( static::$skipCustomProfilePic === NULL )
{
$version = $this->db->select( 'value', 'setting', array( "varname=?", 'templateversion' ) )->first();
static::$skipCustomProfilePic = version_compare( $version, '5.5.3', '>=' );
}
}
catch( \Exception $e ) {} # If we can't query, we won't be able to do anything anyway
}
return $return;
}
/**
* Software Name
*
@@ -135,12 +172,16 @@ class _Vbulletin5 extends \IPS\convert\Software
{
return 0;
}
catch( \Exception $e )
{
throw new \IPS\convert\Exception( sprintf( \IPS\Member::loggedIn()->language()->get( 'could_not_count_rows' ), $table ) );
}
break;
case 'node':
if ( $where === 'clubs' )
{
return count( $this->fetchClubs() );
return \count( $this->fetchClubs() );
}
else
{
@@ -160,7 +201,14 @@ class _Vbulletin5 extends \IPS\convert\Software
break;
case 'userchangelog':
return $this->db->select( 'count(changeid)', 'userchangelog', array( $this->db->in( 'fieldname', array_keys( static::$changeLogTypes ) ) ) )->first();
try
{
return $this->db->select( 'count(changeid)', 'userchangelog', array( $this->db->in( 'fieldname', array_keys( static::$changeLogTypes ) ) ) )->first();
}
catch( \Exception $e )
{
throw new \IPS\convert\Exception( sprintf( \IPS\Member::loggedIn()->language()->get( 'could_not_count_rows' ), $table ) );
}
break;
default:
@@ -395,8 +443,8 @@ class _Vbulletin5 extends \IPS\convert\Software
'field_required' => TRUE,
'field_extra' => array(
'options' => array(
'database' => \IPS\Member::loggedIn()->language()->addToStack( 'database' ),
'file_system' => \IPS\Member::loggedIn()->language()->addToStack( 'file_system' ),
'database' => \IPS\Member::loggedIn()->language()->addToStack( 'conv_store_database' ),
'file_system' => \IPS\Member::loggedIn()->language()->addToStack( 'conv_store_file_system' ),
),
'userSuppliedInput' => 'file_system',
),
@@ -426,15 +474,17 @@ class _Vbulletin5 extends \IPS\convert\Software
break;
case 'convertClubs':
\IPS\Member::loggedIn()->language()->words['club_photo_location'] = \IPS\Member::loggedIn()->language()->addToStack( 'photo_location' );
$return['convertClubs'] = array();
$return['convertClubs']['photo_location'] = array(
$return['convertClubs']['club_photo_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' ),
'database' => \IPS\Member::loggedIn()->language()->addToStack( 'conv_store_database' ),
'file_system' => \IPS\Member::loggedIn()->language()->addToStack( 'conv_store_file_system' ),
),
'userSuppliedInput' => 'file_system',
),
@@ -483,24 +533,16 @@ class _Vbulletin5 extends \IPS\convert\Software
/**
* Fix post data
*
* @param string raw post data
* @return string parsed post data
* @param string $post Raw post data
* @return string Parsed post data
*/
public static function fixPostData( $post )
{
/* Fix Quotes */
$post = preg_replace( "#\[quote=([^\];]+?)\]#i", "[quote name='$1']", $post );
$post = preg_replace( "#\[quote=([^\];]+?);\d+\]#i", "[quote name='$1']", $post );
$post = preg_replace( "#\[quote=([^\];]+?);n\d+\]#i", "[quote name='$1']", $post );
/* Remove video tags and allow our parser to handle the embeds it supports */
$post = preg_replace( "#\[video=[a-z]+;[a-z0-9_]+\](.*?)\[\/video\]#i", '$1', $post );
/* IMG2 BBcode */
/* IMG2 BBcode - Needs to be ran before htmlspecialchars due to it containing JSON */
$matches = array();
preg_match_all( "/\[IMG2=JSON](.+?)\[\/IMG2\]/i", $post, $matches );
if ( count( $matches ) )
if ( \count( $matches ) )
{
foreach( $matches[0] as $k => $v )
{
@@ -533,40 +575,46 @@ class _Vbulletin5 extends \IPS\convert\Software
$matches = array();
preg_match_all( '#\[user(=\"?(\d+)\"?)?\](.+?)\[\/user\]#i', $post, $matches ); // this purposefully matches more than the default vB mention.
if ( count( $matches ) )
if ( \count( $matches ) )
{
if ( isset( $matches[1] ) )
/* Make sure we actually have mention data */
if ( isset( $matches[1] ) AND \count( $matches[1] ) )
{
$mentions = array();
foreach( $matches[1] AS $k => $v )
foreach ( $matches[1] AS $k => $v )
{
if ( isset( $matches[3][$k] ) )
if ( isset( $matches[3][ $k ] ) )
{
$name = trim( $matches[3][ $k ], '@' );
$mentions[ $name ] = $v;
$mentions[ $matches[3][ $k ] ] = $v;
}
}
$maps = array();
$urls = array();
$cardUrls = array();
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'name', array_keys( $mentions ) ) ) ), 'IPS\Member' ) AS $member )
{
$maps[ $member->name ] = $member->member_id;
$urls[ $member->name ] = $member->url();
$cardUrls[ $member->name ] = $member->url()->setQueryString( 'do', 'hovercard' );
}
$maps = iterator_to_array( \IPS\Db::i()->select( 'name, member_id', 'core_members', array( \IPS\Db::i()->in( 'name', array_keys( $mentions ) ) ) )->setKeyField( 'name' )->setValueField( 'member_id' ) );
foreach( $mentions AS $member_name => $member_id )
foreach ( $mentions AS $memberName => $memberId )
{
$maps[ $member_name ] = preg_quote( $maps[ $member_name ], '#' );
$memberNameEscaped = preg_quote( $member_name, '#' );
if ( !isset( $maps[ $memberName ] ) )
{
continue;
}
$post = preg_replace( "#\[user(=\"?(\d+)\"?)?\]@?{$memberNameEscaped}\[\/user\]#i", "<a contenteditable=\"false\" rel=\"\" href=\"{$urls[ $member_name ]}\" data-mentionid=\"{$maps[ $member_name ]}\" data-ipshover-target=\"{$cardUrls[ $member_name ]}\" data-ipshover=\"\">@{$member_name}</a>", $post );
$memberNameQuoted = preg_quote( $memberName, '#' );
$post = preg_replace( "#\[user(=\"?(\d+)\"?)?\]@?{$memberNameQuoted}\[\/user\]#i", "[mention={$maps[ $memberName ]}]{$memberName}[/mention]", $post );
}
}
}
// run everything through htmlspecialchars to prevent XSS
$post = htmlspecialchars( $post, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE );
/* Fix Quotes */
$post = preg_replace( "#\[quote=([^\];]+?)\]#i", "[quote name='$1']", $post );
$post = preg_replace( "#\[quote=([^\];]+?);\d+\]#i", "[quote name='$1']", $post );
$post = preg_replace( "#\[quote=([^\];]+?);n\d+\]#i", "[quote name='$1']", $post );
/* Remove video tags and allow our parser to handle the embeds it supports */
$post = preg_replace( "#\[video=[a-z_]+;[a-z0-9_]+\](.*?)\[\/video\]#i", '$1', $post );
return $post;
}
@@ -684,7 +732,7 @@ class _Vbulletin5 extends \IPS\convert\Software
$merge = ( $this->app->_session['more_info']['convertProfileFields']["map_pfield_{$field['profilefieldid']}"] != 'none' ) ? $this->app->_session['more_info']['convertProfileFields']["map_pfield_{$field['profilefieldid']}"] : NULL;
if ( in_array( mb_strtolower( $field['type'] ), array( 'select', 'radio', 'checkbox', 'select_multiple' ) ) )
if ( \in_array( mb_strtolower( $field['type'] ), array( 'select', 'radio', 'checkbox', 'select_multiple' ) ) )
{
$content = json_encode( @\unserialize( $field['data'] ) );
}
@@ -707,14 +755,13 @@ class _Vbulletin5 extends \IPS\convert\Software
'pf_desc' => $desc,
'pf_type' => $type,
'pf_content' => $content,
'pf_not_null' => ( in_array( $field['required'], array( 1, 3 ) ) ) ? 1 : 0,
'pf_member_hide' => $field['hidden'],
'pf_not_null' => ( \in_array( $field['required'], array( 1, 3 ) ) ) ? 1 : 0,
'pf_member_hide' => $field['hidden'] ? 'hide' : 'all',
'pf_max_input' => $field['maxlength'],
'pf_member_edit' => ( $field['editable'] >= 1 ) ? 1 : 0,
'pf_position' => $field['displayorder'],
'pf_show_on_reg' => ( $field['required'] == 2 ) ? 1 : 0,
'pf_input_format' => $field['regex'],
'pf_admin_only' => ( $field['editable'] == 0 ) ? 1 : 0,
'pf_group_id' => $field['profilefieldcategoryid'],
'pf_multiple' => $multiple
);
@@ -728,7 +775,7 @@ class _Vbulletin5 extends \IPS\convert\Software
/**
* Maps vBulletin Profile Field type to the IPS Equivalent
*
* @param string The vB Field Type
* @param string $type The vB Field Type
* @return string The IPS Field Type
*/
protected static function _pfieldMap( $type )
@@ -791,7 +838,7 @@ class _Vbulletin5 extends \IPS\convert\Software
$promotion = $this->db->select( '*', 'userpromotion', array( "usergroupid=?", $group['usergroupid'] ) )->first();
/* We only support Posts or Join Date */
if ( in_array( $promotion['strategy'], array( 17, 18 ) ) )
if ( \in_array( $promotion['strategy'], array( 17, 18 ) ) )
{
switch( $promotion['strategy'] )
{
@@ -889,7 +936,7 @@ class _Vbulletin5 extends \IPS\convert\Software
}
/* Now check for group promotions */
if( count( $libraryClass->groupPromotions ) )
if( \count( $libraryClass->groupPromotions ) )
{
foreach( $libraryClass->groupPromotions as $groupPromotion )
{
@@ -901,7 +948,7 @@ class _Vbulletin5 extends \IPS\convert\Software
/**
* @brief Member history type map
*/
protected static $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'usergroupid' => 'member_group_id', 'membergroupids' => 'mgroup_others' );
protected static $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'usergroupid' => 'group', 'membergroupids' => 'group' );
/**
* Convert member history
@@ -915,17 +962,24 @@ class _Vbulletin5 extends \IPS\convert\Software
foreach( $this->fetch( 'userchangelog', 'changeid', array( $this->db->in( 'fieldname', array_keys( static::$changeLogTypes ) ) ) ) AS $change )
{
$libraryClass->convertMemberHistory( array(
'log_id' => $change['changeid'],
'log_member' => $change['userid'],
'log_by' => $change['adminid'],
'log_type' => static::$changeLogTypes[ $change['fieldname'] ],
'log_data' => array( 'old' => $change['oldvalue'], 'new' => $change['newvalue'] ),
'log_date' => $change['change_time'],
'log_ip_address' => long2ip( (int) $change['ipaddress'] )
)
$info = array(
'log_id' => $change['changeid'],
'log_member' => $change['userid'],
'log_by' => $change['adminid'],
'log_type' => static::$changeLogTypes[ $change['fieldname'] ],
'log_data' => array( 'old' => $change['oldvalue'], 'new' => $change['newvalue'] ),
'log_date' => $change['change_time'],
'log_ip_address' => long2ip( (int) $change['ipaddress'] )
);
/* Add extra data for group change */
if( static::$changeLogTypes[ $change['fieldname'] ] == 'group' )
{
$info['log_data']['type'] = $change['fieldname'] == 'usergroupid' ? 'primary' : 'secondary';
$info['log_data']['by'] = 'manual';
}
$libraryClass->convertMemberHistory( $info );
$libraryClass->setLastKeyValue( $change['changeid'] );
}
}
@@ -941,6 +995,9 @@ class _Vbulletin5 extends \IPS\convert\Software
$libraryClass::setKey( 'user.userid' );
/* User titles */
$userTitles = iterator_to_array( $this->db->select( 'title', 'usertitle' ) );
foreach( $this->fetch( 'user', 'user.userid', NULL, 'user.*, usertextfield.signature' )->join( 'usertextfield', 'user.userid = usertextfield.userid' ) AS $user )
{
/* <3 Closures */
@@ -1036,9 +1093,9 @@ class _Vbulletin5 extends \IPS\convert\Software
'member_id' => $user['userid'],
'member_group_id' => $user['usergroupid'],
'mgroup_others' => $user['membergroupids'],
'name' => $user['username'],
'name' => html_entity_decode( $user['username'], \ENT_QUOTES | \ENT_HTML5, 'UTF-8' ),
'email' => $user['email'],
'member_title' => $user['usertitle'],
'member_title' => \in_array( $user['usertitle'], $userTitles ) ? NULL : $user['usertitle'],
'joined' => $user['joindate'],
'ip_address' => $user['ipaddress'],
'warn_level' => $user['ipoints'],
@@ -1056,10 +1113,10 @@ class _Vbulletin5 extends \IPS\convert\Software
'members_bitoptions' => array(
'view_sigs' => $checkpermission( 'useroptions', 'showsignatures' ), // View signatures?
'coppa_user' => $checkpermission( 'useroptions', 'coppauser' ), // Was the member validated using coppa?
'timezone_override' => !$checkpermission( 'useroptions', 'dstauto' ), // If TRUE, user's timezone will not be detected automatically
),
'members_bitoptions2' => array(
'show_pm_popup' => $user['pmpopup'], // "Show pop-up when I have a new message"
'is_anon' => $checkpermission( 'useroptions', 'invisible' ), // Anonymous status
),
'pp_setting_count_comments' => $checkpermission( 'useroptions', 'vm_enable' ),
'pp_reputation_points' => $user['reputation'],
@@ -1070,6 +1127,18 @@ class _Vbulletin5 extends \IPS\convert\Software
'fb_uid' => $user['fbuserid'],
'fb_token' => $user['fbaccesstoken']
);
/* If there is a validation record for this user, treat them as validating */
try
{
$activation = $this->db->select( '*', 'useractivation', array( "userid=?", $user['userid'] ) )->first();
$info['members_bitoptions']['validating'] = TRUE;
}
catch( \UnderflowException $e )
{
$activation = NULL;
}
/* Profile Fields */
try
@@ -1113,11 +1182,10 @@ class _Vbulletin5 extends \IPS\convert\Software
'pf_desc' => '',
'pf_type' => 'Text',
'pf_content' => '[]',
'pf_member_hide' => 0,
'pf_member_hide' => 'all',
'pf_max_input' => 255,
'pf_member_edit' => 1,
'pf_show_on_reg' => 0,
'pf_admin_only' => 0,
) );
}
@@ -1126,16 +1194,21 @@ class _Vbulletin5 extends \IPS\convert\Software
/* Profile Photos */
$firstTable = 'customavatar';
$secondTable = 'customprofilepic';
$secondTable = !static::$skipCustomProfilePic ? 'customprofilepic' : FALSE; # vB 5.5.3 removes the customprofilepic table
if ( $this->app->_session['more_info']['convertMembers']['photo_type'] == 'profile_photos' )
{
$firstTable = 'customprofilepic';
$firstTable = !static::$skipCustomProfilePic ? 'customprofilepic' : FALSE; # vB 5.5.3 removes the customprofilepic table
$secondTable = 'customavatar';
}
if ( $this->app->_session['more_info']['convertMembers']['photo_location'] == 'database' )
{
try
{
if( $firstTable === FALSE )
{
throw new \UnderflowException();
}
foreach( $this->db->select( 'filedata, filename', $firstTable, array( "userid=?", $user['userid'] ) )->first() AS $key => $value )
{
if ( $key == 'filedata' )
@@ -1153,6 +1226,11 @@ class _Vbulletin5 extends \IPS\convert\Software
{
try
{
if( $secondTable === FALSE )
{
throw new \UnderflowException();
}
foreach( $this->db->select( 'filedata, filename', $secondTable, array( "userid=?", $user['userid'] ) )->first() AS $key => $value )
{
if ( $key == 'filedata' )
@@ -1188,6 +1266,11 @@ class _Vbulletin5 extends \IPS\convert\Software
{
try
{
if( $firstTable === FALSE )
{
throw new \UnderflowException();
}
$ext = $this->db->select( 'filename', $firstTable, array( "userid=?", $user['userid'] ) )->first();
$ext = explode( '.', $ext );
$ext = array_pop( $ext );
@@ -1204,6 +1287,11 @@ class _Vbulletin5 extends \IPS\convert\Software
}
catch( \UnderflowException $e )
{
if( $secondTable === FALSE )
{
throw new \UnderflowException();
}
$ext = $this->db->select( 'filename', $secondTable, array( "userid=?", $user['userid'] ) )->first();
$ext = explode( '.', $ext );
$ext = array_pop( $ext );
@@ -1225,14 +1313,29 @@ class _Vbulletin5 extends \IPS\convert\Software
}
}
$libraryClass->convertMember( $info, $profileFields, $filename, $filepath, $filedata );
$memberId = $libraryClass->convertMember( $info, $profileFields, $filename, $filepath, $filedata );
/* If we are validating, store our validation row entry */
if( $activation !== NULL AND $memberId )
{
\IPS\Db::i()->replace( 'core_validating', array(
'vid' => md5( $activation['activationid'] . $activation['useractivationid'] ),
'member_id' => $memberId,
'entry_date' => $activation['dateline'],
'new_reg' => ( $activation['emailchange'] == 0 ? TRUE : FALSE ),
'email_chg' => ( $activation['emailchange'] == 1 ? TRUE : FALSE ),
'user_verified' => FALSE,
'ip_address' => $user['ipaddress'],
'email_sent' => $activation['dateline']
) );
}
/* Any friends need converting to followers? */
foreach( $this->db->select( '*', 'userlist', array( "type=? AND friend=? AND userid=?", 'buddy', 'yes', $user['userid'] ) ) AS $follower )
{
$libraryClass->convertFollow( array(
'follow_app' => 'core',
'follow_area' => 'members',
'follow_area' => 'member',
'follow_rel_id' => $follower['relationid'],
'follow_rel_id_type' => 'core_members',
'follow_member_id' => $follower['userid'],
@@ -1360,7 +1463,7 @@ class _Vbulletin5 extends \IPS\convert\Software
foreach( $this->db->select( 'userid', 'node', array( "parentid=?", $pm['nodeid'] ) ) AS $userid )
{
$to[$userid] = $userid;
if ( is_null( $toId ) AND $userid != $pm['userid'] )
if ( \is_null( $toId ) AND $userid != $pm['userid'] )
{
$toId = $userid;
}
@@ -1373,7 +1476,7 @@ class _Vbulletin5 extends \IPS\convert\Software
'mt_starter_id' => $pm['userid'],
'mt_start_time' => $pm['publishdate'],
'mt_last_post_time' => $pm['lastcontent'],
'mt_to_count' => count( $to ),
'mt_to_count' => \count( $to ),
'mt_to_member_id' => $toId,
'mt_replies' => $replies,
'mt_first_msg_id' => $pm['nodeid'],
@@ -1549,7 +1652,7 @@ class _Vbulletin5 extends \IPS\convert\Software
$iconpath = NULL;
if ( $filedata )
{
if ( $this->app->_session['more_info']['convertClubs']['photo_location'] == 'database' )
if ( $this->app->_session['more_info']['convertClubs']['club_photo_location'] == 'database' )
{
$icondata = $filedata['filedata'];
}
@@ -1557,7 +1660,7 @@ class _Vbulletin5 extends \IPS\convert\Software
{
$folder = preg_split( '//', $filedata['userid'], NULL, PREG_SPLIT_NO_EMPTY );
$folder = implode( '/', $folder );
$iconpath = $this->app->_session['more_info']['convertClubs']['photo_location'] . '/' . $folder . '/' . $filedata['filedataid'] . '.attach';
$iconpath = $this->app->_session['more_info']['convertClubs']['club_photo_location'] . '/' . $folder . '/' . $filedata['filedataid'] . '.attach';
}
}
@@ -1779,7 +1882,7 @@ class _Vbulletin5 extends \IPS\convert\Software
/**
* Get Setting Value - useful for global settings that need to be translated to group or member settings
*
* @param $key The setting key
* @param string $key The setting key
* @return mixed
*/
protected function _setting( $key )
@@ -1876,7 +1979,8 @@ class _Vbulletin5 extends \IPS\convert\Software
/**
* Helper method to retrieve content type ids
*
* @return void
* @param string $type Content type
* @return string
*/
protected function fetchType( $type )
{