diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index f75e59bee0..23f5bbff8d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -6,36 +6,119 @@ namespace Umbraco.Core.Models.PublishedContent /// /// - /// Represents a cached content. + /// Represents a published content item. /// + /// + /// Can be a published document, media or member. + /// public interface IPublishedContent : IPublishedElement { #region Content - // fixme - all these are colliding with models => ? - // or could we force them to be 'new' in models? - - int Id { get; } - int TemplateId { get; } - int SortOrder { get; } - string Name { get; } - string UrlName { get; } // fixme rename - string DocumentTypeAlias { get; } // fixme obsolete - int DocumentTypeId { get; } // fixme obsolete - string WriterName { get; } - string CreatorName { get; } - int WriterId { get; } - int CreatorId { get; } - string Path { get; } - DateTime CreateDate { get; } - DateTime UpdateDate { get; } - int Level { get; } - string Url { get; } - - IReadOnlyDictionary CultureNames { get; } + // todo - IPublishedContent properties colliding with models + // we need to find a way to remove as much clutter as possible from IPublishedContent, + // since this is preventing someone from creating a property named 'Path' and have it + // in a model, for instance. we could move them all under one unique property eg + // Infos, so we would do .Infos.SortOrder - just an idea - not going to do it in v8 /// - /// Gets a value indicating whether the content is a content (aka a document) or a media. + /// Gets the unique identifier of the content item. + /// + int Id { get; } + + /// + /// Gets the name of the content item. + /// + /// + /// The value of this property is contextual. When the content type is multi-lingual, + /// this is the name for the 'current' culture. + /// + /// FIXME culture aware - returns the value for the 'current' culture whatever it is + see ?? for others + string Name { get; } + + /// + /// Gets the url segment of the content item. + /// + /// + /// The value of this property is contextual. When the content type is multi-lingual, + /// this is the name for the 'current' culture. + /// + /// FIXME rename UrlSegment + culture aware + string UrlName { get; } // fixme rename, segment! + + /// + /// Gets the sort order of the content item. + /// + int SortOrder { get; } + + /// + /// Gets the tree level of the content item. + /// + int Level { get; } + + /// + /// Gets the tree path of the content item. + /// + string Path { get; } + + /// + /// Gets the identifier of the template to use to render the content item. + /// + int TemplateId { get; } + + /// + /// Gets the identifier of the user who created the content item. + /// + int CreatorId { get; } + + /// + /// Gets the name of the user who created the content item. + /// + string CreatorName { get; } + + /// + /// Gets the date the content item was created. + /// + DateTime CreateDate { get; } + + /// + /// Gets the identifier of the user who last updated the content item. + /// + int WriterId { get; } + + /// + /// Gets the name of the user who last updated the content item. + /// + string WriterName { get; } + + /// + /// Gets the date the content item was last updated. + /// + /// + /// For published content items, this is also the date the item was published. + /// This date is global to the content item, see FIXME for per-culture dates + /// + DateTime UpdateDate { get; } + + /// + /// Gets the url of the content item. + /// + /// + /// The value of this property is contextual. It depends on the 'current' + /// In addition, when the content type is multi-lingual, this is the url for the + /// 'current' culture. + /// + /// FIXME explain what 'current' means here + string Url { get; } + + // fixme document + //PublishedCultureInfos Culture(string culture = "."); + //string GetName(string culture = "."); // best naming? GetName? CultureName? + PublishedCultureInfos GetCulture(string culture = "."); + IReadOnlyDictionary Cultures { get; } + + /// + /// Gets the type of the content item (document, media...). /// PublishedItemType ItemType { get; } @@ -51,13 +134,13 @@ namespace Umbraco.Core.Models.PublishedContent #region Tree /// - /// Gets the parent of the content. + /// Gets the parent of the content item. /// /// The parent of root content is null. IPublishedContent Parent { get; } /// - /// Gets the children of the content. + /// Gets the children of the content item. /// /// Children are sorted by their sortOrder. IEnumerable Children { get; } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs index 6cd4818587..4b579d824b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.PublishedContent #region PublishedElement /// - /// Gets the unique key of the published snapshot item. + /// Gets the unique key of the published element. /// Guid Key { get; } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 528a545f8f..f8a4e16bd2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -39,72 +39,99 @@ namespace Umbraco.Core.Models.PublishedContent public IPublishedContent Unwrap() => _content; #region ContentType - + + /// public virtual PublishedContentType ContentType => _content.ContentType; #endregion + + #region PublishedElement - #region Content - - public virtual int Id => _content.Id; - + /// public Guid Key => _content.Key; - public virtual int TemplateId => _content.TemplateId; + #endregion - public virtual int SortOrder => _content.SortOrder; + #region PublishedContent - public virtual string Name => _content.Name; - - public virtual IReadOnlyDictionary CultureNames => _content.CultureNames; + /// + public virtual int Id => _content.Id; + /// + public virtual string Name => _content.Name; + + /// public virtual string UrlName => _content.UrlName; - public virtual string DocumentTypeAlias => _content.DocumentTypeAlias; - - public virtual int DocumentTypeId => _content.DocumentTypeId; - - public virtual string WriterName => _content.WriterName; - - public virtual string CreatorName => _content.CreatorName; - - public virtual int WriterId => _content.WriterId; - - public virtual int CreatorId => _content.CreatorId; - - public virtual string Path => _content.Path; - - public virtual DateTime CreateDate => _content.CreateDate; - - public virtual DateTime UpdateDate => _content.UpdateDate; + /// + public virtual int SortOrder => _content.SortOrder; + /// public virtual int Level => _content.Level; + /// + public virtual string Path => _content.Path; + + /// + public virtual int TemplateId => _content.TemplateId; + + /// + public virtual int CreatorId => _content.CreatorId; + + /// + public virtual string CreatorName => _content.CreatorName; + + /// + public virtual DateTime CreateDate => _content.CreateDate; + + /// + public virtual int WriterId => _content.WriterId; + + /// + public virtual string WriterName => _content.WriterName; + + /// + public virtual DateTime UpdateDate => _content.UpdateDate; + + /// public virtual string Url => _content.Url; + /// + public PublishedCultureInfos GetCulture(string culture = ".") => _content.GetCulture(culture); + + /// + public IReadOnlyDictionary Cultures => _content.Cultures; + + /// public virtual PublishedItemType ItemType => _content.ItemType; + /// public virtual bool IsDraft => _content.IsDraft; #endregion #region Tree + /// public virtual IPublishedContent Parent => _content.Parent; + /// public virtual IEnumerable Children => _content.Children; #endregion #region Properties + /// public virtual IEnumerable Properties => _content.Properties; + /// public virtual IPublishedProperty GetProperty(string alias) { return _content.GetProperty(alias); } + /// public virtual IPublishedProperty GetProperty(string alias, bool recurse) { return _content.GetProperty(alias, recurse); diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureName.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureName.cs index 59ac875aa4..898649f2e5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureName.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureName.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Exceptions; namespace Umbraco.Core.Models.PublishedContent { @@ -16,4 +17,54 @@ namespace Umbraco.Core.Models.PublishedContent public string Name { get; } public string UrlName { get; } } + + /// + /// Contains culture specific values for . + /// + public class PublishedCultureInfos + { + /// + /// Initializes a new instance of the class. + /// + public PublishedCultureInfos(string culture, string name, bool published, DateTime publishedDate) + { + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + + Culture = culture; + Name = name; + UrlSegment = name.ToUrlSegment(culture); + Published = published; + PublishedDate = publishedDate; + } + + /// + /// Gets the culture. + /// + public string Culture { get; } + + /// + /// Gets the name of the item. + /// + public string Name { get; } + + /// + /// Gets the url segment of the item. + /// + public string UrlSegment { get; } + + /// + /// Gets a value indicating whether the culture is published. + /// + /// + /// A published content item will only have published cultures, and therefore this + /// value will always be true. On the other hand, fixme drafts? + /// + public bool Published { get; } + + /// + /// Gets the date when fixme? + /// + public DateTime PublishedDate { get; } // fixme - model? model.UpdateDate - here? + } } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index afdb4c3646..0a41b3c09e 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -1157,7 +1157,16 @@ namespace Umbraco.Core /// The text to filter. /// The culture. /// The safe url segment. - public static string ToUrlSegment(this string text, CultureInfo culture) + public static string ToUrlSegment(this string text, string culture) + => text.ToUrlSegment(CultureInfo.GetCultureInfo(culture)); + + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment. + /// + /// The text to filter. + /// The culture. + /// The safe url segment. + public static string ToUrlSegment(this string text, CultureInfo culture) // fixme obsolete that one, use the string one? { return Current.ShortStringHelper.CleanStringForUrlSegment(text, culture); } diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index 34def77faa..78a75af835 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -16,6 +16,7 @@ using Current = Umbraco.Web.Composing.Current; using LightInject; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Tests.PublishedContent; namespace Umbraco.Tests.Cache.PublishedCache { @@ -23,6 +24,8 @@ namespace Umbraco.Tests.Cache.PublishedCache [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class PublishMediaCacheTests : BaseWebTest { + private Dictionary _mediaTypes; + protected override void Compose() { base.Compose(); @@ -32,6 +35,21 @@ namespace Umbraco.Tests.Cache.PublishedCache .Append(); } + protected override void Initialize() + { + base.Initialize(); + var type = new AutoPublishedContentType(22, "myType", new PublishedPropertyType[] { }); + var image = new AutoPublishedContentType(23, "Image", new PublishedPropertyType[] { }); + var testMediaType = new AutoPublishedContentType(24, "TestMediaType", new PublishedPropertyType[] { }); + _mediaTypes = new Dictionary + { + { type.Alias, type }, + { image.Alias, image }, + { testMediaType.Alias, testMediaType } + }; + ContentTypesCache.GetPublishedContentTypeByAlias = alias => _mediaTypes[alias]; + } + private IMediaType MakeNewMediaType(IUser user, string text, int parentId = -1) { var mt = new MediaType(parentId) { Name = text, Alias = text, Thumbnail = "icon-folder", Icon = "icon-folder" }; @@ -69,6 +87,7 @@ namespace Umbraco.Tests.Cache.PublishedCache { var user = ServiceContext.UserService.GetUserById(0); var mType = MakeNewMediaType(user, "TestMediaType"); + _mediaTypes[mType.Alias] = new PublishedContentType(mType, null); var mRoot = MakeNewMedia("MediaRoot", mType, user, -1); var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id); @@ -82,8 +101,8 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(mRoot.CreateDate.ToString("dd/MM/yyyy HH:mm:ss"), publishedMedia.CreateDate.ToString("dd/MM/yyyy HH:mm:ss")); Assert.AreEqual(mRoot.CreatorId, publishedMedia.CreatorId); //Assert.AreEqual(mRoot.User.Name, publishedMedia.CreatorName); - Assert.AreEqual(mRoot.ContentType.Alias, publishedMedia.DocumentTypeAlias); - Assert.AreEqual(mRoot.ContentType.Id, publishedMedia.DocumentTypeId); + Assert.AreEqual(mRoot.ContentType.Alias, publishedMedia.ContentType.Alias); + Assert.AreEqual(mRoot.ContentType.Id, publishedMedia.ContentType.Id); Assert.AreEqual(mRoot.Level, publishedMedia.Level); Assert.AreEqual(mRoot.Name, publishedMedia.Name); Assert.AreEqual(mRoot.Path, publishedMedia.Path); @@ -186,11 +205,11 @@ namespace Umbraco.Tests.Cache.PublishedCache }; var result = new SearchResult("1234", 1, 1, () => fields.ToDictionary(x => x.Key, x => new List { x.Value })); - + var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new StaticCacheProvider(), ContentTypesCache); var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result)); - DoAssert(doc, 1234, key, 0, 0, "/media/test.jpg", "Image", 0, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2); + DoAssert(doc, 1234, key, 0, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2); Assert.AreEqual(null, doc.Parent); } @@ -206,7 +225,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new StaticCacheProvider(), ContentTypesCache); var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true)); - DoAssert(doc, 2000, key, 0, 2, "image1", "Image", 2044, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); + DoAssert(doc, 2000, key, 0, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); Assert.AreEqual(null, doc.Parent); Assert.AreEqual(2, doc.Children.Count()); Assert.AreEqual(2001, doc.Children.ElementAt(0).Id); @@ -271,7 +290,7 @@ namespace Umbraco.Tests.Cache.PublishedCache }; } - private PublishedMediaCache.DictionaryPublishedContent GetDictionaryDocument( + private DictionaryPublishedContent GetDictionaryDocument( string idKey = "id", string templateKey = "template", string nodeNameKey = "nodeName", @@ -284,11 +303,11 @@ namespace Umbraco.Tests.Cache.PublishedCache { if (children == null) children = new List(); - var dicDoc = new PublishedMediaCache.DictionaryPublishedContent( + var dicDoc = new DictionaryPublishedContent( //the dictionary GetDictionary(idVal, keyVal, parentIdVal, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey), //callback to get the parent - d => new PublishedMediaCache.DictionaryPublishedContent( + d => new DictionaryPublishedContent( // the dictionary GetDictionary(parentIdVal, default(Guid), -1, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey), // callback to get the parent: there is no parent @@ -317,7 +336,7 @@ namespace Umbraco.Tests.Cache.PublishedCache } private void DoAssert( - PublishedMediaCache.DictionaryPublishedContent dicDoc, + DictionaryPublishedContent dicDoc, int idVal = 1234, Guid keyVal = default(Guid), int templateIdVal = 0, @@ -375,8 +394,8 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(templateIdVal, doc.TemplateId); Assert.AreEqual(sortOrderVal, doc.SortOrder); Assert.AreEqual(urlNameVal, doc.UrlName); - Assert.AreEqual(nodeTypeAliasVal, doc.DocumentTypeAlias); - Assert.AreEqual(nodeTypeIdVal, doc.DocumentTypeId); + Assert.AreEqual(nodeTypeAliasVal, doc.ContentType.Alias); + Assert.AreEqual(nodeTypeIdVal, doc.ContentType.Id); Assert.AreEqual(writerNameVal, doc.WriterName); Assert.AreEqual(creatorNameVal, doc.CreatorName); Assert.AreEqual(writerIdVal, doc.WriterId); diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 2ad5f470ef..d86b7f0e6b 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -273,10 +273,9 @@ namespace Umbraco.Tests.Published public override int TemplateId { get; } public override int SortOrder { get; } public override string Name { get; } - public override IReadOnlyDictionary CultureNames => throw new NotSupportedException(); + public override PublishedCultureInfos GetCulture(string culture = ".") => throw new NotSupportedException(); + public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); public override string UrlName { get; } - public override string DocumentTypeAlias { get; } - public override int DocumentTypeId { get; } public override string WriterName { get; } public override string CreatorName { get; } public override int WriterId { get; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 4668a86c78..56c60446ef 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -96,8 +96,9 @@ namespace Umbraco.Tests.PublishedContent public void To_DataTable_With_Filter() { var doc = GetContent(true, 1); - //change a doc type alias - ((TestPublishedContent) doc.Children.ElementAt(0)).DocumentTypeAlias = "DontMatch"; + //change a doc type alias + var c = (TestPublishedContent) doc.Children.ElementAt(0); + c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.InvariantNeutral); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -133,8 +134,9 @@ namespace Umbraco.Tests.PublishedContent CreateDate = DateTime.Now, CreatorId = 1, CreatorName = "Shannon", - DocumentTypeAlias = contentTypeAlias, - DocumentTypeId = 2, + // fixme what're we gonna do? + //DocumentTypeAlias = contentTypeAlias, + //DocumentTypeId = 2, Id = 3, SortOrder = 4, TemplateId = 5, @@ -175,6 +177,8 @@ namespace Umbraco.Tests.PublishedContent ((Collection) d.Properties).Add( new RawValueProperty(factory.CreatePropertyType("property3", 1), d, "value" + (indexVals + 2))); } + + d.ContentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.InvariantNeutral); return d; } @@ -201,10 +205,9 @@ namespace Umbraco.Tests.PublishedContent public int TemplateId { get; set; } public int SortOrder { get; set; } public string Name { get; set; } - public IReadOnlyDictionary CultureNames => throw new NotSupportedException(); + public PublishedCultureInfos GetCulture(string culture = ".") => throw new NotSupportedException(); + public IReadOnlyDictionary Cultures => throw new NotSupportedException(); public string UrlName { get; set; } - public string DocumentTypeAlias { get; set; } - public int DocumentTypeId { get; set; } public string WriterName { get; set; } public string CreatorName { get; set; } public int WriterId { get; set; } @@ -240,10 +243,7 @@ namespace Umbraco.Tests.PublishedContent return property; } - public PublishedContentType ContentType - { - get { throw new NotImplementedException(); } - } + public PublishedContentType ContentType { get; set; } } } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs index ab4b1138c5..d6321af692 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs @@ -1,6 +1,8 @@ -using NUnit.Framework; +using System.Collections.Generic; +using NUnit.Framework; using Umbraco.Core.Composing; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -13,6 +15,7 @@ namespace Umbraco.Tests.PublishedContent private UmbracoContext ctx; private string xmlContent = ""; private bool createContentTypes = true; + private Dictionary _contentTypes; protected override string GetXmlContent(int templateId) { @@ -49,8 +52,8 @@ namespace Umbraco.Tests.PublishedContent [Test] public void IsDocumentType_Recursive_BaseType_ReturnsTrue() { - ContentTypesCache.GetPublishedContentTypeByAlias = null; // fixme this is not pretty InitializeInheritedContentTypes(); + ContentTypesCache.GetPublishedContentTypeByAlias = null; // fixme this is not pretty var publishedContent = ctx.ContentCache.GetById(1100); Assert.That(publishedContent.IsDocumentType("base", true)); @@ -76,9 +79,17 @@ namespace Umbraco.Tests.PublishedContent var inheritedType = new ContentType(baseType, contentTypeAlias) { Alias = contentTypeAlias, Name = "Inherited" }; contentTypeService.Save(baseType); contentTypeService.Save(inheritedType); + _contentTypes = new Dictionary + { + { baseType.Alias, new PublishedContentType(baseType, null) }, + { inheritedType.Alias, new PublishedContentType(inheritedType, null) } + }; + ContentTypesCache.GetPublishedContentTypeByAlias = alias => _contentTypes[alias]; createContentTypes = false; } + ContentTypesCache.GetPublishedContentTypeByAlias = alias => _contentTypes[alias]; + xmlContent = @" diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentOtherTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentOtherTests.cs index e1c2ed9d5a..e4fce2a3be 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentOtherTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentOtherTests.cs @@ -117,6 +117,9 @@ namespace Umbraco.Tests.PublishedContent var variationAccessor = new TestPublishedVariationContextAccessor(); + // invariant is the current default + variationAccessor.Context = new PublishedVariationContext(); + var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; var snapshotService = new PublishedSnapshotService(options, null, @@ -139,9 +142,6 @@ namespace Umbraco.Tests.PublishedContent var snapshot = snapshotService.CreatePublishedSnapshot(previewToken: null); var publishedContent = snapshot.Content.GetById(1); - // invariant is the current default - variationAccessor.Context = null; - Assert.IsNotNull(publishedContent); Assert.AreEqual("It Works1!", publishedContent.Name); Assert.AreEqual("val1", publishedContent.Value("prop")); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 04ed54d81c..2cf555efda 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -364,9 +364,18 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_GroupBy_DocumentTypeAlias() { + var home = new AutoPublishedContentType(22, "Home", new PublishedPropertyType[] { }); + var custom = new AutoPublishedContentType(23, "CustomDocument", new PublishedPropertyType[] { }); + var contentTypes = new Dictionary + { + { home.Alias, home }, + { custom.Alias, custom } + }; + ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias]; + var doc = GetNode(1046); - var found1 = doc.Children.GroupBy(x => x.DocumentTypeAlias).ToArray(); + var found1 = doc.Children.GroupBy(x => x.ContentType.Alias).ToArray(); Assert.AreEqual(2, found1.Length); Assert.AreEqual(2, found1.Single(x => x.Key.ToString() == "Home").Count()); @@ -376,10 +385,19 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_Where_DocumentTypeAlias() { + var home = new AutoPublishedContentType(22, "Home", new PublishedPropertyType[] { }); + var custom = new AutoPublishedContentType(23, "CustomDocument", new PublishedPropertyType[] { }); + var contentTypes = new Dictionary + { + { home.Alias, home }, + { custom.Alias, custom } + }; + ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias]; + var doc = GetNode(1046); - var found1 = doc.Children.Where(x => x.DocumentTypeAlias == "CustomDocument"); - var found2 = doc.Children.Where(x => x.DocumentTypeAlias == "Home"); + var found1 = doc.Children.Where(x => x.ContentType.Alias == "CustomDocument"); + var found2 = doc.Children.Where(x => x.ContentType.Alias == "Home"); Assert.AreEqual(1, found1.Count()); Assert.AreEqual(2, found2.Count()); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index cfc0df25b7..66f7871a4b 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -2,8 +2,10 @@ using System.Collections; using System.Collections.ObjectModel; using System.Globalization; +using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.TestHelpers; @@ -72,8 +74,6 @@ namespace Umbraco.Tests.PublishedContent var pc = new Mock(); pc.Setup(content => content.Id).Returns(1); pc.Setup(content => content.Name).Returns("test"); - pc.Setup(content => content.DocumentTypeId).Returns(2); - pc.Setup(content => content.DocumentTypeAlias).Returns("testAlias"); pc.Setup(content => content.WriterName).Returns("admin"); pc.Setup(content => content.CreatorName).Returns("admin"); pc.Setup(content => content.CreateDate).Returns(DateTime.Now); @@ -81,6 +81,7 @@ namespace Umbraco.Tests.PublishedContent pc.Setup(content => content.Path).Returns("-1,1"); pc.Setup(content => content.Parent).Returns(() => null); pc.Setup(content => content.Properties).Returns(new Collection()); + pc.Setup(content => content.ContentType).Returns(new PublishedContentType(22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.InvariantNeutral)); return pc; } } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 2be86640d7..307ef459fb 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -165,8 +165,6 @@ namespace Umbraco.Tests.PublishedContent IsDraft = false; ContentType = contentType; - DocumentTypeAlias = contentType.Alias; - DocumentTypeId = contentType.Id; } #endregion @@ -178,10 +176,9 @@ namespace Umbraco.Tests.PublishedContent public int TemplateId { get; set; } public int SortOrder { get; set; } public string Name { get; set; } - public IReadOnlyDictionary CultureNames => throw new NotSupportedException(); + public PublishedCultureInfos GetCulture(string culture = ".") => throw new NotSupportedException(); + public IReadOnlyDictionary Cultures => throw new NotSupportedException(); public string UrlName { get; set; } - public string DocumentTypeAlias { get; private set; } - public int DocumentTypeId { get; private set; } public string WriterName { get; set; } public string CreatorName { get; set; } public int WriterId { get; set; } diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index f722906053..c64c70e65a 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -17,6 +17,8 @@ using Umbraco.Web.Routing; using Umbraco.Web.WebApi; using Umbraco.Core.Strings; using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.Testing; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web.Runtime; @@ -141,6 +143,9 @@ namespace Umbraco.Tests.Routing frequest.PublishedContent = umbracoContext.ContentCache.GetById(1172); frequest.TemplateModel = template; + var type = new AutoPublishedContentType(22, "CustomDocument", new PublishedPropertyType[] { }); + ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; + var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContext, Mock.Of())); handler.GetHandlerForRoute(umbracoContext.HttpContext.Request.RequestContext, frequest); diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 7d9bbec855..042794e573 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache; @@ -10,18 +7,31 @@ namespace Umbraco.Tests.TestHelpers.Stubs { internal class TestPublishedContent : PublishedElement, IPublishedContent { - public TestPublishedContent(PublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultureNames = null) + public TestPublishedContent(PublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) : base(contentType, key, values, previewing) { Id = id; - CultureNames = cultureNames; + Cultures = cultures; } public int Id { get; } public int TemplateId { get; set; } public int SortOrder { get; set; } public string Name { get; set; } - public IReadOnlyDictionary CultureNames { get; set; } + public IPublishedVariationContextAccessor VariationContextAccessor { get; set; } + public PublishedCultureInfos GetCulture(string culture = ".") + { + // handle context culture + if (culture == ".") + culture = VariationContextAccessor?.Context.Culture; + + // no invariant culture infos + if (culture == null || Cultures == null) return null; + + // get + return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos : null; + } + public IReadOnlyDictionary Cultures { get; set; } public string UrlName { get; set; } public string DocumentTypeAlias => ContentType.Alias; public int DocumentTypeId { get; set; } diff --git a/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedVariationContextAccessor.cs b/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedVariationContextAccessor.cs index 9327b462f1..455e5795a9 100644 --- a/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedVariationContextAccessor.cs +++ b/src/Umbraco.Tests/Testing/Objects/Accessors/TestPublishedVariationContextAccessor.cs @@ -8,6 +8,10 @@ namespace Umbraco.Tests.Testing.Objects.Accessors public class TestPublishedVariationContextAccessor : IPublishedVariationContextAccessor { /// - public PublishedVariationContext Context { get; set; } + public PublishedVariationContext Context + { + get; + set; + } } } diff --git a/src/Umbraco.Web/Editors/MacroController.cs b/src/Umbraco.Web/Editors/MacroController.cs index dc4d42a48c..ba23d4c8dd 100644 --- a/src/Umbraco.Web/Editors/MacroController.cs +++ b/src/Umbraco.Web/Editors/MacroController.cs @@ -12,6 +12,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.Macros; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Editors { @@ -26,6 +27,13 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class MacroController : UmbracoAuthorizedJsonController, IRequiresSessionState { + private readonly IPublishedVariationContextAccessor _variationContextAccessor; + + public MacroController(IPublishedVariationContextAccessor variationContextAccessor) + { + _variationContextAccessor = variationContextAccessor; + } + /// /// Gets the macro parameters to be filled in for a particular macro /// @@ -113,7 +121,7 @@ namespace Umbraco.Web.Editors //the 'easiest' way might be to create an IPublishedContent manually and populate the legacy 'page' object with that //and then set the legacy parameters. - var legacyPage = new global::umbraco.page(doc); + var legacyPage = new global::umbraco.page(doc, _variationContextAccessor); UmbracoContext.HttpContext.Items["pageID"] = doc.Id; UmbracoContext.HttpContext.Items["pageElements"] = legacyPage.Elements; UmbracoContext.HttpContext.Items[global::Umbraco.Core.Constants.Conventions.Url.AltTemplate] = null; diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index d51a00b3a2..26ca518259 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -90,7 +90,7 @@ namespace Umbraco.Web.Editors { timer.Start(); - pointerNode = pointerNode.FirstChild(x => x.DocumentTypeAlias == contentTypeAlias); + pointerNode = pointerNode.FirstChild(x => x.ContentType.Alias == contentTypeAlias); if (pointerNode == null) break; @@ -277,7 +277,7 @@ namespace Umbraco.Web.Editors if (targetNode == null || targetNode.Id == current.Id) return aliases; if (targetNode.Id != current.Id) { - aliases.Add(targetNode.DocumentTypeAlias); + aliases.Add(targetNode.ContentType.Alias); } diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index a89cab2c65..084ca1fee2 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -19,24 +19,75 @@ namespace Umbraco.Web.Models [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] public abstract class PublishedContentBase : IPublishedContent { - #region Content - private string _url; + + #region ContentType - /// - /// Gets the url of the content. - /// + public abstract PublishedContentType ContentType { get; } + + #endregion + + #region PublishedElement + + /// + public abstract Guid Key { get; } + + #endregion + + #region PublishedContent + + /// + public abstract int Id { get; } + + /// + public abstract string Name { get; } + + /// + public abstract string UrlName { get; } + + /// + public abstract int SortOrder { get; } + + /// + public abstract int Level { get; } + + /// + public abstract string Path { get; } + + /// + public abstract int TemplateId { get; } + + /// + public abstract int CreatorId { get; } + + /// + public abstract string CreatorName { get; } + + /// + public abstract DateTime CreateDate { get; } + + /// + public abstract int WriterId { get; } + + /// + public abstract string WriterName { get; } + + /// + public abstract DateTime UpdateDate { get; } + + /// /// - /// If this content is Content, the url that is returned is the one computed by the NiceUrlProvider, otherwise if - /// this content is Media, the url returned is the value found in the 'umbracoFile' property. + /// The url of documents are computed by the document url providers. The url of medias are, at the moment, + /// computed here from the 'umbracoFile' property -- but we should move to media url providers at some point. /// public virtual string Url { + // fixme contextual! get { // should be thread-safe although it won't prevent url from being resolved more than once if (_url != null) - return _url; + return _url; // fixme very bad idea with nucache? or? switch (ItemType) { @@ -86,86 +137,45 @@ namespace Umbraco.Web.Models return _url; } - } + } + + /// + public abstract PublishedCultureInfos GetCulture(string culture = "."); + /// + public abstract IReadOnlyDictionary Cultures { get; } + + /// public abstract PublishedItemType ItemType { get; } - public abstract int Id { get; } - public abstract Guid Key { get; } - public abstract int TemplateId { get; } - public abstract int SortOrder { get; } - public abstract string Name { get; } - //TODO: On the base ContentData instance this dictionary contains a CultureVariation, should we expose that model here or a different model? - public abstract IReadOnlyDictionary CultureNames { get; } - public abstract string UrlName { get; } - public abstract string DocumentTypeAlias { get; } - public abstract int DocumentTypeId { get; } - public abstract string WriterName { get; } - public abstract string CreatorName { get; } - public abstract int WriterId { get; } - public abstract int CreatorId { get; } - public abstract string Path { get; } - public abstract DateTime CreateDate { get; } - public abstract DateTime UpdateDate { get; } - public abstract int Level { get; } + /// public abstract bool IsDraft { get; } #endregion #region Tree - /// - /// Gets the parent of the content. - /// + /// public abstract IPublishedContent Parent { get; } - /// - /// Gets the children of the content. - /// - /// Children are sorted by their sortOrder. + /// public abstract IEnumerable Children { get; } #endregion - #region ContentType - - public abstract PublishedContentType ContentType { get; } - - #endregion - #region Properties - /// - /// Gets the properties of the content. - /// + /// public abstract IEnumerable Properties { get; } - /// - /// Gets a property identified by its alias. - /// - /// The property alias. - /// The property identified by the alias. - /// - /// If no property with the specified alias exists, returns null. - /// The returned property may have no value (ie HasValue is false). - /// The alias is case-insensitive. - /// + /// public abstract IPublishedProperty GetProperty(string alias); - /// - /// Gets a property identified by its alias. - /// - /// The property alias. - /// A value indicating whether to navigate the tree upwards until a property with a value is found. - /// The property identified by the alias. - /// - /// Navigate the tree upwards and look for a property with that alias and with a value (ie HasValue is true). - /// If found, return the property. If no property with that alias is found, having a value or not, return null. Otherwise - /// return the first property that was found with the alias but had no value (ie HasValue is false). - /// The alias is case-insensitive. - /// + /// public virtual IPublishedProperty GetProperty(string alias, bool recurse) - { + { + // fixme - but can this work with variants? + var property = GetProperty(alias); if (recurse == false) return property; diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 3b0b6b35e2..446827f0a3 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -300,7 +300,7 @@ namespace Umbraco.Web.Mvc } //check if there's a custom controller assigned, base on the document type alias. - var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, request.PublishedContent.DocumentTypeAlias); + var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, request.PublishedContent.ContentType.Alias); //check if that controller exists if (controllerType != null) @@ -320,7 +320,7 @@ namespace Umbraco.Web.Mvc else { Current.Logger.Warn(() => - $"The current Document Type {request.PublishedContent.DocumentTypeAlias} matches a locally declared controller of type {controllerType.FullName}. Custom Controllers for Umbraco routing must implement '{typeof(IRenderController).FullName}' and inherit from '{typeof(ControllerBase).FullName}'."); + $"The current Document Type {request.PublishedContent.ContentType.Alias} matches a locally declared controller of type {controllerType.FullName}. Custom Controllers for Umbraco routing must implement '{typeof(IRenderController).FullName}' and inherit from '{typeof(ControllerBase).FullName}'."); //we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults // that have already been set above. diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 5f2df36bf1..4b235e42b9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -104,8 +104,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // hideTopLevelNode = support legacy stuff, look for /*/path/to/node // else normal, look for /path/to/node content = hideTopLevelNode.Value - ? GetAtRoot(preview).SelectMany(x => x.Children).FirstOrDefault(x => x.GetUrlName(_localizationService, culture) == parts[0]) - : GetAtRoot(preview).FirstOrDefault(x => x.GetUrlName(_localizationService, culture) == parts[0]); + ? GetAtRoot(preview).SelectMany(x => x.Children).FirstOrDefault(x => x.GetCulture(culture).UrlSegment == parts[0]) + : GetAtRoot(preview).FirstOrDefault(x => x.GetCulture(culture).UrlSegment == parts[0]); content = FollowRoute(content, parts, 1, culture); } @@ -114,7 +114,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // have to look for /foo (see note in ApplyHideTopLevelNodeFromPath). if (content == null && hideTopLevelNode.Value && parts.Length == 1) { - content = GetAtRoot(preview).FirstOrDefault(x => x.GetUrlName(_localizationService, culture) == parts[0]); + content = GetAtRoot(preview).FirstOrDefault(x => x.GetCulture(culture).UrlSegment == parts[0]); } return content; @@ -147,9 +147,9 @@ namespace Umbraco.Web.PublishedCache.NuCache var hasDomains = _domainHelper.NodeHasDomains(n.Id); while (hasDomains == false && n != null) // n is null at root { - var urlName = n.GetUrlName(_localizationService, culture); + var urlSegment = n.GetCulture(culture).UrlSegment; - pathParts.Add(urlName); + pathParts.Add(urlSegment); // move to parent node n = n.Parent; @@ -178,14 +178,9 @@ namespace Umbraco.Web.PublishedCache.NuCache var part = parts[i++]; content = content.Children.FirstOrDefault(x => { - // fixme - should use ISystemDefaultCultureAccessor NOT ILocalizationService! - var urlName = x.GetUrlName(_localizationService, culture); - return urlName == part; + var urlSegment = x.GetCulture(culture).UrlSegment; + return urlSegment == part; }); - - // fixme - if content has a wildcard domain, switch culture! or, shall we? - // no - do NOT support wildcard domains, it makes no sense - // OTOH support '*/en' as a valid domain } return content; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index a0ec6e4687..d2a4e2f539 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // ReSharper disable once InconsistentNaming internal readonly ContentData _contentData; // internal for ContentNode cloning - private readonly string _urlName; + private readonly string _urlSegment; private IReadOnlyDictionary _cultureNames; #region Constructors @@ -28,9 +28,9 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentNode = contentNode; _contentData = contentData; _publishedSnapshotAccessor = publishedSnapshotAccessor; - VariationContextAccessor = variationContextAccessor; + VariationContextAccessor = variationContextAccessor; // fixme why is this a property? should be be on the base class? - _urlName = _contentData.Name.ToUrlSegment(); + _urlSegment = _contentData.Name.ToUrlSegment(); IsPreviewing = _contentData.Published == false; var properties = new List(); @@ -74,7 +74,7 @@ namespace Umbraco.Web.PublishedCache.NuCache VariationContextAccessor = origin.VariationContextAccessor; _contentData = origin._contentData; - _urlName = origin._urlName; + _urlSegment = origin._urlSegment; IsPreviewing = origin.IsPreviewing; // here is the main benefit: we do not re-create properties so if anything @@ -91,7 +91,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentNode = origin._contentNode; _contentData = origin._contentData; - _urlName = origin._urlName; + _urlSegment = origin._urlSegment; IsPreviewing = true; // clone properties so _isPreviewing is true @@ -155,51 +155,131 @@ namespace Umbraco.Web.PublishedCache.NuCache #endregion - #region IPublishedContent + #region Content Type - public override int Id => _contentNode.Id; + /// + public override PublishedContentType ContentType => _contentNode.ContentType; + + #endregion + + #region PublishedElement + + /// public override Guid Key => _contentNode.Uid; - public override int DocumentTypeId => _contentNode.ContentType.Id; - public override string DocumentTypeAlias => _contentNode.ContentType.Alias; - public override PublishedItemType ItemType => _contentNode.ContentType.ItemType; - public override string Name => _contentData.Name; - public override IReadOnlyDictionary CultureNames + #endregion + + #region PublishedContent + + /// + public override int Id => _contentNode.Id; + + /// + public override string Name { get { - if (!ContentType.Variations.HasFlag(ContentVariation.CultureNeutral)) - return null; + if (!ContentType.Variations.Has(ContentVariation.CultureNeutral)) // fixme CultureSegment? + return _contentData.Name; - if (_cultureNames == null) - { - var d = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach(var c in _contentData.CultureInfos) - { - d[c.Key] = new PublishedCultureName(c.Value.Name, c.Value.Name.ToUrlSegment()); - } - _cultureNames = d; - } - return _cultureNames; + var culture = VariationContextAccessor.Context.Culture; + if (culture == null) + return _contentData.Name; + + return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.Name : null; } } - public override int Level => _contentNode.Level; - public override string Path => _contentNode.Path; + + /// + public override string UrlName + { + get + { + if (!ContentType.Variations.Has(ContentVariation.CultureNeutral)) // fixme CultureSegment? + return _urlSegment; + + var culture = VariationContextAccessor.Context.Culture; + if (culture == null) + return _urlSegment; + + return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.UrlSegment : null; + } + } + + /// public override int SortOrder => _contentNode.SortOrder; + + /// + public override int Level => _contentNode.Level; + + /// + public override string Path => _contentNode.Path; + + /// public override int TemplateId => _contentData.TemplateId; - public override string UrlName => _urlName; - - public override DateTime CreateDate => _contentNode.CreateDate; - public override DateTime UpdateDate => _contentData.VersionDate; - + /// public override int CreatorId => _contentNode.CreatorId; + + /// public override string CreatorName => GetProfileNameById(_contentNode.CreatorId); + + /// + public override DateTime CreateDate => _contentNode.CreateDate; + + /// public override int WriterId => _contentData.WriterId; + + /// public override string WriterName => GetProfileNameById(_contentData.WriterId); + /// + public override DateTime UpdateDate => _contentData.VersionDate; + + private IReadOnlyDictionary _cultureInfos; + + private static readonly IReadOnlyDictionary NoCultureInfos = new Dictionary(); + + /// + public override PublishedCultureInfos GetCulture(string culture = ".") + { + // handle context culture + if (culture == ".") + culture = VariationContextAccessor.Context.Culture; + + // no invariant culture infos + if (culture == null) return null; + + // get + return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos : null; + } + + /// + public override IReadOnlyDictionary Cultures + { + get + { + if (!ContentType.Variations.Has(ContentVariation.CultureNeutral)) // fixme CultureSegment? + return NoCultureInfos; + + if (_cultureInfos != null) return _cultureInfos; + + return _cultureInfos = _contentData.CultureInfos // fixme can it be null? + .ToDictionary(x => x.Key, x => new PublishedCultureInfos(x.Key, x.Value.Name, false, DateTime.MinValue)); // fixme values! + } + } + + /// + public override PublishedItemType ItemType => _contentNode.ContentType.ItemType; + + /// public override bool IsDraft => _contentData.Published == false; + #endregion + + #region Tree + + /// public override IPublishedContent Parent { get @@ -218,10 +298,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - private string _childrenCacheKey; - - private string ChildrenCacheKey => _childrenCacheKey ?? (_childrenCacheKey = CacheKeys.PublishedContentChildren(Key, IsPreviewing)); - + /// public override IEnumerable Children { get @@ -235,6 +312,10 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + private string _childrenCacheKey; + + private string ChildrenCacheKey => _childrenCacheKey ?? (_childrenCacheKey = CacheKeys.PublishedContentChildren(Key, IsPreviewing)); + private IEnumerable GetChildren() { IEnumerable c; @@ -258,8 +339,15 @@ namespace Umbraco.Web.PublishedCache.NuCache // Q: perfs-wise, is it better than having the store managed an ordered list } + #endregion + + #region Properties + + + /// public override IEnumerable Properties => PropertiesArray; + /// public override IPublishedProperty GetProperty(string alias) { var index = _contentNode.ContentType.GetPropertyIndex(alias); @@ -270,6 +358,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return property; } + /// public override IPublishedProperty GetProperty(string alias, bool recurse) { var property = GetProperty(alias); @@ -283,8 +372,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return (Property)cache.GetCacheItem(key, () => base.GetProperty(alias, true)); } - public override PublishedContentType ContentType => _contentNode.ContentType; - #endregion #region Caching diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index bf764b0ff0..084daf6457 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -144,14 +144,12 @@ namespace Umbraco.Web.PublishedCache public override string Name => _member.Name; - public override IReadOnlyDictionary CultureNames => throw new NotSupportedException(); + public override PublishedCultureInfos GetCulture(string culture = ".") => throw new NotSupportedException(); + + public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); public override string UrlName => throw new NotSupportedException(); - public override string DocumentTypeAlias => _member.ContentTypeAlias; - - public override int DocumentTypeId => _member.ContentType.Id; - //TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current public override string WriterName => _member.GetCreatorProfile().Name; diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs new file mode 100644 index 0000000000..5ef600ba76 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Xml.XPath; +using Examine.LuceneEngine.Providers; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + /// + /// An IPublishedContent that is represented all by a dictionary. + /// + /// + /// This is a helper class and definitely not intended for public use, it expects that all of the values required + /// to create an IPublishedContent exist in the dictionary by specific aliases. + /// + internal class DictionaryPublishedContent : PublishedContentBase + { + // note: I'm not sure this class fully complies with IPublishedContent rules especially + // I'm not sure that _properties contains all properties including those without a value, + // neither that GetProperty will return a property without a value vs. null... @zpqrtbnk + + // List of properties that will appear in the XML and do not match + // anything in the ContentType, so they must be ignored. + private static readonly string[] IgnoredKeys = { "version", "isDoc" }; + + public DictionaryPublishedContent( + IReadOnlyDictionary valueDictionary, + Func getParent, + Func> getChildren, + Func getProperty, + ICacheProvider cacheProvider, + PublishedContentTypeCache contentTypeCache, + XPathNavigator nav, + bool fromExamine) + { + if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary)); + if (getParent == null) throw new ArgumentNullException(nameof(getParent)); + if (getProperty == null) throw new ArgumentNullException(nameof(getProperty)); + + _getParent = new Lazy(() => getParent(ParentId)); + _getChildren = new Lazy>(() => getChildren(Id, nav)); + _getProperty = getProperty; + _cacheProvider = cacheProvider; + + LoadedFromExamine = fromExamine; + + ValidateAndSetProperty(valueDictionary, val => _id = Int32.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! + ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); + //ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); + ValidateAndSetProperty(valueDictionary, val => _sortOrder = Int32.Parse(val), "sortOrder"); + ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName"); + ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); + ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", LuceneIndexer.ItemTypeFieldName); + ValidateAndSetProperty(valueDictionary, val => _documentTypeId = Int32.Parse(val), "nodeType"); + //ValidateAndSetProperty(valueDictionary, val => _writerName = val, "writerName"); + ValidateAndSetProperty(valueDictionary, val => _creatorName = val, "creatorName", "writerName"); //this is a bit of a hack fix for: U4-1132 + //ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID"); + ValidateAndSetProperty(valueDictionary, val => _creatorId = Int32.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132 + ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path"); + ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate"); + ValidateAndSetProperty(valueDictionary, val => _updateDate = ParseDateTimeValue(val), "updateDate"); + ValidateAndSetProperty(valueDictionary, val => _level = Int32.Parse(val), "level"); + ValidateAndSetProperty(valueDictionary, val => + { + int pId; + ParentId = -1; + if (Int32.TryParse(val, out pId)) + { + ParentId = pId; + } + }, "parentID"); + + _contentType = contentTypeCache.Get(PublishedItemType.Media, _documentTypeAlias); + _properties = new Collection(); + + //handle content type properties + //make sure we create them even if there's no value + foreach (var propertyType in _contentType.PropertyTypes) + { + var alias = propertyType.Alias; + _keysAdded.Add(alias); + string value; + const bool isPreviewing = false; // false :: never preview a media + var property = valueDictionary.TryGetValue(alias, out value) == false || value == null + ? new XmlPublishedProperty(propertyType, this, isPreviewing) + : new XmlPublishedProperty(propertyType, this, isPreviewing, value); + _properties.Add(property); + } + + //loop through remaining values that haven't been applied + foreach (var i in valueDictionary.Where(x => + _keysAdded.Contains(x.Key) == false // not already processed + && IgnoredKeys.Contains(x.Key) == false)) // not ignorable + { + if (i.Key.InvariantStartsWith("__")) + { + // no type for that one, dunno how to convert, drop it + //IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); + //_properties.Add(property); + } + else + { + // this is a property that does not correspond to anything, ignore and log + Current.Logger.Warn("Dropping property \"" + i.Key + "\" because it does not belong to the content type."); + } + } + } + + private DateTime ParseDateTimeValue(string val) + { + if (LoadedFromExamine == false) + return DateTime.Parse(val); + + //we need to parse the date time using Lucene converters + var ticks = Int64.Parse(val); + return new DateTime(ticks); + } + + /// + /// Flag to get/set if this was laoded from examine cache + /// + internal bool LoadedFromExamine { get; } + + //private readonly Func _getParent; + private readonly Lazy _getParent; + //private readonly Func> _getChildren; + private readonly Lazy> _getChildren; + private readonly Func _getProperty; + private readonly ICacheProvider _cacheProvider; + + /// + /// Returns 'Media' as the item type + /// + public override PublishedItemType ItemType => PublishedItemType.Media; + + public override IPublishedContent Parent => _getParent.Value; + + public int ParentId { get; private set; } + + public override int Id => _id; + + public override Guid Key => _key; + + public override int TemplateId => 0; + + public override int SortOrder => _sortOrder; + + public override string Name => _name; + + public override PublishedCultureInfos GetCulture(string culture = ".") => throw new NotSupportedException(); + + public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); + + public override string UrlName => _urlName; + + public override string WriterName => _creatorName; + + public override string CreatorName => _creatorName; + + public override int WriterId => _creatorId; + + public override int CreatorId => _creatorId; + + public override string Path => _path; + + public override DateTime CreateDate => _createDate; + + public override DateTime UpdateDate => _updateDate; + + public override int Level => _level; + + public override bool IsDraft => false; + + public override IEnumerable Properties => _properties; + + public override IEnumerable Children => _getChildren.Value; + + public override IPublishedProperty GetProperty(string alias) + { + return _getProperty(this, alias); + } + + public override PublishedContentType ContentType => _contentType; + + // override to implement cache + // cache at context level, ie once for the whole request + // but cache is not shared by requests because we wouldn't know how to clear it + public override IPublishedProperty GetProperty(string alias, bool recurse) + { + if (recurse == false) return GetProperty(alias); + + var key = $"XmlPublishedCache.PublishedMediaCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}"; + var cacheProvider = _cacheProvider; + return cacheProvider.GetCacheItem(key, () => base.GetProperty(alias, true)); + } + + private readonly List _keysAdded = new List(); + private int _id; + private Guid _key; + //private int _templateId; + private int _sortOrder; + private string _name; + private string _urlName; + private string _documentTypeAlias; + private int _documentTypeId; + //private string _writerName; + private string _creatorName; + //private int _writerId; + private int _creatorId; + private string _path; + private DateTime _createDate; + private DateTime _updateDate; + //private Guid _version; + private int _level; + private readonly ICollection _properties; + private readonly PublishedContentType _contentType; + + private void ValidateAndSetProperty(IReadOnlyDictionary valueDictionary, Action setProperty, params string[] potentialKeys) + { + var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null); + if (key == null) + { + throw new FormatException("The valueDictionary is not formatted correctly and is missing any of the '" + String.Join(",", potentialKeys) + "' elements"); + } + + setProperty(valueDictionary[key]); + _keysAdded.Add(key); + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 48a63aa8d3..8f354240e0 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Configuration; using System.IO; using System.Linq; using System.Threading; using System.Xml.XPath; using Examine; -using Examine.LuceneEngine.Providers; using Examine.LuceneEngine.SearchCriteria; using Examine.Providers; using Lucene.Net.Store; @@ -16,7 +14,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; -using Umbraco.Web.Models; using Umbraco.Examine; using umbraco; using Umbraco.Core.Cache; @@ -607,230 +604,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return mediaList; } - /// - /// An IPublishedContent that is represented all by a dictionary. - /// - /// - /// This is a helper class and definitely not intended for public use, it expects that all of the values required - /// to create an IPublishedContent exist in the dictionary by specific aliases. - /// - internal class DictionaryPublishedContent : PublishedContentBase - { - // note: I'm not sure this class fully complies with IPublishedContent rules especially - // I'm not sure that _properties contains all properties including those without a value, - // neither that GetProperty will return a property without a value vs. null... @zpqrtbnk - - // List of properties that will appear in the XML and do not match - // anything in the ContentType, so they must be ignored. - private static readonly string[] IgnoredKeys = { "version", "isDoc" }; - - public DictionaryPublishedContent( - IReadOnlyDictionary valueDictionary, - Func getParent, - Func> getChildren, - Func getProperty, - ICacheProvider cacheProvider, - PublishedContentTypeCache contentTypeCache, - XPathNavigator nav, - bool fromExamine) - { - if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary)); - if (getParent == null) throw new ArgumentNullException(nameof(getParent)); - if (getProperty == null) throw new ArgumentNullException(nameof(getProperty)); - - _getParent = new Lazy(() => getParent(ParentId)); - _getChildren = new Lazy>(() => getChildren(Id, nav)); - _getProperty = getProperty; - _cacheProvider = cacheProvider; - - LoadedFromExamine = fromExamine; - - ValidateAndSetProperty(valueDictionary, val => _id = int.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! - ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); - //ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); - ValidateAndSetProperty(valueDictionary, val => _sortOrder = int.Parse(val), "sortOrder"); - ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName"); - ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); - ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", LuceneIndexer.ItemTypeFieldName); - ValidateAndSetProperty(valueDictionary, val => _documentTypeId = int.Parse(val), "nodeType"); - //ValidateAndSetProperty(valueDictionary, val => _writerName = val, "writerName"); - ValidateAndSetProperty(valueDictionary, val => _creatorName = val, "creatorName", "writerName"); //this is a bit of a hack fix for: U4-1132 - //ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID"); - ValidateAndSetProperty(valueDictionary, val => _creatorId = int.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132 - ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path"); - ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate"); - ValidateAndSetProperty(valueDictionary, val => _updateDate = ParseDateTimeValue(val), "updateDate"); - ValidateAndSetProperty(valueDictionary, val => _level = int.Parse(val), "level"); - ValidateAndSetProperty(valueDictionary, val => - { - int pId; - ParentId = -1; - if (int.TryParse(val, out pId)) - { - ParentId = pId; - } - }, "parentID"); - - _contentType = contentTypeCache.Get(PublishedItemType.Media, _documentTypeAlias); - _properties = new Collection(); - - //handle content type properties - //make sure we create them even if there's no value - foreach (var propertyType in _contentType.PropertyTypes) - { - var alias = propertyType.Alias; - _keysAdded.Add(alias); - string value; - const bool isPreviewing = false; // false :: never preview a media - var property = valueDictionary.TryGetValue(alias, out value) == false || value == null - ? new XmlPublishedProperty(propertyType, this, isPreviewing) - : new XmlPublishedProperty(propertyType, this, isPreviewing, value); - _properties.Add(property); - } - - //loop through remaining values that haven't been applied - foreach (var i in valueDictionary.Where(x => - _keysAdded.Contains(x.Key) == false // not already processed - && IgnoredKeys.Contains(x.Key) == false)) // not ignorable - { - if (i.Key.InvariantStartsWith("__")) - { - // no type for that one, dunno how to convert, drop it - //IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); - //_properties.Add(property); - } - else - { - // this is a property that does not correspond to anything, ignore and log - Current.Logger.Warn("Dropping property \"" + i.Key + "\" because it does not belong to the content type."); - } - } - } - - private DateTime ParseDateTimeValue(string val) - { - if (LoadedFromExamine == false) - return DateTime.Parse(val); - - //we need to parse the date time using Lucene converters - var ticks = long.Parse(val); - return new DateTime(ticks); - } - - /// - /// Flag to get/set if this was laoded from examine cache - /// - internal bool LoadedFromExamine { get; } - - //private readonly Func _getParent; - private readonly Lazy _getParent; - //private readonly Func> _getChildren; - private readonly Lazy> _getChildren; - private readonly Func _getProperty; - private readonly ICacheProvider _cacheProvider; - - /// - /// Returns 'Media' as the item type - /// - public override PublishedItemType ItemType => PublishedItemType.Media; - - public override IPublishedContent Parent => _getParent.Value; - - public int ParentId { get; private set; } - - public override int Id => _id; - - public override Guid Key => _key; - - public override int TemplateId => 0; - - public override int SortOrder => _sortOrder; - - public override string Name => _name; - - public override IReadOnlyDictionary CultureNames => throw new NotSupportedException(); - - public override string UrlName => _urlName; - - public override string DocumentTypeAlias => _documentTypeAlias; - - public override int DocumentTypeId => _documentTypeId; - - public override string WriterName => _creatorName; - - public override string CreatorName => _creatorName; - - public override int WriterId => _creatorId; - - public override int CreatorId => _creatorId; - - public override string Path => _path; - - public override DateTime CreateDate => _createDate; - - public override DateTime UpdateDate => _updateDate; - - public override int Level => _level; - - public override bool IsDraft => false; - - public override IEnumerable Properties => _properties; - - public override IEnumerable Children => _getChildren.Value; - - public override IPublishedProperty GetProperty(string alias) - { - return _getProperty(this, alias); - } - - public override PublishedContentType ContentType => _contentType; - - // override to implement cache - // cache at context level, ie once for the whole request - // but cache is not shared by requests because we wouldn't know how to clear it - public override IPublishedProperty GetProperty(string alias, bool recurse) - { - if (recurse == false) return GetProperty(alias); - - var key = $"XmlPublishedCache.PublishedMediaCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}"; - var cacheProvider = _cacheProvider; - return cacheProvider.GetCacheItem(key, () => base.GetProperty(alias, true)); - } - - private readonly List _keysAdded = new List(); - private int _id; - private Guid _key; - //private int _templateId; - private int _sortOrder; - private string _name; - private string _urlName; - private string _documentTypeAlias; - private int _documentTypeId; - //private string _writerName; - private string _creatorName; - //private int _writerId; - private int _creatorId; - private string _path; - private DateTime _createDate; - private DateTime _updateDate; - //private Guid _version; - private int _level; - private readonly ICollection _properties; - private readonly PublishedContentType _contentType; - - private void ValidateAndSetProperty(IReadOnlyDictionary valueDictionary, Action setProperty, params string[] potentialKeys) - { - var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null); - if (key == null) - { - throw new FormatException("The valueDictionary is not formatted correctly and is missing any of the '" + string.Join(",", potentialKeys) + "' elements"); - } - - setProperty(valueDictionary[key]); - _keysAdded.Add(key); - } - } - internal void Resync() { // clear recursive properties cached by XmlPublishedContent.GetProperty diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 6f9746c57d..eba0312a46 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -150,25 +150,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } - public override IReadOnlyDictionary CultureNames => throw new NotSupportedException(); + public override PublishedCultureInfos GetCulture(string culture = ".") => throw new NotSupportedException(); - public override string DocumentTypeAlias - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _docTypeAlias; - } - } - - public override int DocumentTypeId - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _docTypeId; - } - } + public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); public override string WriterName { diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index fd2ddc51e5..c6d39aa0b0 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -122,8 +122,8 @@ namespace Umbraco.Web #endregion #region Value - - // fixme missing variations, but recurse/variations/fallback = ? + + // fixme missing variations, but recurse/variations/fallback = ? /// /// Recursively gets the value of a content's property identified by its alias. @@ -163,7 +163,7 @@ namespace Umbraco.Web /// The alias is case-insensitive. /// public static object Value(this IPublishedContent content, string alias, string culture = ".", string segment = ".", object defaultValue = default, bool recurse = false) - { + { // fixme - variations+recurse not implemented here var property = content.GetProperty(alias, recurse); return property == null || property.HasValue(culture, segment) == false ? defaultValue : property.GetValue(); @@ -172,7 +172,7 @@ namespace Umbraco.Web #endregion #region Value - + /// /// Recursively gets the value of a content's property identified by its alias, converted to a specified type. /// @@ -290,7 +290,7 @@ namespace Umbraco.Web /// True if the content is of the specified content type; otherwise false. public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) { - return content.DocumentTypeAlias.InvariantEquals(docTypeAlias); + return content.ContentType.Alias.InvariantEquals(docTypeAlias); } /// @@ -477,7 +477,7 @@ namespace Umbraco.Web /// Does not consider the content itself. Returns all ancestors, of the specified content type. public static IEnumerable Ancestors(this IPublishedContent content, string contentTypeAlias) { - return content.AncestorsOrSelf(false, n => n.DocumentTypeAlias == contentTypeAlias); + return content.AncestorsOrSelf(false, n => n.ContentType.Alias == contentTypeAlias); } /// @@ -542,7 +542,7 @@ namespace Umbraco.Web /// May or may not begin with the content itself, depending on its content type. public static IEnumerable AncestorsOrSelf(this IPublishedContent content, string contentTypeAlias) { - return content.AncestorsOrSelf(true, n => n.DocumentTypeAlias == contentTypeAlias); + return content.AncestorsOrSelf(true, n => n.ContentType.Alias == contentTypeAlias); } /// @@ -605,7 +605,7 @@ namespace Umbraco.Web /// Does not consider the content itself. May return null. public static IPublishedContent Ancestor(this IPublishedContent content, string contentTypeAlias) { - return content.EnumerateAncestors(false).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); + return content.EnumerateAncestors(false).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias); } /// @@ -668,7 +668,7 @@ namespace Umbraco.Web /// May or may not return the content itself depending on its content type. May return null. public static IPublishedContent AncestorOrSelf(this IPublishedContent content, string contentTypeAlias) { - return content.EnumerateAncestors(true).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); + return content.EnumerateAncestors(true).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias); } /// @@ -781,7 +781,7 @@ namespace Umbraco.Web public static IEnumerable Descendants(this IPublishedContent content, string contentTypeAlias) { - return content.DescendantsOrSelf(false, p => p.DocumentTypeAlias == contentTypeAlias); + return content.DescendantsOrSelf(false, p => p.ContentType.Alias == contentTypeAlias); } public static IEnumerable Descendants(this IPublishedContent content) @@ -808,7 +808,7 @@ namespace Umbraco.Web public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string contentTypeAlias) { - return content.DescendantsOrSelf(true, p => p.DocumentTypeAlias == contentTypeAlias); + return content.DescendantsOrSelf(true, p => p.ContentType.Alias == contentTypeAlias); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content) @@ -835,7 +835,7 @@ namespace Umbraco.Web public static IPublishedContent Descendant(this IPublishedContent content, string contentTypeAlias) { - return content.EnumerateDescendants(false).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); + return content.EnumerateDescendants(false).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias); } public static T Descendant(this IPublishedContent content) @@ -862,7 +862,7 @@ namespace Umbraco.Web public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string contentTypeAlias) { - return content.EnumerateDescendants(true).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); + return content.EnumerateDescendants(true).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias); } public static T DescendantOrSelf(this IPublishedContent content) @@ -1018,7 +1018,7 @@ namespace Umbraco.Web /// The children of the content, of any of the specified types. public static IEnumerable Children(this IPublishedContent content, params string[] alias) { - return content.Children(x => alias.InvariantContains(x.DocumentTypeAlias)); + return content.Children(x => alias.InvariantContains(x.ContentType.Alias)); } /// @@ -1094,14 +1094,14 @@ namespace Umbraco.Web ? content.Children.Any() ? content.Children.ElementAt(0) : null - : content.Children.FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAliasFilter); + : content.Children.FirstOrDefault(x => x.ContentType.Alias == contentTypeAliasFilter); if (firstNode == null) return new DataTable(); //no children found //use new utility class to create table so that we don't have to maintain code in many places, just one var dt = Core.DataTableExtensions.GenerateDataTable( //pass in the alias of the first child node since this is the node type we're rendering headers for - firstNode.DocumentTypeAlias, + firstNode.ContentType.Alias, //pass in the callback to extract the Dictionary of all defined aliases to their names alias => GetPropertyAliasesAndNames(services, alias), //pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place. @@ -1114,7 +1114,7 @@ namespace Umbraco.Web { if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false) { - if (n.DocumentTypeAlias != contentTypeAliasFilter) + if (n.ContentType.Alias != contentTypeAliasFilter) continue; //skip this one, it doesn't match the filter } @@ -1122,7 +1122,7 @@ namespace Umbraco.Web { { "Id", n.Id }, { "NodeName", n.Name }, - { "NodeTypeAlias", n.DocumentTypeAlias }, + { "NodeTypeAlias", n.ContentType.Alias }, { "CreateDate", n.CreateDate }, { "UpdateDate", n.UpdateDate }, { "CreatorName", n.CreatorName }, @@ -1208,33 +1208,5 @@ namespace Umbraco.Web } #endregion - - #region Culture - - /// - /// Return the URL name for the based on the culture specified or default culture defined - /// - /// - /// - /// - /// - public static string GetUrlName(this IPublishedContent content, ILocalizationService localizationService, string culture = null) - { - // fixme publishedContent could get ISystemDefaultCultureAccessor injected! - - if (content.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral)) - { - var cultureCode = culture ?? localizationService.GetDefaultLanguageIsoCode(); // fixme kill.kill.kill - if (cultureCode != null && content.CultureNames.TryGetValue(cultureCode, out var cultureName)) - { - return cultureName.UrlName; - } - } - - //if we get here, the content type is invariant or we don't have access to a usable culture code - return content.UrlName; - } - - #endregion } } diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index 17078826ff..41d4d4883c 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -24,6 +24,7 @@ using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Macros; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; @@ -69,8 +70,10 @@ namespace Umbraco.Web.Runtime //it still needs to use the install controller so we can't do that composition.Container.RegisterFrom(); - // register the system culture provider + // register accessors for cultures + // fixme merge the two accessors? composition.Container.RegisterSingleton(); + composition.Container.RegisterSingleton(); var typeLoader = composition.Container.GetInstance(); var logger = composition.Container.GetInstance(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e0380c898e..e730c0b29c 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -391,6 +391,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index 61f263f02c..e49bdff9ec 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -65,7 +65,7 @@ namespace umbraco throw new ArgumentException("Document request has no node.", "frequest"); populatePageData(frequest.PublishedContent.Id, - frequest.PublishedContent.Name, frequest.PublishedContent.DocumentTypeId, frequest.PublishedContent.DocumentTypeAlias, + frequest.PublishedContent.Name, frequest.PublishedContent.ContentType.Id, frequest.PublishedContent.ContentType.Alias, frequest.PublishedContent.WriterName, frequest.PublishedContent.CreatorName, frequest.PublishedContent.CreateDate, frequest.PublishedContent.UpdateDate, frequest.PublishedContent.Path, frequest.PublishedContent.Parent == null ? -1 : frequest.PublishedContent.Parent.Id); @@ -89,7 +89,7 @@ namespace umbraco if (doc == null) throw new ArgumentNullException("doc"); populatePageData(doc.Id, - doc.Name, doc.DocumentTypeId, doc.DocumentTypeAlias, + doc.Name, doc.ContentType.Id, doc.ContentType.Alias, doc.WriterName, doc.CreatorName, doc.CreateDate, doc.UpdateDate, doc.Path, doc.Parent == null ? -1 : doc.Parent.Id); @@ -108,8 +108,8 @@ namespace umbraco /// /// The content. /// This is for usage only. - internal page(IContent content) - : this(new PagePublishedContent(content)) + internal page(IContent content, IPublishedVariationContextAccessor variationContextAccessor) + : this(new PagePublishedContent(content, variationContextAccessor)) { } #endregion @@ -408,19 +408,24 @@ namespace umbraco private readonly PublishedContentType _contentType; private readonly IPublishedProperty[] _properties; private readonly IPublishedContent _parent; - private IReadOnlyDictionary _cultureNames; + private IReadOnlyDictionary _cultureNames; + private IReadOnlyDictionary _cultureInfos; + private readonly IPublishedVariationContextAccessor _variationContextAccessor; + + private static readonly IReadOnlyDictionary NoCultureInfos = new Dictionary(); private PagePublishedContent(int id) { _id = id; } - public PagePublishedContent(IContent inner) + public PagePublishedContent(IContent inner, IPublishedVariationContextAccessor variationContextAccessor) { if (inner == null) throw new NullReferenceException("content"); _inner = inner; + _variationContextAccessor = variationContextAccessor; _id = _inner.Id; _key = _inner.Key; @@ -472,23 +477,31 @@ namespace umbraco get { return _inner.Name; } } - public IReadOnlyDictionary CultureNames + public PublishedCultureInfos GetCulture(string culture = ".") { - get - { - if (!_inner.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral)) - return null; - - if (_cultureNames == null) - { - var d = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var c in _inner.Names) - { - d[c.Key] = new PublishedCultureName(c.Value, c.Value.ToUrlSegment()); - } - _cultureNames = d; - } - return _cultureNames; + // handle context culture + if (culture == ".") + culture = _variationContextAccessor.Context.Culture; + + // no invariant culture infos + if (culture == null) return null; + + // get + return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos : null; + } + + public IReadOnlyDictionary Cultures + { + get + { + if (!_inner.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral)) // fixme CultureSegment? + return NoCultureInfos; + + if (_cultureInfos != null) + return _cultureInfos; + + return _cultureInfos = _inner.Names + .ToDictionary(x => x.Key, x => new PublishedCultureInfos(x.Key, x.Value, false, DateTime.MinValue)); // fixme values! } }