using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web.UI.WebControls.WebParts;
using System.Xml;
using Lapointe.SharePoint.STSADM.Commands.Lists;
using Lapointe.SharePoint.STSADM.Commands.SPValidators;
using Lapointe.SharePoint.STSADM.Commands.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Deployment;
using Microsoft.SharePoint.Navigation;
#if MOSS
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.Fields;
using Microsoft.SharePoint.Publishing.Navigation;
using Microsoft.SharePoint.Publishing.WebControls;
#endif
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
using WebPart=Microsoft.SharePoint.WebPartPages.WebPart;
using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
using Lapointe.SharePoint.STSADM.Commands.Features;
namespace Lapointe.SharePoint.STSADM.Commands.SiteCollectionSettings
{
/// <summary>
/// Repairs a site collection that has been imported from an exported sub-site.
/// Note that the sourceurl can be the actual source site or any site collection
/// that can be used as a model for the target.
/// </summary>
public class RepairSiteCollectionImportedFromSubSite : SPOperation
{
public RepairSiteCollectionImportedFromSubSite()
{
SPParamCollection parameters = new SPParamCollection();
parameters.Add(new SPParam("sourceurl", "source", true, null, new SPUrlValidator(), "Please specify the source sub-site or model site collection."));
parameters.Add(new SPParam("targeturl", "target", true, null, new SPUrlValidator(), "Please specify the target location of the new site collection to repair."));
StringBuilder sb = new StringBuilder();
sb.Append("\r\n\r\nRepairs a site collection that has been imported from an exported sub-site. Note that the sourceurl can be the actual source site or any site collection that can be used as a model for the target.\r\n\r\nParameters:");
sb.Append("\r\n\t-sourceurl <source location of the existing sub-site or model site collection>");
sb.Append("\r\n\t-targeturl <target location for the new site collection>");
Init(parameters, sb.ToString());
}
#region ISPStsadmCommand Members
/// <summary>
/// Gets the help message.
/// </summary>
/// <param name="command">The command.</param>
/// <returns></returns>
public override string GetHelpMessage(string command)
{
return HelpMessage;
}
/// <summary>
/// Runs the specified command.
/// </summary>
/// <param name="command">The command.</param>
/// <param name="keyValues">The key values.</param>
/// <param name="output">The output.</param>
/// <returns></returns>
public override int Execute(string command, StringDictionary keyValues, out string output)
{
output = string.Empty;
string sourceurl = Params["sourceurl"].Value.TrimEnd('/');
string targeturl = Params["targeturl"].Value.TrimEnd('/');
RepairSite(sourceurl, targeturl, true);
return OUTPUT_SUCCESS;
}
#endregion
/// <summary>
/// Repairs the site.
/// </summary>
/// <param name="sourceurl">The sourceurl.</param>
/// <param name="targeturl">The targeturl.</param>
/// <param name="verbose">if set to <c>true</c> [verbose].</param>
public static void RepairSite(string sourceurl, string targeturl, bool verbose)
{
Verbose = verbose;
using (SPSite targetSite = new SPSite(targeturl.TrimEnd('/')))
using (SPWeb targetWeb = targetSite.AllWebs[Utilities.GetServerRelUrlFromFullUrl(targeturl.TrimEnd('/'))])
using (SPSite sourceSite = new SPSite(sourceurl.TrimEnd('/')))
using (SPWeb sourceWeb = sourceSite.AllWebs[Utilities.GetServerRelUrlFromFullUrl(sourceurl.TrimEnd('/'))])
{
AddMissingFeatures(sourceSite, sourceWeb, targetSite, targetWeb);
try
{
Log("Progress: Begin copying content types...");
ContentTypes.CopyContentTypes.Copy(sourceurl, targeturl, verbose);
}
finally
{
Log("Progess: End copying content types.");
}
}
// We need to re-open all the objects as some values such as content types need to be refreshed.
using (SPSite targetSite = new SPSite(targeturl.TrimEnd('/')))
using (SPWeb targetWeb = targetSite.AllWebs[Utilities.GetServerRelUrlFromFullUrl(targeturl.TrimEnd('/'))])
using (SPSite sourceSite = new SPSite(sourceurl.TrimEnd('/')))
using (SPWeb sourceWeb = sourceSite.AllWebs[Utilities.GetServerRelUrlFromFullUrl(sourceurl.TrimEnd('/'))])
{
SetMasterPageGallerySettings(sourceSite, targetSite, targetWeb);
#if MOSS
PublishingWeb targetPublishingWeb = PublishingWeb.GetPublishingWeb(targetWeb);
PublishingSite targetPublishingSite = new PublishingSite(targetSite);
PublishingWeb sourcePublishingWeb = PublishingWeb.GetPublishingWeb(sourceWeb);
PublishingSite sourcePublishingSite = new PublishingSite(sourceSite);
FixPageLayoutsAndSiteTemplates(sourcePublishingSite, sourcePublishingWeb, targetPublishingSite, targetPublishingWeb, targetSite, targetWeb);
#endif
}
TimerJob.ExecAdmSvcJobs.Execute(false, true);
using (SPSite targetSite = new SPSite(targeturl.TrimEnd('/')))
using (SPWeb targetWeb = targetSite.AllWebs[Utilities.GetServerRelUrlFromFullUrl(targeturl.TrimEnd('/'))])
using (SPSite sourceSite = new SPSite(sourceurl.TrimEnd('/')))
using (SPWeb sourceWeb = sourceSite.AllWebs[Utilities.GetServerRelUrlFromFullUrl(sourceurl.TrimEnd('/'))])
{
#if MOSS
PublishingWeb targetPublishingWeb = PublishingWeb.GetPublishingWeb(targetWeb);
FixPublishingPages(targetSite);
SetGlobalNavigation(targetPublishingWeb);
RetargetGroupedListingsWebPart.RetargetGroupedListings(targetWeb, "grouped listings");
#endif
//RepairDiscussionLists(targetSite);
RetargetMiscWebParts(sourceSite, sourceWeb, targetWeb);
}
}
#region Worker Methods
#region Retargart Miscellaneous Web Parts
/// <summary>
/// Retargets the misc web parts.
/// </summary>
/// <param name="sourceSite">The source site.</param>
/// <param name="sourceWeb">The source web.</param>
/// <param name="targetWeb">The target web.</param>
internal static void RetargetMiscWebParts(SPSite sourceSite, SPWeb sourceWeb, SPWeb targetWeb)
{
StringDictionary listMap = new StringDictionary();
// Handle the primary web first then address the child webs
FindMatchingLists(listMap, sourceWeb, targetWeb);
string parentTargetUrl = targetWeb.ServerRelativeUrl;
string parentSourceUrl = sourceWeb.ServerRelativeUrl;
RetargetMiscWebPartsRecursiveHelper(listMap, parentSourceUrl, parentTargetUrl, sourceSite, targetWeb);
RetargetMiscWebParts(targetWeb, listMap);
}
/// <summary>
/// Recursive helper for retargetting the misc web parts.
/// </summary>
/// <param name="listMap">The list map.</param>
/// <param name="parentSourceUrl">The parent source URL.</param>
/// <param name="parentTargetUrl">The parent target URL.</param>
/// <param name="sourceSite">The source site.</param>
/// <param name="targetWeb">The target web.</param>
private static void RetargetMiscWebPartsRecursiveHelper(StringDictionary listMap, string parentSourceUrl, string parentTargetUrl, SPSite sourceSite, SPWeb targetWeb)
{
foreach (SPWeb childTargetWeb in targetWeb.Webs)
{
try
{
string targetParentRelativeUrl = childTargetWeb.ServerRelativeUrl.Substring(parentTargetUrl.Length).TrimEnd('/');
string sourceUrl = Utilities.ConcatServerRelativeUrls(parentSourceUrl, targetParentRelativeUrl).TrimEnd('/');
using (SPWeb childSourceWeb = sourceSite.AllWebs[sourceUrl])
{
if (childSourceWeb.Exists)
{
FindMatchingLists(listMap, childSourceWeb, childTargetWeb);
}
}
RetargetMiscWebPartsRecursiveHelper(listMap, parentSourceUrl, parentTargetUrl, sourceSite, childTargetWeb);
}
finally
{
childTargetWeb.Dispose();
}
}
}
/// <summary>
/// Finds the matching lists.
/// </summary>
/// <param name="listMap">The list map.</param>
/// <param name="sourceWeb">The source web.</param>
/// <param name="targetWeb">The target web.</param>
private static void FindMatchingLists(StringDictionary listMap, SPWeb sourceWeb, SPWeb targetWeb)
{
foreach (SPList targetList in targetWeb.Lists)
{
try
{
// Try to find a matching list in the source web
SPList sourceList = sourceWeb.Lists[targetList.Title];
listMap.Add(sourceList.ID.ToString(), targetList.ID.ToString());
}
catch (ArgumentException)
{
// There is no matching source list - this should happen if our source is the actual source and not a model.
// If the source is just a model then we're wasting our time here but unfortunately there's no way to
// determine that without asking the user.
}
}
}
/// <summary>
/// Retargets the misc web parts for the target web. Loops through all sub-webs and all files.
/// </summary>
/// <param name="targetWeb">The target web.</param>
/// <param name="listMap">The list map.</param>
private static void RetargetMiscWebParts(SPWeb targetWeb, StringDictionary listMap)
{
foreach (SPWeb subweb in targetWeb.Webs)
{
try
{
RetargetMiscWebParts(subweb, listMap);
}
finally
{
subweb.Dispose();
}
}
foreach (SPFile file in targetWeb.Files)
{
RetargetMiscWebParts(targetWeb, listMap, file);
}
foreach (SPList list in targetWeb.Lists)
{
foreach (SPListItem item in list.Items)
{
if (item.File != null && item.File.Url.ToLowerInvariant().EndsWith(".aspx"))
{
RetargetMiscWebParts(targetWeb, listMap, item.File);
}
}
}
targetWeb.Dispose();
}
/// <summary>
/// Retargets the misc web parts on a specific file. Loops through all web parts on the file. Currently only
/// <see cref="DataFormWebPart"/> and <see cref="ContentByQueryWebPart"/> are considered.
/// </summary>
/// <param name="targetWeb">The target web.</param>
/// <param name="listMap">The list map.</param>
/// <param name="file">The file.</param>
private static void RetargetMiscWebParts(SPWeb targetWeb, StringDictionary listMap, SPFile file)
{
if (file == null)
{
return; // This should never be the case.
}
if (!Utilities.EnsureAspx(file.Url, true, false))
return; // We can only handle aspx and master pages.
if (file.InDocumentLibrary && Utilities.IsCheckedOut(file.Item) && !Utilities.IsCheckedOutByCurrentUser(file.Item))
{
return; // The item is checked out by a different user so leave it alone.
}
bool fileModified = false;
bool wasCheckedOut = true;
SPLimitedWebPartManager manager = null;
try
{
manager = targetWeb.GetLimitedWebPartManager(file.Url, PersonalizationScope.Shared);
SPLimitedWebPartCollection webParts = manager.WebParts;
for (int i = 0; i < webParts.Count; i++)
{
WebPart webPart = null;
try
{
webPart = webParts[i] as WebPart;
if (webPart == null)
{
webParts[i].Dispose();
continue;
}
#if MOSS
if (!(webPart is ContentByQueryWebPart || webPart is DataFormWebPart))
{
webPart.Dispose();
continue;
}
#else
if (!(webPart is DataFormWebPart))
{
webPart.Dispose();
continue;
}
#endif
foreach (string sourceId in listMap.Keys)
{
bool modified = false;
ReplaceWebPartContent.Settings settings = new ReplaceWebPartContent.Settings();
settings.LogFile = null;
settings.Quiet = true;
settings.Test = false;
settings.SearchString = string.Format("(?i:{0})", sourceId);
settings.ReplaceString = listMap[sourceId];
settings.Publish = true;
settings.UnsafeXml = true;
Regex regex = new Regex(settings.SearchString);
// As every web part has different requirements we are only going to consider a small subset.
// Custom web parts will not be addressed as there's no interface that can utilized.
#if MOSS
if (webPart is ContentByQueryWebPart)
{
ContentByQueryWebPart wp = (ContentByQueryWebPart)webPart;
webPart = ReplaceWebPartContent.ReplaceValues(
targetWeb,
file,
settings,
wp,
regex,
ref manager,
ref wasCheckedOut,
ref modified);
}
else
#endif
if (webPart is DataFormWebPart)
{
DataFormWebPart wp = (DataFormWebPart)webPart;
webPart = ReplaceWebPartContent.ReplaceValues(
targetWeb,
file,
settings,
wp,
regex,
ref manager,
ref wasCheckedOut,
ref modified);
}
if (modified)
manager.SaveChanges(webPart);
if (modified)
fileModified = true;
}
}
finally
{
if (webPart != null)
webPart.Dispose();
}
}
if (fileModified)
file.CheckIn(
"Checking in changes to list item due to retargetting of web part as a result of converting a sub-site to a site collection.");
if (file.InDocumentLibrary && fileModified && !wasCheckedOut)
PublishItems.PublishListItem(file.Item, file.Item.ParentList,
new PublishItems.Settings(true, false, null),
"\"stsadm.exe -o repairsitecollectionimportedfromsubsite | convertsubsitetositecollection\"");
}
finally
{
if (manager != null)
{
manager.Web.Dispose(); // manager.Dispose() does not dispose of the SPWeb object and results in a memory leak.
manager.Dispose();
}
}
}
#endregion
#if MOSS
#region Set Global Navigation
/// <summary>
/// Sets the global navigation.
/// </summary>
/// <param name="targetPublishingWeb">The target publishing web.</param>
private static void SetGlobalNavigation(PublishingWeb targetPublishingWeb)
{
SPNavigationNodeCollection globalNodes = targetPublishingWeb.GlobalNavigationNodes;
SPNavigationNodeCollection currentNodes = targetPublishingWeb.CurrentNavigationNodes;
if (globalNodes.Count > 0)
{
return;
}
SetGlobalNavigationRecursiveHelper(currentNodes, globalNodes);
}
/// <summary>
/// Recursive routine for setting the global navigation (necessary to handle child elements).
/// </summary>
/// <param name="currentNodes">The current nodes.</param>
/// <param name="globalNodes">The global nodes.</param>
private static void SetGlobalNavigationRecursiveHelper(SPNavigationNodeCollection currentNodes, SPNavigationNodeCollection globalNodes)
{
// We're going to copy the current nodes in order to set the global nodes (so use current as the default)
for (int i = 0; i < currentNodes.Count; i++)
{
SPNavigationNode currentNode = currentNodes[i];
NodeTypes type = NodeTypes.None;
if (currentNode.Properties["NodeType"] != null)
type = (NodeTypes)Enum.Parse(typeof(NodeTypes), (string)currentNode.Properties["NodeType"]);
SPNavigationNode node = SPNavigationSiteMapNode.CreateSPNavigationNode(
currentNode.Title, currentNode.Url, type, globalNodes);
foreach (DictionaryEntry de in currentNode.Properties)
{
node.Properties[de.Key] = de.Value;
}
node.Update();
node.MoveToLast(globalNodes);
if (currentNode.Children.Count > 0)
SetGlobalNavigationRecursiveHelper(currentNode.Children, node.Children);
}
}
#endregion
/// <summary>
/// Fixes the publishing pages.
/// </summary>
/// <param name="targetSite">The target site.</param>
private static void FixPublishingPages(SPSite targetSite)
{
Log("Progress: Begin fixing publishing pages...");
try
{
foreach (SPWeb web in targetSite.AllWebs)
{
try
{
if (!PublishingWeb.IsPublishingWeb(web))
continue;
PublishingWeb pubweb = PublishingWeb.GetPublishingWeb(web);
Pages.FixPublishingPagesPageLayoutUrl.FixPages(pubweb);
}
finally
{
web.Dispose();
}
}
}
finally
{
Log("Progress: End fixing publishing pages.");
}
}
/// <summary>
/// Fixes the page layouts and sets the site templates.
/// </summary>
/// <param name="sourcePublishingSite">The source publishing site.</param>
/// <param name="sourcePublishingWeb">The source publishing web.</param>
/// <param name="targetPublishingSite">The target publishing site.</param>
/// <param name="targetPublishingWeb">The target publishing web.</param>
/// <param name="targetSite">The target site.</param>
/// <param name="targetWeb">The target web.</param>
private static void FixPageLayoutsAndSiteTemplates(PublishingSite sourcePublishingSite, PublishingWeb sourcePublishingWeb, PublishingSite targetPublishingSite, PublishingWeb targetPublishingWeb, SPSite targetSite, SPWeb targetWeb)
{
try
{
Log("Progress: Begin fixing page layouts and site templates...");
// Next thing we need to do is reset the "__PageLayouts" property - after doing the import
// this value (targetWeb.AllProperties["__PageLayouts"]) will equal "__inherit" but it should
// be either an empty string or an xml list of layouts. Fixing this resolves the xml error
// that we receive when going to "targetSite/_layouts/AreaTemplateSettings.aspx":
// "Data at the root level is invalid. Line 1, position 1". This error occurs when GetAvailablePageLayouts()
// is called - within that method there's call to get to GetEffectiveAvailablePageLayoutsAsString()
// which in this case returns back "__inherit" but should return back "" - as a result the next
// statement after that is a call to IsPropertyAllowingAll - this returns back false because it's not
// an empty string - as a result an XmlDocument.LoadXml() call is made which fails because "__inherit"
// is not valid xml.
if (!string.IsNullOrEmpty(targetWeb.AllProperties["__PageLayouts"] as string))
{
Log(
"Progress: Setting \"__PageLayouts\" web property to string.Empty (current value is \"{0}\")...",
targetWeb.AllProperties["__PageLayouts"] as string);
targetWeb.AllProperties["__PageLayouts"] = string.Empty;
// We need to update the web so that the changes above stick and the following code can execute.
targetWeb.Update();
}
// Reset to make sure everything gets propagated correctly. (Note - this may need to be moved to after we copy the page layouts)
Log("Progress: Setting available page layouts...");
if (sourcePublishingWeb.IsAllowingAllPageLayouts)
targetPublishingWeb.AllowAllPageLayouts(true);
else
targetPublishingWeb.SetAvailablePageLayouts(sourcePublishingWeb.GetAvailablePageLayouts(), true);
// Reset the site templates settings.
if (sourcePublishingWeb.IsAllowingAllWebTemplates)
{
Log("Progress: Allowing all site templates...");
targetPublishingWeb.AllowAllWebTemplates(true);
}
else
{
Log("Progress: Setting explicit site template settings...");
// Handle site templates set explicitly.
Collection<SPWebTemplate> list = new Collection<SPWebTemplate>();
foreach (SPWebTemplate template in sourcePublishingWeb.GetAvailableCrossLanguageWebTemplates())
{
list.Add(template);
}
targetPublishingWeb.SetAvailableCrossLanguageWebTemplates(list, true);
foreach (SPLanguage lang in sourcePublishingWeb.Web.RegionalSettings.InstalledLanguages)
{
list = new Collection<SPWebTemplate>();
foreach (
SPWebTemplate template in sourcePublishingWeb.GetAvailableWebTemplates((uint) lang.LCID))
{
list.Add(template);
}
if (list.Count > 0)
targetPublishingWeb.SetAvailableWebTemplates(list, (uint) lang.LCID, true);
}
}
Log("Progress: Setting page layouts...");
foreach (PageLayout layout in sourcePublishingSite.GetPageLayouts(false))
{
// We need to reset all the page layouts to match that of the source site
// (after the import all the layouts are messed up and treated as plain files and not page layouts
// because the content type is no longer associated).
PageLayoutCollection targetSiteLayouts = targetPublishingSite.GetPageLayouts(false);
PageLayout tempPageLayout = null;
try
{
tempPageLayout = targetSiteLayouts[
targetPublishingSite.PageLayouts.LayoutsDocumentLibrary.RootFolder.ServerRelativeUrl.
TrimStart('/') + "/" + layout.Name];
}
catch (ArgumentException)
{
}
if (tempPageLayout == null)
{
// We didn't find an item in the collection so let's attempt to add it back.
Log("Progress: Source layout {0} not found in target layout collection, searching files...",
layout.Name);
// First we need to see if the file is already there.
SPFile file = null;
foreach (SPListItem item in targetPublishingSite.PageLayouts.LayoutsDocumentLibrary.Items)
{
if (item.Name == layout.Name)
{
// We found the file so exit out of the loop.
file = item.File;
Log("Progress: Layout file found.");
break;
}
}
if (file == null)
{
Log("Progress: Layout file Not found.");
SPList targetMasterGallery =
targetSite.RootWeb.GetCatalog(SPListTemplateType.MasterPageCatalog);
SPFolder targetMasterGalleryFolder = targetMasterGallery.RootFolder;
try
{
Log("Progress: Copying page layout {0} from source site...", layout.Name);
// We couldn't find a file so copy the file from the source.
file = targetMasterGalleryFolder.Files.Add(layout.Name,
layout.ListItem.File.OpenBinary());
}
catch (Exception ex)
{
Log(
string.Format("ERROR: Unable to copy page layout from source location - {0}",
ex.Message), EventLogEntryType.Error);
continue;
}
}
if (file == null)
{
Log("WARNING: Unable to copy page layout from source location.", EventLogEntryType.Warning);
continue;
}
// Reset all the properties on the file so that it's flagged as a PageLayout content type.
Log("Progress: Setting page layout properties...");
SPListItem listItem = file.Item;
listItem[FieldId.Hidden] = layout.ListItem[FieldId.Hidden];
listItem[FieldId.Title] = layout.ListItem[FieldId.Title];
listItem[FieldId.ContentType] =
GetContentType(targetSite.RootWeb, ContentTypeId.PageLayout).Name;
listItem[FieldId.AssociatedContentType] =
new ContentTypeIdFieldValue(GetContentType(targetSite.RootWeb,
layout.AssociatedContentType.Id));
listItem[FieldId.AssociatedVariations] = layout.ListItem[FieldId.AssociatedVariations];
listItem[SPBuiltInFieldId.File_x0020_Type] = layout.ListItem[SPBuiltInFieldId.File_x0020_Type];
// Add any additional properties specific to the page layout
PageLayout newLayout = new PageLayout(listItem);
newLayout.Description = layout.Description;
newLayout.Title = layout.Title;
if (!string.IsNullOrEmpty(layout.PreviewImageUrl))
{
string previewImageUrl =
targetWeb.ServerRelativeUrl +
layout.PreviewImageUrl.Substring(
layout.PreviewImageUrl.IndexOf("/_catalogs/"));
newLayout.PreviewImageUrl = targetSite.MakeFullUrl(previewImageUrl);
}
listItem.SystemUpdate();
if (Utilities.IsCheckedOut(file.Item))
{
Log("Progress: Checking in page layout file...");
file.CheckIn("", SPCheckinType.MajorCheckIn);
if (file.Item.ModerationInformation != null)
file.Approve("");
}
}
}
}
finally
{
Log("Progress: End fixing page layouts and site templates.");
}
}
#endif
/// <summary>
/// Adds the missing features.
/// </summary>
/// <param name="sourceSite">The source site.</param>
/// <param name="sourceWeb">The source web.</param>
/// <param name="targetSite">The target site.</param>
/// <param name="targetWeb">The target web.</param>
public static void AddMissingFeatures(SPSite sourceSite, SPWeb sourceWeb, SPSite targetSite, SPWeb targetWeb)
{
Log("Progress: Begin adding missing features...");
try
{
// Set any features that need to be enabled.
Dictionary<SPFeatureScope, SPFeatureCollection> activeFeatures =
new Dictionary<SPFeatureScope, SPFeatureCollection>();
if (sourceSite != null)
{
activeFeatures[SPFeatureScope.WebApplication] = sourceSite.WebApplication.Features;
activeFeatures[SPFeatureScope.Site] = sourceSite.Features;
}
if (sourceWeb != null)
activeFeatures[SPFeatureScope.Web] = sourceWeb.Features;
// Note that you should be able to use the ActivationDependencies property of the SPDefinition,
// however, I found that this property is not reliable (good example: Publishing is dependent
// on PublishingSite but when you view the ActivationDependencies for the Publishing definition
// it shows zero dependencies.
Queue<SPFeatureDefinition> queuedFeatures = new Queue<SPFeatureDefinition>();
// For some reason these need to be added before the rest...
if (SPFarm.Local.FeatureDefinitions["BaseSite"] != null)
queuedFeatures.Enqueue(SPFarm.Local.FeatureDefinitions["BaseSite"]);
if (SPFarm.Local.FeatureDefinitions["PremiumSite"] != null)
queuedFeatures.Enqueue(SPFarm.Local.FeatureDefinitions["PremiumSite"]);
if (SPFarm.Local.FeatureDefinitions["PublishingSite"] != null)
queuedFeatures.Enqueue(SPFarm.Local.FeatureDefinitions["PublishingSite"]);
foreach (SPFeatureDefinition definition in SPFarm.Local.FeatureDefinitions)
{
try
{
if (definition.Scope == SPFeatureScope.Farm)
continue;
if (!queuedFeatures.Contains(definition))
queuedFeatures.Enqueue(definition);
}
catch (Exception ex)
{
Log("ERROR: {0}", ex.Message);
}
}
while (queuedFeatures.Count > 0)
{
SPFeatureDefinition definition = queuedFeatures.Dequeue();
if (definition == null)
continue;
SPFeatureScope scope = SPFeatureScope.ScopeInvalid;
try
{
scope = definition.Scope;
}
catch (Exception ex)
{
Log("ERROR: {0}", ex.Message);
continue;
}
Guid featureID = definition.Id;
if (activeFeatures[scope] != null)
{
bool isActive = (activeFeatures[scope][featureID] != null);
if (!isActive)
continue;
try
{
switch (scope)
{
case SPFeatureScope.Site:
if (targetSite != null && targetSite.Features[featureID] == null)
{
Log("Progress: Activating site scoped feature \"{0}\"...", definition.DisplayName);
targetSite.Features.Add(featureID);
}
break;
case SPFeatureScope.Web:
if (targetWeb != null && targetWeb.Features[featureID] == null)
{
Log("Progress: Activating web scoped feature \"{0}\"...", definition.DisplayName);
targetWeb.Features.Add(featureID);
}
break;
case SPFeatureScope.WebApplication:
if (sourceSite != null && sourceSite.WebApplication.Features[featureID] == null)
{
Log("Progress: Activating web application scoped feature \"{0}\"...", definition.DisplayName);
sourceSite.WebApplication.Features.Add(featureID);
}
break;
default:
continue;
}
}
catch (ArgumentOutOfRangeException)
{
// We couldn't add the item most likely due a dependent content type that has not yet been added.
queuedFeatures.Enqueue(definition);
}
catch (InvalidOperationException)
{
// We couldn't add the item most likely due to dependencies so add to the back of the queue.
queuedFeatures.Enqueue(definition);
}
catch (ArgumentException)
{
// We couldn't add the item most likely due to dependencies so add to the back of the queue.
queuedFeatures.Enqueue(definition);
}
catch (SPException ex)
{
// This can occur occasionally if the feature does some external modifications (should be rare)
if (ex.Message == "The web being updated was changed by an external process.")
{
if (targetWeb != null)
targetWeb.Close();
if (targetSite != null)
targetWeb = targetSite.OpenWeb();
queuedFeatures.Enqueue(definition);
}
else
{
Log("WARNING: Unable to activate feature '{0} ({1})'\r\n{2}",
definition.DisplayName, definition.Name, ex.Message);
}
}
}
}
}
finally
{
Log("Progress: End adding missing features.");
}
}
/// <summary>
/// Sets the master page gallery settings.
/// </summary>
/// <param name="sourceSite">The source site.</param>
/// <param name="targetSite">The target site.</param>
/// <param name="targetWeb">The target web.</param>
internal static void SetMasterPageGallerySettings(SPSite sourceSite, SPSite targetSite, SPWeb targetWeb)
{
Log("Progress: Begin setting master page gallery settings...");
try
{
SPList targetMasterGallery = targetSite.RootWeb.GetCatalog(SPListTemplateType.MasterPageCatalog);
SPList sourceMasterGallery = sourceSite.RootWeb.GetCatalog(SPListTemplateType.MasterPageCatalog);
// Need to make sure the master gallery has it's content types enabled (get's disabled during the import)
// and make sure all other settings are set to match the source.
Log("Progress: Setting versioning properties...");
targetMasterGallery.ContentTypesEnabled = true;
targetMasterGallery.EnableModeration = sourceMasterGallery.EnableModeration;
targetMasterGallery.EnableVersioning = sourceMasterGallery.EnableVersioning;
targetMasterGallery.EnableMinorVersions = sourceMasterGallery.EnableMinorVersions;
targetMasterGallery.MajorVersionLimit = sourceMasterGallery.MajorVersionLimit;
try
{
targetMasterGallery.MajorWithMinorVersionsLimit = sourceMasterGallery.MajorWithMinorVersionsLimit;
}
catch (NotSupportedException)
{
// If we're here then something is wrong with the source list so just ignore the error.
}
targetMasterGallery.DraftVersionVisibility = sourceMasterGallery.DraftVersionVisibility;
targetMasterGallery.ForceCheckout = sourceMasterGallery.ForceCheckout;
targetMasterGallery.Update();
SPField contentTypeField = targetMasterGallery.Fields.GetFieldByInternalName("ContentType");
if (contentTypeField.Type != SPFieldType.Choice)
{
Log("Progress: ContentType field must be a Choice column (currently is {0})", contentTypeField.TypeAsString);
if (contentTypeField.Type == SPFieldType.Text)
{
#if MOSS
if (PublishingWeb.IsPublishingWeb(targetWeb))
{
Log("Progress: ContentType field is a Text type in a publishing web, attempting to activate PublishingResources to correct...");
Guid publishingResourcesFeatureId = new Guid("AEBC918D-B20F-4a11-A1DB-9ED84D79C87E");
FeatureHelper fh = new FeatureHelper();
fh.ActivateDeactivateFeatureAtSite(targetSite, true, publishingResourcesFeatureId, true, false);
// Get the list again to make sure we're not dealing with a cached copy
targetMasterGallery = targetSite.RootWeb.GetCatalog(SPListTemplateType.MasterPageCatalog);
contentTypeField = targetMasterGallery.Fields.GetFieldByInternalName("ContentType");
if (contentTypeField.Type != SPFieldType.Choice)
{
Log("Progress: Failed to fix ContentType field via PublishingResources feature activation, attempting to copy entire gallery from source...");
CopyList cp = new CopyList();
cp.Copy(sourceSite.MakeFullUrl(sourceMasterGallery.RootFolder.ServerRelativeUrl),
targetSite.Url, null, true, false, true, true, false, false, SPIncludeVersions.All, SPUpdateVersions.Overwrite, true, false, false, false, false);
}
}
#endif
/***** The code below was necessary prior to SP2 (can't be sure of the exact update)
* Activating the publishingresources feature seems to now resolve the issue
* that this code was fixing.
*
*
// Need to reset the content type field – after the import it gets changed to a
// Text field but it needs to be a Choice field otherwise no Page Layout objects
// will be returned.
string childXml =
string.Format(@"<Default>{0}</Default>
<CHOICES>
<CHOICE>{0}</CHOICE>
<CHOICE>{1}</CHOICE>
<CHOICE>{2}</CHOICE>
<CHOICE>{3}</CHOICE>
</CHOICES>",
SPUtility.GetLocalizedString("$Resources:cmscore,contenttype_pagelayout_name;", null, (uint)targetWeb.Locale.LCID),
SPUtility.GetLocalizedString("$Resources:cmscore,contenttype_masterpage_name;", null, (uint)targetWeb.Locale.LCID),
SPUtility.GetLocalizedString("$Resources:core,MasterPage", null, (uint)targetWeb.Locale.LCID),
SPUtility.GetLocalizedString("$Resources:core,Folder", null, (uint)targetWeb.Locale.LCID));
using (SqlConnection connection = new SqlConnection(targetMasterGallery.ParentWeb.Site.ContentDatabase.DatabaseConnectionString))
{
try
{
string sql = "select tp_fields from alllists where tp_id=@listID";
SqlCommand sqlCommand = new SqlCommand(sql, connection);
connection.Open();
sqlCommand.Parameters.AddWithValue("listID", targetMasterGallery.ID.ToString());
string sourceXml = (string)sqlCommand.ExecuteScalar();
string header = sourceXml.Substring(0, sourceXml.IndexOf('<'));
sourceXml = "<Fields>" + sourceXml.Substring(sourceXml.IndexOf('<')) + "</Fields>";
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(sourceXml);
XmlElement fieldElement = (XmlElement)xmlDoc.SelectSingleNode("//Field[@ID='{" + contentTypeField.Id + "}']");
fieldElement.InnerXml = childXml;
fieldElement.SetAttribute("RowOrdinal", "0");
fieldElement.SetAttribute("Type", "Choice");
fieldElement.SetAttribute("Format", "Dropdown");
fieldElement.SetAttribute("FillInChoice", "FALSE");
fieldElement.SetAttribute("Sealed", "FALSE");
fieldElement.SetAttribute("Name", "ContentType");
fieldElement.SetAttribute("ColName", "tp_ContentType");
fieldElement.SetAttribute("SourceID", "http://schemas.microsoft.com/sharepoint/v3");
fieldElement.SetAttribute("ID", "{c042a256-787d-4a6f-8a8a-cf6ab767f12d}");
fieldElement.SetAttribute("DisplayName",
SPUtility.GetLocalizedString("$Resources:core,Content_Type;", null,
(uint) targetWeb.Locale.LCID));
fieldElement.SetAttribute("StaticName", "ContentType");
fieldElement.SetAttribute("Group", "_Hidden");
fieldElement.SetAttribute("PITarget", "MicrosoftWindowsSharePointServices");
fieldElement.SetAttribute("PIAttribute", "ContentTypeID");
sourceXml = header + xmlDoc.DocumentElement.InnerXml;
sql = "update alllists set tp_fields=@fieldXml where tp_id=@listID";
sqlCommand = new SqlCommand(sql, connection);
sqlCommand.Parameters.AddWithValue("listID", targetMasterGallery.ID.ToString());
sqlCommand.Parameters.AddWithValue("fieldXml", sourceXml);
sqlCommand.ExecuteNonQuery();
}
finally
{
if (connection.State != ConnectionState.Closed)
connection.Close();
}
}
* */
}
}
}
finally
{
Log("Progress: End setting master page gallery settings.");
}
}
/*
#region Fix Discussion Lists
/// <summary>
/// Repairs the discussion lists for all webs belonging to the site collection.
/// </summary>
/// <param name="site">The site.</param>
internal static void RepairDiscussionLists(SPSite site)
{
foreach (SPWeb web in site.AllWebs)
{
try
{
RepairDiscussionLists(site, web);
}
finally
{
web.Dispose();
}
}
}
/// <summary>
/// Repairs the discussion list for all lists belonging to the web.
/// </summary>
/// <param name="site">The site.</param>
/// <param name="web">The web.</param>
internal static void RepairDiscussionLists(SPSite site, SPWeb web)
{
foreach (SPList list in web.GetListsOfType(SPBaseType.DiscussionBoard))
{
RepairDiscussionList(site, list);
}
}
/// <summary>
/// Repairs the discussion list.
/// </summary>
/// <param name="site">The site.</param>
/// <param name="list">The list.</param>
internal static void RepairDiscussionList(SPSite site, SPList list)
{
if (list.ContentTypes["Discussion"] == null)
{
return;
}
foreach (SPListItem item in list.Items)
{
SPListItem folder = GetFolderById(list, (int)item["ParentFolderId"]);
if (folder == null)
{
Console.WriteLine("WARNING: Unable to find parent folder for '{0}'", item.Url);
continue;
}
string parentFolder = (string)folder["FileRef"];
string fileRef = parentFolder + "/" + item["FileLeafRef"];
if (fileRef == (string)item["FileRef"])
{
continue;
}
Guid parentFolderGuid = folder.UniqueId;
using (SqlConnection connection = new SqlConnection(site.ContentDatabase.DatabaseConnectionString))
{
try
{
string sql = "select ItemChildCount from AllDocs where id=@itemID";
SqlCommand sqlCommand = new SqlCommand(sql, connection);
connection.Open();
sqlCommand.Parameters.AddWithValue("itemID", parentFolderGuid);
int currentCount = (int)sqlCommand.ExecuteScalar();
currentCount++;
sql = "update AllDocs set DirName=@dir, ParentId=@parentId where Id=@itemID";
sqlCommand = new SqlCommand(sql, connection);
sqlCommand.Parameters.AddWithValue("dir", parentFolder.Trim('/'));
sqlCommand.Parameters.AddWithValue("parentId", parentFolderGuid);
sqlCommand.Parameters.AddWithValue("itemID", item.UniqueId);
sqlCommand.ExecuteNonQuery();
sql = "update AllDocs set ItemChildCount=@count where Id=@itemID";
sqlCommand = new SqlCommand(sql, connection);
sqlCommand.Parameters.AddWithValue("itemID", parentFolderGuid);
sqlCommand.Parameters.AddWithValue("count", currentCount);
sqlCommand.ExecuteNonQuery();
sql = "update AllUserData set tp_DirName=@dir where tp_ID=@itemID and tp_ListId=@listID and tp_SiteId=@siteID";
sqlCommand = new SqlCommand(sql, connection);
sqlCommand.Parameters.AddWithValue("dir", parentFolder.Trim('/'));
sqlCommand.Parameters.AddWithValue("itemID", item.ID);
sqlCommand.Parameters.AddWithValue("listID", list.ID);
sqlCommand.Parameters.AddWithValue("siteID", site.ID);
sqlCommand.ExecuteNonQuery();
}
finally
{
if (connection.State != ConnectionState.Closed)
connection.Close();
}
}
}
}
#endregion
*/
#endregion
#region Helper Methods
/// <summary>
/// Gets the type of the content.
/// </summary>
/// <param name="web">The web.</param>
/// <param name="contentTypeId">The content type id.</param>
/// <returns></returns>
private static SPContentType GetContentType(SPWeb web, SPContentTypeId contentTypeId)
{
SPContentType type = web.AvailableContentTypes[contentTypeId];
if (type == null)
{
throw new SPException("Content Type Not Found In Web " + contentTypeId + ", " + web.Url);
}
return type;
}
/// <summary>
/// Gets the folder by id.
/// </summary>
/// <param name="list">The list.</param>
/// <param name="id">The id.</param>
/// <returns></returns>
private static SPListItem GetFolderById(SPList list, int id)
{
foreach (SPListItem folder in list.Folders)
{
if (id == folder.ID)
return folder;
}
return null;
}
#endregion
}
}