<?php
/**
 * @brief		CLI conversion process
 * @author		<a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright	(c) Invision Power Services, Inc.
 * @license		https://www.invisioncommunity.com/legal/standards/
 * @package		Invision Community
 * @since		9 Sept 2013
 */

namespace IPSUtf8\modules\cli;

/**
 * CLI Conversion process
 */
class cli
{
	/**
	 * @brief	Conversion class
	 */
	protected static $convertClass = NULL;
	
	/**
	 * Execute
	 *
	 * @return	void
	 */
	public function execute()
	{
		static::$convertClass = '\IPSUtf8\Convert';
		
		$convertClass = static::$convertClass;
		
		if ( ! is_dir( THIS_PATH . '/tmp' ) )
		{
			@mkdir( THIS_PATH . '/tmp' );
			@chmod( THIS_PATH . '/tmp', 0777 );
		}
		
		if ( ! is_writable( THIS_PATH . '/tmp' ) )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput( "<warning>Please ensure that '" . THIS_PATH . '/tmp' . "' is writable.</warning>");
			exit();
		}

		if ( \IPSUtf8\Request::i()->isRestore() )
		{
			$this->restore();
		}
		
		if ( \IPSUtf8\Request::i()->isDeleteOriginals() )
		{
			$this->deleteOriginals();
		}
		
