Merge remote-tracking branch 'origin/v8/dev' into v8/bugfix/AB10622-be-property-editor-caching

This commit is contained in:
Elitsa Marinovska
2021-05-06 10:54:55 +02:00
38 changed files with 470 additions and 194 deletions

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Core.Cache
internal class DefaultRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
where TEntity : class, IEntity
{
private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const
private static readonly TEntity[] s_emptyEntities = new TEntity[0]; // const
private readonly RepositoryCachePolicyOptions _options;
public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
@@ -29,16 +29,24 @@ namespace Umbraco.Core.Cache
_options = options ?? throw new ArgumentNullException(nameof(options));
}
protected string GetEntityCacheKey(object id)
protected string GetEntityCacheKey(int id) => EntityTypeCacheKey + id;
protected string GetEntityCacheKey(TId id)
{
if (id == null) throw new ArgumentNullException(nameof(id));
return GetEntityTypeCacheKey() + id;
if (EqualityComparer<TId>.Default.Equals(id, default))
{
return string.Empty;
}
if (typeof(TId).IsValueType)
{
return EntityTypeCacheKey + id;
}
return EntityTypeCacheKey + id.ToString().ToUpperInvariant();
}
protected string GetEntityTypeCacheKey()
{
return $"uRepo_{typeof (TEntity).Name}_";
}
protected string EntityTypeCacheKey { get; } = $"uRepo_{typeof(TEntity).Name}_";
protected virtual void InsertEntity(string cacheKey, TEntity entity)
{
@@ -52,7 +60,7 @@ namespace Umbraco.Core.Cache
// getting all of them, and finding nothing.
// if we can cache a zero count, cache an empty array,
// for as long as the cache is not cleared (no expiration)
Cache.Insert(GetEntityTypeCacheKey(), () => EmptyEntities);
Cache.Insert(EntityTypeCacheKey, () => s_emptyEntities);
}
else
{
@@ -81,7 +89,7 @@ namespace Umbraco.Core.Cache
}
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
}
catch
{
@@ -91,7 +99,7 @@ namespace Umbraco.Core.Cache
Cache.Clear(GetEntityCacheKey(entity.Id));
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
throw;
}
@@ -113,7 +121,7 @@ namespace Umbraco.Core.Cache
}
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
}
catch
{
@@ -123,7 +131,7 @@ namespace Umbraco.Core.Cache
Cache.Clear(GetEntityCacheKey(entity.Id));
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
throw;
}
@@ -144,7 +152,7 @@ namespace Umbraco.Core.Cache
var cacheKey = GetEntityCacheKey(entity.Id);
Cache.Clear(cacheKey);
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
}
}
@@ -195,7 +203,7 @@ namespace Umbraco.Core.Cache
else
{
// get everything we have
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(GetEntityTypeCacheKey())
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(EntityTypeCacheKey)
.ToArray(); // no need for null checks, we are not caching nulls
if (entities.Length > 0)
@@ -218,7 +226,7 @@ namespace Umbraco.Core.Cache
{
// if none of them were in the cache
// and we allow zero count - check for the special (empty) entry
var empty = Cache.GetCacheItem<TEntity[]>(GetEntityTypeCacheKey());
var empty = Cache.GetCacheItem<TEntity[]>(EntityTypeCacheKey);
if (empty != null) return empty;
}
}
@@ -238,7 +246,7 @@ namespace Umbraco.Core.Cache
/// <inheritdoc />
public override void ClearAll()
{
Cache.ClearByKey(GetEntityTypeCacheKey());
Cache.ClearByKey(EntityTypeCacheKey);
}
}
}

View File

@@ -86,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Update(dto);
entity.ResetDirtyProperties();
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent>(entity.Id));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent, int>(entity.Id));
}
/// <inheritdoc />

View File

@@ -130,7 +130,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var translation in dictionaryItem.Translations)
translation.Value = translation.Value.ToValidXmlString();
var dto = DictionaryItemFactory.BuildDto(dictionaryItem);
var id = Convert.ToInt32(Database.Insert(dto));
@@ -152,7 +152,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var translation in entity.Translations)
translation.Value = translation.Value.ToValidXmlString();
var dto = DictionaryItemFactory.BuildDto(entity);
Database.Update(dto);
@@ -174,8 +174,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.ResetDirtyProperties();
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
}
protected override void PersistDeletedItem(IDictionaryItem entity)
@@ -186,8 +186,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = entity.Key });
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
entity.DeleteDate = DateTime.Now;
}
@@ -203,8 +203,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = dto.UniqueId });
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.UniqueId));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(dto.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(dto.UniqueId));
}
}

View File

