diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 2b62c6ba2156..2940caaf1db6 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -329,6 +329,7 @@ or click here to choose files You can drag files here to upload Cannot upload this file, it does not have an approved file type + Cannot upload this file, the media type with alias '%0%' is not allowed here Cannot upload this file, it does not have a valid file name Max file size is Media root @@ -1448,6 +1449,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Insufficient user permissions, could not complete the operation Cancelled Operation was cancelled by a 3rd party add-in + This file is being uploaded as part of a folder, but creating a new folder is not allowed here + Creating a new folder is not allowed here Publishing was cancelled by a 3rd party add-in Property type already exists Property type created diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 79121d015d13..8d3e7d171788 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -331,8 +331,9 @@ Click to upload or click here to choose files - You can drag files here to upload. + You can drag files here to upload. Cannot upload this file, it does not have an approved file type + Cannot upload this file, the media type with alias '%0%' is not allowed here Cannot upload this file, it does not have a valid file name Max file size is Media root @@ -1460,6 +1461,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Insufficient user permissions, could not complete the operation Cancelled Operation was cancelled by a 3rd party add-in + This file is being uploaded as part of a folder, but creating a new folder is not allowed here + Creating a new folder is not allowed here + Publishing was cancelled by a 3rd party add-in Property type already exists Property type created DataType: %1%]]> diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 18cbac332821..45d7aebae1b5 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -598,6 +598,13 @@ public MediaItemDisplay PostAddFolder(PostedFolder folder) { var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true); + var isFolderAllowed = IsFolderCreationAllowedHere(intParentId); + if (isFolderAllowed == false) + { + throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("speechBubbles", "folderCreationNotAllowed"))); + } + var mediaService = Services.MediaService; var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); @@ -640,10 +647,16 @@ public async Task PostAddFile() var tempFiles = new PostedFiles(); var mediaService = Services.MediaService; + var localizedTextService = Services.TextService; //in case we pass a path with a folder in it, we will create it and upload media to it. if (result.FormData.ContainsKey("path")) { + if (!IsFolderCreationAllowedHere(parentId)) + { + AddCancelMessage(tempFiles, message: "speechBubbles/folderUploadNotAllowed"); + return Request.CreateResponse(HttpStatusCode.OK, tempFiles); + } var folders = result.FormData["path"].Split(Constants.CharArrays.ForwardSlash); @@ -653,7 +666,7 @@ public async Task PostAddFile() IMedia folderMediaItem; //if uploading directly to media root and not a subfolder - if (parentId == -1) + if (parentId == Constants.System.Root) { //look for matching folder folderMediaItem = @@ -691,6 +704,44 @@ public async Task PostAddFile() } } + var mediaTypeAlias = string.Empty; + var allMediaTypes = Services.MediaTypeService.GetAll().ToList(); + var allowedContentTypes = new HashSet(); + + if (parentId != Constants.System.Root) + { + var mediaFolderItem = mediaService.GetById(parentId); + var mediaFolderType = allMediaTypes.FirstOrDefault(x => x.Alias == mediaFolderItem.ContentType.Alias); + + if (mediaFolderType != null) + { + IMediaType mediaTypeItem = null; + + foreach (ContentTypeSort allowedContentType in mediaFolderType.AllowedContentTypes) + { + IMediaType checkMediaTypeItem = allMediaTypes.FirstOrDefault(x => x.Id == allowedContentType.Id.Value); + allowedContentTypes.Add(checkMediaTypeItem); + + var fileProperty = checkMediaTypeItem?.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == Constants.Conventions.Media.File); + if (fileProperty != null) + { + mediaTypeItem = checkMediaTypeItem; + } + } + + //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; + } + } + } + else + { + var typesAllowedAtRoot = allMediaTypes.Where(x => x.AllowedAsRoot).ToList(); + allowedContentTypes.UnionWith(typesAllowedAtRoot); + } + //get the files foreach (var file in result.FileData) { @@ -698,81 +749,94 @@ public async Task PostAddFile() var safeFileName = fileName.ToSafeFileName(); var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); - if (Current.Configs.Settings().Content.IsFileAllowedForUpload(ext)) + if (!Current.Configs.Settings().Content.IsFileAllowedForUpload(ext)) + { + tempFiles.Notifications.Add(new Notification( + localizedTextService.Localize("speechBubbles", "operationFailedHeader"), + localizedTextService.Localize("media", "disallowedFileType"), + NotificationStyle.Warning)); + continue; + } + + if (string.IsNullOrEmpty(mediaTypeAlias)) { - var mediaType = Constants.Conventions.MediaTypes.File; + mediaTypeAlias = Constants.Conventions.MediaTypes.File; if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect) { - var mediaTypes = Services.MediaTypeService.GetAll(); // Look up MediaTypes - foreach (var mediaTypeItem in mediaTypes) + foreach (var mediaTypeItem in allMediaTypes) { - var fileProperty = mediaTypeItem.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == "umbracoFile"); - if (fileProperty != null) { - var dataTypeKey = fileProperty.DataTypeKey; - var dataType = Services.DataTypeService.GetDataType(dataTypeKey); - - if (dataType != null && dataType.Configuration is IFileExtensionsConfig fileExtensionsConfig) { - var fileExtensions = fileExtensionsConfig.FileExtensions; - if (fileExtensions != null) - { - if (fileExtensions.Where(x => x.Value == ext).Count() != 0) - { - mediaType = mediaTypeItem.Alias; - break; - } - } - } + var fileProperty = mediaTypeItem.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == Constants.Conventions.Media.File); + if (fileProperty == null) + { + continue; + } + + var dataTypeKey = fileProperty.DataTypeKey; + var dataType = Services.DataTypeService.GetDataType(dataTypeKey); + + if (dataType == null || dataType.Configuration is not IFileExtensionsConfig fileExtensionsConfig) + { + continue; + } + + var fileExtensions = fileExtensionsConfig.FileExtensions; + if (fileExtensions == null || fileExtensions.All(x => x.Value != ext)) + { + continue; } + mediaTypeAlias = mediaTypeItem.Alias; + break; } // If media type is still File then let's check if it's an image. - if (mediaType == Constants.Conventions.MediaTypes.File && Current.Configs.Settings().Content.ImageFileTypes.Contains(ext)) + if (mediaTypeAlias == Constants.Conventions.MediaTypes.File && Current.Configs.Settings().Content.ImageFileTypes.Contains(ext)) { - mediaType = Constants.Conventions.MediaTypes.Image; + mediaTypeAlias = Constants.Conventions.MediaTypes.Image; } } else { - mediaType = result.FormData["contentTypeAlias"]; + mediaTypeAlias = result.FormData["contentTypeAlias"]; } + } - var mediaItemName = fileName.ToFriendlyName(); + if (allowedContentTypes.Any(x => x.Alias == mediaTypeAlias) == false) + { + tempFiles.Notifications.Add(new Notification( + localizedTextService.Localize("speechBubbles", "operationFailedHeader"), + localizedTextService.Localize("media", "disallowedMediaType", new[] { mediaTypeAlias }), + NotificationStyle.Warning)); + continue; + } - var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); + var mediaItemName = fileName.ToFriendlyName(); - var fileInfo = new FileInfo(file.LocalFileName); - var fs = fileInfo.OpenReadWithRetry(); - if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); - using (fs) - { - f.SetValue(Services.ContentTypeBaseServices, Constants.Conventions.Media.File,fileName, fs); - } + var createdMediaItem = mediaService.CreateMedia(mediaItemName, parentId, mediaTypeAlias, Security.CurrentUser.Id); - var saveResult = mediaService.Save(f, Security.CurrentUser.Id); - if (saveResult == false) - { - AddCancelMessage(tempFiles, - message: Services.TextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName); - } - else - { - tempFiles.UploadedFiles.Add(new ContentPropertyFile - { - FileName = fileName, - PropertyAlias = Constants.Conventions.Media.File, - TempFilePath = file.LocalFileName - }); - } + var fileInfo = new FileInfo(file.LocalFileName); + var fs = fileInfo.OpenReadWithRetry(); + if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); + using (fs) + { + createdMediaItem.SetValue(Services.ContentTypeBaseServices, Constants.Conventions.Media.File, fileName, fs); + } + + var saveResult = mediaService.Save(createdMediaItem, Security.CurrentUser.Id); + if (saveResult == false) + { + AddCancelMessage(tempFiles, message: "speechBubbles/operationCancelledText" + " -- " + mediaItemName); } else { - tempFiles.Notifications.Add(new Notification( - Services.TextService.Localize("speechBubbles", "operationFailedHeader"), - Services.TextService.Localize("media", "disallowedFileType"), - NotificationStyle.Warning)); + tempFiles.UploadedFiles.Add(new ContentPropertyFile + { + FileName = fileName, + PropertyAlias = Constants.Conventions.Media.File, + TempFilePath = file.LocalFileName + }); } } @@ -792,6 +856,29 @@ public async Task PostAddFile() return Request.CreateResponse(HttpStatusCode.OK, tempFiles); } + private bool IsFolderCreationAllowedHere(int parentId) + { + var allMediaTypes = Services.MediaTypeService.GetAll().ToList(); + var isFolderAllowed = false; + if (parentId == Constants.System.Root) + { + var typesAllowedAtRoot = allMediaTypes.Where(ct => ct.AllowedAsRoot).ToList(); + isFolderAllowed = typesAllowedAtRoot.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder); + } + else + { + var parentMediaType = Services.MediaService.GetById(parentId); + var mediaFolderType = allMediaTypes.FirstOrDefault(x => x.Alias == parentMediaType.ContentType.Alias); + if (mediaFolderType != null) + { + isFolderAllowed = + mediaFolderType.AllowedContentTypes.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder); + } + } + + return isFolderAllowed; + } + private IMedia FindInChildren(int mediaId, string nameToFind, string contentTypeAlias) { const int pageSize = 500;