/**
* Installs a product from the xml text
*
* This function depends on the vb class loader, which requires that the
* framework init is called.
*
* @return bool True if the product requires a template merge, false otherwise
*/
function install_product($xml, $allow_overwrite = false, $verbose = true)
{
    global $vbphrase;
    global $vbulletin;
    $assertor = vB::getDbAssertor();
    require_once DIR . '/includes/class_bitfield_builder.php';
    require_once DIR . '/includes/class_xml.php';
    //share some code with the main xml style import
    require_once DIR . '/includes/adminfunctions_template.php';
    if ($verbose) {
        print_dots_start('<b>' . $vbphrase['importing_product'] . "</b>, {$vbphrase['please_wait']}", ':', 'dspan');
    }
    $xmlobj = new vB_XML_Parser($xml);
    if ($xmlobj->error_no() == 1) {
        if ($verbose) {
            print_dots_stop();
        }
        throw new vB_Exception_AdminStopMessage('no_xml_and_no_path');
    }
    if (!($arr = $xmlobj->parse())) {
        if ($verbose) {
            print_dots_stop();
        }
        throw new vB_Exception_AdminStopMessage(array('xml_error_x_at_line_y', $xmlobj->error_string(), $xmlobj->error_line()));
    }
    // ############## general product information
    $info = array('productid' => substr(preg_replace('#[^a-z0-9_]#', '', strtolower($arr['productid'])), 0, 25), 'title' => $arr['title'], 'description' => $arr['description'], 'version' => $arr['version'], 'active' => $arr['active'], 'url' => $arr['url'], 'versioncheckurl' => $arr['versioncheckurl']);
    if (!$info['productid']) {
        if ($verbose) {
            print_dots_stop();
        }
        throw new vB_Exception_AdminStopMessage('invalid_file_specified');
    }
    if (strtolower($info['productid']) == 'vbulletin') {
        if ($verbose) {
            print_dots_stop();
        }
        throw new vB_Exception_AdminStopMessage(array('product_x_installed_no_overwrite', 'vBulletin'));
    }
    // check for bitfield conflicts on install
    $bitfields = vB_Bitfield_Builder::return_data();
    if (!$bitfields) {
        $bfobj =& vB_Bitfield_Builder::init();
        if ($bfobj->errors) {
            if ($verbose) {
                print_dots_stop();
            }
            throw new vB_Exception_AdminStopMessage(array('bitfield_conflicts_x', '<li>' . implode('</li><li>', $bfobj->errors) . '</li>'));
        }
    }
    // get system version info
    $system_versions = array('php' => PHP_VERSION, 'vbulletin' => $vbulletin->options['templateversion'], 'products' => fetch_product_list(true));
    $mysql_version = $assertor->getRow('mysqlVersion');
    $system_versions['mysql'] = $mysql_version['version'];
    // ############## import dependencies
    if (isset($arr['dependencies']['dependency']) and is_array($arr['dependencies']['dependency'])) {
        $dependencies =& $arr['dependencies']['dependency'];
        if (!isset($dependencies[0])) {
            $dependencies = array($dependencies);
        }
        $dependency_errors = array();
        $ignore_dependency_errors = array();
        // let's check the dependencies
        foreach ($dependencies as $dependency) {
            // if we get an error, we haven't met this dependency
            // if we go through without a problem, we have automatically met
            // all dependencies for this "class" (mysql, php, vb, a specific product, etc)
            $this_dependency_met = true;
            // build a phrase for the version compats -- will look like (minver / maxver)
            if ($dependency['minversion']) {
                $compatible_phrase = construct_phrase($vbphrase['compatible_starting_with_x'], htmlspecialchars_uni($dependency['minversion']));
            } else {
                $compatible_phrase = '';
            }
            if ($dependency['maxversion']) {
                $incompatible_phrase = construct_phrase($vbphrase['incompatible_with_x_and_greater'], htmlspecialchars_uni($dependency['maxversion']));
            } else {
                $incompatible_phrase = '';
            }
            if ($compatible_phrase or $incompatible_phrase) {
                $required_version_info = "({$compatible_phrase}";
                if ($compatible_phrase and $incompatible_phrase) {
                    $required_version_info .= ' / ';
                }
                $required_version_info .= "{$incompatible_phrase})";
            }
            // grab the appropriate installed version string
            if ($dependency['dependencytype'] == 'product') {
                // group dependencies into types -- individual products get their own group
                $dependency_type_key = "product-{$dependency['parentproductid']}";
                // undocumented feature -- you can put a producttitle attribute in a dependency so the id isn't displayed
                $parent_product_title = !empty($dependency['producttitle']) ? $dependency['producttitle'] : $dependency['parentproductid'];
                $parent_product = $system_versions['products']["{$dependency['parentproductid']}"];
                if (!$parent_product) {
                    // required product is not installed
                    $dependency_errors["{$dependency_type_key}"] = construct_phrase($vbphrase['product_x_must_be_installed'], htmlspecialchars_uni($parent_product_title), $required_version_info);
                    continue;
                    // can't do version checks if the product isn't installed
                } else {
                    if ($parent_product['active'] == 0) {
                        // product is installed, but inactive
                        $dependency_errors["{$dependency_type_key}-inactive"] = construct_phrase($vbphrase['product_x_must_be_activated'], htmlspecialchars_uni($parent_product_title));
                        $this_dependency_met = false;
                        // allow version checks to continue
                    }
                }
                $sys_version_str = $parent_product['version'];
                $version_incompatible_phrase = 'product_incompatible_version_x_product_y';
            } else {
                $dependency_type_key = $dependency['dependencytype'];
                $parent_product_title = '';
                $sys_version_str = $system_versions["{$dependency['dependencytype']}"];
                $version_incompatible_phrase = 'product_incompatible_version_x_' . $dependency['dependencytype'];
            }
            // if no version string, we are trying to do an unsupported dep check
            if ($sys_version_str == '') {
                continue;
            }
            $sys_version = fetch_version_array($sys_version_str);
            // error if installed version < minversion
            if ($dependency['minversion']) {
                $dep_version = fetch_version_array($dependency['minversion']);
                for ($i = 0; $i <= 5; $i++) {
                    if ($sys_version["{$i}"] < $dep_version["{$i}"]) {
                        // installed version is too old
                        $dependency_errors["{$dependency_type_key}"] = construct_phrase($vbphrase["{$version_incompatible_phrase}"], htmlspecialchars_uni($sys_version_str), $required_version_info, $parent_product_title);
                        $this_dependency_met = false;
                        break;
                    } else {
                        if ($sys_version["{$i}"] > $dep_version["{$i}"]) {
                            break;
                        }
                    }
                }
            }
            // error if installed version >= maxversion
            if ($dependency['maxversion']) {
                $dep_version = fetch_version_array($dependency['maxversion']);
                $all_equal = true;
                for ($i = 0; $i <= 5; $i++) {
                    if ($sys_version["{$i}"] > $dep_version["{$i}"]) {
                        // installed version is newer than the maxversion
                        $dependency_errors["{$dependency_type_key}"] = construct_phrase($vbphrase["{$version_incompatible_phrase}"], htmlspecialchars_uni($sys_version_str), $required_version_info, $parent_product_title);
                        $this_dependency_met = false;
                        break;
                    } else {
                        if ($sys_version["{$i}"] < $dep_version["{$i}"]) {
                            // not every part is the same and since we've got less we can exit
                            $all_equal = false;
                            break;
                        } else {
                            if ($sys_version["{$i}"] != $dep_version["{$i}"]) {
                                // not every part is the same
                                $all_equal = false;
                            }
                        }
                    }
                }
                if ($all_equal == true) {
                    // installed version is same as the max version, which is the first incompat version
                    $dependency_errors["{$dependency_type_key}"] = construct_phrase($vbphrase["{$version_incompatible_phrase}"], htmlspecialchars_uni($sys_version_str), $required_version_info, $parent_product_title);
                    $this_dependency_met = false;
                }
            }
            if ($this_dependency_met) {
                // we met 1 dependency for this type -- this emulates or'ing together groups
                $ignore_dependency_errors["{$dependency_type_key}"] = true;
            }
        }
        // for any group we met a dependency for, ignore any errors we might
        // have gotten for the group
        foreach ($ignore_dependency_errors as $dependency_type_key => $devnull) {
            unset($dependency_errors["{$dependency_type_key}"]);
        }
        if ($dependency_errors) {
            $dependency_errors = array_unique($dependency_errors);
            $dependency_errors = '<ol><li>' . implode('</li><li>', $dependency_errors) . '</li></ol>';
            if ($verbose) {
                print_dots_stop();
            }
            throw new vB_Exception_AdminStopMessage(array('dependencies_not_met_x', $dependency_errors));
        }
    }
    // look to see if we already have this product installed
    if ($existingprod = $assertor->getRow('product', array('productid' => $info['productid']))) {
        if (!$allow_overwrite) {
            if ($verbose) {
                print_dots_stop();
            }
            throw new vB_Exception_AdminStopMessage(array('product_x_installed_no_overwrite', $info['title']));
        }
        $active = $existingprod['active'];
        // not sure what we're deleting, so rebuild everything
        $rebuild = array('templates' => true, 'hooks' => true, 'phrases' => true, 'options' => true, 'cron' => true);
        $installed_version = $existingprod['version'];
    } else {
        $active = $info['active'] ? 1 : 0;
        $rebuild = array('templates' => false, 'hooks' => false, 'phrases' => false, 'options' => false, 'cron' => false);
        $installed_version = null;
    }
    // ############## import install/uninstall code
    if (isset($arr['codes']['code']) and is_array($arr['codes']['code'])) {
        $codes =& $arr['codes']['code'];
        if (!isset($codes[0])) {
            $codes = array($codes);
        }
        // run each of the codes
        foreach ($codes as $code) {
            // Run if: code version is * (meaning always run), no version
            //		previously installed, or if the code is for a newer version
            //		than is currently installed
            if ($code['version'] == '*' or $installed_version === null or is_newer_version($code['version'], $installed_version)) {
                eval($code['installcode']);
            }
        }
        // Clear routes from datastore
        build_datastore('routes', serialize(array()), 1);
        //assume that the product may have installed content types and purge the content type cache
        vB_Cache::instance()->purge('vb_types.types');
    }
    // dependencies checked, install code run. Now clear out the old product info;
    // settings should be retained in memory already
    delete_product($info['productid'], false, true);
    if (is_array($codes)) {
        // we've now run all the codes, if execution is still going
        // then it's going to complete fully, so insert the codes
        $productCodes = array();
        foreach ($codes as $code) {
            /* insert query */
            $productCodes[] = array('productid' => $info['productid'], 'version' => $code['version'], 'installcode' => $code['installcode'], 'uninstallcode' => $code['uninstallcode']);
        }
        $assertor->insertMultiple('productcode', array('productid', 'version', 'installcode', 'uninstallcode'), $productCodes);
    }
    if (is_array($dependencies)) {
        // dependencies met, codes run -- now we can insert the dependencies into the DB
        $productDependencies = array();
        foreach ($dependencies as $dependency) {
            /* insert query */
            $productDependencies[] = array('productid' => $info['productid'], 'dependencytype' => $dependency['dependencytype'], 'parentproductid' => $dependency['parentproductid'], 'minversion' => $dependency['minversion'], 'maxversion' => $dependency['maxversion']);
        }
        $assertor->insertMultiple('productdependency', array('productid', 'dependencytype', 'parentproductid', 'minversion', 'maxversion'), $productDependencies);
    }
    /* insert query */
    $assertor->insert('product', array('productid' => $info['productid'], 'title' => $info['title'], 'description' => $info['description'], 'version' => $info['version'], 'active' => intval($active), 'url' => $info['url'], 'versioncheckurl' => $info['versioncheckurl']));
    // ############## import templates
    if (isset($arr['templates']['template']) and is_array($arr['templates']['template'])) {
        $querybits = array();
        $querytemplates = 0;
        $templates =& $arr['templates']['template'];
        if (!isset($templates[0])) {
            $templates = array($templates);
        }
        foreach ($templates as $template) {
            $title = $template['name'];
            $template['template'] = $template['value'];
            $template['username'] = $template['username'];
            $template['templatetype'] = $template['templatetype'];
            $template['date'] = intval($template['date']);
            if ($template['templatetype'] != 'template') {
                // template is a special template
                $querybits[] = array('styleid' => -1, 'templatetype' => $template['templatetype'], 'title' => $title, 'template' => $template['template'], 'template_un' => '', 'dateline' => $template['date'], 'username' => $template['username'], 'version' => $template['version'], 'product' => $info['productid']);
            } else {
                // template is a standard template
                $querybits[] = array('styleid' => -1, 'templatetype' => $template['templatetype'], 'title' => $title, 'template' => compile_template($template['value']), 'template_un' => $template['template'], 'dateline' => $template['date'], 'username' => $template['username'], 'version' => $template['version'], 'product' => $info['productid']);
            }
            if (++$querytemplates % 20 == 0) {
                /*insert query*/
                $assertor->assertQuery('replaceValues', array('values' => $querybits, 'table' => 'template'));
                $querybits = array();
            }
            // Send some output to the browser inside this loop so certain hosts
            // don't artificially kill the script. See bug #34585
            if (VB_AREA != 'Upgrade' and VB_AREA != 'Install') {
                echo ' ';
                vbflush();
            }
        }
        // insert any remaining templates
        if (!empty($querybits)) {
            /*insert query*/
            $assertor->assertQuery('replaceValues', array('values' => $querybits, 'table' => 'template'));
        }
        unset($querybits);
        $rebuild['templates'] = true;
    }
    // ############## import stylevars
    if (isset($arr['stylevardfns']['stylevargroup']) and is_array($arr['stylevardfns']['stylevargroup'])) {
        xml_import_stylevar_definitions($arr['stylevardfns'], $info['productid']);
    }
    if (!empty($arr['stylevars']) and is_array($arr['stylevars']) and is_array($arr['stylevars']['stylevar'])) {
        xml_import_stylevars($arr['stylevars'], -1);
    }
    // ############## import hooks
    if (isset($arr['hooks']['hook']) and is_array($arr['hooks']['hook'])) {
        $hooks =& $arr['hooks']['hook'];
        if (!isset($hooks[0])) {
            $hooks = array($hooks);
        }
        foreach ($hooks as $hook) {
            $hook['product'] = $info['productid'];
            $assertor->insert('hook', $hook);
        }
        $rebuild['hooks'] = true;
    }
    // ############## import phrases
    if (isset($arr['phrases']['phrasetype']) and is_array($arr['phrases']['phrasetype'])) {
        require_once DIR . '/includes/adminfunctions_language.php';
        $master_phrasetypes = array();
        $master_phrasefields = array();
        foreach (vB_Api::instanceInternal('phrase')->fetch_phrasetypes(false) as $phrasetype) {
            $master_phrasefields["{$phrasetype['fieldname']}"] = true;
        }
        $phrasetypes =& $arr['phrases']['phrasetype'];
        if (!isset($phrasetypes[0])) {
            $phrasetypes = array($phrasetypes);
        }
        foreach ($phrasetypes as $phrasetype) {
            if (empty($phrasetype['phrase'])) {
                continue;
            }
            if ($phrasetype['fieldname'] == '' or !preg_match('#^[a-z0-9_]+$#i', $phrasetype['fieldname'])) {
                continue;
            }
            $fieldname = $master_phrasefields["{$phrasetype['fieldname']}"];
            if (!$fieldname) {
                $assertor->assertQuery('installProductPhraseTypeInsert', array('fieldname' => $phrasetype['fieldname'], 'title' => $phrasetype['name'], 'editrows' => 3, 'product' => $info['productid']));
                // need to add the column to the language table as well
                $assertor->assertQuery('addLanguageFromPackage', array('fieldname' => $phrasetype['fieldname']));
            }
            $phrases =& $phrasetype['phrase'];
            if (!isset($phrases[0])) {
                $phrases = array($phrases);
            }
            $sql = array();
            foreach ($phrases as $phrase) {
                $sql[] = array('languageid' => -1, 'fieldname' => $phrasetype['fieldname'], 'varname' => $phrase['name'], 'text' => $phrase['value'], 'product' => $info['productid'], 'username' => $phrase['username'], 'dateline' => $phrase['date'], 'version' => $phrase['version']);
            }
            /*insert query*/
            $assertor->assertQuery('replaceValues', array('values' => $sql, 'table' => 'phrase'));
        }
        $rebuild['phrases'] = true;
    }
    // ############## import settings
    if (isset($arr['options']['settinggroup']) and is_array($arr['options']['settinggroup'])) {
        $settinggroups =& $arr['options']['settinggroup'];
        if (!isset($settinggroups[0])) {
            $settinggroups = array($settinggroups);
        }
        foreach ($settinggroups as $group) {
            if (empty($group['setting'])) {
                continue;
            }
            // create the setting group if it doesn't already exist
            $check = $assertor->assertQuery('settinggroup', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'grouptitle' => $group['name']));
            if ($check->valid()) {
                $current = $check->current();
                if ($group['adminperm'] != $current['adminperm'] or $group['displayorder'] != $current['displayorder']) {
                    $assertor->assertQuery('settinggroup', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_UPDATE, 'grouptitle' => $group['name'], 'displayorder' => $group['displayorder'], 'adminperm' => $group['adminperm']));
                }
            } else {
                /*insert query*/
                $assertor->assertQuery('settinggroup', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_INSERTIGNORE, 'grouptitle' => $group['name'], 'displayorder' => $group['displayorder'], 'volatile' => 1, 'product' => $info['productid'], 'adminperm' => $group['adminperm']));
            }
            $settings =& $group['setting'];
            if (!isset($settings[0])) {
                $settings = array($settings);
            }
            $setting_bits = array();
            foreach ($settings as $setting) {
                if (isset($vbulletin->options["{$setting['varname']}"])) {
                    $newvalue = $vbulletin->options["{$setting['varname']}"];
                } else {
                    $newvalue = $setting['defaultvalue'];
                }
                $setting_bits[] = array('varname' => $setting['varname'], 'grouptitle' => $group['name'], 'value' => trim($newvalue), 'defaultvalue' => trim($setting['defaultvalue']), 'datatype' => trim($setting['datatype']), 'optioncode' => $setting['optioncode'], 'displayorder' => $setting['displayorder'], 'advanced' => intval($setting['advanced']), 'volatile' => 1, 'validationcode' => $setting['validationcode'], 'blacklist' => $setting['blacklist'], 'ispublic' => intval($setting['public']), 'product' => $info['productid'], 'adminperm' => empty($setting['adminperm']) ? '' : $setting['adminperm']);
            }
            /*insert query*/
            $assertor->assertQuery('replaceValues', array('values' => $setting_bits, 'table' => 'setting'));
        }
        $rebuild['options'] = true;
    }
    // ############## import admin help
    if (isset($arr['helptopics']['helpscript']) and is_array($arr['helptopics']['helpscript'])) {
        $help_scripts =& $arr['helptopics']['helpscript'];
        if (!isset($help_scripts[0])) {
            $help_scripts = array($help_scripts);
        }
        foreach ($help_scripts as $help_script) {
            // Deal with single entry
            if (!is_array($help_script['helptopic'][0])) {
                $help_script['helptopic'] = array($help_script['helptopic']);
            }
            $help_sql = array();
            foreach ($help_script['helptopic'] as $topic) {
                $helpsql[] = array('script' => $help_script['name'], 'action' => $topic['act'], 'optionname' => $topic['opt'], 'displayorder' => intval($topic['disp']), 'volatile' => 1, 'product' => $info['productid']);
            }
            if (!empty($helpsql)) {
                /*insert query*/
                $assertor->assertQuery('replaceValues', array('values' => $helpsql, 'table' => 'adminhelp'));
            }
        }
    }
    // ############## import cron
    if (isset($arr['cronentries']['cron']) and is_array($arr['cronentries']['cron'])) {
        require_once DIR . '/includes/functions_cron.php';
        $cron_entries =& $arr['cronentries']['cron'];
        if (!isset($cron_entries[0])) {
            $cron_entries = array($cron_entries);
        }
        foreach ($cron_entries as $cron) {
            $cron['varname'] = preg_replace('#[^a-z0-9_]#i', '', $cron['varname']);
            if (!$cron['varname']) {
                continue;
            }
            $cron['active'] = $cron['active'] ? 1 : 0;
            $cron['loglevel'] = $cron['loglevel'] ? 1 : 0;
            $scheduling = $cron['scheduling'];
            $scheduling['weekday'] = intval($scheduling['weekday']);
            $scheduling['day'] = intval($scheduling['day']);
            $scheduling['hour'] = intval($scheduling['hour']);
            $scheduling['minute'] = explode(',', preg_replace('#[^0-9,-]#i', '', $scheduling['minute']));
            if (count($scheduling['minute']) == 0) {
                $scheduling['minute'] = array(0);
            } else {
                $scheduling['minute'] = array_map('intval', $scheduling['minute']);
            }
            /*insert query*/
            $cronSql[] = array('weekday' => $scheduling['weekday'], 'day' => $scheduling['day'], 'hour' => $scheduling['hour'], 'minute' => serialize($scheduling['minute']), 'filename' => $cron['filename'], 'loglevel' => $cron['loglevel'], 'active' => $cron['active'], 'varname' => $cron['varname'], 'volatile' => 1, 'product' => $info['productid']);
            $cronid = $assertor->assertQuery('replaceValues', array('values' => $cronSql, 'table' => 'cron', 'returnId' => true));
            if ($cronid) {
                build_cron_item($cronid);
            }
            $rebuild['cron'] = true;
        }
    }
    // ############## import faq
    if (isset($arr['faqentries']['faq']) and is_array($arr['faqentries']['faq'])) {
        $faq_entries =& $arr['faqentries']['faq'];
        if (!isset($faq_entries[0])) {
            $faq_entries = array($faq_entries);
        }
        $sql = array();
        foreach ($faq_entries as $faq) {
            $sql[] = array('faqname' => $faq['faqname'], 'faqparent' => $faq['faqparent'], 'displayorder' => intval($faq['displayorder']), 'volatile' => 1, 'product' => $info['productid']);
        }
        if ($sql) {
            /*insert query*/
            $assertor->assertQuery('replaceValues', array('values' => $sql, 'table' => 'faq'));
        }
    }
    // ############## import widgets
    if (isset($arr['widgets']['widget']) and is_array($arr['widgets']['widget'])) {
        $widgetImporter = new vB_Xml_Import_Widget($info['productid']);
        $widgetImporter->importFromParsedXML($arr['widgets']);
    }
    // ############## import pagetemplates
    if (isset($arr['pagetemplates']['pagetemplate']) and is_array($arr['pagetemplates']['pagetemplate'])) {
        $pageTemplateImporter = new vB_Xml_Import_PageTemplate($info['productid']);
        $pageTemplateImporter->importFromParsedXML($arr['pagetemplates']);
    }
    // ############## import page
    if (isset($arr['pages']['page']) and is_array($arr['pages']['page'])) {
        $pageImporter = new vB_Xml_Import_Page($info['productid']);
        $pageImporter->importFromParsedXML($arr['pages']);
    }
    // ############## import channels
    if (isset($arr['channels']['channel']) and is_array($arr['channels']['channel'])) {
        $channelImporter = new vB_Xml_Import_Channel($info['productid']);
        $channelImporter->importFromParsedXML($arr['channels']);
    }
    // ############## import routes
    if (isset($arr['routes']['route']) and is_array($arr['routes']['route'])) {
        $routeImporter = new vB_Xml_Import_Route($info['productid']);
        $routeImporter->importFromParsedXML($arr['routes']);
    }
    if (isset($routeImporter)) {
        // update pages and channels with new route ids
        if (isset($pageImporter)) {
            $pageImporter->updatePageRoutes();
        }
        if (isset($channelImporter)) {
            $channelImporter->updateChannelRoutes();
        }
    }
    // Check if the hook system is disabled. If it is, enable it.
    if (!$vbulletin->options['enablehooks']) {
        $assertor->update('setting', array('value' => 1), array('varname' => 'enablehooks'));
        $rebuild['options'] = true;
    }
    // Now rebuild everything we need...
    if ($rebuild['hooks']) {
        vB_Api::instanceInternal("Hook")->buildHookDatastore();
    }
    if ($rebuild['templates']) {
        if ($error = build_all_styles(0, 0, '', false, $verbose)) {
            return $error;
        }
    }
    if ($rebuild['phrases']) {
        require_once DIR . '/includes/adminfunctions_language.php';
        build_language();
    }
    if ($rebuild['options']) {
        vB::getDatastore()->build_options();
    }
    if ($rebuild['cron']) {
        require_once DIR . '/includes/functions_cron.php';
        build_cron_next_run();
    }
    build_product_datastore();
    // build bitfields to remove/add this products bitfields
    vB_Bitfield_Builder::save();
    if ($verbose) {
        print_dots_stop();
    }
    $info['need_merge'] = ($rebuild['templates'] and $installed_version);
    return $info;
}
 /**
  * Restores a page.
  *
  * Specifically, this restores the page, page template, and route
  * information so it reflects the values present at initial install. This is meant
  * to be used only on the vBulletin default pages.
  *
  * @param  string Page GUID
  * @param  bool   Print the Page title or not
  */
 public function restorePage($guid, $printMessage = true)
 {
     $xmlpage = $this->xml['page'][$guid];
     $xmlroute = $this->getXmlRouteByPageGuid($xmlpage['guid']);
     $xmlpagetemplate = $this->getXmlPageTemplateByPageGuid($xmlpage['guid']);
     $dbpage = $this->getMatchingPageFromDbByXmlGuid($xmlpage['guid']);
     $dbroute = $this->getDbRouteByRouteId($dbpage['routeid']);
     $dbpagetemplate = $this->getDbPageTemplateByPageTemplateId($dbpage['pagetemplateid']);
     if ($printMessage) {
         echo $xmlpage['title'];
     }
     // delete existing records
     $this->assertor->delete('page', array('guid' => $xmlpage['guid']));
     $this->assertor->delete('pagetemplate', array('guid' => $xmlpagetemplate['guid']));
     $this->assertor->delete('routenew', array('guid' => $xmlroute['guid']));
     // remove name from current db route so xml route can be restored w/o an index conflict
     if ($dbroute['guid'] != $xmlroute['guid']) {
         $this->assertor->update('routenew', array('name' => vB_dB_Query::VALUE_ISNULL), array('routeid' => $dbroute['routeid']));
     }
     // restore pagetemplate record
     $options = vB_Xml_Import::OPTION_OVERWRITE;
     $xml_importer = new vB_Xml_Import_PageTemplate('vbulletin', $options);
     $xml_importer->importFromFile("{$this->xmldir}/vbulletin-pagetemplates.xml", $xmlpagetemplate['guid']);
     $xml_importer->replacePhrasePlaceholdersInWidgetConfigs();
     // restore page record
     $options = vB_Xml_Import::OPTION_OVERWRITE;
     $xml_importer = new vB_Xml_Import_Page('vbulletin', $options);
     $xml_importer->importFromFile("{$this->xmldir}/vbulletin-pages.xml", $xmlpage['guid']);
     // restore route record
     $options = vB_Xml_Import::OPTION_OVERWRITE;
     $xml_importer = new vB_Xml_Import_Route('vbulletin', $options);
     $xml_importer->importFromFile("{$this->xmldir}/vbulletin-routes.xml", $xmlroute['guid']);
     // update page route
     $xml_importer = new vB_Xml_Import_Page('vbulletin', 0);
     $parsedXML = $xml_importer->parseFile("{$this->xmldir}/vbulletin-pages.xml");
     $xml_importer->updatePageRoutes($parsedXML);
     // get the new route
     $newRoute = $this->assertor->getRow('routenew', array('guid' => $xmlroute['guid']));
     // set previous db route to 301 redirect
     if ($dbroute['guid'] != $xmlroute['guid']) {
         $this->assertor->update('routenew', array('redirect301' => $newRoute['routeid']), array('routeid' => $dbroute['routeid']));
     }
     // update node routeid
     $this->assertor->update('vbForum:node', array('routeid' => $newRoute['routeid']), array('routeid' => $dbroute['routeid']));
     // clear cache
     vB_Cache::resetAllCache();
 }