@@ -1163,7 +1163,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (withCache)
{
// if the cache contains the (proper version of the) item, use it
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent>(dto.NodeId));
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent, int>(dto.NodeId));
if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id)
{
content[i] = (Content)cached;

View File

@@ -508,7 +508,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (withCache)
{
// if the cache contains the (proper version of the) item, use it
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia>(dto.NodeId));
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia, int>(dto.NodeId));
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
{
content[i] = (Models.Media)cached;

View File

@@ -331,7 +331,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
protected override void PersistUpdatedItem(IMember entity)
{
{
// update
entity.UpdatingEntity();
@@ -534,7 +534,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s
.Select<ContentVersionDto>(x => x.Id)
.From<ContentVersionDto>()
.From<ContentVersionDto>()
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
@@ -606,7 +606,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (withCache)
{
// if the cache contains the (proper version of the) item, use it
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember>(dto.NodeId));
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember, int>(dto.NodeId));
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
{
content[i] = (Member) cached;

View File

@@ -8,14 +8,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal static class RepositoryCacheKeys
{
private static readonly Dictionary<Type, string> Keys = new Dictionary<Type, string>();
private static readonly Dictionary<Type, string> s_keys = new Dictionary<Type, string>();
public static string GetKey<T>()
{
var type = typeof(T);
return Keys.TryGetValue(type, out var key) ? key : (Keys[type] = "uRepo_" + type.Name + "_");
return s_keys.TryGetValue(type, out var key) ? key : (s_keys[type] = "uRepo_" + type.Name + "_");
}
public static string GetKey<T>(object id) => GetKey<T>() + id;
public static string GetKey<T, TId>(TId id)
{
if (EqualityComparer<TId>.Default.Equals(id, default))
{
return string.Empty;
}
if (typeof(TId).IsValueType)
{
return GetKey<T>() + id;
}
return GetKey<T>() + id.ToString().ToUpperInvariant();
}
}
}

View File

@@ -83,6 +83,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override IUser PerformGet(int id)
{
// This will never resolve to a user, yet this is asked
// for all of the time (especially in cases of members).
// Don't issue a SQL call for this, we know it will not exist.
if (id == default || id < -1)
{
return null;
}
var sql = SqlContext.Sql()
.Select<UserDto>()
.From<UserDto>()
@@ -168,7 +176,7 @@ ORDER BY colName";
}
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
{
{
var now = DateTime.UtcNow;
var dto = new UserLoginDto
{

View File

@@ -442,7 +442,6 @@
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
function performSave(args) {
//Used to check validility of nested form - coming from Content Apps mostly
//Set them all to be invalid
var fieldsToRollback = checkValidility();
@@ -455,7 +454,8 @@
create: $scope.page.isNew,
action: args.action,
showNotifications: args.showNotifications,
softRedirect: true
softRedirect: true,
skipValidation: args.skipValidation
}).then(function (data) {
//success
init();
@@ -467,23 +467,24 @@
eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: true });
resetNestedFieldValiation(fieldsToRollback);
if($scope.contentForm.$invalid !== true) {
resetNestedFieldValiation(fieldsToRollback);
}
ensureDirtyIsSetIfAnyVariantIsDirty();
return $q.when(data);
},
function (err) {
syncTreeNode($scope.content, $scope.content.path);
if($scope.contentForm.$invalid !== true) {
resetNestedFieldValiation(fieldsToRollback);
}
if (err && err.status === 400 && err.data) {
// content was saved but is invalid.
eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: false });
}
resetNestedFieldValiation(fieldsToRollback);
return $q.reject(err);
});
}
@@ -735,48 +736,48 @@
clearNotifications($scope.content);
// TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
if (hasVariants($scope.content)) {
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "openSaveDialog" })) {
var dialog = {
parentScope: $scope,
view: "views/content/overlays/save.html",
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_save",
submit: function (model) {
model.submitButtonState = "busy";
var dialog = {
parentScope: $scope,
view: "views/content/overlays/save.html",
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_save",
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
return performSave({
saveMethod: $scope.saveMethod(),
action: "save",
showNotifications: false,
skipValidation: true
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
return performSave({
saveMethod: $scope.saveMethod(),
action: "save",
showNotifications: false
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
handleHttpException(err);
});
},
close: function (oldModel) {
overlayService.close();
}
};
return $q.when(data);
},
function (err) {
clearDirtyState($scope.content.variants);
//model.submitButtonState = "error";
// Because this is the "save"-action, then we actually save though there was a validation error, therefor we will show success and display the validation errors politely.
if(err && err.data && err.data.ModelState && Object.keys(err.data.ModelState).length > 0) {
model.submitButtonState = "success";
} else {
model.submitButtonState = "error";
}
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
handleHttpException(err);
});
},
close: function (oldModel) {
overlayService.close();
}
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
overlayService.open(dialog);
}
else {
//ensure the flags are set
@@ -784,11 +785,17 @@
$scope.page.saveButtonState = "busy";
return performSave({
saveMethod: $scope.saveMethod(),
action: "save"
action: "save",
skipValidation: true
}).then(function () {
$scope.page.saveButtonState = "success";
}, function (err) {
$scope.page.saveButtonState = "error";
// Because this is the "save"-action, then we actually save though there was a validation error, therefor we will show success and display the validation errors politely.
if(err && err.data && err.data.ModelState && Object.keys(err.data.ModelState).length > 0) {
$scope.page.saveButtonState = "success";
} else {
$scope.page.saveButtonState = "error";
}
handleHttpException(err);
});
}

