Ensures that domains assigned to non-published nodes are filtered when routing, ensures caches are cleared when content is deleted or langauges are changed, updates logic for dealing with null invariant content names, adds more validation for saving cultre variants.
This commit is contained in:
@@ -55,8 +55,6 @@ namespace Umbraco.Core.Models
|
||||
Id = 0; // no identity
|
||||
VersionId = 0; // no versions
|
||||
|
||||
//fixme we always need to set the invariant name else an exception will throw if we try to persist
|
||||
Name = name;
|
||||
SetName(culture, name);
|
||||
|
||||
_contentTypeId = contentType.Id;
|
||||
|
||||
@@ -159,20 +159,7 @@ namespace Umbraco.Core.Models
|
||||
return Current.Services.MediaService.GetById(media.ParentId);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Variants
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the content has any property type that allows language variants
|
||||
/// </summary>
|
||||
public static bool HasPropertyTypeVaryingByCulture(this IContent content)
|
||||
{
|
||||
// fixme - what about CultureSegment? what about content.ContentType.Variations?
|
||||
return content.PropertyTypes.Any(x => x.Variations.Has(ContentVariation.CultureNeutral));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes characters that are not valide XML characters from all entity properties
|
||||
/// of type string. See: http://stackoverflow.com/a/961504/5018
|
||||
|
||||
@@ -177,9 +177,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.TagRelationship + " WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.Domain + " WHERE domainRootStructureID = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.Document + " WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentCultureVariation + " WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentVersion + " WHERE id IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)",
|
||||
"DELETE FROM cmsPreviewXml WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id",
|
||||
"DELETE FROM cmsContentXml WHERE nodeId = @id",
|
||||
"DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id",
|
||||
@@ -229,6 +231,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override void PersistNewItem(IContent entity)
|
||||
{
|
||||
//fixme - stop doing this just so we have access to AddingEntity
|
||||
var content = (Content) entity;
|
||||
|
||||
content.AddingEntity();
|
||||
@@ -238,6 +241,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (entity.Template == null)
|
||||
entity.Template = entity.ContentType.DefaultTemplate;
|
||||
|
||||
entity.Name = FormatInvariantNameValue(entity);
|
||||
// ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
|
||||
|
||||
@@ -416,6 +420,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
Database.Execute(Sql().Update<DocumentVersionDto>(u => u.Set(x => x.Published, false)).Where<DocumentVersionDto>(x => x.Id == content.PublishedVersionId));
|
||||
}
|
||||
|
||||
entity.Name = FormatInvariantNameValue(entity);
|
||||
// ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
|
||||
@@ -1074,6 +1079,36 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
#region Utilities
|
||||
|
||||
/// <summary>
|
||||
/// This ensures that the Name property exists and validates if all names are null
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
private string FormatInvariantNameValue(IContent content)
|
||||
{
|
||||
if (content.Name.IsNullOrWhiteSpace() && content.Names.Count == 0)
|
||||
throw new InvalidOperationException("Cannot save content with empty name.");
|
||||
|
||||
if (content.Name.IsNullOrWhiteSpace() && content.Names.Count > 0)
|
||||
{
|
||||
//if we are saving a variant, we current need to have the invariant name set too
|
||||
//fixme - this needs to be discussed! http://issues.umbraco.org/issue/U4-11286
|
||||
|
||||
var defaultCulture = LanguageRepository.GetDefaultIsoCode();
|
||||
if (!defaultCulture.IsNullOrWhiteSpace() && content.Names.TryGetValue(defaultCulture, out var cultureName))
|
||||
{
|
||||
return cultureName;
|
||||
}
|
||||
else
|
||||
{
|
||||
//our only option is to take the first
|
||||
return content.Names.First().Value;
|
||||
}
|
||||
}
|
||||
|
||||
return content.Name;
|
||||
}
|
||||
|
||||
protected override string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0)
|
||||
{
|
||||
return EnsureUniqueNaming == false ? nodeName : base.EnsureUniqueNodeName(parentId, nodeName, id);
|
||||
|
||||
@@ -163,6 +163,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns><see cref="IContent"/></returns>
|
||||
public IContent Create(string name, Guid parentId, string contentTypeAlias, int userId = 0)
|
||||
{
|
||||
//fixme - what about culture?
|
||||
|
||||
var parent = GetById(parentId);
|
||||
return Create(name, parent, contentTypeAlias, userId);
|
||||
}
|
||||
@@ -181,6 +183,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns>The content object.</returns>
|
||||
public IContent Create(string name, int parentId, string contentTypeAlias, int userId = 0)
|
||||
{
|
||||
//fixme - what about culture?
|
||||
|
||||
var contentType = GetContentType(contentTypeAlias);
|
||||
if (contentType == null)
|
||||
throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias));
|
||||
@@ -212,6 +216,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns>The content object.</returns>
|
||||
public IContent Create(string name, IContent parent, string contentTypeAlias, int userId = 0)
|
||||
{
|
||||
//fixme - what about culture?
|
||||
|
||||
if (parent == null) throw new ArgumentNullException(nameof(parent));
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
@@ -241,6 +247,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns>The content object.</returns>
|
||||
public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = 0)
|
||||
{
|
||||
//fixme - what about culture?
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
// locking the content tree secures content types too
|
||||
@@ -273,6 +281,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns>The content object.</returns>
|
||||
public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0)
|
||||
{
|
||||
//fixme - what about culture?
|
||||
|
||||
if (parent == null) throw new ArgumentNullException(nameof(parent));
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
@@ -864,11 +874,6 @@ namespace Umbraco.Core.Services.Implement
|
||||
return OperationResult.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(content.Name))
|
||||
{
|
||||
throw new ArgumentException("Cannot save content with empty name.");
|
||||
}
|
||||
|
||||
var isNew = content.IsNewEntity();
|
||||
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
@@ -1217,7 +1222,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<IContent>(content, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs, nameof(Deleting)))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Cancel(evtMsgs);
|
||||
@@ -1229,7 +1234,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// just raise the event
|
||||
if (content.Trashed == false && content.Published)
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), "UnPublished");
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(UnPublished));
|
||||
|
||||
DeleteLocked(scope, content);
|
||||
|
||||
@@ -1265,7 +1270,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
_documentRepository.Delete(c);
|
||||
var args = new DeleteEventArgs<IContent>(c, false); // raise event & get flagged files
|
||||
scope.Events.Dispatch(Deleted, this, args);
|
||||
scope.Events.Dispatch(Deleted, this, args, nameof(Deleted));
|
||||
|
||||
// fixme not going to work, do it differently
|
||||
_mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files
|
||||
@@ -2149,7 +2154,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
var query = Query<IContent>().WhereIn(x => x.ContentTypeId, contentTypeIdsA);
|
||||
var contents = _documentRepository.Get(query).ToArray();
|
||||
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IContent>(contents)))
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IContent>(contents), nameof(Deleting)))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
@@ -2296,7 +2301,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
_documentBlueprintRepository.Delete(content);
|
||||
scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs<IContent>(content), "DeletedBlueprint");
|
||||
scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs<IContent>(content), nameof(DeletedBlueprint));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
@@ -2357,7 +2362,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
_documentBlueprintRepository.Delete(blueprint);
|
||||
}
|
||||
|
||||
scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs<IContent>(blueprints), "DeletedBlueprint");
|
||||
scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs<IContent>(blueprints), nameof(DeletedBlueprint));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,8 +327,8 @@
|
||||
//get the selected variant and build the additional published variants
|
||||
saveModel.publishVariations = [];
|
||||
|
||||
//if there's more than 1 variant than we need to set the language and include the variants to publish
|
||||
if (displayModel.variants.length > 1) {
|
||||
//if there's any variants than we need to set the language and include the variants to publish
|
||||
if (displayModel.variants.length > 0) {
|
||||
_.each(displayModel.variants,
|
||||
function (d) {
|
||||
//set the selected variant if this is current
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
server-validation-field="Alias">
|
||||
</umb-generate-alias>
|
||||
|
||||
<a ng-if="variants.length > 1" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen">
|
||||
<a ng-if="variants.length > 0" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen">
|
||||
<span>{{vm.currentVariant.language.name}}</span>
|
||||
<ins class="umb-variant-switcher__expand" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}"> </ins>
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -17,12 +18,14 @@ namespace Umbraco.Web.Cache
|
||||
{
|
||||
private readonly IPublishedSnapshotService _publishedSnapshotService;
|
||||
private readonly IdkMap _idkMap;
|
||||
private readonly IDomainService _domainService;
|
||||
|
||||
public ContentCacheRefresher(CacheHelper cacheHelper, IPublishedSnapshotService publishedSnapshotService, IdkMap idkMap)
|
||||
public ContentCacheRefresher(CacheHelper cacheHelper, IPublishedSnapshotService publishedSnapshotService, IdkMap idkMap, IDomainService domainService)
|
||||
: base(cacheHelper)
|
||||
{
|
||||
_publishedSnapshotService = publishedSnapshotService;
|
||||
_idkMap = idkMap;
|
||||
_domainService = domainService;
|
||||
}
|
||||
|
||||
#region Define
|
||||
@@ -49,6 +52,8 @@ namespace Umbraco.Web.Cache
|
||||
runtimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey);
|
||||
runtimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey);
|
||||
|
||||
var idsRemoved = new HashSet<int>();
|
||||
|
||||
foreach (var payload in payloads)
|
||||
{
|
||||
// remove that one
|
||||
@@ -62,6 +67,31 @@ namespace Umbraco.Web.Cache
|
||||
var pathid = "," + payload.Id + ",";
|
||||
runtimeCache.ClearCacheObjectTypes<IContent>((k, v) => v.Path.Contains(pathid));
|
||||
}
|
||||
|
||||
//if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
|
||||
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove))
|
||||
{
|
||||
idsRemoved.Add(payload.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (idsRemoved.Count > 0)
|
||||
{
|
||||
var assignedDomains = _domainService.GetAll(true).Where(x => x.RootContentId.HasValue && idsRemoved.Contains(x.RootContentId.Value)).ToList();
|
||||
|
||||
if (assignedDomains.Count > 0)
|
||||
{
|
||||
//fixme - this is duplicating the logic in DomainCacheRefresher BUT we cannot inject that into this because it it not registered explicitly in the container,
|
||||
// and we cannot inject the CacheRefresherCollection since that would be a circular reference, so what is the best way to call directly in to the
|
||||
// DomainCacheRefresher?
|
||||
|
||||
ClearAllIsolatedCacheByEntityType<IDomain>();
|
||||
// note: must do what's above FIRST else the repositories still have the old cached
|
||||
// content and when the PublishedCachesService is notified of changes it does not see
|
||||
// the new content...
|
||||
// notify
|
||||
_publishedSnapshotService.Notify(assignedDomains.Select(x => new DomainCacheRefresher.JsonPayload(x.Id, DomainChangeTypes.Remove)).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
// note: must do what's above FIRST else the repositories still have the old cached
|
||||
@@ -130,10 +160,6 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
#endregion
|
||||
|
||||
#region Indirect
|
||||
|
||||
public static void RefreshContentTypes(CacheHelper cacheHelper)
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace Umbraco.Web.Cache
|
||||
public DomainChangeTypes ChangeType { get; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
|
||||
namespace Umbraco.Web.Cache
|
||||
{
|
||||
public sealed class LanguageCacheRefresher : CacheRefresherBase<LanguageCacheRefresher>
|
||||
{
|
||||
public LanguageCacheRefresher(CacheHelper cacheHelper)
|
||||
public LanguageCacheRefresher(CacheHelper cacheHelper, IPublishedSnapshotService publishedSnapshotService, IDomainService domainService)
|
||||
: base(cacheHelper)
|
||||
{ }
|
||||
{
|
||||
_publishedSnapshotService = publishedSnapshotService;
|
||||
_domainService = domainService;
|
||||
}
|
||||
|
||||
#region Define
|
||||
|
||||
protected override LanguageCacheRefresher This => this;
|
||||
|
||||
public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654");
|
||||
|
||||
public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654");
|
||||
private readonly IPublishedSnapshotService _publishedSnapshotService;
|
||||
private readonly IDomainService _domainService;
|
||||
|
||||
public override Guid RefresherUniqueId => UniqueId;
|
||||
|
||||
public override string Name => "Language Cache Refresher";
|
||||
@@ -26,7 +35,8 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
public override void Refresh(int id)
|
||||
{
|
||||
ClearAllIsolatedCacheByEntityType<ILanguage>();
|
||||
ClearAllIsolatedCacheByEntityType<ILanguage>();
|
||||
RefreshDomains(id);
|
||||
base.Refresh(id);
|
||||
}
|
||||
|
||||
@@ -34,10 +44,30 @@ namespace Umbraco.Web.Cache
|
||||
{
|
||||
ClearAllIsolatedCacheByEntityType<ILanguage>();
|
||||
//if a language is removed, then all dictionary cache needs to be removed
|
||||
ClearAllIsolatedCacheByEntityType<IDictionaryItem>();
|
||||
ClearAllIsolatedCacheByEntityType<IDictionaryItem>();
|
||||
RefreshDomains(id);
|
||||
base.Remove(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
private void RefreshDomains(int langId)
|
||||
{
|
||||
var assignedDomains = _domainService.GetAll(true).Where(x => x.LanguageId.HasValue && x.LanguageId.Value == langId).ToList();
|
||||
|
||||
if (assignedDomains.Count > 0)
|
||||
{
|
||||
//fixme - this is duplicating the logic in DomainCacheRefresher BUT we cannot inject that into this because it it not registered explicitly in the container,
|
||||
// and we cannot inject the CacheRefresherCollection since that would be a circular reference, so what is the best way to call directly in to the
|
||||
// DomainCacheRefresher?
|
||||
|
||||
ClearAllIsolatedCacheByEntityType<IDomain>();
|
||||
// note: must do what's above FIRST else the repositories still have the old cached
|
||||
// content and when the PublishedCachesService is notified of changes it does not see
|
||||
// the new content...
|
||||
// notify
|
||||
_publishedSnapshotService.Notify(assignedDomains.Select(x => new DomainCacheRefresher.JsonPayload(x.Id, DomainChangeTypes.Remove)).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,9 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns></returns>
|
||||
[FilterAllowedOutgoingContent(typeof(IEnumerable<ContentItemDisplay>))]
|
||||
public IEnumerable<ContentItemDisplay> GetByIds([FromUri]int[] ids)
|
||||
{
|
||||
{
|
||||
//fixme what about cultures?
|
||||
|
||||
var foundContent = Services.ContentService.GetByIds(ids);
|
||||
return foundContent.Select(x => MapToDisplay(x));
|
||||
}
|
||||
@@ -217,7 +219,8 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
|
||||
//fixme what about cultures?
|
||||
public ContentItemDisplay GetBlueprintById(int id)
|
||||
{
|
||||
var foundContent = Services.ContentService.GetBlueprintById(id);
|
||||
@@ -270,19 +273,6 @@ namespace Umbraco.Web.Editors
|
||||
return content;
|
||||
}
|
||||
|
||||
[EnsureUserPermissionForContent("id")]
|
||||
public ContentItemDisplay GetWithTreeDefinition(int id)
|
||||
{
|
||||
var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
|
||||
if (foundContent == null)
|
||||
{
|
||||
HandleContentNotFound(id);
|
||||
}
|
||||
|
||||
var content = MapToDisplay(foundContent);
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty content item for the
|
||||
/// </summary>
|
||||
@@ -954,8 +944,9 @@ namespace Umbraco.Web.Editors
|
||||
if (contentItem.Name.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//set the name according to the culture settings
|
||||
if (!contentItem.Culture.IsNullOrWhiteSpace() && contentItem.PersistedContent.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral))
|
||||
{
|
||||
if (contentItem.PersistedContent.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral))
|
||||
{
|
||||
if (contentItem.Culture.IsNullOrWhiteSpace()) throw new InvalidOperationException($"Cannot save a content item that is {ContentVariation.CultureNeutral} with a culture specified");
|
||||
contentItem.PersistedContent.SetName(contentItem.Culture, contentItem.Name);
|
||||
}
|
||||
else
|
||||
@@ -1185,11 +1176,11 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns></returns>
|
||||
private ContentItemDisplay MapToDisplay(IContent content, string culture = null)
|
||||
{
|
||||
//a languageId must exist in the mapping context if this content item has any property type that can be varied by language
|
||||
//otherwise the property validation will fail since it's expecting to be get/set with a language ID. If a languageId is not explicitly
|
||||
//sent up, then it means that the user is editing the default variant language.
|
||||
if (culture == null && content.HasPropertyTypeVaryingByCulture())
|
||||
{
|
||||
//A culture must exist in the mapping context if this content type is CultureNeutral since for a culture variant to be edited,
|
||||
// the Cuture property of ContentItemDisplay must exist (at least currently).
|
||||
if (culture == null && content.ContentType.Variations.Has(ContentVariation.CultureNeutral))
|
||||
{
|
||||
//If a culture is not explicitly sent up, then it means that the user is editing the default variant language.
|
||||
culture = Services.LocalizationService.GetDefaultLanguageIsoCode();
|
||||
}
|
||||
|
||||
|
||||
@@ -815,6 +815,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
switch (payload.ChangeType)
|
||||
{
|
||||
case DomainChangeTypes.RefreshAll:
|
||||
//fixme why this check?
|
||||
if (!(_serviceContext.DomainService is DomainService))
|
||||
throw new Exception("oops");
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
DefaultCulture = systemDefaultCultureProvider.DefaultCulture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all <see cref="Domain"/> in the current domain cache including any domains that may be referenced by content items that are no longer published
|
||||
/// </summary>
|
||||
/// <param name="includeWildcards"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Domain> GetAll(bool includeWildcards)
|
||||
{
|
||||
return _domainService.GetAll(includeWildcards)
|
||||
@@ -24,6 +29,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
.Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all assigned <see cref="Domain"/> for the content id specified even if the content item is not published
|
||||
/// </summary>
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="includeWildcards"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Domain> GetAssigned(int contentId, bool includeWildcards)
|
||||
{
|
||||
return _domainService.GetAssignedDomains(contentId, includeWildcards)
|
||||
|
||||
@@ -266,7 +266,11 @@ namespace Umbraco.Web.Routing
|
||||
_logger.Debug<PublishedRouter>(() => $"{tracePrefix}Uri=\"{request.Uri}\"");
|
||||
|
||||
var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains;
|
||||
var domains = domainsCache.GetAll(includeWildcards: false);
|
||||
|
||||
//get the domains but filter to ensure that any referenced content is actually published
|
||||
var domains = domainsCache.GetAll(includeWildcards: false)
|
||||
.Where(x => request.UmbracoContext.PublishedSnapshot.Content.GetById(x.ContentId) != null);
|
||||
|
||||
var defaultCulture = domainsCache.DefaultCulture;
|
||||
|
||||
// try to find a domain matching the current request
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.Mapping;
|
||||
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Binders
|
||||
{
|
||||
internal class ContentItemBinder : ContentItemBaseBinder<IContent, ContentItemSave>
|
||||
{
|
||||
{
|
||||
protected override ContentItemValidationHelper<IContent, ContentItemSave> GetValidationHelper()
|
||||
{
|
||||
return new ContentValidationHelper();
|
||||
}
|
||||
|
||||
protected override IContent GetExisting(ContentItemSave model)
|
||||
{
|
||||
return Services.ContentService.GetById(Convert.ToInt32(model.Id));
|
||||
@@ -36,6 +45,27 @@ namespace Umbraco.Web.WebApi.Binders
|
||||
{
|
||||
[ContextMapper.CultureKey] = culture
|
||||
});
|
||||
}
|
||||
|
||||
internal class ContentValidationHelper : ContentItemValidationHelper<IContent, ContentItemSave>
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates that the correct information is in the request for saving a culture variant
|
||||
/// </summary>
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool ValidateCultureVariant(ContentItemSave postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
var contentType = postedItem.PersistedContent.GetContentType();
|
||||
if (contentType.Variations.Has(Core.Models.ContentVariation.CultureNeutral) && postedItem.Culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
//we cannot save a content item that is culture variant if no culture was specified in the request!
|
||||
actionContext.Response = actionContext.Request.CreateValidationErrorResponse($"No 'Culture' found in request. Cannot save a content item that is of a {Core.Models.ContentVariation.CultureNeutral} content type without a specified culture.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,9 +186,9 @@ namespace Umbraco.Web.WebApi.Binders
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="modelState"></param>
|
||||
/// <returns></returns>
|
||||
public override bool ValidatePropertyData(ContentItemBasic<ContentPropertyBasic, IMember> postedItem, ContentItemDto<IMember> dto, ModelStateDictionary modelState)
|
||||
public override bool ValidatePropertyData(MemberSave postedItem, ContentItemDto<IMember> dto, ModelStateDictionary modelState)
|
||||
{
|
||||
var memberSave = (MemberSave)postedItem;
|
||||
var memberSave = postedItem;
|
||||
|
||||
if (memberSave.Username.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -234,7 +234,7 @@ namespace Umbraco.Web.WebApi.Binders
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool ValidateProperties(ContentItemBasic<ContentPropertyBasic, IMember> postedItem, HttpActionContext actionContext)
|
||||
protected override bool ValidateProperties(MemberSave postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
var propertiesToValidate = postedItem.Properties.ToList();
|
||||
var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
|
||||
|
||||
@@ -48,9 +48,15 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
//now do each validation step
|
||||
if (ValidateExistingContent(contentItem, actionContext) == false) return;
|
||||
if (ValidateCultureVariant(contentItem, actionContext) == false) return;
|
||||
if (ValidateProperties(contentItem, actionContext) == false) return;
|
||||
if (ValidatePropertyData(contentItem, contentItem.ContentDto, actionContext.ModelState) == false) return;
|
||||
}
|
||||
|
||||
protected virtual bool ValidateCultureVariant(TModelSave postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the content exists
|
||||
@@ -58,7 +64,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool ValidateExistingContent(ContentItemBasic<ContentPropertyBasic, TPersisted> postedItem, HttpActionContext actionContext)
|
||||
protected virtual bool ValidateExistingContent(TModelSave postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
if (postedItem.PersistedContent == null)
|
||||
{
|
||||
@@ -76,7 +82,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool ValidateProperties(ContentItemBasic<ContentPropertyBasic, TPersisted> postedItem, HttpActionContext actionContext)
|
||||
protected virtual bool ValidateProperties(TModelSave postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
return ValidateProperties(postedItem.Properties.ToList(), postedItem.PersistedContent.Properties.ToList(), actionContext);
|
||||
}
|
||||
@@ -116,7 +122,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// <remarks>
|
||||
/// All property data validation goes into the modelstate with a prefix of "Properties"
|
||||
/// </remarks>
|
||||
public virtual bool ValidatePropertyData(ContentItemBasic<ContentPropertyBasic, TPersisted> postedItem, ContentItemDto<TPersisted> dto, ModelStateDictionary modelState)
|
||||
public virtual bool ValidatePropertyData(TModelSave postedItem, ContentItemDto<TPersisted> dto, ModelStateDictionary modelState)
|
||||
{
|
||||
foreach (var p in dto.Properties)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user