2298 lines
68 KiB
PHP
2298 lines
68 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @brief Converter vBulletin 4.x Master Class
|
|
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
|
* @copyright (c) Invision Power Services, Inc.
|
|
* @package Invision Community
|
|
* @subpackage convert
|
|
* @since 21 Jan 2015
|
|
*/
|
|
|
|
namespace IPS\convert\Software\Core;
|
|
|
|
/* To prevent PHP errors (extending class does not exist) revealing path */
|
|
|
|
use DomainException;
|
|
use InvalidArgumentException;
|
|
use IPS\Application\Module;
|
|
use IPS\Content\Search\Index;
|
|
use IPS\convert\App;
|
|
use IPS\convert\Library;
|
|
use IPS\convert\Software;
|
|
use IPS\core\Ignore;
|
|
use IPS\Data\Cache;
|
|
use IPS\Data\Store;
|
|
use IPS\Db;
|
|
use IPS\Http\Request\Exception;
|
|
use IPS\Http\Url;
|
|
use IPS\Image;
|
|
use IPS\Login;
|
|
use IPS\Member;
|
|
use IPS\Member\Club;
|
|
use IPS\Patterns\ActiveRecordIterator;
|
|
use IPS\Request;
|
|
use IPS\Settings;
|
|
use IPS\Task;
|
|
use OutOfRangeException;
|
|
use UnderflowException;
|
|
use function count;
|
|
use function defined;
|
|
use function in_array;
|
|
use function is_array;
|
|
use function is_null;
|
|
use function preg_match;
|
|
use function strlen;
|
|
use function substr;
|
|
use function unserialize;
|
|
use const ENT_HTML5;
|
|
use const ENT_QUOTES;
|
|
|
|
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
|
{
|
|
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* vBulletin Core Converter
|
|
*/
|
|
class Vbulletin extends Software
|
|
{
|
|
/**
|
|
* @brief vBulletin 4 Stores all attachments under one table - this will store the content type for the forums app.
|
|
*/
|
|
protected static ?string $postContentType = 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 ?bool $isLegacy = NULL;
|
|
|
|
/**
|
|
* @brief Flag to indicate the post data has been fixed during conversion, and we only need to use Legacy Parser
|
|
*/
|
|
public static bool $contentFixed = TRUE;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param 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( App $app, bool $needDB=TRUE )
|
|
{
|
|
parent::__construct( $app, $needDB );
|
|
|
|
if ( $needDB )
|
|
{
|
|
try
|
|
{
|
|
/* Is this vB3 or vB4? */
|
|
if ( static::$isLegacy === NULL )
|
|
{
|
|
$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::$postContentType === NULL AND ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) )
|
|
{
|
|
static::$postContentType = $this->db->select( 'contenttypeid', 'contenttype', array( "class=?", 'Post' ) )->first();
|
|
}
|
|
}
|
|
catch( \Exception $e ) {} # If we can't query, we won't be able to do anything anyway
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Software Name
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function softwareName(): string
|
|
{
|
|
/* Child classes must override this method */
|
|
return "vBulletin (3.8.x/4.x)";
|
|
}
|
|
|
|
/**
|
|
* Software Key
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function softwareKey(): string
|
|
{
|
|
/* Child classes must override this method */
|
|
return "vbulletin";
|
|
}
|
|
|
|
/**
|
|
* Content we can convert from this software.
|
|
*
|
|
* @return array|null
|
|
*/
|
|
public static function canConvert(): ?array
|
|
{
|
|
return array(
|
|
'convertEmoticons' => array(
|
|
'table' => 'smilie',
|
|
'where' => NULL
|
|
),
|
|
'convertCustomBbcode' => array(
|
|
'table' => 'bbcode',
|
|
'where' => NULL
|
|
),
|
|
'convertProfileFieldGroups' => array(
|
|
'table' => 'profilefieldcategory',
|
|
'where' => NULL
|
|
),
|
|
'convertProfileFields' => array(
|
|
'table' => 'profilefield',
|
|
'where' => NULL
|
|
),
|
|
'convertGroups' => array(
|
|
'table' => 'usergroup',
|
|
'where' => NULL
|
|
),
|
|
'convertMembers' => array(
|
|
'table' => 'user',
|
|
'where' => NULL,
|
|
'extra_steps' => array( 'convertMembersFollowers' ),
|
|
),
|
|
'convertMembersFollowers' => array(
|
|
'table' => 'userlist',
|
|
'where' => array( "type=? AND friend=?", 'buddy', 'yes' )
|
|
),
|
|
'convertMemberHistory' => array(
|
|
'table' => 'userchangelog',
|
|
'where' => NULL
|
|
),
|
|
'convertIgnoredUsers' => array(
|
|
'table' => 'userlist',
|
|
'where' => array( "type=?", 'ignore' )
|
|
),
|
|
'convertAnnouncements' => array(
|
|
'table' => 'announcement',
|
|
'where' => NULL
|
|
),
|
|
'convertPrivateMessages' => array(
|
|
'table' => 'pm',
|
|
'where' => array( "parentpmid=?", 0 ),
|
|
),
|
|
'convertPrivateMessageReplies' => array(
|
|
'table' => 'pm',
|
|
'where' => array( 'NOT (folderid= -1 AND parentpmid > 0)' )
|
|
),
|
|
'convertClubs' => array(
|
|
'table' => 'socialgroup',
|
|
'where' => NULL
|
|
),
|
|
'convertClubMembers' => array(
|
|
'table' => 'socialgroupmember',
|
|
'where' => NULL
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Allows software to add additional menu row options
|
|
*
|
|
* @return array
|
|
*/
|
|
public function extraMenuRows(): array
|
|
{
|
|
$rows = array();
|
|
$count = $this->countRows( static::canConvert()['convertMembersFollowers']['table'], static::canConvert()['convertMembersFollowers']['where'] );
|
|
|
|
if( $count )
|
|
{
|
|
$rows['convertMembersFollowers'] = array(
|
|
'step_method' => 'convertMembersFollowers',
|
|
'step_title' => 'convert_follows',
|
|
'ips_rows' => Db::i()->select( 'COUNT(*)', 'core_follow', array( 'follow_app=? and follow_area=?', 'core', 'member' ) ),
|
|
'source_rows' => $count,
|
|
'per_cycle' => 200,
|
|
'dependencies' => array( 'convertMembers' ),
|
|
'link_type' => 'core_follow',
|
|
);
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Count Source Rows for a specific step
|
|
*
|
|
* @param string $table The table containing the rows to count.
|
|
* @param string|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( string $table, string|array|null $where=NULL, bool $recache=FALSE ): int
|
|
{
|
|
switch( $table )
|
|
{
|
|
case 'userchangelog':
|
|
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( Member::loggedIn()->language()->get( 'could_not_count_rows' ), $table ) );
|
|
}
|
|
|
|
case 'pm':
|
|
try
|
|
{
|
|
/* vB doesn't put an index on a column that makes this converison much quicker, so we'll do it. */
|
|
if( !$this->db->checkForIndex( 'pm', 'parent_id' ) )
|
|
{
|
|
$this->db->addIndex( 'pm', array(
|
|
'type' => 'key',
|
|
'name' => 'parent_id',
|
|
'columns' => array( 'parentpmid', 'folderid' )
|
|
) );
|
|
}
|
|
}
|
|
catch( \Exception $e )
|
|
{
|
|
throw new \IPS\convert\Exception( sprintf( Member::loggedIn()->language()->get( 'could_not_count_rows' ), $table ) );
|
|
}
|
|
|
|
return parent::countRows( $table, $where, $recache );
|
|
|
|
default:
|
|
return parent::countRows( $table, $where, $recache );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Can we convert passwords from this software.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function loginEnabled(): bool
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Can we convert settings?
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function canConvertSettings(): bool
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Settings Map
|
|
*
|
|
* @return array
|
|
*/
|
|
public function settingsMap(): array
|
|
{
|
|
return array(
|
|
'bbtitle' => 'board_name',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Settings Map Listing
|
|
*
|
|
* @return array
|
|
*/
|
|
public function settingsMapList(): array
|
|
{
|
|
$settings = array();
|
|
foreach( $this->settingsMap() AS $theirs => $ours )
|
|
{
|
|
try
|
|
{
|
|
$setting = $this->db->select( 'varname, value', 'setting', array( "varname=?", $theirs ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
$title = $this->db->select( 'text', 'phrase', array( "varname=?", "setting_{$setting['varname']}_title" ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$title = $setting['varname'];
|
|
}
|
|
|
|
$settings[$setting['varname']] = array( 'title' => $title, 'value' => $setting['value'], 'our_key' => $ours, 'our_title' => Member::loggedIn()->language()->addToStack( $ours ) );
|
|
}
|
|
|
|
return $settings;
|
|
}
|
|
|
|
/**
|
|
* Returns a block of text, or a language string, that explains what the admin must do to start this conversion
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public static function getPreConversionInformation(): ?string
|
|
{
|
|
return 'convert_vb4_preconvert';
|
|
}
|
|
|
|
/**
|
|
* List of conversion methods that require additional information
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function checkConf(): array
|
|
{
|
|
return array(
|
|
'convertEmoticons',
|
|
'convertProfileFieldGroups',
|
|
'convertProfileFields',
|
|
'convertGroups',
|
|
'convertMembers',
|
|
'convertClubs'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get More Information
|
|
*
|
|
* @param string $method Conversion method
|
|
* @return array|null
|
|
*/
|
|
public function getMoreInfo( string $method ): ?array
|
|
{
|
|
$return = array();
|
|
|
|
switch( $method )
|
|
{
|
|
case 'convertEmoticons':
|
|
$return['convertEmoticons'] = array(
|
|
'emoticon_path' => array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Text',
|
|
'field_default' => NULL,
|
|
'field_required' => TRUE,
|
|
'field_extra' => array(),
|
|
'field_hint' => Member::loggedIn()->language()->addToStack('convert_vb_smilie_path'),
|
|
'field_validation' => function( $value ) { if ( !@is_dir( $value ) ) { throw new DomainException( 'path_invalid' ); } },
|
|
),
|
|
'keep_existing_emoticons' => array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Checkbox',
|
|
'field_default' => TRUE,
|
|
'field_required' => FALSE,
|
|
'field_extra' => array(),
|
|
'field_hint' => NULL,
|
|
)
|
|
);
|
|
break;
|
|
|
|
case 'convertProfileFieldGroups':
|
|
$return['convertProfileFieldGroups'] = array();
|
|
|
|
$options = array();
|
|
$options['none'] = Member::loggedIn()->language()->addToStack( 'none' );
|
|
foreach( new ActiveRecordIterator( Db::i()->select( '*', 'core_pfields_groups' ), 'IPS\core\ProfileFields\Group' ) AS $group )
|
|
{
|
|
$options[$group->_id] = $group->_title;
|
|
}
|
|
|
|
foreach( $this->db->select( '*', 'profilefieldcategory' ) AS $group )
|
|
{
|
|
$id = $group['profilefieldcategoryid']; # vB doesn't use spaces in column names and its driving me crazy typing it out
|
|
|
|
try
|
|
{
|
|
Member::loggedIn()->language()->words["map_pfgroup_{$id}"] = $this->db->select( 'text', 'phrase', array( "varname=?", "category{$id}_title" ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
Member::loggedIn()->language()->words["map_pfgroup_{$id}"] = "vBulletin Profile Group {$id}";
|
|
}
|
|
|
|
Member::loggedIn()->language()->words["map_pfgroup_{$id}_desc"] = Member::loggedIn()->language()->addToStack( 'map_pfgroup_desc' );
|
|
|
|
$return['convertProfileFieldGroups']["map_pfgroup_{$group['profilefieldcategoryid']}"] = array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Select',
|
|
'field_default' => NULL,
|
|
'field_required' => FALSE,
|
|
'field_extra' => array( 'options' => $options ),
|
|
'field_hint' => NULL,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 'convertProfileFields':
|
|
$return['convertProfileFields'] = array();
|
|
|
|
$options = array();
|
|
$options['none'] = Member::loggedIn()->language()->addToStack( 'none' );
|
|
foreach( new ActiveRecordIterator( Db::i()->select( '*', 'core_pfields_data' ), 'IPS\core\ProfileFields\Field' ) AS $field )
|
|
{
|
|
$options[$field->_id] = $field->_title;
|
|
}
|
|
|
|
foreach( $this->db->select( '*', 'profilefield' ) AS $field )
|
|
{
|
|
try
|
|
{
|
|
Member::loggedIn()->language()->words["map_pfield_{$field['profilefieldid']}"] = $this->db->select( 'text', 'phrase', array( "varname=?", "field{$field['profilefieldid']}_title" ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
Member::loggedIn()->language()->words["map_pfield_{$field['profilefieldid']}"] = "vBulletin Profile Field {$field['profilefieldid']}";
|
|
}
|
|
Member::loggedIn()->language()->words["map_pfield_{$field['profilefieldid']}_desc"] = Member::loggedIn()->language()->addToStack( 'map_pfield_desc' );
|
|
|
|
$return['convertProfileFields']["map_pfield_{$field['profilefieldid']}"] = array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Select',
|
|
'field_default' => NULL,
|
|
'field_required' => FALSE,
|
|
'field_extra' => array( 'options' => $options ),
|
|
'field_hint' => NULL,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 'convertGroups':
|
|
$return['convertGroups'] = array();
|
|
|
|
$options = array();
|
|
$options['none'] = 'None';
|
|
foreach( new ActiveRecordIterator( Db::i()->select( '*', 'core_groups' ), 'IPS\Member\Group' ) AS $group )
|
|
{
|
|
$options[$group->g_id] = $group->name;
|
|
}
|
|
|
|
foreach( $this->db->select( '*', 'usergroup' ) AS $group )
|
|
{
|
|
Member::loggedIn()->language()->words["map_group_{$group['usergroupid']}"] = $group['title'];
|
|
Member::loggedIn()->language()->words["map_group_{$group['usergroupid']}_desc"] = Member::loggedIn()->language()->addToStack( 'map_group_desc' );
|
|
|
|
$return['convertGroups']["map_group_{$group['usergroupid']}"] = array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Select',
|
|
'field_default' => NULL,
|
|
'field_required' => FALSE,
|
|
'field_extra' => array( 'options' => $options ),
|
|
'field_hint' => NULL,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 'convertMembers':
|
|
$return['convertMembers'] = array();
|
|
|
|
/* We can only retain one type of photo */
|
|
$return['convertMembers']['photo_type'] = array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Radio',
|
|
'field_default' => 'avatars',
|
|
'field_required' => TRUE,
|
|
'field_extra' => array( 'options' => array( 'avatars' => Member::loggedIn()->language()->addToStack( 'avatars' ), 'profile_photos' => Member::loggedIn()->language()->addToStack( 'profile_photos' ) ) ),
|
|
'field_hint' => NULL,
|
|
);
|
|
|
|
/* Find out where the photos live */
|
|
$return['convertMembers']['photo_location'] = array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Radio',
|
|
'field_default' => 'database',
|
|
'field_required' => TRUE,
|
|
'field_extra' => array(
|
|
'options' => array(
|
|
'database' => Member::loggedIn()->language()->addToStack( 'conv_store_database' ),
|
|
'file_system' => Member::loggedIn()->language()->addToStack( 'conv_store_file_system' ),
|
|
),
|
|
'userSuppliedInput' => 'file_system',
|
|
),
|
|
'field_hint' => NULL,
|
|
'field_validation' => function( $value ) { if ( $value != 'database' AND !@is_dir( $value ) ) { throw new DomainException( 'path_invalid' ); } },
|
|
);
|
|
|
|
/* Find out where the avatar gallery photos live */
|
|
$return['convertMembers']['avatar_gallery_location'] = array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Text',
|
|
'field_default' => NULL,
|
|
'field_required' => TRUE,
|
|
'field_extra' => array(),
|
|
'field_hint' => Member::loggedIn()->language()->addToStack('convert_vb_avatar_gallery_path'),
|
|
'field_validation' => function( $value ) { if ( !@is_dir( $value ) ) { throw new DomainException( 'path_invalid' ); } },
|
|
);
|
|
|
|
/* And decide what to do about these... */
|
|
foreach( array( 'homepage', 'icq', 'aim', 'yahoo', 'msn', 'skype', 'user_title' ) AS $field )
|
|
{
|
|
Member::loggedIn()->language()->words["field_{$field}"] = Member::loggedIn()->language()->addToStack( 'pseudo_field', FALSE, array( 'sprintf' => ucwords( $field ) ) );
|
|
Member::loggedIn()->language()->words["field_{$field}_desc"] = Member::loggedIn()->language()->addToStack( 'pseudo_field_desc' );
|
|
$return['convertMembers']["field_{$field}"] = array(
|
|
'field_class' => 'IPS\\Helpers\\Form\\Radio',
|
|
'field_default' => 'no_convert',
|
|
'field_required' => TRUE,
|
|
'field_extra' => array(
|
|
'options' => array(
|
|
'no_convert' => Member::loggedIn()->language()->addToStack( 'no_convert' ),
|
|
'create_field' => Member::loggedIn()->language()->addToStack( 'create_field' ),
|
|
),
|
|
'userSuppliedInput' => 'create_field'
|
|
),
|
|
'field_hint' => NULL
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 'convertClubs':
|
|
Member::loggedIn()->language()->words['club_photo_location'] = Member::loggedIn()->language()->addToStack( 'photo_location' );
|
|
|
|
$return['convertClubs'] = 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' => Member::loggedIn()->language()->addToStack( 'conv_store_database' ),
|
|
'file_system' => Member::loggedIn()->language()->addToStack( 'conv_store_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();
|
|
}
|
|
|
|
/**
|
|
* Finish - Adds everything it needs to the queues and clears data store
|
|
*
|
|
* @return array Messages to display
|
|
*/
|
|
public function finish(): array
|
|
{
|
|
/* Search Index Rebuild */
|
|
Index::i()->rebuild();
|
|
|
|
/* Clear Cache and Store */
|
|
Store::i()->clearAll();
|
|
Cache::i()->clearAll();
|
|
|
|
/* Non-Content Rebuilds */
|
|
Task::queue( 'convert', 'RebuildProfilePhotos', array( 'app' => $this->app->app_id ), 5, array( 'app' ) );
|
|
Task::queue( 'convert', 'RebuildNonContent', array( 'app' => $this->app->app_id, 'link' => 'core_announcements', 'extension' => 'core_Announcement' ), 2, array( 'app', 'link', 'extension' ) );
|
|
Task::queue( 'convert', 'RebuildNonContent', array( 'app' => $this->app->app_id, 'link' => 'core_message_posts', 'extension' => 'core_Messaging' ), 2, array( 'app', 'link', 'extension' ) );
|
|
Task::queue( 'convert', 'RebuildNonContent', array( 'app' => $this->app->app_id, 'link' => 'core_members', 'extension' => 'core_Signatures' ), 2, array( 'app', 'link', 'extension' ) );
|
|
|
|
/* Content Counts */
|
|
Task::queue( 'core', 'RecountMemberContent', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
|
|
Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\core\Messenger\Message' ), 3, array( 'class' ) );
|
|
|
|
/* Clubs */
|
|
Task::queue( 'convert', 'RecountClubMembers', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
|
|
|
|
/* First Post Data */
|
|
Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
|
|
|
|
/* Attachments */
|
|
Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
|
|
|
|
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild", "f_announce_rebuild" );
|
|
}
|
|
|
|
/**
|
|
* Pre-process content for the Invision Community text parser
|
|
*
|
|
* @param string $post post
|
|
* @param string|null $className Classname passed by post-conversion rebuild
|
|
* @param int|null $contentId ID passed by post-conversion rebuild
|
|
* @return string The converted post
|
|
*/
|
|
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
|
{
|
|
if( preg_match( "#\[sigpic]#i", $post ) )
|
|
{
|
|
$post = preg_replace( "#\[sigpic\](.+?)\[\/sigpic\]#i", "", $post );
|
|
}
|
|
$post = str_replace( "<", "<", $post );
|
|
$post = str_replace( ">", ">", $post );
|
|
$post = str_replace( "'", "'", $post );
|
|
|
|
if( preg_match( "#\[quote=#i", $post ) )
|
|
{
|
|
$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 */
|
|
if( preg_match( "#\[video=#i", $post ) )
|
|
{
|
|
$post = preg_replace( "#\[video=[a-z_]+;[a-z0-9_-]+\](.*?)\[\/video\]#i", '$1', $post );
|
|
}
|
|
|
|
/* This is not a core feature in vBulletin, but is a popular addon and the code was supplied below.
|
|
Addon URL: https://www.dragonbyte-tech.com/store/product/20-advanced-user-tagging/ */
|
|
if( preg_match( "#\[mention=#i", $post ) )
|
|
{
|
|
preg_match_all( '#\[MENTION=(\d+)\](.+?)\[\/MENTION\]#i', $post, $matches );
|
|
|
|
if ( count( $matches ) )
|
|
{
|
|
/* Make sure we actually have mention data */
|
|
if ( isset( $matches[1] ) AND count( $matches[1] ) )
|
|
{
|
|
$mentions = array();
|
|
foreach ( $matches[1] AS $k => $v )
|
|
{
|
|
if ( isset( $matches[2][ $k ] ) )
|
|
{
|
|
$mentions[ $matches[2][ $k ] ] = $v;
|
|
}
|
|
}
|
|
|
|
$maps = iterator_to_array( Db::i()->select( 'name, member_id', 'core_members', array( Db::i()->in( 'name', array_keys( $mentions ) ) ) )->setKeyField( 'name' )->setValueField( 'member_id' ) );
|
|
|
|
foreach ( $mentions AS $memberName => $memberId )
|
|
{
|
|
if( !isset( $maps[ $memberName ] ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$memberNameQuoted = preg_quote( $memberName, '#' );
|
|
$post = preg_replace( "#\[MENTION={$memberId}\]{$memberNameQuoted}\[\/MENTION\]#i", "[mention={$maps[ $memberName ]}]{$memberName}[/mention]", $post );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $post;
|
|
}
|
|
|
|
/**
|
|
* Convert emoticons
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertEmoticons() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'smilieid' );
|
|
|
|
foreach( $this->fetch( 'smilie', 'smilieid' ) AS $emoticon )
|
|
{
|
|
$filename = explode( '/', $emoticon['smiliepath'] );
|
|
$filename = array_pop( $filename );
|
|
|
|
$info = array(
|
|
'id' => $emoticon['smilieid'],
|
|
'typed' => $emoticon['smilietext'],
|
|
'filename' => $filename,
|
|
'emo_position' => $emoticon['displayorder'],
|
|
);
|
|
|
|
try
|
|
{
|
|
$imageCategory = $this->db->select( '*', 'imagecategory', array( "imagecategoryid=?", $emoticon['imagecategoryid'] ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$imageCategory = array(
|
|
'title' => "Converted",
|
|
'displayorder' => 1,
|
|
);
|
|
}
|
|
|
|
$set = array(
|
|
'set' => md5( $imageCategory['title'] ),
|
|
'title' => $imageCategory['title'],
|
|
'position' => $imageCategory['displayorder']
|
|
);
|
|
|
|
/* In some rare cases, these may be URLs to a CDN */
|
|
$filedata = $filepath = null;
|
|
if ( mb_substr( $emoticon['smiliepath'], 0, 4 ) == 'http' )
|
|
{
|
|
/* Remote */
|
|
try
|
|
{
|
|
$filedata = (string) Url::external( $emoticon['smiliepath'] )->request()->get();
|
|
}
|
|
catch( \Exception $e ) {}
|
|
}
|
|
else
|
|
{
|
|
$filepath = $this->app->_session['more_info']['convertEmoticons']['emoticon_path'];
|
|
}
|
|
|
|
$libraryClass->convertEmoticon( $info, $set, $this->app->_session['more_info']['convertEmoticons']['keep_existing_emoticons'], $filepath, $filedata );
|
|
|
|
$libraryClass->setLastKeyValue( $emoticon['smilieid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert profile field groups
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertProfileFieldGroups() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'profilefieldcategoryid' );
|
|
|
|
foreach( $this->fetch( 'profilefieldcategory', 'profilefieldcategoryid' ) AS $group )
|
|
{
|
|
try
|
|
{
|
|
$name = $this->db->select( 'text', 'phrase', array( "varname=?", "category{$group['profilefieldcategoryid']}_title" ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$name = "vBulletin Profile Group {$group['profilefieldcategoryid']}";
|
|
}
|
|
|
|
$merge = ( $this->app->_session['more_info']['convertProfileFieldGroups']["map_pfgroup_{$group['profilefieldcategoryid']}"] != 'none' ) ? $this->app->_session['more_info']['convertProfileFieldGroups']["map_pfgroup_{$group['profilefieldcategoryid']}"] : NULL;
|
|
|
|
$libraryClass->convertProfileFieldGroup( array(
|
|
'pf_group_id' => $group['profilefieldcategoryid'],
|
|
'pf_group_name' => $name,
|
|
'pf_group_order' => $group['displayorder']
|
|
), $merge );
|
|
|
|
$libraryClass->setLastKeyValue( $group['profilefieldcategoryid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert profile fields
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertProfileFields() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'profilefieldid' );
|
|
|
|
foreach( $this->fetch( 'profilefield', 'profilefieldid' ) AS $field )
|
|
{
|
|
try
|
|
{
|
|
$name = $this->db->select( 'text', 'phrase', array( "varname=?", "field{$field['profilefieldid']}_title" ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$name = "vBulletin Profile Field {$field['profilefieldid']}";
|
|
}
|
|
|
|
try
|
|
{
|
|
$desc = $this->db->select( 'text', 'phrase', array( "varname=?", "field{$field['profilefieldid']}_desc" ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$desc = "";
|
|
}
|
|
|
|
$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' ) ) )
|
|
{
|
|
$options = @unserialize( $field['data'] );
|
|
if ( $options )
|
|
{
|
|
$content = json_encode( $options );
|
|
}
|
|
else
|
|
{
|
|
/* We can try to restore the serialized value */
|
|
$data = preg_replace_callback( '/s:(\d+):"(.*?)";/i', function( $match ) {
|
|
return ( $match[1] == strlen( $match[2] ) ) ? $match[0] : 's:' . strlen( $match[2] ) . ':"' . $match[2] . '";';
|
|
}, $field['data'] );
|
|
|
|
if( $options = @unserialize( $data ) )
|
|
{
|
|
$content = json_encode( $options );
|
|
}
|
|
else
|
|
{
|
|
/* Nothing else we can really do here */
|
|
$content = json_encode( array() );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$content = json_encode( array() );
|
|
}
|
|
|
|
$type = static::_pfieldMap( $field['type'] );
|
|
|
|
$multiple = 0;
|
|
if ( $field['type'] == 'select_multiple' )
|
|
{
|
|
$multiple = 1;
|
|
}
|
|
|
|
$info = array(
|
|
'pf_id' => $field['profilefieldid'],
|
|
'pf_name' => $name,
|
|
'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'] ? '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_group_id' => $field['profilefieldcategoryid'],
|
|
'pf_multiple' => $multiple
|
|
);
|
|
|
|
$libraryClass->convertProfileField( $info, $merge );
|
|
|
|
$libraryClass->setLastKeyValue( $field['profilefieldid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps vBulletin Profile Field type to the IPS Equivalent
|
|
*
|
|
* @param string $type The vB Field Type
|
|
* @return string The IPS Field Type
|
|
*/
|
|
protected static function _pfieldMap( string $type ) : string
|
|
{
|
|
switch( mb_strtolower( $type ) )
|
|
{
|
|
case 'select':
|
|
case 'radio':
|
|
case 'checkbox':
|
|
return ucwords( $type );
|
|
|
|
case 'textarea':
|
|
return 'TextArea';
|
|
|
|
case 'select_multiple':
|
|
return 'Select';
|
|
|
|
/* Just do Text as default */
|
|
default:
|
|
return 'Text';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert groups
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertGroups() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'usergroupid' );
|
|
|
|
foreach( $this->fetch( 'usergroup', 'usergroupid' ) AS $group )
|
|
{
|
|
/* <3 Closures */
|
|
$self = $this;
|
|
$checkpermission = function ( $name, $perm ) use ( $group, $self )
|
|
{
|
|
if ( $group[$name] & self::$bitOptions[$name][$perm] )
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
};
|
|
|
|
/* Work out promotion */
|
|
$g_promotion = '-1&-1';
|
|
$gbw_promote_unit = 0;
|
|
try
|
|
{
|
|
$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 ) ) )
|
|
{
|
|
switch( $promotion['strategy'] )
|
|
{
|
|
case 17: # posts
|
|
$g_promotion = array( $promotion['joinusergroupid'], $promotion['posts'] );
|
|
break;
|
|
|
|
case 18: #joindate
|
|
$g_promotion = array( $promotion['joinusergroupid'], $promotion['date'] );
|
|
$gbw_promote_unit = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch( UnderflowException $e ) {}
|
|
|
|
/* Work out photo vars - vBulletin has a concept of avatars and profile photos, so we are just going to use the larger of the two */
|
|
$g_max_photo_vars = array();
|
|
$g_max_photo_vars[] = ( $group['profilepicmaxsize'] > $group['avatarmaxsize'] ) ? $group['profilepicmaxsize'] : $group['avatarmaxsize'];
|
|
|
|
/* We don't have individual controls for height and width, so work out which value is the largest and use that */
|
|
$highestValue = 0;
|
|
foreach( array( 'profilepicmaxheight', 'avatarmaxheight', 'profilepicmaxwidth', 'avatarmaxwidth' ) AS $value )
|
|
{
|
|
if ( $group[$value] > $highestValue )
|
|
{
|
|
$highestValue = $group[$value];
|
|
}
|
|
}
|
|
|
|
$g_max_photo_vars[] = $highestValue;
|
|
$g_max_photo_vars[] = $highestValue;
|
|
|
|
/* Work out signature limits */
|
|
$g_signature_limits = '1:::::';
|
|
if ( $checkpermission( 'genericpermissions', 'canusesignature' ) )
|
|
{
|
|
$g_signature_limits = array();
|
|
$g_signature_limits[] = 0;
|
|
$g_signature_limits[] = $group['sigmaximages'];
|
|
$g_signature_limits[] = $group['sigpicmaxwidth'];
|
|
$g_signature_limits[] = $group['sigpicmaxheight'];
|
|
$g_signature_limits[] = '';
|
|
$g_signature_limits[] = ( $group['sigmaxlines'] > 0 ) ? $group['sigmaxlines'] : '';
|
|
}
|
|
|
|
/* Let's dissect all of these bit options */
|
|
$info = array(
|
|
'g_id' => $group['usergroupid'],
|
|
'g_name' => $group['title'],
|
|
'g_view_board' => $checkpermission( 'forumpermissions', 'canview' ) ? 1 : 0,
|
|
'g_mem_info' => $checkpermission( 'genericpermissions', 'canviewmembers' ) ? 1 : 0,
|
|
'g_use_search' => $checkpermission( 'forumpermissions', 'cansearch' ) ? 1 : 0,
|
|
'g_edit_profile' => $checkpermission( 'genericpermissions', 'canmodifyprofile' ) ? 1 : 0,
|
|
'g_edit_posts' => $checkpermission( 'forumpermissions', 'caneditpost' ) ? 1 : 0,
|
|
'g_delete_own_posts' => $checkpermission( 'forumpermissions', 'candeletepost' ) ? 1 : 0,
|
|
'g_use_pm' => ( $group['pmquota'] > 0 ) ? 1 : 0,
|
|
'g_is_supmod' => $checkpermission( 'adminpermissions', 'ismoderator' ) ? 1 : 0,
|
|
'g_access_cp' => $checkpermission( 'adminpermissions', 'cancontrolpanel' ) ? 1 : 0,
|
|
'g_append_edit' => $checkpermission( 'genericoptions', 'showeditedby' ) ? 1 : 0, # Fun fact, I couldn't find this as it was actually in the place it should be rather than forumpermissions
|
|
'g_access_offline' => $checkpermission( 'adminpermissions', 'cancontrolpanel' ) ? 1 : 0,
|
|
'g_attach_max' => ( $group['attachlimit'] > 0 ) ? $group['attachlimit'] : -1,
|
|
'prefix' => $group['opentag'],
|
|
'suffix' => $group['closetag'],
|
|
'g_max_messages' => $group['pmquota'],
|
|
'g_max_mass_pm' => $group['pmsendmax'],
|
|
'g_promotion' => $g_promotion,
|
|
'g_photo_max_vars' => $g_max_photo_vars,
|
|
'g_bypass_badwords' => ( ( $checkpermission( 'adminpermissions', 'ismoderator' ) OR $checkpermission( 'adminpermissions', 'cancontrolpanel' ) ) AND $this->_setting( 'ctCensorMod' ) ),
|
|
'g_mod_preview' => !$checkpermission( 'forumpermissions', 'followforummoderation' ) ? 1 : 0,
|
|
'g_signature_limits' => $g_signature_limits,
|
|
'g_bitoptions' => array(
|
|
'gbw_promote_unit_type' => $gbw_promote_unit, // Type of group promotion to use. 1 is days since joining, 0 is content count. Corresponds to g_promotion
|
|
'gbw_allow_upload_bgimage' => 1, // Can upload a cover photo?
|
|
'gbw_view_reps' => $checkpermission( 'genericpermissions2', 'canprofilerep' ), // Can view who gave reputation?
|
|
'gbw_disable_tagging' => !$checkpermission( 'genericpermissions', 'cancreatetag' ), // Can NOT use tags
|
|
'gbw_disable_prefixes' => !$checkpermission( 'genericpermissions', 'cancreatetag' ), // Can NOT use prefixes
|
|
'gbw_pm_override_inbox_full'=> $checkpermission( 'pmpermissions', 'canignorequota' ), // 1 means this group can send other members PMs even when that member's inbox is full
|
|
'gbw_cannot_be_ignored' => ( ( $checkpermission( 'adminpermissions', 'ismoderator' ) OR $checkpermission( 'adminpermissions', 'cancontrolpanel' ) ) AND $this->_setting( 'ignoremods' ) ), // 1 means they cannot be ignored. 0 means they can
|
|
),
|
|
'g_hide_own_posts' => $checkpermission( 'forumpermissions', 'candeletepost' ),
|
|
'g_pm_flood_mins' => $this->_setting( 'pmfloodtime' ) / 60,
|
|
'g_post_polls' => $checkpermission( 'forumpermissions', 'canpostpoll' ) ? 1 : 0,
|
|
'g_vote_polls' => $checkpermission( 'forumpermissions', 'canvote' ) ? 1 : 0
|
|
);
|
|
|
|
$merge = ( $this->app->_session['more_info']['convertGroups']["map_group_{$group['usergroupid']}"] != 'none' ) ? $this->app->_session['more_info']['convertGroups']["map_group_{$group['usergroupid']}"] : NULL;
|
|
|
|
$libraryClass->convertGroup( $info, $merge );
|
|
|
|
$libraryClass->setLastKeyValue( $group['usergroupid'] );
|
|
}
|
|
|
|
/* Now check for group promotions */
|
|
if( count( $libraryClass->groupPromotions ) )
|
|
{
|
|
foreach( $libraryClass->groupPromotions as $groupPromotion )
|
|
{
|
|
$libraryClass->convertGroupPromotion( $groupPromotion );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Member history type map
|
|
*/
|
|
protected static array $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'usergroupid' => 'group', 'membergroupids' => 'group' );
|
|
|
|
/**
|
|
* Convert member history
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertMemberHistory() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
$libraryClass::setKey( 'changeid' );
|
|
|
|
foreach( $this->fetch( 'userchangelog', 'changeid', array( $this->db->in( 'fieldname', array_keys( static::$changeLogTypes ) ) ) ) AS $change )
|
|
{
|
|
$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']
|
|
);
|
|
|
|
/* 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'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief default pseudo fields to convert
|
|
*/
|
|
protected static array $pseudoFields = [ 'homepage', 'icq', 'aim', 'yahoo', 'msn', 'skype', 'user_title' ];
|
|
|
|
/**
|
|
* Convert members
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertMembers() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
$libraryClass::setKey( 'user.userid' );
|
|
|
|
/* User titles */
|
|
$userTitles = iterator_to_array( $this->db->select( 'title', 'usertitle' ) );
|
|
$userTitles = array_merge( $userTitles, iterator_to_array( $this->db->select( 'usertitle', 'usergroup', [ 'usertitle!=?', '' ] ) ) );
|
|
|
|
$data = iterator_to_array( $this->fetch( 'user', 'user.userid', NULL, 'user.*, usertextfield.signature' )->join( 'usertextfield', 'user.userid = usertextfield.userid' ) );
|
|
$userIds = array_column( $data, 'userid' );
|
|
|
|
/* User Data */
|
|
$bans = iterator_to_array( $this->db->select( 'liftdate, userid', 'userban', [ $this->db->in( 'userid', $userIds ) ] )->setKeyField('userid')->setValueField( 'liftdate') );
|
|
$activation = iterator_to_array( $this->db->select( '*', 'useractivation', [ $this->db->in( 'userid', $userIds ) ] )->setKeyField('userid') );
|
|
$fields = iterator_to_array( $this->db->select( '*', 'userfield', [ $this->db->in( 'userid', $userIds ) ] )->setKeyField( 'userid' ) );
|
|
|
|
foreach( $data AS $user )
|
|
{
|
|
/* <3 Closures */
|
|
$self = $this;
|
|
$checkpermission = function ( $name, $perm ) use ( $user, $self )
|
|
{
|
|
if ( $name == 'useroptions' )
|
|
{
|
|
$key = 'options';
|
|
}
|
|
else
|
|
{
|
|
$key = $name;
|
|
}
|
|
|
|
if ( $user[$key] & self::$bitOptions[$name][$perm] )
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
};
|
|
|
|
/* Fetch our last warning */
|
|
$warnings = iterator_to_array( $this->db->select( '*', 'infraction', array( "userid=?", $user['userid'] ), "dateline ASC" ) );
|
|
$lastWarning = 0;
|
|
|
|
/* If there are warnings, get the last warning */
|
|
if( count( $warnings ) )
|
|
{
|
|
$lastWarning = end( $warnings );
|
|
reset( $warnings );
|
|
}
|
|
|
|
/* Birthday */
|
|
if ( $user['birthday'] )
|
|
{
|
|
list( $bday_month, $bday_day, $bday_year ) = explode( '-', $user['birthday'] );
|
|
|
|
if ( $bday_year == '0000' )
|
|
{
|
|
$bday_year = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$bday_month = $bday_day = $bday_year = NULL;
|
|
}
|
|
|
|
/* Auto Track */
|
|
$auto_track = 0;
|
|
switch( $user['autosubscribe'] )
|
|
{
|
|
case 1:
|
|
$auto_track = array( 'content' => true, 'comments' => true, 'method' => 'immediate' );
|
|
break;
|
|
|
|
case 2:
|
|
$auto_track = array( 'content' => true, 'comments' => true, 'method' => 'daily' );
|
|
break;
|
|
|
|
case 3:
|
|
$auto_track = array( 'content' => true, 'comments' => true, 'method' => 'weekly' );
|
|
break;
|
|
}
|
|
|
|
/* User Banned */
|
|
$temp_ban = 0;
|
|
try
|
|
{
|
|
if( !isset( $bans[ $user['userid'] ] ) )
|
|
{
|
|
throw new UnderflowException();
|
|
}
|
|
$temp_ban = $bans[ $user['userid'] ];
|
|
|
|
if ( $temp_ban == 0 )
|
|
{
|
|
$temp_ban = -1;
|
|
}
|
|
}
|
|
catch( UnderflowException $e ) {}
|
|
|
|
/* Main Members Table */
|
|
$info = array(
|
|
'member_id' => $user['userid'],
|
|
'member_group_id' => $user['usergroupid'],
|
|
'mgroup_others' => $user['membergroupids'],
|
|
'name' => html_entity_decode( $user['username'], ENT_QUOTES | ENT_HTML5, 'UTF-8' ),
|
|
'email' => $user['email'],
|
|
'joined' => $user['joindate'],
|
|
'ip_address' => $user['ipaddress'],
|
|
'warn_level' => ( $user['ipoints'] > 2147483647 ) ? 2147483647 : $user['ipoints'],
|
|
'warn_lastwarn' => ( is_array( $lastWarning ) ) ? $lastWarning['dateline'] : $lastWarning,
|
|
'bday_day' => $bday_day,
|
|
'bday_month' => $bday_month,
|
|
'bday_year' => $bday_year,
|
|
'last_visit' => $user['lastvisit'],
|
|
'last_activity' => $user['lastactivity'],
|
|
'auto_track' => $auto_track,
|
|
'temp_ban' => $temp_ban,
|
|
'members_profile_views' => $user['profilevisits'],
|
|
'conv_password' => $user['password'],
|
|
'conv_password_extra' => $user['salt'],
|
|
'members_bitoptions' => array(
|
|
'view_sigs' => $checkpermission( 'useroptions', 'showsignatures' ), // View signatures?
|
|
'coppa_user' => $checkpermission( 'useroptions', 'coppauser' ), // Was the member validated using coppa?
|
|
),
|
|
'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'],
|
|
'signature' => $user['signature'] ?: '',
|
|
'allow_admin_mails' => $checkpermission( 'useroptions', 'adminemail' ),
|
|
'members_disable_pm' => !$checkpermission( 'useroptions', 'receivepm' ),
|
|
'member_posts' => $user['posts'],
|
|
);
|
|
|
|
/* If there is a validation record for this user, treat them as validating */
|
|
try
|
|
{
|
|
if( !isset( $activation[ $user['userid'] ] ) )
|
|
{
|
|
throw new UnderflowException();
|
|
}
|
|
$activation = $activation[ $user['userid'] ];
|
|
|
|
$info['members_bitoptions']['validating'] = TRUE;
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$activation = NULL;
|
|
}
|
|
|
|
/* Profile Fields */
|
|
try
|
|
{
|
|
if( !isset( $fields[ $user['userid'] ] ) )
|
|
{
|
|
throw new UnderflowException();
|
|
}
|
|
$profileFields = $fields[ $user['userid'] ];
|
|
|
|
unset( $profileFields['userid'] );
|
|
unset( $profileFields['temp'] );
|
|
|
|
/* Basic fields - we only need ID => Value, the library will handle the rest */
|
|
foreach( $profileFields AS $key => $value )
|
|
{
|
|
$profileFields[ str_replace( 'field', '', $key ) ] = $value;
|
|
}
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$profileFields = array();
|
|
}
|
|
|
|
/* Pseudo Fields */
|
|
foreach( static::$pseudoFields AS $pseudo )
|
|
{
|
|
/* Are we retaining? */
|
|
if ( $this->app->_session['more_info']['convertMembers']["field_{$pseudo}"] == 'no_convert' )
|
|
{
|
|
/* No, skip */
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
/* We don't actually need this, but we need to make sure the field was created */
|
|
$this->app->getLink( $pseudo, 'core_pfields_data' );
|
|
}
|
|
catch( OutOfRangeException $e )
|
|
{
|
|
$libraryClass->convertProfileField( array(
|
|
'pf_id' => $pseudo,
|
|
'pf_name' => $this->app->_session['more_info']['convertMembers']["field_{$pseudo}"],
|
|
'pf_desc' => '',
|
|
'pf_type' => 'Text',
|
|
'pf_content' => '[]',
|
|
'pf_member_hide' => 'all',
|
|
'pf_max_input' => 255,
|
|
'pf_member_edit' => 1,
|
|
'pf_show_on_reg' => 0,
|
|
) );
|
|
}
|
|
|
|
if( $pseudo == 'user_title' )
|
|
{
|
|
$profileFields[ $pseudo ] = in_array( $user['usertitle'], $userTitles ) ? NULL : $user['usertitle'];
|
|
}
|
|
else
|
|
{
|
|
$profileFields[ $pseudo ] = $user[ $pseudo ];
|
|
}
|
|
}
|
|
|
|
/* Profile Photos */
|
|
$firstTable = 'customavatar';
|
|
$secondTable = 'customprofilepic';
|
|
if ( $this->app->_session['more_info']['convertMembers']['photo_type'] == 'profile_photos' )
|
|
{
|
|
$firstTable = 'customprofilepic';
|
|
$secondTable = 'customavatar';
|
|
}
|
|
$filedata = NULL;
|
|
$filename = NULL;
|
|
if ( $this->app->_session['more_info']['convertMembers']['photo_location'] == 'database' )
|
|
{
|
|
try
|
|
{
|
|
foreach( $this->db->select( 'filedata, filename', $firstTable, array( "userid=?", $user['userid'] ) )->first() AS $key => $value )
|
|
{
|
|
if ( $key == 'filedata' )
|
|
{
|
|
$filedata = $value;
|
|
}
|
|
else
|
|
{
|
|
$filename = $value;
|
|
}
|
|
}
|
|
$filepath = NULL;
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
try
|
|
{
|
|
foreach( $this->db->select( 'filedata, filename', $secondTable, array( "userid=?", $user['userid'] ) )->first() AS $key => $value )
|
|
{
|
|
if ( $key == 'filedata' )
|
|
{
|
|
$filedata = $value;
|
|
}
|
|
else
|
|
{
|
|
$filename = $value;
|
|
}
|
|
}
|
|
|
|
$filepath = NULL;
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
list( $filedata, $filename, $filepath ) = array( NULL, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
if( !empty( $filedata ) AND empty( $filename ) )
|
|
{
|
|
$filename = 'vb-import-' . $info['member_id'] . '.jpg'; // We'll use job here, but image detection will figure it out from magic bytes
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$filepath = $this->app->_session['more_info']['convertMembers']['photo_location'];
|
|
$first = 'avatar';
|
|
$second = 'profilepic';
|
|
|
|
if ( $this->app->_session['more_info']['convertMembers']['photo_type'] == 'profile_photos' )
|
|
{
|
|
$first = 'profilepic';
|
|
$second = 'avatar';
|
|
}
|
|
|
|
try
|
|
{
|
|
try
|
|
{
|
|
$ext = $this->db->select( 'filename', $firstTable, array( "userid=?", $user['userid'] ) )->first();
|
|
$ext = explode( '.', $ext );
|
|
$ext = array_pop( $ext );
|
|
|
|
if ( file_exists( rtrim( $filepath, '/' ) . '/' . $first . $user['userid'] . '_' . $user[$first . 'revision'] . '.'. $ext ) )
|
|
{
|
|
$filename = $first . $user['userid'] . '_' . $user[$first . 'revision'] . '.'. $ext;
|
|
}
|
|
else
|
|
{
|
|
/* The filename with the original extension doesn't exist. vBulletin may storing the image as a .gif file instead, so try that. */
|
|
if ( file_exists( rtrim( $filepath, '/' ) . '/' . $first . $user['userid'] . '_' . $user[$first . 'revision'] . '.gif' ) )
|
|
{
|
|
$filename = $first . $user['userid'] . '_' . $user[$first . 'revision'] . '.gif';
|
|
}
|
|
else
|
|
{
|
|
/* Throw an exception so we can try the other */
|
|
throw new UnderflowException;
|
|
}
|
|
}
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$ext = $this->db->select( 'filename', $secondTable, array( "userid=?", $user['userid'] ) )->first();
|
|
$ext = explode( '.', $ext );
|
|
$ext = array_pop( $ext );
|
|
|
|
if ( file_exists( rtrim( $filepath, '/' ) . '/' . $second . $user['userid'] . '_' . $user[$second . 'revision'] . '.'. $ext ) )
|
|
{
|
|
$filename = $second . $user['userid'] . '_' . $user[$second . 'revision'] . '.'. $ext;
|
|
}
|
|
else
|
|
{
|
|
/* The filename with the original extension doesn't exist. vBulletin may be storing the image as a .gif file instead, so try that. */
|
|
if ( file_exists( rtrim( $filepath, '/' ) . '/' . $second . $user['userid'] . '_' . $user[$second . 'revision'] . '.gif' ) )
|
|
{
|
|
$filename = $second . $user['userid'] . '_' . $user[$second . 'revision'] . '.gif';
|
|
}
|
|
else
|
|
{
|
|
throw new UnderflowException;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
list( $filedata, $filename, $filepath ) = array( NULL, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
/* We'll try the avatar gallery */
|
|
if( empty( $filename ) AND empty( $filedata ) AND (int) $user['avatarid'] )
|
|
{
|
|
$avatar = $this->_getFromAvatarGallery( (int) $user['avatarid'] );
|
|
|
|
if( is_array( $avatar ) )
|
|
{
|
|
$filename = $avatar['name'];
|
|
$filedata = $avatar['data'];
|
|
}
|
|
}
|
|
|
|
$memberId = $libraryClass->convertMember( $info, $profileFields, $filename, $filepath, $filedata );
|
|
|
|
/* If we are validating, store our validation row entry */
|
|
if( $activation !== NULL AND $memberId )
|
|
{
|
|
Db::i()->replace( 'core_validating', array(
|
|
'vid' => md5( $activation['activationid'] . $activation['useractivationid'] ),
|
|
'member_id' => $memberId,
|
|
'entry_date' => $activation['dateline'],
|
|
'new_reg' => $activation['emailchange'] == 0,
|
|
'email_chg' => $activation['emailchange'] == 1,
|
|
'user_verified' => FALSE,
|
|
'ip_address' => $user['ipaddress'],
|
|
'email_sent' => $activation['dateline']
|
|
) );
|
|
}
|
|
|
|
/* Skip this if we know that the user doesn't have any warnings */
|
|
if( count( $warnings ) )
|
|
{
|
|
/* And warn logs made on the profile - we'll do content specific later */
|
|
foreach ( $warnings AS $warn )
|
|
{
|
|
if( $warn['postid'] )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$warnId = $libraryClass->convertWarnLog( array(
|
|
'wl_id' => $warn['infractionid'],
|
|
'wl_member' => $warn['userid'],
|
|
'wl_moderator' => $warn['whoadded'],
|
|
'wl_date' => $warn['dateline'],
|
|
'wl_points' => $warn['points'],
|
|
'wl_note_member' => $warn['note'],
|
|
'wl_note_mods' => $warn['customreason'],
|
|
) );
|
|
|
|
/* Add a member history record for this member */
|
|
$libraryClass->convertMemberHistory( array(
|
|
'log_id' => 'w' . $warn['infractionid'],
|
|
'log_member' => $warn['userid'],
|
|
'log_by' => $warn['whoadded'],
|
|
'log_type' => 'warning',
|
|
'log_data' => array( 'wid' => $warnId ),
|
|
'log_date' => $warn['dateline']
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
$libraryClass->setLastKeyValue( $user['userid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert member followers
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertMembersFollowers() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
foreach ( $this->fetch( 'userlist', 'relationid', array( "type=? AND friend=?", 'buddy', 'yes' ) ) as $row )
|
|
{
|
|
$libraryClass->convertFollow( array(
|
|
'follow_app' => 'core',
|
|
'follow_area' => 'member',
|
|
'follow_rel_id' => $row['relationid'],
|
|
'follow_rel_id_type' => 'core_members',
|
|
'follow_member_id' => $row['userid'],
|
|
'follow_notify_freq' => 'none',
|
|
) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert one or more settings
|
|
*
|
|
* @param array $settings Settings to convert
|
|
* @return void
|
|
*/
|
|
public function convertSettings( array $settings=array() ) : void
|
|
{
|
|
foreach( $this->settingsMap() AS $theirs => $ours )
|
|
{
|
|
if ( !isset( $settings[$ours] ) OR !$settings[$ours] )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
$setting = $this->db->select( 'value', 'setting', array( "varname=?", $theirs ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $setting ), array( "conf_key=?", $ours ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert ignored users
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertIgnoredUsers() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
foreach( $this->fetch( 'userlist', 'userid', array( "type=?", 'ignore' ) ) AS $ignore )
|
|
{
|
|
$info = array(
|
|
'ignore_id' => $ignore['userid'] . '-' . $ignore['relationid'],
|
|
'ignore_owner_id' => $ignore['userid'],
|
|
'ignore_ignore_id' => $ignore['relationid'],
|
|
);
|
|
|
|
foreach( Ignore::types() AS $type )
|
|
{
|
|
$info['ignore_' . $type] = 1;
|
|
}
|
|
|
|
$libraryClass->convertIgnoredUser( $info );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert custom bbcode
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertCustomBbcode() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'bbcodeid' );
|
|
|
|
foreach( $this->fetch( 'bbcode', 'bbcodeid' ) AS $bbcode )
|
|
{
|
|
$libraryClass->convertCustomBbcode( array(
|
|
'bbcode_id' => $bbcode['bbcodeid'],
|
|
'bbcode_title' => $bbcode['title'],
|
|
'bbcode_description' => $bbcode['bbcodeexplanation'],
|
|
'bbcode_tag' => $bbcode['bbcodetag'],
|
|
'bbcode_replacement' => str_replace( '%1$s', '{content}', $bbcode['bbcodereplacement'] ),
|
|
'bbcode_example' => $bbcode['bbcodeexample']
|
|
) );
|
|
|
|
$libraryClass->setLastKeyValue( $bbcode['bbcodeid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert announcements
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertAnnouncements() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'announcementid' );
|
|
|
|
foreach( $this->fetch( 'announcement', 'announcementid' ) AS $announcement )
|
|
{
|
|
$libraryClass->convertAnnouncement( array(
|
|
'announce_id' => $announcement['announcementid'],
|
|
'announce_title' => $announcement['title'],
|
|
'announce_content' => $announcement['pagetext'],
|
|
'announce_member_id' => $announcement['userid'],
|
|
'announce_views' => $announcement['views'],
|
|
'announce_start' => $announcement['startdate'],
|
|
'announce_end' => $announcement['enddate'],
|
|
'announce_active' => 0, # Set this to no - we can't really figure out where these go.
|
|
) );
|
|
|
|
$libraryClass->setLastKeyValue( $announcement['announcementid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert emoticons
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertPrivateMessages() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'pm.pmid' );
|
|
|
|
foreach( $this->fetch( 'pm', 'pm.pmid', array( "pm.parentpmid=?", 0 ), "pm.*, pmtext.*" )->join( 'pmtext', 'pm.pmtextid = pmtext.pmtextid' ) AS $pm )
|
|
{
|
|
if ( !$pm['pmtextid'] )
|
|
{
|
|
$libraryClass->setLastKeyValue( $pm['pmid'] );
|
|
continue;
|
|
}
|
|
|
|
/* Convert PM */
|
|
$this->_convertPm( $libraryClass, $pm );
|
|
|
|
$libraryClass->setLastKeyValue( $pm['pmid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert PM replies
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertPrivateMessageReplies() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'pm.pmid' );
|
|
|
|
foreach( $this->fetch( 'pm', 'pm.pmid', array( 'NOT (pm.folderid= -1 AND pm.parentpmid > 0)' ), "pm.*, pmtext.*, test.pmid as parent_test" )
|
|
->join( 'pmtext', 'pm.pmtextid = pmtext.pmtextid' )
|
|
->join( array( 'pm', 'test' ), 'pm.parentpmid=test.pmid' )
|
|
AS $pm )
|
|
{
|
|
/* See if the parent exists, or whether it's already been deleted in vBulletin. */
|
|
$parentId = FALSE;
|
|
if( $pm['parentpmid'] AND !$pm['parent_test'] )
|
|
{
|
|
/* It has previously been deleted, so this message can become it's own conversation */
|
|
$parentId = $this->_convertPm( $libraryClass, $pm );
|
|
}
|
|
|
|
$libraryClass->convertPrivateMessageReply( array(
|
|
'msg_id' => $pm['pmid'],
|
|
'msg_topic_id' => (!$pm['parentpmid'] OR $parentId ) ? $pm['pmid'] : $pm['parentpmid'],
|
|
'msg_date' => $pm['dateline'],
|
|
'msg_post' => $pm['message'],
|
|
'msg_author_id' => $pm['fromuserid'],
|
|
'msg_is_first_post' => 1
|
|
) );
|
|
|
|
$libraryClass->setLastKeyValue( $pm['pmid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert clubs
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertClubs() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
|
|
$libraryClass::setKey( 'groupid' );
|
|
|
|
foreach( $this->fetch( 'socialgroup', 'groupid' ) AS $group )
|
|
{
|
|
switch( $group['type'] )
|
|
{
|
|
case 'public':
|
|
$type = 'public';
|
|
break;
|
|
|
|
case 'moderated':
|
|
$type = 'closed';
|
|
break;
|
|
|
|
case 'inviteonly':
|
|
$type = 'private';
|
|
break;
|
|
}
|
|
|
|
$icon = NULL;
|
|
try
|
|
{
|
|
$icon = $this->db->select( '*', 'socialgroupicon', array( "groupid=?", $group['groupid'] ) )->first();
|
|
}
|
|
catch( UnderflowException $e ) {}
|
|
|
|
$info = array(
|
|
'club_id' => $group['groupid'],
|
|
'name' => $group['name'],
|
|
'type' => $type,
|
|
'created' => $group['dateline'],
|
|
'members' => $group['members'],
|
|
'owner' => $group['creatoruserid'],
|
|
'profile_photo' => ( $icon ) ? "clubicon{$group['groupid']}.{$icon['extension']}" : NULL,
|
|
'about' => $group['description'],
|
|
'last_activity' => $group['lastupdate']
|
|
);
|
|
|
|
$iconpath = NULL;
|
|
$icondata = NULL;
|
|
if ( $icon )
|
|
{
|
|
if ( $this->app->_session['more_info']['convertClubs']['club_photo_location'] == 'database' )
|
|
{
|
|
$icondata = $icon['filedata'];
|
|
}
|
|
else
|
|
{
|
|
$iconpath = $this->app->_session['more_info']['convertClubs']['club_photo_location'] . "/socialgroupicon_{$icon['groupid']}_{$icon['dateline']}.gif";
|
|
}
|
|
}
|
|
|
|
$libraryClass->convertClub( $info, $iconpath, $icondata );
|
|
|
|
$libraryClass->setLastKeyValue( $group['groupid'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert club members
|
|
*
|
|
* @return void
|
|
*/
|
|
public function convertClubMembers() : void
|
|
{
|
|
$libraryClass = $this->getLibrary();
|
|
foreach( $this->fetch( 'socialgroupmember', 'groupid' ) AS $member )
|
|
{
|
|
$type = NULL;
|
|
try
|
|
{
|
|
$group = $this->db->select( '*', 'socialgroup', array( "groupid=?", $member['groupid'] ) )->first();
|
|
|
|
if ( $member['userid'] == $group['creatoruserid'] )
|
|
{
|
|
$type = 'leader';
|
|
}
|
|
}
|
|
catch( UnderflowException $e ) {}
|
|
|
|
if ( $type === NULL )
|
|
{
|
|
$type = ( $member['type'] === 'moderated' ) ? 'requested' : $member['type'];
|
|
}
|
|
|
|
$libraryClass->convertClubMember( array(
|
|
'club_id' => $member['groupid'],
|
|
'member_id' => $member['userid'],
|
|
'joined' => $member['dateline'],
|
|
'status' => $type,
|
|
) );
|
|
}
|
|
}
|
|
|
|
/* !vBulletin Specific Stuff */
|
|
/**
|
|
* @brief Silly Bit Options for Groups. Typically we would leave out app specific options (such as Forums here) however we need them for some general permissions, like uploading.
|
|
* @note This is public simply because I do not want to do this ever again if it ever changes.
|
|
*/
|
|
public static array $bitOptions = array(
|
|
'forumpermissions' => array(
|
|
'canview' => 1,
|
|
'canviewothers' => 2,
|
|
'cansearch' => 4,
|
|
'canemail' => 8,
|
|
'canpostnew' => 16,
|
|
'canreplyown' => 32,
|
|
'canreplyothers' => 64,
|
|
'caneditpost' => 128,
|
|
'candeletepost' => 256,
|
|
'candeletethread' => 512,
|
|
'canopenclose' => 1024,
|
|
'canmove' => 2048,
|
|
'cangetattachment' => 4096,
|
|
'canpostattachment' => 8192,
|
|
'canpostpoll' => 16384,
|
|
'canvote' => 32768,
|
|
'canthreadrate' => 65536,
|
|
'followforummoderation' => 131072,
|
|
'canseedelnotice' => 262144,
|
|
'canviewthreads' => 524288,
|
|
'cantagown' => 1048576,
|
|
'cantagothers' => 2097152,
|
|
'candeletetagown' => 4194304,
|
|
'canseethumbnails' => 8388608,
|
|
'canattachmentcss' => 16777216,
|
|
'bypassdoublepost' => 33554432,
|
|
'canwrtmembers' => 67108864,
|
|
),
|
|
'pmpermissions' => array(
|
|
'cantrackpm' => 1,
|
|
'candenypmreceipts' => 2,
|
|
'canignorequota' => 4,
|
|
),
|
|
'wolpermissions' => array(
|
|
'canwhosonline' => 1,
|
|
'canwhosonlineip' => 2,
|
|
'canwhosonlinefull' => 4,
|
|
'canwhosonlinebad' => 8,
|
|
'canwhosonlinelocation' => 16,
|
|
),
|
|
'adminpermissions' => array(
|
|
'ismoderator' => 1,
|
|
'cancontrolpanel' => 2,
|
|
'canadminsettings' => 4,
|
|
'canadminstyles' => 8,
|
|
'canadminlanguages' => 16,
|
|
'canadminforums' => 32,
|
|
'canadminthreads' => 64,
|
|
'canadmincalendars' => 128,
|
|
'canadminusers' => 256,
|
|
'canadminpermissions' => 512,
|
|
'canadminfaq' => 1024,
|
|
'canadminimages' => 2048,
|
|
'canadminbbcodes' => 4096,
|
|
'canadmincron' => 8192,
|
|
'canadminmaintain' => 16384,
|
|
'canadminplugins' => 65536,
|
|
'canadminnotices' => 131072,
|
|
'canadminmodlog' => 262144,
|
|
'cansitemap' => 524288,
|
|
'canadminads' => 1048576,
|
|
'canadmintags' => 2097152,
|
|
'canadminblocks' => 4194304,
|
|
'cansetdefaultprofile' => 8388608,
|
|
),
|
|
'genericpermissions' => array(
|
|
'canviewmembers' => 1,
|
|
'canmodifyprofile' => 2,
|
|
'caninvisible' => 4,
|
|
'canviewothersusernotes' => 8,
|
|
'canmanageownusernotes' => 16,
|
|
'canseehidden' => 32,
|
|
'canbeusernoted' => 64,
|
|
'canprofilepic' => 128,
|
|
'canseeownrep' => 256,
|
|
'cananimateprofilepic' => 134217728,
|
|
'canuseavatar' => 512,
|
|
'canusesignature' => 1024,
|
|
'canusecustomtitle' => 2048,
|
|
'canseeprofilepic' => 4096,
|
|
'canviewownusernotes' => 8192,
|
|
'canmanageothersusernotes' => 16384,
|
|
'canpostownusernotes' => 32768,
|
|
'canpostothersusernotes' => 65536,
|
|
'caneditownusernotes' => 131072,
|
|
'canseehiddencustomfields' => 262144,
|
|
'canuserep' => 524288,
|
|
'canhiderep' => 1048576,
|
|
'cannegativerep' => 2097152,
|
|
'cangiveinfraction' => 4194304,
|
|
'cananimateavatar' => 67108864,
|
|
'canseeinfraction' => 8388608,
|
|
'cangivearbinfraction' => 536870912,
|
|
'canreverseinfraction' => 16777216,
|
|
'cansearchft_bool' => 33554432,
|
|
'canemailmember' => 268435456,
|
|
'cancreatetag' => 1073741824,
|
|
),
|
|
'genericpermissions2' => array(
|
|
'canusefriends' => 1,
|
|
'canprofilerep' => 2,
|
|
'canwgomembers' => 4,
|
|
),
|
|
'genericoptions' => array(
|
|
'showgroup' => 1,
|
|
'showbirthday' => 2,
|
|
'showmemberlist' => 4,
|
|
'showeditedby' => 8,
|
|
'allowmembergroups' => 16,
|
|
'isnotbannedgroup' => 32,
|
|
'requirehvcheck' => 64,
|
|
),
|
|
'signaturepermissions' => array(
|
|
'canbbcode' => 131072,
|
|
'canbbcodebasic' => 1,
|
|
'canbbcodecolor' => 2,
|
|
'canbbcodesize' => 4,
|
|
'canbbcodefont' => 8,
|
|
'canbbcodealign' => 16,
|
|
'canbbcodelist' => 32,
|
|
'canbbcodelink' => 64,
|
|
'canbbcodecode' => 128,
|
|
'canbbcodephp' => 256,
|
|
'canbbcodehtml' => 512,
|
|
'canbbcodequote' => 1024,
|
|
'allowimg' => 2048,
|
|
'allowvideo' => 262144,
|
|
'allowsmilies' => 4096,
|
|
'allowhtml' => 8192,
|
|
'cansigpic' => 32768,
|
|
'cananimatesigpic' => 65536,
|
|
),
|
|
'visitormessagepermissions' => array(
|
|
'canmessageownprofile' => 1,
|
|
'canmessageothersprofile' => 2,
|
|
'caneditownmessages' => 4,
|
|
'candeleteownmessages' => 8,
|
|
'canmanageownprofile' => 32,
|
|
'followforummoderation' => 16,
|
|
),
|
|
'useroptions' => array(
|
|
'showsignatures' => 1,
|
|
'showavatars' => 2,
|
|
'showimages' => 4,
|
|
'coppauser' => 8,
|
|
'adminemail' => 16,
|
|
'showvcard' => 32,
|
|
'dstauto' => 64,
|
|
'dstonoff' => 128,
|
|
'showemail' => 256,
|
|
'invisible' => 512,
|
|
'showreputation' => 1024,
|
|
'receivepm' => 2048,
|
|
'emailonpm' => 4096,
|
|
'hasaccessmask' => 8192,
|
|
'vbasset_enable' => 16384,
|
|
'postorder' => 32768,
|
|
'receivepmbuddies' => 131072,
|
|
'noactivationmails' => 262144,
|
|
'pmboxwarning' => 524288,
|
|
'showusercss' => 1048576,
|
|
'receivefriendemailrequest' => 2097152,
|
|
'vm_enable' => 8388608,
|
|
'vm_contactonly' => 16777216,
|
|
'pmdefaultsavecopy' => 33554432
|
|
),
|
|
'announcementoptions' => array(
|
|
'allowbbcode' => 1,
|
|
'allowhtml' => 2,
|
|
'allowsmilies' => 4,
|
|
'parseurl' => 8,
|
|
'signature' => 16,
|
|
),
|
|
'cluboptions' => array(
|
|
'owner_mod_queue' => 1,
|
|
'join_to_view' => 2,
|
|
'enable_group_messages' => 4,
|
|
'enable_group_albums' => 8,
|
|
'only_owner_discussions' => 16
|
|
)
|
|
);
|
|
|
|
/**
|
|
* @brief Fetched Settings Cache
|
|
*/
|
|
protected array $settingsCache = array();
|
|
|
|
/**
|
|
* Get Setting Value - useful for global settings that need to be translated to group or member settings
|
|
*
|
|
* @param string $key The setting key
|
|
* @return mixed
|
|
*/
|
|
protected function _setting( string $key ) : mixed
|
|
{
|
|
if ( isset( $this->settingsCache[$key] ) )
|
|
{
|
|
return $this->settingsCache[$key];
|
|
}
|
|
|
|
try
|
|
{
|
|
$setting = $this->db->select( 'value, defaultvalue', 'setting', array( "varname=?", $key ) )->first();
|
|
|
|
if ( $setting['value'] )
|
|
{
|
|
$this->settingsCache[$key] = $setting['value'];
|
|
}
|
|
else
|
|
{
|
|
$this->settingsCache[$key] = $setting['defaultvalue'];
|
|
}
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
/* If we failed to find it, we probably will fail again on later attempts */
|
|
$this->settingsCache[$key] = NULL;
|
|
}
|
|
|
|
return $this->settingsCache[$key];
|
|
}
|
|
|
|
/**
|
|
* Check if we can redirect the legacy URLs from this software to the new locations
|
|
*
|
|
* @return Url|NULL
|
|
*/
|
|
public function checkRedirects(): ?Url
|
|
{
|
|
$url = Request::i()->url();
|
|
|
|
/* Attachment URLs are the same across VB 3.8 and VB 4 */
|
|
if( mb_strpos( $url->data[ Url::COMPONENT_PATH ], 'attachment.php' ) !== FALSE )
|
|
{
|
|
$id = NULL;
|
|
try
|
|
{
|
|
$id = (string) $this->app->getLink( Request::i()->attachmentid, array( 'attachments', 'core_attachments' ) );
|
|
}
|
|
/* Try any child conversions */
|
|
catch( \Exception $e )
|
|
{
|
|
foreach( $this->app->children() as $child )
|
|
{
|
|
try
|
|
{
|
|
$id = (string) $child->getLink( Request::i()->attachmentid, array( 'attachments', 'core_attachments' ) );
|
|
break;
|
|
}
|
|
catch( \Exception $e ) {}
|
|
}
|
|
}
|
|
|
|
if( !$id )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return Url::external( Settings::i()->base_url . 'applications/core/interface/file/attachment.php' )->setQueryString( 'id', $id );
|
|
}
|
|
/* Tag URLs are straightforward */
|
|
elseif( mb_strpos( $url->data[ Url::COMPONENT_PATH ], 'tags.php' ) !== FALSE AND isset( Request::i()->tag ) )
|
|
{
|
|
return Url::internal( "app=core&module=search&controller=search&tags=" . Request::i()->tag, 'front', 'search' );
|
|
}
|
|
/* Club Index */
|
|
elseif( mb_strpos( $url->data[ Url::COMPONENT_PATH ], 'group.php' ) !== FALSE AND isset( Request::i()->groupid ) )
|
|
{
|
|
try
|
|
{
|
|
return Club::loadAndCheckPerms( (string) $this->app->getLink( Request::i()->groupid, array( 'core_clubs' ) ) )->url();
|
|
}
|
|
catch( \Exception $e )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* If we can't access profiles, don't bother trying to redirect */
|
|
if( !Member::loggedIn()->canAccessModule( Module::get( 'core', 'members' ) ) )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Profile URLs can be in one of 3 formats really...
|
|
* /member.php/1-name
|
|
* /members/1-name
|
|
* /member.php?u=1
|
|
* /member.php?1-name
|
|
*/
|
|
$path = $url->data[ Url::COMPONENT_PATH ];
|
|
if( mb_strpos( $path, 'member.php' ) !== FALSE )
|
|
{
|
|
if( isset( Request::i()->u ) )
|
|
{
|
|
$oldId = Request::i()->u;
|
|
}
|
|
elseif( preg_match( '#^(\d+)-[^/]+#i', $url->data[ Url::COMPONENT_QUERY ], $matches ) )
|
|
{
|
|
$oldId = $matches[1];
|
|
}
|
|
else
|
|
{
|
|
$queryStringPieces = explode( '-', mb_substr( $path, mb_strpos( $path, 'member.php/' ) + mb_strlen( 'member.php/' ) ) );
|
|
$oldId = $queryStringPieces[0];
|
|
}
|
|
}
|
|
elseif( preg_match( '#/members/([0-9]+)#i', $url->data[ Url::COMPONENT_PATH ], $matches ) )
|
|
{
|
|
$oldId = (int) $matches[1];
|
|
}
|
|
|
|
if( isset( $oldId ) )
|
|
{
|
|
try
|
|
{
|
|
$data = (string) $this->app->getLink( $oldId, array( 'members', 'core_members' ) );
|
|
return Member::load( $data )->url();
|
|
}
|
|
catch( \Exception $e )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Process a login
|
|
*
|
|
* @param Member $member The member
|
|
* @param string $password Password from form
|
|
* @return bool
|
|
*/
|
|
public static function login( Member $member, string $password ) : bool
|
|
{
|
|
if ( Login::compareHashes( $member->conv_password, md5( md5( str_replace( ''', "'", html_entity_decode( $password ) ) ) . $member->conv_password_extra ) ) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Centralised PM conversation conversion
|
|
*
|
|
* @param $libraryClass Library
|
|
* @param $pm array
|
|
* @return mixed
|
|
*/
|
|
protected function _convertPm( Library $libraryClass, array $pm ) : mixed
|
|
{
|
|
try
|
|
{
|
|
$replies = $this->db->select( 'COUNT(*)', 'pm', array( "parentpmid=? AND folderid!=?", $pm['pmid'], -1 ) )->first();
|
|
$replies += 1;
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
$replies = 1;
|
|
}
|
|
|
|
$toUserArray = @unserialize( $pm['touserarray'] );
|
|
|
|
$topic = array(
|
|
'mt_id' => $pm['pmid'],
|
|
'mt_date' => $pm['dateline'],
|
|
'mt_title' => $pm['title'],
|
|
'mt_starter_id' => $pm['fromuserid'],
|
|
'mt_start_time' => $pm['dateline'],
|
|
'mt_last_post_time' => $pm['dateline'],
|
|
'mt_to_count' => ( $toUserArray AND is_array( $toUserArray ) ) ? count( $toUserArray ) : 1,
|
|
'mt_to_member_id' => $pm['userid'],
|
|
'mt_replies' => $replies,
|
|
'mt_first_msg_id' => $pm['pmid'],
|
|
);
|
|
|
|
$authors = array();
|
|
$authors[ $pm['fromuserid'] ] = $pm['fromuserid'];
|
|
|
|
try
|
|
{
|
|
foreach( $this->db->select( 'folderid, fromuserid', 'pm', array( "parentpmid=?", $pm['pmid'] ) )->join( 'pmtext', 'pm.pmtextid = pmtext.pmtextid' ) AS $post )
|
|
{
|
|
if ( $post['folderid'] != -1 )
|
|
{
|
|
$authors[ $post['fromuserid'] ] = $post['fromuserid'];
|
|
}
|
|
}
|
|
}
|
|
catch( UnderflowException $e ) {}
|
|
|
|
/* Use stored author ids */
|
|
if( isset( $toUserArray['cc'] ) AND is_array( $toUserArray['cc'] ) )
|
|
{
|
|
foreach( array_keys( $toUserArray['cc'] ) as $id )
|
|
{
|
|
$authors[ $id ] = $id;
|
|
}
|
|
}
|
|
elseif( $toUserArray AND is_array( $toUserArray ) AND count( $toUserArray ) )
|
|
{
|
|
foreach( array_keys( $toUserArray ) as $id )
|
|
{
|
|
$authors[ $id ] = $id;
|
|
}
|
|
}
|
|
|
|
$maps = array();
|
|
|
|
foreach( $authors AS $mapAuthor )
|
|
{
|
|
$maps[ $mapAuthor ] = array(
|
|
'map_user_id' => $mapAuthor,
|
|
'map_topic_id' => $pm['pmid'],
|
|
'map_user_active' => 1,
|
|
'map_user_banned' => 0,
|
|
'map_has_unread' => 0,
|
|
'map_is_starter' => ( $mapAuthor == $pm['fromuserid'] ) ? 1 : 0,
|
|
);
|
|
}
|
|
|
|
return $libraryClass->convertPrivateMessage( $topic, $maps );
|
|
}
|
|
|
|
protected array $_avatars = array();
|
|
|
|
/**
|
|
* Fetch Avatar from Avatar Gallery
|
|
*
|
|
* @param int $id Avatar ID
|
|
* @return array|bool Array of data/name or FALSE
|
|
*/
|
|
protected function _getFromAvatarGallery( int $id ) : array|bool
|
|
{
|
|
if( isset( $this->_avatars[ $id ] ) )
|
|
{
|
|
return $this->_avatars[ $id ];
|
|
}
|
|
|
|
try
|
|
{
|
|
$avatar = $this->db->select( 'avatarpath', 'avatar', array( 'avatarid=?', $id ) )->first();
|
|
}
|
|
catch( UnderflowException $e )
|
|
{
|
|
return $this->_avatars[ $id ] = FALSE;
|
|
}
|
|
|
|
/* It might be a remote image */
|
|
if( substr( $avatar, 0, 8 ) === 'https://' OR substr( $avatar, 0, 7 ) === 'http://' )
|
|
{
|
|
try
|
|
{
|
|
$profilePhotoName = pathinfo( parse_url( $avatar, PHP_URL_PATH ), PATHINFO_BASENAME );
|
|
$profilePhotoData = Url::external( $avatar )->request()->get();
|
|
|
|
/* Check it's really an image */
|
|
try
|
|
{
|
|
Image::create( $profilePhotoData );
|
|
}
|
|
catch( InvalidArgumentException $e )
|
|
{
|
|
return $this->_avatars[ $id ] = FALSE;
|
|
}
|
|
|
|
return $this->_avatars[ $id ] = array( 'data' => $profilePhotoData, 'name' => $profilePhotoName );
|
|
}
|
|
catch ( Exception $e )
|
|
{
|
|
return $this->_avatars[ $id ] = FALSE;
|
|
}
|
|
}
|
|
|
|
/* nope, it's a file */
|
|
$dirName = pathinfo( $avatar, PATHINFO_DIRNAME );
|
|
if( substr( $this->app->_session['more_info']['convertMembers']['avatar_gallery_location'], -strlen( $dirName ) ) == $dirName )
|
|
{
|
|
$profilePhotoName = pathinfo( $avatar, PATHINFO_BASENAME );
|
|
$profilePhotoData = file_get_contents( rtrim( $this->app->_session['more_info']['convertMembers']['avatar_gallery_location'], '/' ) . '/' . $profilePhotoName );
|
|
|
|
return $this->_avatars[ $id ] = array( 'data' => $profilePhotoData, 'name' => $profilePhotoName );
|
|
}
|
|
|
|
return $this->_avatars[ $id ] = FALSE;
|
|
}
|
|
} |