<?php
/*========================================================================*\
|| ###################################################################### ||
|| # vBulletin 5.4.1
|| # ------------------------------------------------------------------ # ||
|| # Copyright 2000-2018 vBulletin Solutions Inc. All Rights Reserved.  # ||
|| # This file may not be redistributed in whole or significant part.   # ||
|| # ----------------- VBULLETIN IS NOT FREE SOFTWARE ----------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html   # ||
|| ###################################################################### ||
\*========================================================================*/

if (VB_AREA != 'Install' AND !isset($GLOBALS['vbulletin']->db))
{
	exit;
}

class vB_Upgrade_final extends vB_Upgrade_Version
{
	/*Constants=====================================================================*/

	/**
	 * The short version of the script
	 *
	 * @var	string
	 */
	public $SHORT_VERSION = 'final';

	/**
	 * The long version of the script
	 *
	 * @var	string
	 */
	public $LONG_VERSION  = 'final';

	/*Properties====================================================================*/

	/**
	 * Step #1 - Import Settings XML
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_139()
	 */
	function step_1()
	{
		vB_Upgrade::createAdminSession();
		vB_Library::instance('usergroup')->buildDatastore();
		vB::getUserContext()->rebuildGroupAccess();
		vB_Channel::rebuildChannelTypes();

		if (VB_AREA == 'Upgrade')
		{
			$this->show_message($this->phrase['final']['import_latest_options']);
			require_once(DIR . '/includes/adminfunctions_options.php');

			if (!($xml = file_read(DIR . '/install/vbulletin-settings.xml')))
			{
				$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-settings.xml'), self::PHP_TRIGGER_ERROR, true);
				return;
			}

			$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-settings.xml'));
			xml_import_settings($xml);

			$this->show_message($this->phrase['core']['import_done']);
		}
		else if (vB_Upgrade::isCLI())
		{
			$library = vB_Upgrade::fetch_library($this->registry,$this->phrase, '', false, null, false, array('execute' => false));
			$library->loadDSSettingsfromConfig();
			$this->show_message($this->phrase['core']['import_done']);
		}
		else
		{
			$this->skip_message();
		}
	}

