diff --git a/src/Umbraco.Core/Mapping/IUmbracoMapper.cs b/src/Umbraco.Core/Mapping/IUmbracoMapper.cs index 30c7bc440f..5ca86eaab3 100644 --- a/src/Umbraco.Core/Mapping/IUmbracoMapper.cs +++ b/src/Umbraco.Core/Mapping/IUmbracoMapper.cs @@ -43,7 +43,7 @@ namespace Umbraco.Cms.Core.Mapping /// The target type. /// The source object. /// The target object. - TTarget? Map(object source); + TTarget? Map(object? source); /// /// Maps a source object to a new target object. @@ -52,7 +52,7 @@ namespace Umbraco.Cms.Core.Mapping /// The source object. /// A mapper context preparation method. /// The target object. - TTarget? Map(object source, Action f); + TTarget? Map(object? source, Action f); /// /// Maps a source object to a new target object. diff --git a/src/Umbraco.Core/Mapping/MapperContext.cs b/src/Umbraco.Core/Mapping/MapperContext.cs index 6b66e63307..6cc67e115a 100644 --- a/src/Umbraco.Core/Mapping/MapperContext.cs +++ b/src/Umbraco.Core/Mapping/MapperContext.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.Mapping public class MapperContext { private readonly IUmbracoMapper _mapper; - private IDictionary? _items; + private IDictionary? _items; /// /// Initializes a new instance of the class. @@ -27,7 +27,7 @@ namespace Umbraco.Cms.Core.Mapping /// /// Gets the context items. /// - public IDictionary Items => _items ?? (_items = new Dictionary()); + public IDictionary Items => _items ?? (_items = new Dictionary()); #region Map diff --git a/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs b/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs index 03b30b3853..b6aca05515 100644 --- a/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs @@ -25,13 +25,17 @@ namespace Umbraco.Cms.Core.Models.ContentEditing [DataMember(Name = "defaultPermissions")] public IDictionary>? DefaultPermissions { get; set; } - public static IDictionary> ClonePermissions(IDictionary> permissions) + public static IDictionary> ClonePermissions(IDictionary>? permissions) { var result = new Dictionary>(); - foreach (var permission in permissions) + if (permissions is not null) { - result[permission.Key] = new List(permission.Value.Select(x => (Permission)x.Clone())); + foreach (var permission in permissions) + { + result[permission.Key] = new List(permission.Value.Select(x => (Permission)x.Clone())); + } } + return result; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs index f2088715a9..a795f85724 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs @@ -115,7 +115,7 @@ namespace Umbraco.Cms.Core.Models.ContentEditing [DataMember(Name = "contentTypeAlias", IsRequired = true)] [Required(AllowEmptyStrings = false)] - public string? ContentTypeAlias { get; set; } + public string ContentTypeAlias { get; set; } = null!; [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs index 7b665ebd36..4921c12058 100644 --- a/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs @@ -65,6 +65,6 @@ namespace Umbraco.Cms.Core.Models.ContentEditing /// [DataMember(Name = "metaData")] [ReadOnly(true)] - public IDictionary AdditionalData { get; private set; } + public IDictionary AdditionalData { get; private set; } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs b/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs index 293693b2f7..5a93ae94c9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs @@ -29,7 +29,7 @@ namespace Umbraco.Extensions }); } - public static void AddErrorNotification(this INotificationModel model, string header, string msg) + public static void AddErrorNotification(this INotificationModel model, string? header, string msg) { if (model.Exists(header, msg, NotificationStyle.Error)) return; @@ -65,6 +65,6 @@ namespace Umbraco.Extensions }); } - private static bool Exists(this INotificationModel model, string header, string message, NotificationStyle notificationType) => model.Notifications?.Any(x => (x.Header?.InvariantEquals(header) ?? false) && (x.Message?.InvariantEquals(message) ?? false) && x.NotificationType == notificationType) ?? false; + private static bool Exists(this INotificationModel model, string? header, string message, NotificationStyle notificationType) => model.Notifications?.Any(x => (x.Header?.InvariantEquals(header) ?? false) && (x.Message?.InvariantEquals(message) ?? false) && x.NotificationType == notificationType) ?? false; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs b/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs index 1afabb10e6..c5f827300a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs +++ b/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs @@ -6,12 +6,12 @@ namespace Umbraco.Cms.Core.Models.ContentEditing public class StylesheetRule { [DataMember(Name = "name")] - public string? Name { get; set; } + public string Name { get; set; } = null!; [DataMember(Name = "selector")] - public string? Selector { get; set; } + public string Selector { get; set; } = null!; [DataMember(Name = "styles")] - public string? Styles { get; set; } + public string Styles { get; set; } = null!; } } diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Core/Models/CultureImpact.cs index 8c6be0f7ae..dea741ab01 100644 --- a/src/Umbraco.Core/Models/CultureImpact.cs +++ b/src/Umbraco.Core/Models/CultureImpact.cs @@ -21,7 +21,7 @@ namespace Umbraco.Cms.Core.Models /// /// /// - public static string? GetCultureForInvariantErrors(IContent content, string[] savingCultures, string defaultCulture) + public static string? GetCultureForInvariantErrors(IContent? content, string[] savingCultures, string? defaultCulture) { if (content == null) throw new ArgumentNullException(nameof(content)); if (savingCultures == null) throw new ArgumentNullException(nameof(savingCultures)); diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs index f90596b1f8..d5cf6fd762 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs @@ -38,7 +38,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories /// /// If true will include the group's default permissions if no permissions are explicitly assigned /// Array of entity Ids, if empty will return permissions for the group for all entities - EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds); + EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[]? groups, bool fallbackToDefaultPermissions, params int[] nodeIds); /// /// Replaces the same permission set for a single group to any number of entities diff --git a/src/Umbraco.Core/Security/ContentPermissions.cs b/src/Umbraco.Core/Security/ContentPermissions.cs index e1f8fb9e44..c26e53252d 100644 --- a/src/Umbraco.Core/Security/ContentPermissions.cs +++ b/src/Umbraco.Core/Security/ContentPermissions.cs @@ -243,18 +243,18 @@ namespace Umbraco.Cms.Core.Security return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x.ToString(CultureInfo.InvariantCulture), ","))); } - public static bool IsInBranchOfStartNode(string path, int[] startNodeIds, string[] startNodePaths, out bool hasPathAccess) + public static bool IsInBranchOfStartNode(string path, int[]? startNodeIds, string[]? startNodePaths, out bool hasPathAccess) { if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); hasPathAccess = false; // check for no access - if (startNodeIds.Length == 0) + if (startNodeIds?.Length == 0) return false; // check for root access - if (startNodeIds.Contains(Constants.System.Root)) + if (startNodeIds?.Contains(Constants.System.Root) ?? false) { hasPathAccess = true; return true; diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index b4fb38e872..9d5340f55d 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -951,7 +951,7 @@ namespace Umbraco.Cms.Core.Services #region Save, Publish, Unpublish /// - public OperationResult Save(IContent content, int userId = Constants.Security.SuperUserId, + public OperationResult Save(IContent content, int? userId = null, ContentScheduleCollection? contentSchedule = null) { PublishedState publishedState = content.PublishedState; @@ -979,13 +979,14 @@ namespace Umbraco.Cms.Core.Services } scope.WriteLock(Constants.Locks.ContentTree); + userId ??= Constants.Security.SuperUserId; if (content.HasIdentity == false) { - content.CreatorId = userId; + content.CreatorId = userId.Value; } - content.WriterId = userId; + content.WriterId = userId.Value; //track the cultures that have changed List? culturesChanging = content.ContentType.VariesByCulture() @@ -1021,12 +1022,12 @@ namespace Umbraco.Cms.Core.Services if (languages is not null) { var langs = string.Join(", ", languages); - Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs); + Audit(AuditType.SaveVariant, userId.Value, content.Id, $"Saved languages: {langs}", langs); } } else { - Audit(AuditType.Save, userId, content.Id); + Audit(AuditType.Save, userId.Value, content.Id); } scope.Complete(); @@ -2752,8 +2753,12 @@ namespace Umbraco.Cms.Core.Services /// The to send to publication /// Optional Id of the User issuing the send to publication /// True if sending publication was successful otherwise false - public bool SendToPublication(IContent content, int userId = Constants.Security.SuperUserId) + public bool SendToPublication(IContent? content, int userId = Constants.Security.SuperUserId) { + if (content is null) + { + return false; + } EventMessages evtMsgs = EventMessagesFactory.Get(); using (IScope scope = ScopeProvider.CreateScope()) diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 06b0f1af76..d9eb95a4be 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -297,7 +297,7 @@ namespace Umbraco.Cms.Core.Services } } - public IEnumerable GetAll(IEnumerable ids) + public IEnumerable GetAll(IEnumerable? ids) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index e943afec88..0d65ac1c2a 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -65,7 +65,7 @@ namespace Umbraco.Cms.Core.Services } /// - public IStylesheet? GetStylesheet(string path) + public IStylesheet? GetStylesheet(string? path) { using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { @@ -74,8 +74,13 @@ namespace Umbraco.Cms.Core.Services } /// - public void SaveStylesheet(IStylesheet stylesheet, int userId = Constants.Security.SuperUserId) + public void SaveStylesheet(IStylesheet? stylesheet, int? userId = null) { + if (stylesheet is null) + { + return; + } + using (IScope scope = ScopeProvider.CreateScope()) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -86,17 +91,17 @@ namespace Umbraco.Cms.Core.Services return; } - + userId ??= Constants.Security.SuperUserId; _stylesheetRepository.Save(stylesheet); scope.Notifications.Publish(new StylesheetSavedNotification(stylesheet, eventMessages).WithStateFrom(savingNotification)); - Audit(AuditType.Save, userId, -1, "Stylesheet"); + Audit(AuditType.Save, userId.Value, -1, "Stylesheet"); scope.Complete(); } } /// - public void DeleteStylesheet(string path, int userId = Constants.Security.SuperUserId) + public void DeleteStylesheet(string path, int? userId) { using (IScope scope = ScopeProvider.CreateScope()) { @@ -115,10 +120,11 @@ namespace Umbraco.Cms.Core.Services return; // causes rollback } + userId ??= Constants.Security.SuperUserId; _stylesheetRepository.Delete(stylesheet); scope.Notifications.Publish(new StylesheetDeletedNotification(stylesheet, eventMessages).WithStateFrom(deletingNotification)); - Audit(AuditType.Delete, userId, -1, "Stylesheet"); + Audit(AuditType.Delete, userId.Value, -1, "Stylesheet"); scope.Complete(); } @@ -186,7 +192,7 @@ namespace Umbraco.Cms.Core.Services } /// - public IScript? GetScript(string name) + public IScript? GetScript(string? name) { using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { @@ -195,8 +201,13 @@ namespace Umbraco.Cms.Core.Services } /// - public void SaveScript(IScript script, int userId = Constants.Security.SuperUserId) + public void SaveScript(IScript? script, int? userId = Constants.Security.SuperUserId) { + if (script is null) + { + return; + } + using (IScope scope = ScopeProvider.CreateScope()) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -210,13 +221,13 @@ namespace Umbraco.Cms.Core.Services _scriptRepository.Save(script); scope.Notifications.Publish(new ScriptSavedNotification(script, eventMessages).WithStateFrom(savingNotification)); - Audit(AuditType.Save, userId, -1, "Script"); + Audit(AuditType.Save, userId.Value, -1, "Script"); scope.Complete(); } } /// - public void DeleteScript(string path, int userId = Constants.Security.SuperUserId) + public void DeleteScript(string path, int? userId = null) { using (IScope scope = ScopeProvider.CreateScope()) { @@ -235,10 +246,11 @@ namespace Umbraco.Cms.Core.Services return; } + userId ??= Constants.Security.SuperUserId; _scriptRepository.Delete(script); scope.Notifications.Publish(new ScriptDeletedNotification(script, eventMessages).WithStateFrom(deletingNotification)); - Audit(AuditType.Delete, userId, -1, "Script"); + Audit(AuditType.Delete, userId.Value, -1, "Script"); scope.Complete(); } } @@ -622,7 +634,7 @@ namespace Umbraco.Cms.Core.Services #region Partial Views - public IEnumerable GetPartialViewSnippetNames(params string[] filterNames) + public IEnumerable GetPartialViewSnippetNames(params string[] filterNames) { var snippetPath = _hostingEnvironment.MapPathContentRoot($"{Constants.SystemDirectories.Umbraco}/PartialViewMacros/Templates/"); var files = Directory.GetFiles(snippetPath, "*.cshtml") @@ -635,7 +647,7 @@ namespace Umbraco.Cms.Core.Services .OrderBy(x => x?.Length) .ToArray(); - return empty.Union(files.Except(empty)); + return empty.Union(files.Except(empty)).WhereNotNull(); } public void DeletePartialViewFolder(string folderPath) @@ -680,13 +692,13 @@ namespace Umbraco.Cms.Core.Services } } - public Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, int userId = Constants.Security.SuperUserId) => + public Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId) => CreatePartialViewMacro(partialView, PartialViewType.PartialView, snippetName, userId); - public Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, int userId = Constants.Security.SuperUserId) => + public Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId) => CreatePartialViewMacro(partialView, PartialViewType.PartialViewMacro, snippetName, userId); - private Attempt CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType, string? snippetName = null, int userId = Constants.Security.SuperUserId) + private Attempt CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType, string? snippetName = null, int? userId = Constants.Security.SuperUserId) { string partialViewHeader; switch (partialViewType) @@ -735,7 +747,7 @@ namespace Umbraco.Cms.Core.Services if (scope.Notifications.PublishCancelable(creatingNotification)) { scope.Complete(); - return Attempt.Fail(); + return Attempt.Fail(); } IPartialViewRepository repository = GetPartialViewRepository(partialViewType); @@ -748,24 +760,25 @@ namespace Umbraco.Cms.Core.Services scope.Notifications.Publish(new PartialViewCreatedNotification(partialView, eventMessages).WithStateFrom(creatingNotification)); - Audit(AuditType.Save, userId, -1, partialViewType.ToString()); + Audit(AuditType.Save, userId!.Value, -1, partialViewType.ToString()); scope.Complete(); } - return Attempt.Succeed(partialView); + return Attempt.Succeed(partialView); } - public bool DeletePartialView(string path, int userId = Constants.Security.SuperUserId) => + public bool DeletePartialView(string path, int? userId = null) => DeletePartialViewMacro(path, PartialViewType.PartialView, userId); - public bool DeletePartialViewMacro(string path, int userId = Constants.Security.SuperUserId) => + public bool DeletePartialViewMacro(string path, int? userId = null) => DeletePartialViewMacro(path, PartialViewType.PartialViewMacro, userId); - private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int userId = Constants.Security.SuperUserId) + private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int? userId = null) { using (IScope scope = ScopeProvider.CreateScope()) { + IPartialViewRepository repository = GetPartialViewRepository(partialViewType); IPartialView? partialView = repository.Get(path); if (partialView == null) @@ -782,9 +795,10 @@ namespace Umbraco.Cms.Core.Services return false; } + userId ??= Constants.Security.SuperUserId; repository.Delete(partialView); scope.Notifications.Publish(new PartialViewDeletedNotification(partialView, eventMessages).WithStateFrom(deletingNotification)); - Audit(AuditType.Delete, userId, -1, partialViewType.ToString()); + Audit(AuditType.Delete, userId.Value, -1, partialViewType.ToString()); scope.Complete(); } @@ -792,13 +806,13 @@ namespace Umbraco.Cms.Core.Services return true; } - public Attempt SavePartialView(IPartialView partialView, int userId = Constants.Security.SuperUserId) => + public Attempt SavePartialView(IPartialView partialView, int? userId = null) => SavePartialView(partialView, PartialViewType.PartialView, userId); - public Attempt SavePartialViewMacro(IPartialView partialView, int userId = Constants.Security.SuperUserId) => + public Attempt SavePartialViewMacro(IPartialView partialView, int? userId = null) => SavePartialView(partialView, PartialViewType.PartialViewMacro, userId); - private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType, int userId = Constants.Security.SuperUserId) + private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType, int? userId = null) { using (IScope scope = ScopeProvider.CreateScope()) { @@ -810,10 +824,11 @@ namespace Umbraco.Cms.Core.Services return Attempt.Fail(); } + userId ??= Constants.Security.SuperUserId; IPartialViewRepository repository = GetPartialViewRepository(partialViewType); repository.Save(partialView); - Audit(AuditType.Save, userId, -1, partialViewType.ToString()); + Audit(AuditType.Save, userId.Value, -1, partialViewType.ToString()); scope.Notifications.Publish(new PartialViewSavedNotification(partialView, eventMessages).WithStateFrom(savingNotification)); scope.Complete(); diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index e53f091c49..9899861a18 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -84,7 +84,7 @@ namespace Umbraco.Cms.Core.Services /// /// Gets documents. /// - IEnumerable? GetByIds(IEnumerable ids); + IEnumerable GetByIds(IEnumerable ids); /// /// Gets documents. @@ -250,7 +250,7 @@ namespace Umbraco.Cms.Core.Services /// /// Saves a document. /// - OperationResult Save(IContent content, int userId = Constants.Security.SuperUserId, ContentScheduleCollection? contentSchedule = null); + OperationResult Save(IContent content, int? userId = null, ContentScheduleCollection? contentSchedule = null); /// /// Saves documents. @@ -460,7 +460,7 @@ namespace Umbraco.Cms.Core.Services /// /// Saves a document and raises the "sent to publication" events. /// - bool SendToPublication(IContent content, int userId = Constants.Security.SuperUserId); + bool SendToPublication(IContent? content, int userId = Constants.Security.SuperUserId); /// /// Publishes and unpublishes scheduled documents. diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index 54e3c157f9..3835491cb4 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -45,7 +45,7 @@ namespace Umbraco.Cms.Core.Services bool HasContentNodes(int id); IEnumerable GetAll(params int[] ids); - IEnumerable GetAll(IEnumerable ids); + IEnumerable GetAll(IEnumerable? ids); IEnumerable GetDescendants(int id, bool andSelf); // parent-child axis IEnumerable GetComposedOf(int id); // composition axis diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index e0faf5ae9a..b6b4772d0d 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -10,7 +10,7 @@ namespace Umbraco.Cms.Core.Services /// public interface IFileService : IService { - IEnumerable GetPartialViewSnippetNames(params string[] filterNames); + IEnumerable GetPartialViewSnippetNames(params string[] filterNames); void CreatePartialViewFolder(string folderPath); void CreatePartialViewMacroFolder(string folderPath); void DeletePartialViewFolder(string folderPath); @@ -24,12 +24,12 @@ namespace Umbraco.Cms.Core.Services IPartialView? GetPartialView(string path); IPartialView? GetPartialViewMacro(string path); - Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, int userId = Constants.Security.SuperUserId); - Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, int userId = Constants.Security.SuperUserId); - bool DeletePartialView(string path, int userId = Constants.Security.SuperUserId); - bool DeletePartialViewMacro(string path, int userId = Constants.Security.SuperUserId); - Attempt SavePartialView(IPartialView partialView, int userId = Constants.Security.SuperUserId); - Attempt SavePartialViewMacro(IPartialView partialView, int userId = Constants.Security.SuperUserId); + Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId); + Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId); + bool DeletePartialView(string path, int? userId = null); + bool DeletePartialViewMacro(string path, int? userId = null); + Attempt SavePartialView(IPartialView partialView, int? userId = null); + Attempt SavePartialViewMacro(IPartialView partialView, int? userId = null); /// /// Gets the content of a partial view as a stream. @@ -84,21 +84,21 @@ namespace Umbraco.Cms.Core.Services /// /// Path of the stylesheet incl. extension /// A object - IStylesheet? GetStylesheet(string path); + IStylesheet? GetStylesheet(string? path); /// /// Saves a /// /// to save /// Optional id of the user saving the stylesheet - void SaveStylesheet(IStylesheet stylesheet, int userId = Constants.Security.SuperUserId); + void SaveStylesheet(IStylesheet? stylesheet, int? userId = null); /// /// Deletes a stylesheet by its name /// /// Name incl. extension of the Stylesheet to delete /// Optional id of the user deleting the stylesheet - void DeleteStylesheet(string path, int userId = Constants.Security.SuperUserId); + void DeleteStylesheet(string path, int? userId = null); /// /// Creates a folder for style sheets @@ -145,21 +145,21 @@ namespace Umbraco.Cms.Core.Services /// /// Name of the script incl. extension /// A object - IScript? GetScript(string name); + IScript? GetScript(string? name); /// /// Saves a /// /// to save /// Optional id of the user saving the script - void SaveScript(IScript script, int userId = Constants.Security.SuperUserId); + void SaveScript(IScript? script, int? userId = Constants.Security.SuperUserId); /// /// Deletes a script by its name /// /// Name incl. extension of the Script to delete /// Optional id of the user deleting the script - void DeleteScript(string path, int userId = Constants.Security.SuperUserId); + void DeleteScript(string path, int? userId = null); /// /// Creates a folder for scripts diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index ecb5945f7c..cf65b1aa67 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -38,7 +38,7 @@ namespace Umbraco.Cms.Core.Services /// /// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors) /// - IEnumerable? GetUserNotifications(IUser user, string path); + IEnumerable? GetUserNotifications(IUser? user, string path); /// /// Returns the notifications for an entity @@ -75,7 +75,7 @@ namespace Umbraco.Cms.Core.Services /// /// This performs a full replace /// - IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions); + IEnumerable? SetNotifications(IUser? user, IEntity entity, string[] actions); /// /// Creates a new notification diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 10f6a04020..5478a924e3 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -139,7 +139,7 @@ namespace Umbraco.Cms.Core.Services /// /// This will return the default permissions for the user's groups for node ids that don't have explicitly defined permissions /// - EntityPermissionCollection GetPermissions(IUser user, params int[] nodeIds); + EntityPermissionCollection GetPermissions(IUser? user, params int[] nodeIds); /// /// Get explicitly assigned permissions for groups and optional node Ids @@ -157,7 +157,7 @@ namespace Umbraco.Cms.Core.Services /// /// User to check permissions for /// Path to check permissions for - EntityPermissionSet GetPermissionsForPath(IUser user, string? path); + EntityPermissionSet GetPermissionsForPath(IUser? user, string? path); /// /// Gets the permissions for the provided groups and path diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 7a7ad9d98a..b579f6f7f7 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -167,8 +167,13 @@ namespace Umbraco.Cms.Core.Services /// /// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors) /// - public IEnumerable? GetUserNotifications(IUser user, string path) + public IEnumerable? GetUserNotifications(IUser? user, string path) { + if (user is null) + { + return null; + } + var userNotifications = GetUserNotifications(user); return FilterUserNotificationsByPath(userNotifications, path); } @@ -246,8 +251,13 @@ namespace Umbraco.Cms.Core.Services /// /// This performs a full replace /// - public IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions) + public IEnumerable? SetNotifications(IUser? user, IEntity entity, string[] actions) { + if (user is null) + { + return null; + } + using (var scope = _uowProvider.CreateScope()) { var notifications = _notificationsRepository.SetNotifications(user, entity, actions); diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 87922a5d83..680882a990 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -915,11 +915,11 @@ namespace Umbraco.Cms.Core.Services /// User to retrieve permissions for /// Specifying nothing will return all permissions for all nodes /// An enumerable list of - public EntityPermissionCollection GetPermissions(IUser user, params int[] nodeIds) + public EntityPermissionCollection GetPermissions(IUser? user, params int[] nodeIds) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _userGroupRepository.GetPermissions(user.Groups.ToArray(), true, nodeIds); + return _userGroupRepository.GetPermissions(user?.Groups.ToArray(), true, nodeIds); } } @@ -964,7 +964,7 @@ namespace Umbraco.Cms.Core.Services /// /// User to check permissions for /// Path to check permissions for - public EntityPermissionSet GetPermissionsForPath(IUser user, string? path) + public EntityPermissionSet GetPermissionsForPath(IUser? user, string? path) { var nodeIds = path?.GetIdsFromPathReversed(); diff --git a/src/Umbraco.Core/Trees/ISearchableTree.cs b/src/Umbraco.Core/Trees/ISearchableTree.cs index 743fd10a87..018aa88ae5 100644 --- a/src/Umbraco.Core/Trees/ISearchableTree.cs +++ b/src/Umbraco.Core/Trees/ISearchableTree.cs @@ -22,6 +22,6 @@ namespace Umbraco.Cms.Core.Trees /// A starting point for the search, generally a node id, but for members this is a member type alias /// /// - IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null); + IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null); } } diff --git a/src/Umbraco.Core/Trees/ITree.cs b/src/Umbraco.Core/Trees/ITree.cs index b114b83d22..106b3eef37 100644 --- a/src/Umbraco.Core/Trees/ITree.cs +++ b/src/Umbraco.Core/Trees/ITree.cs @@ -19,7 +19,7 @@ /// /// Gets the tree group. /// - string TreeGroup { get; } + string? TreeGroup { get; } /// /// Gets the tree alias. @@ -29,7 +29,7 @@ /// /// Gets or sets the tree title (fallback if the tree alias isn't localized) /// - string TreeTitle { get; } + string? TreeTitle { get; } /// /// Gets the tree use. diff --git a/src/Umbraco.Core/Trees/Tree.cs b/src/Umbraco.Core/Trees/Tree.cs index 7ba01880d7..f229dd8019 100644 --- a/src/Umbraco.Core/Trees/Tree.cs +++ b/src/Umbraco.Core/Trees/Tree.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Core.Trees [DebuggerDisplay("Tree - {SectionAlias}/{TreeAlias}")] public class Tree : ITree { - public Tree(int sortOrder, string applicationAlias, string group, string alias, string title, TreeUse use, Type treeControllerType, bool isSingleNodeTree) + public Tree(int sortOrder, string applicationAlias, string? group, string alias, string? title, TreeUse use, Type treeControllerType, bool isSingleNodeTree) { SortOrder = sortOrder; SectionAlias = applicationAlias ?? throw new ArgumentNullException(nameof(applicationAlias)); @@ -30,13 +30,13 @@ namespace Umbraco.Cms.Core.Trees public string SectionAlias { get; set; } /// - public string TreeGroup { get; } + public string? TreeGroup { get; } /// public string TreeAlias { get; } /// - public string TreeTitle { get; set; } + public string? TreeTitle { get; set; } /// public TreeUse TreeUse { get; set; } diff --git a/src/Umbraco.Core/Trees/TreeNode.cs b/src/Umbraco.Core/Trees/TreeNode.cs index ba856b6d3a..3c166c9fdd 100644 --- a/src/Umbraco.Core/Trees/TreeNode.cs +++ b/src/Umbraco.Core/Trees/TreeNode.cs @@ -22,7 +22,7 @@ namespace Umbraco.Cms.Core.Trees /// The parent id for the current node /// /// - public TreeNode(string nodeId, string? parentId, string getChildNodesUrl, string menuUrl) + public TreeNode(string nodeId, string? parentId, string? getChildNodesUrl, string? menuUrl) { if (nodeId == null) throw new ArgumentNullException(nameof(nodeId)); if (string.IsNullOrWhiteSpace(nodeId)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(nodeId)); @@ -65,13 +65,13 @@ namespace Umbraco.Cms.Core.Trees /// The JSON URL to load the nodes children /// [DataMember(Name = "childNodesUrl")] - public string ChildNodesUrl { get; set; } + public string? ChildNodesUrl { get; set; } /// /// The JSON URL to load the menu from /// [DataMember(Name = "menuUrl")] - public string MenuUrl { get; set; } + public string? MenuUrl { get; set; } /// /// Returns true if the icon represents a CSS class instead of a file path diff --git a/src/Umbraco.Infrastructure/Mapping/UmbracoMapper.cs b/src/Umbraco.Infrastructure/Mapping/UmbracoMapper.cs index 9b4406ea83..9d24c8a6e5 100644 --- a/src/Umbraco.Infrastructure/Mapping/UmbracoMapper.cs +++ b/src/Umbraco.Infrastructure/Mapping/UmbracoMapper.cs @@ -133,7 +133,7 @@ namespace Umbraco.Cms.Core.Mapping /// The target type. /// The source object. /// The target object. - public TTarget? Map(object source) + public TTarget? Map(object? source) => Map(source, new MapperContext(this)); /// @@ -143,7 +143,7 @@ namespace Umbraco.Cms.Core.Mapping /// The source object. /// A mapper context preparation method. /// The target object. - public TTarget? Map(object source, Action f) + public TTarget? Map(object? source, Action f) { var context = new MapperContext(this); f(context); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs index d19c896690..7a3ca69db1 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -110,7 +110,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement /// /// If true will include the group's default permissions if no permissions are explicitly assigned /// Array of entity Ids, if empty will return permissions for the group for all entities - public EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds) + public EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[]? groups, bool fallbackToDefaultPermissions, params int[] nodeIds) { if (groups == null) throw new ArgumentNullException(nameof(groups)); diff --git a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs index f426441192..79887c1c0f 100644 --- a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs @@ -61,7 +61,7 @@ namespace Umbraco.Cms.Infrastructure.Search /// /// If set to true, user and group start node permissions will be ignored. /// - public IEnumerable? ExamineSearch( + public IEnumerable ExamineSearch( string query, UmbracoEntityTypes entityType, int pageSize, @@ -145,8 +145,8 @@ namespace Umbraco.Cms.Infrastructure.Search /// /// /// - private IEnumerable? MediaFromSearchResults(IEnumerable results) - => _mapper.Map>(results); + private IEnumerable MediaFromSearchResults(IEnumerable results) + => _mapper.Map>(results) ?? Enumerable.Empty(); /// /// Returns a collection of entities for content based on search results diff --git a/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs index ec364eb850..5c46308cb1 100644 --- a/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs @@ -59,7 +59,7 @@ namespace Umbraco.Cms.Core.Security /// /// The that represents the asynchronous operation, containing the user matching the specified if it exists. /// - Task FindByIdAsync(string userId); + Task FindByIdAsync(string? userId); /// /// Generates a password reset token for the specified , using @@ -88,7 +88,7 @@ namespace Umbraco.Cms.Core.Security /// The that represents the asynchronous operation, containing the /// of the operation. /// - Task ConfirmEmailAsync(TUser user, string token); + Task ConfirmEmailAsync(TUser user, string? token); /// /// Gets the user, if any, associated with the normalized value of the specified email address. @@ -112,7 +112,7 @@ namespace Umbraco.Cms.Core.Security /// The that represents the asynchronous operation, containing the /// of the operation. /// - Task ResetPasswordAsync(TUser user, string token, string newPassword); + Task ResetPasswordAsync(TUser user, string? token, string? newPassword); /// /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date @@ -258,7 +258,7 @@ namespace Umbraco.Cms.Core.Security /// /// A generated password string GeneratePassword(); - + /// /// Used to validate the password without an identity user /// Validation code is based on the default ValidatePasswordAsync code @@ -345,7 +345,7 @@ namespace Umbraco.Cms.Core.Security /// The login provide whose information should be removed. /// The key given by the external login provider for the specified user. /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing the Microsoft.AspNetCore.Identity.IdentityResult of the operation. - Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey); + Task RemoveLoginAsync(TUser user, string? loginProvider, string? providerKey); /// /// Resets the access failed count for the user diff --git a/src/Umbraco.Infrastructure/Trees/TreeRootNode.cs b/src/Umbraco.Infrastructure/Trees/TreeRootNode.cs index 7f006a056d..8cd596558f 100644 --- a/src/Umbraco.Infrastructure/Trees/TreeRootNode.cs +++ b/src/Umbraco.Infrastructure/Trees/TreeRootNode.cs @@ -89,7 +89,7 @@ namespace Umbraco.Cms.Core.Models.Trees /// /// /// - public static TreeRootNode CreateSingleTreeRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children, bool isSingleNodeTree = false) + public static TreeRootNode CreateSingleTreeRoot(string nodeId, string? getChildNodesUrl, string? menuUrl, string? title, TreeNodeCollection? children, bool isSingleNodeTree = false) { return new TreeRootNode(nodeId, getChildNodesUrl, menuUrl) { @@ -105,7 +105,7 @@ namespace Umbraco.Cms.Core.Models.Trees /// /// /// - private TreeRootNode(string nodeId, string getChildNodesUrl, string menuUrl) + private TreeRootNode(string nodeId, string? getChildNodesUrl, string? menuUrl) : base(nodeId, null, getChildNodesUrl, menuUrl) { //default to false diff --git a/src/Umbraco.Web.BackOffice/ActionResults/JavaScriptResult.cs b/src/Umbraco.Web.BackOffice/ActionResults/JavaScriptResult.cs index 2d9b87b680..c092c57897 100644 --- a/src/Umbraco.Web.BackOffice/ActionResults/JavaScriptResult.cs +++ b/src/Umbraco.Web.BackOffice/ActionResults/JavaScriptResult.cs @@ -4,7 +4,7 @@ namespace Umbraco.Cms.Web.BackOffice.ActionResults { public class JavaScriptResult : ContentResult { - public JavaScriptResult(string script) + public JavaScriptResult(string? script) { this.Content = script; this.ContentType = "application/javascript"; diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index d478784fa3..2d1e9198a2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -145,7 +145,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// [ValidateAngularAntiForgeryToken] [Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)] - public async Task> PostVerifyInvite([FromQuery] int id, [FromQuery] string token) + public async Task> PostVerifyInvite([FromQuery] int id, [FromQuery] string token) { if (string.IsNullOrWhiteSpace(token)) return NotFound(); @@ -158,7 +158,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (identityUser == null) return NotFound(); - var result = await _userManager.ConfirmEmailAsync(identityUser, decoded); + var result = await _userManager.ConfirmEmailAsync(identityUser, decoded!); if (result.Succeeded == false) { @@ -178,7 +178,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [ValidateAngularAntiForgeryToken] public async Task PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel) { - var user = await _userManager.FindByIdAsync(User.Identity.GetUserId()); + var user = await _userManager.FindByIdAsync(User.Identity?.GetUserId()); if (user == null) throw new InvalidOperationException("Could not find user"); var authType = (await _signInManager.GetExternalAuthenticationSchemesAsync()) @@ -275,13 +275,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] [SetAngularAntiForgeryTokens] [CheckIfUserTicketDataIsStale] - public UserDetail GetCurrentUser() + public UserDetail? GetCurrentUser() { - var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + var user = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; var result = _umbracoMapper.Map(user); - //set their remaining seconds - result.SecondsUntilTimeout = HttpContext.User.GetRemainingAuthSeconds(); + if (result is not null) + { + //set their remaining seconds + result.SecondsUntilTimeout = HttpContext.User.GetRemainingAuthSeconds(); + } return result; } @@ -297,11 +300,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [Authorize(Policy = AuthorizationPolicies.BackOfficeAccessWithoutApproval)] [SetAngularAntiForgeryTokens] [Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)] - public ActionResult GetCurrentInvitedUser() + public ActionResult GetCurrentInvitedUser() { - var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + var user = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - if (user.IsApproved) + if (user?.IsApproved ?? false) { // if they are approved, than they are no longer invited and we can return an error return Forbid(); @@ -309,8 +312,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var result = _umbracoMapper.Map(user); - // set their remaining seconds - result.SecondsUntilTimeout = HttpContext.User.GetRemainingAuthSeconds(); + if (result is not null) + { + // set their remaining seconds + result.SecondsUntilTimeout = HttpContext.User.GetRemainingAuthSeconds(); + } return result; } @@ -321,7 +327,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// [SetAngularAntiForgeryTokens] [Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)] - public async Task> PostLogin(LoginModel loginModel) + public async Task> PostLogin(LoginModel loginModel) { // Sign the user in with username/password, this also gives a chance for developers to // custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker @@ -342,13 +348,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return new ValidationErrorResult($"The registered {typeof(IBackOfficeTwoFactorOptions)} of type {_backOfficeTwoFactorOptions.GetType()} did not return a view for two factor auth "); } - IUser attemptedUser = _userService.GetByUsername(loginModel.Username); + IUser? attemptedUser = _userService.GetByUsername(loginModel.Username); // create a with information to display a custom two factor send code view var verifyResponse = new ObjectResult(new { twoFactorView = twofactorView, - userId = attemptedUser.Id + userId = attemptedUser?.Id }) { StatusCode = StatusCodes.Status402PaymentRequired @@ -388,7 +394,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var user = _userService.GetByEmail(model.Email); if (user != null) { - var from = _globalSettings.Smtp.From; + var from = _globalSettings.Smtp?.From; var code = await _userManager.GeneratePasswordResetTokenAsync(identityUser); var callbackUrl = ConstructCallbackUrl(identityUser.Id, code); @@ -445,7 +451,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return NotFound(); } - var from = _globalSettings.Smtp.From; + var from = _globalSettings.Smtp?.From; // Generate the token and send it var code = await _userManager.GenerateTwoFactorTokenAsync(user, provider); if (string.IsNullOrWhiteSpace(code)) @@ -479,7 +485,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [SetAngularAntiForgeryTokens] [AllowAnonymous] - public async Task> PostVerify2FACode(Verify2FACodeModel model) + public async Task> PostVerify2FACode(Verify2FACodeModel model) { if (ModelState.IsValid == false) { @@ -561,7 +567,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { var user = _userService.GetByUsername(identityUser.UserName); // also check InvitedDate and never logged in, otherwise this would allow a disabled user to reactivate their account with a forgot password - if (user.LastLoginDate == default && user.InvitedDate != null) + if (user?.LastLoginDate == default && user?.InvitedDate != null) { user.IsApproved = true; user.InvitedDate = null; diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index f85da612ab..e01759791d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -184,7 +184,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return RedirectToAction(nameof(Default)); } - var result = await _userManager.ConfirmEmailAsync(identityUser, decoded); + var result = await _userManager.ConfirmEmailAsync(identityUser, decoded!); if (result.Succeeded == false) { @@ -249,9 +249,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// [HttpGet] [AllowAnonymous] - public async Task>> LocalizedText(string culture = null) + public async Task>> LocalizedText(string? culture = null) { - CultureInfo cultureInfo; + CultureInfo? cultureInfo; if (string.IsNullOrWhiteSpace(culture)) { // Force authentication to occur since this is not an authorized endpoint, we need this to get a user. @@ -269,7 +269,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers cultureInfo = CultureInfo.GetCultureInfo(culture); } - var allValues = _textService.GetAllStoredValues(cultureInfo); + var allValues = _textService.GetAllStoredValues(cultureInfo!); var pathedValues = allValues.Select(kv => { var slashIndex = kv.Key.IndexOf('/'); @@ -320,7 +320,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [HttpPost] [AllowAnonymous] - public ActionResult ExternalLogin(string provider, string redirectUrl = null) + public ActionResult ExternalLogin(string provider, string? redirectUrl = null) { if (redirectUrl == null) { @@ -511,13 +511,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } else if (result == SignInResult.LockedOut) { - errors.Add($"The local user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out."); + errors.Add($"The local user {loginInfo.Principal.Identity?.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out."); } else if (result == SignInResult.NotAllowed) { // This occurs when SignInManager.CanSignInAsync fails which is when RequireConfirmedEmail , RequireConfirmedPhoneNumber or RequireConfirmedAccount fails // however since we don't enforce those rules (yet) this shouldn't happen. - errors.Add($"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in."); + errors.Add($"The user {loginInfo.Principal.Identity?.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in."); } else if (result == SignInResult.Failed) { @@ -527,7 +527,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers else if (result == ExternalLoginSignInResult.NotAllowed) { // This occurs when the external provider has approved the login but custom logic in OnExternalLogin has denined it. - errors.Add($"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not been accepted and cannot sign in."); + errors.Add($"The user {loginInfo.Principal.Identity?.Name} for the external provider {loginInfo.ProviderDisplayName} has not been accepted and cannot sign in."); } else if (result == AutoLinkSignInResult.FailedNotLinked) { @@ -558,7 +558,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return response(); } - private IActionResult RedirectToLocal(string returnUrl) + private IActionResult RedirectToLocal(string? returnUrl) { if (Url.IsLocalUrl(returnUrl)) { diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeNotificationsController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeNotificationsController.cs index 27be8ec263..ad65941cbf 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeNotificationsController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeNotificationsController.cs @@ -38,7 +38,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - protected override ActionResult ValidationProblem(string errorMessage) + protected override ActionResult ValidationProblem(string? errorMessage) => ValidationProblem(errorMessage, string.Empty); /// @@ -47,7 +47,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - protected ActionResult ValidationProblem(string errorHeader, string errorMessage) + protected ActionResult ValidationProblem(string? errorHeader, string errorMessage) { var notificationModel = new SimpleNotificationModel { @@ -66,6 +66,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. => new ValidationErrorResult(new SimpleNotificationModel()); - + } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index e5f82cc0e1..a302edc56b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -78,34 +78,34 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (display == null) throw new ArgumentNullException("display"); if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); - var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; switch (type) { case Constants.Trees.PartialViews: - var view = new PartialView(PartialViewType.PartialView, display.VirtualPath); + var view = new PartialView(PartialViewType.PartialView, display.VirtualPath ?? string.Empty); view.Content = display.Content; - var result = _fileService.CreatePartialView(view, display.Snippet, currentUser.Id); + var result = _fileService.CreatePartialView(view, display.Snippet, currentUser?.Id); if (result.Success) { return Ok(); } else { - return ValidationProblem(result.Exception.Message); + return ValidationProblem(result.Exception?.Message); } case Constants.Trees.PartialViewMacros: - var viewMacro = new PartialView(PartialViewType.PartialViewMacro, display.VirtualPath); + var viewMacro = new PartialView(PartialViewType.PartialViewMacro, display.VirtualPath ?? string.Empty); viewMacro.Content = display.Content; - var resultMacro = _fileService.CreatePartialViewMacro(viewMacro, display.Snippet, currentUser.Id); + var resultMacro = _fileService.CreatePartialViewMacro(viewMacro, display.Snippet, currentUser?.Id); if (resultMacro.Success) return Ok(); else - return ValidationProblem(resultMacro.Exception.Message); + return ValidationProblem(resultMacro.Exception?.Message); case Constants.Trees.Scripts: - var script = new Script(display.VirtualPath); - _fileService.SaveScript(script, currentUser.Id); + var script = new Script(display.VirtualPath ?? string.Empty); + _fileService.SaveScript(script, currentUser?.Id); return Ok(); default: @@ -180,7 +180,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// This is a string but will be 'scripts' 'partialViews', 'partialViewMacros' or 'stylesheets' /// The filename or URL encoded path of the file to open /// The file and its contents from the virtualPath - public ActionResult GetByPath(string type, string virtualPath) + public ActionResult GetByPath(string type, string virtualPath) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath"); @@ -194,9 +194,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (view != null) { var display = _umbracoMapper.Map(view); - display.FileType = Constants.Trees.PartialViews; - display.Path = Url.GetTreePathFromFilePath(view.Path); - display.Id = System.Web.HttpUtility.UrlEncode(view.Path); + + if (display is not null) + { + display.FileType = Constants.Trees.PartialViews; + display.Path = Url.GetTreePathFromFilePath(view.Path); + display.Id = System.Web.HttpUtility.UrlEncode(view.Path); + } + return display; } @@ -206,9 +211,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (viewMacro != null) { var display = _umbracoMapper.Map(viewMacro); - display.FileType = Constants.Trees.PartialViewMacros; - display.Path = Url.GetTreePathFromFilePath(viewMacro.Path); - display.Id = System.Web.HttpUtility.UrlEncode(viewMacro.Path); + + if (display is not null) + { + display.FileType = Constants.Trees.PartialViewMacros; + display.Path = Url.GetTreePathFromFilePath(viewMacro.Path); + display.Id = System.Web.HttpUtility.UrlEncode(viewMacro.Path); + } + return display; } break; @@ -217,9 +227,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (script != null) { var display = _umbracoMapper.Map(script); - display.FileType = Constants.Trees.Scripts; - display.Path = Url.GetTreePathFromFilePath(script.Path); - display.Id = System.Web.HttpUtility.UrlEncode(script.Path); + + if (display is not null) + { + display.FileType = Constants.Trees.Scripts; + display.Path = Url.GetTreePathFromFilePath(script.Path); + display.Id = System.Web.HttpUtility.UrlEncode(script.Path); + } + return display; } break; @@ -228,9 +243,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (stylesheet != null) { var display = _umbracoMapper.Map(stylesheet); - display.FileType = Constants.Trees.Stylesheets; - display.Path = Url.GetTreePathFromFilePath(stylesheet.Path); - display.Id = System.Web.HttpUtility.UrlEncode(stylesheet.Path); + + if (display is not null) + { + display.FileType = Constants.Trees.Stylesheets; + display.Path = Url.GetTreePathFromFilePath(stylesheet.Path); + display.Id = System.Web.HttpUtility.UrlEncode(stylesheet.Path); + } + return display; } break; @@ -276,41 +296,65 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - public ActionResult GetScaffold(string type, string id, string snippetName = null) + public ActionResult GetScaffold(string type, string id, string? snippetName = null) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Value cannot be null or whitespace.", "id"); - CodeFileDisplay codeFileDisplay; + CodeFileDisplay? codeFileDisplay; switch (type) { case Constants.Trees.PartialViews: codeFileDisplay = _umbracoMapper.Map(new PartialView(PartialViewType.PartialView, string.Empty)); - codeFileDisplay.VirtualPath = Constants.SystemDirectories.PartialViews; - if (snippetName.IsNullOrWhiteSpace() == false) - codeFileDisplay.Content = _fileService.GetPartialViewSnippetContent(snippetName); + if (codeFileDisplay is not null) + { + codeFileDisplay.VirtualPath = Constants.SystemDirectories.PartialViews; + if (snippetName.IsNullOrWhiteSpace() == false) + { + codeFileDisplay.Content = _fileService.GetPartialViewSnippetContent(snippetName!); + } + } + break; case Constants.Trees.PartialViewMacros: codeFileDisplay = _umbracoMapper.Map(new PartialView(PartialViewType.PartialViewMacro, string.Empty)); - codeFileDisplay.VirtualPath = Constants.SystemDirectories.MacroPartials; - if (snippetName.IsNullOrWhiteSpace() == false) - codeFileDisplay.Content = _fileService.GetPartialViewMacroSnippetContent(snippetName); + if (codeFileDisplay is not null) + { + codeFileDisplay.VirtualPath = Constants.SystemDirectories.MacroPartials; + if (snippetName.IsNullOrWhiteSpace() == false) + { + codeFileDisplay.Content = _fileService.GetPartialViewMacroSnippetContent(snippetName!); + } + } + break; case Constants.Trees.Scripts: codeFileDisplay = _umbracoMapper.Map(new Script(string.Empty)); - codeFileDisplay.VirtualPath = _globalSettings.UmbracoScriptsPath; + if (codeFileDisplay is not null) + { + codeFileDisplay.VirtualPath = _globalSettings.UmbracoScriptsPath; + } + break; case Constants.Trees.Stylesheets: codeFileDisplay = _umbracoMapper.Map(new Stylesheet(string.Empty)); - codeFileDisplay.VirtualPath = _globalSettings.UmbracoCssPath; + if (codeFileDisplay is not null) + { + codeFileDisplay.VirtualPath = _globalSettings.UmbracoCssPath; + } + break; default: return new UmbracoProblemResult("Unsupported editortype", HttpStatusCode.BadRequest); } + if (codeFileDisplay is null) + { + return codeFileDisplay; + } // Make sure that the root virtual path ends with '/' - codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.EnsureEndsWith("/"); + codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath?.EnsureEndsWith("/"); if (id != Constants.System.RootString) { @@ -319,7 +363,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers codeFileDisplay.Path = Url.GetTreePathFromFilePath(id); } - codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.TrimStart("~"); + codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath?.TrimStart("~"); codeFileDisplay.FileType = type; return codeFileDisplay; } @@ -338,7 +382,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath"); virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath); - var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; switch (type) { case Constants.Trees.PartialViews: @@ -347,7 +391,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _fileService.DeletePartialViewFolder(virtualPath); return Ok(); } - if (_fileService.DeletePartialView(virtualPath, currentUser.Id)) + if (_fileService.DeletePartialView(virtualPath, currentUser?.Id)) { return Ok(); } @@ -359,7 +403,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _fileService.DeletePartialViewMacroFolder(virtualPath); return Ok(); } - if (_fileService.DeletePartialViewMacro(virtualPath, currentUser.Id)) + if (_fileService.DeletePartialViewMacro(virtualPath, currentUser?.Id)) { return Ok(); } @@ -373,7 +417,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } if (_fileService.GetScript(virtualPath) != null) { - _fileService.DeleteScript(virtualPath, currentUser.Id); + _fileService.DeleteScript(virtualPath, currentUser?.Id); return Ok(); } return new UmbracoProblemResult("No Script or folder found with the specified path", HttpStatusCode.NotFound); @@ -385,7 +429,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } if (_fileService.GetStylesheet(virtualPath) != null) { - _fileService.DeleteStylesheet(virtualPath, currentUser.Id); + _fileService.DeleteStylesheet(virtualPath, currentUser?.Id); return Ok(); } return new UmbracoProblemResult("No Stylesheet found with the specified path", HttpStatusCode.NotFound); @@ -416,8 +460,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (partialViewResult.Success) { display = _umbracoMapper.Map(partialViewResult.Result, display); - display.Path = Url.GetTreePathFromFilePath(partialViewResult.Result.Path); - display.Id = System.Web.HttpUtility.UrlEncode(partialViewResult.Result.Path); + display.Path = Url.GetTreePathFromFilePath(partialViewResult.Result?.Path); + display.Id = System.Web.HttpUtility.UrlEncode(partialViewResult.Result?.Path); return display; } @@ -431,8 +475,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (partialViewMacroResult.Success) { display = _umbracoMapper.Map(partialViewMacroResult.Result, display); - display.Path = Url.GetTreePathFromFilePath(partialViewMacroResult.Result.Path); - display.Id = System.Web.HttpUtility.UrlEncode(partialViewMacroResult.Result.Path); + display.Path = Url.GetTreePathFromFilePath(partialViewMacroResult.Result?.Path); + display.Id = System.Web.HttpUtility.UrlEncode(partialViewMacroResult.Result?.Path); return display; } @@ -445,8 +489,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var scriptResult = CreateOrUpdateScript(display); display = _umbracoMapper.Map(scriptResult, display); - display.Path = Url.GetTreePathFromFilePath(scriptResult.Path); - display.Id = System.Web.HttpUtility.UrlEncode(scriptResult.Path); + display.Path = Url.GetTreePathFromFilePath(scriptResult?.Path); + display.Id = System.Web.HttpUtility.UrlEncode(scriptResult?.Path); return display; //display.AddErrorNotification( @@ -457,8 +501,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var stylesheetResult = CreateOrUpdateStylesheet(display); display = _umbracoMapper.Map(stylesheetResult, display); - display.Path = Url.GetTreePathFromFilePath(stylesheetResult.Path); - display.Id = System.Web.HttpUtility.UrlEncode(stylesheetResult.Path); + display.Path = Url.GetTreePathFromFilePath(stylesheetResult?.Path); + display.Id = System.Web.HttpUtility.UrlEncode(stylesheetResult?.Path); return display; default: @@ -473,7 +517,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// The style sheet data /// The style rules - public StylesheetRule[] PostExtractStylesheetRules(StylesheetData data) + public StylesheetRule[]? PostExtractStylesheetRules(StylesheetData data) { if (data.Content.IsNullOrWhiteSpace()) { @@ -496,7 +540,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// Any "umbraco style rules" in the CSS will be removed and replaced with the rules passed in /// - public string PostInterpolateStylesheetRules(StylesheetData data) + public string? PostInterpolateStylesheetRules(StylesheetData data) { // first remove all existing rules var existingRules = data.Content.IsNullOrWhiteSpace() @@ -507,7 +551,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers data.Content = StylesheetHelper.ReplaceRule(data.Content, rule.Name, null); } - data.Content = data.Content.TrimEnd(Constants.CharArrays.LineFeedCarriageReturn); + data.Content = data.Content?.TrimEnd(Constants.CharArrays.LineFeedCarriageReturn); // now add all the posted rules if (data.Rules != null && data.Rules.Any()) @@ -537,69 +581,73 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// It's important to note that Scripts are DIFFERENT from cshtml files since scripts use IFileSystem and cshtml files /// use a normal file system because they must exist on a real file system for ASP.NET to work. /// - private IScript CreateOrUpdateScript(CodeFileDisplay display) + private IScript? CreateOrUpdateScript(CodeFileDisplay display) { return CreateOrUpdateFile(display, ".js", _fileSystems.ScriptsFileSystem, name => _fileService.GetScript(name), (script, userId) => _fileService.SaveScript(script, userId), - name => new Script(name)); + name => new Script(name ?? string.Empty)); } - private IStylesheet CreateOrUpdateStylesheet(CodeFileDisplay display) + private IStylesheet? CreateOrUpdateStylesheet(CodeFileDisplay display) { return CreateOrUpdateFile(display, ".css", _fileSystems.StylesheetsFileSystem, name => _fileService.GetStylesheet(name), (stylesheet, userId) => _fileService.SaveStylesheet(stylesheet, userId), - name => new Stylesheet(name) + name => new Stylesheet(name ?? string.Empty) ); } - private T CreateOrUpdateFile(CodeFileDisplay display, string extension, IFileSystem fileSystem, - Func getFileByName, Action saveFile, Func createFile) where T : IFile + private T CreateOrUpdateFile(CodeFileDisplay display, string extension, IFileSystem? fileSystem, + Func getFileByName, Action saveFile, Func createFile) where T : IFile? { //must always end with the correct extension display.Name = EnsureCorrectFileExtension(display.Name, extension); var virtualPath = display.VirtualPath ?? string.Empty; // this is all weird, should be using relative paths everywhere! - var relPath = fileSystem.GetRelativePath(virtualPath); + var relPath = fileSystem?.GetRelativePath(virtualPath); - if (relPath.EndsWith(extension) == false) + if (relPath?.EndsWith(extension) == false) { //this would typically mean it's new relPath = relPath.IsNullOrWhiteSpace() ? relPath + display.Name : relPath.EnsureEndsWith('/') + display.Name; } - var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; var file = getFileByName(relPath); if (file != null) { // might need to find the path - var orgPath = file.OriginalPath.Substring(0, file.OriginalPath.IndexOf(file.Name)); + var orgPath = file.Name is null ? string.Empty : file.OriginalPath.Substring(0, file.OriginalPath.IndexOf(file.Name)); file.Path = orgPath + display.Name; file.Content = display.Content; //try/catch? since this doesn't return an Attempt? - saveFile(file, currentUser.Id); + saveFile(file, currentUser?.Id); } else { file = createFile(relPath); - file.Content = display.Content; - saveFile(file, currentUser.Id); + if (file is not null) + { + file.Content = display.Content; + } + + saveFile(file, currentUser?.Id); } return file; } - private Attempt CreateOrUpdatePartialView(CodeFileDisplay display) + private Attempt CreateOrUpdatePartialView(CodeFileDisplay display) { return CreateOrUpdatePartialView(display, Constants.SystemDirectories.PartialViews, _fileService.GetPartialView, _fileService.SavePartialView, _fileService.CreatePartialView); } - private Attempt CreateOrUpdatePartialViewMacro(CodeFileDisplay display) + private Attempt CreateOrUpdatePartialViewMacro(CodeFileDisplay display) { return CreateOrUpdatePartialView(display, Constants.SystemDirectories.MacroPartials, _fileService.GetPartialViewMacro, _fileService.SavePartialViewMacro, _fileService.CreatePartialViewMacro); @@ -614,56 +662,56 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - private Attempt CreateOrUpdatePartialView( + private Attempt CreateOrUpdatePartialView( CodeFileDisplay display, string systemDirectory, - Func getView, - Func> saveView, - Func> createView) + Func getView, + Func> saveView, + Func> createView) { //must always end with the correct extension display.Name = EnsureCorrectFileExtension(display.Name, ".cshtml"); - Attempt partialViewResult; - var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; + Attempt partialViewResult; + var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; var virtualPath = NormalizeVirtualPath(display.VirtualPath, systemDirectory); var view = getView(virtualPath); if (view != null) { // might need to find the path - var orgPath = view.OriginalPath.Substring(0, view.OriginalPath.IndexOf(view.Name)); + var orgPath = view.OriginalPath.Substring(0, view.OriginalPath.IndexOf(view.Name ?? string.Empty)); view.Path = orgPath + display.Name; view.Content = display.Content; - partialViewResult = saveView(view, currentUser.Id); + partialViewResult = saveView(view, currentUser?.Id); } else { view = new PartialView(PartialViewType.PartialView, virtualPath + display.Name); view.Content = display.Content; - partialViewResult = createView(view, display.Snippet, currentUser.Id); + partialViewResult = createView(view, display.Snippet, currentUser?.Id); } return partialViewResult; } - private string NormalizeVirtualPath(string virtualPath, string systemDirectory) + private string NormalizeVirtualPath(string? virtualPath, string systemDirectory) { if (virtualPath.IsNullOrWhiteSpace()) return string.Empty; systemDirectory = systemDirectory.TrimStart("~"); systemDirectory = systemDirectory.Replace('\\', '/'); - virtualPath = virtualPath.TrimStart("~"); + virtualPath = virtualPath!.TrimStart("~"); virtualPath = virtualPath.Replace('\\', '/'); virtualPath = virtualPath.ReplaceFirst(systemDirectory, string.Empty); return virtualPath; } - private string EnsureCorrectFileExtension(string value, string extension) + private string? EnsureCorrectFileExtension(string? value, string extension) { - if (value.EndsWith(extension) == false) + if (value?.EndsWith(extension) == false) value += extension; return value; @@ -681,9 +729,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers // this is an internal class for passing stylesheet data from the client to the controller while editing public class StylesheetData { - public string Content { get; set; } + public string? Content { get; set; } - public StylesheetRule[] Rules { get; set; } + public StylesheetRule[]? Rules { get; set; } } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index ef5f39540e..578d4e4b10 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -67,7 +67,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly ILogger _logger; private readonly IScopeProvider _scopeProvider; - public object Domains { get; private set; } + public object? Domains { get; private set; } public ContentController( ICultureDictionary cultureDictionary, @@ -137,7 +137,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// Permission check is done for letter 'R' which is for which the user must have access to update /// - public async Task>> PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) + public async Task?>> PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) { if (saveModel.ContentId <= 0) return NotFound(); @@ -164,7 +164,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers foreach (var userGroup in allUserGroups) { //check if there's a permission set posted up for this user group - IEnumerable groupPermissions; + IEnumerable? groupPermissions; if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) { if (groupPermissions is null) @@ -196,7 +196,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// Permission check is done for letter 'R' which is for which the user must have access to view /// [Authorize(Policy = AuthorizationPolicies.ContentPermissionAdministrationById)] - public ActionResult> GetDetailedPermissions(int contentId) + public ActionResult?> GetDetailedPermissions(int contentId) { if (contentId <= 0) return NotFound(); var content = _contentService.GetById(contentId); @@ -209,14 +209,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return GetDetailedPermissions(content, allUserGroups); } - private ActionResult> GetDetailedPermissions(IContent content, IEnumerable allUserGroups) + private ActionResult?> GetDetailedPermissions(IContent content, IEnumerable allUserGroups) { //get all user groups and map their default permissions to the AssignedUserGroupPermissions model. //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults. var defaultPermissionsByGroup = _umbracoMapper.MapEnumerable(allUserGroups); - var defaultPermissionsAsDictionary = defaultPermissionsByGroup + var defaultPermissionsAsDictionary = defaultPermissionsByGroup.WhereNotNull() .ToDictionary(x => Convert.ToInt32(x.Id), x => x); //get the actual assigned permissions @@ -288,12 +288,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return content; } - private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent) + private static void SetupBlueprint(ContentItemDisplay content, IContent? persistedContent) { content.AllowPreview = false; //set a custom path since the tree that renders this has the content type id as the parent - content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id); + content.Path = string.Format("-1,{0},{1}", persistedContent?.ContentTypeId, content.Id); content.AllowedActions = new[] { "A" }; content.IsBlueprint = true; @@ -390,7 +390,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { // It's important to do this operation within a scope to reduce the amount of readlock queries. using var scope = _scopeProvider.CreateScope(autoComplete: true); - var contentTypes = contentTypesByAliases.ContentTypeAliases.Select(alias => _contentTypeService.Get(alias)); + var contentTypes = contentTypesByAliases.ContentTypeAliases?.Select(alias => _contentTypeService.Get(alias)).WhereNotNull(); return GetEmpties(contentTypes, contentTypesByAliases.ParentId).ToDictionary(x => x.ContentTypeAlias); } @@ -419,7 +419,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private ContentItemDisplay GetEmptyInner(IContentType contentType, int parentId) { - var emptyContent = _contentService.Create("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().Result ?? 0); + var emptyContent = _contentService.Create("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); var mapped = MapToDisplay(emptyContent); return CleanContentItemDisplay(mapped); @@ -446,13 +446,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - private IEnumerable GetEmpties(IEnumerable contentTypes, int parentId) + private IEnumerable GetEmpties(IEnumerable? contentTypes, int parentId) { var result = new List(); var backOfficeSecurity = _backofficeSecurityAccessor.BackOfficeSecurity; - var userId = backOfficeSecurity.GetUserId().Result ?? 0; - var currentUser = backOfficeSecurity.CurrentUser; + var userId = backOfficeSecurity?.GetUserId().Result ?? -1; + var currentUser = backOfficeSecurity?.CurrentUser; // We know that if the ID is less than 0 the parent is null. // Since this is called with parent ID it's safe to assume that the parent is the same for all the content types. var parent = parentId > 0 ? _contentService.GetById(parentId) : null; @@ -463,26 +463,32 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [path] = _userService.GetPermissionsForPath(currentUser, path) }; - foreach (var contentType in contentTypes) + if (contentTypes is not null) { - var emptyContent = _contentService.Create("", parentId, contentType, userId); - - var mapped = MapToDisplay(emptyContent, context => + foreach (var contentType in contentTypes) { - // Since the permissions depend on current user and path, we add both of these to context as well, - // that way we can compare the path and current user when mapping, if they're the same just take permissions - // and skip getting them again, in theory they should always be the same, but better safe than sorry., - context.Items["Parent"] = parent; - context.Items["CurrentUser"] = currentUser; - context.Items["Permissions"] = permissions; - }); - result.Add(CleanContentItemDisplay(mapped)); + var emptyContent = _contentService.Create("", parentId, contentType, userId); + + var mapped = MapToDisplay(emptyContent, context => + { + // Since the permissions depend on current user and path, we add both of these to context as well, + // that way we can compare the path and current user when mapping, if they're the same just take permissions + // and skip getting them again, in theory they should always be the same, but better safe than sorry., + context.Items["Parent"] = parent; + context.Items["CurrentUser"] = currentUser; + context.Items["Permissions"] = permissions; + }); + if (mapped is not null) + { + result.Add(CleanContentItemDisplay(mapped)); + } + } } return result; } - private ActionResult> GetEmptyByKeysInternal(Guid[] contentTypeKeys, int parentId) + private ActionResult> GetEmptyByKeysInternal(Guid[]? contentTypeKeys, int parentId) { using var scope = _scopeProvider.CreateScope(autoComplete: true); var contentTypes = _contentTypeService.GetAll(contentTypeKeys).ToList(); @@ -516,7 +522,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } [OutgoingEditorModelEvent] - public ActionResult GetEmptyBlueprint(int blueprintId, int parentId) + public ActionResult GetEmptyBlueprint(int blueprintId, int parentId) { var blueprint = _contentService.GetBlueprintById(blueprintId); if (blueprint == null) @@ -530,8 +536,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var mapped = _umbracoMapper.Map(blueprint); - //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); + if (mapped is not null) + { + //remove the listview app if it exists + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); + } return mapped; } @@ -605,12 +614,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (pageNumber > 0 && pageSize > 0) { - IQuery queryFilter = null; + IQuery? queryFilter = null; if (filter.IsNullOrWhiteSpace() == false) { //add the default text filter queryFilter = _sqlContext.Query() - .Where(x => x.Name.Contains(filter)); + .Where(x => x.Name != null) + .Where(x => x.Name!.Contains(filter)); } children = _contentService @@ -642,6 +652,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (!includeProperties.IsNullOrWhiteSpace()) context.SetIncludedProperties(includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries)); })) + .WhereNotNull() .ToList(); // evaluate now return pagedResult; @@ -670,9 +681,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return ValidationProblem(ModelState); } - var blueprint = _contentService.CreateContentFromBlueprint(content, name, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().Result ?? 0); + var blueprint = _contentService.CreateContentFromBlueprint(content, name, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - _contentService.SaveBlueprint(blueprint, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().Result ?? 0); + _contentService.SaveBlueprint(blueprint, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); var notificationModel = new SimpleNotificationModel(); notificationModel.AddSuccessNotification( @@ -683,10 +694,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return notificationModel; } - private bool EnsureUniqueName(string name, IContent content, string modelName) + private bool EnsureUniqueName(string? name, IContent content, string modelName) { var existing = _contentService.GetBlueprintsForContentTypes(content.ContentTypeId); - if (existing.Any(x => x.Name == name && x.Id != content.Id)) + if (existing?.Any(x => x.Name == name && x.Id != content.Id) ?? false) { ModelState.AddModelError(modelName, _localizedTextService.Localize("blueprints", "duplicateBlueprintMessage")); return false; @@ -706,12 +717,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers contentItem, (content, _) => { - if (!EnsureUniqueName(content.Name, content, "Name")) + if (!EnsureUniqueName(content.Name, content, "Name") || contentItem.PersistedContent is null) { return OperationResult.Cancel(new EventMessages()); } - _contentService.SaveBlueprint(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); + _contentService.SaveBlueprint(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); // we need to reuse the underlying logic so return the result that it wants return OperationResult.Succeed(new EventMessages()); @@ -736,13 +747,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { var contentItemDisplay = await PostSaveInternal( contentItem, - (content, contentSchedule) => _contentService.Save(content, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id, contentSchedule), + (content, contentSchedule) => _contentService.Save(content, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id, contentSchedule), MapToDisplayWithSchedule); return contentItemDisplay; } - private async Task>> PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func> mapToDisplay) + private async Task>> PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func> mapToDisplay) where TVariant : ContentVariantDisplay { // Recent versions of IE/Edge may send in the full client side file path instead of just the file name. @@ -816,11 +827,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var defaultCulture = _allLangs.Value.Values.FirstOrDefault(x => x.IsDefault)?.IsoCode; var cultureForInvariantErrors = CultureImpact.GetCultureForInvariantErrors( contentItem.PersistedContent, - contentItem.Variants.Where(x => x.Save).Select(x => x.Culture).ToArray(), + contentItem.Variants.Where(x => x.Save).Select(x => x.Culture).WhereNotNull().ToArray(), defaultCulture); //get the updated model - bool isBlueprint = contentItem.PersistedContent.Blueprint; + bool isBlueprint = contentItem.PersistedContent?.Blueprint ?? false; var contentSavedHeader = isBlueprint ? "editBlueprintSavedHeader" : "editContentSavedHeader"; var contentSavedText = isBlueprint ? "editBlueprintSavedText" : "editContentSavedText"; @@ -845,7 +856,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers case ContentSaveAction.SendPublish: case ContentSaveAction.SendPublishNew: - var sendResult = _contentService.SendToPublication(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); + var sendResult = _contentService.SendToPublication(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); wasCancelled = sendResult == false; if (sendResult) { @@ -1047,7 +1058,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// private void SaveAndNotify(ContentItemSave contentItem, Func saveMethod, int variantCount, Dictionary notifications, SimpleNotificationModel globalNotifications, - string savedContentHeaderLocalizationAlias, string invariantSavedLocalizationAlias, string variantSavedLocalizationAlias, string cultureForInvariantErrors, ContentScheduleCollection contentSchedule, + string savedContentHeaderLocalizationAlias, string invariantSavedLocalizationAlias, string variantSavedLocalizationAlias, string? cultureForInvariantErrors, ContentScheduleCollection? contentSchedule, out bool wasCancelled) { var saveResult = saveMethod(contentItem.PersistedContent, contentSchedule); @@ -2461,7 +2472,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - private ContentItemDisplay MapToDisplay(IContent content) => + private ContentItemDisplay MapToDisplay(IContent? content) => MapToDisplay(content, context => { context.Items["CurrentUser"] = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; @@ -2485,10 +2496,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - private ContentItemDisplay MapToDisplay(IContent content, Action contextOptions) + private ContentItemDisplay? MapToDisplay(IContent? content, Action contextOptions) { - ContentItemDisplay display = _umbracoMapper.Map(content, contextOptions); - display.AllowPreview = display.AllowPreview && content.Trashed == false && content.ContentType.IsElement == false; + ContentItemDisplay? display = _umbracoMapper.Map(content, contextOptions); + if (display is not null) + { + display.AllowPreview = display.AllowPreview && content?.Trashed == false && content.ContentType.IsElement == false; + } + return display; } @@ -2501,14 +2516,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var content = _contentService.GetById(contentId); if (content == null) return NotFound(); - var userNotifications = _notificationService.GetUserNotifications(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, content.Path).ToList(); + var userNotifications = _notificationService.GetUserNotifications(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, content.Path)?.ToList(); foreach (var a in _actionCollection.Where(x => x.ShowInNotifier)) { var n = new NotifySetting { Name = _localizedTextService.Localize("actions", a.Alias), - Checked = userNotifications.FirstOrDefault(x => x.Action == a.Letter.ToString()) != null, + Checked = userNotifications?.FirstOrDefault(x => x.Action == a.Letter.ToString()) != null, NotifyCode = a.Letter.ToString() }; notifications.Add(n); @@ -2523,7 +2538,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var content = _contentService.GetById(contentId); if (content == null) return NotFound(); - _notificationService.SetNotifications(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, content, notifyOptions); + _notificationService.SetNotifications(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, content, notifyOptions); return NoContent(); } @@ -2534,7 +2549,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers int contentId, int pageNumber = 1, int pageSize = 10, - string culture = null) + string? culture = null) { if (!string.IsNullOrEmpty(culture)) { @@ -2544,7 +2559,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - IEnumerable results = _contentVersionService.GetPagedContentVersions( + IEnumerable? results = _contentVersionService.GetPagedContentVersions( contentId, pageNumber - 1, pageSize, @@ -2563,14 +2578,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [Authorize(Policy = AuthorizationPolicies.ContentPermissionAdministrationById)] public IActionResult PostSetContentVersionPreventCleanup(int contentId, int versionId, bool preventCleanup) { - IContent content = _contentService.GetVersion(versionId); + IContent? content = _contentService.GetVersion(versionId); if (content == null || content.Id != contentId) { return NotFound(); } - _contentVersionService.SetPreventCleanup(versionId, preventCleanup, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().Result ?? 0); + _contentVersionService.SetPreventCleanup(versionId, preventCleanup, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); return NoContent(); } @@ -2621,21 +2636,21 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } [HttpGet] - public ContentVariantDisplay GetRollbackVersion(int versionId, string? culture = null) + public ContentVariantDisplay? GetRollbackVersion(int versionId, string? culture = null) { var version = _contentService.GetVersion(versionId); var content = MapToDisplay(version); return culture == null - ? content.Variants.FirstOrDefault() //No culture set - so this is an invariant node - so just list me the first item in here - : content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); + ? content.Variants?.FirstOrDefault() //No culture set - so this is an invariant node - so just list me the first item in here + : content.Variants?.FirstOrDefault(x => x.Language?.IsoCode == culture); } [Authorize(Policy = AuthorizationPolicies.ContentPermissionRollbackById)] [HttpPost] public IActionResult PostRollbackContent(int contentId, int versionId, string culture = "*") { - var rollbackResult = _contentService.Rollback(contentId, versionId, culture, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().Result ?? 0); + var rollbackResult = _contentService.Rollback(contentId, versionId, culture, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); if (rollbackResult.Success) return Ok(); diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index ec0d273b56..955af96972 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -97,7 +97,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security /// The external login URL users should be redirected to during the login flow. /// The current user's identifier, which will be used to provide CSRF protection. /// A configured . - public override AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string userId = null) + public override AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string? redirectUrl, string? userId = null) { // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs // to be able to use our own XsrfKey/LoginProviderKey because the default is private :/ diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index ee7d480731..6ff690ad1f 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -116,12 +116,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees foreach (var t in allTrees) { var nodeResult = await TryGetRootNode(t, queryStrings); - if (!(nodeResult.Result is null)) + if (!(nodeResult?.Result is null)) { return nodeResult.Result; } - var node = nodeResult.Value; + var node = nodeResult?.Value; if (node != null) { nodes.Add(node); @@ -189,7 +189,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// loading multiple trees we will just return null so that it's not added /// to the list /// - private async Task> TryGetRootNode(Tree tree, FormCollection querystring) + private async Task?> TryGetRootNode(Tree tree, FormCollection querystring) { if (tree == null) { @@ -210,24 +210,24 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var childrenResult = await GetChildren(tree, id, querystring); - if (!(childrenResult.Result is null)) + if (!(childrenResult?.Result is null)) { return new ActionResult(childrenResult.Result); } - var children = childrenResult.Value; + var children = childrenResult?.Value; var rootNodeResult = await GetRootNode(tree, querystring); - if (!(rootNodeResult.Result is null)) + if (!(rootNodeResult?.Result is null)) { return rootNodeResult.Result; } - var rootNode = rootNodeResult.Value; + var rootNode = rootNodeResult?.Value; var sectionRoot = TreeRootNode.CreateSingleTreeRoot( Constants.System.RootString, - rootNode.ChildNodesUrl, + rootNode!.ChildNodesUrl, rootNode.MenuUrl, rootNode.Name, children, @@ -249,7 +249,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// Gets the root node of a tree. /// - private async Task> GetRootNode(Tree tree, FormCollection querystring) + private async Task?> GetRootNode(Tree tree, FormCollection querystring) { if (tree == null) { @@ -259,23 +259,27 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var result = await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring); // return null if the user isn't authorized to view that tree - if (!((ForbidResult)result.Result is null)) + if (!((ForbidResult?)result.Result is null)) { return null; } - var controller = (TreeControllerBase)result.Value; - var rootNodeResult = await controller.GetRootNode(querystring); - if (!(rootNodeResult.Result is null)) + var controller = (TreeControllerBase?)result.Value; + TreeNode? rootNode = null; + if (controller is not null) { - return rootNodeResult.Result; - } + var rootNodeResult = await controller.GetRootNode(querystring); + if (!(rootNodeResult.Result is null)) + { + return rootNodeResult.Result; + } - var rootNode = rootNodeResult.Value; + rootNode = rootNodeResult.Value; - if (rootNode == null) - { - throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\"."); + if (rootNode == null) + { + throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\"."); + } } return rootNode; @@ -284,7 +288,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// Get the child nodes of a tree node. /// - private async Task> GetChildren(Tree tree, int id, FormCollection querystring) + private async Task?> GetChildren(Tree tree, int id, FormCollection querystring) { if (tree == null) { @@ -294,18 +298,18 @@ namespace Umbraco.Cms.Web.BackOffice.Trees // the method we proxy has an 'id' parameter which is *not* in the querystring, // we need to add it for the proxy to work (else, it does not find the method, // when trying to run auth filters etc). - var d = querystring?.ToDictionary(x => x.Key, x => x.Value) ?? new Dictionary(); + var d = querystring.ToDictionary(x => x.Key, x => x.Value) ?? new Dictionary(); d["id"] = StringValues.Empty; var proxyQuerystring = new FormCollection(d); var controllerResult = await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring); if (!(controllerResult.Result is null)) { - return new ActionResult(controllerResult.Result); + return new ActionResult(controllerResult.Result); } - var controller = (TreeControllerBase)controllerResult.Value; - return await controller.GetNodes(id.ToInvariantString(), querystring); + var controller = (TreeControllerBase?)controllerResult.Value; + return controller is not null ? await controller.GetNodes(id.ToInvariantString(), querystring) : null; } /// diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs index 72bf6c299c..173183645b 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs @@ -52,7 +52,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); } - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -61,15 +61,19 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var root = rootResult.Value; - //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.ContentBlueprints}/intro"; + if (root is not null) + { + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.ContentBlueprints}/intro"; + + //check if there are any content blueprints + root.HasChildren = _contentService.GetBlueprintsForContentTypes()?.Any() ?? false; + } - //check if there are any content blueprints - root.HasChildren = _contentService.GetBlueprintsForContentTypes().Any(); return root; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); @@ -142,7 +146,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees { var ct = _contentTypeService.Get(cte.Id); var createItem = menu.Items.Add(LocalizedTextService, opensDialog: true); - createItem.NavigateToRoute("/settings/contentBlueprints/edit/-1?create=true&doctype=" + ct.Alias); + createItem?.NavigateToRoute("/settings/contentBlueprints/edit/-1?create=true&doctype=" + ct?.Alias); menu.Items.Add(new RefreshNode(LocalizedTextService, true)); diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs index 74e8b10b23..1e3a3ba726 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs @@ -82,17 +82,17 @@ namespace Umbraco.Cms.Web.BackOffice.Trees protected override bool RecycleBinSmells => _contentService.RecycleBinSmells(); - private int[] _userStartNodes; + private int[]? _userStartNodes; protected override int[] UserStartNodes - => _userStartNodes ?? (_userStartNodes = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches)); + => _userStartNodes ?? (_userStartNodes = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.CalculateContentStartNodeIds(_entityService, _appCaches) ?? Array.Empty()); /// - protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormCollection queryStrings) + protected override TreeNode? GetSingleTreeNode(IEntitySlim entity, string parentId, FormCollection queryStrings) { - var culture = queryStrings?["culture"].ToString(); + var culture = queryStrings["culture"].ToString(); var allowedUserOptions = GetAllowedUserMenuItemsForNode(entity); if (CanUserAccessNode(entity, allowedUserOptions, culture)) @@ -163,7 +163,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees menu.DefaultMenuAlias = ActionNew.ActionAlias; // we need to get the default permissions as you can't set permissions on the very root node - var permission = _userService.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, Constants.System.Root).First(); + var permission = _userService.GetPermissions(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, Constants.System.Root).First(); var nodeActions = _actions.FromEntityPermission(permission) .Select(x => new MenuItem(x)); @@ -199,7 +199,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } //if the user has no path access for this node, all they can do is refresh - if (!_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasContentPathAccess(item, _entityService, _appCaches)) + if (!_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasContentPathAccess(item, _entityService, _appCaches) ?? false) { var menu = _menuItemCollectionFactory.Create(); menu.Items.Add(new RefreshNode(LocalizedTextService, true)); @@ -252,10 +252,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var culture = queryStrings["culture"].TryConvertTo(); //if this is null we'll set it to the default. - var cultureVal = (culture.Success ? culture.Result : null).IfNullOrWhiteSpace(_localizationService.GetDefaultLanguageIsoCode()); + var cultureVal = (culture.Success ? culture.Result : null)?.IfNullOrWhiteSpace(_localizationService.GetDefaultLanguageIsoCode()); // set names according to variations - foreach (var entity in result.Value) + foreach (var entity in result.Value!) { EnsureName(entity, cultureVal); } @@ -320,7 +320,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - private void EnsureName(IEntitySlim entity, string culture) + private void EnsureName(IEntitySlim entity, string? culture) { if (culture == null) { @@ -366,7 +366,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var menuItem = menu.Items.Add(LocalizedTextService, hasSeparator, opensDialog); } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) { return _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Document, pageSize, pageIndex, out totalFound, searchFrom); } diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs index aa2f4194f4..e54e695be3 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs @@ -68,7 +68,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - public ActionResult GetTreeNode([FromRoute] string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) + public ActionResult GetTreeNode([FromRoute] string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) { int asInt; Guid asGuid = Guid.Empty; @@ -90,8 +90,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var node = GetSingleTreeNode(entity, entity.ParentId.ToInvariantString(), queryStrings); - //add the tree alias to the node since it is standalone (has no root for which this normally belongs) - node.AdditionalData["treeAlias"] = TreeAlias; + if (node is not null) + { + // Add the tree alias to the node since it is standalone (has no root for which this normally belongs) + node.AdditionalData["treeAlias"] = TreeAlias; + } + return node; } @@ -102,7 +106,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var nodeResult = base.CreateRootNode(queryStrings); if ((nodeResult.Result is null)) @@ -111,7 +115,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var node = nodeResult.Value; - if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false) + if (node is not null && IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false) { node.AdditionalData["noAccess"] = true; } @@ -119,7 +123,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return node; } - protected abstract TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormCollection queryStrings); + protected abstract TreeNode? GetSingleTreeNode(IEntitySlim entity, string parentId, FormCollection queryStrings); /// /// Returns a for the and @@ -129,8 +133,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormCollection queryStrings, - int[] startNodeIds, string[] startNodePaths) + internal TreeNode? GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormCollection queryStrings, + int[]? startNodeIds, string[]? startNodePaths) { var entityIsAncestorOfStartNodes = ContentPermissions.IsInBranchOfStartNode(e.Path, startNodeIds, startNodePaths, out var hasPathAccess); var ignoreUserStartNodes = IgnoreUserStartNodes(queryStrings); @@ -151,17 +155,17 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return treeNode; } - private void GetUserStartNodes(out int[] startNodeIds, out string[] startNodePaths) + private void GetUserStartNodes(out int[]? startNodeIds, out string[]? startNodePaths) { switch (RecycleBinId) { case Constants.System.RecycleBinMedia: - startNodeIds = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches); - startNodePaths = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetMediaStartNodePaths(_entityService, _appCaches); + startNodeIds = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.CalculateMediaStartNodeIds(_entityService, _appCaches); + startNodePaths = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.GetMediaStartNodePaths(_entityService, _appCaches); break; case Constants.System.RecycleBinContent: - startNodeIds = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches); - startNodePaths = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetContentStartNodePaths(_entityService, _appCaches); + startNodeIds = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.CalculateContentStartNodeIds(_entityService, _appCaches); + startNodePaths = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.GetContentStartNodePaths(_entityService, _appCaches); break; default: throw new NotSupportedException("Path access is only determined on content or media"); @@ -183,7 +187,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// protected abstract int[] UserStartNodes { get; } - protected virtual ActionResult PerformGetTreeNodes(string id, FormCollection queryStrings) + protected virtual ActionResult PerformGetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); @@ -205,7 +209,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access if (ignoreUserStartNodes == false && HasPathAccess(id, queryStrings) == false) { - _logger.LogWarning("User {Username} does not have access to node with id {Id}", _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Username, id); + _logger.LogWarning("User {Username} does not have access to node with id {Id}", _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Username, id); return nodes; } @@ -226,7 +230,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return entitiesResult.Result; } - var entities = entitiesResult.Value.ToList(); + var entities = entitiesResult.Value?.ToList(); //get the current user start node/paths GetUserStartNodes(out var userStartNodes, out var userStartNodePaths); @@ -237,23 +241,23 @@ namespace Umbraco.Cms.Web.BackOffice.Trees if (id == rootIdString && hasAccessToRoot == false) { // first add the entities that are topmost to the nodes collection - var topMostEntities = entities.Where(x => x.Level == 1).ToArray(); - nodes.AddRange(topMostEntities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); + var topMostEntities = entities?.Where(x => x.Level == 1).ToArray(); + nodes.AddRange(topMostEntities?.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).WhereNotNull() ?? Enumerable.Empty()); // now add the topmost nodes of the entities that aren't topmost to the nodes collection as well // - these will appear as "no-access" nodes in the tree, but will allow the editors to drill down through the tree // until they reach their start nodes - var topNodeIds = entities.Except(topMostEntities).Select(GetTopNodeId).Where(x => x != 0).Distinct().ToArray(); - if (topNodeIds.Length > 0) + var topNodeIds = entities?.Except(topMostEntities ?? Enumerable.Empty()).Select(GetTopNodeId).Where(x => x != 0).Distinct().ToArray(); + if (topNodeIds?.Length > 0) { var topNodes = _entityService.GetAll(UmbracoObjectType, topNodeIds.ToArray()); - nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); + nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).WhereNotNull()); } } else { // the user has access to the root, just add the entities - nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); + nodes.AddRange(entities?.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).WhereNotNull() ?? Enumerable.Empty()); } return nodes; @@ -323,13 +327,13 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - protected bool HasPathAccess(IUmbracoEntity entity, FormCollection queryStrings) + protected bool HasPathAccess(IUmbracoEntity? entity, FormCollection queryStrings) { if (entity == null) return false; return RecycleBinId == Constants.System.RecycleBinContent - ? _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasContentPathAccess(entity, _entityService, _appCaches) - : _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasMediaPathAccess(entity, _entityService, _appCaches); + ? _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasContentPathAccess(entity, _entityService, _appCaches) ?? false + : _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasMediaPathAccess(entity, _entityService, _appCaches) ?? false; } /// @@ -341,7 +345,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// This method is overwritten strictly to render the recycle bin, it should serve no other purpose /// - protected sealed override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected sealed override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //check if we're rendering the root if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root)) @@ -370,7 +374,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees //and for some reason when there are no dashboards, this parameter is missing if (IsDialog(queryStrings) == false && id == Constants.System.RootString && queryStrings.HasKey("application")) { - nodes.Add(CreateTreeNode( + nodes?.Add(CreateTreeNode( RecycleBinId.ToInvariantString(), id, queryStrings, @@ -431,7 +435,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// Currently this just checks if it is a container type, if it is we cannot render children. In the future this might check for other things. /// - private ActionResult GetTreeNodesInternal(string id, FormCollection queryStrings) + private ActionResult GetTreeNodesInternal(string id, FormCollection queryStrings) { var current = GetEntityFromId(id); @@ -462,8 +466,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var deleteAction = _actionCollection.FirstOrDefault(y => y.Letter == ActionDelete.ActionLetter); if (deleteAction != null) { - var perms = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetPermissions(Constants.System.RecycleBinContentString, _userService); - deleteAllowed = perms.FirstOrDefault(x => x.Contains(deleteAction.Letter)) != null; + var perms = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.GetPermissions(Constants.System.RecycleBinContentString, _userService); + deleteAllowed = perms?.FirstOrDefault(x => x.Contains(deleteAction.Letter)) != null; } var menu = MenuItemCollectionFactory.Create(); @@ -513,7 +517,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees internal IEnumerable GetAllowedUserMenuItemsForNode(IUmbracoEntity dd) { - var permissionsForPath = _userService.GetPermissionsForPath(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, dd.Path).GetAllPermissions(); + var permissionsForPath = _userService.GetPermissionsForPath(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser, dd.Path).GetAllPermissions(); return _actionCollection.GetByLetters(permissionsForPath).Select(x => new MenuItem(x)); } @@ -524,7 +528,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// A list of MenuItems that the user has permissions to execute on the current document /// By default the user must have Browse permissions to see the node in the Content tree /// - internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable allowedUserOptions, string culture) + internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable allowedUserOptions, string? culture) { // TODO: At some stage when we implement permissions on languages we'll need to take care of culture return allowedUserOptions.Select(x => x.Action).OfType().Any(); @@ -536,11 +540,11 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - internal Tuple GetIdentifierFromString(string id) + internal Tuple? GetIdentifierFromString(string id) { Guid idGuid; int idInt; - Udi idUdi; + Udi? idUdi; if (Guid.TryParse(id, out idGuid)) { @@ -568,11 +572,11 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// This object has it's own contextual cache for these lookups /// - internal IEntitySlim GetEntityFromId(string id) + internal IEntitySlim? GetEntityFromId(string id) { return _entityCache.GetOrAdd(id, s => { - IEntitySlim entity; + IEntitySlim? entity; if (Guid.TryParse(s, out var idGuid)) { @@ -596,7 +600,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees }); } - private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); private bool? _ignoreUserStartNodes; diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index 9fd4f98875..a688713109 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -40,7 +40,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees _entityService = entityService; } - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -48,12 +48,17 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return rootResult; } var root = rootResult.Value; - //check if there are any types - root.HasChildren = _contentTypeService.GetAll().Any(); + + if (root is not null) + { + //check if there are any types + root.HasChildren = _contentTypeService.GetAll().Any(); + } + return root; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { if (!int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) { @@ -98,8 +103,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees node.Path = dt.Path; // now we can enrich the result with content type data that's not available in the entity service output - node.Alias = contentType.Alias; - node.AdditionalData["isElement"] = contentType.IsElement; + node.Alias = contentType?.Alias ?? string.Empty; + node.AdditionalData["isElement"] = contentType?.IsElement; return node; })); @@ -175,7 +180,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) => _treeSearcher.EntitySearch(UmbracoObjectTypes.DocumentType, query, pageSize, pageIndex, out totalFound, searchFrom); } diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs index 2a2c4bed0c..408eca9556 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs @@ -41,7 +41,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees _dataTypeService = dataTypeService; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { if (!int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) { @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var systemListViewDataTypeIds = GetNonDeletableSystemListViewDataTypeIds(); var children = _entityService.GetChildren(intId, UmbracoObjectTypes.DataType).ToArray(); - var dataTypes = Enumerable.ToDictionary(_dataTypeService.GetAll(children.Select(c => c.Id).ToArray()), dt => dt.Id); + var dataTypes = Enumerable.ToDictionary(_dataTypeService.GetAll(children.Select(c => c.Id).ToArray()) ?? Enumerable.Empty(), dt => dt.Id); nodes.AddRange( children @@ -79,7 +79,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees .Select(dt => { var dataType = dataTypes[dt.Id]; - var node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, dataType.Editor.Icon, false); + var node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, dataType.Editor?.Icon, false); node.Path = dt.Path; return node; }) @@ -170,7 +170,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) => _treeSearcher.EntitySearch(UmbracoObjectTypes.DataType, query, pageSize, pageIndex, out totalFound, searchFrom); } } diff --git a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs index c61fabfa7b..82c5c36297 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs @@ -36,7 +36,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees _localizationService = localizationService; } - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -51,8 +51,11 @@ namespace Umbraco.Cms.Web.BackOffice.Trees if (!queryStrings["application"].ToString().IsNullOrWhiteSpace()) section = queryStrings["application"]; - // this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = $"{section}/{Constants.Trees.Dictionary}/list"; + if (root is not null) + { + // this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = $"{section}/{Constants.Trees.Dictionary}/list"; + } return root; } @@ -68,7 +71,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { if (!int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) { @@ -82,14 +85,14 @@ namespace Umbraco.Cms.Web.BackOffice.Trees if (id == Constants.System.RootString) { nodes.AddRange( - _localizationService.GetRootDictionaryItems().OrderBy(ItemSort()).Select( + _localizationService.GetRootDictionaryItems()?.OrderBy(ItemSort()).Select( x => CreateTreeNode( x.Id.ToInvariantString(), id, queryStrings, x.ItemKey, Constants.Icons.Dictionary, - _localizationService.GetDictionaryItemChildren(x.Key).Any()))); + _localizationService.GetDictionaryItemChildren(x.Key)?.Any() ?? false)) ?? Enumerable.Empty()); } else { @@ -98,14 +101,14 @@ namespace Umbraco.Cms.Web.BackOffice.Trees if (parentDictionary == null) return nodes; - nodes.AddRange(_localizationService.GetDictionaryItemChildren(parentDictionary.Key).ToList().OrderBy(ItemSort()).Select( + nodes.AddRange(_localizationService.GetDictionaryItemChildren(parentDictionary.Key)?.ToList().OrderBy(ItemSort()).Select( x => CreateTreeNode( x.Id.ToInvariantString(), id, queryStrings, x.ItemKey, Constants.Icons.Dictionary, - _localizationService.GetDictionaryItemChildren(x.Key).Any()))); + _localizationService.GetDictionaryItemChildren(x.Key)?.Any() ?? false)) ?? Enumerable.Empty()); } return nodes; diff --git a/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs index d20c400e7f..579b1c5f14 100644 --- a/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs @@ -29,7 +29,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees MenuItemCollectionFactory = menuItemCollectionFactory; } - protected abstract IFileSystem FileSystem { get; } + protected abstract IFileSystem? FileSystem { get; } protected IMenuItemCollectionFactory MenuItemCollectionFactory { get; } protected abstract string[] Extensions { get; } protected abstract string FileIcon { get; } @@ -49,29 +49,33 @@ namespace Umbraco.Cms.Web.BackOffice.Trees treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var path = string.IsNullOrEmpty(id) == false && id != Constants.System.RootString ? WebUtility.UrlDecode(id).TrimStart("/") : ""; - var directories = FileSystem.GetDirectories(path); + var directories = FileSystem?.GetDirectories(path); var nodes = new TreeNodeCollection(); - foreach (var directory in directories) + if (directories is not null) { - var hasChildren = FileSystem.GetFiles(directory).Any() || FileSystem.GetDirectories(directory).Any(); + foreach (var directory in directories) + { + var hasChildren = FileSystem is not null && (FileSystem.GetFiles(directory).Any() || FileSystem.GetDirectories(directory).Any()); - var name = Path.GetFileName(directory); - var node = CreateTreeNode(WebUtility.UrlEncode(directory), path, queryStrings, name, "icon-folder", hasChildren); - OnRenderFolderNode(ref node); - if (node != null) - nodes.Add(node); + var name = Path.GetFileName(directory); + var node = CreateTreeNode(WebUtility.UrlEncode(directory), path, queryStrings, name, "icon-folder", hasChildren); + OnRenderFolderNode(ref node); + if (node != null) + nodes.Add(node); + } } + //this is a hack to enable file system tree to support multiple file extension look-up //so the pattern both support *.* *.xml and xml,js,vb for lookups - var files = FileSystem.GetFiles(path).Where(x => + var files = FileSystem?.GetFiles(path).Where(x => { var extension = Path.GetExtension(x); @@ -81,22 +85,25 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return extension != null && Extensions.Contains(extension.Trim(Constants.CharArrays.Period), StringComparer.InvariantCultureIgnoreCase); }); - foreach (var file in files) + if (files is not null) { - var withoutExt = Path.GetFileNameWithoutExtension(file); - if (withoutExt.IsNullOrWhiteSpace()) continue; + foreach (var file in files) + { + var withoutExt = Path.GetFileNameWithoutExtension(file); + if (withoutExt.IsNullOrWhiteSpace()) continue; - var name = Path.GetFileName(file); - var node = CreateTreeNode(WebUtility.UrlEncode(file), path, queryStrings, name, FileIcon, false); - OnRenderFileNode(ref node); - if (node != null) - nodes.Add(node); + var name = Path.GetFileName(file); + var node = CreateTreeNode(WebUtility.UrlEncode(file), path, queryStrings, name, FileIcon, false); + OnRenderFileNode(ref node); + if (node != null) + nodes.Add(node); + } } return nodes; } - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -111,7 +118,13 @@ namespace Umbraco.Cms.Web.BackOffice.Trees { return treeNodesResult.Result; } - root.HasChildren = treeNodesResult.Value.Any(); + + if (root is not null) + { + root.HasChildren = treeNodesResult.Value?.Any() ?? false; + + } + return root; } @@ -138,7 +151,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees //create action menu.Items.Add(LocalizedTextService, opensDialog: true); - var hasChildren = FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any(); + var hasChildren = FileSystem is not null && (FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any()); //We can only delete folders if it doesn't have any children (folders or files) if (hasChildren == false) @@ -177,8 +190,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees ? WebUtility.UrlDecode(id).TrimStart("/") : ""; - var isFile = FileSystem.FileExists(path); - var isDirectory = FileSystem.DirectoryExists(path); + var isFile = FileSystem?.FileExists(path) ?? false; + var isDirectory = FileSystem?.DirectoryExists(path) ?? false; if (isDirectory) { diff --git a/src/Umbraco.Web.BackOffice/Trees/ITreeNodeController.cs b/src/Umbraco.Web.BackOffice/Trees/ITreeNodeController.cs index 409d92558c..ccb8b7508c 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ITreeNodeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ITreeNodeController.cs @@ -16,7 +16,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - ActionResult GetTreeNode( + ActionResult GetTreeNode( string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings ); diff --git a/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs index 93ddd37bfb..aaffe4f823 100644 --- a/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs @@ -25,13 +25,13 @@ namespace Umbraco.Cms.Web.BackOffice.Trees : base(textService, umbracoApiControllerTypeCollection, eventAggregator) { } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //We don't have any child nodes & only use the root node to load a custom UI return new TreeNodeCollection(); } - protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult? GetMenuForNode(string id, FormCollection queryStrings) { //We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI return null; @@ -41,7 +41,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -50,11 +50,14 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var root = rootResult.Value; - //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.Languages}/overview"; - root.Icon = Constants.Icons.Language; - root.HasChildren = false; - root.MenuUrl = null; + if (root is not null) + { + // This will load in a custom UI instead of the dashboard for the root node + root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.Languages}/overview"; + root.Icon = Constants.Icons.Language; + root.HasChildren = false; + root.MenuUrl = null; + } return root; } diff --git a/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs index 29424c5e12..7fbcfb6100 100644 --- a/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs @@ -25,13 +25,13 @@ namespace Umbraco.Cms.Web.BackOffice.Trees { } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //We don't have any child nodes & only use the root node to load a custom UI return new TreeNodeCollection(); } - protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult? GetMenuForNode(string id, FormCollection queryStrings) { //We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI return null; @@ -41,7 +41,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -50,11 +50,14 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var root = rootResult.Value; - //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.LogViewer}/overview"; - root.Icon = Constants.Icons.LogViewer; - root.HasChildren = false; - root.MenuUrl = null; + if (root is not null) + { + // This will load in a custom UI instead of the dashboard for the root node + root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.LogViewer}/overview"; + root.Icon = Constants.Icons.LogViewer; + root.HasChildren = false; + root.MenuUrl = null; + } return root; } diff --git a/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs index d90a53c8e2..310d961bb7 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees _macroService = macroService; } - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -39,12 +39,16 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var root = rootResult.Value; - //check if there are any macros - root.HasChildren = _macroService.GetAll().Any(); + if (root is not null) + { + // Check if there are any macros + root.HasChildren = _macroService.GetAll().Any(); + } + return root; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs index a1a83bc5f3..8033d0975b 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs @@ -65,9 +65,9 @@ namespace Umbraco.Cms.Web.BackOffice.Trees protected override bool RecycleBinSmells => _mediaService.RecycleBinSmells(); - private int[] _userStartNodes; + private int[]? _userStartNodes; protected override int[] UserStartNodes - => _userStartNodes ?? (_userStartNodes = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches)); + => _userStartNodes ?? (_userStartNodes = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.CalculateMediaStartNodeIds(_entityService, _appCaches)) ?? Array.Empty(); /// /// Creates a tree node for a content item based on an UmbracoEntity @@ -134,7 +134,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } //if the user has no path access for this node, all they can do is refresh - if (!_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasMediaPathAccess(item, _entityService, _appCaches)) + if (!_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasMediaPathAccess(item, _entityService, _appCaches) ?? false) { menu.Items.Add(new RefreshNode(LocalizedTextService, true)); return menu; @@ -183,7 +183,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return HasPathAccess(entity, queryStrings); } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) { return _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); } diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs index a631165613..de46d135a3 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs @@ -40,7 +40,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees _entityService = entityService; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { if (!int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) { @@ -134,7 +134,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } menu.Items.Add(LocalizedTextService, opensDialog: true); - if(ct.IsSystemMediaType() == false) + if(ct?.IsSystemMediaType() == false) { menu.Items.Add(LocalizedTextService, opensDialog: true); } @@ -145,7 +145,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) => _treeSearcher.EntitySearch(UmbracoObjectTypes.MediaType, query, pageSize, pageIndex, out totalFound, searchFrom); } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs index 0559a17a53..77918a452f 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs @@ -35,17 +35,21 @@ namespace Umbraco.Cms.Web.BackOffice.Trees .OrderBy(x => x.Name) .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, Constants.Icons.MemberGroup, false)); - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - ActionResult rootResult = base.CreateRootNode(queryStrings); + ActionResult rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) { - return rootResult; + return rootResult.Result; + } + TreeNode? root = rootResult.Value; + + if (root is not null) + { + // Check if there are any groups + root.HasChildren = _memberGroupService.GetAll().Any(); } - TreeNode root = rootResult.Value; - //check if there are any groups - root.HasChildren = _memberGroupService.GetAll().Any(); return root; } } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs index 4e42b10b26..8b17abbbd7 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs @@ -56,21 +56,25 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// Gets an individual tree node /// - public ActionResult GetTreeNode([FromRoute]string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public ActionResult GetTreeNode([FromRoute]string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { - ActionResult node = GetSingleTreeNode(id, queryStrings); + ActionResult node = GetSingleTreeNode(id, queryStrings); if (!(node.Result is null)) { return node.Result; } - //add the tree alias to the node since it is standalone (has no root for which this normally belongs) - node.Value.AdditionalData["treeAlias"] = TreeAlias; + if (node.Value is not null) + { + // Add the tree alias to the node since it is standalone (has no root for which this normally belongs) + node.Value.AdditionalData["treeAlias"] = TreeAlias; + } + return node; } - protected ActionResult GetSingleTreeNode(string id, FormCollection queryStrings) + protected ActionResult GetSingleTreeNode(string id, FormCollection queryStrings) { Guid asGuid; if (Guid.TryParse(id, out asGuid) == false) @@ -100,7 +104,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return node; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); @@ -112,7 +116,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees nodes.AddRange(_memberTypeService.GetAll() .Select(memberType => - CreateTreeNode(memberType.Alias, id, queryStrings, memberType.Name, memberType.Icon.IfNullOrWhiteSpace(Constants.Icons.Member), true, + CreateTreeNode(memberType.Alias, id, queryStrings, memberType.Name, memberType.Icon?.IfNullOrWhiteSpace(Constants.Icons.Member), true, queryStrings.GetRequiredValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + memberType.Alias))); } @@ -144,7 +148,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees //add delete option for all members menu.Items.Add(LocalizedTextService, opensDialog: true); - if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.HasAccessToSensitiveData()) + if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? false) { menu.Items.Add(new ExportMember(LocalizedTextService)); } @@ -152,7 +156,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) { return _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Member, pageSize, pageIndex, out totalFound, searchFrom); } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs index 94c84accab..c8000da0ae 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs @@ -28,7 +28,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees MenuItemCollectionFactory = menuItemCollectionFactory; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); nodes.AddRange(GetTreeNodesFromService(id, queryStrings)); diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs index 5595365e2b..c9e77e7eff 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs @@ -39,7 +39,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -48,10 +48,15 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var root = rootResult.Value; - //check if there are any member types - root.HasChildren = _memberTypeService.GetAll().Any(); + if (root is not null) + { + // Check if there are any member types + root.HasChildren = _memberTypeService.GetAll().Any(); + } + return root; } + protected override IEnumerable GetTreeNodesFromService(string id, FormCollection queryStrings) { return _memberTypeService.GetAll() @@ -59,7 +64,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, dt?.Icon ?? Constants.Icons.MemberType, false)); } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) => _treeSearcher.EntitySearch(UmbracoObjectTypes.MemberType, query, pageSize, pageIndex, out totalFound, searchFrom); } diff --git a/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs index cb915f545b..7075258559 100644 --- a/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs @@ -34,7 +34,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -43,17 +43,20 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var root = rootResult.Value; + if (root is not null) + { + // This will load in a custom UI instead of the dashboard for the root node + root.RoutePath = $"{Constants.Applications.Packages}/{Constants.Trees.Packages}/repo"; + root.Icon = Constants.Icons.Packages; - //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = $"{Constants.Applications.Packages}/{Constants.Trees.Packages}/repo"; - root.Icon = Constants.Icons.Packages; + root.HasChildren = false; + } - root.HasChildren = false; return root; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //full screen app without tree nodes return TreeNodeCollection.Empty; diff --git a/src/Umbraco.Web.BackOffice/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/PartialViewMacrosTreeController.cs index 96d9004635..2a27273f8c 100644 --- a/src/Umbraco.Web.BackOffice/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/PartialViewMacrosTreeController.cs @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees [CoreTree] public class PartialViewMacrosTreeController : PartialViewsTreeController { - protected override IFileSystem FileSystem { get; } + protected override IFileSystem? FileSystem { get; } private static readonly string[] ExtensionsStatic = {"cshtml"}; diff --git a/src/Umbraco.Web.BackOffice/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/PartialViewsTreeController.cs index c79175dba8..78cecea307 100644 --- a/src/Umbraco.Web.BackOffice/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/PartialViewsTreeController.cs @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees [CoreTree] public class PartialViewsTreeController : FileSystemTreeController { - protected override IFileSystem FileSystem { get; } + protected override IFileSystem? FileSystem { get; } private static readonly string[] ExtensionsStatic = {"cshtml"}; diff --git a/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs index 22f96fa7c8..5178253667 100644 --- a/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs @@ -63,7 +63,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees return menu; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs index 89e767c124..7d87b5cca9 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, TreeTitle = "Scripts", SortOrder = 10, TreeGroup = Constants.Trees.Groups.Templating)] public class ScriptsTreeController : FileSystemTreeController { - protected override IFileSystem FileSystem { get; } + protected override IFileSystem? FileSystem { get; } private static readonly string[] ExtensionsStatic = { "js" }; diff --git a/src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs index 58fc53e28c..d1524970fd 100644 --- a/src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/StaticFilesTreeController.cs @@ -27,7 +27,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees _fileSystem = fileSystem; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var path = string.IsNullOrEmpty(id) == false && id != Constants.System.RootString ? WebUtility.UrlDecode(id).TrimStart("/") @@ -73,6 +73,6 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } // We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI - protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) => null; + protected override ActionResult? GetMenuForNode(string id, FormCollection queryStrings) => null; } } diff --git a/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs index 08dd8f2cde..e1a89d6dce 100644 --- a/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, TreeTitle = "Stylesheets", SortOrder = 9, TreeGroup = Constants.Trees.Groups.Templating)] public class StylesheetsTreeController : FileSystemTreeController { - protected override IFileSystem FileSystem { get; } + protected override IFileSystem? FileSystem { get; } private static readonly string[] ExtensionsStatic = { "css" }; diff --git a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs index e017f0e6d0..a155493aff 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs @@ -45,7 +45,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees _fileService = fileService; } - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) @@ -54,8 +54,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } var root = rootResult.Value; - //check if there are any templates - root.HasChildren = _fileService.GetTemplates(-1).Any(); + if (root is not null) + { + //check if there are any templates + root.HasChildren = _fileService.GetTemplates(-1)?.Any() ?? false; + } + return root; } @@ -70,7 +74,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); @@ -78,17 +82,20 @@ namespace Umbraco.Cms.Web.BackOffice.Trees ? _fileService.GetTemplates(-1) : _fileService.GetTemplates(int.Parse(id, CultureInfo.InvariantCulture)); - nodes.AddRange(found.Select(template => CreateTreeNode( - template.Id.ToString(CultureInfo.InvariantCulture), - // TODO: Fix parent ID stuff for templates - "-1", - queryStrings, - template.Name, - template.IsMasterTemplate ? "icon-newspaper" : "icon-newspaper-alt", - template.IsMasterTemplate, - null, - Udi.Create(ObjectTypes.GetUdiType(Constants.ObjectTypes.TemplateType), template.Key) - ))); + if (found is not null) + { + nodes.AddRange(found.Select(template => CreateTreeNode( + template.Id.ToString(CultureInfo.InvariantCulture), + // TODO: Fix parent ID stuff for templates + "-1", + queryStrings, + template.Name, + template.IsMasterTemplate ? "icon-newspaper" : "icon-newspaper-alt", + template.IsMasterTemplate, + null, + Udi.Create(ObjectTypes.GetUdiType(Constants.ObjectTypes.TemplateType), template.Key) + ))); + } return nodes; } @@ -105,7 +112,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees //Create the normal create action var item = menu.Items.Add(LocalizedTextService, opensDialog: true); - item.NavigateToRoute($"{queryStrings.GetRequiredValue("application")}/templates/edit/{id}?create=true"); + item?.NavigateToRoute($"{queryStrings.GetRequiredValue("application")}/templates/edit/{id}?create=true"); if (id == Constants.System.RootString) { @@ -149,7 +156,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees }; } - public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom = null) => _treeSearcher.EntitySearch(UmbracoObjectTypes.Template, query, pageSize, pageIndex, out totalFound, searchFrom); } } diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeAttribute.cs b/src/Umbraco.Web.BackOffice/Trees/TreeAttribute.cs index 3a6f9314e3..bfd3b115ff 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeAttribute.cs @@ -31,12 +31,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// Gets or sets the tree title. /// - public string TreeTitle { get; set; } + public string? TreeTitle { get; set; } /// /// Gets or sets the group of the tree. /// - public string TreeGroup { get; set; } + public string? TreeGroup { get; set; } /// /// Gets the usage of the tree. diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeController.cs b/src/Umbraco.Web.BackOffice/Trees/TreeController.cs index b68ff38a7c..d2d051515c 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeController.cs @@ -27,16 +27,16 @@ namespace Umbraco.Cms.Web.BackOffice.Trees } /// - public override string RootNodeDisplayName => Tree.GetRootNodeDisplayName(this, LocalizedTextService); + public override string? RootNodeDisplayName => Tree.GetRootNodeDisplayName(this, LocalizedTextService); /// - public override string TreeGroup => _treeAttribute.TreeGroup; + public override string? TreeGroup => _treeAttribute.TreeGroup; /// public override string TreeAlias => _treeAttribute.TreeAlias; /// - public override string TreeTitle => _treeAttribute.TreeTitle; + public override string? TreeTitle => _treeAttribute.TreeTitle; /// public override TreeUse TreeUse => _treeAttribute.TreeUse; diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs index 20d0e1a305..03e009c281 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// to the back end to be used in the query for model data. /// [Obsolete("See GetTreeNodesAsync")] - protected abstract ActionResult GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); + protected abstract ActionResult GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); /// /// Returns the menu structure for the node @@ -57,7 +57,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// [Obsolete("See GetMenuForNodeAsync")] - protected abstract ActionResult GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); + protected abstract ActionResult? GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); /// /// The method called to render the contents of the tree structure @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// - protected virtual async Task> GetTreeNodesAsync(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) + protected virtual async Task> GetTreeNodesAsync(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) { return GetTreeNodes(id, queryStrings); } @@ -85,7 +85,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// If overriden, GetMenuForNode will not be called /// - protected virtual async Task> GetMenuForNodeAsync(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) + protected virtual async Task?> GetMenuForNodeAsync(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings) { return GetMenuForNode(id, queryStrings); } @@ -93,16 +93,16 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// The name to display on the root node /// - public abstract string RootNodeDisplayName { get; } + public abstract string? RootNodeDisplayName { get; } /// - public abstract string TreeGroup { get; } + public abstract string? TreeGroup { get; } /// public abstract string TreeAlias { get; } /// - public abstract string TreeTitle { get; } + public abstract string? TreeTitle { get; } /// public abstract TreeUse TreeUse { get; } @@ -121,7 +121,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - public async Task> GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public async Task> GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; var nodeResult = CreateRootNode(queryStrings); @@ -132,20 +132,22 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var node = nodeResult.Value; - //add the tree alias to the root - node.AdditionalData["treeAlias"] = TreeAlias; + if (node is not null) + { + // Add the tree alias to the root + node.AdditionalData["treeAlias"] = TreeAlias; + AddQueryStringsToAdditionalData(node, queryStrings); - AddQueryStringsToAdditionalData(node, queryStrings); + // Check if the tree is searchable and add that to the meta data as well + if (this is ISearchableTree) + node.AdditionalData.Add("searchable", "true"); - //check if the tree is searchable and add that to the meta data as well - if (this is ISearchableTree) - node.AdditionalData.Add("searchable", "true"); + // Now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog(queryStrings)) + node.RoutePath = "#"; - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog(queryStrings)) - node.RoutePath = "#"; - - await _eventAggregator.PublishAsync(new RootNodeRenderingNotification(node, queryStrings, TreeAlias)); + await _eventAggregator.PublishAsync(new RootNodeRenderingNotification(node, queryStrings, TreeAlias)); + } return node; } @@ -162,7 +164,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// - public async Task> GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public async Task> GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; var nodesResult = await GetTreeNodesAsync(id, queryStrings); @@ -174,16 +176,25 @@ namespace Umbraco.Cms.Web.BackOffice.Trees var nodes = nodesResult.Value; - foreach (var node in nodes) - AddQueryStringsToAdditionalData(node, queryStrings); - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog(queryStrings)) + if (nodes is not null) + { foreach (var node in nodes) - node.RoutePath = "#"; + { + AddQueryStringsToAdditionalData(node, queryStrings); + } - //raise the event - await _eventAggregator.PublishAsync(new TreeNodesRenderingNotification(nodes, queryStrings, TreeAlias, id)); + // Now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog(queryStrings)) + { + foreach (var node in nodes) + { + node.RoutePath = "#"; + } + } + + // Raise the event + await _eventAggregator.PublishAsync(new TreeNodesRenderingNotification(nodes, queryStrings, TreeAlias, id)); + } return nodes; } @@ -194,18 +205,23 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - public async Task> GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public async Task> GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; var menuResult = await GetMenuForNodeAsync(id, queryStrings); - if (!(menuResult.Result is null)) + if (!(menuResult?.Result is null)) { return menuResult.Result; } - var menu = menuResult.Value; - //raise the event - await _eventAggregator.PublishAsync(new MenuRenderingNotification(id, menu, queryStrings, TreeAlias)); + var menu = menuResult?.Value; + + if (menu is not null) + { + //raise the event + await _eventAggregator.PublishAsync(new MenuRenderingNotification(id, menu, queryStrings, TreeAlias)); + } + return menu; } @@ -213,7 +229,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected virtual ActionResult CreateRootNode(FormCollection queryStrings) + protected virtual ActionResult CreateRootNode(FormCollection queryStrings) { var rootNodeAsString = Constants.System.RootString; queryStrings.TryGetValue(TreeQueryStringParameters.Application, out var currApp); @@ -259,7 +275,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon) + public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string? title, string? icon) { var jsonUrl = Url.GetTreeUrl(_apiControllers, GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(_apiControllers, GetType(), id, queryStrings); @@ -339,7 +355,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon, bool hasChildren) + public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string? title, string? icon, bool hasChildren) { var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); treeNode.HasChildren = hasChildren; @@ -357,7 +373,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon, bool hasChildren, string routePath) + public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string? title, string? icon, bool hasChildren, string routePath) { var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); treeNode.HasChildren = hasChildren; @@ -377,7 +393,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// /// /// - public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) + public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string? title, string icon, bool hasChildren, string? routePath, Udi udi) { var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); treeNode.HasChildren = hasChildren; diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeNodesRenderingNotification.cs b/src/Umbraco.Web.BackOffice/Trees/TreeNodesRenderingNotification.cs index b7334c0e5b..5621bc5756 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeNodesRenderingNotification.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeNodesRenderingNotification.cs @@ -62,7 +62,7 @@ namespace Umbraco.Cms.Core.Notifications /// /// Gets the id of the node rendered /// - public string Id { get; } + public string? Id { get; } } } diff --git a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs index eb63dcf354..dd2de371c7 100644 --- a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees { public static class UrlHelperExtensions { - internal static string GetTreePathFromFilePath(this IUrlHelper urlHelper, string virtualPath, string basePath = "") + internal static string GetTreePathFromFilePath(this IUrlHelper urlHelper, string? virtualPath, string basePath = "") { //This reuses the Logic from umbraco.cms.helpers.DeepLink class //to convert a filepath to a tree syncing path string. @@ -19,17 +19,17 @@ namespace Umbraco.Cms.Web.BackOffice.Trees //removes the basepath from the path //and normalizes paths - / is used consistently between trees and editors basePath = basePath.TrimStart("~"); - virtualPath = virtualPath.TrimStart("~"); - virtualPath = virtualPath.Substring(basePath.Length); - virtualPath = virtualPath.Replace('\\', '/'); + virtualPath = virtualPath?.TrimStart("~"); + virtualPath = virtualPath?.Substring(basePath.Length); + virtualPath = virtualPath?.Replace('\\', '/'); //-1 is the default root id for trees var sb = new StringBuilder("-1"); //split the virtual path and iterate through it - var pathPaths = virtualPath.Split(Constants.CharArrays.ForwardSlash); + var pathPaths = virtualPath?.Split(Constants.CharArrays.ForwardSlash); - for (var p = 0; p < pathPaths.Length; p++) + for (var p = 0; p < pathPaths?.Length; p++) { var path = WebUtility.UrlEncode(string.Join("/", pathPaths.Take(p + 1))); if (string.IsNullOrEmpty(path) == false) @@ -43,7 +43,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees public static string GetTreeUrl(this IUrlHelper urlHelper, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, Type treeType, string nodeId, FormCollection queryStrings) { - var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, "GetNodes", treeType) + var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, "GetNodes", treeType)? .EnsureEndsWith('?'); //now we need to append the query strings @@ -56,7 +56,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees public static string GetMenuUrl(this IUrlHelper urlHelper, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, Type treeType, string nodeId, FormCollection queryStrings) { - var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, "GetMenu", treeType) + var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, "GetMenu", treeType)? .EnsureEndsWith('?'); //now we need to append the query strings diff --git a/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs index 3afdecf36e..c7d8b29955 100644 --- a/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs @@ -33,24 +33,27 @@ namespace Umbraco.Cms.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected override ActionResult CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { var rootResult = base.CreateRootNode(queryStrings); if (!(rootResult.Result is null)) { - return rootResult; + return rootResult.Result; } var root = rootResult.Value; - //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = $"{Constants.Applications.Users}/{Constants.Trees.Users}/users"; - root.Icon = Constants.Icons.UserGroup; + if (root is not null) + { + // this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = $"{Constants.Applications.Users}/{Constants.Trees.Users}/users"; + root.Icon = Constants.Icons.UserGroup; + root.HasChildren = false; + } - root.HasChildren = false; return root; } - protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //full screen app without tree nodes return TreeNodeCollection.Empty; diff --git a/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs b/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs index 83754d7e3b..dc4b8a4320 100644 --- a/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs @@ -41,9 +41,9 @@ namespace Umbraco.Extensions /// The cookie value can either be a simple string value /// /// - public static bool FromBase64CookieData(this ViewDataDictionary viewData, HttpContext httpContext, string cookieName, IJsonSerializer serializer) + public static bool FromBase64CookieData(this ViewDataDictionary viewData, HttpContext? httpContext, string cookieName, IJsonSerializer serializer) { - var hasCookie = httpContext.Request.Cookies.ContainsKey(cookieName); + var hasCookie = httpContext?.Request.Cookies.ContainsKey(cookieName) ?? false; if (!hasCookie) return false; // get the cookie value diff --git a/src/Umbraco.Web.Common/Security/IBackOfficeSignInManager.cs b/src/Umbraco.Web.Common/Security/IBackOfficeSignInManager.cs index 0c7ff39c02..33da11fbf2 100644 --- a/src/Umbraco.Web.Common/Security/IBackOfficeSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/IBackOfficeSignInManager.cs @@ -12,7 +12,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security /// public interface IBackOfficeSignInManager { - AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string? userId = null); + AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string? redirectUrl, string? userId = null); Task ExternalLoginSignInAsync(ExternalLoginInfo loginInfo, bool isPersistent, bool bypassTwoFactor = false); Task> GetExternalAuthenticationSchemesAsync(); Task GetExternalLoginInfoAsync(string? expectedXsrf = null); @@ -21,7 +21,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security Task SignOutAsync(); Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent, string? authenticationMethod = null); Task CreateUserPrincipalAsync(BackOfficeIdentityUser user); - Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient); + Task TwoFactorSignInAsync(string? provider, string? code, bool isPersistent, bool rememberClient); Task UpdateExternalAuthenticationTokensAsync(ExternalLoginInfo externalLogin); } } diff --git a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs index 8e276b57a6..f47bb44c54 100644 --- a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs @@ -138,7 +138,7 @@ namespace Umbraco.Cms.Web.Common.Security } /// - public override async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient) + public override async Task TwoFactorSignInAsync(string? provider, string? code, bool isPersistent, bool rememberClient) { // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs#L552 // replaced in order to use a custom auth type and to implement logging/events