diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs
index f0cb8c0574..ff1925c72d 100644
--- a/src/Umbraco.Core/Models/ContentBase.cs
+++ b/src/Umbraco.Core/Models/ContentBase.cs
@@ -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;
diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs
index c0e4214881..0c232ff1e5 100644
--- a/src/Umbraco.Core/Models/ContentExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentExtensions.cs
@@ -159,20 +159,7 @@ namespace Umbraco.Core.Models
return Current.Services.MediaService.GetById(media.ParentId);
}
#endregion
-
- #region Variants
-
- ///
- /// Returns true if the content has any property type that allows language variants
- ///
- 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
-
+
///
/// Removes characters that are not valide XML characters from all entity properties
/// of type string. See: http://stackoverflow.com/a/961504/5018
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index 76321abe6a..7f79788091 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -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(u => u.Set(x => x.Published, false)).Where(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
+ ///
+ /// This ensures that the Name property exists and validates if all names are null
+ ///
+ ///
+ ///
+ 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);
diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs
index fc37000e13..919ca73273 100644
--- a/src/Umbraco.Core/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentService.cs
@@ -163,6 +163,8 @@ namespace Umbraco.Core.Services.Implement
///
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
/// The content object.
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
/// The content object.
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
/// The content object.
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
/// The content object.
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(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(content, false, false), "UnPublished");
+ scope.Events.Dispatch(UnPublished, this, new PublishEventArgs(content, false, false), nameof(UnPublished));
DeleteLocked(scope, content);
@@ -1265,7 +1270,7 @@ namespace Umbraco.Core.Services.Implement
_documentRepository.Delete(c);
var args = new DeleteEventArgs(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().WhereIn(x => x.ContentTypeId, contentTypeIdsA);
var contents = _documentRepository.Get(query).ToArray();
- if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contents)))
+ if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(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(content), "DeletedBlueprint");
+ scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), nameof(DeletedBlueprint));
scope.Complete();
}
}
@@ -2357,7 +2362,7 @@ namespace Umbraco.Core.Services.Implement
_documentBlueprintRepository.Delete(blueprint);
}
- scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), "DeletedBlueprint");
+ scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), nameof(DeletedBlueprint));
scope.Complete();
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js
index 1888ff623d..70004900f9 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js
@@ -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
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html
index 41d44114a0..dfe235753c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html
@@ -50,7 +50,7 @@
server-validation-field="Alias">
-
+
{{vm.currentVariant.language.name}}
diff --git a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs
index f44781886d..58c5732650 100644
--- a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs
@@ -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();
+
foreach (var payload in payloads)
{
// remove that one
@@ -62,6 +67,31 @@ namespace Umbraco.Web.Cache
var pathid = "," + payload.Id + ",";
runtimeCache.ClearCacheObjectTypes((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();
+ // 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)
diff --git a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs
index 5e3c0220a8..5520d72cd2 100644
--- a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs
@@ -84,6 +84,7 @@ namespace Umbraco.Web.Cache
public DomainChangeTypes ChangeType { get; }
}
- #endregion
+ #endregion
+
}
}
diff --git a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs
index 2270f6471c..d523d3686f 100644
--- a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs
@@ -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
{
- 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();
+ ClearAllIsolatedCacheByEntityType();
+ RefreshDomains(id);
base.Refresh(id);
}
@@ -34,10 +44,30 @@ namespace Umbraco.Web.Cache
{
ClearAllIsolatedCacheByEntityType();
//if a language is removed, then all dictionary cache needs to be removed
- ClearAllIsolatedCacheByEntityType();
+ ClearAllIsolatedCacheByEntityType();
+ 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();
+ // 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());
+ }
+ }
}
}
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs
index efdc6c23ab..691938c771 100644
--- a/src/Umbraco.Web/Editors/ContentController.cs
+++ b/src/Umbraco.Web/Editors/ContentController.cs
@@ -72,7 +72,9 @@ namespace Umbraco.Web.Editors
///
[FilterAllowedOutgoingContent(typeof(IEnumerable))]
public IEnumerable 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;
- }
-
///
/// Gets an empty content item for the
///
@@ -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
///
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();
}
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
index b498a1a42b..06eb14898e 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
@@ -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");
diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DomainCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DomainCache.cs
index 9a82840024..0f40d78225 100644
--- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DomainCache.cs
+++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DomainCache.cs
@@ -17,6 +17,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
DefaultCulture = systemDefaultCultureProvider.DefaultCulture;
}
+ ///
+ /// Returns all in the current domain cache including any domains that may be referenced by content items that are no longer published
+ ///
+ ///
+ ///
public IEnumerable 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));
}
+ ///
+ /// Returns all assigned for the content id specified even if the content item is not published
+ ///
+ ///
+ ///
+ ///
public IEnumerable GetAssigned(int contentId, bool includeWildcards)
{
return _domainService.GetAssignedDomains(contentId, includeWildcards)
diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs
index 35e5ab1af4..f85d2d6c28 100644
--- a/src/Umbraco.Web/Routing/PublishedRouter.cs
+++ b/src/Umbraco.Web/Routing/PublishedRouter.cs
@@ -266,7 +266,11 @@ namespace Umbraco.Web.Routing
_logger.Debug(() => $"{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
diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
index 8c3b705bb6..6863a267b1 100644
--- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
+++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
@@ -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
- {
+ {
+ protected override ContentItemValidationHelper 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
+ {
+ ///
+ /// Validates that the correct information is in the request for saving a culture variant
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
}
}
}
diff --git a/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs b/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs
index 57215ab5ee..45a1947c50 100644
--- a/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs
+++ b/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs
@@ -186,9 +186,9 @@ namespace Umbraco.Web.WebApi.Binders
///
///
///
- public override bool ValidatePropertyData(ContentItemBasic postedItem, ContentItemDto dto, ModelStateDictionary modelState)
+ public override bool ValidatePropertyData(MemberSave postedItem, ContentItemDto dto, ModelStateDictionary modelState)
{
- var memberSave = (MemberSave)postedItem;
+ var memberSave = postedItem;
if (memberSave.Username.IsNullOrWhiteSpace())
{
@@ -234,7 +234,7 @@ namespace Umbraco.Web.WebApi.Binders
///
///
///
- protected override bool ValidateProperties(ContentItemBasic postedItem, HttpActionContext actionContext)
+ protected override bool ValidateProperties(MemberSave postedItem, HttpActionContext actionContext)
{
var propertiesToValidate = postedItem.Properties.ToList();
var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs
index c1a8c989e5..f0e23fcadf 100644
--- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs
+++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs
@@ -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;
+ }
///
/// Ensure the content exists
@@ -58,7 +64,7 @@ namespace Umbraco.Web.WebApi.Filters
///
///
///
- protected virtual bool ValidateExistingContent(ContentItemBasic postedItem, HttpActionContext actionContext)
+ protected virtual bool ValidateExistingContent(TModelSave postedItem, HttpActionContext actionContext)
{
if (postedItem.PersistedContent == null)
{
@@ -76,7 +82,7 @@ namespace Umbraco.Web.WebApi.Filters
///
///
///
- protected virtual bool ValidateProperties(ContentItemBasic 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
///
/// All property data validation goes into the modelstate with a prefix of "Properties"
///
- public virtual bool ValidatePropertyData(ContentItemBasic postedItem, ContentItemDto dto, ModelStateDictionary modelState)
+ public virtual bool ValidatePropertyData(TModelSave postedItem, ContentItemDto dto, ModelStateDictionary modelState)
{
foreach (var p in dto.Properties)
{