	/**
	 * Step #2 - Import Admin Help XML
	 *
	 */
	function step_2()
	{
		$this->show_message($this->phrase['final']['import_latest_adminhelp']);
		require_once(DIR . '/includes/adminfunctions_help.php');

		if (!($xml = file_read(DIR . '/install/vbulletin-adminhelp.xml')))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-adminhelp.xml'), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-adminhelp.xml'));

		xml_import_help_topics($xml);
		$this->show_message($this->phrase['core']['import_done']);
	}

	/**
	 * Step #3 - Import Language XML
	 *
	 */
	function step_3()
	{
		$this->show_message($this->phrase['final']['import_latest_language']);
		require_once(DIR . '/includes/adminfunctions_language.php');

		if (!($xml = file_read(DIR . '/install/vbulletin-language.xml')))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-language.xml'), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-language.xml'));

		xml_import_language($xml, -1, '', false, true, !defined('SUPPRESS_KEEPALIVE_ECHO'));
		$this->show_message($this->phrase['core']['import_done']);

		/* If fresh install, let's use the just imported language's charset as the default.
		 * vB_Upgrade's setup_environment() will overwrite this later from the upgrade phrases,
		 * but that doesn't matter because we just need the right charset for the custom language
		 * import below within the same session.
		 */
		if (VB_AREA == 'Install')
		{
			$assertor = vB::getDbAssertor();
			$row = $assertor->getRow('setting', array('varname' => 'languageid'));
			if ($row AND isset($row['value']))
			{
				$charset = $assertor->getColumn('language', 'charset', array('languageid' => $row['value']));
				if (is_array($charset))
				{
					$charset = $charset[0];
				}
				vB_Template_Runtime::addStyleVar('charset', $charset);
			}
		}

		// Try to import custom languages
		// List files in customlanguages dir
		$customlangdir = DIR . '/install/customlanguages';

		if (is_dir($customlangdir))
		{
			if ($dh = opendir($customlangdir))
			{
				$languagesLoaded = array();
				while (($file = readdir($dh)) !== false)
				{
					if (strpos($file, 'vbulletin-custom-language-') === 0)
					{
						if (!($xml = file_read($customlangdir . '/' . $file)))
						{
							$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], $file), self::PHP_TRIGGER_ERROR, false);
						}
						else
						{
							// returns languageid, or false if the custom language wasn't imported
							$langid = xml_import_language($xml, 0, '', true, true, !defined('SUPPRESS_KEEPALIVE_ECHO'), true);
							if ($langid === false)
							{
								$this->show_message(sprintf($this->phrase['vbphrase']['skipping_file'], $file));
							}
							else
							{
								$languagesLoaded[] = $langid;

								$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], $file));
								$this->show_message($this->phrase['core']['import_done']);

								// VBV-14130 Only set the default language for fresh installs.
								if (VB_AREA == 'Install')
								{
									$vblangcode = $assertor->getColumn('language', 'vblangcode', array('languageid' => $langid));
									if (is_array($vblangcode))
									{
										$vblangcode = $vblangcode[0];
									}

									$config = vB::getConfig();
									if (isset($config['Install']['default_language_vblangcode']))
									{
										// If they set this in their backend config, use that to set the default. Note,
										// if they set this incorrectly, this won't change the default language.
										$setDefault = (	$config['Install']['default_language_vblangcode'] === $vblangcode );
									}
									else
									{
										// Set the default language to the imported custom language.
										// This isn't perfect, since we may have more than one custom
										// language, but it's better to set the last imported custom language
										// to be the default than to leave English as the default
										// if they downloaded the package with a different language than English
										$setDefault = true;
									}

									if ($setDefault)
									{
										vB::getDatastore()->setOption('languageid', $langid, true);
										$this->show_message(sprintf($this->phrase['final']['default_language_set_to_x'], $vblangcode, $file));
									}
								}
							}
						}
					}
				}

				$db = vB::getDbAssertor();

				$set = $db->select(
					'language',
					array('languageid' => $languagesLoaded),
					false,
					array('languageid', 'vblangcode')
				);

				$productlib = vB_Library::instance('product');
				foreach($set AS $row)
				{
					$type = 'vbulletin-custom-language-' . $row['vblangcode'];
					$xmlfiles = $productlib->loadProductXmlList($type);

					foreach($xmlfiles AS $file)
					{
						if (!($xml = file_read($file)))
						{
							$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], $file), self::PHP_TRIGGER_ERROR, false);
						}
						else
						{
							$result = xml_import_language($xml, $row['languageid'], '', true, true, !defined('SUPPRESS_KEEPALIVE_ECHO'), true);
						}
					}
				}

				closedir($dh);
			}
		}

		build_language_datastore();
	}

	/**
	 * Step #4 - Import widgets XML
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_128() & vB_Upgrade_510a1 step_3()
	 */
	function step_4()
	{
		$this->show_message($this->phrase['final']['import_latest_widgets']);
		$widgetFile = DIR . '/install/vbulletin-widgets.xml';
		if (!($xml = file_read($widgetFile)))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-widgets.xml'), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-widgets.xml'));

		$xml_importer = new vB_Xml_Import_Widget();
		$xml_importer->importFromFile($widgetFile);

		$this->show_message($this->phrase['core']['import_done']);
	}

	/**
	 * Step #5 - Import screenlayout XML
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_514b3 step_1()
	 */
	function step_5()
	{
		$filename = 'vbulletin-screenlayouts.xml';
		$file = DIR . '/install/' . $filename;
		if (!file_exists($file))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], $filename), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], $filename));

		$options = vB_Xml_Import::OPTION_OVERWRITE;
		$importer = new vB_Xml_Import_Screenlayout('vbulletin', $options);
		$importer->importFromFile($file);

		$this->show_message($this->phrase['core']['import_done']);
	}

	/**
	 * Step #6 - Import pagetemplates XML
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_129() & vB_Upgrade_510a1 step_4()
	 *	WARNING: step_5 (import screenlayout XML) should be called before step_6 (import pagetemplates XML). Otherwise,
	 *			pagetemplate table may be missing its screenlayoutids. If it's not possible to call step_5() first,
	 *			you must add a step to fix the pagetemplates with missing screenlayoutids. See VBV-13771.
	 */
	function step_6()
	{
		$pageTemplateFile = DIR . '/install/vbulletin-pagetemplates.xml';
		if (!($xml = file_read($pageTemplateFile)))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-pagetemplates.xml'), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-pagetemplates.xml'));

		// TODO: there might be some upgrades in which we do want to add some widgetinstances
		$options = (VB_AREA == 'Upgrade') ? 0 : vB_Xml_Import::OPTION_ADDWIDGETS;
		$xml_importer = new vB_Xml_Import_PageTemplate('vbulletin', $options);
		$xml_importer->importFromFile($pageTemplateFile);

		// Update widget instance config item values that use phrases (phrase:<phrasevarname>)
		// This updates all widget config items that need it, including pre-existing widget instances
		// This is needed because 500a30 step 1 imports the page templates, and the
		// phrase placeholders are not replaced in the admin config because the target phrase(s)
		// do not yet exist in the phrase table at that point. See VBV-12214.
		$pageTemplateImporter = new vB_Xml_Import_PageTemplate('vbulletin', 0);
		$pageTemplateImporter->replacePhrasePlaceholdersInWidgetConfigs();

		/*
			Until we add a way to store which pagetemplate is "default" or "protected", we're going with
			a list of hard-coded GUIDs in the page class.
			This check is to remind devs to add the guid to the function when they add a new pagetemplate.
			If the guid isn't added to the function, admins will be able to accidentally delete the pagetemplates
			via sitebuilder. VBV-14123
		 */
		$config = vB::getConfig();
		if (!empty($config['Misc']['debug']))
		{
			$knownDefaults = vB_Page::getDefaultPageTemplateGUIDs();
			$xml = $xml_importer::parseFile($pageTemplateFile);
			// probably unnecessary since nowadays the pagetemplatefile is never going to have just 1 pagetemplate, but copied
			// from pagetemplate importer
			$pageTemplates = is_array($xml['pagetemplate'][0]) ? $xml['pagetemplate'] : array($xml['pagetemplate']);
			$unknowns = array();
			foreach ($pageTemplates AS $__pagetemplate)
			{
				$__guid = $__pagetemplate['guid'];
				if (!isset($knownDefaults[$__guid]))
				{
					$unknowns[$__guid] = $__guid;
				}
			}

			// halt upgrade for dev.
			if (!empty($unknowns))
			{
				$guids = implode(",\n", $unknowns);
				$this->add_error(
					sprintf($this->phrase['vbphrase']['missing_pagetemplate_guids_in_vb_page'], $guids),
					self::PHP_TRIGGER_ERROR, true
				);
				return;
			}
		}

		$this->show_message($this->phrase['core']['import_done']);
	}

	/**
	 * Step #7 - Import pages XML
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_131() & vB_Upgrade_510a1 step_5()
	 */
	function step_7()
	{
		vB_Upgrade::createAdminSession();

		// Importing pages
		$pageFile = DIR . '/install/vbulletin-pages.xml';
		if (!($xml = file_read($pageFile)))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-pages.xml'), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-pages.xml'));

		$page_importer = new vB_Xml_Import_Page('vbulletin', 0);
		$page_importer->importFromFile($pageFile);
		build_language();

		$this->show_message($this->phrase['core']['import_done']);
	}

	/**
	 * Step #8 - Import channels XML
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_132() & vB_Upgrade_510a1 step_6()
	 */
	function step_8()
	{
		vB_Upgrade::createAdminSession();

		// Import channels
		$channelFile = DIR . '/install/vbulletin-channels.xml';
		if (!($xml = file_read($channelFile)))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-channels.xml'), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-channels.xml'));

		$channel_importer = new vB_Xml_Import_Channel('vbulletin', 0);
		$channel_importer->importFromFile($channelFile);

		// rebuild caches after adding channels
		vB::getUserContext()->rebuildGroupAccess();
		vB_Channel::rebuildChannelTypes();

		// Update widget instance config items that use channels (channelguid:<GUID>)
		$pageTemplateImporter = new vB_Xml_Import_PageTemplate('vbulletin', 0);
		$pageTemplateImporter->replaceChannelGuidsInWidgetConfigs();

		$this->show_message($this->phrase['core']['import_done']);
	}

	/**
	 * Step #9 - Import routes XML
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_133(), vB_Upgrade_510a1 step_7() & vB_Upgrade_510a7 step_1()
	 */
	function step_9()
	{
		vB_Upgrade::createAdminSession();

		// Importing routes
		$routesFile = DIR . '/install/vbulletin-routes.xml';
		if (!($xml = file_read($routesFile)))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-routes.xml'), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-routes.xml'));

		$route_importer = new vB_Xml_Import_Route();
		$route_importer->importFromFile($routesFile);

		// Update pages with new route ids
		$pageFile = DIR . '/install/vbulletin-pages.xml';
		$page_importer = new vB_Xml_Import_Page('vbulletin', 0);
		$parsedXML = $page_importer->parseFile($pageFile);
		$page_importer->updatePageRoutes($parsedXML);

		// Update channels with route ids
		$channelFile = DIR . '/install/vbulletin-channels.xml';
		$channel_importer = new vB_Xml_Import_Channel('vbulletin', 0);
		$parsedXML = $channel_importer->parseFile($channelFile);
		$channel_importer->updateChannelRoutes($parsedXML);

		$this->show_message($this->phrase['core']['import_done']);
	}

	/**
	 * After populating the node and channel tables with their data, we need to
	 * create the Channel widget instance configuration and update ONLY IF IT
	 * HASN'T BEEN ALREADY SET
	 *
	 * WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_134()
	 */
	function step_10()
	{
		// TODO Hard-coding widgetinstanceid = 1 and parentid = 1 in this
		// function seems fragile. Consider using vB_Xml_Import::getImportedId
		// to get the correct ID based on GUID

		$widgetid = $this->db->query_first("
			SELECT widgetinstanceid
			FROM `" . TABLE_PREFIX . "widgetinstance`
			WHERE widgetinstanceid = 1 AND adminconfig = ''
		");

		if (empty($widgetid))
		{
			$this->skip_message();
			return;
		}

		$contenttype = $this->db->query_first("
			SELECT contenttypeid
			FROM " . TABLE_PREFIX . "contenttype
			WHERE class = 'Channel'
		");
		$channelContentTypeId = $contenttype['contenttypeid'];

		$widgetConfig = array(
			'channel_node_ids' => array(),
		);

		$rootChannelResult = $this->db->query_read("
			SELECT *
			FROM " . TABLE_PREFIX . "node
			WHERE
				parentid = 1
				AND
				contenttypeid = $channelContentTypeId
		");
		while ($rootChannel = $this->db->fetch_array($rootChannelResult))
		{
			$widgetConfig['channel_node_ids'][] = $rootChannel['nodeid'];

			$subChannelResult = $this->db->query_read($q = "
				SELECT *
				FROM " . TABLE_PREFIX . "node
				WHERE
					parentid = $rootChannel[nodeid]
					AND
					contenttypeid = $channelContentTypeId
			");

			while ($subChannel = $this->db->fetch_array($subChannelResult))
			{
				$widgetConfig['channel_node_ids'][] = $subChannel['nodeid'];
			}
		}

		$this->run_query(
		sprintf($this->phrase['vbphrase']['update_table'], TABLE_PREFIX . 'widgetinstance'),
			"
			UPDATE `" . TABLE_PREFIX . "widgetinstance`
			SET adminconfig = '" . $this->db->escape_string(serialize($widgetConfig)) . "'
			WHERE widgetinstanceid = 1 AND adminconfig = ''
			"
		);

	}

	/**
	 * Create the channel routes
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_135()
	 */
	function step_11()
	{
		vB_Upgrade::createAdminSession();
		$channelXML = new vB_Xml_Import_Channel();
		$channelXML->fixMissingChannelRoutes();
		// We need to send a string into show_message().
		$this->show_message(sprintf($this->phrase['vbphrase']['update_table'], TABLE_PREFIX . 'node'));
	}

	/**
	 * Add routes to node table
	 *
	 *	WARNING: If you modify the step#, you must update vB_Upgrade_500a1 step_136()
	 */
	function step_12()
	{
		$contenttype = $this->db->query_first("
			SELECT contenttypeid
			FROM " . TABLE_PREFIX . "contenttype
			WHERE class = 'Channel'
		");
		$channelContentTypeId = $contenttype['contenttypeid'];

		$this->show_message(sprintf($this->phrase['vbphrase']['update_table'], TABLE_PREFIX . 'node'));

		// updating channel routes
		$this->db->query_write("
			UPDATE " . TABLE_PREFIX . "node c
			SET c.routeid = (SELECT MAX(r.routeid) FROM " . TABLE_PREFIX . "routenew r WHERE r.contentid = c.nodeid AND class='vB5_Route_Channel')
			WHERE c.routeid = 0 AND c.contenttypeid = $channelContentTypeId
			"
		);
	}

	/**
	 * Check Product Dependencies
	 *
	 */
	function step_13()
	{
		if (VB_AREA == 'Install')
		{
			$this->skip_message();
			return;
		}

		$this->show_message($this->phrase['final']['verifying_product_dependencies']);

		require_once(DIR . '/includes/class_upgrade_product.php');
		$this->product = new vB_Upgrade_Product($this->registry, $this->phrase['vbphrase'], true, $this->caller);

		$dependency_list = array();
		$product_dependencies = $this->db->query_read("
			SELECT pd.*
			FROM " . TABLE_PREFIX . "productdependency AS pd
			INNER JOIN " . TABLE_PREFIX . "product AS p ON (p.productid = pd.productid)
			WHERE
				pd.productid IN ('dummy') # // Any Integrated 3rd party products
					AND
				p.active = 1
			ORDER BY
				pd.dependencytype, pd.parentproductid, pd.minversion
		");
		while ($product_dependency = $this->db->fetch_array($product_dependencies))
		{
			$dependency_list["$product_dependency[productid]"][] = array(
				'dependencytype'  => $product_dependency['dependencytype'],
				'parentproductid' => $product_dependency['parentproductid'],
				'minversion'      => $product_dependency['minversion'],
				'maxversion'      => $product_dependency['maxversion'],
			);
		}

		$product_list = fetch_product_list(true);
		$disabled = array();

		foreach($dependency_list AS $productid => $dependencies)
		{
			$this->show_message(sprintf($this->phrase['final']['verifying_product_x'], $productid));
			$this->product->productinfo['productid'] = $productid;
			$disableproduct = false;
			try
			{
				$this->product->import_dependencies($dependencies);
			}
			catch(vB_Exception_AdminStopMessage $e)
			{
				$message = $this->stop_exception($e);
				$this->show_message($message);
				$disableproduct = true;
			}

			if ($disableproduct)
			{
				$disabled[] = $productid;
				$this->product->disable();
				$this->add_adminmessage(
					'disabled_product_x_y_z',
					array(
						'dismissable' => 1,
						'script'      => '',
						'action'      => '',
						'execurl'     => '',
						'method'      => '',
						'status'      => 'undone',
					),
					true,
					array($product_list["$productid"]['title'], $productid, $message)
				);
				$this->show_message(sprintf($this->phrase['final']['product_x_disabled'], $productid));
			}
		}

		if (!should_install_suite())
		{
			if (!$disabled['vbblog'] AND $product_list['vbblog']['active'])
			{
				$this->product = new vB_Upgrade_Product($this->registry, $this->phrase['vbphrase'], true, $this->caller);
				$this->product->productinfo['productid'] = 'vbblog';
				$this->product->disable();
				$this->show_message(sprintf($this->phrase['final']['product_x_disabled'], 'vbblog'));
			}
		}

	}

	/**
	 * Import Style XML
	 *
	 * @param	array	contains id to startat processing at
	 *
	 */
	function step_14($data = null)
	{
		$perpage = 1;
		$startat = intval($data['startat']);
		require_once(DIR . '/includes/functions_databuild.php');
		require_once(DIR . '/includes/adminfunctions_template.php');

		if (!($xml = file_read(DIR . '/install/vbulletin-style.xml')))
		{
			$this->add_error(sprintf($this->phrase['vbphrase']['file_not_found'], 'vbulletin-style.xml'), self::PHP_TRIGGER_ERROR, true);
			return;
		}

		if ($startat == 0)
		{
			$this->show_message(sprintf($this->phrase['vbphrase']['importing_file'], 'vbulletin-style.xml'));
		}

		$info = xml_import_style($xml, -1, -1, '', false, 1, false, $startat, $perpage);

		if (!$info['done'])
		{
			$this->show_message($info['output']);
			return array('startat' => $startat + $perpage);
		}
		else
		{
			vB_Upgrade::createAdminSession();
			build_bbcode_video(true);
			$this->show_message($this->phrase['core']['import_done']);
		}
	}

	/**
	 * Import Theme XMLs
	 *
	 */
	function step_15($data = null)
	{
		$result = $this->importThemes($data);
		if (!empty($result['messages']))
		{
			foreach ($result['messages'] AS $msg)
			{
				$this->show_message($msg);
			}
		}

		if (isset($result['startat']))
		{
			return array('startat' => $result['startat'] );
		}
	}

	function importThemes($data = null)
	{
		//defines $upgrade_options['theme_import'] array -- defined in a seperate file for easy editing.
		require(DIR . '/install/upgrade_options.php');

		$perpage = 1;
		$startat = intval($data['startat']);

		vB_Upgrade::createAdminSession();
		if ($startat == 0)
		{
			return array(
				'messages' => array(
					'import_themes' => $this->phrase['final']['import_themes']
				),
				// this is a hack to get the upgrader to output this message first, rather than
				// after all the outputs of xml_import_style() on the first iteration.
				'startat' => ++$startat
			);
		}
		$xml_importer = new vB_Xml_Import_Theme();

		try
		{
			$overwrite = true;
			if (isset($upgrade_options['theme_import']['overwrite']))
			{
				$overwrite = (boolean) $upgrade_options['theme_import']['overwrite'];
			}
			$info = $xml_importer->importThemes($perpage, $overwrite);
		}
		catch(vB_Exception_AdminStopMessage $e)
		{
			// If there was this exception, it most likely came from
			// failing to generate the default parent theme. We cannot
			// continue importing any themes without this parent theme.
			$message = $this->stop_exception($e);
			/*
			return array(
				'messages' => array(
					'exception' => $message,
					'skipping_try_again' => $this->phrase['core']['skipping_try_again'],
				)
			);
			$this->show_message($message);
			*/

			$this->add_error($message, self::PHP_TRIGGER_ERROR, true);
			return;
		}

		if (!$info['done'])
		{
			/*
			 * 	There's a problem after importing custom languages where the installer started off as ISO-8859-1, but the phrases
			 *	are likely imported as UTF-8, so any phrases with multibyte characters end up rendered mangled. The style importer
			 *	has at least 1 phrase, 'creating_a_new_style_called_x'.
			 *	This is a bit of a hack to convert those phrases to ISO-8859-1 on the fly so the upgrader will be able to show the
			 *	phrases with multibyte characters that are used by the style importer. This relies on the fact that the 'charset'
			 *	stylevar will be reset to the same as the installer ajax page initialization by vB_Upgrade->setup_environment().
			 */
			$languageid = vB_Api::instanceInternal('phrase')->getLanguageid(true);

			//bit of a hack, but some things come through as rendered phrases
			//some as standard phrase arrays that need to be rendered.
			//need to fix that so that everything comes out as a phrase array
			$output = $info['output'];
			if(is_array($output))
			{
				$output = $this->render_phrase_array($output);
			}
			$output = vB_String::toCharset($output, $languageid['charset'], vB_Template_Runtime::fetchStyleVar('charset'));

			return array(
				'messages' => array(
					'output' => $output
				),
				// startat has no meaning in this step. We just use it to keep this going until the theme importer has finished.
				'startat' => ++$startat
			);
		}
		else
		{
			return array('messages' => array('done' => $this->phrase['core']['import_done']));
		}
	}

	/**
	 * Reset all caches
	 */
	function step_16()
	{
		/*
		 * There are two reasons for reset cache in this class:
		 *  1- we want to run this once, no matter what version we are upgrading
		 *  2- we need to make sure that db has been updated with cache table
		 */

		$this->show_message($this->phrase['final']['resetting_cache']);

		// we need to restore original cache values, reverting the change in upgrade.php
		$config =& vB::getConfig();
		if (!empty($config['Backup']['Cache']['class']))
		{
			foreach ($config['Backup']['Cache']['class'] AS $key => $class)
			{
				$config['Cache']['class'][$key] = $class;
			}
		}

		// now reset all cache types
		vB_Cache::resetAllCache();

		//And set the fastDS cache for rebuild. We don't want to rebuild here- we might be running in CLI,
		// and a rebuild does nothing for the website. Also, we may be on two different server.
		//We want to force a fastDS rebuild, but we can't just call rebuild. There may be dual web servers,
		// and calling rebuild only rebuilds one of them.
		$options = vB::getDatastore()->getValue('miscoptions');
		$options['dsdate'] = vB::getRequest()->getTimeNow();
		$options['tmtdate'] = vB::getRequest()->getTimeNow();
		$options['phrasedate'] = vB::getRequest()->getTimeNow();
		vB::getDatastore()->build('miscoptions', serialize($options), 1);
	}

	/**
	 * Rebuild filesystem template cache
	 */
	function step_17()
	{
		if (vB::getDatastore()->getOption('cache_templates_as_files'))
		{
			$this->show_message($this->phrase['final']['rebuild_filesystem_template_cache']);
			vB_Upgrade::createAdminSession();
			$templateApi = vB_Api::instanceInternal('template');
			$templateApi->deleteAllTemplateFiles();
			$templateApi->saveAllTemplatesToFile();
		}
		else
		{
			$this->skip_message();
		}
	}

	/**
	 * Import the password schemes
	 */
	function step_18()
	{
		$this->show_message($this->phrase['final']['import_password_schemes']);
		vB_Library::instance('login')->importPasswordSchemes();
	}

	/**
	 * Step #19 insert default notifications data.
	 */
	function step_19()
	{
		$this->show_message($this->phrase['final']['adding_notification_defaults']);
		$this->addNotificationDefaultData();
	}

	/**
	 * Step-less function to insert default notifications data. Note that categories must be added to the db
	 * before types can be added. This allows version-specific upgraders to call it without having to go back
	 * and change the calls every time the step#'s are shifted around.
	 */
	function addNotificationDefaultData()
	{
		// remember to add to caller: $this->show_message($this->phrase['final']['adding_notification_defaults']);

		$lib = vB_Library::instance('Notification');
		$classes = $lib->getDefaultTypes();
		foreach ($classes as $class)
		{
			$lib->insertNotificationTypeToDB($class);
		}
	}


	function step_20()
	{
		if (VB_AREA == 'Install')
		{
			$this->skip_message();
			return;
		}

		$this->show_message($this->phrase['final']['check_for_updated_phrases']);

		vB_Upgrade::createAdminSession();
		$customcache = vB_Api::instanceInternal('phrase')->findUpdates();
		if (count($customcache) > 0)
		{
			$this->add_adminmessage(
				'custom_phrases_need_updating',
				array(
					'dismissable' => 1,
					'script'      => 'phrase.php',
					'action'      => 'findupdates',
					'execurl'     => 'phrase.php?do=findupdates',
					'method'      => 'get',
					'status'      => 'undone',
				),
				false
			);
		}
	}

	/**
	 * Step #21 Template Merge
	 * THIS SHOULD ALWAYS BE THE LAST STEP
	 * If this step changes vbulletin-upgrade.js must also be updated in the process_bad_response() function
	 *
	 * @param	array	contains start info
	 *
	 */
	function step_21($data = null)
	{
		if ($data['options']['skiptemplatemerge'])
		{
			$this->skip_message();
			return;
		}

		if ($data['response'] == 'timeout')
		{
			$this->show_message($this->phrase['final']['step_timed_out']);
			return;
		}

		$this->show_message($this->phrase['final']['merge_template_changes']);
		$startat = intval($data['startat']);
		require_once(DIR . '/includes/class_template_merge.php');
		if ($startat < 0)
		{
			// finished, need to rebuild styles.
			// Style rebuild sometimes needs a session.
			vB_Upgrade::createAdminSession();

			if ($error = build_all_styles(0, 0, '', true))
			{
				$this->add_error($error, self::PHP_TRIGGER_ERROR, true);
				return false;
			}
			$this->show_message(sprintf($this->phrase['vbphrase']['processing_complete']));
			return;
		}

		$products = array("'vbulletin'");

		// VBV-14672: Skip merges for styles with GUID (currently themes only)
		$styleids = array();
		if (!empty($upgrade_options['template_merge']['skip_themes']))
		{
			$stylesWithGuidQry = vB::getDbAssertor()->assertQuery('vBInstall:getStylesWithGUID');
			foreach ($stylesWithGuidQry AS $row)
			{
				$styleids[] = intval($row['styleid']);
			}
		}

		$merge_data = new vB_Template_Merge_Data($this->registry);
		$merge_data->start_offset = $startat;
		if (intval($upgrade_options['template_merge']['batch_size']) > 0)
		{
			$merge_data->batch_size = intval($upgrade_options['template_merge']['batch_size']);
		}
		$merge_data->add_condition($c = "tnewmaster.product IN (" . implode(', ', $products) . ")");

		if (!empty($styleids))
		{
			$merge_data->add_condition($c = "tcustom.styleid NOT IN (" . implode(', ', $styleids) . ")");
		}

		$merge = new vB_Template_Merge($this->registry);
		if (intval($upgrade_options['template_merge']['time_limit']) > 0)
		{
			$merge->time_limit = intval($upgrade_options['template_merge']['time_limit']);
		}
		else
		{
			$merge->time_limit = 4;
		}

		$output = array();
		$completed = $merge->merge_templates($merge_data, $output);

		if ($output)
		{
			foreach($output AS $message)
			{
				$this->show_message($message);
			}
		}

		$processed = $merge->fetch_processed_count();
		$this->show_message(sprintf($this->phrase['core']['processed_x_records_starting_at_y'], $processed, $startat));
		if ($completed)
		{
			// Hack to allow style rebuild to be in its own iteration due to mitigate timeout concerns...
			$this->show_message(sprintf($this->phrase['final']['merge_complete_rebuilding_style_information']));
			$startat = -1;
			return array('startat' =>  $startat);
		}
		else
		{
			$startat = $startat + $processed;
			return array('startat' =>  $startat);
		}
	}
}

/*=========================================================================*\
|| #######################################################################
|| # NulleD By - vBSupport.org
|| # CVS: $RCSfile$ - $Revision: 97610 $
|| #######################################################################
\*=========================================================================*/
