Example #1
0
 /**
  * Clones a page template with its widgets and returns the new page template id.
  * @param int $pageTemplateId
  * @return int
  */
 public static function clonePageTemplate($pageTemplateId)
 {
     if (!($templatePage = vB_Api::instanceInternal('pagetemplate')->fetchPageTemplateById($pageTemplateId))) {
         throw new Exception('Cannot find pagetemplate');
     }
     $db = vB::getDbAssertor();
     // clone page template
     $newTemplateId = $db->insert('pagetemplate', array('title' => 'Clone of ' . $templatePage['title'], 'screenlayoutid' => $templatePage['screenlayoutid'], 'guid' => vB_Xml_Export_PageTemplate::createGUID($templatePage)));
     if (is_array($newTemplateId)) {
         $newTemplateId = (int) array_pop($newTemplateId);
     }
     // clone widgets
     $widgets = $db->getRows('widgetinstance', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, vB_dB_Query::CONDITIONS_KEY => array('pagetemplateid' => $pageTemplateId)));
     foreach ($widgets as $widget) {
         unset($widget['widgetinstanceid']);
         $widget['pagetemplateid'] = $newTemplateId;
         $db->insert('widgetinstance', $widget);
     }
     return $newTemplateId;
 }
Example #2
0
 /**
  * Saves a page based on page editor info
  * @param 	mixed	$input
  * @return 	mixed	array
  * 	success boolean
  * 	url string -- DEPRECATED this will not always be correct due to the lack of complete route data.  See the action savePage
  * 		in the front end controller for a way to generate the correct url for the updated page
  * 	pageid int -- the pageid for the update or created page
  */
 public function pageSave($input)
 {
     $this->checkHasAdminPermission('canusesitebuilder');
     /* Sample input
     		Array
     		(
     			[pageid] => 1,
     			[screenlayoutid] => 2,
     			[displaysections[0 => [{"widgetId":"3","widgetInstanceId":"1"},{"widgetId":"4","widgetInstanceId":"2"}],
     			[displaysections[1 => [{"widgetId":"1","widgetInstanceId":"3"},{"widgetId":"2","widgetInstanceId":"4"}],
     			[pagetitle] => Forums,
     			[resturl] => forums,
     			[pagetemplateid] => 0,	// 0 if we are saving the page template as a new page template
     			[templatetitle] => Name,
     			[btnSaveEditPage] =>
     		)
     		*/
     $done = false;
     $i = 0;
     $displaysections = array();
     foreach ($input as $key => $value) {
         if (!empty($value) and preg_match('/^displaysections\\[([0-9]+)$/i', $key, $matches)) {
             $displaysection_value = json_decode($value, true);
             if (!empty($displaysection_value)) {
                 $displaysections[$matches[1]] = $displaysection_value;
             }
         }
     }
     // TODO: apparently JQuery will send POST data using the UTF-8 charset,
     // so we don't convert the resturl. However, if the url can be edited from anywhere
     // else than Site Builder, we'll need to convert it properly to ensure that the
     // route table gets the proper UTF-8 characters saved.
     // cleaning input
     $input = array('pagetitle' => trim(strval($input['pagetitle'])), 'resturl' => trim(strval($input['resturl']), " \t\n\r\v/"), 'pageid' => intval($input['pageid']), 'nodeid' => intval($input['nodeid']), 'userid' => intval($input['userid']), 'pagetemplateid' => intval($input['pagetemplateid']), 'templatetitle' => trim(strval($input['templatetitle'])), 'screenlayoutid' => intval($input['screenlayoutid']), 'displaysections' => $displaysections, 'metadescription' => trim(strval($input['metadescription'])));
     // we need to check that resturl does not contain any reserved characters.
     if (!$this->checkCustomUrl($input['resturl'])) {
         throw new vB_Exception_Api('invalid_custom_url', vB_String::INVALID_CUSTOM_URL_CHAR);
     }
     if (empty($input['pagetitle'])) {
         throw new vB_Exception_Api('page_title_cannot_be_empty');
     }
     if (empty($input['templatetitle']) and $input['pagetemplateid'] < 1) {
         throw new vB_Exception_Api('page_template_title_cannot_be_empty');
     }
     if ($input['screenlayoutid'] < 1) {
         throw new vB_Exception_Api('you_must_specify_a_screen_layout');
     }
     $this->db = vB::getDbAssertor();
     // --- save the page template ----------------------------
     // get page info
     $forceNewPage = false;
     /* if prefix is modified, we need to create a new page, pagetemplate and widgets */
     $isPrefixUsed = false;
     if ($input['pageid'] > 0) {
         $page = $this->fetchPageById($input['pageid'], array('nodeid' => $input['nodeid'], 'userid' => $input['userid']));
         if (!is_array($page)) {
             $page = array();
         } else {
             $forceNewPage = ($page['isgeneric'] and $input['resturl'] != $page['urlprefix']);
             // if we are modifying a page url, we need to check the new url...
             if ($input['resturl'] != $page['urlprefix']) {
                 $isPrefixUsed = vB5_Route::isPrefixUsed($input['resturl'], $page['routeid']);
             }
         }
     } else {
         // if it is a new page, we need to check the url
         $isPrefixUsed = vB5_Route::isPrefixUsed($input['resturl']);
         $page = array();
     }
     $routeApi = vB_Api::instanceInternal('route');
     // if the used prefix is a 301 to another route, then we can take it.
     if ($isPrefixUsed !== FALSE and empty($isPrefixUsed['redirectRouteId'])) {
         throw new vB_Exception_Api('this_url_is_already_used');
     }
     // page template
     $valuePairs = array('title' => $input['templatetitle'], 'screenlayoutid' => $input['screenlayoutid']);
     $pagetemplateid = $input['pagetemplateid'];
     if ($pagetemplateid < 1 or $forceNewPage) {
         $valuePairs['guid'] = vB_Xml_Export_PageTemplate::createGUID($valuePairs);
         // If no widgets were configured on the page template, we won't have a page template ID.
         $pagetemplateid = $this->db->insert('pagetemplate', $valuePairs);
         if (is_array($pagetemplateid)) {
             $pagetemplateid = (int) array_pop($pagetemplateid);
         }
         $newTemplate = true;
     } else {
         $this->db->update('pagetemplate', $valuePairs, array('pagetemplateid' => $pagetemplateid));
         $newTemplate = false;
     }
     // widgets on page template
     $widgetApi = vB_Api::instanceInternal('widget');
     $currentWidgetInstances = $widgetApi->fetchWidgetInstancesByPageTemplateId($pagetemplateid);
     $currentWidgetInstanceIds = $this->getAllCurrentModuleInstances($currentWidgetInstances);
     $savedWidgetInstanceIds = array();
     $widgets = array();
     foreach ($input['displaysections'] as $displaycolumn => $columnwidgets) {
         $displayorder = 0;
         foreach ($columnwidgets as $columnwidget) {
             $columnwidgetid = intval($columnwidget['widgetId']);
             $columnwidgetinstanceid = intval($columnwidget['widgetInstanceId']);
             if (!$columnwidgetid) {
                 continue;
             }
             if ($newTemplate) {
                 $widgetInstanceId = 0;
             } else {
                 $widgetInstanceId = $columnwidgetinstanceid;
                 $savedWidgetInstanceIds[$widgetInstanceId] = $columnwidgetid;
             }
             $widget = array('widgetinstanceid' => $widgetInstanceId, 'pagetemplateid' => $pagetemplateid, 'widgetid' => $columnwidgetid, 'displaysection' => $displaycolumn, 'displayorder' => $displayorder);
             if (isset($columnwidget['subModules'])) {
                 $widget['subModules'] = $columnwidget['subModules'];
                 $widget['displaySubModules'] = $columnwidget['displaySubModules'];
                 if (!$newTemplate) {
                     $savedWidgetInstanceIds += $this->getAllSubModulesInstances($columnwidget['subModules']);
                 }
             }
             $widgets[] = $widget;
             ++$displayorder;
         }
     }
     // check we are not adding a system widget
     $newWidgets = array_diff_key($savedWidgetInstanceIds, $currentWidgetInstanceIds);
     if ($newWidgets) {
         foreach ($newWidgets as $widgetId) {
             if ($widgetApi->isSystemWidget($widgetId)) {
                 throw new vB_Exception_Api('cannot_add_system_module');
             }
         }
     }
     // check we are not removing a system widget
     $deleteWidgets = array_diff_key($currentWidgetInstanceIds, $savedWidgetInstanceIds);
     if ($deleteWidgets) {
         foreach ($deleteWidgets as $widgetId) {
             if ($widgetApi->isSystemWidget($widgetId)) {
                 throw new vB_Exception_Api('cannot_remove_system_module');
             }
         }
     }
     // save widget placements on the page template
     foreach ($widgets as $widget) {
         $widgetinstanceid = $widget['widgetinstanceid'];
         unset($widget['widgetinstanceid']);
         $subModules = isset($widget['subModules']) ? $widget['subModules'] : array();
         unset($widget['subModules']);
         $displaySubModules = isset($widget['displaySubModules']) ? $widget['displaySubModules'] : array();
         unset($widget['displaySubModules']);
         if ($widgetinstanceid > 0 and !$forceNewPage) {
             $this->db->update('widgetinstance', $widget, array('widgetinstanceid' => $widgetinstanceid));
         } else {
             $widgetinstanceid = $this->db->insert('widgetinstance', $widget);
             if (is_array($widgetinstanceid)) {
                 $widgetinstanceid = (int) array_pop($widgetinstanceid);
             }
         }
         // save submodules if available
         if (!empty($subModules)) {
             $this->saveSubModules($pagetemplateid, $widgetinstanceid, $subModules, $displaySubModules, $forceNewPage);
         }
     }
     // remove any widgets that have been removed from the page template
     if (!empty($deleteWidgets)) {
         $deleted = $widgetApi->deleteWidgetInstances(array_keys($deleteWidgets));
         if ($deleted != count($deleteWidgets)) {
             throw new vB_Exception_Api('unable_to_delete_widget_instances');
         }
     }
     // --- save the page  ---------------------------------
     // permalink
     $urlprefix = $input['resturl'];
     $valuePairs = array('pagetemplateid' => $pagetemplateid);
     // save page
     if (!empty($page) and !$forceNewPage) {
         // update page record
         $conditions = array('pageid' => $page['pageid']);
         $this->save($valuePairs, $conditions);
         $pageid = $page['pageid'];
         $guidforphrase = vB_Library::instance('phrase')->cleanGuidForPhrase($page['guid']);
         // update this page's current route if needed
         if ($input['resturl'] != $page['urlprefix']) {
             $data = array('prefix' => $urlprefix);
             if (isset($input['nodeid']) and !empty($input['nodeid'])) {
                 $data['nodeid'] = $input['nodeid'];
             }
             vB5_Route::updateRoute($page['routeid'], $data);
         }
     } else {
         $valuePairs['guid'] = vB_Xml_Export_Page::createGUID($valuePairs);
         $guidforphrase = vB_Library::instance('phrase')->cleanGuidForPhrase($valuePairs['guid']);
         // insert a new page
         $pageid = $this->save($valuePairs);
         if (is_array($pageid)) {
             $pageid = (int) array_pop($pageid);
         }
         // route
         if (isset($page['routeid'])) {
             // update this page's current route
             $data = array('pageid' => $pageid, 'prefix' => $urlprefix, 'nodeid' => $input['nodeid']);
             $routeid = vB5_Route::updateRoute($page['routeid'], $data);
         } else {
             $valuePairs = array('prefix' => $urlprefix, 'contentid' => $pageid);
             $routeid = $routeApi->createRoute('vB5_Route_Page', $valuePairs);
         }
         if (is_array($routeid)) {
             $routeid = (int) array_pop($routeid);
         }
         // update page with routeid (for deleting it when deleting a page)
         $routeApi->updateNewPageRoute($pageid, $routeid);
         // VBV-13666. Since $forceNewPage is true, we have created a new
         // page record corresponding to $pageid. But it appears that the
         // actual page being used is still the old page record. Thus we need
         // to update the routeid in the old page record so that the redirect
         // works correctly (the page route class gets the cannonical route
         // record based on the routeid in the page record). When/if we fix/change
         // the behavior for creating a new page record when updating the page
         // route prefix, this code will be wrong-- instead, we will need code
         // in the if branch above (for updating a page as opposed to creating
         // a new one), to update the routeid in the page record.
         if (!empty($page['pageid'])) {
             $routeApi->updateNewPageRoute($page['pageid'], $routeid);
         }
     }
     // Insert/Update phrases for page title, meta description.
     // Only update phrases of current language. Keep other translations.
     $phraseLib = vB_Library::instance('phrase');
     $currentlanguageid = vB::getCurrentSession()->get('languageid');
     if (empty($currentlanguageid)) {
         $currentlanguageid = vB::getDatastore()->getOption('languageid');
     }
     $translations = vB::getDbAssertor()->assertQuery('phrase', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, vB_dB_Query::CONDITIONS_KEY => array(array('field' => 'varname', 'value' => array('page_' . $guidforphrase . '_title', 'page_' . $guidforphrase . '_metadesc'), 'operator' => vB_dB_Query::OPERATOR_EQ), array('field' => 'fieldname', 'value' => 'pagemeta', 'operator' => vB_dB_Query::OPERATOR_EQ), array('field' => 'languageid', 'value' => array(-1, $currentlanguageid), 'operator' => vB_dB_Query::OPERATOR_NE))));
     $pagetrans = array('page_' . $guidforphrase . '_title' => array(), 'page_' . $guidforphrase . '_metadesc' => array());
     $hasdefault = array();
     $productid = false;
     foreach ($translations as $translation) {
         if (!$productid) {
             $productid = $translation['product'];
         }
         $pagetrans[$translation['varname']][$translation['languageid']] = $translation['text'];
         if ($translation['languageid'] == 0) {
             $hasdefault[$translation['varname']] = true;
         }
     }
     // Add input text to translates
     $pagetrans['page_' . $guidforphrase . '_title'][$currentlanguageid] = $input['pagetitle'];
     $pagetrans['page_' . $guidforphrase . '_metadesc'][$currentlanguageid] = $input['metadescription'];
     foreach (array_keys($pagetrans) as $varname) {
         if (empty($hasdefault[$varname]) or $currentlanguageid == vB::getDatastore()->getOption('languageid')) {
             // If the page phrase doesn't have a default one (languageid = 0) or current language is default language
             // We should update the phrase for default language (languageid = 0)
             $pagetrans[$varname][0] = $pagetrans[$varname][$currentlanguageid];
         }
     }
     if (!$productid) {
         $page = vB::getDbAssertor()->getColumn('page', 'product', array('pageid' => $pageid));
         $productid = array_pop($page);
     }
     $phraseLib->save('pagemeta', 'page_' . $guidforphrase . '_title', array('text' => $pagetrans['page_' . $guidforphrase . '_title'], 'product' => $productid, 'oldvarname' => 'page_' . $guidforphrase . '_title', 'oldfieldname' => 'global', 'skipdebug' => 1));
     $phraseLib->save('pagemeta', 'page_' . $guidforphrase . '_metadesc', array('text' => $pagetrans['page_' . $guidforphrase . '_metadesc'], 'product' => $productid, 'oldvarname' => 'page_' . $guidforphrase . '_metadesc', 'oldfieldname' => 'global', 'skipdebug' => 1));
     build_language();
     vB_Cache::instance()->event('pageChg_' . $pageid);
     $page = $this->fetchPageById($pageid, array('nodeid' => $input['nodeid'], 'userid' => $input['userid']));
     return array('success' => true, 'url' => $page['url'], 'pageid' => $pageid);
 }
