Merge pull request #7837 from AndyButland/feature/7540-use-image-url-generator-back-office

Netcore: Use API to get Image URLs in BackOffice instead of hardcoded querystrings
This commit is contained in:
Shannon Deminick
2020-03-31 14:38:23 +11:00
committed by GitHub
28 changed files with 226 additions and 177 deletions
@@ -1,4 +1,6 @@
namespace Umbraco.Core.Models
using Umbraco.Core.Models;
namespace Umbraco.Core.Media
{
public interface IImageUrlGenerator
{
@@ -1,9 +1,10 @@
using System.Globalization;
using System.Text;
using Umbraco.Core;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
namespace Umbraco.Web.Models
namespace Umbraco.Infrastructure.Media
{
public class ImageProcessorImageUrlGenerator : IImageUrlGenerator
{
@@ -27,18 +28,18 @@ namespace Umbraco.Web.Models
//Only put quality here, if we don't have a format specified.
//Otherwise we need to put quality at the end to avoid it being overridden by the format.
if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality);
if (options.HeightRatio != null) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture));
if (options.WidthRatio != null) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture));
if (options.Width != null) imageProcessorUrl.Append("&width=").Append(options.Width);
if (options.Height != null) imageProcessorUrl.Append("&height=").Append(options.Height);
if (options.Quality.HasValue && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality);
if (options.HeightRatio.HasValue) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture));
if (options.WidthRatio.HasValue) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture));
if (options.Width.HasValue) imageProcessorUrl.Append("&width=").Append(options.Width);
if (options.Height.HasValue) imageProcessorUrl.Append("&height=").Append(options.Height);
if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false");
if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode);
if (options.FurtherOptions != null) imageProcessorUrl.Append(options.FurtherOptions);
if (!string.IsNullOrWhiteSpace(options.AnimationProcessMode)) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode);
if (!string.IsNullOrWhiteSpace(options.FurtherOptions)) imageProcessorUrl.Append(options.FurtherOptions);
//If furtherOptions contains a format, we need to put the quality after the format.
if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality);
if (options.CacheBusterValue != null) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue);
if (options.Quality.HasValue && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality);
if (!string.IsNullOrWhiteSpace(options.CacheBusterValue)) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue);
return imageProcessorUrl.ToString();
}
@@ -16,6 +16,7 @@ using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Actions;
using Umbraco.Web.Services;
using Umbraco.Core.Media;
namespace Umbraco.Web.Models.Mapping
{
@@ -9,6 +9,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Core.Security;
using Umbraco.Core.Media;
namespace Umbraco.Core.Models
{
@@ -5,6 +5,7 @@ using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
@@ -6,6 +6,7 @@ using Umbraco.Core;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
@@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Serialization;
using Umbraco.Core.Strings;
@@ -16,10 +16,10 @@ using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Services;
using Umbraco.Tests.Components;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web.Models;
using Umbraco.Web;
using Umbraco.Web.PropertyEditors;
using System.Text;
using Umbraco.Infrastructure.Media;
namespace Umbraco.Tests.Models
{
@@ -23,6 +23,7 @@ using Umbraco.Web.PropertyEditors;
using System.Text;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Core.Cache;
using Umbraco.Core.Media;
namespace Umbraco.Tests.PropertyEditors
{
@@ -16,6 +16,7 @@ using Umbraco.Web;
using Umbraco.Web.Templates;
using Umbraco.Web.Models;
using Umbraco.Web.Routing;
using Umbraco.Core.Media;
namespace Umbraco.Tests.PublishedContent
{
@@ -25,6 +25,7 @@ using Umbraco.Web.Templates;
using Umbraco.Web.Models;
using Umbraco.Web.Routing;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Core.Media;
namespace Umbraco.Tests.PublishedContent
{
@@ -36,6 +36,7 @@ using File = System.IO.File;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Tests.Common;
using Umbraco.Tests.Common.Composing;
using Umbraco.Core.Media;
namespace Umbraco.Tests.Runtimes
{
+1 -1
View File
@@ -50,7 +50,6 @@ using FileSystems = Umbraco.Core.IO.FileSystems;
using Umbraco.Web.Templates;
using Umbraco.Web.PropertyEditors;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Models;
using Umbraco.Net;
using Umbraco.Core.Request;
using Umbraco.Core.Security;
@@ -63,6 +62,7 @@ using Umbraco.Web.Security.Providers;
using Umbraco.Web.Trees;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Tests.Common;
using Umbraco.Core.Media;
namespace Umbraco.Tests.Testing
{
@@ -37,6 +37,7 @@ using IUser = Umbraco.Core.Models.Membership.IUser;
using Umbraco.Core.Mapping;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Web.Routing;
using Umbraco.Core.Media;
namespace Umbraco.Tests.Web.Controllers
{
@@ -3,7 +3,7 @@
* @name umbraco.services.mediaHelper
* @description A helper object used for dealing with media items
**/
function mediaHelper(umbRequestHelper, $log) {
function mediaHelper(umbRequestHelper, $http, $log) {
//container of fileresolvers
var _mediaFileResolvers = {};
@@ -304,11 +304,6 @@ function mediaHelper(umbRequestHelper, $log) {
return imagePath;
}
// Check if file is a svg
if (this.getFileExtension(imagePath) === "svg") {
return imagePath;
}
// If the path is not an image we cannot get a thumb
if (!this.detectIfImageByExtension(imagePath)) {
return null;
@@ -399,6 +394,54 @@ function mediaHelper(umbRequestHelper, $log) {
var lowered = filePath.toLowerCase();
var ext = lowered.substr(lowered.lastIndexOf(".") + 1);
return ext;
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getProcessedImageUrl
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns image URL with configured crop and other processing parameters.
*
* @param {string} imagePath Raw image path
* @param {object} options Object describing image generation parameters:
* {
* animationProcessMode: <string>
* cacheBusterValue: <string>
* focalPoint: {
* left: <int>
* top: <int>
* },
* height: <int>
* mode: <string>
* upscale: <boolean>
* width: <int>
* }
*/
getProcessedImageUrl: function (imagePath, options) {
if (!options) {
return imagePath;
}
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"imagesApiBaseUrl",
"GetProcessedImageUrl",
{
imagePath,
animationProcessMode: options.animationProcessMode,
cacheBusterValue: options.cacheBusterValue,
focalPoint: options.focalPoint,
height: options.height,
mode: options.mode,
upscale: options.upscale || false,
width: options.width
})),
"Failed to retrieve processed image URL for image: " + imagePath);
}
};
@@ -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, localStorageService) {
$routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource, eventsService, localStorageService, mediaHelper) {
//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
@@ -298,15 +298,20 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) {
var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h);
editor.dom.setAttrib(imageDomElement, 'width', newSize.width);
editor.dom.setAttrib(imageDomElement, 'height', newSize.height);
// Images inserted via Media Picker will have a URL we can use for ImageResizer QueryStrings
// Images pasted/dragged in are not persisted to media until saved & thus will need to be added
if(imgUrl){
var src = imgUrl + "?width=" + newSize.width + "&height=" + newSize.height;
editor.dom.setAttrib(imageDomElement, 'data-mce-src', src);
if (imgUrl) {
mediaHelper.getProcessedImageUrl(imgUrl,
{
height: newSize.height,
width: newSize.width
})
.then(function (resizedImgUrl) {
editor.dom.setAttrib(imageDomElement, 'data-mce-src', resizedImgUrl);
});
}
editor.execCommand("mceAutoResize", false, null, null);
@@ -1495,10 +1500,17 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
});
args.editor.on('ObjectResized', function (e) {
var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max";
var srcAttr = $(e.target).attr("src");
var path = srcAttr.split("?")[0];
$(e.target).attr("data-mce-src", path + qs);
mediaHelper.getProcessedImageUrl(path,
{
height: e.height,
moded: "max",
width: e.width
})
.then(function (resizedPath) {
$(e.target).attr("data-mce-src", resizedPath);
});
syncContent();
});
@@ -41,30 +41,5 @@
};
angular.module("umbraco")
.controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController)
.run(function (mediaHelper, umbRequestHelper, assetsService) {
if (mediaHelper && mediaHelper.registerFileResolver) {
//NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
// they contain different data structures so if we need to query against it we need to be aware of this.
mediaHelper.registerFileResolver("Umbraco.UploadField", function (property, entity, thumbnail) {
if (thumbnail) {
if (mediaHelper.detectIfImageByExtension(property.value)) {
//get default big thumbnail from image processor
var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first";
return thumbnailUrl;
}
else {
return null;
}
}
else {
return property.value;
}
});
}
});
.controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController);
})();
@@ -1,90 +1,93 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.Grid.MediaController",
function ($scope, $timeout, userService, editorService) {
$scope.thumbnailUrl = getThumbnailUrl();
if (!$scope.model.config.startNodeId) {
if ($scope.model.config.ignoreUserStartNodes === true) {
$scope.model.config.startNodeId = -1;
$scope.model.config.startNodeIsVirtual = true;
function ($scope, userService, editorService, mediaHelper) {
} else {
userService.getCurrentUser().then(function (userData) {
$scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
$scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
});
}
}
$scope.setImage = function(){
var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined;
var startNodeIsVirtual = startNodeId ? $scope.model.config.startNodeIsVirtual : undefined;
setThumbnailUrl();
var mediaPicker = {
startNodeId: startNodeId,
startNodeIsVirtual: startNodeIsVirtual,
cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined,
showDetails: true,
disableFolderSelect: true,
onlyImages: true,
dataTypeKey: $scope.model.dataTypeKey,
submit: function(model) {
var selectedImage = model.selection[0];
$scope.control.value = {
focalPoint: selectedImage.focalPoint,
id: selectedImage.id,
udi: selectedImage.udi,
image: selectedImage.image,
caption: selectedImage.altText
};
editorService.close();
},
close: function() {
editorService.close();
if (!$scope.model.config.startNodeId) {
if ($scope.model.config.ignoreUserStartNodes === true) {
$scope.model.config.startNodeId = -1;
$scope.model.config.startNodeIsVirtual = true;
} else {
userService.getCurrentUser().then(function (userData) {
$scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
$scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
});
}
}
editorService.mediaPicker(mediaPicker);
};
$scope.$watch('control.value', function(newValue, oldValue) {
if(angular.equals(newValue, oldValue)){
return; // simply skip that
}
$scope.thumbnailUrl = getThumbnailUrl();
}, true);
function getThumbnailUrl() {
if($scope.control.value && $scope.control.value.image) {
var url = $scope.control.value.image;
$scope.setImage = function () {
var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined;
var startNodeIsVirtual = startNodeId ? $scope.model.config.startNodeIsVirtual : undefined;
if($scope.control.editor.config && $scope.control.editor.config.size){
url += "?width=" + $scope.control.editor.config.size.width;
url += "&height=" + $scope.control.editor.config.size.height;
url += "&animationprocessmode=first";
var mediaPicker = {
startNodeId: startNodeId,
startNodeIsVirtual: startNodeIsVirtual,
cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined,
showDetails: true,
disableFolderSelect: true,
onlyImages: true,
dataTypeKey: $scope.model.dataTypeKey,
submit: function (model) {
var selectedImage = model.selection[0];
if($scope.control.value.focalPoint){
url += "&center=" + $scope.control.value.focalPoint.top +"," + $scope.control.value.focalPoint.left;
url += "&mode=crop";
$scope.control.value = {
focalPoint: selectedImage.focalPoint,
id: selectedImage.id,
udi: selectedImage.udi,
image: selectedImage.image,
caption: selectedImage.altText
};
editorService.close();
},
close: function () {
editorService.close();
}
}
// set default size if no crop present (moved from the view)
if (url.indexOf('?') == -1)
{
url += "?width=800&upscale=false&animationprocessmode=false"
}
return url;
}
return null;
};
editorService.mediaPicker(mediaPicker);
};
});
$scope.$watch('control.value', function (newValue, oldValue) {
if (angular.equals(newValue, oldValue)) {
return; // simply skip that
}
setThumbnailUrl();
}, true);
function setThumbnailUrl() {
if ($scope.control.value && $scope.control.value.image) {
var url = $scope.control.value.image;
var imageOptions = {};
if (url.indexOf('?') == -1) {
// set default size if no crop present (moved from the view)
imageOptions.animationprocessmode = false;
imageOptions.width = 800;
}
else {
if ($scope.control.editor.config && $scope.control.editor.config.size) {
imageOptions.animationprocessmode = "first";
imageOptions.height = $scope.control.editor.config.size.height;
imageOptions.width = $scope.control.editor.config.size.width;
}
if ($scope.control.value.focalPoint) {
imageOptions.focalPoint = {
left: $scope.control.value.focalPoint.left,
top: $scope.control.value.focalPoint.top
}
imageOptions.mode = "crop";
}
}
mediaHelper.getProcessedImageUrl($scope.control.value.image, imageOptions)
.then(function (url) {
$scope.thumbnailUrl = url;
});
}
};
});
@@ -214,47 +214,4 @@ angular.module('umbraco')
$scope.$on('$destroy', function () {
unsubscribe();
});
})
.run(function (mediaHelper, umbRequestHelper) {
if (mediaHelper && mediaHelper.registerFileResolver) {
//NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
// they contain different data structures so if we need to query against it we need to be aware of this.
mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) {
if (property.value && property.value.src) {
if (thumbnail === true) {
return property.value.src + "?width=500&mode=max&animationprocessmode=first";
}
else {
return property.value.src;
}
//this is a fallback in case the cropper has been asssigned a upload field
}
else if (angular.isString(property.value)) {
if (thumbnail) {
if (mediaHelper.detectIfImageByExtension(property.value)) {
var thumbnailUrl = umbRequestHelper.getApiUrl(
"imagesApiBaseUrl",
"GetBigThumbnail",
[{ originalImagePath: property.value }]);
return thumbnailUrl;
}
else {
return null;
}
}
else {
return property.value;
}
}
return null;
});
}
});
@@ -22,8 +22,8 @@ using Umbraco.Web.Security;
using Umbraco.Web.WebApi.Filters;
using Umbraco.Core.Mapping;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models;
using Umbraco.Web.Routing;
using Umbraco.Core.Media;
namespace Umbraco.Web.Editors
{
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Models;
using Umbraco.Core.Media;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
@@ -4,6 +4,7 @@ using System.Net;
using System.Net.Http;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
@@ -83,5 +84,46 @@ namespace Umbraco.Web.Editors
return response;
}
/// <summary>
/// Gets a processed image for the image at the given path
/// </summary>
/// <param name="imagePath"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="focalPointLeft"></param>
/// <param name="focalPointTop"></param>
/// <param name="animationProcessMode"></param>
/// <param name="mode"></param>
/// <param name="upscale"></param>
/// <returns></returns>
/// <remarks>
/// If there is no media, image property or image file is found then this will return not found.
/// </remarks>
public string GetProcessedImageUrl(string imagePath,
int? width = null,
int? height = null,
int? focalPointLeft = null,
int? focalPointTop = null,
string animationProcessMode = "first",
string mode = "max",
bool upscale = false,
string cacheBusterValue = "")
{
var options = new ImageUrlGenerationOptions(imagePath)
{
AnimationProcessMode = animationProcessMode,
CacheBusterValue = cacheBusterValue,
Height = height,
ImageCropMode = mode,
UpScale = upscale,
Width = width,
};
if (focalPointLeft.HasValue && focalPointTop.HasValue)
{
options.FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(focalPointTop.Value, focalPointLeft.Value);
}
return _imageUrlGenerator.GetImageUrl(options);
}
}
}
+1
View File
@@ -7,6 +7,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Mapping;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
@@ -36,6 +36,7 @@ using Task = System.Threading.Tasks.Task;
using Umbraco.Core.Mapping;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Web.Routing;
using Umbraco.Core.Media;
namespace Umbraco.Web.Editors
{
@@ -6,6 +6,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Web.Models;
using Umbraco.Core.Media;
namespace Umbraco.Web
{
@@ -44,12 +44,12 @@ using Umbraco.Web.Trees;
using Umbraco.Web.WebApi;
using Umbraco.Web.PropertyEditors;
using Umbraco.Examine;
using Umbraco.Core.Models;
using Umbraco.Net;
using Umbraco.Core.Request;
using Umbraco.Core.Session;
using Umbraco.Web.AspNet;
using Umbraco.Web.Models;
using Umbraco.Core.Media;
using Umbraco.Infrastructure.Media;
namespace Umbraco.Web.Runtime
{
+1 -1
View File
@@ -4,7 +4,7 @@ using System.Linq;
using System.Web;
using System.Web.Mvc;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Media;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Web.Composing;