diff --git a/src/Umbraco.Core/DocumentExtensions.cs b/src/Umbraco.Core/DocumentExtensions.cs index af670fe03e..628acc7ff2 100644 --- a/src/Umbraco.Core/DocumentExtensions.cs +++ b/src/Umbraco.Core/DocumentExtensions.cs @@ -32,17 +32,6 @@ namespace Umbraco.Core return converted.Result; return ifCannotConvert; } - - /// - /// Returns the property based on the case insensitive match of the alias - /// - /// - /// - /// - public static IDocumentProperty GetProperty(this IDocument d, string alias) - { - return d.Properties.FirstOrDefault(p => p.Alias.InvariantEquals(alias)); - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IDocument.cs b/src/Umbraco.Core/Models/IDocument.cs index fae2866ff2..07d9b55317 100644 --- a/src/Umbraco.Core/Models/IDocument.cs +++ b/src/Umbraco.Core/Models/IDocument.cs @@ -31,6 +31,21 @@ namespace Umbraco.Core.Models Guid Version { get; } int Level { get; } Collection Properties { get; } - IEnumerable Children { get; } + IEnumerable Children { get; } + + /// + /// Returns a property on the object based on an alias + /// + /// + /// + /// + /// Although we do have a a property to return Properties of the object, in some cases a custom implementation may not know + /// about all properties until specifically asked for one by alias. + /// + /// This method is mostly used in places such as DynamicDocument when trying to resolve a property based on an alias. + /// In some cases Pulish Stores, a property value may exist in multiple places and we need to fallback to different cached locations + /// therefore sometimes the 'Properties' collection may not be sufficient. + /// + IDocumentProperty GetProperty(string alias); } } \ No newline at end of file diff --git a/src/Umbraco.Tests/ContentStores/PublishMediaStoreTests.cs b/src/Umbraco.Tests/ContentStores/PublishMediaStoreTests.cs index 8a988a7a35..960fde1891 100644 --- a/src/Umbraco.Tests/ContentStores/PublishMediaStoreTests.cs +++ b/src/Umbraco.Tests/ContentStores/PublishMediaStoreTests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Xml; using Examine; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web; @@ -189,9 +190,11 @@ namespace Umbraco.Tests //there is no parent a => null, //we're not going to test this so ignore - a => new List()), + a => new List(), + (dd, a) => dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(a))), //callback to get the children - d => children); + d => children, + (dd, a) => dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(a))); return dicDoc; } diff --git a/src/Umbraco.Tests/DynamicDocument/DocumentTests.cs b/src/Umbraco.Tests/DynamicDocument/DocumentTests.cs new file mode 100644 index 0000000000..a5c081f7e3 --- /dev/null +++ b/src/Umbraco.Tests/DynamicDocument/DocumentTests.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Dynamics; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; + +namespace Umbraco.Tests.DynamicDocument +{ + /// + /// Unit tests for IDocument and extensions + /// + [TestFixture] + public class DocumentTests : BaseRoutingTest + { + public override void Initialize() + { + base.Initialize(); + //need to specify a different callback for testing + Umbraco.Web.DocumentExtensions.GetPropertyAliasesAndNames = s => + { + var userFields = new Dictionary() + { + {"property1", "Property 1"}, + {"property2", "Property 2"} + }; + if (s == "Child") + { + userFields.Add("property4", "Property 4"); + } + else + { + userFields.Add("property3", "Property 3"); + } + + //ensure the standard fields are there + var allFields = new Dictionary() + { + {"Id", "Id"}, + {"NodeName", "NodeName"}, + {"NodeTypeAlias", "NodeTypeAlias"}, + {"CreateDate", "CreateDate"}, + {"UpdateDate", "UpdateDate"}, + {"CreatorName", "CreatorName"}, + {"WriterName", "WriterName"}, + {"Url", "Url"} + }; + foreach (var f in userFields.Where(f => !allFields.ContainsKey(f.Key))) + { + allFields.Add(f.Key, f.Value); + } + return allFields; + }; + var routingContext = GetRoutingContext("/test"); + + //set the UmbracoContext.Current since the extension methods rely on it + UmbracoContext.Current = routingContext.UmbracoContext; + } + + public override void TearDown() + { + base.TearDown(); + Umbraco.Web.DocumentExtensions.GetPropertyAliasesAndNames = null; + UmbracoContext.Current = null; + } + + [Test] + public void To_DataTable() + { + var doc = GetDocument(true, 1); + var dt = doc.ChildrenAsTable(); + + Assert.AreEqual(11, dt.Columns.Count); + Assert.AreEqual(3, dt.Rows.Count); + Assert.AreEqual("value4", dt.Rows[0]["Property 1"]); + Assert.AreEqual("value5", dt.Rows[0]["Property 2"]); + Assert.AreEqual("value6", dt.Rows[0]["Property 4"]); + Assert.AreEqual("value7", dt.Rows[1]["Property 1"]); + Assert.AreEqual("value8", dt.Rows[1]["Property 2"]); + Assert.AreEqual("value9", dt.Rows[1]["Property 4"]); + Assert.AreEqual("value10", dt.Rows[2]["Property 1"]); + Assert.AreEqual("value11", dt.Rows[2]["Property 2"]); + Assert.AreEqual("value12", dt.Rows[2]["Property 4"]); + } + + [Test] + public void To_DataTable_With_Filter() + { + var doc = GetDocument(true, 1); + //change a doc type alias + ((TestDocument) doc.Children.ElementAt(0)).DocumentTypeAlias = "DontMatch"; + + var dt = doc.ChildrenAsTable("Child"); + + Assert.AreEqual(11, dt.Columns.Count); + Assert.AreEqual(2, dt.Rows.Count); + Assert.AreEqual("value7", dt.Rows[0]["Property 1"]); + Assert.AreEqual("value8", dt.Rows[0]["Property 2"]); + Assert.AreEqual("value9", dt.Rows[0]["Property 4"]); + Assert.AreEqual("value10", dt.Rows[1]["Property 1"]); + Assert.AreEqual("value11", dt.Rows[1]["Property 2"]); + Assert.AreEqual("value12", dt.Rows[1]["Property 4"]); + } + + [Test] + public void To_DataTable_No_Rows() + { + var doc = GetDocument(false, 1); + var dt = doc.ChildrenAsTable(); + //will return an empty data table + Assert.AreEqual(0, dt.Columns.Count); + Assert.AreEqual(0, dt.Rows.Count); + } + + private IDocument GetDocument(bool createChildren, int indexVals) + { + var d = new TestDocument + { + CreateDate = DateTime.Now, + CreatorId = 1, + CreatorName = "Shannon", + DocumentTypeAlias = createChildren? "Parent" : "Child", + DocumentTypeId = 2, + Id = 3, + SortOrder = 4, + TemplateId = 5, + UpdateDate = DateTime.Now, + Path = "-1,3", + UrlName = "home-page", + Name = "Page" + Guid.NewGuid().ToString(), + Version = Guid.NewGuid(), + WriterId = 1, + WriterName = "Shannon", + Parent = null, + Level = 1, + Properties = new Collection( + new List() + { + new PropertyResult("property1", "value" + indexVals, Guid.NewGuid(), PropertyResultType.UserProperty), + new PropertyResult("property2", "value" + (indexVals + 1), Guid.NewGuid(), PropertyResultType.UserProperty) + }), + Children = new List() + }; + if (createChildren) + { + d.Children = new List() + { + GetDocument(false, indexVals + 3), + GetDocument(false, indexVals + 6), + GetDocument(false, indexVals + 9) + }; + } + if (!createChildren) + { + //create additional columns, used to test the different columns for child nodes + d.Properties.Add(new PropertyResult("property4", "value" + (indexVals + 2), Guid.NewGuid(), PropertyResultType.UserProperty)); + } + else + { + d.Properties.Add(new PropertyResult("property3", "value" + (indexVals + 2), Guid.NewGuid(), PropertyResultType.UserProperty)); + } + return d; + } + + + private class TestDocument : IDocument + { + public IDocument Parent { get; set; } + public int Id { get; set; } + public int TemplateId { get; set; } + public int SortOrder { get; set; } + public string Name { get; set; } + 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; } + public int CreatorId { get; set; } + public string Path { get; set; } + public DateTime CreateDate { get; set; } + public DateTime UpdateDate { get; set; } + public Guid Version { get; set; } + public int Level { get; set; } + public Collection Properties { get; set; } + public IEnumerable Children { get; set; } + public IDocumentProperty GetProperty(string alias) + { + return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/DynamicDocument/DynamicDocumentTests.cs b/src/Umbraco.Tests/DynamicDocument/DynamicDocumentTests.cs index 98c59dc1d3..b81372557e 100644 --- a/src/Umbraco.Tests/DynamicDocument/DynamicDocumentTests.cs +++ b/src/Umbraco.Tests/DynamicDocument/DynamicDocumentTests.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; using NUnit.Framework; using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.Routing; -using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Routing; using umbraco.BusinessLogic; @@ -17,188 +11,6 @@ using umbraco.cms.businesslogic.web; namespace Umbraco.Tests.DynamicDocument { - - /// - /// Unit tests for IDocument and extensions - /// - [TestFixture] - public class DocumentTests : BaseRoutingTest - { - public override void Initialize() - { - base.Initialize(); - //need to specify a different callback for testing - DocumentExtensions.GetPropertyAliasesAndNames = s => - { - var userFields = new Dictionary() - { - {"property1", "Property 1"}, - {"property2", "Property 2"} - }; - if (s == "Child") - { - userFields.Add("property4", "Property 4"); - } - else - { - userFields.Add("property3", "Property 3"); - } - - //ensure the standard fields are there - var allFields = new Dictionary() - { - {"Id", "Id"}, - {"NodeName", "NodeName"}, - {"NodeTypeAlias", "NodeTypeAlias"}, - {"CreateDate", "CreateDate"}, - {"UpdateDate", "UpdateDate"}, - {"CreatorName", "CreatorName"}, - {"WriterName", "WriterName"}, - {"Url", "Url"} - }; - foreach (var f in userFields.Where(f => !allFields.ContainsKey(f.Key))) - { - allFields.Add(f.Key, f.Value); - } - return allFields; - }; - var routingContext = GetRoutingContext("/test"); - - //set the UmbracoContext.Current since the extension methods rely on it - UmbracoContext.Current = routingContext.UmbracoContext; - } - - public override void TearDown() - { - base.TearDown(); - DocumentExtensions.GetPropertyAliasesAndNames = null; - UmbracoContext.Current = null; - } - - [Test] - public void To_DataTable() - { - var doc = GetDocument(true, 1); - var dt = doc.ChildrenAsTable(); - - Assert.AreEqual(11, dt.Columns.Count); - Assert.AreEqual(3, dt.Rows.Count); - Assert.AreEqual("value4", dt.Rows[0]["Property 1"]); - Assert.AreEqual("value5", dt.Rows[0]["Property 2"]); - Assert.AreEqual("value6", dt.Rows[0]["Property 4"]); - Assert.AreEqual("value7", dt.Rows[1]["Property 1"]); - Assert.AreEqual("value8", dt.Rows[1]["Property 2"]); - Assert.AreEqual("value9", dt.Rows[1]["Property 4"]); - Assert.AreEqual("value10", dt.Rows[2]["Property 1"]); - Assert.AreEqual("value11", dt.Rows[2]["Property 2"]); - Assert.AreEqual("value12", dt.Rows[2]["Property 4"]); - } - - [Test] - public void To_DataTable_With_Filter() - { - var doc = GetDocument(true, 1); - //change a doc type alias - ((TestDocument) doc.Children.ElementAt(0)).DocumentTypeAlias = "DontMatch"; - - var dt = doc.ChildrenAsTable("Child"); - - Assert.AreEqual(11, dt.Columns.Count); - Assert.AreEqual(2, dt.Rows.Count); - Assert.AreEqual("value7", dt.Rows[0]["Property 1"]); - Assert.AreEqual("value8", dt.Rows[0]["Property 2"]); - Assert.AreEqual("value9", dt.Rows[0]["Property 4"]); - Assert.AreEqual("value10", dt.Rows[1]["Property 1"]); - Assert.AreEqual("value11", dt.Rows[1]["Property 2"]); - Assert.AreEqual("value12", dt.Rows[1]["Property 4"]); - } - - [Test] - public void To_DataTable_No_Rows() - { - var doc = GetDocument(false, 1); - var dt = doc.ChildrenAsTable(); - //will return an empty data table - Assert.AreEqual(0, dt.Columns.Count); - Assert.AreEqual(0, dt.Rows.Count); - } - - private IDocument GetDocument(bool createChildren, int indexVals) - { - var d = new TestDocument - { - CreateDate = DateTime.Now, - CreatorId = 1, - CreatorName = "Shannon", - DocumentTypeAlias = createChildren? "Parent" : "Child", - DocumentTypeId = 2, - Id = 3, - SortOrder = 4, - TemplateId = 5, - UpdateDate = DateTime.Now, - Path = "-1,3", - UrlName = "home-page", - Name = "Page" + Guid.NewGuid().ToString(), - Version = Guid.NewGuid(), - WriterId = 1, - WriterName = "Shannon", - Parent = null, - Level = 1, - Properties = new Collection( - new List() - { - new PropertyResult("property1", "value" + indexVals, Guid.NewGuid(), PropertyResultType.UserProperty), - new PropertyResult("property2", "value" + (indexVals + 1), Guid.NewGuid(), PropertyResultType.UserProperty) - }), - Children = new List() - }; - if (createChildren) - { - d.Children = new List() - { - GetDocument(false, indexVals + 3), - GetDocument(false, indexVals + 6), - GetDocument(false, indexVals + 9) - }; - } - if (!createChildren) - { - //create additional columns, used to test the different columns for child nodes - d.Properties.Add(new PropertyResult("property4", "value" + (indexVals + 2), Guid.NewGuid(), PropertyResultType.UserProperty)); - } - else - { - d.Properties.Add(new PropertyResult("property3", "value" + (indexVals + 2), Guid.NewGuid(), PropertyResultType.UserProperty)); - } - return d; - } - - - private class TestDocument : IDocument - { - public IDocument Parent { get; set; } - public int Id { get; set; } - public int TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - 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; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - public Collection Properties { get; set; } - public IEnumerable Children { get; set; } - } - - } - [TestFixture] public class DynamicDocumentTests : DynamicDocumentTestsBase { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index eda0dcba03..f35cdee1e5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -54,6 +54,7 @@ + diff --git a/src/Umbraco.Web/DefaultPublishedMediaStore.cs b/src/Umbraco.Web/DefaultPublishedMediaStore.cs index fd4ce31389..05d797a6b7 100644 --- a/src/Umbraco.Web/DefaultPublishedMediaStore.cs +++ b/src/Umbraco.Web/DefaultPublishedMediaStore.cs @@ -9,6 +9,7 @@ using Umbraco.Core; using Umbraco.Core.Dynamics; using Umbraco.Core.Models; using umbraco; +using umbraco.cms.businesslogic; namespace Umbraco.Web { @@ -88,14 +89,18 @@ namespace Umbraco.Web { values.Add("level", values["__Path"].Split(',').Length.ToString()); } - + return new DictionaryDocument(values, - d => d.ParentId != -1 //parent should be null if -1 - ? GetUmbracoMedia(d.ParentId) - : null, - //callback to return the children of the current node - d => GetChildrenMedia(d.ParentId)); + d => d.ParentId != -1 //parent should be null if -1 + ? GetUmbracoMedia(d.ParentId) + : null, + //callback to return the children of the current node + d => GetChildrenMedia(d.ParentId), + GetProperty) + { + LoadedFromExamine = true + }; } internal IDocument ConvertFromXPathNavigator(XPathNavigator xpath) @@ -151,7 +156,52 @@ namespace Umbraco.Web ? GetUmbracoMedia(d.ParentId) : null, //callback to return the children of the current node based on the xml structure already found - d => GetChildrenMedia(d.ParentId, xpath)); + d => GetChildrenMedia(d.ParentId, xpath), + GetProperty); + } + + /// + /// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists + /// in the results, if it does not, then we'll have to revert to looking up in the db. + /// + /// + /// + /// + private IDocumentProperty GetProperty(DictionaryDocument dd, string alias) + { + if (dd.LoadedFromExamine) + { + //if this is from Examine, lets check if the alias does not exist on the document + if (dd.Properties.All(x => x.Alias != alias)) + { + //ok it doesn't exist, we might assume now that Examine didn't index this property because the index is not set up correctly + //so before we go loading this from the database, we can check if the alias exists on the content type at all, this information + //is cached so will be quicker to look up. + if (dd.Properties.Any(x => x.Alias == "__NodeTypeAlias")) + { + var aliasesAndNames = ContentType.GetAliasesAndNames(dd.Properties.First(x => x.Alias.InvariantEquals("__NodeTypeAlias")).Value.ToString()); + if (aliasesAndNames != null) + { + if (!aliasesAndNames.ContainsKey(alias)) + { + //Ok, now we know it doesn't exist on this content type anyways + return null; + } + } + } + + //if we've made it here, that means it does exist on the content type but not in examine, we'll need to query the db :( + var media = global::umbraco.library.GetMedia(dd.Id, true); + if (media != null && media.Current != null) + { + media.MoveNext(); + var mediaDoc = ConvertFromXPathNavigator(media.Current); + return mediaDoc.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + } + } + + return dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } /// @@ -234,13 +284,18 @@ namespace Umbraco.Web public DictionaryDocument( IDictionary valueDictionary, Func getParent, - Func> getChildren) + Func> getChildren, + Func getProperty) { if (valueDictionary == null) throw new ArgumentNullException("valueDictionary"); if (getParent == null) throw new ArgumentNullException("getParent"); - + if (getProperty == null) throw new ArgumentNullException("getProperty"); + _getParent = getParent; _getChildren = getChildren; + _getProperty = getProperty; + + LoadedFromExamine = false; //default to false ValidateAndSetProperty(valueDictionary, val => Id = int.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! ValidateAndSetProperty(valueDictionary, val => TemplateId = int.Parse(val), "template", "templateId"); @@ -279,8 +334,14 @@ namespace Umbraco.Web } } + /// + /// Flag to get/set if this was laoded from examine cache + /// + internal bool LoadedFromExamine { get; set; } + private readonly Func _getParent; private readonly Func> _getChildren; + private readonly Func _getProperty; public IDocument Parent { @@ -310,6 +371,11 @@ namespace Umbraco.Web get { return _getChildren(this); } } + public IDocumentProperty GetProperty(string alias) + { + return _getProperty(this, alias); + } + private readonly List _keysAdded = new List(); private void ValidateAndSetProperty(IDictionary valueDictionary, Action setProperty, params string[] potentialKeys) { diff --git a/src/Umbraco.Web/Models/XmlDocument.cs b/src/Umbraco.Web/Models/XmlDocument.cs index f51e8dc973..cf8aaaf32e 100644 --- a/src/Umbraco.Web/Models/XmlDocument.cs +++ b/src/Umbraco.Web/Models/XmlDocument.cs @@ -75,6 +75,11 @@ namespace Umbraco.Web.Models } } + public IDocumentProperty GetProperty(string alias) + { + return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + public IDocument Parent { get