		if ( IPB_LOCK and ! \IPSUtf8\Session::i()->is_ipb )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput( "<warning>Cannot locate the IP.Board database tables. Please check to ensure the SQL Prefix if set, is correct in 'conf_global.php'.</warning>");
			exit();
		}
		
		if ( \IPSUtf8\Request::i()->isDumpMethod() )
		{
			$this->dumpMethod();
		}
		
		if ( \IPSUtf8\Request::i()->isInfo() )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput( implode( "\n", $convertClass::i()->getDebugString() ) );
			exit();
		}
		
		$welcome = 'Welcome to the IPS UTF8 Conversion utility (v' . \IPSUtf8\Convert::VERSION_ID . ')';
		$intro   = "This utility will convert all the tables in this database to UTF-8.\nThe converted data is inserted into new tables prefixed with x_utf_ and the original data kept.";
			
		\IPSUtf8\Output\Cli::i()->sendOutput( str_repeat( '-', \strlen( $welcome ) ) . "\n" . $welcome . "\n" . str_repeat( '-', \strlen( $welcome ) ) );
		
		if ( \IPSUtf8\Session::i()->has_archive )
		{
			if ( ! \count( \IPSUtf8\Convert\Archive::i()->getNonUtf8Tables ) )
			{
				\IPSUtf8\Output\Cli::i()->sendOutput( "You have an archive table in a different database, and it is UTF8 and collations are correct." );
			}
			else
			{
				\IPSUtf8\Output\Cli::i()->sendOutput( "<choice>You have an archive table in a different database, convert this now?\n[y] Enter 'y' to convert (recommended)\n[x] Enter 'n' to skip conversion.</choice>", 100 );
				
				if ( \IPSUtf8\Output\Cli::i()->response === 'y' )
				{
					/* Convert */
					while( \IPSUtf8\Convert\Archive::i()->process( 250 ) === true )
					{
						$json = \IPSUtf8\Session::i()->json;
				
						\IPSUtf8\Output\Cli::i()->sendOutput( \IPSUtf8\Output\Cli::i()->progressBar( \IPSUtf8\Session::i()->current_table, \IPSUtf8\Convert\Archive::i()->getTotalRowsToConvert(), $json['convertedCount'] ), 101 );
					}
					
					\IPSUtf8\Convert\Archive::i()->renameTables();
					\IPSUtf8\Session::i()->reset();
					
					\IPSUtf8\Output\Cli::i()->sendOutput( "Archive table converted" );
				}
			}
		}
		
		if ( ! \count( \IPSUtf8\Session::i()->tables ) )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput("No tables found for processing. Please check to ensure the correct database name and prefix are being used.");
			exit();
		}

		if ( empty( \IPSUtf8\Session::i()->status ) )
		{
			if ( $convertClass::i()->databaseIsUtf8() )
			{ 
				/* Check collations */
				if ( ! \count( $convertClass::i()->getNonUtf8CollationTables() ) )
				{
					/* We're golden! */
					\IPSUtf8\Output\Cli::i()->sendOutput("The database is set to UTF-8, collations are correct and doesn't need converting.\n<choice>[x] Enter 'x' to exit the conversion (RECOMMENDED).\n[y] Enter 'y' to perform a full conversion anyway</choice>", 100 );
					
					if ( \IPSUtf8\Output\Cli::i()->response === 'x' )
					{
						exit();
					}
					else if ( \IPSUtf8\Output\Cli::i()->response === 'y' )
					{
						$sessionData = \IPSUtf8\Session::i()->json;
						$sessionData['force_conversion'] = 1;
						
						\IPSUtf8\Session::i()->json = $sessionData;
						\IPSUtf8\Session::i()->save();
						
						/* Force a recount of tables */
						$convertClass::i()->getNonUtf8Tables( true );
					}
				}
				else
				{
					$this->collationFix();
				}
			}
			
			\IPSUtf8\Output\Cli::i()->sendOutput( "\n" . \count( $convertClass::i()->getNonUtf8Tables() ) . " table(s) are not UTF-8 and need converting.\n");
			$origUtf8Charset	= \IPSUtf8\Db::i('utf8')->getCharset();
			$canMb4 = (bool) \IPSUtf8\Db::i('utf8')->set_charset( 'utf8mb4' );
			\IPSUtf8\Db::i('utf8')->set_charset( $origUtf8Charset );
			if ( version_compare( \IPSUtf8\Db::i()->server_info, '5.5.3', '>=' ) AND $canMb4 !== FALSE )
			{
				$sessionData = \IPSUtf8\Session::i()->json;
				$sessionData['use_utf8mb4'] = 1;

				\IPSUtf8\Session::i()->json = $sessionData;
				\IPSUtf8\Session::i()->save();
			}
			
			
			\IPSUtf8\Output\Cli::i()->sendOutput( $intro );			
			\IPSUtf8\Output\Cli::i()->sendOutput( "Press enter to continue", 100 );
		}
		else if ( \IPSUtf8\Session::i()->status === 'completed' )
		{
			$this->completed();
		}
		else
		{
			$date  = date('D j F Y', \IPSUtf8\Session::i()->updated );
			$count = \count( array_keys( \IPSUtf8\Session::i()->completed_json ) );
			$pcent = round( ( \IPSUtf8\Session::i()->json['convertedCount'] / $convertClass::i()->getTotalRowsToConvert() * 100 ) );
			
			$more = "<warning>You have a conversion session unfinished. The session was last updated on " . $date . " and has processed " . $count . " tables (" . $pcent . "%) so far.</warning>";
			 
			\IPSUtf8\Output\Cli::i()->sendOutput( $intro );
			\IPSUtf8\Output\Cli::i()->sendOutput( $more );
			
			\IPSUtf8\Output\Cli::i()->sendOutput( "<choice>[c] Enter 'c' to continue\n[x] Enter 'x' to reset and start the conversion again.</choice>", 100 );
			
			if ( \IPSUtf8\Output\Cli::i()->response === 'x' )
			{
				\IPSUtf8\Output\Cli::i()->sendOutput( "Are you sure? This will re-start the conversion\n<choice>[y] Enter 'y' to re-start the conversion\n[c] Enter 'c' to continue the conversion.</choice>", 100 );
				
				if ( \IPSUtf8\Output\Cli::i()->response === 'y' )
				{
					\IPSUtf8\Session::i()->reset();
					return $this->execute();
				}
			}
		}
		
		$json = \IPSUtf8\Session::i()->json;
		\IPSUtf8\Output\Cli::i()->sendOutput( \IPSUtf8\Output\Cli::i()->progressBar( \IPSUtf8\Session::i()->current_table, $convertClass::i()->getTotalRowsToConvert(), $json['convertedCount'] ), 101 );
			
		/* Convert */
		while( $convertClass::i()->process( 250 ) === true )
		{
			$json = \IPSUtf8\Session::i()->json;
			
			\IPSUtf8\Output\Cli::i()->sendOutput( \IPSUtf8\Output\Cli::i()->progressBar( \IPSUtf8\Session::i()->current_table, $convertClass::i()->getTotalRowsToConvert(), $json['convertedCount'] ), 101 );
		}
		
		/* Check for completed */
		if ( \IPSUtf8\Session::i()->status === 'completed' )
		{
			$json = \IPSUtf8\Session::i()->json;
			
			\IPSUtf8\Output\Cli::i()->sendOutput( \IPSUtf8\Output\Cli::i()->progressBar( \IPSUtf8\Session::i()->current_table, $convertClass::i()->getTotalRowsToConvert(), $convertClass::i()->getTotalRowsToConvert() ), 101 );
			$this->completed();
		}
	}
	
	/**
	 * Fix collations
	 *
	 */
	public function collationFix()
	{
		$convertClass = static::$convertClass;
		
		\IPSUtf8\Output\Cli::i()->sendOutput("The database is set to UTF-8 and all tables are UTF-8 but " . \count( $convertClass::i()->getNonUtf8CollationTables() ) . " table(s) have incorrect collations and need fixing.\n<choice>[f] Enter 'f' to fix table and field collations (RECOMMENDED)\n[y] Enter 'y' to perform a full conversion\n[x] Enter 'x' to exit the conversion</choice>", 100 );
				
		if ( \IPSUtf8\Output\Cli::i()->response === 'x' )
		{
			exit();
		}
		else if ( \IPSUtf8\Output\Cli::i()->response === 'f' )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput("Running now. This can take a while to complete...");
			
			$convertClass::i()->fixCollation();
			
			\IPSUtf8\Output\Cli::i()->sendOutput("Collation checked and fixed where appropriate");
			
			exit();
		}
	}
	
	/**
	 * Process is completed
	 */
	public function completed()
	{
		$convertClass	= static::$convertClass;
		$sessionData	= \IPSUtf8\Session::i()->json;
		$sql_charset	= ( $sessionData['use_utf8mb4'] ) ? 'utf8mb4' : 'utf8';
		$utf8mb4		= ( $sessionData['use_utf8mb4'] ) ? "\n\$INFO['sql_utf8mb4'] = true;" : '';
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "\nConversion has completed in " . \IPSUtf8\Session::i()->timeTaken( true ) );

		\IPSUtf8\Output\Cli::i()->sendOutput( "<choice>[f] Enter 'f' to finish\n[x] Enter 'x' to reset and start the conversion again.</choice>", 100 );
			
		if ( \IPSUtf8\Output\Cli::i()->response === 'x' )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput( "Are you sure? This will re-start the conversion\n<choice>[y] Enter 'y' to re-start the conversion\n[f] Enter 'f' to finish the conversion.</choice>", 100 );
			
			if ( \IPSUtf8\Output\Cli::i()->response === 'y' )
			{
				\IPSUtf8\Session::i()->reset();
				
				return $this->execute();
			}
		}
		
		$convertClass::i()->finish();
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "You can now test the conversion by:\n\nEditing conf_global.php to add:\n \$INFO['sql_charset'] = '" . $sql_charset . "';{$utf8mb4}\nChange \$INFO['sql_tbl_prefix'] to: \$INFO['sql_tbl_prefix'] = '" . \IPSUtf8\Db::i('utf8')->prefix . "';\n" );
		\IPSUtf8\Output\Cli::i()->sendOutput( "If you are happy with the conversion, you can have it permanently rename the tables so that you can restore your original 'sql_table_prefix' and delete the original tables." );
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "<choice>[y] Enter 'y' to rename your tables\n[x] Enter 'x' to reset and start the conversion again.</choice>", 100 );
		
		if ( \IPSUtf8\Output\Cli::i()->response === 'x' )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput( "Are you sure? This will re-start the conversion\n<choice>[y] Enter 'y' to re-start the conversion\n[f] Enter 'f' to finish the conversion.</choice>", 100 );
			
			if ( \IPSUtf8\Output\Cli::i()->response === 'y' )
			{
				\IPSUtf8\Session::i()->reset();
				
				return $this->execute();
			}
		}
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "Renaming tables, this may take a short while." );
		
		$convertClass::i()->renameTables();
		
		if ( \count( $convertClass::i()->getNonUtf8CollationTables() ) )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput( "Checking and fixing collations." );
			$convertClass::i()->fixCollation();
		}
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "You can now complete the conversion by editing conf_global.php to add \$INFO['sql_charset'] = '" . $sql_charset . "'; and to change \$INFO['sql_tbl_prefix'] to:\n\$INFO['sql_tbl_prefix'] = '" . \IPSUtf8\Db::i()->prefix . "';" );
		
		exit();
	}
	
	/**
	 * Restore tables orig_ back
	 *
	 * @return void
	 */
	protected function restore()
	{
		$convertClass = static::$convertClass;
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "<choice>Restore the original *non-UTF8* tables?\n[y] Enter 'y' to restore\n[x] Enter 'n' to exit.</choice>", 100 );
		
		if ( \IPSUtf8\Output\Cli::i()->response === 'y' )
		{
			$convertClass::i()->restoreOriginalTables();
			
			\IPSUtf8\Session::i()->reset();
			
			\IPSUtf8\Output\Cli::i()->sendOutput( "Tables restored" );
		}
		
		exit();
	}
	
	/**
	 * Delete tables orig_
	 *
	 * @return void
	 */
	protected function deleteOriginals()
	{
		$convertClass = static::$convertClass;
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "<choice>DELETE the original *non-UTF8* tables?\n[y] Enter 'y' to delete\n[x] Enter 'n' to exit.</choice>", 100 );
		
		if ( \IPSUtf8\Output\Cli::i()->response === 'y' )
		{
			$convertClass::i()->deleteOriginalTables();
			
			\IPSUtf8\Session::i()->reset();
			
			\IPSUtf8\Output\Cli::i()->sendOutput( "Tables deleted" );
		}
		
		exit();
	}
	
	/**
	 * Dump method. No laughing at the back
	 *
	 * @return void
	 */
	protected function dumpMethod()
	{
		$convertClass = static::$convertClass;
		
		\IPSUtf8\Session::i()->reset();
		
		/* Names are getting silly now */
		if ( ! $convertClass::canDump() )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput("Sorry, you can not use this as either shell_exec or iconv are disabled. Please re-run without the --dump flag");
			exit();
		}
		
		\IPSUtf8\Output\Cli::i()->sendOutput("<warning>This method will OVERWRITE your existing database so please only perform this on a copy of your database.</warning>");
		\IPSUtf8\Output\Cli::i()->sendOutput( "<choice>[y] Enter 'y' to continue\n[n] Enter 'n' to exit.</choice>", 100 );
			
		if ( \IPSUtf8\Output\Cli::i()->response !== 'y' )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput("If you want to convert without overwriting your database, please re-run without the --dump flag");
			exit();
		}
		
		/* Lets continue as we're good to go */
		$myDump = 'mysqldump';
		if ( isset( $GLOBALS['argv'][2] ) )
		{
			$myDumpPath = str_replace( array( '/mysqldump', 'mysqldump' ), '', $GLOBALS['argv'][2] );
		}
		else
		{
			\IPSUtf8\Output\Cli::i()->sendOutput( "Please enter the path to mysqldump. If you do not know, or it does not have a path, just press enter\n<choice>Enter the path to mysqldump</choice>", 100 );
			
			$myDumpPath = trim( str_replace( array( '/mysqldump', 'mysqldump' ), '', \IPSUtf8\Output\Cli::i()->response ), "\n" );
		}
		
		$myDump = $myDumpPath ? rtrim( $myDumpPath, '/' ) . '/' . $myDump : $myDump;
		$mySql  = $myDumpPath ? rtrim( $myDumpPath, '/' ) . '/mysql' : 'mysql';
		
		$output = shell_exec( $myDump . ' --help' );
		
		if ( stristr( $output, 'not found' ) )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput( $myDump . " could not be found.");
			exit();
		}
		
		$toConvert = $convertClass::i()->getNonUtf8Tables();
		
		if ( ! \count( $toConvert ) )
		{
			\IPSUtf8\Output\Cli::i()->sendOutput("Nothing to convert!");
			exit();
		}
		
		require( ROOT_PATH . '/conf_global.php' );
		
		$socket = '';
		$port   = '';
		if ( isset( $INFO['sql_socket'] ) and ! empty($INFO['sql_socket']) )
		{
			$socket = ' --socket=' . $INFO['sql_socket'];
		}
		
		if ( isset( $INFO['sql_port'] ) and ! empty($INFO['sql_port']) )
		{
			$port = ' --port=' . $INFO['sql_port'];
		}
		
		foreach( $toConvert as $table )
		{
			//$command = $myDump . $socket . " --max_allowed_packet=500M --quick --add-drop-table -u " . $INFO['sql_user'] . " --password='" . $INFO['sql_pass'] . "' " . $INFO['sql_database'] . " " . $table . " | sed -e 's/CHARSET\\=latin1/CHARSET\\=utf8\\ COLLATE\\=utf8_general_ci/g' | iconv -f latin1 -t UTF-8 | " . $mySql . " -u " . $INFO['sql_user'] . " --password='" . $INFO['sql_pass'] . "' " . $INFO['sql_database'];
			
			$tableCharSet = \IPSUtf8\Convert::i()->database_charset;
			$tableData    = \IPSUtf8\Session::i()->tables[ $table ];
			
			if ( ! empty( $tableData['charset'] ) )
			{
				$tableCharSet = $tableData['charset'];
			}
			
			$sed = " -e 's/CHARSET\\=" . $tableCharSet . "/CHARSET\\=utf8\\ COLLATE\\=utf8_unicode_ci/g' ";
			
			$command = $myDump . $socket . $port . " --max_allowed_packet=500M --quick --add-drop-table -h " . $INFO['sql_host'] . " -u " . $INFO['sql_user'] . " --password='" . $INFO['sql_pass'] . "' " . $INFO['sql_database'] . " " . \IPSUtf8\Db::i()->prefix . $table . " | sed " . $sed . " | iconv -f " . $tableCharSet . " -t UTF-8 > tmp/{$table}.sql";
			
			\IPSUtf8\Output\Cli::i()->sendOutput("Running\n" . $command . "\nPlease be patient, this may take some time");
			
			exec( $command, $output );
			
			$command = $mySql . $port . " -h " . $INFO['sql_host'] . " -u " . $INFO['sql_user'] . " --password='" . $INFO['sql_pass'] . "' " . $INFO['sql_database'] . "< tmp/{$table}.sql";
			
			\IPSUtf8\Output\Cli::i()->sendOutput("Running\n" . $command . "\nPlease be patient, this may take some time");
			
			exec( $command, $output );
			
			shell_exec( "rm tmp/{$table}.sql" );
			
			if ( stristr( implode( "\n", $output ), 'Error' ) )
			{
				\IPSUtf8\Output\Cli::i()->sendOutput( "Failed:\n" . $output );
				exit();
			}
			
			/* Update timestamp */
			\IPSUtf8\Session::i()->save();
		}
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "\nConversion has completed in " . \IPSUtf8\Session::i()->timeTaken( true ) );
				
		/* Still here... then fix collations and update DB charset */
		\IPSUtf8\Output\Cli::i()->sendOutput("Fixing table collations...");
		
		\IPSUtf8\Session::i()->updateTableData();
		$convertClass::i()->fixCollation();
		
		\IPSUtf8\Output\Cli::i()->sendOutput("Finishing...");
		$convertClass::i()->finish();
		
		\IPSUtf8\Output\Cli::i()->sendOutput( "You can now complete the conversion by editing conf_global.php to add \$INFO['sql_charset'] = 'UTF8';" );
		exit();
	}
	
}