<?php
/*========================================================================*\
|| ###################################################################### ||
|| # vBulletin 5.1.9 Patch Level 4 - Licence Number LD125EAAF9
|| # ------------------------------------------------------------------ # ||
|| # Copyright 2000-2016 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();
		build_channel_permissions();
		vB::getUserContext()->rebuildGroupAccess();
		vB_Channel::rebuildChannelTypes();
		//build_product_datastore(); Why do this here ?

		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))
			{
				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
							$check = xml_import_language($xml, 0, '', true, true, !defined('SUPPRESS_KEEPALIVE_ECHO'), true);
							if ($check === false)
							{
								$this->show_message(sprintf($this->phrase['vbphrase']['skipping_file'], $file));
							}
							else
							{
								$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' => $check));
									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', $check, true);
										$this->show_message(sprintf($this->phrase['final']['default_language_set_to_x'], $vblangcode, $file));
									}
								}
							}
						}
					}
				}

				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();

		$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
		build_channel_permissions();
		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()
	{
		$this->show_message($this->phrase['final']['creating_new_routes']);
		$contenttype = $this->db->query_first("
			SELECT contenttypeid
			FROM " . TABLE_PREFIX . "contenttype
			WHERE class = 'Channel'
		");
		$channelContentTypeId = $contenttype['contenttypeid'];

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

		// fetch info for blog channels
		$blogParentId = vB_Library::instance('Blog')->getBlogChannel();
		$blogRoute = vB::getDbAssertor()->getRow('routenew', array(
			vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
			vB_dB_Query::CONDITIONS_KEY => array('guid' => 'vbulletin-4ecbdacd6aac05.50909926')
		));
		$blogPageTemplate = vB_Page::getBlogChannelPageTemplate();
		$blogsPage = vB::getDbAssertor()->getField('page', array(
			vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
			vB_dB_Query::CONDITIONS_KEY => array('guid' => vB_Page::PAGE_BLOG)
		));

		$rootChannelResult = $this->db->query_read("
			SELECT n.nodeid, n.title, n.parentid, c.category
			FROM " . TABLE_PREFIX . "node n
			INNER JOIN " . TABLE_PREFIX . "channel c ON n.nodeid = c.nodeid
			WHERE
				parentid IN (1, $blogParentId)
				AND
				contenttypeid = $channelContentTypeId
				AND
				routeid = 0
		");
		$queue = array();
		$prefixes = array(1 => '', $blogParentId => $blogRoute['prefix']);
		while ($channel = $this->db->fetch_array($rootChannelResult))
		{
			$channel['page_parentid'] = ($channel['parentid'] == $blogParentId) ? $blogsPage : 1;
			$queue[] = $channel;
		}

		$defaultPageTemplates = vB::getDbAssertor()->assertQuery('pagetemplate', array(
			vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
			vB_dB_Query::CONDITIONS_KEY => array(
				array('field' => 'guid', 'value' => array(
					vB_Page::TEMPLATE_CHANNEL,
					vB_Page::TEMPLATE_CONVERSATION,
					vB_Page::TEMPLATE_CATEGORY
				))
			)
		));

		foreach ($defaultPageTemplates AS $pageTemplate)
		{
			$pageTemplates[$pageTemplate['guid']] = $pageTemplate['pagetemplateid'];
		}

		$phraseLib = vB_Library::instance('phrase');
		while ($channel = array_shift($queue))
		{
			$hyphenedTitle = strtolower(str_replace(' ', '-', $channel['title']));

			// create main page for channel
			if ($channel['category'] > 0)
			{
				$pagetemplateid = $pageTemplates[vB_Page::TEMPLATE_CATEGORY];
			}
			else if ($channel['parentid'] == $blogParentId)
			{
				$pagetemplateid = $blogPageTemplate;
			}
			else
			{
				$pagetemplateid = $pageTemplates[vB_Page::TEMPLATE_CHANNEL];
			}
			$parentid = (int) $channel['page_parentid'];

			// Create phrases for page of channel title
			$guid = vB_Xml_Export_Page::createGUID(array());
			$guidforphrase = vB_Library::instance('phrase')->cleanGuidForPhrase($guid);
			$phraseLib->save('pagemeta',
				'page_' . $guidforphrase . '_title',
				array(
					'text' => array($channel['title']),
					'ismaster' => 1,
					'product' => 'vbulletin',
					't' => 0,
					'oldvarname' => 'page_' . $guidforphrase . '_title',
					'oldfieldname' => 'global',
				)
			);
			$phraseLib->save('pagemeta',
				'page_' . $guidforphrase . '_metadesc',
				array(
					'text' => array($channel['description']),
					'ismaster' => 1,
					'product' => 'vbulletin',
					't' => 0,
					'oldvarname' => 'page_' . $guidforphrase . '_metadesc',
					'oldfieldname' => 'global',
				)
			);

			$this->db->query_write("
				INSERT INTO `" . TABLE_PREFIX . "page`
				(`parentid`, `pagetemplateid`, `title`, `pagetype`, `guid`)
				VALUES
				($parentid, $pagetemplateid, '" . $this->db->escape_string($channel['title']) . "', '" . vB_Page::TYPE_CUSTOM .  "', '" . $guid . "');
			");
			$pageid = $this->db->insert_id();
			$mainpageid = $pageid;

			$newprefix = '';
			if (!empty($prefixes[$channel['parentid']]))
			{
				$newprefix .= $prefixes[$channel['parentid']] . '/';
			}
			$newprefix .= $hyphenedTitle;
			$duplicateRegex = $this->db->query_first("
				SELECT COUNT(*) AS num
				FROM " . TABLE_PREFIX . "routenew
				WHERE regex = '" . $this->db->escape_string($newprefix) . "'
			");
			if ($duplicateRegex['num'] > 0)
			{
				// we cannot have duplicate regex
				$newprefix .= $channel['nodeid'];
			}

			$prefixes[$channel['nodeid']] = $newprefix;

			// @TODO the regex below and the regex used in vB5_Route_Channel
			// should be a constant in the vB5_Route_Channel class
			// and referenced from there.
			$this->db->query_write("
				REPLACE INTO `" . TABLE_PREFIX . "routenew`
				(`prefix`, `regex`, `class`, `controller`, `action`, `template`, `arguments`, `contentid`, `guid`)
				VALUES
				('" . $this->db->escape_string($newprefix) . "', '" . $this->db->escape_string(preg_quote($newprefix)) . "(?:/page(?P<pagenum>[0-9]+))?', 'vB5_Route_Channel', 'page', 'index', '', '" . serialize(
				array(
					'channelid' => $channel['nodeid'],
					'nodeid' => $channel['nodeid'],
					'pagenum' => '$pagenum',
					'pageid' => $pageid,
				)) . "', {$channel['nodeid']}, '" . vB_Xml_Export_Route::createGUID(array()) . "');
			");
			$pageRouteId = $this->db->insert_id();

			$this->db->query_write("
				UPDATE `" . TABLE_PREFIX . "page`
				SET `routeid` = $pageRouteId
				WHERE pageid = $pageid;
			");
			$this->db->query_write("
				UPDATE `" . TABLE_PREFIX . "node`
				SET `routeid` = $pageRouteId
				WHERE nodeid = {$channel['nodeid']} AND contenttypeid = $channelContentTypeId;
			");

			// create conversation page for channels that are not a category
			if ($channel['category'] == 0)
			{
				$pagetemplateid = $pageTemplates[vB_Page::TEMPLATE_CONVERSATION];
				$parentid = $mainpageid;

				$guid = vB_Xml_Export_Page::createGUID(array());
				$guidforphrase = vB_Library::instance('phrase')->cleanGuidForPhrase($guid);
				$phraseLib->save('pagemeta',
					'page_' . $guidforphrase . '_title',
					array(
						'text' => array($channel['title']),
						'ismaster' => 1,
						'product' => 'vbulletin',
						't' => 0,
						'oldvarname' => 'page_' . $guidforphrase . '_title',
						'oldfieldname' => 'global',
					)
				);
				$phraseLib->save('pagemeta',
					'page_' . $guidforphrase . '_metadesc',
					array(
						'text' => array($channel['description']),
						'ismaster' => 1,
						'product' => 'vbulletin',
						't' => 0,
						'oldvarname' => 'page_' . $guidforphrase . '_metadesc',
						'oldfieldname' => 'global',
					)
				);

				$this->db->query_write("
					REPLACE INTO `" . TABLE_PREFIX . "page`
					(`parentid`, `pagetemplateid`, `title`, `pagetype`, `guid`)
					VALUES
					($parentid, $pagetemplateid, '" . $this->db->escape_string($channel['title']) . "', '" . vB_Page::TYPE_DEFAULT .  "', '" . $guid . "');
				");
				$pageid = $this->db->insert_id();

				// TODO:
				// This may need to a check for conversation vs article route in the future.
				// Just putting in a minimal-change fix for now, VBV-11356
				$regex = $this->db->escape_string(preg_quote($newprefix) . '/' . vB5_Route_Conversation::REGEXP);
				$this->db->query_write("
					REPLACE INTO `" . TABLE_PREFIX . "routenew`
					(`prefix`, `regex`, `class`, `controller`, `action`, `template`, `arguments`, `contentid`, `guid`)
					VALUES
					('" . $this->db->escape_string($newprefix) . "', '$regex',
					'vB5_Route_Conversation', 'page', 'index', '', '" . serialize(
					array(
						'nodeid'         => '$nodeid',
						'pagenum'        => '$pagenum',
						'contentpagenum' => '$contentpagenum',
						'channelid'      => $channel['nodeid'],
						'pageid'         => $pageid,
					)) . "', {$channel['nodeid']}, '" . vB_Xml_Export_Route::createGUID(array()) . "');
				");
				$pageRouteId = $this->db->insert_id();
				$this->db->query_write("
					UPDATE `" . TABLE_PREFIX . "page`
					SET `routeid` = $pageRouteId
					WHERE pageid = $pageid;
				");
				$this->db->query_write("
					UPDATE `" . TABLE_PREFIX . "node`
					SET `routeid` = $pageRouteId
					WHERE parentid = {$channel['nodeid']} AND contenttypeid = $textTypeId;
				");
			}

			// add subchannels to queue
			$subChannelResult = $this->db->query_read($q = "
				SELECT nodeid, title, parentid
				FROM " . TABLE_PREFIX . "node
				WHERE
					parentid = {$channel['nodeid']}
					AND
					contenttypeid = $channelContentTypeId
					AND
					routeid = 0
			");

			$subChannels = array();
			while ($subChannel = $this->db->fetch_array($subChannelResult))
			{
				$subChannel['page_parentid'] = $mainpageid;
				$queue[] = $subChannel;
			}
		}
	}

	/**
	 * 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)
	{
		//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)
		{
			$this->show_message($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.
			return array('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);
			$this->show_message($message);
			$this->show_message($this->phrase['core']['skipping_try_again']);
			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);
			$info['output'] = vB_String::toCharset($info['output'], $languageid['charset'], vB_Template_Runtime::fetchStyleVar('charset'));

			$this->show_message($info['output']);
			// startat has no meaning in this step. We just use it to keep this going until the theme importer has finished.
			return array('startat' => ++$startat);
		}
		else
		{
			$this->show_message($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['tmdate'] = 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);
		}
	}

	/**
	 * Step #20 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_20($data = null)
	{
		//defines $upgrade_options['template_merge'] array -- defined in a seperate file for easy editing.
		require(DIR . '/install/upgrade_options.php');

		if (!empty($upgrade_options['template_merge']['skip']))
		{
			$this->show_message($this->phrase['core']['skipping_due_to_option']);
			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);
		}
	}
}

/*=========================================================================*\
|| #######################################################################
|| # Downloaded: 05:43, Thu May 26th 2016
|| # CVS: $RCSfile$ - $Revision: 84859 $
|| #######################################################################
\*=========================================================================*/
