private async Task CopyPageMetadataAsync(IFile targetFile, MappingProviderOutput mappingOutput, Guid taskId) { // Ensure the properties to define the cache key var targetWeb = targetFile.PnPContext.Web; await targetWeb.EnsurePropertiesAsync(w => w.Id).ConfigureAwait(false); var targetSite = targetFile.PnPContext.Site; await targetSite.EnsurePropertiesAsync(s => s.Id).ConfigureAwait(false); await targetFile.EnsurePropertiesAsync(f => f.ListId).ConfigureAwait(false); // Retrieve the list of fields from cache var fieldsToCopy = await GetFieldsFromCache(targetFile, targetWeb, targetSite).ConfigureAwait(false); //bool listItemWasReloaded = false; if (fieldsToCopy.Count > 0) { // Load the list item corresponding to the file await targetFile.LoadAsync(f => f.ListItemAllFields).ConfigureAwait(false); var targetItem = targetFile.ListItemAllFields; // Initially set the content type Id - Are we sure about this excerpt? //targetItem[PageConstants.ContentTypeId] = mappingOutput.Metadata[PageConstants.ContentTypeId]?.Value; //await targetItem.UpdateOverwriteVersionAsync().ConfigureAwait(false); // TODO: Complete metadata fields handling (taxonomy with mapping provider and other fields) foreach (var fieldToCopy in fieldsToCopy.Where(f => f.Type == "TaxonomyFieldTypeMulti" || f.Type == "TaxonomyFieldType")) { logger.LogInformation( TransformationResources.Info_MappingTaxonomyField .CorrelateString(taskId), fieldToCopy.Name); // https://pnp.github.io/pnpcore/using-the-sdk/listitems-fields.html#taxonomy-fields } foreach (var fieldToCopy in fieldsToCopy.Where(f => f.Type != "TaxonomyFieldTypeMulti" && f.Type != "TaxonomyFieldType")) { logger.LogInformation( TransformationResources.Info_MappingRegularField .CorrelateString(taskId), fieldToCopy.Name); // https://pnp.github.io/pnpcore/using-the-sdk/listitems-fields.html } } }
/// <summary> /// Transforms a page from the configured data source to a modern SharePoint Online page /// </summary> /// <param name="task">The context of the transformation process</param> /// <param name="token">The cancellation token, if any</param> /// <returns>The URL of the transformed page</returns> public virtual async Task <Uri> TransformAsync(PageTransformationTask task, CancellationToken token = default) { if (task == null) { throw new ArgumentNullException(nameof(task)); } logger.LogInformation( TransformationResources.Info_RunningTransformationTask.CorrelateString(task.Id), task.Id, task.SourceItemId.Id); // Get the source item by id var sourceItem = await task.SourceProvider.GetItemAsync(task.SourceItemId, token).ConfigureAwait(false); // Resolve the target page uri var targetPageUri = await targetPageUriResolver.ResolveAsync(sourceItem, task.TargetContext, token).ConfigureAwait(false); // Call pre transformations handlers var preContext = new PagePreTransformationContext(task, sourceItem, targetPageUri); foreach (var pagePreTransformation in pagePreTransformations) { await pagePreTransformation.PreTransformAsync(preContext, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); } // Save start date and time for telemetry DateTime transformationStartDateTime = DateTime.Now; // Invoke the configured main mapping provider var context = new PageTransformationContext(task, sourceItem, targetPageUri); var input = new MappingProviderInput(context); MappingProviderOutput output = await mappingProvider.MapAsync(input, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); // Here we generate the actual SPO modern page in SharePoint Online var generatedPage = await pageGenerator.GenerateAsync(context, output, targetPageUri, token).ConfigureAwait(false); var generatedPageUri = generatedPage.GeneratedPageUrl; token.ThrowIfCancellationRequested(); // Save duration for telemetry TimeSpan duration = DateTime.Now.Subtract(transformationStartDateTime); // Save telemetry var telemetryProperties = output.TelemetryProperties.Merge(generatedPage.TelemetryProperties); // Add global telemetry properties telemetryProperties.Add(TelemetryService.CorrelationId, task.Id.ToString()); telemetryProperties.Add(TelemetryService.AADTenantId, context.Task.TargetContext.GlobalOptions.AADTenantId.ToString()); this.telemetry.LogTransformationCompleted(duration, telemetryProperties); // Call post transformations handlers var postContext = new PagePostTransformationContext(task, sourceItem, generatedPageUri); foreach (var pagePostTransformation in pagePostTransformations) { await pagePostTransformation.PostTransformAsync(postContext, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); } return(generatedPageUri); }
internal async Task SetAuthorInPageHeaderAsync(PageTransformationContext context, MappingProviderOutput mappingOutput, IPage targetClientSidePage, Guid taskId, CancellationToken token = default) { // Try to get th var userMappingProvider = serviceProvider.GetService <IUserMappingProvider>(); if (userMappingProvider == null) { throw new ApplicationException(TransformationResources.Error_MissingUserMappingProvider); } try { var mappedAuthorOutput = await userMappingProvider.MapUserAsync(new UserMappingProviderInput(context, mappingOutput.TargetPage.Author), token).ConfigureAwait(false); var ensuredPageAuthorUser = await targetClientSidePage.PnPContext.Web.EnsureUserAsync(mappedAuthorOutput.UserPrincipalName).ConfigureAwait(false); if (ensuredPageAuthorUser != null) { var author = ensuredPageAuthorUser.ToUserEntity(); if (author != null) { if (!author.IsGroup) { // Don't serialize null values var jsonSerializerOptions = new JsonSerializerOptions() { #if NET5_0_OR_GREATER DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, #else IgnoreNullValues = true, #endif }; var json = JsonSerializer.Serialize(author, jsonSerializerOptions); if (!string.IsNullOrEmpty(json)) { targetClientSidePage.PageHeader.Authors = json; } } } else { logger.LogWarning( TransformationResources.Warning_PageHeaderAuthorNotSet .CorrelateString(taskId), mappingOutput.TargetPage.Author); } } else { logger.LogWarning( TransformationResources.Warning_PageHeaderAuthorNotSet .CorrelateString(taskId), mappingOutput.TargetPage.Author); } } catch (Exception ex) { logger.LogWarning( TransformationResources.Warning_PageHeaderAuthorNotSetGenericError .CorrelateString(taskId), ex.Message); } }
/// <summary> /// Translates the provided input into a page /// </summary> /// <param name="context">Page transformation options</param> /// <param name="mappingOutput">The mapping provider output</param> /// <param name="targetPageUri">The url for the target page</param> /// <param name="token">Cancellation token</param> /// <returns>The result of the page generation</returns> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="ApplicationException"></exception> public async Task <PageGeneratorOutput> GenerateAsync(PageTransformationContext context, MappingProviderOutput mappingOutput, Uri targetPageUri, CancellationToken token = default) { #region Validate input arguments if (context == null) { throw new ArgumentNullException(nameof(context)); } if (mappingOutput == null) { throw new ArgumentNullException(nameof(mappingOutput)); } if (targetPageUri == null) { throw new ArgumentNullException(nameof(targetPageUri)); } #endregion var result = new PageGeneratorOutput(); #region Validate target site // Ensure to have the needed target web properties var targetWeb = context.Task.TargetContext.Web; await targetWeb.EnsurePropertiesAsync(w => w.WebTemplate).ConfigureAwait(false); // Check target site if (targetWeb.WebTemplate != "SITEPAGEPUBLISHING" && targetWeb.WebTemplate != "STS" && targetWeb.WebTemplate != "GROUP" && targetWeb.WebTemplate != "BDR" && targetWeb.WebTemplate != "DEV") { logger.LogError( TransformationResources.Error_CrossSiteTransferTargetsNonModernSite .CorrelateString(context.Task.Id)); throw new ArgumentException(TransformationResources.Error_CrossSiteTransferTargetsNonModernSite); } #endregion #region Validate and create the target page // Ensure PostAsNews is used together with PagePublishing if (this.defaultPageTransformationOptions.PostAsNews && !this.defaultPageTransformationOptions.PublishPage) { this.defaultPageTransformationOptions.PublishPage = true; logger.LogWarning( TransformationResources.Warning_PostingAPageAsNewsRequiresPagePublishing .CorrelateString(context.Task.Id)); } // Check if the target page already exists string targetPageUriString = targetPageUri.IsAbsoluteUri ? targetPageUri.AbsolutePath : targetPageUri.ToString(); IFile targetFile = null; var targetFileExists = false; try { targetFile = await targetWeb.GetFileByServerRelativeUrlAsync(targetPageUriString).ConfigureAwait(false); targetFileExists = true; } catch (SharePointRestServiceException) { // Simply ignore this exception and assume that the page does not exist targetFileExists = false; } if (targetFileExists) { logger.LogInformation( TransformationResources.Info_PageAlreadyExistsInTargetLocation .CorrelateString(context.Task.Id), targetPageUri); if (!this.defaultPageTransformationOptions.Overwrite) { // Raise an exception and stop the process if Overwrite is not allowed var errorMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, TransformationResources.Error_PageNotOverwriteIfExists, targetPageUri.ToString()); logger.LogError(errorMessage .CorrelateString(context.Task.Id)); throw new ApplicationException(errorMessage); } } // Create the client side page using PnP Core SDK and save it var targetPage = await targetWeb.NewPageAsync().ConfigureAwait(false); var targetPageFilePath = $"{mappingOutput.TargetPage.Folder}{mappingOutput.TargetPage.PageName}"; await targetPage.SaveAsync(targetPageFilePath).ConfigureAwait(false); // Reload the generated file targetFile = await targetWeb.GetFileByServerRelativeUrlAsync(targetPageUriString).ConfigureAwait(false); #endregion #region Check if the page is the home page logger.LogDebug( TransformationResources.Debug_TransformCheckIfPageIsHomePage .CorrelateString(context.Task.Id)); // Check if the page is the home page bool replacedByOOBHomePage = false; // Check if the transformed page is the web's home page if (mappingOutput.TargetPage.IsHomePage) { targetPage.LayoutType = PnP.Core.Model.SharePoint.PageLayoutType.Home; if (this.defaultPageTransformationOptions.ReplaceHomePageWithDefaultHomePage) { targetPage.KeepDefaultWebParts = true; replacedByOOBHomePage = true; logger.LogInformation( TransformationResources.Info_TransformSourcePageHomePageUsingStock .CorrelateString(context.Task.Id)); } } // If it is not the home, let's define the actual page structure if (!replacedByOOBHomePage) { logger.LogInformation( TransformationResources.Info_TransformSourcePageAsArticlePage .CorrelateString(context.Task.Id)); #region Configure header from target page if (mappingOutput.TargetPage.PageHeader == null || (mappingOutput.TargetPage.PageHeader as PageHeader).Type == PageHeaderType.None) { logger.LogInformation( TransformationResources.Info_TransformArticleSetHeaderToNone .CorrelateString(context.Task.Id)); if (mappingOutput.TargetPage.SetAuthorInPageHeader) { targetPage.SetDefaultPageHeader(); targetPage.PageHeader.LayoutType = PageHeaderLayoutType.NoImage; logger.LogInformation( TransformationResources.Info_TransformArticleSetHeaderToNoneWithAuthor .CorrelateString(context.Task.Id)); await SetAuthorInPageHeaderAsync(context, mappingOutput, targetPage, context.Task.Id, token).ConfigureAwait(false); } else { targetPage.RemovePageHeader(); } } else if ((mappingOutput.TargetPage.PageHeader as PageHeader).Type == PageHeaderType.Default) { logger.LogInformation( TransformationResources.Info_TransformArticleSetHeaderToDefault .CorrelateString(context.Task.Id)); targetPage.SetDefaultPageHeader(); } else if ((mappingOutput.TargetPage.PageHeader as PageHeader).Type == PageHeaderType.Custom) { var infoMessage = $"{TransformationResources.Info_TransformArticleSetHeaderToCustom} {TransformationResources.Info_TransformArticleHeaderImageUrl} {mappingOutput.TargetPage.PageHeader.ImageServerRelativeUrl}"; logger.LogInformation(infoMessage .CorrelateString(context.Task.Id)); targetPage.SetCustomPageHeader(mappingOutput.TargetPage.PageHeader.ImageServerRelativeUrl, mappingOutput.TargetPage.PageHeader.TranslateX, mappingOutput.TargetPage.PageHeader.TranslateY); } #endregion } #endregion // Set page title targetPage.PageTitle = mappingOutput.TargetPage.PageTitle; // Create the web parts and the transformed content // Process all the sections, columns, and controls await GenerateTargetCanvasControlsAsync(targetWeb, targetPage, mappingOutput, context.Task.Id).ConfigureAwait(false); // Process metadata await CopyPageMetadataAsync(targetFile, mappingOutput, context.Task.Id).ConfigureAwait(false); // TODO: Process permissions #region Leftover code, most likely to be removed //if (transformationInformation.SkipHiddenWebParts) //{ // webParts = webParts.Where(c => !c.Hidden).ToList(); //} // Persist the new client side page // Set metadata fields for list item of the page // Configure page item permissions // if (KeepPageSpecificPermissions) { } // Other page settings //#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(); // if (!pageTransformationInformation.KeepPageCreationModificationInformation && // !pageTransformationInformation.PostAsNews && // pageTransformationInformation.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 (pageTransformationInformation.DisablePageComments) //{ // targetPage.DisableComments(); // LogInfo(LogStrings.TransformDisablePageComments, LogStrings.Heading_ArticlePageHandling); //} //#endregion // Swap pages? //#region Restore page author/editor/created/modified //if ((pageTransformationInformation.SourcePage != null && pageTransformationInformation.KeepPageCreationModificationInformation && this.SourcePageAuthor != null && this.SourcePageEditor != null) || // pageTransformationInformation.PostAsNews) //{ // UpdateTargetPageWithSourcePageInformation(finalListItemToUpdate, pageTransformationInformation, finalListItemToUpdate[Constants.FileRefField].ToString(), hasTargetContext); //} //#endregion // Log generation completed #endregion // Save the generated page await targetPage.SaveAsync(targetPageFilePath).ConfigureAwait(false); // Restore page author/editor/created/modified if ((this.defaultPageTransformationOptions.KeepPageCreationModificationInformation && mappingOutput.TargetPage.Author != null && mappingOutput.TargetPage.Editor != null) || this.defaultPageTransformationOptions.PostAsNews) { await UpdateTargetPageWithSourcePageInformationAsync(targetFile, mappingOutput, context.Task.Id).ConfigureAwait(false); } // Return the generated page URL var generatedPageFile = await targetPage.GetPageFileAsync(f => f.ServerRelativeUrl).ConfigureAwait(false); var generatedPageUri = new Uri($"{targetWeb.Url.Scheme}://{targetWeb.Url.Host}{generatedPageFile.ServerRelativeUrl}"); // Validate the URI of the output page if (!generatedPageUri.AbsoluteUri.Equals(targetPageUri.AbsoluteUri, StringComparison.InvariantCultureIgnoreCase)) { throw new ApplicationException(TransformationResources.Error_InvalidTargetPageUri); } result.GeneratedPageUrl = targetPageUri; return(result); }
private async Task GenerateTargetCanvasControlsAsync(IWeb targetWeb, IPage targetPage, MappingProviderOutput mappingOutput, Guid taskId) { // Prepare global tokens var globalTokens = await PrepareGlobalTokensAsync(targetWeb).ConfigureAwait(false); // Get the list of components available in the current site var componentsToAdd = await GetClientSideComponentsAsync(targetPage).ConfigureAwait(false); int sectionOrder = 0; foreach (var section in mappingOutput.TargetPage.Sections) { section.Order = sectionOrder; targetPage.AddSection(section.CanvasTemplate, sectionOrder); var targetSection = targetPage.Sections[sectionOrder]; sectionOrder++; int columnOrder = 0; int controlOrder = 0; foreach (var column in section.Columns) { var targetColumn = targetSection.Columns[columnOrder]; columnOrder++; controlOrder++; foreach (var control in column.Controls) { GenerateTargetCanvasControl(targetPage, componentsToAdd, controlOrder, targetColumn, control, globalTokens, taskId); } } } }
private async Task UpdateTargetPageWithSourcePageInformationAsync(IFile targetFile, MappingProviderOutput mappingOutput, Guid taskId) { try { // Ensure the properties to define the cache key var targetWeb = targetFile.PnPContext.Web; await targetWeb.EnsurePropertiesAsync(w => w.Id).ConfigureAwait(false); // Load the list item corresponding to the file await targetFile.LoadAsync(f => f.ListItemAllFields).ConfigureAwait(false); var targetItem = targetFile.ListItemAllFields; // Update the target page information properties var pageAuthorUser = await targetWeb.EnsureUserAsync(mappingOutput.TargetPage.Author).ConfigureAwait(false); var pageEditorUser = await targetWeb.EnsureUserAsync(mappingOutput.TargetPage.Editor).ConfigureAwait(false); // Prep a new FieldUserValue object instance and update the list item var pageAuthor = new FieldUserValue() { LookupValue = pageAuthorUser.Title, LookupId = pageAuthorUser.Id }; var pageEditor = new FieldUserValue() { LookupValue = pageEditorUser.Title, LookupId = pageEditorUser.Id }; if (this.defaultPageTransformationOptions.KeepPageCreationModificationInformation || this.defaultPageTransformationOptions.PostAsNews) { if (this.defaultPageTransformationOptions.KeepPageCreationModificationInformation) { // All 4 fields have to be set! targetItem[SharePointConstants.CreatedByField] = pageAuthor; targetItem[SharePointConstants.ModifiedByField] = pageEditor; targetItem[SharePointConstants.CreatedField] = mappingOutput.TargetPage.Created; targetItem[SharePointConstants.ModifiedField] = mappingOutput.TargetPage.Modified; } if (this.defaultPageTransformationOptions.PostAsNews) { targetItem[SharePointConstants.PromotedStateField] = "2"; // Determine what will be the publishing date that will show up in the news rollup if (this.defaultPageTransformationOptions.KeepPageCreationModificationInformation) { targetItem[SharePointConstants.FirstPublishedDateField] = mappingOutput.TargetPage.Modified; } else { targetItem[SharePointConstants.FirstPublishedDateField] = targetItem[SharePointConstants.ModifiedField]; } } await targetItem.UpdateOverwriteVersionAsync().ConfigureAwait(false); if (this.defaultPageTransformationOptions.PublishPage) { await targetFile.PublishAsync(TransformationResources.Info_PublishMessage).ConfigureAwait(false); } } } catch (Exception ex) { // Eat exceptions as this is not critical for the generated page logger.LogWarning( TransformationResources.Warning_NonCriticalErrorDuringPublish .CorrelateString(taskId), ex.Message); } }