View File

@@ -135,11 +135,11 @@
}
// This directive allows for us to run a custom $compile for the view within the repeater which allows
// us to maintain a $scope hierarchy with the rendered view based on the $scope that initiated the
// us to maintain a $scope hierarchy with the rendered view based on the $scope that initiated the
// infinite editing. The retain the $scope hiearchy a special $parentScope property is passed in to the model.
function EditorRepeaterDirective($http, $templateCache, $compile, angularHelper) {
function link(scope, el, attr, ctrl) {
function link(scope, el) {
var editor = scope && scope.$parent ? scope.$parent.model : null;
if (!editor) {
return;

View File

@@ -20,7 +20,7 @@ TODO
angular.module("umbraco.directives")
.directive('umbFileDropzone',
function ($timeout, Upload, localizationService, umbRequestHelper, overlayService) {
function ($timeout, Upload, localizationService, umbRequestHelper, overlayService, mediaHelper, mediaTypeHelper) {
return {
restrict: 'E',
replace: true,
@@ -88,21 +88,12 @@ angular.module("umbraco.directives")
});
scope.queue = [];
}
// One allowed type
if (scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) {
// Standard setup - set alias to auto select to let the server best decide which media type to use
if (scope.acceptedMediatypes[0].alias === 'Image') {
scope.contentTypeAlias = "umbracoAutoSelect";
} else {
scope.contentTypeAlias = scope.acceptedMediatypes[0].alias;
}
// If we have Accepted Media Types, we will ask to choose Media Type, if Choose Media Type returns false, it only had one choice and therefor no reason to
if (scope.acceptedMediatypes && _requestChooseMediaTypeDialog() === false) {
scope.contentTypeAlias = "umbracoAutoSelect";
_processQueueItem();
}
// More than one, open dialog
if (scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1) {
_chooseMediaType();
}
}
}
@@ -146,8 +137,8 @@ angular.module("umbraco.directives")
// set percentage property on file
file.uploadProgress = progressPercentage;
// set uploading status on file
file.uploadStatus = "uploading";
}
file.uploadStatus = "uploading";
}
})
.success(function(data, status, headers, config) {
if (data.notifications && data.notifications.length > 0) {
@@ -195,35 +186,61 @@ angular.module("umbraco.directives")
});
}
function _chooseMediaType() {
function _requestChooseMediaTypeDialog() {
const dialog = {
view: "itempicker",
filter: scope.acceptedMediatypes.length > 15,
availableItems: scope.acceptedMediatypes,
submit: function (model) {
scope.contentTypeAlias = model.selectedItem.alias;
_processQueueItem();
if (scope.acceptedMediatypes.length === 1) {
// if only one accepted type, then we wont ask to choose.
return false;
}
overlayService.close();
},
close: function () {
var uploadFileExtensions = scope.queue.map(file => mediaHelper.getFileExtension(file.name));
scope.queue.map(function (file) {
file.uploadStatus = "error";
file.serverErrorMessage = "Cannot upload this file, no mediatype selected";
scope.rejected.push(file);
});
scope.queue = [];
var filteredMediaTypes = mediaTypeHelper.getTypeAcceptingFileExtensions(scope.acceptedMediatypes, uploadFileExtensions);
overlayService.close();
}
};
var mediaTypesNotFile = filteredMediaTypes.filter(mediaType => mediaType.alias !== "File");
localizationService.localize("defaultdialogs_selectMediaType").then(value => {
dialog.title = value;
if (mediaTypesNotFile.length <= 1) {
// if only one or less accepted types when we have filtered type 'file' out, then we wont ask to choose.
return false;
}
localizationService.localizeMany(["defaultdialogs_selectMediaType", "mediaType_autoPickMediaType"]).then(function (translations) {
filteredMediaTypes.push({
alias: "umbracoAutoSelect",
name: translations[1],
icon: "icon-wand"
});
const dialog = {
view: "itempicker",
filter: filteredMediaTypes.length > 8,
availableItems: filteredMediaTypes,
submit: function (model) {
scope.contentTypeAlias = model.selectedItem.alias;
_processQueueItem();
overlayService.close();
},
close: function () {
scope.queue.map(function (file) {
file.uploadStatus = "error";
file.serverErrorMessage = "No files uploaded, no mediatype selected";
scope.rejected.push(file);
});
scope.queue = [];
overlayService.close();
}
};
dialog.title = translations[0];
overlayService.open(dialog);
});
return true;// yes, we did open the choose-media dialog, therefor we return true.
}
scope.handleFiles = function(files, event) {

View File

@@ -15,6 +15,7 @@
function valFormManager(serverValidationManager, $rootScope, $timeout, $location, overlayService, eventsService, $routeParams, navigationService, editorService, localizationService, angularHelper) {
var SHOW_VALIDATION_CLASS_NAME = "show-validation";
var SHOW_VALIDATION_Type_CLASS_NAME = "show-validation-type-";
var SAVING_EVENT_NAME = "formSubmitting";
var SAVED_EVENT_NAME = "formSubmitted";
@@ -25,7 +26,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
function ValFormManagerController($scope) {
//This exposes an API for direct use with this directive
// We need this as a way to reference this directive in the scope chain. Since this directive isn't a component and
// We need this as a way to reference this directive in the scope chain. Since this directive isn't a component and
// because it's an attribute instead of an element, we can't use controllerAs or anything like that. Plus since this is
// an attribute an isolated scope doesn't work so it's a bit weird. By doing this we are able to lookup the parent valFormManager
// in the scope hierarchy even if the DOM hierarchy doesn't match (i.e. in infinite editing)
@@ -44,6 +45,8 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
this.isShowingValidation = () => $scope.showValidation === true;
this.getValidationMessageType = () => $scope.valMsgType;
this.notify = notify;
this.isValid = function () {
@@ -94,6 +97,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
var parentFormMgr = scope.parentFormMgr = getAncestorValFormManager(scope, ctrls, 1);
var subView = ctrls.length > 1 ? ctrls[2] : null;
var labels = {};
var valMsgType = 2;// error
var labelKeys = [
"prompt_unsavedChanges",
@@ -109,9 +113,48 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
labels.stayButton = values[3];
});
//watch the list of validation errors to notify the application of any validation changes
// TODO: Wouldn't it be easier/faster to watch formCtrl.$invalid ?
scope.$watch(() => angularHelper.countAllFormErrors(formCtrl),
var lastValidationMessageType = null;
function setValidationMessageType(type) {
removeValidationMessageType();
scope.valMsgType = type;
// overall a copy of message types from notifications.service:
var postfix = "";
switch(type) {
case 0:
//save
break;
case 1:
//info
postfix = "info";
break;
case 2:
//error
postfix = "error";
break;
case 3:
//success
postfix = "success";
break;
case 4:
//warning
postfix = "warning";
break;
}
var cssClass = SHOW_VALIDATION_Type_CLASS_NAME+postfix;
element.addClass(cssClass);
lastValidationMessageType = cssClass;
}
function removeValidationMessageType() {
if(lastValidationMessageType) {
element.removeClass(lastValidationMessageType);
lastValidationMessageType = null;
}
}
// watch the list of validation errors to notify the application of any validation changes
scope.$watch(() => formCtrl.$invalid,
function (e) {
notify(scope);
@@ -138,6 +181,8 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.isShowingValidation())) {
element.addClass(SHOW_VALIDATION_CLASS_NAME);
scope.showValidation = true;
var parentValMsgType = parentFormMgr ? parentFormMgr.getValidationMessageType() : 2;
setValidationMessageType(parentValMsgType || 2);
notifySubView();
}
@@ -145,8 +190,16 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
//listen for the forms saving event
unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function (ev, args) {
var messageType = 2;//error
switch (args.action) {
case "save":
messageType = 4;//warning
break;
}
element.addClass(SHOW_VALIDATION_CLASS_NAME);
scope.showValidation = true;
setValidationMessageType(messageType);
notifySubView();
//set the flag so we can check to see if we should display the error.
isSavingNewItem = $routeParams.create;
@@ -156,6 +209,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function (ev, args) {
//remove validation class
element.removeClass(SHOW_VALIDATION_CLASS_NAME);
removeValidationMessageType();
scope.showValidation = false;
notifySubView();
}));

View File

@@ -32,7 +32,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
return true;
}
function showNotificationsForModelsState(ms) {
function showNotificationsForModelsState(ms, messageType) {
messageType = messageType || 2;
for (const [key, value] of Object.entries(ms)) {
var errorMsg = value[0];
@@ -42,12 +43,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
var idsToErrors = serverValidationManager.parseComplexEditorError(errorMsg, "");
idsToErrors.forEach(x => {
if (x.modelState) {
showNotificationsForModelsState(x.modelState);
showNotificationsForModelsState(x.modelState, messageType);
}
});
}
else if (value[0]) {
notificationsService.error("Validation", value[0]);
//notificationsService.error("Validation", value[0]);
console.log({type:messageType, header:"Validation", message:value[0]})
notificationsService.showNotification({type:messageType, header:"Validation", message:value[0]})
}
}
}
@@ -93,7 +96,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
//we will use the default one for content if not specified
var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback;
if (formHelper.submitForm({ scope: args.scope, action: args.action })) {
var formSubmitOptions = { scope: args.scope, action: args.action };
if(args.skipValidation === true) {
formSubmitOptions.skipValidation = true;
formSubmitOptions.keepServerValidation = true;
}
if (formHelper.submitForm(formSubmitOptions)) {
return args.saveMethod(args.content, args.create, fileManager.getFiles(), args.showNotifications)
.then(function (data) {
@@ -124,6 +132,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
showNotifications: args.showNotifications,
softRedirect: args.softRedirect,
err: err,
action: args.action,
rebindCallback: function () {
// if the error contains data, we want to map that back as we want to continue editing this save. Especially important when the content is new as the returned data will contain ID etc.
if(err.data) {
@@ -639,9 +648,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
//wire up the server validation errs
formHelper.handleServerValidation(args.err.data.ModelState);
var messageType = 2;//error
if (args.action === "save") {
messageType = 4;//warning
}
//add model state errors to notifications
if (args.showNotifications) {
showNotificationsForModelsState(args.err.data.ModelState);
showNotificationsForModelsState(args.err.data.ModelState, messageType);
}
if (!this.redirectToCreatedContent(args.err.data.id, args.softRedirect) || args.softRedirect) {

View File

@@ -23,15 +23,15 @@ function mediaTypeHelper(mediaTypeResource, $q) {
getAllowedImagetypes: function (mediaId){
// TODO: This is horribly inneficient - why make one request per type!?
//This should make a call to c# to get exactly what it's looking for instead of returning every single media type and doing
//This should make a call to c# to get exactly what it's looking for instead of returning every single media type and doing
//some filtering on the client side.
//This is also called multiple times when it's not needed! Example, when launching the media picker, this will be called twice
//This is also called multiple times when it's not needed! Example, when launching the media picker, this will be called twice
//which means we'll be making at least 6 REST calls to fetch each media type
// Get All allowedTypes
return mediaTypeResource.getAllowedTypes(mediaId)
.then(function(types){
var allowedQ = types.map(function(type){
return mediaTypeResource.getById(type.id);
});
@@ -39,16 +39,8 @@ function mediaTypeHelper(mediaTypeResource, $q) {
// Get full list
return $q.all(allowedQ).then(function(fullTypes){
// Find all the media types with an Image Cropper property editor
var filteredTypes = mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper']);
// If there is only one media type with an Image Cropper we will return this one
if(filteredTypes.length === 1) {
return filteredTypes;
// If there is more than one Image cropper, custom media types have been added, and we return all media types with and Image cropper or UploadField
} else {
return mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper', 'Umbraco.UploadField']);
}
// Find all the media types with an Image Cropper or Upload Field property editor
return mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper', 'Umbraco.UploadField']);
});
});
@@ -68,6 +60,31 @@ function mediaTypeHelper(mediaTypeResource, $q) {
}
});
},
getTypeAcceptingFileExtensions: function (mediaTypes, fileExtensions) {
return mediaTypes.filter(mediaType => {
var uploadProperty;
mediaType.groups.forEach(group => {
var foundProperty = group.properties.find(property => property.alias === "umbracoFile");
if(foundProperty) {
uploadProperty = foundProperty;
}
});
if(uploadProperty) {
var acceptedFileExtensions;
if(uploadProperty.editor === "Umbraco.ImageCropper") {
acceptedFileExtensions = Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes;
} else if(uploadProperty.editor === "Umbraco.UploadField") {
acceptedFileExtensions = (uploadProperty.config.fileExtensions && uploadProperty.config.fileExtensions.length > 0) ? uploadProperty.config.fileExtensions.map(x => x.value) : null;
}
if(acceptedFileExtensions && acceptedFileExtensions.length > 0) {
return fileExtensions.length === fileExtensions.filter(fileExt => acceptedFileExtensions.includes(fileExt)).length;
}
return true;
}
return false;
});
}
};

View File

@@ -54,6 +54,15 @@
border-color: @errorBorder;
color: @errorText;
}
.alert-warning() {
background-color: @warningBackground;
border-color: @warningBorder;
color: @warningText;
}
.alert-warning {
.alert-warning()
}
.alert-danger h4,
.alert-error h4 {
color: @errorText;
@@ -110,6 +119,14 @@
padding: 6px 16px 6px 12px;
margin-bottom: 6px;
.show-validation-type-warning & {
.alert-warning();
font-weight: bold;
&.alert-error::after {
border-top-color: @warningBackground;
}
}
&::after {
content:'';
position: absolute;

View File

@@ -50,6 +50,10 @@ button.umb-variant-switcher__toggle {
font-weight: bold;
background-color: @errorBackground;
color: @errorText;
.show-validation-type-warning & {
background-color: @warningBackground;
color: @warningText;
}
animation-duration: 1.4s;
animation-iteration-count: infinite;
@@ -233,7 +237,10 @@ button.umb-variant-switcher__toggle {
.umb-variant-switcher__item.--error {
.umb-variant-switcher__name {
color: @red;
color: @formErrorText;
.show-validation-type-warning & {
color: @formWarningText;
}
&::after {
content: '!';
position: relative;
@@ -250,6 +257,10 @@ button.umb-variant-switcher__toggle {
font-weight: bold;
background-color: @errorBackground;
color: @errorText;
.show-validation-type-warning & {
background-color: @warningBackground;
color: @warningText;
}
animation-duration: 1.4s;
animation-iteration-count: infinite;

View File

@@ -267,6 +267,9 @@
.umb-overlay .text-error {
color: @formErrorText;
}
.umb-overlay .text-warning {
color: @formWarningText;
}
.umb-overlay .text-success {
color: @formSuccessText;

View File

@@ -86,6 +86,16 @@
}
}
}
.show-validation.show-validation-type-warning &.-has-error {
color: @yellow-d2;
&:hover {
color: @yellow-d2 !important;
}
&::before {
background-color: @yellow-d2;
}
}
}
&__action:active,
@@ -122,14 +132,6 @@
line-height: 16px;
display: block;
&.-type-alert {
background-color: @red;
}
&.-type-warning {
background-color: @yellow-d2;
}
&:empty {
height: 12px;
min-width: 12px;
@@ -137,6 +139,11 @@
&.--error-badge {
display: none;
font-weight: 900;
background-color: @red;
.show-validation-type-warning & {
background-color: @yellow-d2;
}
}
}

View File

@@ -26,7 +26,10 @@ a.umb-list-item:focus {
}
.umb-list-item--error {
color: @red;
color: @formErrorText;
}
.umb-list-item--warning {
color: @formWarningText;
}
.umb-list-item:hover .umb-list-checkbox,

View File

@@ -48,6 +48,10 @@
&.--error {
border-color: @formErrorBorder !important;
}
.show-validation-type-warning &.--error {
border-color: @formWarningBorder !important;
}
}
.umb-nested-content__item.ui-sortable-placeholder {
@@ -292,4 +296,4 @@
.umb-textarea, .umb-textstring {
width:100%;
}
}
}

View File

@@ -86,6 +86,13 @@
background-color: @red !important;
border-color: @errorBorder;
}
.show-validation.show-validation-type-warning .umb-tab--error > .umb-tab-button,
.show-validation.show-validation-type-warning .umb-tab--error > .umb-tab-button:hover,
.show-validation.show-validation-type-warning .umb-tab--error > .umb-tab-button:focus {
color: @white !important;
background-color: @yellow-d2 !important;
border-color: @warningBorder;
}
.show-validation .umb-tab--error .umb-tab-button:before {
content: "\e25d";

View File

@@ -506,10 +506,20 @@ input[type="checkbox"][readonly] {
.formFieldState(@formErrorText, @formErrorText, @formErrorBackground);
}
// ValidationError as a warning
.show-validation.show-validation-type-warning.ng-invalid .control-group.error,
.show-validation.show-validation-type-warning.ng-invalid .umb-editor-header__name-wrapper {
.formFieldState(@formWarningText, @formWarningText, @formWarningBackground);
}
//val-highlight directive styling
.highlight-error {
color: @formErrorText !important;
border-color: @red-l1 !important;
.show-validation-type-warning & {
color: @formWarningText !important;
border-color: @yellow-d2 !important;
}
}
// FORM ACTIONS

View File

@@ -291,8 +291,8 @@
@btnSuccessBackground: @ui-btn-positive;// updated 2019
@btnSuccessBackgroundHighlight: @ui-btn-positive-hover;// updated 2019
@btnWarningBackground: @orange;
@btnWarningBackgroundHighlight: lighten(@orange, 10%);
@btnWarningBackground: @yellow-d2;
@btnWarningBackgroundHighlight: lighten(@yellow-d2, 10%);
@btnDangerBackground: @red;
@btnDangerBackgroundHighlight: @red-l1;
@@ -480,7 +480,7 @@
@formWarningBorder: darken(spin(@warningBackground, -10), 3%);
@formErrorText: @errorBackground;
@formErrorBackground: lighten(@errorBackground, 55%);
@formErrorBackground: @errorBackground;
@formErrorBorder: @red;
@formSuccessText: @successBackground;

View File

@@ -16,7 +16,9 @@
<div class="umb-list-item"
ng-repeat="variant in vm.availableVariants track by variant.compositeId">
<ng-form name="saveVariantSelectorForm">
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--error': saveVariantSelectorForm.saveVariantSelector.$invalid}">
<div class="umb-variant-selector-entry" ng-class="{'umb-list-item--warning': saveVariantSelectorForm.saveVariantSelector.$invalid}">
<input type="hidden" name="saveInvariant" val-server-field="_content_variant_invariant_null_" ng-model="variant.save"></input>
<umb-checkbox input-id="{{variant.htmlId}}"
name="saveVariantSelector"
@@ -24,9 +26,10 @@
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}">
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
<span ng-bind="variant.displayName"></span>
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
</span>
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
<span ng-bind="variant.segment"></span>
@@ -36,10 +39,13 @@
<span class="umb-variant-selector-entry__description" ng-if="!saveVariantSelectorForm.saveVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.isMandatory"> - </span>
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="general_mandatory"></localize></span>
<span ng-if="variant.isMandatory"><localize key="general_mandatory"></localize></span>
</span>
<span class="umb-variant-selector-entry__description" ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
<span class="text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
<span class="text-warning" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
</span>
<span class="umb-variant-selector-entry__description" ng-messages="saveVariantSelectorForm.saveInvariant.$error" show-validation-on-submit>
<span class="text-warning" ng-message="valServerField">{{saveVariantSelectorForm.saveInvariant.errorMsg}}</span>
</span>
</umb-checkbox>

View File

@@ -60,8 +60,14 @@
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & {
> button {
color: @formErrorText;
.show-validation-type-warning & {
color: @formWarningText;
}
span.caret {
border-top-color: @formErrorText;
.show-validation-type-warning & {
border-top-color: @formWarningText;
}
}
}
}
@@ -84,6 +90,9 @@
padding: 2px;
line-height: 10px;
background-color: @formErrorText;
.show-validation-type-warning & {
background-color: @formWarningText;
}
font-weight: 900;
animation-duration: 1.4s;

View File

@@ -42,6 +42,9 @@
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & {
color: @formErrorText;
.show-validation-type-warning & {
color: @formWarningText;
}
}
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block:not(.--active) > .umb-block-list__block--content > div > & {
> span {
@@ -61,6 +64,9 @@
padding: 2px;
line-height: 10px;
background-color: @formErrorText;
.show-validation-type-warning & {
background-color: @formWarningText;
}
font-weight: 900;
animation-duration: 1.4s;

View File

@@ -23,10 +23,6 @@
> .umb-block-list__block--actions {
opacity: 0;
transition: opacity 120ms;
.--error {
color: @formErrorBorder !important;
}
}
&:hover,
@@ -100,6 +96,12 @@ ng-form.ng-invalid-val-server-match-settings > .umb-block-list__block > .umb-blo
&:hover {
color: @ui-action-discreet-type-hover;
}
&.--error {
color: @errorBackground;
.show-validation-type-warning & {
color: @warningBackground;
}
}
> .__error-badge {
position: absolute;
top: -2px;
@@ -113,7 +115,10 @@ ng-form.ng-invalid-val-server-match-settings > .umb-block-list__block > .umb-blo
font-weight: bold;
padding: 2px;
line-height: 8px;
background-color: @red;
background-color: @errorBackground;
.show-validation-type-warning & {
background-color: @warningBackground;
}
display: none;
font-weight: 900;
}

View File

@@ -55,6 +55,12 @@
vm.supportCopy = clipboardService.isSupported();
vm.clipboardItems = [];
unsubscribe.push(eventsService.on("clipboardService.storageUpdate", updateClipboard));
unsubscribe.push($scope.$on("editors.content.splitViewChanged", (event, eventData) => {
var compositeId = vm.umbVariantContent.editor.compositeId;
if(eventData.editors.some(x => x.compositeId === compositeId)) {
updateAllBlockObjects();
}
}));
vm.layout = []; // The layout object specific to this Block Editor, will be a direct reference from Property Model.
vm.availableBlockTypes = []; // Available block entries of this property editor.
@@ -66,6 +72,7 @@
});
vm.$onInit = function() {
if (vm.umbProperty && !vm.umbVariantContent) {// if we dont have vm.umbProperty, it means we are in the DocumentTypeEditor.
// not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope
// inheritance is (i.e.infinite editing)
@@ -175,6 +182,8 @@
// is invalid for some reason or the data structure has changed.
invalidLayoutItems.push(entry);
}
} else {
updateBlockObject(entry.$block);
}
});
@@ -197,6 +206,16 @@
}
function updateAllBlockObjects() {
// Update the blockObjects in our layout.
vm.layout.forEach(entry => {
// $block must have the data property to be a valid BlockObject, if not its considered as a destroyed blockObject.
if (entry.$block) {
updateBlockObject(entry.$block);
}
});
}
function getDefaultViewForBlock(block) {
var defaultViewFolderPath = "views/propertyeditors/blocklist/blocklistentryeditors/";
@@ -237,9 +256,6 @@
if (block === null) return null;
ensureCultureData(block.content);
ensureCultureData(block.settings);
block.view = (block.config.view ? block.config.view : getDefaultViewForBlock(block));
block.showValidation = block.config.view ? true : false;
@@ -254,20 +270,51 @@
block.setParentForm = function (parentForm) {
this._parentForm = parentForm;
};
block.activate = activateBlock.bind(null, block);
block.edit = function () {
/** decorator methods, to enable switching out methods without loosing references that would have been made in Block Views codes */
block.activate = function() {
this._activate();
};
block.edit = function() {
this._edit();
};
block.editSettings = function() {
this._editSettings();
};
block.requestDelete = function() {
this._requestDelete();
};
block.delete = function() {
this._delete();
};
block.copy = function() {
this._copy();
};
updateBlockObject(block);
return block;
}
/** As the block object now contains references to this instance of a property editor, we need to ensure that the Block Object contains latest references.
* This is a bit hacky but the only way to maintain this reference currently.
* Notice this is most relevant for invariant properties on variant documents, specially for the scenario where the scope of the reference we stored is destroyed, therefor we need to ensure we always have references to a current running property editor*/
function updateBlockObject(block) {
ensureCultureData(block.content);
ensureCultureData(block.settings);
block._activate = activateBlock.bind(null, block);
block._edit = function () {
var blockIndex = vm.layout.indexOf(this.layout);
editBlock(this, false, blockIndex, this._parentForm);
};
block.editSettings = function () {
block._editSettings = function () {
var blockIndex = vm.layout.indexOf(this.layout);
editBlock(this, true, blockIndex, this._parentForm);
};
block.requestDelete = requestDeleteBlock.bind(null, block);
block.delete = deleteBlock.bind(null, block);
block.copy = copyBlock.bind(null, block);
return block;
block._requestDelete = requestDeleteBlock.bind(null, block);
block._delete = deleteBlock.bind(null, block);
block._copy = copyBlock.bind(null, block);
}
function addNewBlock(index, contentElementTypeKey) {

View File

@@ -343,6 +343,7 @@
<area alias="mediaType">
<key alias="copyFailed">Kopiering af medietypen fejlede</key>
<key alias="moveFailed">Flytning af medietypen fejlede</key>
<key alias="autoPickMediaType">Auto vælg</key>
</area>
<area alias="memberType">
<key alias="copyFailed">Kopiering af medlemstypen fejlede</key>

View File

@@ -362,6 +362,7 @@
<area alias="mediaType">
<key alias="copyFailed">Failed to copy media type</key>
<key alias="moveFailed">Failed to move media type</key>
<key alias="autoPickMediaType">Auto pick</key>
</area>
<area alias="memberType">
<key alias="copyFailed">Failed to copy member type</key>

View File

@@ -369,6 +369,7 @@
<area alias="mediaType">
<key alias="copyFailed">Failed to copy media type</key>
<key alias="moveFailed">Failed to move media type</key>
<key alias="autoPickMediaType">Auto pick</key>
</area>
<area alias="memberType">
<key alias="copyFailed">Failed to copy member type</key>

View File

@@ -54,9 +54,9 @@ namespace Umbraco.Web.Cache
foreach (var payload in payloads.Where(x => x.Id != default))
{
//By INT Id
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent>(payload.Id));
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent, int>(payload.Id));
//By GUID Key
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent>(payload.Key));
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent, Guid?>(payload.Key));
_idkMap.ClearCache(payload.Id);

View File

@@ -51,7 +51,7 @@ namespace Umbraco.Web.Cache
var macroRepoCache = AppCaches.IsolatedCaches.Get<IMacro>();
if (macroRepoCache)
{
macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey<IMacro>(payload.Id));
macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey<IMacro, int>(payload.Id));
}
}

View File

@@ -62,8 +62,8 @@ namespace Umbraco.Web.Cache
// repository cache
// it *was* done for each pathId but really that does not make sense
// only need to do it for the current media
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia>(payload.Id));
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia>(payload.Key));
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia, int>(payload.Id));
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia, Guid?>(payload.Key));
// remove those that are in the branch
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove))

