Ejemplo n.º 1
0
        /// <summary>
        /// Scans a list for "modern" compatibility
        /// </summary>
        /// <param name="file">List form page to start the scan from</param>
        /// <param name="list">List linked to the form page</param>
        /// <returns>Object describing modern compatiblity</returns>
        public static ListScanResult ModernCompatability(this File file, List list, ref ConcurrentStack <ScanError> scanErrors)
        {
            if (file == null)
            {
                throw new ArgumentNullException("file");
            }

            if (list == null)
            {
                throw new ArgumentNullException("list");
            }

            ClientContext cc = file.Context as ClientContext;

            ListScanResult result = new ListScanResult();

            // Load properties
            file.EnsureProperties(p => p.PageRenderType);

            // If it works in modern, we're good
            if (file.PageRenderType == ListPageRenderType.Modern)
            {
                // let's return since we know it will work
                return(result);
            }
            else
            {
                result.PageRenderType = file.PageRenderType;
            }

            // Hmmm...it's not working, so let's list *all* reasons why it's not working in modern

            // Step 1: load the tenant / site / web / list level blocking options
            // Tenant
            // Currently we've no API to detect tenant setting...but since we anyhow should scan all lists this does not matter that much

            // Site
            Site site = cc.Site;

            site.EnsureProperties(p => p.Features, p => p.Url);
            result.BlockedAtSiteLevel = site.Features.Where(f => f.DefinitionId == FeatureId_Site_Modern).Count() > 0;
            // Web
            cc.Web.EnsureProperties(p => p.Features, p => p.Url);
            result.BlockedAtWebLevel = cc.Web.Features.Where(f => f.DefinitionId == FeatureId_Web_Modern).Count() > 0;
            // List
            list.EnsureProperties(p => p.ListExperienceOptions, p => p.UserCustomActions, p => p.BaseTemplate);
            result.ListExperience = list.ListExperienceOptions;
            result.XsltViewWebPartCompatibility.ListBaseTemplate = list.BaseTemplate;

            if (list.ListExperienceOptions == ListExperience.ClassicExperience)
            {
                result.BlockedAtListLevel = true;
            }

            // Step 2: verify we can load a web part manager and ensure there's only one web part of the page
            LimitedWebPartManager wpm;

            try
            {
                wpm = file.GetLimitedWebPartManager(PersonalizationScope.Shared);
                file.Context.Load(wpm.WebParts, wps => wps.Include(wp => wp.WebPart.Title, wp => wp.WebPart.Properties));
                file.Context.ExecuteQueryRetry();
            }
            catch (Exception ex)
            {
                result.BlockedByNotBeingAbleToLoadPage          = true;
                result.BlockedByNotBeingAbleToLoadPageException = ex.ToString();
                return(result);
            }

            if (wpm.WebParts.Count != 1)
            {
                result.BlockedByZeroOrMultipleWebParts = true;
                return(result);
            }

            var webPart = wpm.WebParts[0].WebPart;

            // Step 3: Inspect the web part used to render the list
            // Step 3.1: JSLink web part check
            if (webPart.Properties.FieldValues.Keys.Contains("JSLink"))
            {
                if (webPart.Properties["JSLink"] != null && !String.IsNullOrEmpty(webPart.Properties["JSLink"].ToString()) && webPart.Properties["JSLink"].ToString().ToLower() != "clienttemplates.js")
                {
                    result.XsltViewWebPartCompatibility.BlockedByJSLink = true;
                    result.XsltViewWebPartCompatibility.JSLink          = webPart.Properties["JSLink"].ToString();
                }
            }

            // Step 3.2: XslLink web part check
            if (webPart.Properties.FieldValues.Keys.Contains("XslLink"))
            {
                if (webPart.Properties["XslLink"] != null && !String.IsNullOrEmpty(webPart.Properties["XslLink"].ToString()) && webPart.Properties["XslLink"].ToString().ToLower() != "main.xsl")
                {
                    result.XsltViewWebPartCompatibility.BlockedByXslLink = true;
                    result.XsltViewWebPartCompatibility.XslLink          = webPart.Properties["XslLink"].ToString();
                }
            }

            // Step 3.3: Xsl web part check
            if (webPart.Properties.FieldValues.Keys.Contains("Xsl"))
            {
                if (webPart.Properties["Xsl"] != null && !String.IsNullOrEmpty(webPart.Properties["Xsl"].ToString()))
                {
                    result.XsltViewWebPartCompatibility.BlockedByXsl = true;
                }
            }

            // Step 3.4: Process fields in view
            if (webPart.Properties.FieldValues.Keys.Contains("XmlDefinition"))
            {
                if (webPart.Properties["XmlDefinition"] != null && !String.IsNullOrEmpty(webPart.Properties["XmlDefinition"].ToString()))
                {
                    try
                    {
                        // Get the fields in this view
                        var viewFields = GetViewFields(webPart.Properties["XmlDefinition"].ToString());

                        // Load fields in one go
                        List <Field> fieldsToProcess = new List <Field>(viewFields.Count);
                        try
                        {
                            foreach (var viewField in viewFields)
                            {
                                Field field = list.Fields.GetByInternalNameOrTitle(viewField);
                                cc.Load(field, p => p.JSLink, p => p.TypeAsString, p => p.FieldTypeKind, p => p.InternalName);
                                fieldsToProcess.Add(field);
                            }
                            cc.ExecuteQueryRetry();
                        }
                        catch
                        {
                            // try to load the fields again, but now individually so we can collect the needed errors + evaulate the fields that do load
                            fieldsToProcess.Clear();
                            foreach (var viewField in viewFields)
                            {
                                try
                                {
                                    Field field = list.Fields.GetByInternalNameOrTitle(viewField);
                                    cc.Load(field, p => p.JSLink, p => p.TypeAsString, p => p.FieldTypeKind);
                                    cc.ExecuteQueryRetry();
                                    fieldsToProcess.Add(field);
                                }
                                catch (Exception ex)
                                {
                                    ScanError error = new ScanError()
                                    {
                                        Error      = ex.Message,
                                        SiteURL    = cc.Web.Url,
                                        SiteColUrl = site.Url
                                    };
                                    scanErrors.Push(error);
                                    Console.WriteLine("Error for site {1}: {0}", ex.Message, cc.Web.Url);
                                }
                            }
                        }

                        // Verify the fields
                        foreach (var field in fieldsToProcess)
                        {
                            try
                            {
                                // JSLink on field
                                if (!string.IsNullOrEmpty(field.JSLink) && field.JSLink != "clienttemplates.js" &&
                                    field.JSLink != "sp.ui.reputation.js" && !field.IsTaxField())
                                {
                                    result.XsltViewWebPartCompatibility.BlockedByJSLinkField = true;
                                    result.XsltViewWebPartCompatibility.JSLinkFields         = string.IsNullOrEmpty(result.XsltViewWebPartCompatibility.JSLinkFields) ? $"{field.InternalName}" : $"{result.XsltViewWebPartCompatibility.JSLinkFields},{field.InternalName}";
                                }

                                //Business data field
                                if (field.IsBusinessDataField())
                                {
                                    result.XsltViewWebPartCompatibility.BlockedByBusinessDataField = true;
                                    result.XsltViewWebPartCompatibility.BusinessDataFields         = string.IsNullOrEmpty(result.XsltViewWebPartCompatibility.BusinessDataFields) ? $"{field.InternalName}" : $"{result.XsltViewWebPartCompatibility.BusinessDataFields},{field.InternalName}";
                                }

                                // Geolocation field
                                if (field.FieldTypeKind == FieldType.Geolocation)
                                {
                                    result.XsltViewWebPartCompatibility.BlockedByGeoLocationField = true;
                                    result.XsltViewWebPartCompatibility.GeoLocationFields         = string.IsNullOrEmpty(result.XsltViewWebPartCompatibility.GeoLocationFields) ? $"{field.InternalName}" : $"{result.XsltViewWebPartCompatibility.GeoLocationFields},{field.InternalName}";
                                }

                                // TaskOutcome field
                                if (field.IsTaskOutcomeField())
                                {
                                    result.XsltViewWebPartCompatibility.BlockedByTaskOutcomeField = true;
                                    result.XsltViewWebPartCompatibility.TaskOutcomeFields         = string.IsNullOrEmpty(result.XsltViewWebPartCompatibility.TaskOutcomeFields) ? $"{field.InternalName}" : $"{result.XsltViewWebPartCompatibility.TaskOutcomeFields},{field.InternalName}";
                                }

                                // Publishing field
                                if (field.IsPublishingField())
                                {
                                    result.XsltViewWebPartCompatibility.BlockedByPublishingField = true;
                                    result.XsltViewWebPartCompatibility.PublishingFields         = string.IsNullOrEmpty(result.XsltViewWebPartCompatibility.PublishingFields) ? $"{field.InternalName}" : $"{result.XsltViewWebPartCompatibility.PublishingFields},{field.InternalName}";
                                }
                            }
                            catch (Exception ex)
                            {
                                ScanError error = new ScanError()
                                {
                                    Error      = ex.Message,
                                    SiteURL    = cc.Web.Url,
                                    SiteColUrl = site.Url,
                                };
                                scanErrors.Push(error);
                                Console.WriteLine("Error for site {1}: {0}", ex.Message, cc.Web.Url);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        ScanError error = new ScanError()
                        {
                            Error      = ex.Message,
                            SiteURL    = cc.Web.Url,
                            SiteColUrl = site.Url
                        };
                        scanErrors.Push(error);
                        Console.WriteLine("Error for site {1}: {0}", ex.Message, cc.Web.Url);
                    }
                }
            }

            // Step 3.5: Process list custom actions
            if (list.UserCustomActions.Count > 0)
            {
                foreach (var customAction in list.UserCustomActions)
                {
                    if (!string.IsNullOrEmpty(customAction.Location) && customAction.Location.Equals("scriptlink", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (!string.IsNullOrEmpty(customAction.ScriptSrc))
                        {
                            result.XsltViewWebPartCompatibility.BlockedByListCustomAction = true;
                            result.XsltViewWebPartCompatibility.ListCustomActions         = string.IsNullOrEmpty(result.XsltViewWebPartCompatibility.ListCustomActions) ? $"{customAction.Name}" : $"{result.XsltViewWebPartCompatibility.ListCustomActions},{customAction.Name}";
                        }
                    }
                }
            }

            // Step 3.6: managed metadata navigation is not an issue anymore
            result.XsltViewWebPartCompatibility.BlockedByManagedMetadataNavFeature = false;

            // Step 4: check the view
            if (webPart.Properties.FieldValues.Keys.Contains("ViewFlags") && webPart.Properties["ViewFlags"] != null && !String.IsNullOrEmpty(webPart.Properties["ViewFlags"].ToString()))
            {
                uint flags;
                if (uint.TryParse(webPart.Properties["ViewFlags"].ToString(), out flags))
                {
                    if ((flags & ViewFlag_Gantt) != 0 || (flags & ViewFlag_Calendar) != 0 || (flags & ViewFlag_Grid) != 0)
                    {
                        result.XsltViewWebPartCompatibility.BlockedByViewType = true;
                        if ((flags & ViewFlag_Gantt) != 0)
                        {
                            result.XsltViewWebPartCompatibility.ViewType = "Gantt";
                        }
                        else if ((flags & ViewFlag_Calendar) != 0)
                        {
                            result.XsltViewWebPartCompatibility.ViewType = "Calendar";
                        }
                        else if ((flags & ViewFlag_Grid) != 0)
                        {
                            if (list.BaseTemplate == (int)ListTemplateType.GenericList ||
                                list.BaseTemplate == (int)ListTemplateType.DocumentLibrary)
                            {
                                // unblock...we've added support for datasheet rendering for custom lists in July 2018 (see https://techcommunity.microsoft.com/t5/Microsoft-SharePoint-Blog/Updates-to-metadata-handling-and-list-templates/ba-p/202113)
                                result.XsltViewWebPartCompatibility.BlockedByViewType = false;
                            }
                            else
                            {
                                result.XsltViewWebPartCompatibility.ViewType = "Grid";
                            }
                        }
                    }
                }
            }

            // Step 5: check the list
            // Step 5.1: check the base template
            if (!list.CanRenderNewExperience())
            {
                result.XsltViewWebPartCompatibility.BlockedByListBaseTemplate = true;
                result.XsltViewWebPartCompatibility.ListBaseTemplate          = list.BaseTemplate;
            }

            return(result);
        }
        /// <summary>
        /// Analyze the web
        /// </summary>
        /// <param name="cc">ClientContext of the web to be analyzed</param>
        /// <returns>Duration of the analysis</returns>
        public override TimeSpan Analyze(ClientContext cc)
        {
            try
            {
                base.Analyze(cc);

                var baseUri   = new Uri(this.SiteUrl);
                var webAppUrl = baseUri.Scheme + "://" + baseUri.Host;

                var lists = cc.Web.GetListsToScan();

                foreach (var list in lists)
                {
                    try
                    {
                        this.ScanJob.IncreaseScannedLists();

                        ListScanResult listScanData;
                        if (list.DefaultViewUrl.ToLower().Contains(".aspx"))
                        {
                            File file = cc.Web.GetFileByServerRelativeUrl(list.DefaultViewUrl);
                            listScanData = file.ModernCompatability(list, ref this.ScanJob.ScanErrors);
                        }
                        else
                        {
                            listScanData = new ListScanResult()
                            {
                                BlockedByNotBeingAbleToLoadPage = true
                            };
                        }

                        if (listScanData != null && !listScanData.WorksInModern)
                        {
                            if (this.ScanJob.ExcludeListsOnlyBlockedByOobReasons && listScanData.OnlyBlockedByOOBReasons)
                            {
                                continue;
                            }

                            listScanData.SiteURL    = this.SiteUrl;
                            listScanData.ListUrl    = $"{webAppUrl}{list.DefaultViewUrl}";
                            listScanData.SiteColUrl = this.SiteCollectionUrl;
                            listScanData.ListTitle  = list.Title;

                            if (!this.ScanJob.ListScanResults.TryAdd($"{Guid.NewGuid().ToString()}{webAppUrl}{list.DefaultViewUrl}", listScanData))
                            {
                                ScanError error = new ScanError()
                                {
                                    Error      = $"Could not add list scan result for {webAppUrl}{list.DefaultViewUrl} from web scan of {this.SiteUrl}",
                                    SiteColUrl = this.SiteCollectionUrl,
                                    SiteURL    = this.SiteUrl,
                                    Field1     = "ListAnalyzer",
                                    Field2     = $"{webAppUrl}{list.DefaultViewUrl}"
                                };
                                this.ScanJob.ScanErrors.Push(error);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        ScanError error = new ScanError()
                        {
                            Error      = ex.Message,
                            SiteColUrl = this.SiteCollectionUrl,
                            SiteURL    = this.SiteUrl,
                            Field1     = "MainListAnalyzerLoop",
                            Field2     = ex.StackTrace,
                            Field3     = $"{webAppUrl}{list.DefaultViewUrl}"
                        };

                        // Send error to telemetry to make scanner better
                        if (this.ScanJob.ScannerTelemetry != null)
                        {
                            this.ScanJob.ScannerTelemetry.LogScanError(ex, error);
                        }

                        this.ScanJob.ScanErrors.Push(error);
                        Console.WriteLine("Error for page {1}: {0}", ex.Message, $"{webAppUrl}{list.DefaultViewUrl}");
                    }
                }
            }
            finally
            {
                this.StopTime = DateTime.Now;
            }

            // return the duration of this scan
            return(new TimeSpan((this.StopTime.Subtract(this.StartTime).Ticks)));
        }