private void ApplyItemLevelPermissions(ListItem item, ListItemPermission lip, bool alwaysBreakItemLevelPermissions = false) { if (lip == null || item == null) { return; } //item.EnsureProperties(p => p.RoleAssignments, p => p.HasUniqueRoleAssignments); // Break permission inheritance on the item if not done yet if (alwaysBreakItemLevelPermissions || !item.HasUniqueRoleAssignments) { item.BreakRoleInheritance(false, false); this.clientContext.ExecuteQueryRetry(); } // Assign item level permissions foreach (var roleAssignment in lip.RoleAssignments) { if (lip.Principals.TryGetValue(roleAssignment.Member.LoginName, out Principal principal)) { var roleDefinitionBindingCollection = new RoleDefinitionBindingCollection(this.clientContext); foreach (var roleDef in roleAssignment.RoleDefinitionBindings) { roleDefinitionBindingCollection.Add(roleDef); } item.RoleAssignments.Add(principal, roleDefinitionBindingCollection); } } this.clientContext.ExecuteQueryRetry(); }
/// <summary> /// Transform the publishing page /// </summary> /// <param name="publishingPageTransformationInformation">Information about the publishing page to transform</param> /// <returns>The path to the created modern page</returns> public string Transform(PublishingPageTransformationInformation publishingPageTransformationInformation) { SetPageId(Guid.NewGuid().ToString()); var logsForSettings = this.DetailSettingsAsLogEntries(publishingPageTransformationInformation); logsForSettings?.ForEach(o => Log(o, LogLevel.Information)); #region Input validation if (publishingPageTransformationInformation.SourcePage == null) { LogError(LogStrings.Error_SourcePageNotFound, LogStrings.Heading_InputValidation); throw new ArgumentNullException(LogStrings.Error_SourcePageNotFound); } // Validate page and it's eligibility for transformation if (!publishingPageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileRefField) || !publishingPageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileLeafRefField)) { LogError(LogStrings.Error_PageNotValidMissingFileRef, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_PageNotValidMissingFileRef); } string pageType = publishingPageTransformationInformation.SourcePage.PageType(); if (pageType.Equals("ClientSidePage", StringComparison.InvariantCultureIgnoreCase)) { LogError(LogStrings.Error_SourcePageIsModern, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_SourcePageIsModern); } if (pageType.Equals("AspxPage", StringComparison.InvariantCultureIgnoreCase)) { LogError(LogStrings.Error_BasicASPXPageCannotTransform, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_BasicASPXPageCannotTransform); } if (pageType.Equals("WikiPage", StringComparison.InvariantCultureIgnoreCase) || pageType.Equals("WebPartPage", StringComparison.InvariantCultureIgnoreCase)) { LogError(LogStrings.Error_PageIsNotAPublishingPage, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_PageIsNotAPublishingPage); } if (IsDelveBlogPage(pageType)) { LogError(LogStrings.Error_DelveBlogPagesNotSupported, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_DelveBlogPagesNotSupported); } // Disable cross-farm item level permissions from copying CrossFarmTransformationValidation(publishingPageTransformationInformation); LogDebug(LogStrings.ValidationChecksComplete, LogStrings.Heading_InputValidation); #endregion try { #region Telemetry #if DEBUG && MEASURE Start(); #endif DateTime transformationStartDateTime = DateTime.Now; LogDebug(LogStrings.LoadingClientContextObjects, LogStrings.Heading_SharePointConnection); LoadClientObject(sourceClientContext, false); LogInfo($"{sourceClientContext.Web.GetUrl()}", LogStrings.Heading_Summary, LogEntrySignificance.SourceSiteUrl); LogDebug(LogStrings.LoadingTargetClientContext, LogStrings.Heading_SharePointConnection); LoadClientObject(targetClientContext, true); PopulateGlobalProperties(sourceClientContext, targetClientContext); if (sourceClientContext.Site.Id.Equals(targetClientContext.Site.Id)) { // Oops, seems source and target point to the same site collection...that's a no go for publishing portal page transformation! LogError(LogStrings.Error_SameSiteTransferNoAllowedForPublishingPages, LogStrings.Heading_SharePointConnection); throw new ArgumentNullException(LogStrings.Error_SameSiteTransferNoAllowedForPublishingPages); } LogInfo($"{targetClientContext.Web.GetUrl()}", LogStrings.Heading_Summary, LogEntrySignificance.TargetSiteUrl); // Need to add further validation for target template if (targetClientContext.Web.WebTemplate != "SITEPAGEPUBLISHING" && targetClientContext.Web.WebTemplate != "STS" && targetClientContext.Web.WebTemplate != "GROUP" && targetClientContext.Web.WebTemplate != "BDR" && targetClientContext.Web.WebTemplate != "DEV") { LogError(LogStrings.Error_CrossSiteTransferTargetsNonModernSite); throw new ArgumentException(LogStrings.Error_CrossSiteTransferTargetsNonModernSite, LogStrings.Heading_SharePointConnection); } // Ensure PostAsNews is used together with PagePublishing if (publishingPageTransformationInformation.PostAsNews && !publishingPageTransformationInformation.PublishCreatedPage) { publishingPageTransformationInformation.PublishCreatedPage = true; LogWarning(LogStrings.Warning_PostingAPageAsNewsRequiresPagePublishing, LogStrings.Heading_Summary); } // Mark this web as publishing web CacheManager.Instance.SetPublishingWeb(this.sourceClientContext.Web.GetUrl()); // Store the information of the source page we do want to retain if (publishingPageTransformationInformation.KeepPageCreationModificationInformation) { StoreSourcePageInformationToKeep(publishingPageTransformationInformation.SourcePage); } LogInfo($"{publishingPageTransformationInformation.SourcePage[Constants.FileRefField].ToString()}", LogStrings.Heading_Summary, LogEntrySignificance.SourcePage); var spVersion = publishingPageTransformationInformation.SourceVersion; var exactSpVersion = publishingPageTransformationInformation.SourceVersionNumber; LogInfo($"{spVersion.DisplaySharePointVersion()} ({exactSpVersion})", LogStrings.Heading_Summary, LogEntrySignificance.SharePointVersion); LogInfo(LogStrings.TransformationModePublishing, LogStrings.Heading_Summary, LogEntrySignificance.TransformMode); //Load User Mapping File InitializeUserMapping(publishingPageTransformationInformation); #if DEBUG && MEASURE Stop("Telemetry"); #endif #endregion #region Page creation // Detect if the page is living inside a folder LogDebug(LogStrings.DetectIfPageIsInFolder, LogStrings.Heading_PageCreation); string pageFolder = ""; // Get the publishing pages library name this.publishingPagesLibraryName = CacheManager.Instance.GetPublishingPagesLibraryName(this.sourceClientContext); if (publishingPageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileDirRefField)) { var fileRefFieldValue = publishingPageTransformationInformation.SourcePage[Constants.FileDirRefField].ToString(); if (fileRefFieldValue.ContainsIgnoringCasing($"/{this.publishingPagesLibraryName}")) { string pagesLibraryRelativeUrl = $"{sourceClientContext.Web.ServerRelativeUrl.TrimEnd(new[] { '/' })}/{this.publishingPagesLibraryName}"; pageFolder = fileRefFieldValue.Replace(pagesLibraryRelativeUrl, "", StringComparison.InvariantCultureIgnoreCase).Trim(); } else { // Page was living in another list, leave the list name as that will be the folder hosting the modern file in SitePages. // This convention is used to avoid naming conflicts pageFolder = fileRefFieldValue.Replace($"{sourceClientContext.Web.ServerRelativeUrl}", "").Trim(); } if (pageFolder.Length > 0 || !string.IsNullOrEmpty(publishingPageTransformationInformation.TargetPageFolder)) { if (pageFolder.StartsWith("/")) { if (pageFolder == "/") { pageFolder = ""; } else { pageFolder = pageFolder.Substring(1); } } // Add a trailing slash pageFolder = pageFolder + "/"; if (!string.IsNullOrEmpty(publishingPageTransformationInformation.TargetPageFolder)) { // Handle special case <root> to indicate page should be created in the root folder if (publishingPageTransformationInformation.TargetPageFolder.Equals("<root>", StringComparison.InvariantCultureIgnoreCase)) { publishingPageTransformationInformation.TargetPageFolder = ""; } if (publishingPageTransformationInformation.TargetPageFolderOverridesDefaultFolder) { pageFolder = publishingPageTransformationInformation.TargetPageFolder; } else { pageFolder = Path.Combine(pageFolder, publishingPageTransformationInformation.TargetPageFolder); } if (!pageFolder.EndsWith("/")) { // Add a trailing slash pageFolder = pageFolder + "/"; } } LogInfo(LogStrings.PageIsLocatedInFolder, LogStrings.Heading_PageCreation); } } publishingPageTransformationInformation.Folder = pageFolder; // If no targetname specified then we'll come up with one if (string.IsNullOrEmpty(publishingPageTransformationInformation.TargetPageName)) { LogInfo(LogStrings.CrossSiteInUseUsingOriginalFileName, LogStrings.Heading_PageCreation); publishingPageTransformationInformation.TargetPageName = $"{publishingPageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString()}"; } // Check if page name is free to use #if DEBUG && MEASURE Start(); #endif bool pageExists = false; PnPCore.IPage targetPage = null; List pagesLibrary = null; Microsoft.SharePoint.Client.File existingFile = null; //The determines of the target client context has been specified and use that to generate the target page var context = targetClientContext; try { LogDebug(LogStrings.LoadingExistingPageIfExists, LogStrings.Heading_PageCreation); // Just try to load the page in the fastest possible manner, we only want to see if the page exists or not existingFile = Load(sourceClientContext, targetClientContext, publishingPageTransformationInformation, out pagesLibrary); pageExists = true; } catch (Exception ex) { if (ex is ArgumentException) { //Non-critical error generated LogInfo(LogStrings.CheckPageExistsError, LogStrings.Heading_PageCreation); } else { //Something else occurred LogError(LogStrings.CheckPageExistsError, LogStrings.Heading_PageCreation, ex); } } #if DEBUG && MEASURE Stop("Load Page"); #endif if (pageExists) { LogInfo(LogStrings.PageAlreadyExistsInTargetLocation, LogStrings.Heading_PageCreation); if (!publishingPageTransformationInformation.Overwrite) { var message = $"{LogStrings.PageNotOverwriteIfExists} {publishingPageTransformationInformation.TargetPageName}."; LogError(message, LogStrings.Heading_PageCreation); throw new ArgumentException(message); } } // Create the client side page targetPage = context.Web.AddClientSidePage($"{publishingPageTransformationInformation.Folder}{publishingPageTransformationInformation.TargetPageName}"); LogInfo($"{LogStrings.ModernPageCreated} ", LogStrings.Heading_PageCreation); #endregion LogInfo(LogStrings.TransformSourcePageAsArticlePage, LogStrings.Heading_ArticlePageHandling); #region Analysis of the source page #if DEBUG && MEASURE Start(); #endif // Analyze the source page Tuple <Pages.PageLayout, List <WebPartEntity> > pageData = null; LogInfo($"{LogStrings.TransformSourcePageIsPublishingPage} - {LogStrings.TransformSourcePageAnalysing}", LogStrings.Heading_ArticlePageHandling); // Grab the pagelayout mapping to use: var pageLayoutMappingModel = new PageLayoutManager(this.RegisteredLogObservers).GetPageLayoutMappingModel(this.publishingPageTransformation, publishingPageTransformationInformation.SourcePage); if (spVersion == SPVersion.SP2010 || spVersion == SPVersion.SP2013Legacy || spVersion == SPVersion.SP2016Legacy) { pageData = new PublishingPageOnPremises(publishingPageTransformationInformation.SourcePage, pageTransformation, this.publishingPageTransformation, publishingPageTransformationInformation as BaseTransformationInformation, targetContext: targetClientContext, logObservers: base.RegisteredLogObservers).Analyze(pageLayoutMappingModel); } else { pageData = new PublishingPage(publishingPageTransformationInformation.SourcePage, pageTransformation, this.publishingPageTransformation, publishingPageTransformationInformation as BaseTransformationInformation, targetContext: targetClientContext, logObservers: base.RegisteredLogObservers).Analyze(pageLayoutMappingModel); } // Wiki content can contain embedded images and videos, which is not supported by the target RTE...split wiki text blocks so the transformator can handle the images and videos as separate web parts LogInfo(LogStrings.WikiTextContainsImagesVideosReferences, LogStrings.Heading_ArticlePageHandling); pageData = new Tuple <Pages.PageLayout, List <WebPartEntity> >(pageData.Item1, new WikiHtmlTransformator(this.sourceClientContext, this.targetClientContext, targetPage, publishingPageTransformationInformation as BaseTransformationInformation, base.RegisteredLogObservers).TransformPlusSplit(pageData.Item2, publishingPageTransformationInformation.HandleWikiImagesAndVideos, publishingPageTransformationInformation.AddTableListImageAsImageWebPart)); #if DEBUG && MEASURE Stop("Analyze page"); #endif #endregion #region Page title configuration #if DEBUG && MEASURE Start(); #endif // Set page title SetPageTitle(publishingPageTransformationInformation, targetPage); if (publishingPageTransformationInformation.PageTitleOverride != null) { var title = publishingPageTransformationInformation.PageTitleOverride(targetPage.PageTitle); targetPage.PageTitle = title; LogInfo($"{LogStrings.TransformPageTitleOverride} - page title: {title}", LogStrings.Heading_ArticlePageHandling); } #if DEBUG && MEASURE Stop("Set page title"); #endif #endregion #region Page layout configuration #if DEBUG && MEASURE Start(); #endif // Use the default layout transformator ILayoutTransformator layoutTransformator = new LayoutTransformator(targetPage); // Do we have an override? bool useCustomLayoutTransformator = false; if (publishingPageTransformationInformation.LayoutTransformatorOverride != null) { LogInfo(LogStrings.TransformLayoutTransformatorOverride, LogStrings.Heading_ArticlePageHandling); layoutTransformator = publishingPageTransformationInformation.LayoutTransformatorOverride(targetPage); useCustomLayoutTransformator = true; } // Apply the layout to the page layoutTransformator.Transform(pageData); // If needed call the specific publishing page layout transformator if ((pageData.Item1 == Pages.PageLayout.PublishingPage_AutoDetect || pageData.Item1 == Pages.PageLayout.PublishingPage_AutoDetectWithVerticalColumn || pageData.Item1 == Pages.PageLayout.PublishingPage_TwoColumnRightVerticalSection || pageData.Item1 == Pages.PageLayout.PublishingPage_TwoColumnLeftVerticalSection) && !useCustomLayoutTransformator) { // Call out the specific publishing layout transformator implementation PublishingLayoutTransformator publishingLayoutTransformator = new PublishingLayoutTransformator(targetPage, pageLayoutMappingModel, base.RegisteredLogObservers); publishingLayoutTransformator.Transform(pageData); } #if DEBUG && MEASURE Stop("Page layout"); #endif #endregion #region Content transformation LogDebug(LogStrings.PreparingContentTransformation, LogStrings.Heading_ArticlePageHandling); #if DEBUG && MEASURE Start(); #endif // Use the default content transformator IContentTransformator contentTransformator = new ContentTransformator(sourceClientContext, targetClientContext, targetPage, pageTransformation, publishingPageTransformationInformation as BaseTransformationInformation, base.RegisteredLogObservers); // Do we have an override? if (publishingPageTransformationInformation.ContentTransformatorOverride != null) { LogInfo(LogStrings.TransformUsingContentTransformerOverride, LogStrings.Heading_ArticlePageHandling); contentTransformator = publishingPageTransformationInformation.ContentTransformatorOverride(targetPage, pageTransformation); } LogInfo(LogStrings.TransformingContentStart, LogStrings.Heading_ArticlePageHandling); // Run the content transformator contentTransformator.Transform(pageData.Item2.Where(c => !c.IsClosed).ToList()); LogInfo(LogStrings.TransformingContentEnd, LogStrings.Heading_ArticlePageHandling); #if DEBUG && MEASURE Stop("Content transformation"); #endif #endregion #region Configure header for target page #if DEBUG && MEASURE Start(); #endif PublishingPageHeaderTransformator headerTransformator = new PublishingPageHeaderTransformator(publishingPageTransformationInformation, sourceClientContext, targetClientContext, this.publishingPageTransformation, base.RegisteredLogObservers); headerTransformator.TransformHeader(ref targetPage); #if DEBUG && MEASURE Stop("Target page header"); #endif #endregion #region Text/Section/Column cleanup // Drop "empty" text parts. Wiki pages tend to have a lot of text parts just containing div's and BR's...no point in keep those as they generate to much whitespace RemoveEmptyTextParts(targetPage); // Remove empty sections and columns to optimize screen real estate if (publishingPageTransformationInformation.RemoveEmptySectionsAndColumns) { RemoveEmptySectionsAndColumns(targetPage); } #endregion #region Page persisting + permissions #region Save the page #if DEBUG && MEASURE Start(); #endif // Persist the client side page var pageName = $"{publishingPageTransformationInformation.Folder}{publishingPageTransformationInformation.TargetPageName}"; targetPage.Save(pageName); LogInfo($"{LogStrings.TransformSavedPageInCrossSiteCollection}: {pageName}", LogStrings.Heading_ArticlePageHandling); // Load the page list item string pageNameToLoad = pageName; if (pageNameToLoad.StartsWith("/")) { pageNameToLoad = pageNameToLoad.Substring(1); } var savedTargetPage = targetClientContext.Web.GetFileByServerRelativePath(ResourcePath.FromDecodedUrl($"{targetPage.PagesLibrary.RootFolder.ServerRelativeUrl}/{pageNameToLoad}")); targetClientContext.Web.Context.Load(savedTargetPage, p => p.ListItemAllFields); targetClientContext.Web.Context.ExecuteQueryRetry(); #if DEBUG && MEASURE Stop("Persist page"); #endif #endregion #region Page metadata handling PublishingMetadataTransformator publishingMetadataTransformator = new PublishingMetadataTransformator(publishingPageTransformationInformation, sourceClientContext, targetClientContext, savedTargetPage, targetPage, pageLayoutMappingModel, this.publishingPageTransformation, this.userTransformator, base.RegisteredLogObservers); publishingMetadataTransformator.Transform(); #endregion #region Permission handling ListItemPermission listItemPermissionsToKeep = null; if (publishingPageTransformationInformation.KeepPageSpecificPermissions) { #if DEBUG && MEASURE Start(); #endif // Check if we do have item level permissions we want to take over listItemPermissionsToKeep = GetItemLevelPermissions(true, pagesLibrary, publishingPageTransformationInformation.SourcePage, savedTargetPage.ListItemAllFields); // When creating the page in another site collection we'll always want to copy item level permissions if specified ApplyItemLevelPermissions(true, savedTargetPage.ListItemAllFields, listItemPermissionsToKeep); #if DEBUG && MEASURE Stop("Permission handling"); #endif } #endregion #region Page Publishing // Tag the file with a page modernization version stamp string serverRelativePathForModernPage = savedTargetPage.ListItemAllFields[Constants.FileRefField].ToString(); bool pageListItemWasReloaded = false; try { var targetPageFile = context.Web.GetFileByServerRelativeUrl(serverRelativePathForModernPage); context.Load(targetPageFile, p => p.Properties); targetPageFile.Properties["sharepointpnp_pagemodernization"] = this.version; targetPageFile.Update(); // In case of KeepPageCreationModificationInformation and PostAsNews the publishing is handled at the very end of the flow, so skip it right here if (!publishingPageTransformationInformation.KeepPageCreationModificationInformation && !publishingPageTransformationInformation.PostAsNews && publishingPageTransformationInformation.PublishCreatedPage) { // Try to publish, if publish is not needed/possible (e.g. when no minor/major versioning set) then this will return an error that we'll be ignoring targetPageFile.Publish(LogStrings.PublishMessage); } // Ensure we've the most recent page list item loaded, must be last statement before calling ExecuteQuery context.Load(savedTargetPage.ListItemAllFields); // Send both the property update and publish as a single operation to SharePoint context.ExecuteQueryRetry(); pageListItemWasReloaded = true; } catch (Exception) { // Eat exceptions as this is not critical for the generated page LogWarning(LogStrings.Warning_NonCriticalErrorDuringVersionStampAndPublish, LogStrings.Heading_ArticlePageHandling); } // Update flags field to indicate this is a "migrated" page try { // If for some reason the reload batched with the previous request did not finish then do it again if (!pageListItemWasReloaded) { context.Load(savedTargetPage.ListItemAllFields); context.ExecuteQueryRetry(); } // Only perform the update when the field was not yet set bool skipSettingMigratedFromServerRendered = false; if (savedTargetPage.ListItemAllFields[Constants.SPSitePageFlagsField] != null) { skipSettingMigratedFromServerRendered = (savedTargetPage.ListItemAllFields[Constants.SPSitePageFlagsField] as string[]).Contains("MigratedFromServerRendered"); } if (!skipSettingMigratedFromServerRendered) { savedTargetPage.ListItemAllFields[Constants.SPSitePageFlagsField] = ";#MigratedFromServerRendered;#"; // Don't use UpdateOverWriteVersion as the listitem already exists // resulting in an "Additions to this Web site have been blocked" error savedTargetPage.ListItemAllFields.SystemUpdate(); context.Load(savedTargetPage.ListItemAllFields); context.ExecuteQueryRetry(); } } catch (Exception) { // Eat any exception } // Disable page comments on the create page, if needed if (publishingPageTransformationInformation.DisablePageComments) { targetPage.DisableComments(); LogInfo(LogStrings.TransformDisablePageComments, LogStrings.Heading_ArticlePageHandling); } #endregion #region Restore page author/editor/created/modified if ((publishingPageTransformationInformation.KeepPageCreationModificationInformation && this.SourcePageAuthor != null && this.SourcePageEditor != null) || publishingPageTransformationInformation.PostAsNews) { UpdateTargetPageWithSourcePageInformation(savedTargetPage.ListItemAllFields, publishingPageTransformationInformation, serverRelativePathForModernPage, true); } #endregion // NO page updates are allowed anymore past this point as otherwise the set page usage information and published/posted state will be impacted! #region Telemetry if (!publishingPageTransformationInformation.SkipTelemetry && this.pageTelemetry != null) { TimeSpan duration = DateTime.Now.Subtract(transformationStartDateTime); this.pageTelemetry.LogTransformationDone(duration, pageType, publishingPageTransformationInformation); this.pageTelemetry.Flush(); } LogInfo(LogStrings.TransformComplete, LogStrings.Heading_PageCreation); #endregion #region Closing CacheManager.Instance.SetLastUsedTransformator(this); LogInfo($"{serverRelativePathForModernPage}", LogStrings.Heading_Summary, LogEntrySignificance.TargetPage); return(Uri.EscapeUriString(serverRelativePathForModernPage)); #endregion #endregion } catch (Exception ex) { LogError(LogStrings.CriticalError_ErrorOccurred, LogStrings.Heading_Summary, ex, isCriticalException: true); // Throw exception if there's no registered log observers if (base.RegisteredLogObservers.Count == 0) { throw; } } return(string.Empty); }
internal ListItemPermission GetItemLevelPermissions(bool hasTargetContext, List pagesLibrary, ListItem source, ListItem target) { ListItemPermission lip = null; if (source.IsPropertyAvailable("HasUniqueRoleAssignments") && source.HasUniqueRoleAssignments) { // You need to have the ManagePermissions permission before item level permissions can be copied if (pagesLibrary.EffectiveBasePermissions.Has(PermissionKind.ManagePermissions)) { // Copy the unique permissions from source to target // Get the unique permissions this.sourceClientContext.Load(source, a => a.EffectiveBasePermissions, a => a.RoleAssignments.Include(roleAsg => roleAsg.Member.LoginName, roleAsg => roleAsg.RoleDefinitionBindings.Include(roleDef => roleDef.Name, roleDef => roleDef.Description))); this.sourceClientContext.ExecuteQueryRetry(); if (source.EffectiveBasePermissions.Has(PermissionKind.ManagePermissions)) { // Load the site groups this.sourceClientContext.Load(this.sourceClientContext.Web.SiteGroups, p => p.Include(g => g.LoginName)); // Get target page information if (hasTargetContext) { this.targetClientContext.Load(target, p => p.HasUniqueRoleAssignments, p => p.RoleAssignments); this.targetClientContext.Load(this.targetClientContext.Web, p => p.RoleDefinitions); this.targetClientContext.Load(this.targetClientContext.Web.SiteGroups, p => p.Include(g => g.LoginName)); } else { this.sourceClientContext.Load(target, p => p.HasUniqueRoleAssignments, p => p.RoleAssignments); } this.sourceClientContext.ExecuteQueryRetry(); if (hasTargetContext) { this.targetClientContext.ExecuteQueryRetry(); } Dictionary <string, Principal> principals = new Dictionary <string, Principal>(10); lip = new ListItemPermission() { RoleAssignments = source.RoleAssignments, Principals = principals }; // Apply new permissions foreach (var roleAssignment in source.RoleAssignments) { var principal = GetPrincipal(this.sourceClientContext.Web, roleAssignment.Member.LoginName); if (principal != null) { if (!lip.Principals.ContainsKey(roleAssignment.Member.LoginName)) { lip.Principals.Add(roleAssignment.Member.LoginName, principal); } } } } } } LogInfo(LogStrings.TransformGetItemPermissions, LogStrings.Heading_ApplyItemLevelPermissions); return(lip); }
internal void ApplyItemLevelPermissions(bool hasTargetContext, ListItem item, ListItemPermission lip, bool alwaysBreakItemLevelPermissions = false) { if (lip == null || item == null) { return; } // Break permission inheritance on the item if not done yet if (alwaysBreakItemLevelPermissions || !item.HasUniqueRoleAssignments) { item.BreakRoleInheritance(false, false); item.Context.ExecuteQueryRetry(); } if (hasTargetContext) { // Ensure principals are available in the target site Dictionary <string, Principal> targetPrincipals = new Dictionary <string, Principal>(lip.Principals.Count); foreach (var principal in lip.Principals) { var targetPrincipal = GetPrincipal(this.targetClientContext.Web, principal.Key, hasTargetContext); if (targetPrincipal != null) { if (!targetPrincipals.ContainsKey(principal.Key)) { targetPrincipals.Add(principal.Key, targetPrincipal); } } } // Assign item level permissions foreach (var roleAssignment in lip.RoleAssignments) { if (targetPrincipals.TryGetValue(roleAssignment.Member.LoginName, out Principal principal)) { var roleDefinitionBindingCollection = new RoleDefinitionBindingCollection(this.targetClientContext); foreach (var roleDef in roleAssignment.RoleDefinitionBindings) { var targetRoleDef = this.targetClientContext.Web.RoleDefinitions.GetByName(roleDef.Name); if (targetRoleDef != null) { roleDefinitionBindingCollection.Add(targetRoleDef); } } item.RoleAssignments.Add(principal, roleDefinitionBindingCollection); } } this.targetClientContext.ExecuteQueryRetry(); } else { // Assign item level permissions foreach (var roleAssignment in lip.RoleAssignments) { if (lip.Principals.TryGetValue(roleAssignment.Member.LoginName, out Principal principal)) { var roleDefinitionBindingCollection = new RoleDefinitionBindingCollection(this.sourceClientContext); foreach (var roleDef in roleAssignment.RoleDefinitionBindings) { roleDefinitionBindingCollection.Add(roleDef); } item.RoleAssignments.Add(principal, roleDefinitionBindingCollection); } } this.sourceClientContext.ExecuteQueryRetry(); } LogInfo(LogStrings.TransformCopiedItemPermissions, LogStrings.Heading_ApplyItemLevelPermissions); }
/// <summary> /// Transform the publishing page /// </summary> /// <param name="publishingPageTransformationInformation">Information about the publishing page to transform</param> /// <returns>The path to the created modern page</returns> public string Transform(PublishingPageTransformationInformation publishingPageTransformationInformation) { SetPageId(Guid.NewGuid().ToString()); var logsForSettings = this.DetailSettingsAsLogEntries(publishingPageTransformationInformation); logsForSettings?.ForEach(o => Log(o, LogLevel.Information)); #region Input validation if (publishingPageTransformationInformation.SourcePage == null) { LogError(LogStrings.Error_SourcePageNotFound, LogStrings.Heading_InputValidation); throw new ArgumentNullException(LogStrings.Error_SourcePageNotFound); } // Validate page and it's eligibility for transformation if (!publishingPageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileRefField) || !publishingPageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileLeafRefField)) { LogError(LogStrings.Error_PageNotValidMissingFileRef, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_PageNotValidMissingFileRef); } string pageType = publishingPageTransformationInformation.SourcePage.PageType(); if (pageType.Equals("ClientSidePage", StringComparison.InvariantCultureIgnoreCase)) { LogError(LogStrings.Error_SourcePageIsModern, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_SourcePageIsModern); } if (pageType.Equals("AspxPage", StringComparison.InvariantCultureIgnoreCase)) { LogError(LogStrings.Error_BasicASPXPageCannotTransform, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_BasicASPXPageCannotTransform); } if (pageType.Equals("WikiPage", StringComparison.InvariantCultureIgnoreCase) || pageType.Equals("WebPartPage", StringComparison.InvariantCultureIgnoreCase)) { LogError(LogStrings.Error_PageIsNotAPublishingPage, LogStrings.Heading_InputValidation); throw new ArgumentException(LogStrings.Error_PageIsNotAPublishingPage); } // Disable cross-farm item level permissions from copying CrossFarmTransformationValidation(publishingPageTransformationInformation); LogDebug(LogStrings.ValidationChecksComplete, LogStrings.Heading_InputValidation); #endregion try { #region Telemetry #if DEBUG && MEASURE Start(); #endif DateTime transformationStartDateTime = DateTime.Now; LogDebug(LogStrings.LoadingClientContextObjects, LogStrings.Heading_SharePointConnection); LoadClientObject(sourceClientContext); LogInfo($"{sourceClientContext.Web.Url}", LogStrings.Heading_Summary, LogEntrySignificance.SourceSiteUrl); LogDebug(LogStrings.LoadingTargetClientContext, LogStrings.Heading_SharePointConnection); LoadClientObject(targetClientContext); if (sourceClientContext.Site.Id.Equals(targetClientContext.Site.Id)) { // Oops, seems source and target point to the same site collection...that's a no go for publishing portal page transformation! LogError(LogStrings.Error_SameSiteTransferNoAllowedForPublishingPages, LogStrings.Heading_SharePointConnection); throw new ArgumentNullException(LogStrings.Error_SameSiteTransferNoAllowedForPublishingPages); } LogInfo($"{targetClientContext.Web.Url}", LogStrings.Heading_Summary, LogEntrySignificance.TargetSiteUrl); // Need to add further validation for target template if (targetClientContext.Web.WebTemplate != "SITEPAGEPUBLISHING" && targetClientContext.Web.WebTemplate != "STS" && targetClientContext.Web.WebTemplate != "GROUP") { LogError(LogStrings.Error_CrossSiteTransferTargetsNonModernSite); throw new ArgumentException(LogStrings.Error_CrossSiteTransferTargetsNonModernSite, LogStrings.Heading_SharePointConnection); } LogInfo($"{publishingPageTransformationInformation.SourcePage[Constants.FileRefField].ToString().ToLower()}", LogStrings.Heading_Summary, LogEntrySignificance.SourcePage); #if DEBUG && MEASURE Stop("Telemetry"); #endif #endregion #region Page creation // Detect if the page is living inside a folder LogDebug(LogStrings.DetectIfPageIsInFolder, LogStrings.Heading_PageCreation); string pageFolder = ""; // Get the publishing pages library name this.publishingPagesLibrary = CacheManager.Instance.GetPublishingPagesLibraryName(this.sourceClientContext); if (publishingPageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileDirRefField)) { var fileRefFieldValue = publishingPageTransformationInformation.SourcePage[Constants.FileDirRefField].ToString().ToLower(); if (fileRefFieldValue.Contains($"/{this.publishingPagesLibrary}")) { string pagesLibraryRelativeUrl = $"{sourceClientContext.Web.ServerRelativeUrl.TrimEnd(new[] { '/' })}/{this.publishingPagesLibrary}"; pageFolder = fileRefFieldValue.Replace(pagesLibraryRelativeUrl.ToLower(), "").Trim(); } else { // Page was living in another list, leave the list name as that will be the folder hosting the modern file in SitePages. // This convention is used to avoid naming conflicts pageFolder = fileRefFieldValue.Replace($"{sourceClientContext.Web.ServerRelativeUrl}", "").Trim(); } if (pageFolder.Length > 0) { if (pageFolder.Contains("/")) { if (pageFolder == "/") { pageFolder = ""; } else { pageFolder = pageFolder.Substring(1); } } // Add a trailing slash pageFolder = pageFolder + "/"; LogInfo(LogStrings.PageIsLocatedInFolder, LogStrings.Heading_PageCreation); } } publishingPageTransformationInformation.Folder = pageFolder; // If no targetname specified then we'll come up with one if (string.IsNullOrEmpty(publishingPageTransformationInformation.TargetPageName)) { LogInfo(LogStrings.CrossSiteInUseUsingOriginalFileName, LogStrings.Heading_PageCreation); publishingPageTransformationInformation.TargetPageName = $"{publishingPageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString()}"; } // Check if page name is free to use #if DEBUG && MEASURE Start(); #endif bool pageExists = false; ClientSidePage targetPage = null; List pagesLibrary = null; Microsoft.SharePoint.Client.File existingFile = null; //The determines of the target client context has been specified and use that to generate the target page var context = targetClientContext; try { LogDebug(LogStrings.LoadingExistingPageIfExists, LogStrings.Heading_PageCreation); // Just try to load the page in the fastest possible manner, we only want to see if the page exists or not existingFile = Load(sourceClientContext, targetClientContext, publishingPageTransformationInformation, out pagesLibrary); pageExists = true; } catch (Exception ex) { if (ex is ArgumentException) { //Non-critical error generated LogInfo(LogStrings.CheckPageExistsError, LogStrings.Heading_PageCreation); } else { //Something else occurred LogError(LogStrings.CheckPageExistsError, LogStrings.Heading_PageCreation, ex); } } #if DEBUG && MEASURE Stop("Load Page"); #endif if (pageExists) { LogInfo(LogStrings.PageAlreadyExistsInTargetLocation, LogStrings.Heading_PageCreation); if (!publishingPageTransformationInformation.Overwrite) { var message = $"{LogStrings.PageNotOverwriteIfExists} {publishingPageTransformationInformation.TargetPageName}."; LogError(message, LogStrings.Heading_PageCreation); throw new ArgumentException(message); } } // Create the client side page targetPage = context.Web.AddClientSidePage($"{publishingPageTransformationInformation.Folder}{publishingPageTransformationInformation.TargetPageName}"); LogInfo($"{LogStrings.ModernPageCreated} ", LogStrings.Heading_PageCreation); #endregion LogInfo(LogStrings.TransformSourcePageAsArticlePage, LogStrings.Heading_ArticlePageHandling); #region Analysis of the source page #if DEBUG && MEASURE Start(); #endif // Analyze the source page Tuple <Pages.PageLayout, List <WebPartEntity> > pageData = null; LogInfo($"{LogStrings.TransformSourcePageIsPublishingPage} - {LogStrings.TransformSourcePageAnalysing}", LogStrings.Heading_ArticlePageHandling); // Grab the pagelayout mapping to use: var pageLayoutMappingModel = GetPageLayoutMappingModel(publishingPageTransformationInformation.SourcePage); pageData = new PublishingPage(publishingPageTransformationInformation.SourcePage, pageTransformation, this.publishingPageTransformation, publishingPageTransformationInformation as BaseTransformationInformation, targetContext: targetClientContext, logObservers: base.RegisteredLogObservers).Analyze(pageLayoutMappingModel); // Wiki content can contain embedded images and videos, which is not supported by the target RTE...split wiki text blocks so the transformator can handle the images and videos as separate web parts LogInfo(LogStrings.WikiTextContainsImagesVideosReferences, LogStrings.Heading_ArticlePageHandling); pageData = new Tuple <Pages.PageLayout, List <WebPartEntity> >(pageData.Item1, new WikiHtmlTransformator(this.sourceClientContext, targetPage, publishingPageTransformationInformation as BaseTransformationInformation, base.RegisteredLogObservers).TransformPlusSplit(pageData.Item2, publishingPageTransformationInformation.HandleWikiImagesAndVideos)); #if DEBUG && MEASURE Stop("Analyze page"); #endif #endregion #region Page title configuration #if DEBUG && MEASURE Start(); #endif // Set page title SetPageTitle(publishingPageTransformationInformation, targetPage); if (publishingPageTransformationInformation.PageTitleOverride != null) { var title = publishingPageTransformationInformation.PageTitleOverride(targetPage.PageTitle); targetPage.PageTitle = title; LogInfo($"{LogStrings.TransformPageTitleOverride} - page title: {title}", LogStrings.Heading_ArticlePageHandling); } #if DEBUG && MEASURE Stop("Set page title"); #endif #endregion #region Page layout configuration #if DEBUG && MEASURE Start(); #endif // Use the default layout transformator ILayoutTransformator layoutTransformator = new LayoutTransformator(targetPage); // Do we have an override? bool useCustomLayoutTransformator = false; if (publishingPageTransformationInformation.LayoutTransformatorOverride != null) { LogInfo(LogStrings.TransformLayoutTransformatorOverride, LogStrings.Heading_ArticlePageHandling); layoutTransformator = publishingPageTransformationInformation.LayoutTransformatorOverride(targetPage); useCustomLayoutTransformator = true; } // Apply the layout to the page layoutTransformator.Transform(pageData); // If needed call the specific publishing page layout transformator if (pageData.Item1 == Pages.PageLayout.PublishingPage_AutoDetect && !useCustomLayoutTransformator) { // Call out the specific publishing layout transformator implementation PublishingLayoutTransformator publishingLayoutTransformator = new PublishingLayoutTransformator(targetPage, base.RegisteredLogObservers); publishingLayoutTransformator.Transform(pageData); } #if DEBUG && MEASURE Stop("Page layout"); #endif #endregion #region Content transformation LogDebug(LogStrings.PreparingContentTransformation, LogStrings.Heading_ArticlePageHandling); #if DEBUG && MEASURE Start(); #endif // Use the default content transformator IContentTransformator contentTransformator = new ContentTransformator(sourceClientContext, targetPage, pageTransformation, publishingPageTransformationInformation as BaseTransformationInformation, base.RegisteredLogObservers); // Do we have an override? if (publishingPageTransformationInformation.ContentTransformatorOverride != null) { LogInfo(LogStrings.TransformUsingContentTransformerOverride, LogStrings.Heading_ArticlePageHandling); contentTransformator = publishingPageTransformationInformation.ContentTransformatorOverride(targetPage, pageTransformation); } LogInfo(LogStrings.TransformingContentStart, LogStrings.Heading_ArticlePageHandling); // Run the content transformator contentTransformator.Transform(pageData.Item2); LogInfo(LogStrings.TransformingContentEnd, LogStrings.Heading_ArticlePageHandling); #if DEBUG && MEASURE Stop("Content transformation"); #endif #endregion #region Configure header for target page #if DEBUG && MEASURE Start(); #endif PublishingPageHeaderTransformator headerTransformator = new PublishingPageHeaderTransformator(publishingPageTransformationInformation, sourceClientContext, targetClientContext, this.publishingPageTransformation, base.RegisteredLogObservers); headerTransformator.TransformHeader(ref targetPage); #if DEBUG && MEASURE Stop("Target page header"); #endif #endregion #region Text/Section/Column cleanup // Drop "empty" text parts. Wiki pages tend to have a lot of text parts just containing div's and BR's...no point in keep those as they generate to much whitespace RemoveEmptyTextParts(targetPage); // Remove empty sections and columns to optimize screen real estate if (publishingPageTransformationInformation.RemoveEmptySectionsAndColumns) { RemoveEmptySectionsAndColumns(targetPage); } #endregion #region Page persisting + permissions #region Save the page #if DEBUG && MEASURE Start(); #endif // Persist the client side page var pageName = $"{publishingPageTransformationInformation.Folder}{publishingPageTransformationInformation.TargetPageName}"; targetPage.Save(pageName); LogInfo($"{LogStrings.TransformSavedPageInCrossSiteCollection}: {pageName}", LogStrings.Heading_ArticlePageHandling); // Tag the file with a page modernization version stamp string serverRelativePathForModernPage = ReturnModernPageServerRelativeUrl(publishingPageTransformationInformation); try { var targetPageFile = context.Web.GetFileByServerRelativeUrl(serverRelativePathForModernPage); context.Load(targetPageFile, p => p.Properties); targetPageFile.Properties["sharepointpnp_pagemodernization"] = this.version; targetPageFile.Update(); if (publishingPageTransformationInformation.PublishCreatedPage) { // Try to publish, if publish is not needed then this will return an error that we'll be ignoring targetPageFile.Publish("Page modernization initial publish"); } // Send both the property update and publish as a single operation to SharePoint context.ExecuteQueryRetry(); } catch (Exception ex) { // Eat exceptions as this is not critical for the generated page LogWarning(LogStrings.Warning_NonCriticalErrorDuringVersionStampAndPublish, LogStrings.Heading_ArticlePageHandling); } // Disable page comments on the create page, if needed if (publishingPageTransformationInformation.DisablePageComments) { targetPage.DisableComments(); LogInfo(LogStrings.TransformDisablePageComments, LogStrings.Heading_ArticlePageHandling); } #if DEBUG && MEASURE Stop("Persist page"); #endif #endregion #region Page metadata handling PublishingMetadataTransformator publishingMetadataTransformator = new PublishingMetadataTransformator(publishingPageTransformationInformation, sourceClientContext, targetClientContext, targetPage, pageLayoutMappingModel, this.publishingPageTransformation, base.RegisteredLogObservers); publishingMetadataTransformator.Transform(); #endregion #region Permission handling ListItemPermission listItemPermissionsToKeep = null; if (publishingPageTransformationInformation.KeepPageSpecificPermissions) { #if DEBUG && MEASURE Start(); #endif // Check if we do have item level permissions we want to take over listItemPermissionsToKeep = GetItemLevelPermissions(true, pagesLibrary, publishingPageTransformationInformation.SourcePage, targetPage.PageListItem); // When creating the page in another site collection we'll always want to copy item level permissions if specified ApplyItemLevelPermissions(true, targetPage.PageListItem, listItemPermissionsToKeep); #if DEBUG && MEASURE Stop("Permission handling"); #endif } #endregion #region Telemetry if (!publishingPageTransformationInformation.SkipTelemetry && this.pageTelemetry != null) { TimeSpan duration = DateTime.Now.Subtract(transformationStartDateTime); this.pageTelemetry.LogTransformationDone(duration, pageType, publishingPageTransformationInformation); this.pageTelemetry.Flush(); } LogInfo(LogStrings.TransformComplete, LogStrings.Heading_PageCreation); #endregion #region Closing CacheManager.Instance.SetLastUsedTransformator(this); return(serverRelativePathForModernPage); #endregion #endregion } catch (Exception ex) { LogError(LogStrings.CriticalError_ErrorOccurred, LogStrings.Heading_Summary, ex, isCriticalException: true); // Throw exception if there's no registered log observers if (base.RegisteredLogObservers.Count == 0) { throw; } } return(string.Empty); }
/// <summary> /// Transform the page /// </summary> /// <param name="pageTransformationInformation">Information about the page to transform</param> /// <returns>The path to created modern page</returns> public string Transform(PageTransformationInformation pageTransformationInformation) { #region Input validation if (pageTransformationInformation.SourcePage == null) { throw new ArgumentNullException("SourcePage cannot be null"); } // Validate page and it's eligibility for transformation if (!pageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileRefField) || !pageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileLeafRefField)) { throw new ArgumentException("Page is not valid due to missing FileRef or FileLeafRef value"); } string pageType = pageTransformationInformation.SourcePage.PageType(); if (pageType.Equals("ClientSidePage", StringComparison.InvariantCultureIgnoreCase)) { throw new ArgumentException("Page already is a modern client side page...no need to transform it."); } if (pageType.Equals("AspxPage", StringComparison.InvariantCultureIgnoreCase)) { throw new ArgumentException("Page is an basic aspx page...can't currently transform that one, sorry!"); } if (pageType.Equals("PublishingPage", StringComparison.InvariantCultureIgnoreCase)) { throw new ArgumentException("Page transformation for publishing pages is currently not supported."); } #endregion #region Telemetry #if DEBUG && MEASURE Start(); #endif DateTime transformationStartDateTime = DateTime.Now; clientContext.ClientTag = $"SPDev:PageTransformator"; // Load all web properties needed further one clientContext.Load(clientContext.Web, p => p.Id, p => p.ServerRelativeUrl, p => p.RootFolder.WelcomePage, p => p.Url); clientContext.Load(clientContext.Site, p => p.RootWeb.ServerRelativeUrl, p => p.Id); // Use regular ExecuteQuery as we want to send this custom clienttag clientContext.ExecuteQuery(); #if DEBUG && MEASURE Stop("Telemetry"); #endif #endregion #region Page creation // Detect if the page is living inside a folder string pageFolder = ""; if (pageTransformationInformation.SourcePage.FieldExistsAndUsed(Constants.FileDirRefField)) { var fileRefFieldValue = pageTransformationInformation.SourcePage[Constants.FileDirRefField].ToString(); pageFolder = fileRefFieldValue.Replace($"{clientContext.Web.ServerRelativeUrl}/SitePages", "").Trim(); if (pageFolder.Length > 0) { if (pageFolder.Contains("/")) { if (pageFolder == "/") { pageFolder = ""; } else { pageFolder = pageFolder.Substring(1); } } // Add a trailing slash pageFolder = pageFolder + "/"; } } pageTransformationInformation.Folder = pageFolder; // If no targetname specified then we'll come up with one if (string.IsNullOrEmpty(pageTransformationInformation.TargetPageName)) { if (string.IsNullOrEmpty(pageTransformationInformation.TargetPagePrefix)) { pageTransformationInformation.SetDefaultTargetPagePrefix(); } pageTransformationInformation.TargetPageName = $"{pageTransformationInformation.TargetPagePrefix}{pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString()}"; } // Check if page name is free to use #if DEBUG && MEASURE Start(); #endif bool pageExists = false; ClientSidePage targetPage = null; List pagesLibrary = null; Microsoft.SharePoint.Client.File existingFile = null; try { // Just try to load the page in the fastest possible manner, we only want to see if the page exists or not existingFile = Load(clientContext, pageTransformationInformation, out pagesLibrary); pageExists = true; } catch (ArgumentException) { } #if DEBUG && MEASURE Stop("Load Page"); #endif if (pageExists) { if (!pageTransformationInformation.Overwrite) { throw new ArgumentException($"There already exists a page with name {pageTransformationInformation.TargetPageName}."); } } // Create the client side page targetPage = clientContext.Web.AddClientSidePage($"{pageTransformationInformation.Folder}{pageTransformationInformation.TargetPageName}"); #endregion #region Home page handling #if DEBUG && MEASURE Start(); #endif bool replacedByOOBHomePage = false; // Check if the transformed page is the web's home page if (clientContext.Web.RootFolder.IsPropertyAvailable("WelcomePage") && !string.IsNullOrEmpty(clientContext.Web.RootFolder.WelcomePage)) { var homePageUrl = clientContext.Web.RootFolder.WelcomePage; var homepageName = Path.GetFileName(clientContext.Web.RootFolder.WelcomePage); if (homepageName.Equals(pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString(), StringComparison.InvariantCultureIgnoreCase)) { targetPage.LayoutType = ClientSidePageLayoutType.Home; if (pageTransformationInformation.ReplaceHomePageWithDefaultHomePage) { targetPage.KeepDefaultWebParts = true; replacedByOOBHomePage = true; } } } #if DEBUG && MEASURE Stop("Home page handling"); #endif #endregion #region Article page handling if (!replacedByOOBHomePage) { #region Configure header from target page #if DEBUG && MEASURE Start(); #endif if (pageTransformationInformation.PageHeader == null || pageTransformationInformation.PageHeader.Type == ClientSidePageHeaderType.None) { targetPage.RemovePageHeader(); } else if (pageTransformationInformation.PageHeader.Type == ClientSidePageHeaderType.Default) { targetPage.SetDefaultPageHeader(); } else if (pageTransformationInformation.PageHeader.Type == ClientSidePageHeaderType.Custom) { targetPage.SetCustomPageHeader(pageTransformationInformation.PageHeader.ImageServerRelativeUrl, pageTransformationInformation.PageHeader.TranslateX, pageTransformationInformation.PageHeader.TranslateY); } #if DEBUG && MEASURE Stop("Target page header"); #endif #endregion #region Analysis of the source page #if DEBUG && MEASURE Start(); #endif // Analyze the source page Tuple <PageLayout, List <WebPartEntity> > pageData = null; if (pageType.Equals("WikiPage", StringComparison.InvariantCultureIgnoreCase)) { pageData = new WikiPage(pageTransformationInformation.SourcePage, pageTransformation).Analyze(); // Wiki pages can contain embedded images and videos, which is not supported by the target RTE...split wiki text blocks so the transformator can handle the images and videos as separate web parts pageData = new Tuple <PageLayout, List <WebPartEntity> >(pageData.Item1, new WikiTransformatorSimple().TransformPlusSplit(pageData.Item2, pageTransformationInformation.HandleWikiImagesAndVideos)); } else if (pageType.Equals("WebPartPage", StringComparison.InvariantCultureIgnoreCase)) { pageData = new WebPartPage(pageTransformationInformation.SourcePage, pageTransformation).Analyze(true); } #if DEBUG && MEASURE Stop("Analyze page"); #endif #endregion #region Page title configuration #if DEBUG && MEASURE Start(); #endif // Set page title if (pageType.Equals("WikiPage", StringComparison.InvariantCultureIgnoreCase)) { SetPageTitle(pageTransformationInformation, targetPage); } else if (pageType.Equals("WebPartPage")) { bool titleFound = false; var titleBarWebPart = pageData.Item2.Where(p => p.Type == WebParts.TitleBar).FirstOrDefault(); if (titleBarWebPart != null) { if (titleBarWebPart.Properties.ContainsKey("HeaderTitle") && !string.IsNullOrEmpty(titleBarWebPart.Properties["HeaderTitle"])) { targetPage.PageTitle = titleBarWebPart.Properties["HeaderTitle"]; titleFound = true; } } if (!titleFound) { SetPageTitle(pageTransformationInformation, targetPage); } } if (pageTransformationInformation.PageTitleOverride != null) { targetPage.PageTitle = pageTransformationInformation.PageTitleOverride(targetPage.PageTitle); } #if DEBUG && MEASURE Stop("Set page title"); #endif #endregion #region Page layout configuration #if DEBUG && MEASURE Start(); #endif // Use the default layout transformator ILayoutTransformator layoutTransformator = new LayoutTransformator(targetPage); // Do we have an override? if (pageTransformationInformation.LayoutTransformatorOverride != null) { layoutTransformator = pageTransformationInformation.LayoutTransformatorOverride(targetPage); } // Apply the layout to the page layoutTransformator.Transform(pageData.Item1); #if DEBUG && MEASURE Stop("Page layout"); #endif #endregion #region Page Banner creation if (!pageTransformationInformation.TargetPageTakesSourcePageName) { if (pageTransformationInformation.ModernizationCenterInformation != null && pageTransformationInformation.ModernizationCenterInformation.AddPageAcceptBanner) { #if DEBUG && MEASURE Start(); #endif // Bump the row values for the existing web parts as we've inserted a new section foreach (var section in targetPage.Sections) { section.Order = section.Order + 1; } // Add new section for banner part targetPage.Sections.Insert(0, new CanvasSection(targetPage, CanvasSectionTemplate.OneColumn, 0)); // Bump the row values for the existing web parts as we've inserted a new section foreach (var webpart in pageData.Item2) { webpart.Row = webpart.Row + 1; } var sourcePageUrl = pageTransformationInformation.SourcePage[Constants.FileRefField].ToString(); var orginalSourcePageName = pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString(); Uri host = new Uri(clientContext.Web.Url); string path = $"{host.Scheme}://{host.DnsSafeHost}{sourcePageUrl.Replace(pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString(), "")}"; // Add "fake" banner web part that then will be transformed onto the page Dictionary <string, string> props = new Dictionary <string, string>(2) { { "SourcePage", $"{path}{orginalSourcePageName}" }, { "TargetPage", $"{path}{pageTransformationInformation.TargetPageName}" } }; WebPartEntity bannerWebPart = new WebPartEntity() { Type = WebParts.PageAcceptanceBanner, Column = 1, Row = 1, Title = "", Order = 0, Properties = props, }; pageData.Item2.Insert(0, bannerWebPart); #if DEBUG && MEASURE Stop("Page Banner"); #endif } } #endregion #region Content transformation #if DEBUG && MEASURE Start(); #endif // Use the default content transformator IContentTransformator contentTransformator = new ContentTransformator(targetPage, pageTransformation); // Do we have an override? if (pageTransformationInformation.ContentTransformatorOverride != null) { contentTransformator = pageTransformationInformation.ContentTransformatorOverride(targetPage, pageTransformation); } // Run the content transformator contentTransformator.Transform(pageData.Item2); #if DEBUG && MEASURE Stop("Content transformation"); #endif #endregion } #endregion #region Page persisting + permissions #region Save the page #if DEBUG && MEASURE Start(); #endif // Persist the client side page targetPage.Save($"{pageTransformationInformation.Folder}{pageTransformationInformation.TargetPageName}", existingFile, pagesLibrary); // Tag the file with a page modernization version stamp try { string path = pageTransformationInformation.SourcePage[Constants.FileRefField].ToString().Replace(pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString(), ""); var targetPageUrl = $"{path}{pageTransformationInformation.TargetPageName}"; var targetPageFile = this.clientContext.Web.GetFileByServerRelativeUrl(targetPageUrl); this.clientContext.Load(targetPageFile, p => p.Properties); targetPageFile.Properties["sharepointpnp_pagemodernization"] = this.version; targetPageFile.Update(); // Try to publish, if publish is not needed then this will return an error that we'll be ignoring targetPageFile.Publish("Page modernization initial publish"); this.clientContext.ExecuteQueryRetry(); } catch (Exception ex) { // Eat exceptions as this is not critical for the generated page } #if DEBUG && MEASURE Stop("Persist page"); #endif #endregion #region Page metadata handling if (pageTransformationInformation.CopyPageMetadata) { #if DEBUG && MEASURE Start(); #endif // Copy the page metadata CopyPageMetadata(pageTransformationInformation, targetPage, pagesLibrary); #if DEBUG && MEASURE Stop("Page metadata handling"); #endif } #endregion #region Permission handling ListItemPermission listItemPermissionsToKeep = null; if (pageTransformationInformation.KeepPageSpecificPermissions) { #if DEBUG && MEASURE Start(); #endif // Check if we do have item level permissions we want to take over listItemPermissionsToKeep = GetItemLevelPermissions(pagesLibrary, pageTransformationInformation.SourcePage, targetPage.PageListItem); if (!pageTransformationInformation.TargetPageTakesSourcePageName) { // If we're not doing a page name swap now we need to update the target item with the needed item level permissions ApplyItemLevelPermissions(targetPage.PageListItem, listItemPermissionsToKeep); } #if DEBUG && MEASURE Stop("Permission handling"); #endif } #endregion #region Page name switching // All went well so far...swap pages if that's needed if (pageTransformationInformation.TargetPageTakesSourcePageName) { #if DEBUG && MEASURE Start(); #endif //Load the source page SwapPages(pageTransformationInformation, listItemPermissionsToKeep); #if DEBUG && MEASURE Stop("Pagename swap"); #endif } #endregion #region Telemetry if (!pageTransformationInformation.SkipTelemetry && this.pageTelemetry != null) { TimeSpan duration = DateTime.Now.Subtract(transformationStartDateTime); this.pageTelemetry.LogTransformationDone(duration); this.pageTelemetry.Flush(); } #endregion #region Return final page url if (!pageTransformationInformation.TargetPageTakesSourcePageName) { string path = pageTransformationInformation.SourcePage[Constants.FileRefField].ToString().Replace(pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString(), ""); var targetPageUrl = $"{path}{pageTransformationInformation.TargetPageName}"; return(targetPageUrl); } else { return(pageTransformationInformation.SourcePage[Constants.FileRefField].ToString()); } #endregion #endregion }
private ListItemPermission GetItemLevelPermissions(List pagesLibrary, ListItem source, ListItem target) { ListItemPermission lip = null; if (source.HasUniqueRoleAssignments) { // You need to have the ManagePermissions permission before item level permissions can be copied if (pagesLibrary.EffectiveBasePermissions.Has(PermissionKind.ManagePermissions)) { // Copy the unique permissions from source to target // Get the unique permissions this.clientContext.Load(source, a => a.EffectiveBasePermissions, a => a.RoleAssignments.Include(roleAsg => roleAsg.Member.LoginName, roleAsg => roleAsg.RoleDefinitionBindings.Include(roleDef => roleDef.Name, roleDef => roleDef.Description))); this.clientContext.ExecuteQueryRetry(); if (source.EffectiveBasePermissions.Has(PermissionKind.ManagePermissions)) { // Load the site groups this.clientContext.Load(this.clientContext.Web.SiteGroups, p => p.Include(g => g.LoginName)); // Get target page information //this.clientContext.Load(target, p => p.HasUniqueRoleAssignments, a => a.RoleAssignments.Include(roleAsg => roleAsg.Member.LoginName, // roleAsg => roleAsg.RoleDefinitionBindings.Include(roleDef => roleDef.Name, roleDef => roleDef.Description))); this.clientContext.Load(target, p => p.HasUniqueRoleAssignments, p => p.RoleAssignments); this.clientContext.ExecuteQueryRetry(); Dictionary <string, Principal> principals = new Dictionary <string, Principal>(10); lip = new ListItemPermission() { RoleAssignments = source.RoleAssignments, Principals = principals }; // Break permission inheritance on the target page if not done yet //if (!target.HasUniqueRoleAssignments) //{ // target.BreakRoleInheritance(false, false); // this.clientContext.ExecuteQueryRetry(); //} // Apply new permissions foreach (var roleAssignment in source.RoleAssignments) { var principal = GetPrincipal(this.clientContext.Web, roleAssignment.Member.LoginName); if (principal != null) { if (!lip.Principals.ContainsKey(roleAssignment.Member.LoginName)) { lip.Principals.Add(roleAssignment.Member.LoginName, principal); } //var roleDefinitionBindingCollection = new RoleDefinitionBindingCollection(this.clientContext); //foreach (var roleDef in roleAssignment.RoleDefinitionBindings) //{ // roleDefinitionBindingCollection.Add(roleDef); //} //target.RoleAssignments.Add(principal, roleDefinitionBindingCollection); } } //this.clientContext.ExecuteQueryRetry(); } } } return(lip); }
/// <summary> /// Performs the logic needed to swap a genered Migrated_Page.aspx to Page.aspx and then Page.aspx to Old_Page.aspx /// </summary> /// <param name="pageTransformationInformation">Information about the page to transform</param> public void SwapPages(PageTransformationInformation pageTransformationInformation, ListItemPermission listItemPermissionsToKeep) { var sourcePageUrl = pageTransformationInformation.SourcePage[Constants.FileRefField].ToString(); var orginalSourcePageName = pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString(); string path = sourcePageUrl.Replace(pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString(), ""); var sourcePage = this.clientContext.Web.GetFileByServerRelativeUrl(sourcePageUrl); this.clientContext.Load(sourcePage); this.clientContext.ExecuteQueryRetry(); if (string.IsNullOrEmpty(pageTransformationInformation.SourcePagePrefix)) { pageTransformationInformation.SetDefaultSourcePagePrefix(); } var newSourcePageUrl = $"{pageTransformationInformation.SourcePagePrefix}{pageTransformationInformation.SourcePage[Constants.FileLeafRefField].ToString()}"; // Rename source page using the sourcepageprefix // STEP1: First copy the source page to a new name. We on purpose use CopyTo as we want to avoid that "linked" url's get // patched up during a MoveTo operation as that would also patch the url's in our new modern page sourcePage.CopyTo($"{path}{newSourcePageUrl}", true); this.clientContext.ExecuteQueryRetry(); // Restore the item level permissions on the copied page (if any) if (pageTransformationInformation.KeepPageSpecificPermissions && listItemPermissionsToKeep != null) { // load the copied target file var newSource = this.clientContext.Web.GetFileByServerRelativeUrl($"{path}{newSourcePageUrl}"); this.clientContext.Load(newSource); this.clientContext.Load(newSource.ListItemAllFields, p => p.RoleAssignments); this.clientContext.ExecuteQueryRetry(); // Reload source page ApplyItemLevelPermissions(newSource.ListItemAllFields, listItemPermissionsToKeep, alwaysBreakItemLevelPermissions: true); } //Load the created target page var targetPageUrl = $"{path}{pageTransformationInformation.TargetPageName}"; var targetPageFile = this.clientContext.Web.GetFileByServerRelativeUrl(targetPageUrl); this.clientContext.Load(targetPageFile); this.clientContext.ExecuteQueryRetry(); // STEP2: Fix possible navigation entries to point to the "copied" source page first // Rename the target page to the original source page name // CopyTo and MoveTo with option to overwrite first internally delete the file to overwrite, which // results in all page navigation nodes pointing to this file to be deleted. Hence let's point these // navigation entries first to the copied version of the page we just created this.clientContext.Web.Context.Load(this.clientContext.Web, w => w.Navigation.QuickLaunch, w => w.Navigation.TopNavigationBar); this.clientContext.Web.Context.ExecuteQueryRetry(); bool navWasFixed = false; IQueryable <NavigationNode> currentNavNodes = null; IQueryable <NavigationNode> globalNavNodes = null; var currentNavigation = this.clientContext.Web.Navigation.QuickLaunch; var globalNavigation = this.clientContext.Web.Navigation.TopNavigationBar; // Check for nav nodes currentNavNodes = currentNavigation.Where(n => n.Url.Equals(sourcePageUrl, StringComparison.InvariantCultureIgnoreCase)); globalNavNodes = globalNavigation.Where(n => n.Url.Equals(sourcePageUrl, StringComparison.InvariantCultureIgnoreCase)); if (currentNavNodes.Count() > 0 || globalNavNodes.Count() > 0) { navWasFixed = true; foreach (var node in currentNavNodes) { node.Url = $"{path}{newSourcePageUrl}"; node.Update(); } foreach (var node in globalNavNodes) { node.Url = $"{path}{newSourcePageUrl}"; node.Update(); } this.clientContext.ExecuteQueryRetry(); } // STEP3: Now copy the created modern page over the original source page, at this point the new page has the same name as the original page had before transformation targetPageFile.CopyTo($"{path}{orginalSourcePageName}", true); this.clientContext.ExecuteQueryRetry(); // Apply the item level permissions on the final page (if any) if (pageTransformationInformation.KeepPageSpecificPermissions && listItemPermissionsToKeep != null) { // load the copied target file var newTarget = this.clientContext.Web.GetFileByServerRelativeUrl($"{path}{orginalSourcePageName}"); this.clientContext.Load(newTarget); this.clientContext.Load(newTarget.ListItemAllFields, p => p.RoleAssignments); this.clientContext.ExecuteQueryRetry(); ApplyItemLevelPermissions(newTarget.ListItemAllFields, listItemPermissionsToKeep, alwaysBreakItemLevelPermissions: true); } // STEP4: Finish with restoring the page navigation: update the navlinks to point back the original page name if (navWasFixed) { // Reload the navigation entries as did update them this.clientContext.Web.Context.Load(this.clientContext.Web, w => w.Navigation.QuickLaunch, w => w.Navigation.TopNavigationBar); this.clientContext.Web.Context.ExecuteQueryRetry(); currentNavigation = this.clientContext.Web.Navigation.QuickLaunch; globalNavigation = this.clientContext.Web.Navigation.TopNavigationBar; if (!string.IsNullOrEmpty($"{path}{newSourcePageUrl}")) { currentNavNodes = currentNavigation.Where(n => n.Url.Equals($"{path}{newSourcePageUrl}", StringComparison.InvariantCultureIgnoreCase)); globalNavNodes = globalNavigation.Where(n => n.Url.Equals($"{path}{newSourcePageUrl}", StringComparison.InvariantCultureIgnoreCase)); } foreach (var node in currentNavNodes) { node.Url = sourcePageUrl; node.Update(); } foreach (var node in globalNavNodes) { node.Url = sourcePageUrl; node.Update(); } this.clientContext.ExecuteQueryRetry(); } //STEP5: Conclude with deleting the originally created modern page as we did copy that already in step 3 targetPageFile.DeleteObject(); this.clientContext.ExecuteQueryRetry(); }