View File

@@ -21,7 +21,7 @@ namespace Umbraco.Web.Cache
_legacyMemberRefresher = new LegacyMemberCacheRefresher(this, appCaches);
}
public class JsonPayload
public class JsonPayload
{
[JsonConstructor]
public JsonPayload(int id, string username)
@@ -87,11 +87,11 @@ namespace Umbraco.Web.Cache
_idkMap.ClearCache(p.Id);
if (memberCache)
{
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(p.Id));
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(p.Username));
}
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
}
}
}
#endregion

View File

@@ -35,7 +35,7 @@ namespace Umbraco.Web.Cache
public override void Refresh(int id)
{
var cache = AppCaches.IsolatedCaches.Get<IRelationType>();
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType>(id));
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType, int>(id));
base.Refresh(id);
}
@@ -48,7 +48,7 @@ namespace Umbraco.Web.Cache
public override void Remove(int id)
{
var cache = AppCaches.IsolatedCaches.Get<IRelationType>();
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType>(id));
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType, int>(id));
base.Remove(id);
}

View File

@@ -43,13 +43,13 @@ namespace Umbraco.Web.Cache
var userCache = AppCaches.IsolatedCaches.Get<IUser>();
if (userCache)
{
userCache.Result.Clear(RepositoryCacheKeys.GetKey<IUser>(id));
userCache.Result.Clear(RepositoryCacheKeys.GetKey<IUser, int>(id));
userCache.Result.ClearByKey(CacheKeys.UserContentStartNodePathsPrefix + id);
userCache.Result.ClearByKey(CacheKeys.UserMediaStartNodePathsPrefix + id);
userCache.Result.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id);
userCache.Result.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id);
}
base.Remove(id);
}

View File

@@ -58,7 +58,7 @@ namespace Umbraco.Web.Cache
var userGroupCache = AppCaches.IsolatedCaches.Get<IUserGroup>();
if (userGroupCache)
{
userGroupCache.Result.Clear(RepositoryCacheKeys.GetKey<IUserGroup>(id));
userGroupCache.Result.Clear(RepositoryCacheKeys.GetKey<IUserGroup, int>(id));
userGroupCache.Result.ClearByKey(UserGroupRepository.GetByAliasCacheKeyPrefix);
}