diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index ac8689c8c143..9c37bb103b7d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -184,13 +184,12 @@ public MediaController( return NotFound(); } - IMedia emptyContent = _mediaService.CreateMedia("", parentId, contentType.Alias, - _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); + IMedia emptyContent = _mediaService.CreateMedia("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); MediaItemDisplay? mapped = _umbracoMapper.Map(emptyContent); if (mapped is not null) { - //remove the listview app if it exists + // remove the listview app if it exists mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); } @@ -205,8 +204,7 @@ public MediaItemDisplay GetRecycleBin() { var apps = new List { - ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, "recycleBin", "media", - Constants.DataTypes.DefaultMediaListView) + ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, "recycleBin", "media", Constants.DataTypes.DefaultMediaListView) }; apps[0].Active = true; var display = new MediaItemDisplay @@ -239,7 +237,8 @@ public MediaItemDisplay GetRecycleBin() if (foundMedia == null) { HandleContentNotFound(id); - //HandleContentNotFound will throw an exception + + // HandleContentNotFound will throw an exception return null; } @@ -304,11 +303,10 @@ public MediaItemDisplay GetRecycleBin() /// /// /// - public PagedResult> GetChildFolders(int id, int pageNumber = 1, - int pageSize = 1000) + public PagedResult> GetChildFolders(int id, int pageNumber = 1, int pageSize = 1000) { - //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... - //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" + // Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... + // if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" var folderTypes = _mediaTypeService .GetAll() .Where(x => x.Alias.EndsWith("Folder")) @@ -321,7 +319,8 @@ public PagedResult> GetChildFolders(int i } IEnumerable children = _mediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out long total, - //lookup these content types + + // lookup these content types _sqlContext.Query().Where(x => folderTypes.Contains(x.ContentTypeId)), Ordering.By("Name")); @@ -337,6 +336,7 @@ public PagedResult> GetChildFolders(int i /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] public IEnumerable> GetRootMedia() => + // TODO: Add permissions check! _mediaService.GetRootMedia()? .Select(_umbracoMapper.Map>).WhereNotNull() ?? @@ -358,7 +358,7 @@ public IActionResult DeleteById(int id) return HandleContentNotFound(id); } - //if the current item is in the recycle bin + // if the current item is in the recycle bin if (foundMedia.Trashed == false) { Attempt moveResult = _mediaService.MoveToRecycleBin(foundMedia, @@ -390,8 +390,10 @@ public async Task PostMove(MoveOrCopy move) { // Authorize... var requirement = new MediaPermissionsResourceRequirement(); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(User, - new MediaPermissionsResource(_mediaService.GetById(move.Id)), requirement); + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync( + User, + new MediaPermissionsResource(_mediaService.GetById(move.Id)), + requirement); if (!authorizationResult.Succeeded) { return Forbid(); @@ -404,18 +406,19 @@ public async Task PostMove(MoveOrCopy move) return convertToActionResult.Convert(); } - var destinationParentID = move.ParentId; - var sourceParentID = toMove?.ParentId; + var destinationParentId = move.ParentId; + var sourceParentId = toMove?.ParentId; var moveResult = toMove is null ? false - : _mediaService.Move(toMove, move.ParentId, - _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); + : _mediaService.Move(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - if (sourceParentID == destinationParentID) + if (sourceParentId == destinationParentId) { - return ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification("", - _localizedTextService.Localize("media", "moveToSameFolderFailed"), NotificationStyle.Error))); + return ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification( + string.Empty, + _localizedTextService.Localize("media", "moveToSameFolderFailed"), + NotificationStyle.Error))); } if (moveResult == false) @@ -436,9 +439,9 @@ public async Task PostMove(MoveOrCopy move) public ActionResult? PostSave( [ModelBinder(typeof(MediaItemBinder))] MediaItemSave contentItem) { - //Recent versions of IE/Edge may send in the full client side file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). + // Recent versions of IE/Edge may send in the full client side file path instead of just the file name. + // To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + // uploaded files to being *only* the actual file name (as it should be). if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) { foreach (ContentPropertyFile file in contentItem.UploadedFiles) @@ -447,14 +450,14 @@ public async Task PostMove(MoveOrCopy move) } } - //If we've reached here it means: + // If we've reached here it means: // * Our model has been bound // * and validated // * any file attachments have been saved to their temporary location for us to use // * we have a reference to the DTO object and the persisted object // * Permissions are valid - //Don't update the name if it is empty + // Don't update the name if it is empty if (contentItem.Name.IsNullOrWhiteSpace() == false && contentItem.PersistedContent is not null) { contentItem.PersistedContent.Name = contentItem.Name; @@ -467,14 +470,14 @@ public async Task PostMove(MoveOrCopy move) (save, property, v) => property?.SetValue(v), //set prop val null); // media are all invariant - //we will continue to save if model state is invalid, however we cannot save if critical data is missing. - //TODO: Allowing media to be saved when it is invalid is odd - media doesn't have a publish phase so suddenly invalid data is allowed to be 'live' + // we will continue to save if model state is invalid, however we cannot save if critical data is missing. + // TODO: Allowing media to be saved when it is invalid is odd - media doesn't have a publish phase so suddenly invalid data is allowed to be 'live' if (!ModelState.IsValid) { - //check for critical data validation issues, we can't continue saving if this data is invalid + // check for critical data validation issues, we can't continue saving if this data is invalid if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem)) { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the model state to the outgoing object and throw validation response MediaItemDisplay? forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); return ValidationProblem(forDisplay, ModelState); @@ -486,20 +489,19 @@ public async Task PostMove(MoveOrCopy move) return null; } - //save the item - Attempt saveStatus = _mediaService.Save(contentItem.PersistedContent, - _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); + // save the item + Attempt saveStatus = _mediaService.Save(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - //return the updated model + // return the updated model MediaItemDisplay? display = _umbracoMapper.Map(contentItem.PersistedContent); - //lastly, if it is not valid, add the model state to the outgoing object and throw a 403 + // lastly, if it is not valid, add the model state to the outgoing object and throw a 403 if (!ModelState.IsValid) { return ValidationProblem(display, ModelState, StatusCodes.Status403Forbidden); } - //put the correct msgs in + // put the correct msgs in switch (contentItem.Action) { case ContentSaveAction.Save: @@ -514,7 +516,7 @@ public async Task PostMove(MoveOrCopy move) { AddCancelMessage(display); - //If the item is new and the operation was cancelled, we need to return a different + // If the item is new and the operation was cancelled, we need to return a different // status code so the UI can handle it since it won't be able to redirect since there // is no Id to redirect to! if (saveStatus.Result?.Result == OperationResultType.FailedCancelledByEvent && @@ -555,7 +557,7 @@ public async Task PostSort(ContentSortOrder sorted) return NotFound(); } - //if there's nothing to sort just return ok + // if there's nothing to sort just return ok if (sorted.IdSortOrder?.Length == 0) { return Ok(); @@ -564,8 +566,7 @@ public async Task PostSort(ContentSortOrder sorted) // Authorize... var requirement = new MediaPermissionsResourceRequirement(); var resource = new MediaPermissionsResource(sorted.ParentId); - AuthorizationResult authorizationResult = - await _authorizationService.AuthorizeAsync(User, resource, requirement); + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(User, resource, requirement); if (!authorizationResult.Succeeded) { return Forbid(); @@ -596,7 +597,7 @@ public async Task PostSort(ContentSortOrder sorted) public async Task> PostAddFolder(PostedFolder folder) { ActionResult? parentIdResult = await GetParentIdAsIntAsync(folder.ParentId, true); - if (!(parentIdResult?.Result is null)) + if (parentIdResult?.Result is not null) { return new ActionResult(parentIdResult.Result); } @@ -633,19 +634,20 @@ public async Task PostAddFile([FromForm] string path, [FromForm] try { var root = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads); - //ensure it exists + + // ensure it exists Directory.CreateDirectory(root); - //must have a file + // must have a file if (file is null || file.Count == 0) { _postAddFileSemaphore.Release(); return NotFound("No file was uploaded"); } - //get the string json from the request + // get the string json from the request ActionResult? parentIdResult = await GetParentIdAsIntAsync(currentFolder, true); - if (!(parentIdResult?.Result is null)) + if (parentIdResult?.Result is not null) { _postAddFileSemaphore.Release(); return parentIdResult.Result; @@ -660,7 +662,7 @@ public async Task PostAddFile([FromForm] string path, [FromForm] var tempFiles = new PostedFiles(); - //in case we pass a path with a folder in it, we will create it and upload media to it. + // in case we pass a path with a folder in it, we will create it and upload media to it. if (!string.IsNullOrEmpty(path)) { if (!IsFolderCreationAllowedHere(parentId.Value)) @@ -677,16 +679,16 @@ public async Task PostAddFile([FromForm] string path, [FromForm] var folderName = folders[i]; IMedia? folderMediaItem; - //if uploading directly to media root and not a subfolder + // if uploading directly to media root and not a subfolder if (parentId == Constants.System.Root) { - //look for matching folder + // look for matching folder folderMediaItem = _mediaService.GetRootMedia()?.FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); if (folderMediaItem == null) { - //if null, create a folder + // if null, create a folder folderMediaItem = _mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); _mediaService.Save(folderMediaItem); @@ -694,10 +696,10 @@ public async Task PostAddFile([FromForm] string path, [FromForm] } else { - //get current parent + // get current parent IMedia? mediaRoot = _mediaService.GetById(parentId.Value); - //if the media root is null, something went wrong, we'll abort + // if the media root is null, something went wrong, we'll abort if (mediaRoot == null) { _postAddFileSemaphore.Release(); @@ -706,19 +708,18 @@ public async Task PostAddFile([FromForm] string path, [FromForm] " returned null"); } - //look for matching folder + // look for matching folder folderMediaItem = FindInChildren(mediaRoot.Id, folderName, Constants.Conventions.MediaTypes.Folder); if (folderMediaItem == null) { - //if null, create a folder - folderMediaItem = _mediaService.CreateMedia(folderName, mediaRoot, - Constants.Conventions.MediaTypes.Folder); + // if null, create a folder + folderMediaItem = _mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); _mediaService.Save(folderMediaItem); } } - //set the media root to the folder id so uploaded files will end there. + // set the media root to the folder id so uploaded files will end there. parentId = folderMediaItem.Id; } } @@ -758,7 +759,7 @@ public async Task PostAddFile([FromForm] string path, [FromForm] } } - //Only set the permission-based mediaType if we only allow 1 specific file under this parent. + // Only set the permission-based mediaType if we only allow 1 specific file under this parent. if (allowedContentTypes.Count == 1 && mediaTypeItem != null) { mediaTypeAlias = mediaTypeItem.Alias; @@ -771,7 +772,7 @@ public async Task PostAddFile([FromForm] string path, [FromForm] allowedContentTypes.UnionWith(typesAllowedAtRoot); } - //get the files + // get the files foreach (IFormFile formFile in file) { var fileName = formFile.FileName.Trim(Constants.CharArrays.DoubleQuote).TrimEnd(); @@ -787,16 +788,16 @@ public async Task PostAddFile([FromForm] string path, [FromForm] continue; } - using var stream = new MemoryStream(); - await formFile.CopyToAsync(stream); - if (_fileStreamSecurityValidator != null && _fileStreamSecurityValidator.IsConsideredSafe(stream) == false) - { - tempFiles.Notifications.Add(new BackOfficeNotification( - _localizedTextService.Localize("speechBubbles", "operationFailedHeader"), - _localizedTextService.Localize("media", "fileSecurityValidationFailure"), - NotificationStyle.Warning)); - continue; - } + using var stream = new MemoryStream(); + await formFile.CopyToAsync(stream); + if (_fileStreamSecurityValidator != null && _fileStreamSecurityValidator.IsConsideredSafe(stream) == false) + { + tempFiles.Notifications.Add(new BackOfficeNotification( + _localizedTextService.Localize("speechBubbles", "operationFailedHeader"), + _localizedTextService.Localize("media", "fileSecurityValidationFailure"), + NotificationStyle.Warning)); + continue; + } if (string.IsNullOrEmpty(mediaTypeAlias)) { @@ -830,29 +831,34 @@ public async Task PostAddFile([FromForm] string path, [FromForm] continue; } + if (allowedContentTypes.Any(x => x.Alias == mediaTypeItem.Alias) == false) + { + continue; + } + mediaTypeAlias = mediaTypeItem.Alias; break; } - // If media type is still File then let's check if it's an imageor a custom image type. + // If media type is still File then let's check if it's an image or a custom image type. if (mediaTypeAlias == Constants.Conventions.MediaTypes.File && _imageUrlGenerator.IsSupportedImageFormat(ext)) { if (allowedContentTypes.Any(mt => mt.Alias == Constants.Conventions.MediaTypes.Image)) - { - mediaTypeAlias = Constants.Conventions.MediaTypes.Image; - } - else - { - IMediaType? customType = allowedContentTypes.FirstOrDefault(mt => - mt.CompositionPropertyTypes.Any(pt => - pt.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper)); - - if (customType is not null) { - mediaTypeAlias = customType.Alias; + mediaTypeAlias = Constants.Conventions.MediaTypes.Image; + } + else + { + IMediaType? customType = allowedContentTypes.FirstOrDefault(mt => + mt.CompositionPropertyTypes.Any(pt => + pt.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper)); + + if (customType is not null) + { + mediaTypeAlias = customType.Alias; + } } - } } } else @@ -872,31 +878,28 @@ public async Task PostAddFile([FromForm] string path, [FromForm] var mediaItemName = fileName.ToFriendlyName(); - IMedia createdMediaItem = _mediaService.CreateMedia(mediaItemName, parentId.Value, mediaTypeAlias, - _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); - + IMedia createdMediaItem = _mediaService.CreateMedia(mediaItemName, parentId.Value, mediaTypeAlias, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); + createdMediaItem.SetValue(_mediaFileManager, _mediaUrlGenerators, _shortStringHelper, _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, fileName, stream); - createdMediaItem.SetValue(_mediaFileManager, _mediaUrlGenerators, _shortStringHelper, - _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, fileName, stream); - - - Attempt saveResult = _mediaService.Save(createdMediaItem, + Attempt saveResult = _mediaService.Save( + createdMediaItem, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); if (saveResult == false) { - AddCancelMessage(tempFiles, + AddCancelMessage( + tempFiles, _localizedTextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName); } } - //Different response if this is a 'blueimp' request + // Different response if this is a 'blueimp' request if (HttpContext.Request.Query.Any(x => x.Key == "origin")) { KeyValuePair origin = HttpContext.Request.Query.First(x => x.Key == "origin"); if (origin.Value == "blueimp") { _postAddFileSemaphore.Release(); - return new JsonResult(tempFiles); //Don't output the angular xsrf stuff, blue imp doesn't like that + return new JsonResult(tempFiles); // Don't output the angular xsrf stuff, blue imp doesn't like that } } @@ -943,7 +946,11 @@ private bool IsFolderCreationAllowedHere(int parentId) var total = long.MaxValue; while (page * pageSize < total) { - IEnumerable children = _mediaService.GetPagedChildren(mediaId, page++, pageSize, out total, + IEnumerable children = _mediaService.GetPagedChildren( + mediaId, + page++, + pageSize, + out total, _sqlContext.Query().Where(x => x.Name == nameToFind)); IMedia? match = children.FirstOrDefault(c => c.ContentType.Alias == contentTypeAlias); if (match != null) @@ -973,7 +980,7 @@ private bool IsFolderCreationAllowedHere(int parentId) parentId = parentUdi?.Guid.ToString(); } - //if it's not an INT then we'll check for GUID + // if it's not an INT then we'll check for GUID if (int.TryParse(parentId, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intParentId) == false) { // if a guid then try to look up the entity @@ -997,7 +1004,7 @@ private bool IsFolderCreationAllowedHere(int parentId) } // Authorize... - //ensure the user has access to this folder by parent id! + // ensure the user has access to this folder by parent id! if (validatePermissions) { var requirement = new MediaPermissionsResourceRequirement(); @@ -1038,14 +1045,13 @@ private ActionResult ValidateMoveOrCopy(MoveOrCopy model) if (model.ParentId < 0) { - //cannot move if the content item is not allowed at the root unless there are - //none allowed at root (in which case all should be allowed at root) + // cannot move if the content item is not allowed at the root unless there are + // none allowed at root (in which case all should be allowed at root) IMediaTypeService mediaTypeService = _mediaTypeService; if (toMove.ContentType.AllowedAsRoot == false && mediaTypeService.GetAll().Any(ct => ct.AllowedAsRoot)) { var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedAtRoot"), - ""); + notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedAtRoot"), string.Empty); return ValidationProblem(notificationModel); } } @@ -1057,7 +1063,7 @@ private ActionResult ValidateMoveOrCopy(MoveOrCopy model) return NotFound(); } - //check if the item is allowed under this one + // check if the item is allowed under this one IMediaType? parentContentType = _mediaTypeService.Get(parent.ContentTypeId); if (parentContentType?.AllowedContentTypes?.Select(x => x.Id).ToArray() .Any(x => x.Value == toMove.ContentType.Id) == false) @@ -1069,12 +1075,11 @@ private ActionResult ValidateMoveOrCopy(MoveOrCopy model) } // Check on paths - if (string.Format(",{0},", parent.Path) - .IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + if ($",{parent.Path}," + .IndexOf($",{toMove.Id},", StringComparison.Ordinal) > -1) { var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"), - ""); + notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"), string.Empty); return ValidationProblem(notificationModel); } } @@ -1099,7 +1104,8 @@ private ActionResult ValidateMoveOrCopy(MoveOrCopy model) /// Returns the child media objects - using the entity INT id /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(int id, + public PagedResult> GetChildren( + int id, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -1107,7 +1113,7 @@ public PagedResult> GetChildren(int id, bool orderBySystemField = true, string filter = "") { - //if a request is made for the root node data but the user's start node is not the default, then + // if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) @@ -1137,7 +1143,6 @@ public PagedResult> GetChildren(int id, } // else proceed as usual - long totalChildren; List children; if (pageNumber > 0 && pageSize > 0) @@ -1147,7 +1152,7 @@ public PagedResult> GetChildren(int id, { int.TryParse(filter, out int filterAsIntId); Guid.TryParse(filter, out Guid filterAsGuid); - //add the default text filter + // add the default text filter queryFilter = _sqlContext.Query() .Where(x => x.Name != null) .Where(x => x.Name!.Contains(filter) @@ -1156,14 +1161,16 @@ public PagedResult> GetChildren(int id, children = _mediaService .GetPagedChildren( - id, pageNumber - 1, pageSize, + id, + pageNumber - 1, + pageSize, out totalChildren, queryFilter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)).ToList(); } else { - //better to not use this without paging where possible, currently only the sort dialog does + // better to not use this without paging where possible, currently only the sort dialog does children = _mediaService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList(); totalChildren = children.Count; } @@ -1237,8 +1244,7 @@ public ActionResult>> GetChil IEntitySlim? entity = _entityService.Get(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, - filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } }