Пример #1
0
        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);
        }
Пример #3
0
        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);
            }
        }
Пример #4
0
        /// <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);
        }
Пример #5
0
        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);
                    }
                }
            }
        }
Пример #6
0
        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);
            }
        }