File: D:/HostingSpaces/RMourik/bassol.nl/wwwroot/App_Code/CMS/UpgradeProcedure.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Xml;
using CMS.Base;
using CMS.CMSImportExport;
using CMS.Core;
using CMS.DataEngine;
using CMS.EventLog;
using CMS.FormEngine;
using CMS.Globalization;
using CMS.Helpers;
using CMS.IO;
using CMS.MacroEngine;
using CMS.Membership;
using CMS.Modules;
using CMS.PortalEngine;
using CMS.Synchronization;
using CMS.URLRewritingEngine;
using CMS.WorkflowEngine;
#region "Code to bind to the ApplicationEvents using CMSModuleLoader"
/// <summary>
/// Upgrade loader
/// </summary>
[UpgradeLoader]
public partial class CMSModuleLoader
{
/// <summary>
/// Module registration
/// </summary>
private class UpgradeLoader : CMSLoaderAttribute
{
/// <summary>
/// Initializes the module
/// </summary>
public override void PreInit()
{
ApplicationEvents.UpdateData.Execute += Update;
}
/// <summary>
/// Updates the application data to a newer version if necessary
/// </summary>
private void Update(object sender, EventArgs eventArgs)
{
UpgradeProcedure.Update();
}
}
}
#endregion
/// <summary>
/// Class carrying the code to perform the upgrade procedure.
/// </summary>
public static class UpgradeProcedure
{
#region "Variables"
private static readonly string FORM_DEFINITION_TABLE = "Temp_FormDefinition";
private static readonly string FORM_DEFINITION_NAME_COLUMN = "ObjectName";
private static readonly string FORM_DEFINITION_VALUE_COLUMN = "FormDefinition";
// Path to the upgrade package
private static string mUpgradePackagePath;
private static string mWebsitePath;
private static string mEventLogSource;
#endregion
#region "Properties"
/// <summary>
/// Gets the source text for event log records generated by upgrade actions
/// </summary>
private static string EventLogSource
{
get
{
return mEventLogSource ?? (mEventLogSource = string.Format("Upgrade to {0}", CMSVersion.MainVersion));
}
}
#endregion
#region "Main update method"
/// <summary>
/// Runs the update procedure.
/// </summary>
public static void Update()
{
if (DatabaseHelper.IsDatabaseAvailable)
{
try
{
string version = SettingsKeyInfoProvider.GetValue("CMSDataVersion");
switch (version.ToLowerCSafe())
{
case "8.1":
using (var context = new CMSActionContext())
{
context.LogLicenseWarnings = false;
UpgradeApplication(Upgrade81To82, "8.2", "Upgrade_81_82.zip");
}
break;
}
}
catch (Exception ex)
{
EventLogProvider.LogException(EventLogSource, "UPGRADE", ex);
}
}
}
#endregion
#region "General purpose - all versions methods"
private static void UpgradeApplication(Func<bool> versionSpecificMethod, string newVersion, string packageName)
{
// Increase the timeout for upgrade request due to expensive operations like macro signing and conversion (needed for large DBs)
HttpContext.Current.Server.ScriptTimeout = 14400;
EventLogProvider.LogInformation(EventLogSource, "START");
// Set the path to the upgrade package (this has to be done here, not in the Import method, because it's an async procedure without HttpContext)
mUpgradePackagePath = HttpContext.Current.Server.MapPath("~/CMSSiteUtils/Import/" + packageName);
mWebsitePath = HttpContext.Current.Server.MapPath("~/");
var dtm = new TableManager(null);
using (var context = new CMSActionContext())
{
context.DisableLogging();
context.CreateVersion = false;
context.LogIntegration = false;
if (dtm.TableExists(FORM_DEFINITION_TABLE))
{
UpdateClasses();
UpdateAlternativeForms();
DropTempDefinitionTable(dtm);
}
}
// Update all views
dtm.RefreshDocumentViews();
RefreshCustomViews(dtm);
// Set data version
SettingsKeyInfoProvider.SetValue("CMSDataVersion", newVersion);
SettingsKeyInfoProvider.SetValue("CMSDBVersion", newVersion);
// Clear hashtables
ModuleManager.ClearHashtables();
// Clear the cache
CacheHelper.ClearCache(null, true);
// Drop the routes
CMSDocumentRouteHelper.DropAllRoutes();
// Init the Mimetype helper (required for the Import)
MimeTypeHelper.LoadMimeTypes();
// Call version specific operations
if (versionSpecificMethod != null)
{
using (var context = new CMSActionContext())
{
context.DisableLogging();
context.CreateVersion = false;
context.LogIntegration = false;
versionSpecificMethod.Invoke();
}
}
// Import upgrade package with webparts, widgets...
UpgradeImportPackage();
RefreshMacroSignatures();
EventLogProvider.LogInformation(EventLogSource, "FINISH");
}
/// <summary>
/// Refreshes all custom views.
/// </summary>
private static void RefreshCustomViews(TableManager tm)
{
tm.RefreshView("View_CMS_User");
tm.RefreshView("View_Community_Member");
tm.RefreshView("View_COM_SKU");
tm.RefreshView("View_NewsletterSubscriberUserRole_Joined");
tm.RefreshView("View_Community_Group");
tm.RefreshView("View_Community_Friend_Friends");
tm.RefreshView("View_Community_Friend_RequestedFriends");
tm.RefreshView("View_OM_Contact_Activity");
tm.RefreshView("View_OM_Contact_Joined");
tm.RefreshView("View_OM_ContactGroupMember_ContactJoined");
tm.RefreshView("View_OM_Account_Joined");
tm.RefreshView("View_OM_Account_MembershipJoined");
tm.RefreshView("View_OM_ContactGroupMember_AccountJoined");
}
/// <summary>
/// Update form definitions of classes (especially system tables).
/// </summary>
private static void UpdateClasses()
{
DataSet classes = GetFormDefinitions();
if (!DataHelper.DataSourceIsEmpty(classes))
{
foreach (DataRow row in classes.Tables[0].Rows)
{
string objectName = DataHelper.GetStringValue(row, FORM_DEFINITION_NAME_COLUMN);
string newDefinition = DataHelper.GetStringValue(row, FORM_DEFINITION_VALUE_COLUMN);
if (!string.IsNullOrEmpty(objectName) && !string.IsNullOrEmpty(newDefinition))
{
var dataClass = DataClassInfoProvider.GetDataClassInfo(objectName);
if (dataClass != null)
{
var newVersionFi = new FormInfo(newDefinition);
// Copy custom fields only for system tables
if (dataClass.ClassShowAsSystemTable)
{
var oldVersionFi = new FormInfo(dataClass.ClassFormDefinition);
CopyCustomFields(oldVersionFi, newVersionFi, false);
}
// Save the modified form definition
dataClass.ClassFormDefinition = newVersionFi.GetXmlDefinition();
// Update the scheme
dataClass.ClassXmlSchema = new TableManager(dataClass.ClassConnectionString).GetXmlSchema(dataClass.ClassTableName);
// Save the new definition
dataClass.Update();
}
}
}
}
}
/// <summary>
/// Updates an existing alternative forms form definitions. Appends existing custom fields to new version definitions.
/// </summary>
private static void UpdateAlternativeForms()
{
DataSet classes = GetFormDefinitions(true);
if (!DataHelper.DataSourceIsEmpty(classes))
{
foreach (DataRow row in classes.Tables[0].Rows)
{
string objectName = DataHelper.GetStringValue(row, FORM_DEFINITION_NAME_COLUMN);
string newDefinition = DataHelper.GetStringValue(row, FORM_DEFINITION_VALUE_COLUMN);
if (!string.IsNullOrEmpty(objectName) && !string.IsNullOrEmpty(newDefinition))
{
var altForm = AlternativeFormInfoProvider.GetAlternativeFormInfo(objectName);
if (altForm != null)
{
var mainDci = DataClassInfoProvider.GetDataClassInfo(altForm.FormClassID);
var classFormDefinition = mainDci.ClassFormDefinition;
if (altForm.FormCoupledClassID > 0)
{
// If coupled class is defined combine form definitions
var coupledDci = DataClassInfoProvider.GetDataClassInfo(altForm.FormCoupledClassID);
if (coupledDci != null)
{
classFormDefinition = FormHelper.MergeFormDefinitions(classFormDefinition, coupledDci.ClassFormDefinition);
}
}
var oldVersionDefinition = FormHelper.MergeFormDefinitions(classFormDefinition, altForm.FormDefinition);
var newVersionDefinition = FormHelper.MergeFormDefinitions(classFormDefinition, newDefinition);
var newVersionFi = new FormInfo(newVersionDefinition);
var oldVersionFi = new FormInfo(oldVersionDefinition);
CopyCustomFields(oldVersionFi, newVersionFi, true);
// Save the modified form definition
altForm.FormDefinition = FormHelper.GetFormDefinitionDifference(classFormDefinition, newVersionFi.GetXmlDefinition(), true);
altForm.Update();
}
}
}
}
}
/// <summary>
/// Copies custom fields from old version of form definition to the new form definition.
/// </summary>
/// <param name="oldVersionFi">Old version form definition</param>
/// <param name="newVersionFi">New version form definition</param>
/// <param name="overwrite">Indicates whether existing fields should be overwritten. Alternative form fields need to be overwritten due to the combination with upgraded class form.</param>
private static void CopyCustomFields(FormInfo oldVersionFi, FormInfo newVersionFi, bool overwrite)
{
// Remove all system fields from old definition to get only custom fields
oldVersionFi.RemoveFields(f => f.System);
// Combine forms so that custom fields from old definition are appended to the new definition
newVersionFi.CombineWithForm(oldVersionFi, new CombineWithFormSettings
{
IncludeCategories = false,
RemoveEmptyCategories = true,
OverwriteExisting = overwrite
});
}
/// <summary>
/// Returns dataset with class names (or alt.form full names) and form definitions which should be used for the upgrade.
/// </summary>
/// <param name="getAltForms">Indicates if alt.form definitions should be returned</param>
private static DataSet GetFormDefinitions(bool getAltForms = false)
{
string queryText = String.Format("SELECT [{0}], [{1}] FROM [{2}] WHERE {3}", FORM_DEFINITION_NAME_COLUMN, FORM_DEFINITION_VALUE_COLUMN, FORM_DEFINITION_TABLE, getAltForms ? "IsAltForm = 1" : "IsAltForm = 0 OR IsAltForm IS NULL");
DataSet ds = null;
try
{
ds = ConnectionHelper.ExecuteQuery(queryText, null, QueryTypeEnum.SQLQuery);
}
catch (Exception ex)
{
EventLogProvider.LogException(EventLogSource, "GETFORMDEFINITION", ex);
}
return ds;
}
/// <summary>
/// Drops temporary table with classes' and alt.forms' form definitions.
/// </summary>
/// <param name="dtm">Table manager</param>
private static void DropTempDefinitionTable(TableManager dtm)
{
try
{
dtm.DropTable(FORM_DEFINITION_TABLE);
}
catch (Exception ex)
{
EventLogProvider.LogException(EventLogSource, "DROPTEMPTABLE", ex);
}
}
/// <summary>
/// Procedures which automatically imports the upgrade export package with all WebParts, Widgets, Reports and TimeZones.
/// </summary>
private static void UpgradeImportPackage()
{
// Import
try
{
RequestStockHelper.Remove("CurrentDomain", true);
var importSettings = new SiteImportSettings(MembershipContext.AuthenticatedUser)
{
DefaultProcessObjectType = ProcessObjectEnum.All,
SourceFilePath = mUpgradePackagePath,
WebsitePath = mWebsitePath
};
using (var context = new CMSActionContext())
{
context.DisableLogging();
context.CreateVersion = false;
context.LogIntegration = false;
ImportProvider.ImportObjectsData(importSettings);
// Regenerate time zones
TimeZoneInfoProvider.GenerateTimeZoneRules();
// Delete the files for separable modules which are not install and therefore not needed
DeleteWebPartsOfUninstalledModules();
ImportMetaFiles(Path.Combine(mWebsitePath, "App_Data\\CMSTemp\\Upgrade"));
}
}
catch (Exception ex)
{
EventLogProvider.LogException(EventLogSource, "IMPORT", ex);
}
}
/// <summary>
/// Refreshes macro signatures in all object which can contain macros.
/// </summary>
private static void RefreshMacroSignatures()
{
// Get object types
var objectTypes = new List<string> {
TransformationInfo.OBJECT_TYPE,
UIElementInfo.OBJECT_TYPE,
FormUserControlInfo.OBJECT_TYPE,
SettingsKeyInfo.OBJECT_TYPE,
AlternativeFormInfo.OBJECT_TYPE,
DataClassInfo.OBJECT_TYPE,
DataClassInfo.OBJECT_TYPE_SYSTEMTABLE,
DataClassInfo.OBJECT_TYPE_CUSTOMTABLE,
DataClassInfo.OBJECT_TYPE_DOCUMENTTYPE,
PageTemplateInfo.OBJECT_TYPE,
LayoutInfo.OBJECT_TYPE,
CssStylesheetInfo.OBJECT_TYPE,
WorkflowActionInfo.OBJECT_TYPE,
};
foreach (string type in objectTypes)
{
try
{
using (var context = new CMSActionContext())
{
context.DisableLogging();
context.CreateVersion = false;
context.LogIntegration = false;
var infos = new InfoObjectCollection(type);
foreach (var info in infos)
{
MacroSecurityProcessor.RefreshSecurityParameters(info, "administrator", true);
}
}
}
catch (Exception ex)
{
EventLogProvider.LogException(EventLogSource, "REFRESHMACROSIGNATURES", ex, 0, "Type: " + type);
}
}
}
/// <summary>
/// Deletes the files for separable modules which are not install and therefore not needed.
/// </summary>
private static void DeleteWebPartsOfUninstalledModules()
{
var webPartsPath = mWebsitePath + "CMSWebParts\\";
var files = new List<string>();
var separableModules = new List<string>
{
ModuleName.BIZFORM,
ModuleName.BLOGS,
ModuleName.COMMUNITY,
ModuleName.ECOMMERCE,
ModuleName.EVENTMANAGER,
ModuleName.FORUMS,
ModuleName.MEDIALIBRARY,
ModuleName.MESSAGEBOARD,
ModuleName.MESSAGING,
ModuleName.NEWSLETTER,
ModuleName.NOTIFICATIONS,
ModuleName.ONLINEMARKETING,
ModuleName.POLLS,
ModuleName.PROJECTMANAGEMENT,
ModuleName.REPORTING,
ModuleName.STRANDSRECOMMENDER,
ModuleName.CHAT,
};
foreach (var separableModule in separableModules)
{
// Add files from this folder to the list of files to delete if the module is not installed
if (!ModuleEntryManager.IsModuleLoaded(separableModule))
{
var folderName = GetWebPartFolderName(separableModule);
files.AddRange(GetAllFiles(webPartsPath + folderName));
}
}
// Remove web parts for separated modules
foreach (String file in files)
{
try
{
File.Delete(file);
}
catch (Exception ex)
{
EventLogProvider.LogException(EventLogSource, "DELETEWEBPARTS", ex, 0, "File: " + file);
}
}
}
/// <summary>
/// Returns list of all files in given folder (recursively, from all subdirectories as well).
/// </summary>
/// <param name="folder">Folder to search in</param>
private static List<String> GetAllFiles(String folder)
{
var files = new List<string>();
files.AddRange(Directory.GetFiles(folder));
var dirs = Directory.GetDirectories(folder);
foreach (string dir in dirs)
{
files.AddRange(GetAllFiles(dir));
}
return files;
}
/// <summary>
/// For given module returns it's folder name within CMSWebParts folder.
/// </summary>
/// <param name="moduleName">Name of the module</param>
/// <returns></returns>
private static string GetWebPartFolderName(string moduleName)
{
// Handle exceptions
switch (moduleName)
{
case ModuleName.BIZFORM:
return "BizForms";
case ModuleName.BLOGS:
return "Blogs";
case ModuleName.NEWSLETTER:
return "Newsletters";
}
// By default, trim "CMS." prefix from module name which will give us folder name withing CMSWebParts directory
return moduleName.Substring(4);
}
/// <summary>
/// Imports default metafiles which were changed in the new version.
/// </summary>
/// <param name="upgradeFolder">Folder where the generated metafiles.xml file is</param>
private static void ImportMetaFiles(string upgradeFolder)
{
try
{
// To get the file use Phobos - Generate files button, Metafile settings.
// Choose only those object types which had metafiles in previous version and these metafiles changed to the new version.
String xmlPath = Path.Combine(upgradeFolder, "metafiles.xml");
if (File.Exists(xmlPath))
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load(xmlPath);
XmlNode metaFilesNode = xDoc.SelectSingleNode("MetaFiles");
if (metaFilesNode != null)
{
String filesDirectory = Path.Combine(upgradeFolder, "Metafiles");
using (new CMSActionContext { LogEvents = false })
{
foreach (XmlNode metaFile in metaFilesNode)
{
// Load metafiles information from XML
String objType = metaFile.Attributes["ObjectType"].Value;
String groupName = metaFile.Attributes["GroupName"].Value;
String codeName = metaFile.Attributes["CodeName"].Value;
String fileName = metaFile.Attributes["FileName"].Value;
String extension = metaFile.Attributes["Extension"].Value;
String fileGUID = metaFile.Attributes["FileGUID"].Value;
String title = (metaFile.Attributes["Title"] != null) ? metaFile.Attributes["Title"].Value : null;
String description = (metaFile.Attributes["Description"] != null) ? metaFile.Attributes["Description"].Value : null;
// Try to find correspondent info object
BaseInfo infoObject = BaseAbstractInfoProvider.GetInfoByName(objType, codeName);
if (infoObject != null)
{
int infoObjectId = infoObject.Generalized.ObjectID;
// Check if metafile exists
InfoDataSet<MetaFileInfo> metaFilesSet = MetaFileInfoProvider.GetMetaFilesWithoutBinary(infoObjectId, objType, groupName, "MetaFileGUID = '" + fileGUID + "'", null);
if (DataHelper.DataSourceIsEmpty(metaFilesSet))
{
// Create new metafile if does not exists
String mfFileName = String.Format("{0}.{1}", fileGUID, extension.TrimStart('.'));
MetaFileInfo mfInfo = new MetaFileInfo(Path.Combine(filesDirectory, mfFileName), infoObjectId, objType, groupName);
mfInfo.MetaFileGUID = ValidationHelper.GetGuid(fileGUID, Guid.NewGuid());
// Set correct properties
mfInfo.MetaFileName = fileName;
if (title != null)
{
mfInfo.MetaFileTitle = title;
}
if (description != null)
{
mfInfo.MetaFileDescription = description;
}
// Save new meta file
MetaFileInfoProvider.SetMetaFileInfo(mfInfo);
}
}
}
// Remove existing files after successful finish
String[] files = Directory.GetFiles(upgradeFolder);
foreach (String file in files)
{
File.Delete(file);
}
}
}
}
}
catch (Exception ex)
{
EventLogProvider.LogException(EventLogSource, "IMPORTMETAFILES", ex);
}
}
#endregion
#region "Update from 8.1 to 8.2"
/// <summary>
/// Handles all the specific operations for upgrade from 8.1 to 8.2.
/// </summary>
/// <returns></returns>
private static bool Upgrade81To82()
{
RemoveAnalyticsExtender();
RemoveGlobalShippingOptionsFromRecycleBin();
EncryptStrandsCatalogFeedPasswords();
return true;
}
#endregion
#region "Private methods"
/// <summary>
/// Encrypts all Strands catalog feed passwords
/// </summary>
private static void EncryptStrandsCatalogFeedPasswords()
{
const string keyName = "CMSStrandsCatalogFeedPassword";
// Get all not empty keys
var keys = SettingsKeyInfoProvider.GetSettingsKeys()
.Columns("SiteID, KeyValue")
.WhereEquals("KeyName", keyName)
.WhereNotEmpty("KeyValue");
foreach (var key in keys)
{
// Encrypt password
SettingsKeyInfoProvider.SetValue(keyName, key.SiteID, EncryptionHelper.EncryptData(key.KeyValue));
}
}
/// <summary>
/// Removes obsolete analytics extender from UI elements
/// </summary>
private static void RemoveAnalyticsExtender()
{
var analyticsUiElement = UIElementInfoProvider.GetUIElementInfo("CMS.Content", "Analytics");
if (analyticsUiElement != null)
{
RemoveAnalyticsExtenderFromUiElement(analyticsUiElement);
}
var orderRulesUiElement = UIElementInfoProvider.GetUIElementInfo("CMS.Ecommerce", "OrderRules");
if (orderRulesUiElement != null)
{
RemoveAnalyticsExtenderFromUiElement(orderRulesUiElement);
}
AssignExtenderToShippingOptionTabs();
}
/// <summary>
/// Removes Analytics Extender from given UIElement
/// </summary>
private static void RemoveAnalyticsExtenderFromUiElement(UIElementInfo uiElement)
{
XmlData customData = new XmlData();
customData.LoadData(uiElement.ElementProperties);
string pageExtenderClassName = ValidationHelper.GetString(customData.GetValue("pageextenderclassname"), String.Empty);
if ("analyticsextender".EqualsCSafe(pageExtenderClassName.ToLower()))
{
customData.Remove("pageextenderclassname");
customData.Remove("pageextenderassemblyname");
uiElement.ElementProperties = customData.GetData();
uiElement.Update();
}
}
/// <summary>
/// Assigns ShippingOptionTabsExtender to Edit.Configuration.ShippingOptionProperties UI element
/// </summary>
private static void AssignExtenderToShippingOptionTabs()
{
var element = UIElementInfoProvider.GetUIElementInfo(ModuleName.ECOMMERCE, "Edit.Configuration.ShippingOptionProperties");
if (element != null)
{
var properties = new XmlData();
properties.LoadData(element.ElementProperties);
properties.SetValue("tabextender", "CMS.Ecommerce");
properties.SetValue("extenderclassname", "CMS.Ecommerce.Extenders.ShippingOptionTabsExtender");
element.ElementProperties = properties.GetData();
element.Update();
}
}
/// <summary>
/// Removes global shipping options from recycle bin.
/// </summary>
private static void RemoveGlobalShippingOptionsFromRecycleBin()
{
const string shippingObjectType = "ecommerce.shippingoption";
var shippingOption = ModuleManager.GetObject(shippingObjectType);
// Nothing to remove when shipping option type not present
if (shippingOption == null)
{
return;
}
// Get histories for all objects of shipping option type
var histories = ObjectVersionHistoryInfoProvider.GetVersionHistories().WhereEquals("VersionObjectType", shippingObjectType);
// Fill ids list with IDs of global shipping options present in histories
var ids = new List<int>();
foreach (var history in histories)
{
if (!String.IsNullOrEmpty(history.VersionXML))
{
HierarchyHelper.LoadObjectFromXML(OperationTypeEnum.Export, shippingOption, history.VersionXML);
if (shippingOption.IsGlobal)
{
ids.Add(shippingOption.Generalized.ObjectID);
}
}
}
// Remove version histories for selected shipping options
if (ids.Count != 0)
{
var where = new WhereCondition().WhereIn("VersionObjectID", ids);
ObjectVersionHistoryInfoProvider.DeleteVersionHistories(where.ToString(true));
}
}
#endregion
}