Merge pull request #6396 from umbraco/v8/bugfix/AB2734-Image-Deletion-Bug

Bug: RTE Image deletion error for images that has not been saved as media items
This commit is contained in:
Bjarke Berg
2019-09-25 08:36:27 +02:00
committed by GitHub
4 changed files with 86 additions and 37 deletions

View File

@@ -4,7 +4,7 @@
function ContentEditController($rootScope, $scope, $routeParams, $q, $window,
appState, contentResource, entityResource, navigationService, notificationsService,
serverValidationManager, contentEditingHelper, localizationService, formHelper, umbRequestHelper,
editorState, $http, eventsService, overlayService, $location) {
editorState, $http, eventsService, overlayService, $location, localStorageService) {
var evts = [];
var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode;
@@ -189,6 +189,13 @@
$scope.page.saveButtonState = "success";
$scope.page.buttonGroupState = "success";
}));
evts.push(eventsService.on("content.saved", function(){
// Clear out localstorage keys that start with tinymce__
// When we save/perist a content node
// NOTE: clearAll supports a RegEx pattern of items to remove
localStorageService.clearAll(/^tinymce__/);
}));
}
/**

View File

@@ -7,7 +7,7 @@
* A service containing all logic for all of the Umbraco TinyMCE plugins
*/
function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService,
$routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource, eventsService) {
$routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource, eventsService, localStorageService) {
//These are absolutely required in order for the macros to render inline
//we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
@@ -203,7 +203,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
}
// Put temp location into localstorage (used to update the img with data-tmpimg later on)
localStorage.setItem(`tinymce__${blobInfo.blobUri()}`, json.tmpLocation);
localStorageService.set(`tinymce__${blobInfo.blobUri()}`, json.tmpLocation);
// We set the img src url to be the same as we started
// The Blob URI is stored in TinyMce's cache
@@ -234,23 +234,43 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
// Get img src
var imgSrc = img.getAttribute("src");
var tmpLocation = localStorage.getItem(`tinymce__${imgSrc}`);
var tmpLocation = localStorageService.get(`tinymce__${imgSrc}`)
// Select the img & add new attr which we can search for
// When its being persisted in RTE property editor
// To create a media item & delete this tmp one etc
tinymce.activeEditor.$(img).attr({ "data-tmpimg": tmpLocation });
// Resize the image to the max size configured
// NOTE: no imagesrc passed into func as the src is blob://...
// We will append ImageResizing Querystrings on perist to DB with node save
sizeImageInEditor(editor, img);
// We need to remove the image from the cache, otherwise we can't handle if we upload the exactly
// same image twice
tinymce.activeEditor.editorUpload.blobCache.removeByUri(imgSrc);
});
});
// Get all img where src starts with blob: AND does NOT have a data=tmpimg attribute
// This is most likely seen as a duplicate image that has already been uploaded
// editor.uploadImages() does not give us any indiciation that the image been uploaded already
var blobImageWithNoTmpImgAttribute = editor.dom.select("img[src^='blob:']:not([data-tmpimg])");
//For each of these selected items
blobImageWithNoTmpImgAttribute.forEach(imageElement => {
var blobSrcUri = editor.dom.getAttrib(imageElement, "src");
// Find the same image uploaded (Should be in LocalStorage)
// May already exist in the editor as duplicate image
// OR added to the RTE, deleted & re-added again
// So lets fetch the tempurl out of localstorage for that blob URI item
var tmpLocation = localStorageService.get(`tinymce__${blobSrcUri}`)
if(tmpLocation){
sizeImageInEditor(editor, imageElement);
editor.dom.setAttrib(imageElement, "data-tmpimg", tmpLocation);
}
});
}
});
}

View File

@@ -13,6 +13,8 @@ describe('RTE controller tests', function () {
}
}
beforeEach(module('LocalStorageModule'));
beforeEach(module('umbraco', function ($provide) {
$provide.value('tinyMceAssets', []);
}));

View File