function get_product_export_xml($productid)
{
    $assertor = vB::getDbAssertor();
    //	Set up the parent tag
    $product_details = $assertor->getRow('product', array('productid' => $productid));
    if (!$product_details) {
        throw new vB_Exception_AdminStopMessage('invalid_product_specified');
    }
    $xml = new vB_XML_Builder();
    // ############## main product info
    $xml->add_group('product', array('productid' => strtolower($product_details['productid']), 'active' => $product_details['active']));
    // Parent for product
    $xml->add_tag('title', $product_details['title']);
    $xml->add_tag('description', $product_details['description']);
    $xml->add_tag('version', $product_details['version']);
    $xml->add_tag('url', $product_details['url']);
    $xml->add_tag('versioncheckurl', $product_details['versioncheckurl']);
    // ############## dependencies
    $product_dependencies = $assertor->assertQuery('productdependency', array('productid' => $productid), array('field' => array('dependencytype', 'parentproductid', 'minversion'), 'direction' => array(vB_dB_Query::SORT_ASC, vB_dB_Query::SORT_ASC, vB_dB_Query::SORT_ASC)));
    $xml->add_group('dependencies');
    while ($product_dependencies and $product_dependencies->valid()) {
        $product_dependency = $product_dependencies->current();
        $deps = array('dependencytype' => $product_dependency['dependencytype']);
        if ($product_dependency['dependencytype'] == 'product') {
            $deps['parentproductid'] = $product_dependency['parentproductid'];
        }
        $deps['minversion'] = $product_dependency['minversion'];
        $deps['maxversion'] = $product_dependency['maxversion'];
        $xml->add_tag('dependency', '', $deps);
        $product_dependencies->next();
    }
    unset($product_dependency);
    $xml->close_group();
    // ############## install / uninstall codes
    $productcodes = $assertor->getRows('productcode', array('productid' => $productid));
    $xml->add_group('codes');
    $productcodes_grouped = array();
    $productcodes_versions = array();
    foreach ($productcodes as $productcode) {
        // have to be careful here, as version numbers are not necessarily unique
        $productcodes_versions["{$productcode['version']}"] = 1;
        $productcodes_grouped["{$productcode['version']}"][] = $productcode;
    }
    $productcodes_versions = array_keys($productcodes_versions);
    usort($productcodes_versions, 'version_sort');
    foreach ($productcodes_versions as $version) {
        foreach ($productcodes_grouped["{$version}"] as $productcode) {
            $xml->add_group('code', array('version' => $productcode['version']));
            $xml->add_tag('installcode', $productcode['installcode']);
            $xml->add_tag('uninstallcode', $productcode['uninstallcode']);
            $xml->close_group();
        }
    }
    $xml->close_group();
    // ############## templates
    $gettemplates = $assertor->assertQuery('template', array('product' => $productid, 'styleid' => -1), 'title');
    $xml->add_group('templates');
    while ($gettemplates and $gettemplates->valid()) {
        $template = $gettemplates->current();
        if (is_newer_version($template['version'], $product_details['version'])) {
            // version in the template is newer than the version of the product,
            // which probably means it's using the vB version
            $template['version'] = $product_details['version'];
        }
        $xml->add_tag('template', $template['templatetype'] == 'template' ? $template['template_un'] : $template['template'], array('name' => htmlspecialchars($template['title']), 'templatetype' => $template['templatetype'], 'date' => $template['dateline'], 'username' => $template['username'], 'version' => htmlspecialchars_uni($template['version'])), true);
        $gettemplates->next();
    }
    $xml->close_group();
    // ############## Stylevars
    $stylevarinfo = get_stylevars_for_export($productid, -1);
    $stylevar_cache = $stylevarinfo['stylevars'];
    $stylevar_dfn_cache = $stylevarinfo['stylevardfns'];
    $xml->add_group('stylevardfns');
    foreach ($stylevar_dfn_cache as $stylevargroupname => $stylevargroup) {
        $xml->add_group('stylevargroup', array('name' => $stylevargroupname));
        foreach ($stylevargroup as $stylevar) {
            $xml->add_tag('stylevar', '', array('name' => htmlspecialchars($stylevar['stylevarid']), 'datatype' => $stylevar['datatype'], 'validation' => base64_encode($stylevar['validation']), 'failsafe' => base64_encode($stylevar['failsafe'])));
        }
        $xml->close_group();
    }
    $xml->close_group();
    $xml->add_group('stylevars');
    foreach ($stylevar_cache as $stylevarid => $stylevar) {
        $xml->add_tag('stylevar', '', array('name' => htmlspecialchars($stylevar['stylevarid']), 'value' => base64_encode($stylevar['value'])));
    }
    $xml->close_group();
    // ############## hooks
    $xml->add_group('hooks');
    $hooks = vB_Api::instanceInternal("Hook")->getHookList(array('hookname'), array('product' => $productid));
    foreach ($hooks as $hook) {
        $xml->add_group('hook');
        $xml->add_tag('hookname', $hook['hookname']);
        $xml->add_tag('title', $hook['title']);
        $xml->add_tag('active', $hook['active']);
        $xml->add_tag('hookorder', $hook['hookorder']);
        $xml->add_tag('template', $hook['template']);
        $xml->add_tag('arguments', $hook['arguments']);
        $xml->close_group();
    }
    $xml->close_group();
    // ############## phrases
    $phrasetypes = vB_Api::instanceInternal('phrase')->fetch_phrasetypes(false);
    $phrases = array();
    $getphrases = $assertor->getRows('vBForum:phrase', array('languageid' => array(-1, 0), 'product' => $productid), array('languageid', 'fieldname', 'varname'));
    foreach ($getphrases as $getphrase) {
        $phrases["{$getphrase['fieldname']}"]["{$getphrase['varname']}"] = $getphrase;
    }
    $xml->add_group('phrases');
    // make sure the phrasegroups are in a reliable order
    ksort($phrases);
    foreach ($phrases as $_fieldname => $typephrases) {
        // create a group for each phrase type that we have phrases for
        // then insert the phrases
        $xml->add_group('phrasetype', array('name' => $phrasetypes["{$_fieldname}"]['title'], 'fieldname' => $_fieldname));
        // make sure the phrases are in a reliable order
        ksort($typephrases);
        foreach ($typephrases as $phrase) {
            $xml->add_tag('phrase', $phrase['text'], array('name' => $phrase['varname'], 'date' => $phrase['dateline'], 'username' => $phrase['username'], 'version' => htmlspecialchars_uni($phrase['version'])), true);
        }
        $xml->close_group();
    }
    $xml->close_group();
    // ############## options
    $setting = array();
    $settinggroup = array();
    $groups = $assertor->getRows('settinggroup', array('volatile' => 1), array('displayorder', 'grouptitle'));
    foreach ($groups as $group) {
        $settinggroup["{$group['grouptitle']}"] = $group;
    }
    ksort($settinggroup);
    $options = $assertor->getRows('setting', array('product' => $productid, 'volatile' => 1), array('displayorder', 'varname'));
    foreach ($options as $row) {
        $setting["{$row['grouptitle']}"][] = $row;
    }
    $xml->add_group('options');
    foreach ($settinggroup as $grouptitle => $group) {
        if (empty($setting["{$grouptitle}"])) {
            continue;
        }
        // add a group for each setting group we have settings for
        $newGroup = array('name' => htmlspecialchars($group['grouptitle']), 'displayorder' => $group['displayorder']);
        if (!empty($group['adminperm'])) {
            $newGroup['adminperm'] = $group['adminperm'];
        }
        $xml->add_group('settinggroup', $newGroup);
        ksort($setting["{$grouptitle}"]);
        foreach ($setting["{$grouptitle}"] as $set) {
            $arr = array('varname' => $set['varname'], 'displayorder' => $set['displayorder']);
            if ($set['advanced']) {
                $arr['advanced'] = 1;
            }
            $xml->add_group('setting', $arr);
            if ($set['datatype']) {
                $xml->add_tag('datatype', $set['datatype']);
            }
            if ($set['optioncode'] != '') {
                $xml->add_tag('optioncode', $set['optioncode']);
            }
            if ($set['validationcode']) {
                $xml->add_tag('validationcode', $set['validationcode']);
            }
            if ($set['defaultvalue'] !== '') {
                $xml->add_tag('defaultvalue', $set['defaultvalue']);
            }
            if ($set['blacklist']) {
                $xml->add_tag('blacklist', 1);
            }
            if ($set['ispublic']) {
                $xml->add_tag('public', 1);
            }
            if ($set['adminperm']) {
                $xml->add_tag('adminperm', $set['adminperm']);
            }
            $xml->close_group();
        }
        $xml->close_group();
    }
    $xml->close_group();
    // ############## admin help
    $help_topics_results = $assertor->getRows('vBForum:adminhelp', array('product' => $productid, 'volatile' => 1), array('script', 'action', 'displayorder', 'optionname'));
    $help_topics = array();
    foreach ($help_topics_results as $help_topic) {
        $help_topics["{$help_topic['script']}"][] = $help_topic;
    }
    ksort($help_topics);
    $xml->add_group('helptopics');
    foreach ($help_topics as $script => $script_topics) {
        $xml->add_group('helpscript', array('name' => $script));
        foreach ($script_topics as $topic) {
            $attr = array('disp' => $topic['displayorder']);
            if ($topic['action']) {
                $attr['act'] = $topic['action'];
            }
            if ($topic['optionname']) {
                $attr['opt'] = $topic['optionname'];
            }
            $xml->add_tag('helptopic', '', $attr);
        }
        $xml->close_group();
    }
    $xml->close_group();
    // ############## Cron entries
    $cron_results = $assertor->getRows('cron', array(vB_dB_Query::CONDITIONS_KEY => array(array('field' => 'product', 'value' => $productid, vB_dB_Query::OPERATOR_KEY => vB_dB_Query::OPERATOR_EQ), array('field' => 'volatile', 'value' => 1, vB_dB_Query::OPERATOR_KEY => vB_dB_Query::OPERATOR_EQ), array('field' => 'varname', 'value' => '', vB_dB_Query::OPERATOR_KEY => vB_dB_Query::OPERATOR_NE))));
    $xml->add_group('cronentries');
    foreach ($cron_results as $cron) {
        $minutes = unserialize($cron['minute']);
        if (!is_array($minutes)) {
            $minutes = array();
        }
        $xml->add_group('cron', array('varname' => $cron['varname'], 'active' => $cron['active'], 'loglevel' => $cron['loglevel']));
        $xml->add_tag('filename', $cron['filename']);
        $xml->add_tag('scheduling', '', array('weekday' => $cron['weekday'], 'day' => $cron['day'], 'hour' => $cron['hour'], 'minute' => implode(',', $minutes)));
        $xml->close_group();
    }
    $xml->close_group();
    $faq_results = $assertor->getRows('vBForum:faq', array('product' => $productid, 'volatile' => 1), 'faqname');
    $xml->add_group('faqentries');
    foreach ($faq_results as $faq) {
        $xml->add_tag('faq', '', array('faqname' => $faq['faqname'], 'faqparent' => $faq['faqparent'], 'displayorder' => $faq['displayorder']));
    }
    $xml->close_group();
    // ############## widgets
    $widgetExporter = new vB_Xml_Export_Widget($productid);
    $widgetExporter->getXml($xml);
    // ############## pagetemplates
    $pageTemplateExporter = new vB_Xml_Export_PageTemplate($productid);
    $pageTemplateExporter->getXml($xml);
    // ############## pages
    $pageExporter = new vB_Xml_Export_Page($productid);
    $pageExporter->getXml($xml);
    // ############## channels
    $channelExporter = new vB_Xml_Export_Channel($productid);
    $channelExporter->getXml($xml);
    // ############## routes
    $routeExporter = new vB_Xml_Export_Route($productid);
    $routeExporter->getXml($xml);
    // ############## Finish up
    $xml->close_group();
    $doc = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n\r\n" . $xml->output();
    unset($xml);
    return $doc;
}