<?php

/**
 * @brief		Converter MyBB Class
 * @author		<a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright	(c) Invision Power Services, Inc.
 * @package		Invision Community
 * @subpackage	Converter
 * @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' ) )
{
	header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
	exit;
}

class _Mybb extends \IPS\convert\Software
{
	/**
	 * Software Name
	 *
	 * @return	string
	 */
	public static function softwareName()
	{
		/* Child classes must override this method */
		return "MyBB 1.8.x";
	}
	
	/**
	 * Software Key
	 *
	 * @return	string
	 */
	public static function softwareKey()
	{
		/* Child classes must override this method */
		return "mybb";
	}
	
	/**
	 * Content we can convert from this software. 
	 *
	 * @return	array
	 */
	public static function canConvert()
	{
		return array(
			'convertBanfilters'			=> array(
				'table'							=> 'banfilters',
				'where'							=> NULL
			),
			'convertEmoticons'				=> array(
				'table'							=> 'smilies',
				'where'							=> NULL,
			),
			'convertWarnActions'			=> array(
				'table'							=> 'warninglevels',
				'where'							=> NULL
			),
			'convertWarnReasons'			=> array(
				'table'							=> 'warningtypes',
				'where'							=> NULL
			),
			'convertProfileFields'		=> array(
				'table'							=> 'profilefields',
				'where'							=> NULL
			),
			'convertGroups'				=> array(
				'table'							=> 'usergroups',
				'where'							=> NULL
			),
			'convertMembers'				=> array(
				'table'							=> 'users', # note convert ignored users during this step, rather than individually
				'where'							=> NULL
			),
			'convertAnnouncements'			=> array(
				'table'							=> 'announcements',
				'where'							=> NULL
			),
			'convertRanks'					=> array(
				'table'							=> 'usertitles',
				'where'							=> NULL
			),
			'convertPrivateMessages'		=> array(
				'table'							=> 'privatemessages',
				'where'							=> NULL
			),
			'convertPrivateMessageReplies'	=> array(
				'table'							=> 'privatemessages',
				'where'							=> NULL,
			),
			'convertProfanityFilters'		=> array(
				'table'							=> 'badwords',
				'where'							=> NULL
			),
			'convertQuestionAndAnswers'	=> array(
				'table'							=> 'questions',
				'where'							=> NULL
			)
		);
	}
	
	/**
	 * Can we convert passwords from this software.
	 *
	 * @return 	boolean
	 */
	public static function loginEnabled()
	{
		return TRUE;
	}

	/**
	 * List of conversion methods that require additional information
	 *
	 * @return	array
	 */
	public static function checkConf()
	{
		return array(
			'convertEmoticons',
			'convertProfileFields',
			'convertGroups',
			'convertMembers'
		);
	}
	
	/**
	 * Get More Information
	 *
	 * @param	string	$method	Conversion method
	 * @return	array
	 */
	public function getMoreInfo( $method )
	{
		$return = array();
		switch( $method )
		{
			case 'convertEmoticons':
				\IPS\Member::loggedIn()->language()->words['emoticon_path'] = \IPS\Member::loggedIn()->language()->addToStack( 'source_path', FALSE, array( 'sprintf' => array( 'MyBB' ) ) );
				$return['convertEmoticons']['emoticon_path'] = array(
					'field_class'		=> 'IPS\\Helpers\\Form\\Text',
					'field_default'		=> NULL,
					'field_required'	=> TRUE,
					'field_extra'		=> array(),
					'field_hint'		=> NULL,
					'field_validation'	=> function( $value ) { if ( !@is_dir( $value ) ) { throw new \DomainException( 'path_invalid' ); } },
				);
				$return['convertEmoticons']['keep_existing_emoticons']	= array(
					'field_class'		=> 'IPS\\Helpers\\Form\\Checkbox',
					'field_default'		=> TRUE,
					'field_required'	=> FALSE,
					'field_extra'		=> array(),
					'field_hint'		=> NULL,
				);
				break;
				
			case 'convertProfileFields':
				$return['convertProfileFields'] = array();
				
				$options = array();
				$options['none'] = \IPS\Member::loggedIn()->language()->addToStack( 'none' );
				foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_pfields_data' ), 'IPS\core\ProfileFields\Field' ) AS $field )
				{
					$options[$field->_id] = $field->_title;
				}
				
				foreach( $this->db->select( '*', 'profilefields' ) AS $field )
				{
					\IPS\Member::loggedIn()->language()->words["map_pfield_{$field['fid']}"]		= $field['name'];
					\IPS\Member::loggedIn()->language()->words["map_pfield_{$field['fid']}_desc"]	= \IPS\Member::loggedIn()->language()->addToStack( 'map_pfield_desc' );
					
					$return['convertProfileFields']["map_pfield_{$field['fid']}"] = 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'] = \IPS\Member::loggedIn()->language()->addToStack( 'none' );
				foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_groups' ), 'IPS\Member\Group' ) AS $group )
				{
					$options[$group->g_id] = $group->name;
				}
				
				foreach( $this->db->select( '*', 'usergroups' ) AS $group )
				{
					\IPS\Member::loggedIn()->language()->words["map_group_{$group['gid']}"]			= $group['title'];
					\IPS\Member::loggedIn()->language()->words["map_group_{$group['gid']}_desc"]	= \IPS\Member::loggedIn()->language()->addToStack( 'map_group_desc' );
					
					$return['convertGroups']["map_group_{$group['gid']}"] = array(
						'field_class'		=> 'IPS\\Helpers\\Form\\Select',
						'field_default'		=> NULL,
						'field_required'	=> FALSE,
						'field_extra'		=> array( 'options' => $options ),
						'field_hint'		=> NULL
					);
				}
				
				\IPS\Member::loggedIn()->language()->words['icon_path'] = \IPS\Member::loggedIn()->language()->addToStack( 'source_path', FALSE, array( 'sprintf' => array( 'MyBB' ) ) );
				$return['convertGroups']['icon_path'] = array(
					'field_class'			=> "IPS\\Helpers\\Form\\Text",
					'field_default'			=> NULL,
					'field_required'		=> FALSE,
					'field_extra'			=> array(),
					'field_hint'			=> \IPS\Member::loggedIn()->language()->addToStack('convert_groupicon_hint'),
					'field_validation'		=> function( $value ) { if ( !empty( $value ) AND !@is_dir( $value ) ) { throw new \DomainException( 'path_invalid' ); } },
				);
				break;
			
			case 'convertMembers':
				$return['convertMembers'] = array();

				/* Find out where the photos live */
				\IPS\Member::loggedIn()->language()->words['photo_location']		= \IPS\Member::loggedIn()->language()->addToStack( 'source_path', FALSE, array( 'sprintf' => array( 'MyBB' ) ) );
				\IPS\Member::loggedIn()->language()->words['photo_location_desc']	= '';
				$return['convertMembers']['photo_location'] = array(
					'field_class'			=> 'IPS\\Helpers\\Form\\Text',
					'field_default'			=> NULL,
					'field_required'		=> TRUE,
					'field_extra'			=> array(),
					'field_hint'			=> NULL,
					'field_validation'	=> function( $value ) { if ( !@is_dir( $value ) ) { throw new \DomainException( 'path_invalid' ); } },
				);

				foreach( array( 'website', 'icq', 'aim', 'yahoo', 'skype', 'google' ) AS $field )
				{
					\IPS\Member::loggedIn()->language()->words["field_{$field}"]		= \IPS\Member::loggedIn()->language()->addToStack( 'pseudo_field', FALSE, array( 'sprintf' => $field ) );
					\IPS\Member::loggedIn()->language()->words["field_{$field}_desc"]	= \IPS\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'			=> \IPS\Member::loggedIn()->language()->addToStack( 'no_convert' ),
								'create_field'			=> \IPS\Member::loggedIn()->language()->addToStack( 'create_field' ),
							),
							'userSuppliedInput'		=> 'create_field'
						),
						'field_hint'			=> NULL
					);
				}
				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()
	{
		/* Search Index Rebuild */
		\IPS\Content\Search\Index::i()->rebuild();
		
		/* Clear Cache and Store */
		\IPS\Data\Store::i()->clearAll();
		\IPS\Data\Cache::i()->clearAll();
		
		/* Non-Content Rebuilds */
		\IPS\Task::queue( 'convert', 'RebuildNonContent', array( 'app' => $this->app->app_id, 'link' => 'core_announcements', 'extension' => 'core_Announcement' ), 2, array( 'app', 'link', 'extension' ) );
		\IPS\Task::queue( 'convert', 'RebuildNonContent', array( 'app' => $this->app->app_id, 'link' => 'core_message_posts', 'extension' => 'core_Messaging' ), 2, array( 'app', 'link', 'extension' ) );
		\IPS\Task::queue( 'convert', 'RebuildNonContent', array( 'app' => $this->app->app_id, 'link' => 'core_members', 'extension' => 'core_Signatures' ), 2, array( 'app', 'link', 'extension' ) );
		
		/* Content Counts */
		\IPS\Task::queue( 'core', 'RecountMemberContent', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );

		/* First Post Data */
		\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );

		/* Attachments */
		\IPS\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" );
	}

	/**
	 * Fix post data
	 *
	 * @param 	string		raw post data
	 * @return 	string		parsed post data
	 */
	public static function fixPostData( $post )
	{
		/* Replace align tags with left/right/center/justify */
		$post = preg_replace( '/\[align=(justify|left|right)\](.+?)\[\/align\]/i', '[$1]$2[/$1]', $post );

		/* remove justify, since we don't support it */
		$post = str_replace( array( '[justify]', '[/justify]' ), '', $post );

		return $post;
	}

	/**
	 * Convert announcements
	 *
	 * @return	void
	 */
	public function convertAnnouncements()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'aid' );
		
		foreach ( $this->fetch( 'announcements', 'aid' ) AS $row )
		{
			$libraryClass->convertAnnouncement( array(
				'announce_id'			=> $row['aid'],
				'announce_title'		=> $row['subject'],
				'announce_content'		=> $row['message'],
				'announce_member_id'	=> $row['uid'],
				'announce_start'		=> $row['startdate'],
				'announce_end'			=> $row['enddate'],
				'announce_active'		=> ( $row['enddate'] == 0 OR $row['enddate'] > time() ) ? 1 : 0,
			) );
			
			$libraryClass->setLastKeyValue( $row['aid'] );
		}
	}
	
	/**
	 * Convert ban filters
	 *
	 * @return	void
	 */
	public function convertBanfilters()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'fid' );
		
		foreach( $this->fetch( 'banfilters', 'fid' ) AS $row )
		{
			switch( $row['type'] )
			{
				case 1:
					$type = 'ip';
					break;
				
				case 2:
					$type = 'name';
					break;
				
				case 3:
					$type = 'email';
					break;
			}
			
			$libraryClass->convertBanfilter( array(
				'ban_id'			=> $row['fid'],
				'ban_type'			=> $type,
				'ban_content'		=> $row['filter'],
				'ban_date'			=> $row['dateline']
			) );
			
			$libraryClass->setLastKeyValue( $row['fid'] );
		}
	}
	
	/**
	 * Convert emoticons
	 *
	 * @return	void
	 */
	public function convertEmoticons()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'sid' );
		
		foreach( $this->fetch( 'smilies', 'sid' ) AS $row )
		{
			$set = array(
				'set'		=> md5( 'Converted' ),
				'title'		=> 'Converted',
				'position'	=> 1,
			);
			
			$filepath	= explode( '/', $row['image'] );
			$filename	= array_pop( $filepath );
			$path		= rtrim( $this->app->_session['more_info']['convertEmoticons']['emoticon_path'], '/' ) . '/' . implode( '/', $filepath );
			
			$typed		= explode( "\n", $row['find'] );
			$typed		= array_shift( $typed );
			
			$info = array(
				'id'			=> $row['sid'],
				'typed'			=> $typed,
				'filename'		=> $filename,
				'clickable'		=> $row['showclickable'],
				'emo_position'	=> $row['disporder']
			);
			
			$libraryClass->convertEmoticon( $info, $set, $this->app->_session['more_info']['convertEmoticons']['keep_existing_emoticons'], $path );
			
			$libraryClass->setLastKeyValue( $row['sid'] );
		}
	}
	
	/**
	 * Convert warn actions
	 *
	 * @return	void
	 */
	public function convertWarnActions()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'lid' );
		
		foreach( $this->fetch( 'warninglevels', 'lid' ) AS $row )
		{
			$action = \unserialize( $row['action'] );
			
			$mq				= 0;
			$mq_unit		= 'h';
			$rpa			= 0;
			$rpa_unit		= 'h';
			$suspend		= 0;
			$suspend_unit	= 'h';
			switch( $action['type'] )
			{
				// Banned
				case 1:
					if ( $action['length'] >= 86400 )
					{
						$suspend_unit	= 'd';
						$suspend		= floor( $action['length'] / 86400 );
					}
					else
					{
						$suspend_unit	= 'h';
						$suspend		= floor( $action['length'] / 3600 );
					}
					break;
				
				// Restrict Posting
				case 2:
					if ( $action['length'] >= 86400 )
					{
						$rpa_unit		= 'd';
						$rpa			= floor( $action['length'] / 86400 );
					}
					else
					{
						$rpa_unit		= 'h';
						$rpa			= floor( $action['length'] / 3600 );
					}
					break;
				
				// Moderate Posting
				case 3:
					if ( $action['length'] >= 86400 )
					{
						$mq_unit		= 'd';
						$mq				= floor( $action['length'] / 86400 );
					}
					else
					{
						$mq_unit		= 'h';
						$mq				= floor( $action['length'] / 3600 );
					}
					break;
			}
			
			$libraryClass->convertWarnAction( array(
				'wa_id'				=> $row['lid'],
				'wa_points'			=> $row['percentage'],
				'wa_mq'				=> $mq,
				'wa_mq_unit'		=> $mq_unit,
				'wa_rpa'			=> $rpa,
				'wa_rpa_unit'		=> $rpa_unit,
				'wa_suspend'		=> $suspend,
				'wa_suspend_unit'	=> $suspend_unit,
			) );
			
			$libraryClass->setLastKeyValue( $row['lid'] );
		}
	}
	
	/**
	 * Convert warn reasons
	 *
	 * @return	void
	 */
	public function convertWarnReasons()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'tid' );
		
		foreach( $this->fetch( 'warningtypes', 'tid' ) AS $row )
		{
			$libraryClass->convertWarnReason( array(
				'wr_id'			=> $row['tid'],
				'wr_name'		=> $row['title'],
				'wr_points'		=> $row['points'],
				'wr_remove'		=> floor( $row['expirationtime'] / 3600 ),
			) );
			
			$libraryClass->setLastKeyValue( $row['tid'] );
		}
	}
	
	/**
	 * Convert profile fields
	 *
	 * @return	void
	 */
	public function convertProfileFields()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'fid' );
		
		foreach( $this->fetch( 'profilefields', 'fid' ) AS $row )
		{
			/* MyBB stores the type and the options in the same column. */
			$typeAndOptions	= explode( "\n", $row['type'] );
			$theirType		= array_shift( $typeAndOptions );
			$ourType		= $this->_fieldMap( $theirType );
			$options		= $typeAndOptions;
			
			$info = array(
				'pf_id'				=> $row['fid'],
				'pf_type'			=> $ourType,
				'pf_name'			=> $row['name'],
				'pf_desc'			=> $row['description'],
				'pf_content'		=> $options,
				'pf_not_null'		=> $row['required'],
				'pf_member_hide'	=> $row['profile'],
				'pf_max_input'		=> $row['maxlength'],
				'pf_position'		=> $row['disporder'],
				'pf_show_on_reg'	=> $row['registration'],
				'pf_input_format'	=> '/' . $row['regex'] . '/i',
				'pf_admin_only'		=> ( $row['editableby'] == -1 ) ? 0 : 1, # MyBB allows you to define which groups can edit, so if this value is anything other than -1, assume it's admin only
				'pf_multiple'		=> ( $theirType == 'multiselect' ) ? 1 : 0
			);
			
			$merge = $this->app->_session['more_info']['convertProfileFields']["map_pfield_{$row['fid']}"] != 'none' ? $this->app->_session['more_info']['convertProfileFields']["map_pfield_{$row['fid']}"] : NULL;
			
			$libraryClass->convertProfileField( $info, $merge );
			
			$libraryClass->setLastKeyValue( $row['fid'] );
		}
	}
	
	/**
	 * Maps a MyBB Profile Field type to IPS
	 *
	 * @param	string	MyBB Type
	 * @return	string	IPS Type
	 */
	protected function _fieldMap( $type )
	{
		switch( $type )
		{
			case 'text':
				return 'Text';
				break;
			
			case 'textarea':
				return "TextArea";
				break;
			
			case 'select':
			case 'multiselect':
				return 'Select';
				break;
			
			case 'checkbox':
				return 'CheckboxSet';
				break;
			
			case 'radio':
				return 'Radio';
				break;
		}
	}
	
	/**
	 * Convert groups
	 *
	 * @return	void
	 */
	public function convertGroups()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'gid' );
		
		foreach( $this->fetch( 'usergroups', 'gid' ) AS $row )
		{
			/* Are we trying to fetch icons? */
			$icon_path = $this->app->_session['more_info']['convertGroups']['icon_path'];
			
			/* Work out promotions */
			$promotion_unit	= 0;
			$promotion_data	= NULL;
			try
			{
				/* We only support one type of promotion, so just fetch the first one we can find */
				$promotion = $this->db->select( '*', 'promotions', array( "originalusergroup=? AND usergrouptype=? AND ( requirements=? OR requirements=? )", $row['gid'], 'primary', 'postcount', 'timeregistered' ) )->first();
				
				switch( $promotion['requirements'] )
				{
					case 'postcount':
						if ( !in_array( $promotion['posttype'], array( '>', '>=', '=' ) ) )
						{
							/* Bubble up */
							throw new \UnderflowException;
						}
						$promotion_unit = 0;
						$promotion_data = array( $promotion['newusergroup'], $promotion['posts'] );
						break;
					
					case 'timeregistered':
						if ( !in_array( $promotion['registeredtype'], array( '>', '>=', '=' ) ) )
						{
							throw new \UnderflowException;
						}
						$promotion_unit = 0;
						$promotion_data = array( $promotion['newusergroup'], $promotion['registered'] );
						break;
				}
			}
			catch( \UnderflowException $e ) {}
			
			/* Username Styles */
			$style = explode( '{username}', $row['namestyle'] );
			$prefix = isset( $style[0] ) ? $style[0] : '';
			$suffix = isset( $style[1] ) ? $style[1] : '';
			
			$info = array(
				'g_id'					=> $row['gid'],
				'g_name'				=> $row['title'],
				'g_icon'				=> ( $icon_path AND $row['image'] ) ? $row['image'] : NULL,
				'g_promotion'			=> $promotion_data ?: NULL,
				'g_bitoptions'			=> array(
					'gbw_promote_unit_type'	=> $promotion_unit,
				),
				'g_view_board'			=> $row['canview'],
				'g_mem_info'			=> $row['canviewprofiles'],
				'g_use_search'			=> $row['cansearch'],
				'g_edit_profile'		=> $row['canusercp'],
				'g_edit_posts'			=> $row['caneditposts'],
				'g_use_pm'				=> $row['cansendpms'],
				'g_pm_flood_mins'		=> $row['emailfloodtime'],
				'g_post_polls'			=> $row['canpostpolls'],
				'g_vote_polls'			=> $row['canvotepolls'],
				'g_delete_own_posts'	=> $row['candeleteposts'],
				'g_is_supmod'			=> $row['issupermod'],
				'g_access_cp'			=> $row['cancp'],
				'g_access_offline'		=> $row['canviewboardclosed'],
				'g_max_messages'		=> $row['pmquota'],
				'g_attach_max'			=> ( $row['attachquota'] == 0 ) ? -1 : $row['attachquota'],
				'prefix'				=> $prefix,
				'suffix'				=> $suffix,
				'g_max_mass_pm'			=> $row['maxpmrecipients'],
				'g_rep_max_positive'	=> $row['maxreputationsday'],
				'g_rep_max_negative'	=> $row['maxreputationsday'],
			);
			
			$merge = $this->app->_session['more_info']['convertGroups']["map_group_{$row['gid']}"] != 'none' ? $this->app->_session['more_info']['convertGroups']["map_group_{$row['gid']}"] : NULL;
			
			$libraryClass->convertGroup( $info, $merge, ( $icon_path ) ? rtrim( $icon_path, '/' ) . '/' . $row['image'] : NULL );
			
			$libraryClass->setLastKeyValue( $row['gid'] );
		}

		/* Now check for group promotions */
		if( count( $libraryClass->groupPromotions ) )
		{
			foreach( $libraryClass->groupPromotions as $groupPromotion )
			{
				$libraryClass->convertGroupPromotion( $groupPromotion );
			}
		}
	}
	
	/**
	 * Convert members
	 *
	 * @return	void
	 */
	public function convertMembers()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'uid' );
		
		foreach( $this->fetch( 'users', 'uid' ) AS $row )
		{
			/* Last warning */
			$last_warn = 0;
			try
			{
				$last_warn = $this->db->select( 'dateline', 'warnings', array( "uid=?", $row['uid'] ), "dateline DESC" )->first();
			}
			catch( \UnderflowException $e ) {}
			
			/* Restrict posting */
			$restrict_post = 0;
			if ( $row['suspendposting'] )
			{
				if ( $row['suspensiontime'] > 0 )
				{
					$restrict_post = $row['suspensiontime'];
				}
				else
				{
					$restrict_post = -1;
				}
			}
			
			/* Birthday */
			$bday_day = $bday_month = $bday_year = NULL;
			$birthday = explode( '-', $row['birthday'] );
			
			if ( isset( $birthday[0] ) )
			{
				$bday_day = $birthday[0];
			}
			
			if ( isset( $birthday[1] ) )
			{
				$bday_month = $birthday[1];
			}
			
			if ( isset( $birthday[2] ) )
			{
				$bday_year = $birthday[2];
			}
			
			/* Moderate Posts */
			$mod_posts = 0;
			if ( $row['moderateposts'] )
			{
				if ( $row['moderationtime'] > 0 )
				{
					$mod_posts = $row['moderationtime'];
				}
				else
				{
					$mod_posts = -1;
				}
			}
			
			/* Subscription */
			switch( $row['subscriptionmethod'] )
			{
				case 0:
					$auto_track = array( 'content' => 0, 'comments' => 0, 'method' => 'none' );
					break;
				
				case 1:
					$auto_track = array( 'content' => 1, 'comments' => 1, 'method' => 'none' );
					break;
				
				case 2:
				case 3:
					$auto_track = array( 'content' => 1, 'comments' => 1, 'method' => 'immediate' );
					break;
			}
			
			/* Banned */
			$temp_ban = 0;
			try
			{
				$temp_ban = $this->db->select( 'lifted', 'banned', array( "uid=?", $row['uid'] ) )->first();
				
				if ( $temp_ban == 0 )
				{
					$temp_ban = -1;
				}
			}
			catch( \UnderflowException $e ) {}
			
			$info = array(
				'member_id'				=> $row['uid'],
				'name'					=> $row['username'],
				'email'					=> $row['email'],
				'password'				=> $row['password'],
				'password_extra'		=> $row['salt'],
				'member_group_id'		=> $row['usergroup'],
				'joined'				=> $row['regdate'],
				'ip_address'			=> $row['regip'],
				'warn_level'			=> $row['warningpoints'],
				'warn_lastwarn'			=> $last_warn,
				'restrict_post'			=> $restrict_post,
				'bday_day'				=> str_pad( $bday_day, 2, '0', STR_PAD_LEFT ),
				'bday_month'			=> str_pad( $bday_month, 2, '0', STR_PAD_LEFT ),
				'bday_year'				=> $bday_year ? intval( $bday_year ) : NULL,
				'msg_count_new'			=> $row['unreadpms'],
				'msg_count_total'		=> $row['totalpms'],
				'msg_show_notification'	=> $row['pmnotify'],
				'last_visit'			=> $row['lastvisit'],
				'last_activity'			=> $row['lastactive'],
				'mod_posts'				=> $mod_posts,
				'auto_track'			=> $auto_track,
				'temp_ban'				=> $temp_ban,
				'mgroup_others'			=> $row['additionalgroups'],
				'members_bitoptions'	=> array(
					'view_sigs'				=> $row['showsigs'],
				),
				'members_bitoptions2'	=> array(
					'show_pm_popup'			=> $row['pmnotify'],
				),
				'pp_reputation_points'	=> $row['reputation'],
				'signature'				=> $row['signature'],
				'timezone'				=> $row['timezone'],
				'allow_admin_mails'		=> $row['allownotices'],
				'members_disable_pm'	=> ( $row['receivepms'] ) ? 0 : 1,
				'member_title'			=> $row['usertitle'],
				'member_posts'			=> $row['postnum'],
				'member_last_post'		=> $row['lastpost'],
			);
			
			$pfields = array();
			foreach( $this->db->select( '*', 'userfields', array( "ufid=?", $row['uid'] ) ) AS $key => $field )
			{
				$key = str_replace( 'fid', '', $key );
				$pfields[ $key ] = $field;
			}
			
			foreach( array( 'website', 'icq', 'aim', 'yahoo', 'skype', 'google' ) 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 */
					$fieldId = $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'	=> 0,
						'pf_max_input'		=> 255,
						'pf_member_edit'	=> 1,
						'pf_show_on_reg'	=> 0,
						'pf_admin_only'		=> 0,
					) );
				}
				
				$pfields[$pseudo] = $row[$pseudo];
			}
			
			/* Photo */
			$filename = NULL;
			$filepath = NULL;
			switch( $row['avatartype'] )
			{
				case 'gravatar':
					$info['pp_gravatar']	= $row['email'];
					$info['pp_photo_type']	= 'gravatar';
					break;
				
				case 'upload':
					$info['pp_photo_type'] = 'custom';
					
					$avatar		= trim( '.', $row['avatar'] );
					$filepath	= explode( '/', $avatar );
					$filename	= array_pop( $filepath );
					$filepath	= rtrim( $this->app->_session['more_info']['convertMembers']['photo_location'], '/' ) . implode( '/', $filepath );
					break;
			}
			
			$libraryClass->convertMember( $info, $pfields, $filename, $filepath );
			
			/* Followers */
			foreach( explode( ',', $row['buddylist'] ) AS $buddy )
			{
				$libraryClass->convertFollow( array(
					'follow_app'			=> 'core',
					'follow_area'			=> 'members',
					'follow_rel_id'			=> $buddy,
					'follow_rel_id_type'	=> 'core_members',
					'follow_member_id'		=> $row['uid'],
				) );
			}
			
			/* Ignore List */
			foreach( explode( ',', $row['ignorelist'] ) AS $ignore )
			{
				$libraryClass->convertIgnoredUser( array(
					'ignore_id'			=> $row['uid'] . '-' . $ignore,
					'ignore_owner_id'	=> $row['uid'],
					'ignore_ignore_id'	=> $ignore
				) );
			}
			
			/* Warnings */
			foreach( $this->db->select( '*', 'warnings', array( "uid=? AND pid=?", $row['uid'], 0 ) ) AS $warning )
			{
				$warnId = $libraryClass->convertWarnLog( array(
					'wl_id'				=> $warning['wid'],
					'wl_member'			=> $warning['uid'],
					'wl_moderator'		=> $warning['issuedby'],
					'wl_date'			=> $warning['dateline'],
					'wl_reason'			=> $warning['tid'],
					'wl_points'			=> $warning['points'],
					'wl_note_mods'		=> $warning['notes'],
					'wl_expire_date'	=> $warning['expires'],
				) );

				/* Add a member history record for this member */
				$libraryClass->convertMemberHistory( array(
						'log_id'		=> 'w' . $warning['wid'],
						'log_member'	=> $warning['uid'],
						'log_by'		=> $warning['issuedby'],
						'log_type'		=> 'warning',
						'log_data'		=> array( 'wid' => $warnId ),
						'log_date'		=> $warning['dateline']
					)
				);
			}
			
			$libraryClass->setLastKeyValue( $row['uid'] );
		}
	}
	
	/**
	 * Convert ranks
	 *
	 * @return	void
	 */
	public function convertRanks()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'utid' );
		
		foreach( $this->fetch( 'usertitles', 'utid' ) AS $row )
		{
			$libraryClass->convertRank( array(
				'id'			=> $row['utid'],
				'title'			=> $row['title'],
				'posts'			=> $row['posts'],
				'pips'			=> $row['stars']
			) );
			
			$libraryClass->setLastKeyValue( $row['utid'] );
		}
	}
	
	/**
	 * Convert PMs
	 *
	 * @return	void
	 */
	public function convertPrivateMessages()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'pmid' );
		
		foreach( $this->fetch( 'privatemessages', 'pmid' ) AS $row )
		{
			/* My hands are kind of tied here... every individual message is stored separately, rather than grouped into conversations. In this instance, we'll bring over each individual message as it's own topic for archival purposes. */
			$recipients = \unserialize( $row['recipients'] );
			
			$topic = array(
				'mt_id'				=> $row['pmid'],
				'mt_date'			=> $row['dateline'],
				'mt_title'			=> $row['subject'],
				'mt_starter_id'		=> $row['uid'],
				'mt_start_time'		=> $row['dateline'],
				'mt_last_post_time'	=> $row['dateline'],
				'mt_to_count'		=> 1,
			);
			
			$maps = array();
			$maps[$row['uid']] = array(
				'map_user_id'		=> $row['uid'],
				'map_read_time'		=> $row['readtime'],
			);
			
			$libraryClass->convertPrivateMessage( $topic, $maps );
			
			$libraryClass->setLastKeyValue( $row['pmid'] );
		}
	}
	
	/**
	 * Convert PM replies
	 *
	 * @return	void
	 */
	public function convertPrivateMessageReplies()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'pmid' );
		
		foreach( $this->fetch( 'privatemessages', 'pmid' ) AS $row )
		{
			$libraryClass->convertPrivateMessageReply( array(
				'msg_id'			=> $row['pmid'],
				'msg_topic_id'		=> $row['pmid'],
				'msg_date'			=> $row['dateline'],
				'msg_post'			=> $row['message'],
				'msg_author_id'		=> $row['uid'],
				'msg_ip_address'	=> $row['ipaddress'],
			) );
			
			$libraryClass->setLastKeyValue( $row['pmid'] );
		}
	}

	/**
	 * Convert profanity filters
	 *
	 * @return	void
	 */
	public function convertProfanityFilters()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'bid' );
		
		foreach( $this->fetch( 'badwords', 'bid' ) AS $row )
		{
			$libraryClass->convertProfanityFilter( array(
				'wid'		=> $row['bid'],
				'type'		=> $row['badword'],
				'swop'		=> $row['replacement'],
			) );
			
			$libraryClass->setLastKeyValue( $row['bid'] );
		}
	}
	
	/**
	 * Convert questions and answers
	 *
	 * @return	void
	 */
	public function convertQuestionAndAnswers()
	{
		$libraryClass = $this->getLibrary();
		
		$libraryClass::setKey( 'qid' );
		
		foreach( $this->fetch( 'questions', 'qid' ) AS $row )
		{
			$answers = explode( "\n", $row['answer'] );
			
			$libraryClass->convertQuestionAndAnswer( array(
				'qa_id'			=> $row['qid'],
				'qa_question'	=> $row['question'],
			), $answers );
		}
	}

	/**
	 * Check if we can redirect the legacy URLs from this software to the new locations
	 *
	 * @return	NULL|\IPS\Http\Url
	 */
	public function checkRedirects()
	{
		/* If we can't access profiles, don't bother trying to redirect */
		if( !\IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'core', 'members' ) ) )
		{
			return NULL;
		}

		$url = \IPS\Request::i()->url();

		if( mb_strpos( $url->data[ \IPS\Http\Url::COMPONENT_PATH ], 'member.php' ) !== FALSE )
		{
			try
			{
				$data = (string) $this->app->getLink( \IPS\Request::i()-uid, array( 'members', 'core_members' ) );
				return \IPS\Member::load( $data )->url();
			}
			catch( \Exception $e )
			{
				return NULL;
			}
		}

		return NULL;
	}
}