@@ -1,5 +1,6 @@
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Umbraco.Core;
@@ -201,7 +202,11 @@ namespace Umbraco.Web.Templates
var tmpImages = htmlDoc.DocumentNode.SelectNodes($"//img[@{TemporaryImageDataAttribute}]");
if (tmpImages == null || tmpImages.Count == 0)
return html;
// An array to contain a list of URLs that
// we have already processed to avoid dupes
var uploadedImages = new Dictionary<string, GuidUdi>();
foreach (var img in tmpImages)
{
// The data attribute contains the path to the tmp img to persist as a media item
@@ -209,36 +214,46 @@ namespace Umbraco.Web.Templates
if (string.IsNullOrEmpty(tmpImgPath))
continue;
var absoluteTempImagePath = IOHelper.MapPath(tmpImgPath);
var fileName = Path.GetFileName(absoluteTempImagePath);
var safeFileName = fileName.ToSafeFileName();
var mediaItemName = safeFileName.ToFriendlyName();
IMedia mediaFile;
GuidUdi udi;
if(mediaParentFolder == Guid.Empty)
mediaFile = mediaService.CreateMedia(mediaItemName, Constants.System.Root, Constants.Conventions.MediaTypes.Image, userId);
else
mediaFile = mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId);
var fileInfo = new FileInfo(absoluteTempImagePath);
var fileStream = fileInfo.OpenReadWithRetry();
if (fileStream == null) throw new InvalidOperationException("Could not acquire file stream");
using (fileStream)
if (uploadedImages.ContainsKey(tmpImgPath) == false)
{
mediaFile.SetValue(contentTypeBaseServiceProvider, Constants.Conventions.Media.File, safeFileName, fileStream);
}
if (mediaParentFolder == Guid.Empty)
mediaFile = mediaService.CreateMedia(mediaItemName, Constants.System.Root, Constants.Conventions.MediaTypes.Image, userId);
else
mediaFile = mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId);
mediaService.Save(mediaFile, userId);
var fileInfo = new FileInfo(absoluteTempImagePath);
var fileStream = fileInfo.OpenReadWithRetry();
if (fileStream == null) throw new InvalidOperationException("Could not acquire file stream");
using (fileStream)
{
mediaFile.SetValue(contentTypeBaseServiceProvider, Constants.Conventions.Media.File, safeFileName, fileStream);
}
mediaService.Save(mediaFile, userId);
udi = mediaFile.GetUdi();
}
else
{
// Already been uploaded & we have it's UDI
udi = uploadedImages[tmpImgPath];
}
// Add the UDI to the img element as new data attribute
var udi = mediaFile.GetUdi();
img.SetAttributeValue("data-udi", udi.ToString());
// Get the new persisted image url
var mediaTyped = Current.UmbracoHelper.Media(mediaFile.Id);
var mediaTyped = Current.UmbracoHelper.Media(udi.Guid);
var location = mediaTyped.Url;
// Find the width & height attributes as we need to set the imageprocessor QueryString
@@ -255,19 +270,24 @@ namespace Umbraco.Web.Templates
// Remove the data attribute (so we do not re-process this)
img.Attributes.Remove(TemporaryImageDataAttribute);
// Delete folder & image now its saved in media
// The folder should contain one image - as a unique guid folder created
// for each image uploaded from TinyMceController
var folderName = Path.GetDirectoryName(absoluteTempImagePath);
try
{
Directory.Delete(folderName, true);
}
catch (Exception ex)
// Add to the dictionary to avoid dupes
if(uploadedImages.ContainsKey(tmpImgPath) == false)
{
logger.Error(typeof(TemplateUtilities), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath);
uploadedImages.Add(tmpImgPath, udi);
// Delete folder & image now its saved in media
// The folder should contain one image - as a unique guid folder created
// for each image uploaded from TinyMceController
var folderName = Path.GetDirectoryName(absoluteTempImagePath);
try
{
Directory.Delete(folderName, true);
}
catch (Exception ex)
{
logger.Error(typeof(TemplateUtilities), ex, "Could not delete temp file or folder {FileName}", absoluteTempImagePath);
}
}
}
return htmlDoc.DocumentNode.OuterHtml;