From 365addedf4fc8996df38f149be9bac11e83e6df6 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 27 Feb 2013 17:01:53 -0100 Subject: [PATCH 01/15] Fixes deleting tabs and having the property types move to Generic Properties --- .../Persistence/Repositories/ContentRepository.cs | 11 +++++++++++ .../Repositories/ContentTypeBaseRepository.cs | 10 ++++++++++ .../umbraco/controls/ContentTypeControlNew.ascx.cs | 12 ++++++------ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 152333c1c0..825898008e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -452,6 +452,17 @@ namespace Umbraco.Core.Persistence.Repositories var propertyDataDtos = Database.Fetch(sql); var propertyFactory = new PropertyFactory(contentType, versionId, id); var properties = propertyFactory.BuildEntity(propertyDataDtos); + + var newProperties = properties.Where(x => x.HasIdentity == false); + foreach (var property in newProperties) + { + var propertyDataDto = new PropertyDataDto{ NodeId = id, PropertyTypeId = property.PropertyTypeId, VersionId = versionId }; + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + + property.Version = versionId; + property.Id = primaryKey; + } + return new PropertyCollection(properties); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 69cfd0ce25..d1d1ee55d8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -243,6 +243,8 @@ namespace Umbraco.Core.Persistence.Repositories var tabs = dbPropertyGroups.Except(entityPropertyGroups); foreach (var tab in tabs) { + Database.Update("SET propertyTypeGroupId = NULL WHERE propertyTypeGroupId = @PropertyGroupId", + new {PropertyGroupId = tab.Item1}); Database.Update("SET parentGroupId = NULL WHERE parentGroupId = @TabId", new {TabId = tab.Item1}); Database.Delete("WHERE contenttypeNodeId = @Id AND text = @Name", @@ -259,6 +261,14 @@ namespace Umbraco.Core.Persistence.Repositories : Convert.ToInt32(Database.Insert(tabDto)); if (propertyGroup.HasIdentity == false) propertyGroup.Id = groupPrimaryKey; //Set Id on new PropertyGroup + + //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group + //unless the PropertyGroupId has already been changed. + foreach (var propertyType in propertyGroup.PropertyTypes) + { + if (propertyType.IsPropertyDirty("PropertyGroupId") == false) + propertyType.PropertyGroupId = propertyGroup.Id; + } } //Run through all PropertyTypes to insert or update entries diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index bf482ea2f5..0b0de2c51b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -521,9 +521,9 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); foreach (cms.businesslogic.propertytype.PropertyType pt in _contentType.PropertyTypes) { //This use to be: - //if (pt.ContentTypeId == cType.Id && !inTab.ContainsKey(pt.Id.ToString()) + if (pt.ContentTypeId == _contentType.Id && !inTab.ContainsKey(pt.Id.ToString())) //But seriously, if it's not on a tab the tabId is 0, it's a lot easier to read IMO - if (pt.ContentTypeId == _contentType.Id && pt.TabId == 0) + //if (pt.ContentTypeId == _contentType.Id && pt.TabId == 0) { var gpw = new GenericPropertyWrapper(); @@ -1038,10 +1038,10 @@ Umbraco.Controls.TabView.onActiveTabChange(function(tabviewid, tabid, tabs) { _contentType.Save(); } } - else - { - _contentType.DeleteVirtualTab(propertyGroupId); - } + + _contentType.DeleteVirtualTab(propertyGroupId); + + LoadContentType(); var ea = new SaveClickEventArgs(ui.Text("contentTypeTabDeleted")); ea.IconType = BasePage.speechBubbleIcon.success; From 836a99128f336aa1bdd52be2bc1beed57af1e04f Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 28 Feb 2013 09:07:15 -0100 Subject: [PATCH 02/15] Fixes moving PropertyTypes between Tabs/PropertyGroups from inherited doc types. Adds method to move a PropertyType to another PropertyGroup. --- src/Umbraco.Core/Models/ContentTypeBase.cs | 28 +++++++++++ src/Umbraco.Core/Models/IContentTypeBase.cs | 8 ++++ .../controls/ContentTypeControlNew.ascx.cs | 47 +++++++++++-------- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index d255cc91a8..0877354bba 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -362,6 +362,34 @@ namespace Umbraco.Core.Models return false; } + /// + /// Moves a PropertyType to a specified PropertyGroup + /// + /// Alias of the PropertyType to move + /// Name of the PropertyGroup to move the PropertyType to + /// + public bool MovePropertyType(string propertyTypeAlias, string propertyGroupName) + { + if (PropertyTypes.Any(x => x.Alias == propertyTypeAlias) == false || PropertyGroups.Any(x => x.Name == propertyGroupName) == false) + return false; + + var propertyType = PropertyTypes.First(x => x.Alias == propertyTypeAlias); + //The PropertyType already belongs to a PropertyGroup, so we have to remove the PropertyType from that group + if (PropertyGroups.Any(x => x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias))) + { + var oldPropertyGroup = PropertyGroups.First(x => x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias)); + oldPropertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias); + } + + propertyType.PropertyGroupId = default(int); + propertyType.ResetDirtyProperties(); + + var propertyGroup = PropertyGroups.First(x => x.Name == propertyGroupName); + propertyGroup.PropertyTypes.Add(propertyType); + + return true; + } + /// /// Removes a PropertyType from the current ContentType /// diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 11a3b8aee6..b0a671eff8 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -99,5 +99,13 @@ namespace Umbraco.Core.Models /// Name of the PropertyGroup to add /// Returns True if a PropertyGroup with the passed in name was added, otherwise False bool AddPropertyGroup(string groupName); + + /// + /// Moves a PropertyType to a specified PropertyGroup + /// + /// Alias of the PropertyType to move + /// Name of the PropertyGroup to move the PropertyType to + /// + bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 0b0de2c51b..5a0f75c688 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -438,8 +438,6 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); : propertyGroup.GetPropertyTypes(); var propertyGroupId = tab.Id; - if (propertyGroup != null && propertyGroup.ParentId > 0) - propertyGroupId = propertyGroup.Id; if (propertyTypes.Any(x => x.ContentTypeId == _contentType.Id)) { @@ -521,7 +519,7 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); foreach (cms.businesslogic.propertytype.PropertyType pt in _contentType.PropertyTypes) { //This use to be: - if (pt.ContentTypeId == _contentType.Id && !inTab.ContainsKey(pt.Id.ToString())) + if (pt.ContentTypeId == _contentType.Id && inTab.ContainsKey(pt.Id.ToString()) == false) //But seriously, if it's not on a tab the tabId is 0, it's a lot easier to read IMO //if (pt.ContentTypeId == _contentType.Id && pt.TabId == 0) { @@ -638,7 +636,8 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); { if(gpw.PropertyType == null) continue; - var propertyType = contentTypeItem.PropertyTypes.FirstOrDefault(x => x.Alias == gpw.PropertyType.Alias); + if(contentTypeItem.PropertyTypes == null || contentTypeItem.PropertyTypes.Any(x => x.Alias == gpw.PropertyType.Alias) == false) continue; + var propertyType = contentTypeItem.PropertyTypes.First(x => x.Alias == gpw.PropertyType.Alias); if (propertyType == null) continue; var dataTypeDefinition = ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById(gpw.GenricPropertyControl.Type); @@ -669,19 +668,23 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); } else { - if ( - contentTypeItem.CompositionPropertyGroups.Any( - x => x.ParentId == gpw.GenricPropertyControl.Tab)) - { - var propertyGroups = contentTypeItem.CompositionPropertyGroups.Where(x => x.ParentId == gpw.GenricPropertyControl.Tab); - var propertyGroup = propertyGroups.First(); - propertyType.PropertyGroupId = propertyGroup.Id; - } - else - { - var propertyGroup = contentTypeItem.CompositionPropertyGroups.First(x => x.Id == gpw.GenricPropertyControl.Tab); - contentTypeItem.AddPropertyGroup(propertyGroup.Name); - } + var propertyGroup = contentTypeItem.CompositionPropertyGroups.First(x => x.Id == gpw.GenricPropertyControl.Tab); + contentTypeItem.AddPropertyGroup(propertyGroup.Name); + contentTypeItem.MovePropertyType(propertyType.Alias, propertyGroup.Name); + + //if ( + // contentTypeItem.CompositionPropertyGroups.Any( + // x => x.ParentId == gpw.GenricPropertyControl.Tab)) + //{ + // var propertyGroups = contentTypeItem.CompositionPropertyGroups.Where(x => x.ParentId == gpw.GenricPropertyControl.Tab); + // var propertyGroup = propertyGroups.First(); + // propertyType.PropertyGroupId = propertyGroup.Id; + //} + //else + //{ + // var propertyGroup = contentTypeItem.CompositionPropertyGroups.First(x => x.Id == gpw.GenricPropertyControl.Tab); + // contentTypeItem.AddPropertyGroup(propertyGroup.Name); + //} } } @@ -690,6 +693,7 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); cms.businesslogic.cache.Cache.ClearCacheItem("UmbracoPropertyTypeCache" + gpw.PropertyType.Id); // clear cache in ContentType cms.businesslogic.cache.Cache.ClearCacheItem("ContentType_PropertyTypes_Content:" + contentTypeItem.Id); + _contentType.ClearVirtualTabs(); } //Update the SortOrder of the PropertyTypes @@ -709,9 +713,12 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); string propSO = tempSO[i].Substring(propSOPosition); int propertyTypeId = int.Parse(propSO); - var propertyType = contentTypeItem.PropertyTypes.FirstOrDefault(x => x.Id == propertyTypeId); - if (propertyType == null) continue; - propertyType.SortOrder = i; + if (contentTypeItem.PropertyTypes != null && + contentTypeItem.PropertyTypes.Any(x => x.Id == propertyTypeId)) + { + var propertyType = contentTypeItem.PropertyTypes.First(x => x.Id == propertyTypeId); + propertyType.SortOrder = i; + } } } } From ecae9c47b796f645a422f4d862acf0bfd727fa73 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 28 Feb 2013 09:37:33 -0100 Subject: [PATCH 03/15] Fixes U4-1798 by adding the current user's id when saving Media, MediaTypes and DocumentTypes. --- src/umbraco.cms/businesslogic/media/Media.cs | 4 +++- src/umbraco.cms/businesslogic/media/MediaType.cs | 5 ++++- src/umbraco.cms/businesslogic/web/DocumentType.cs | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/umbraco.cms/businesslogic/media/Media.cs b/src/umbraco.cms/businesslogic/media/Media.cs index 3cfc19bc1e..17594788be 100644 --- a/src/umbraco.cms/businesslogic/media/Media.cs +++ b/src/umbraco.cms/businesslogic/media/Media.cs @@ -251,7 +251,9 @@ namespace umbraco.cms.businesslogic.media if (!e.Cancel) { - ApplicationContext.Current.Services.MediaService.Save(MediaItem); + var current = User.GetCurrent(); + int userId = current == null ? 0 : current.Id; + ApplicationContext.Current.Services.MediaService.Save(MediaItem, userId); base.Save(); diff --git a/src/umbraco.cms/businesslogic/media/MediaType.cs b/src/umbraco.cms/businesslogic/media/MediaType.cs index de0449bd52..46c24305c4 100644 --- a/src/umbraco.cms/businesslogic/media/MediaType.cs +++ b/src/umbraco.cms/businesslogic/media/MediaType.cs @@ -4,6 +4,7 @@ using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Caching; using System.Linq; +using umbraco.BusinessLogic; namespace umbraco.cms.businesslogic.media { @@ -128,7 +129,9 @@ namespace umbraco.cms.businesslogic.media _mediaType.AddContentType(contentType); } - ApplicationContext.Current.Services.ContentTypeService.Save(_mediaType); + var current = User.GetCurrent(); + int userId = current == null ? 0 : current.Id; + ApplicationContext.Current.Services.ContentTypeService.Save(_mediaType, userId); //Ensure that MediaTypes are reloaded from db by clearing cache InMemoryCacheProvider.Current.Clear(); diff --git a/src/umbraco.cms/businesslogic/web/DocumentType.cs b/src/umbraco.cms/businesslogic/web/DocumentType.cs index c6f9c28644..93f3b04aad 100644 --- a/src/umbraco.cms/businesslogic/web/DocumentType.cs +++ b/src/umbraco.cms/businesslogic/web/DocumentType.cs @@ -453,7 +453,9 @@ namespace umbraco.cms.businesslogic.web _contentType.AddContentType(contentType); } - ApplicationContext.Current.Services.ContentTypeService.Save(_contentType); + var current = User.GetCurrent(); + int userId = current == null ? 0 : current.Id; + ApplicationContext.Current.Services.ContentTypeService.Save(_contentType, userId); //Ensure that DocumentTypes are reloaded from db by clearing cache. //NOTE Would be nice if we could clear cache by type instead of emptying the entire cache. From 916e442ee498ede35549e1a84646ca57caf8a24f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 28 Feb 2013 09:53:57 -0100 Subject: [PATCH 04/15] Fix errors in log caused by no prevalues beind selected. --- .../DefaultDataKeyValue.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/umbraco.editorControls/DefaultDataKeyValue.cs b/src/umbraco.editorControls/DefaultDataKeyValue.cs index d8bcacf26f..ea67ac299a 100644 --- a/src/umbraco.editorControls/DefaultDataKeyValue.cs +++ b/src/umbraco.editorControls/DefaultDataKeyValue.cs @@ -20,17 +20,22 @@ namespace umbraco.editorControls { // Get the value from string v = ""; - try + try { - IRecordsReader dr = SqlHelper.ExecuteReader("Select [value] from cmsDataTypeprevalues where id in (" + SqlHelper.EscapeString(Value.ToString()) + ")"); + // Don't query if there's nothing to query for.. + if (string.IsNullOrWhiteSpace(Value.ToString()) == false) + { + IRecordsReader dr = SqlHelper.ExecuteReader("Select [value] from cmsDataTypeprevalues where id in (@id)", SqlHelper.CreateParameter("id", Value.ToString())); - while (dr.Read()) { - if (v.Length == 0) - v += dr.GetString("value"); - else - v += "," + dr.GetString("value"); - } - dr.Close(); + while (dr.Read()) + { + if (v.Length == 0) + v += dr.GetString("value"); + else + v += "," + dr.GetString("value"); + } + dr.Close(); + } } catch {} return d.CreateCDataSection(v); From b017e8690acf5025a2182fd05302e8d04edfcf67 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 28 Feb 2013 09:53:57 -0100 Subject: [PATCH 05/15] Fix errors in log caused by no prevalues beind selected. --- src/umbraco.editorControls/DefaultDataKeyValue.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/umbraco.editorControls/DefaultDataKeyValue.cs b/src/umbraco.editorControls/DefaultDataKeyValue.cs index d8bcacf26f..03b5508369 100644 --- a/src/umbraco.editorControls/DefaultDataKeyValue.cs +++ b/src/umbraco.editorControls/DefaultDataKeyValue.cs @@ -22,9 +22,13 @@ namespace umbraco.editorControls string v = ""; try { - IRecordsReader dr = SqlHelper.ExecuteReader("Select [value] from cmsDataTypeprevalues where id in (" + SqlHelper.EscapeString(Value.ToString()) + ")"); + // Don't query if there's nothing to query for.. + if (string.IsNullOrWhiteSpace(Value.ToString()) == false) + { + IRecordsReader dr = SqlHelper.ExecuteReader("Select [value] from cmsDataTypeprevalues where id in (@id)", SqlHelper.CreateParameter("id", Value.ToString())); - while (dr.Read()) { + while (dr.Read()) + { if (v.Length == 0) v += dr.GetString("value"); else @@ -32,6 +36,7 @@ namespace umbraco.editorControls } dr.Close(); } + } catch {} return d.CreateCDataSection(v); } From c38c18fed874758bd3d760d828396f390fc20d25 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 28 Feb 2013 14:54:42 -0100 Subject: [PATCH 06/15] Adding migration for updating PropertyGroups and PropertyTypes --- src/Umbraco.Core/CoreBootManager.cs | 4 +++- src/Umbraco.Core/Models/ContentTypeCompositionBase.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 534c523e15..a26a5b7b6d 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix; +using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Publishing; @@ -204,7 +205,8 @@ namespace Umbraco.Core typeof (UpdateCmsContentTypeAllowedContentTypeTable), typeof (UpdateCmsContentTypeTable), typeof (UpdateCmsContentVersionTable), - typeof (UpdateCmsPropertyTypeGroupTable) + typeof (UpdateCmsPropertyTypeGroupTable), + typeof (UpdatePropertyTypesAndGroups) }); PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index d7b91c038b..72c0a6f72a 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -178,7 +178,7 @@ namespace Umbraco.Core.Models if (CompositionPropertyGroups.Any(x => x.Name == propertyGroupName)) { var parentPropertyGroup = CompositionPropertyGroups.First(x => x.Name == propertyGroupName && x.ParentId.HasValue == false); - propertyGroup.SortOrder = parentPropertyGroup.SortOrder + 1; + propertyGroup.SortOrder = parentPropertyGroup.SortOrder; propertyGroup.ParentId = parentPropertyGroup.Id; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2af4f04820..d3c92bb8b5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -382,6 +382,7 @@ + From 6eeae52333f60ccce8d694036c33c05f4ab726be Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 28 Feb 2013 15:01:50 -0100 Subject: [PATCH 07/15] Adding the actually migration class --- .../UpdatePropertyTypesAndGroups.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs new file mode 100644 index 0000000000..9904dda19b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne +{ + [Migration("6.0.1", 0, GlobalSettings.UmbracoMigrationName)] + public class UpdatePropertyTypesAndGroups : MigrationBase + { + public override void Up() + { + if (base.Context != null && base.Context.Database != null) + { + var propertyTypes = base.Context.Database.Fetch("WHERE propertyTypeGroupId > 0"); + var propertyGroups = base.Context.Database.Fetch("WHERE id > 0"); + + foreach (var propertyType in propertyTypes) + { + var parentPropertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyTypeGroupId); + if (parentPropertyTypeGroup != null) + { + if(parentPropertyTypeGroup.ContentTypeNodeId == propertyType.ContentTypeId) continue; + + var propertyGroup = new PropertyTypeGroupDto + { + ContentTypeNodeId = propertyType.ContentTypeId, + ParentGroupId = parentPropertyTypeGroup.Id, + Text = parentPropertyTypeGroup.Text, + SortOrder = parentPropertyTypeGroup.SortOrder + }; + + int id = Convert.ToInt16(base.Context.Database.Insert(propertyGroup)); + propertyType.PropertyTypeGroupId = id; + base.Context.Database.Update(propertyType); + } + } + } + } + + public override void Down() + { + + } + } +} \ No newline at end of file From c250fa560e9afa1a6fa2015edc6f7d821c4121db Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 28 Feb 2013 15:08:30 -0100 Subject: [PATCH 08/15] Restrict the NuGet .Core dependency to the same version. --- build/NuSpecs/UmbracoCms.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index a8cdff8863..6d0cae608a 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -15,7 +15,7 @@ en-US umbraco - + From e57e2f5b5f504d3706af7bccfc239d045c79b50a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 28 Feb 2013 15:38:07 -0100 Subject: [PATCH 09/15] Migrations should be sorted by version number as well --- src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index 2eb5896374..ad5fcdebc5 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -105,7 +105,7 @@ namespace Umbraco.Core.Persistence.Migrations migrationAttribute.TargetVersion > _configuredVersion && migrationAttribute.TargetVersion <= _targetVersion && migrationAttribute.ProductName == _productName - orderby migrationAttribute.SortOrder ascending + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending select migration); return migrations; } @@ -119,7 +119,8 @@ namespace Umbraco.Core.Persistence.Migrations migrationAttribute.TargetVersion > _configuredVersion && migrationAttribute.TargetVersion <= _targetVersion && migrationAttribute.ProductName == _productName - orderby migrationAttribute.SortOrder descending + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending + select migration); return migrations; } From 92ef4d805552bda8891422bff8dcb11616903f70 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Thu, 28 Feb 2013 16:00:35 -0100 Subject: [PATCH 10/15] Adds migration execute method for code, so it'll run in the process method of the migration runner. --- .../Syntax/Execute/ExecuteBuilder.cs | 12 ++++++++- .../ExecuteCodeStatementExpression.cs | 26 ++++++++++++++++++ .../Syntax/Execute/IExecuteBuilder.cs | 5 +++- .../UpdatePropertyTypesAndGroups.cs | 27 ++++++++++++------- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 5 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs index 7a70b0991e..e537253664 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions; +using System; +using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions; namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute { @@ -21,5 +22,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute _databaseProviders) {SqlStatement = sqlStatement}; _context.Expressions.Add(expression); } + + public void Code(Func codeStatement) + { + var expression = _databaseProviders == null + ? new ExecuteCodeStatementExpression { CodeStatement = codeStatement } + : new ExecuteCodeStatementExpression(_context.CurrentDatabaseProvider, + _databaseProviders) { CodeStatement = codeStatement }; + _context.Expressions.Add(expression); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs new file mode 100644 index 0000000000..0f88fe2ee6 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs @@ -0,0 +1,26 @@ +using System; + +namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions +{ + public class ExecuteCodeStatementExpression : MigrationExpressionBase + { + public ExecuteCodeStatementExpression() + { + } + + public ExecuteCodeStatementExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders) + : base(current, databaseProviders) + { + } + + public virtual Func CodeStatement { get; set; } + + public override string Process(Database database) + { + if(CodeStatement != null) + return CodeStatement(database); + + return base.Process(database); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs index 47a25cf0e3..46717e0a3f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs @@ -1,7 +1,10 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute +using System; + +namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute { public interface IExecuteBuilder : IFluentSyntax { void Sql(string sqlStatement); + void Code(Func codeStatement); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs index 9904dda19b..e599ebe1be 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs @@ -10,17 +10,27 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne { public override void Up() { - if (base.Context != null && base.Context.Database != null) + Execute.Code(UpdatePropertyTypesAndGroupsDo); + } + + public override void Down() + { + + } + + public static string UpdatePropertyTypesAndGroupsDo(Database database) + { + if (database != null) { - var propertyTypes = base.Context.Database.Fetch("WHERE propertyTypeGroupId > 0"); - var propertyGroups = base.Context.Database.Fetch("WHERE id > 0"); + var propertyTypes = database.Fetch("WHERE propertyTypeGroupId > 0"); + var propertyGroups = database.Fetch("WHERE id > 0"); foreach (var propertyType in propertyTypes) { var parentPropertyTypeGroup = propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyTypeGroupId); if (parentPropertyTypeGroup != null) { - if(parentPropertyTypeGroup.ContentTypeNodeId == propertyType.ContentTypeId) continue; + if (parentPropertyTypeGroup.ContentTypeNodeId == propertyType.ContentTypeId) continue; var propertyGroup = new PropertyTypeGroupDto { @@ -30,17 +40,14 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne SortOrder = parentPropertyTypeGroup.SortOrder }; - int id = Convert.ToInt16(base.Context.Database.Insert(propertyGroup)); + int id = Convert.ToInt16(database.Insert(propertyGroup)); propertyType.PropertyTypeGroupId = id; - base.Context.Database.Update(propertyType); + database.Update(propertyType); } } } - } - public override void Down() - { - + return string.Empty; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d3c92bb8b5..68c718fd71 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -348,6 +348,7 @@ + From 6d5f7c4293ee7b92bd71ca3c19cd7dab5b1becdd Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 28 Feb 2013 16:23:43 -0100 Subject: [PATCH 11/15] Checking the filepath correctly this time --- .../umbraco.presentation/install/steps/database.ascx.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.cs index 008ceb4175..ed18bcb9c0 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.cs @@ -81,7 +81,11 @@ namespace umbraco.presentation.install.steps var sqlCeDatabaseExists = false; if (dbIsSqlCe) - sqlCeDatabaseExists = File.Exists(databaseSettings.ConnectionString.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString())); + { + var datasource = databaseSettings.ConnectionString.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString() + Path.DirectorySeparatorChar); + var filePath = datasource.Replace("Datasource=", string.Empty); + sqlCeDatabaseExists = File.Exists(filePath); + } // Either the connection details are not fully specified or it's a SQL CE database that doesn't exist yet if (databaseSettings == null From a3b03988b301b0b7277ff4d98f581b0acb299dbc Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 1 Mar 2013 06:55:54 -0100 Subject: [PATCH 12/15] Created branch 6.0.2 From ee2943b9fb425ce8ec48f3a761a87face17c23fa Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 1 Mar 2013 06:56:34 -0100 Subject: [PATCH 13/15] Created branch 4.11.6 From 4882df43bd5820ee7eb3aab33a0c048744f57ee8 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sat, 2 Mar 2013 01:50:53 +0600 Subject: [PATCH 14/15] Back merged from 6.1 the changes required for #U4-1822 and #U4-1797 --- src/Umbraco.Core/Dynamics/DynamicXml.cs | 6 +- src/Umbraco.Core/Dynamics/ExtensionMethods.cs | 59 +- .../DynamicDocumentTestsBase.cs | 49 +- .../PublishedContent/PublishedContentTests.cs | 146 ++++- .../StronglyTypedQueryTests.cs | 43 +- src/Umbraco.Web/Dynamics/Grouping.cs | 2 +- .../Models/DynamicPublishedContent.cs | 44 +- .../Models/DynamicPublishedContentList.cs | 50 +- .../Models/IOwnerCollectionAware.cs | 12 + src/Umbraco.Web/Models/XmlPublishedContent.cs | 27 +- src/Umbraco.Web/PublishedContentExtensions.cs | 533 +++++++++++------- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../RazorDynamicNode/DynamicNode.cs | 10 +- .../RazorDynamicNode/ExtensionMethods.cs | 6 +- 14 files changed, 719 insertions(+), 269 deletions(-) create mode 100644 src/Umbraco.Web/Models/IOwnerCollectionAware.cs diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs index 1c9bfdd5b5..f75ed76de5 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXml.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs @@ -561,7 +561,8 @@ namespace Umbraco.Core.Dynamics } public IEnumerable Descendants(Func func) { - var flattenedNodes = this.BaseElement.Elements().Map(func, (XElement n) => { return n.Elements(); }); + //var flattenedNodes = this.BaseElement.Elements().Map(func, n => n.Elements()); + var flattenedNodes = this.BaseElement.Elements().SelectMany(n => n.Elements()).Where(func); return flattenedNodes.ToList().ConvertAll(n => new DynamicXml(n)); } public IEnumerable DescendantsOrSelf() @@ -570,7 +571,8 @@ namespace Umbraco.Core.Dynamics } public IEnumerable DescendantsOrSelf(Func func) { - var flattenedNodes = this.BaseElement.Elements().Map(func, (XElement n) => { return n.Elements(); }); + //var flattenedNodes = this.BaseElement.Elements().Map(func, n => n.Elements()); + var flattenedNodes = this.BaseElement.Elements().SelectMany(n => n.Elements()).Where(func); var list = new List(); list.Add(this); list.AddRange(flattenedNodes.ToList().ConvertAll(n => new DynamicXml(n))); diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs index f36d4f255d..0637864ceb 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs @@ -5,36 +5,35 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Dynamics { + [Obsolete("This class should not be used, it is just referenced by already obsoleted code and will be removed in the future")] internal static class ExtensionMethods { - public static IEnumerable Map( - this IEnumerable source, - Func selectorFunction, - Func> getChildrenFunction) - { - if (!source.Any()) - { - return source; - } - // Add what we have to the stack - var flattenedList = source.Where(selectorFunction); - // Go through the input enumerable looking for children, - // and add those if we have them - foreach (TSource element in source) - { - var secondInner = getChildrenFunction(element); - if (secondInner.Any()) - { - secondInner = secondInner.Map(selectorFunction, getChildrenFunction); - } - flattenedList = flattenedList.Concat(secondInner); - } - return flattenedList; - } - - - + //public static IEnumerable Map( + // this IEnumerable source, + // Func selectorFunction, + // Func> getChildrenFunction) + //{ + // if (!source.Any()) + // { + // return source; + // } + // // Add what we have to the stack + // var flattenedList = source.Where(selectorFunction); + // // Go through the input enumerable looking for children, + // // and add those if we have them + // foreach (TSource element in source) + // { + // var secondInner = getChildrenFunction(element); + // if (secondInner.Any()) + // { + // secondInner = secondInner.Map(selectorFunction, getChildrenFunction); + // } + // flattenedList = flattenedList.Concat(secondInner); + // } + // return flattenedList; + //} + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, IEnumerable needles) { if (haystack == null) throw new ArgumentNullException("haystack"); @@ -44,6 +43,7 @@ namespace Umbraco.Core.Dynamics } return false; } + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, params string[] needles) { if (haystack == null) throw new ArgumentNullException("haystack"); @@ -53,6 +53,7 @@ namespace Umbraco.Core.Dynamics } return false; } + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, StringComparison comparison, IEnumerable needles) { if (haystack == null) throw new ArgumentNullException("haystack"); @@ -62,6 +63,7 @@ namespace Umbraco.Core.Dynamics } return false; } + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, StringComparison comparison, params string[] needles) { if (haystack == null) throw new ArgumentNullException("haystack"); @@ -71,12 +73,13 @@ namespace Umbraco.Core.Dynamics } return false; } + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsInsensitive(this string haystack, string needle) { if (haystack == null) throw new ArgumentNullException("haystack"); return haystack.IndexOf(needle, StringComparison.CurrentCultureIgnoreCase) >= 0; } - + [Obsolete("This method should not be used and will be removed in the future")] public static bool HasValue(this string s) { return !string.IsNullOrWhiteSpace(s); diff --git a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs index ac4b322aac..86a47aea86 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs @@ -46,17 +46,18 @@ namespace Umbraco.Tests.PublishedContent This is some content]]> - + - - + + + + + 1 - - @@ -77,6 +78,28 @@ namespace Umbraco.Tests.PublishedContent /// protected abstract dynamic GetDynamicNode(int id); + /// + /// Tests the IsLast method with the result set from a Where statement + /// + [Test] + public void Is_Last_From_Where_Filter() + { + var doc = GetDynamicNode(1173); + + foreach (var d in doc.Children.Where("Visible")) + { + if (d.Id != 1178) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + + } + [Test] public void Single() { @@ -222,11 +245,11 @@ namespace Umbraco.Tests.PublishedContent var doc = GetDynamicNode(1173); Assert.AreEqual(0, doc.Index()); doc = GetDynamicNode(1176); - Assert.AreEqual(1, doc.Index()); - doc = GetDynamicNode(1177); - Assert.AreEqual(2, doc.Index()); - doc = GetDynamicNode(1178); Assert.AreEqual(3, doc.Index()); + doc = GetDynamicNode(1177); + Assert.AreEqual(1, doc.Index()); + doc = GetDynamicNode(1178); + Assert.AreEqual(2, doc.Index()); } [Test] @@ -372,7 +395,7 @@ namespace Umbraco.Tests.PublishedContent var casted = (IEnumerable)skip; Assert.AreEqual(2, casted.Count()); - Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1177, 1178 })); + Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1178, 1176 })); } @@ -397,7 +420,7 @@ namespace Umbraco.Tests.PublishedContent var casted = (IEnumerable)take; Assert.AreEqual(2, casted.Count()); - Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1176 })); + Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1177 })); } [Test] @@ -615,7 +638,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Next_Without_Sibling() { - var asDynamic = GetDynamicNode(1178); + var asDynamic = GetDynamicNode(1176); Assert.IsNull(asDynamic.Next()); } @@ -637,7 +660,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); - Assert.AreEqual((int)1174, (int)result.Id); + Assert.AreEqual((int)1178, (int)result.Id); } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index cb7bb0d11f..966d233bbc 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web; using NUnit.Framework; @@ -35,23 +36,24 @@ namespace Umbraco.Tests.PublishedContent 1 - + This is some content]]> - + - - + + + + + 1 - - - + - + @@ -79,6 +81,122 @@ namespace Umbraco.Tests.PublishedContent return doc; } + [Test] + public void Is_Last_From_Where_Filter_Dynamic_Linq() + { + var doc = GetNode(1173); + + foreach (var d in doc.Children.Where("Visible")) + { + if (d.Id != 1178) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Is_Last_From_Where_Filter() + { + var doc = GetNode(1173); + + foreach (var d in doc.Children.Where(x => x.IsVisible())) + { + if (d.Id != 1178) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Is_Last_From_Take() + { + var doc = GetNode(1173); + + foreach (var d in doc.Children.Take(3)) + { + if (d.Id != 1178) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Is_Last_From_Skip() + { + var doc = GetNode(1173); + + foreach (var d in doc.Children.Skip(1)) + { + if (d.Id != 1176) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Is_Last_From_Concat() + { + var doc = GetNode(1173); + + + foreach (var d in doc.Children.Concat(new[] { GetNode(1175), GetNode(4444) })) + { + if (d.Id != 4444) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Descendants_Ordered_Properly() + { + var doc = GetNode(1046); + + var currentLevel = 0; + var lastSortOrder = 0; + var levelChangesAt = new[] { 1046, 1173, 1174 }; + + foreach (var d in doc.DescendantsOrSelf()) + { + if (levelChangesAt.Contains(d.Id)) + { + Assert.Greater(d.Level, currentLevel); + currentLevel = d.Level; + } + else + { + Assert.AreEqual(currentLevel, d.Level); + Assert.Greater(d.SortOrder, lastSortOrder); + } + lastSortOrder = d.SortOrder; + } + } + [Test] public void Get_Property_Value_Uses_Converter() { @@ -112,11 +230,11 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1173); Assert.AreEqual(0, doc.Index()); doc = GetNode(1176); - Assert.AreEqual(1, doc.Index()); - doc = GetNode(1177); - Assert.AreEqual(2, doc.Index()); - doc = GetNode(1178); Assert.AreEqual(3, doc.Index()); + doc = GetNode(1177); + Assert.AreEqual(1, doc.Index()); + doc = GetNode(1178); + Assert.AreEqual(2, doc.Index()); } [Test] @@ -347,7 +465,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Next_Without_Sibling() { - var doc = GetNode(1178); + var doc = GetNode(1176); Assert.IsNull(doc.Next()); } @@ -369,7 +487,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); - Assert.AreEqual((int)1174, (int)result.Id); + Assert.AreEqual((int)1178, (int)result.Id); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs index a69d5f917c..8d9faeccd1 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web; +using Umbraco.Web.Models; namespace Umbraco.Tests.PublishedContent { @@ -215,7 +216,7 @@ namespace Umbraco.Tests.PublishedContent } } - public class PublishedContentWrapper : IPublishedContent + public class PublishedContentWrapper : IPublishedContent, IOwnerCollectionAware { protected IPublishedContent WrappedContent { get; private set; } @@ -305,6 +306,46 @@ namespace Umbraco.Tests.PublishedContent { return WrappedContent.GetProperty(alias); } + + private IEnumerable _ownersCollection; + + /// + /// Need to get/set the owner collection when an item is returned from the result set of a query + /// + /// + /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 + /// + IEnumerable IOwnerCollectionAware.OwnersCollection + { + get + { + var publishedContentBase = WrappedContent as IOwnerCollectionAware; + if (publishedContentBase != null) + { + return publishedContentBase.OwnersCollection; + } + + //if the owners collection is null, we'll default to it's siblings + if (_ownersCollection == null) + { + //get the root docs if parent is null + _ownersCollection = this.Siblings(); + } + return _ownersCollection; + } + set + { + var publishedContentBase = WrappedContent as IOwnerCollectionAware; + if (publishedContentBase != null) + { + publishedContentBase.OwnersCollection = value; + } + else + { + _ownersCollection = value; + } + } + } } public partial class HomeContentItem : ContentPageContentItem diff --git a/src/Umbraco.Web/Dynamics/Grouping.cs b/src/Umbraco.Web/Dynamics/Grouping.cs index e36ce9167f..4675b16e1b 100644 --- a/src/Umbraco.Web/Dynamics/Grouping.cs +++ b/src/Umbraco.Web/Dynamics/Grouping.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Dynamics internal class Grouping : IGrouping where T : DynamicObject { public K Key { get; set; } - public IEnumerable Elements; + public IEnumerable Elements { get; set; } public IEnumerator GetEnumerator() { diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs index 861ffba9b1..d1fc83d0df 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContent.cs @@ -19,9 +19,9 @@ namespace Umbraco.Web.Models /// /// The base dynamic model for views /// - public class DynamicPublishedContent : DynamicObject, IPublishedContent + public class DynamicPublishedContent : DynamicObject, IPublishedContent, IOwnerCollectionAware { - protected IPublishedContent PublishedContent { get; private set; } + protected internal IPublishedContent PublishedContent { get; private set; } private DynamicPublishedContentList _cachedChildren; private readonly ConcurrentDictionary _cachedMemberOutput = new ConcurrentDictionary(); @@ -35,6 +35,46 @@ namespace Umbraco.Web.Models #endregion + private IEnumerable _ownersCollection; + + /// + /// Need to get/set the owner collection when an item is returned from the result set of a query + /// + /// + /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 + /// + IEnumerable IOwnerCollectionAware.OwnersCollection + { + get + { + var publishedContentBase = PublishedContent as IOwnerCollectionAware; + if (publishedContentBase != null) + { + return publishedContentBase.OwnersCollection; + } + + //if the owners collection is null, we'll default to it's siblings + if (_ownersCollection == null) + { + //get the root docs if parent is null + _ownersCollection = this.Siblings(); + } + return _ownersCollection; + } + set + { + var publishedContentBase = PublishedContent as IOwnerCollectionAware; + if (publishedContentBase != null) + { + publishedContentBase.OwnersCollection = value; + } + else + { + _ownersCollection = value; + } + } + } + public dynamic AsDynamic() { return this; diff --git a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs index bc24b99aca..3b323b004a 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs @@ -11,10 +11,19 @@ using Umbraco.Web.Dynamics; namespace Umbraco.Web.Models { - public class DynamicPublishedContentList : DynamicObject, IEnumerable + /// + /// A collection of DynamicPublishedContent items + /// + /// + /// Implements many of the dynamic methods required for execution against this list. It also ensures + /// that the correct OwnersCollection properties is assigned to the underlying PublishedContentBase object + /// of the DynamicPublishedContent item (so long as the IPublishedContent item is actually PublishedContentBase). + /// All relates to this issue here: http://issues.umbraco.org/issue/U4-1797 + /// + public class DynamicPublishedContentList : DynamicObject, IEnumerable, IEnumerable { internal List Items { get; set; } - + public DynamicPublishedContentList() { Items = new List(); @@ -22,14 +31,28 @@ namespace Umbraco.Web.Models public DynamicPublishedContentList(IEnumerable items) { var list = items.ToList(); + //set the owners list for each item + list.ForEach(x => SetOwnersList(x, this)); Items = list; } public DynamicPublishedContentList(IEnumerable items) { var list = items.Select(x => new DynamicPublishedContent(x)).ToList(); + //set the owners list for each item + list.ForEach(x => SetOwnersList(x, this)); Items = list; } + + private static void SetOwnersList(IPublishedContent content, IEnumerable list) + { + var publishedContentBase = content as IOwnerCollectionAware; + if (publishedContentBase != null) + { + publishedContentBase.OwnersCollection = list; + } + } + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { int index = (int)indexes[0]; @@ -134,12 +157,12 @@ namespace Umbraco.Web.Models } if (name == "Take") { - result = new DynamicPublishedContentList(this.Take((int)firstArg)); + result = new DynamicPublishedContentList(this.Take((int)firstArg)); return true; } if (name == "Skip") { - result = new DynamicPublishedContentList(this.Skip((int)firstArg)); + result = new DynamicPublishedContentList(this.Skip((int)firstArg)); return true; } if (name == "InGroupsOf") @@ -483,14 +506,26 @@ namespace Umbraco.Web.Models return DynamicQueryable.Select(Items.AsQueryable(), predicate, values); } + /// + /// Allows the adding of an item from the collection + /// + /// public void Add(DynamicPublishedContent publishedContent) { + SetOwnersList(publishedContent, this); this.Items.Add(publishedContent); } + + /// + /// Allows the removal of an item from the collection + /// + /// public void Remove(DynamicPublishedContent publishedContent) { if (this.Items.Contains(publishedContent)) { + //set owners list to null + SetOwnersList(publishedContent, null); this.Items.Remove(publishedContent); } } @@ -503,7 +538,12 @@ namespace Umbraco.Web.Models return true; } - public IEnumerator GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() { return Items.GetEnumerator(); } diff --git a/src/Umbraco.Web/Models/IOwnerCollectionAware.cs b/src/Umbraco.Web/Models/IOwnerCollectionAware.cs new file mode 100644 index 0000000000..a224f5c227 --- /dev/null +++ b/src/Umbraco.Web/Models/IOwnerCollectionAware.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Umbraco.Web.Models +{ + /// + /// An interface describing that the object should be aware of it's containing collection + /// + internal interface IOwnerCollectionAware + { + IEnumerable OwnersCollection { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/XmlPublishedContent.cs b/src/Umbraco.Web/Models/XmlPublishedContent.cs index 41e134bee2..6c2dc79617 100644 --- a/src/Umbraco.Web/Models/XmlPublishedContent.cs +++ b/src/Umbraco.Web/Models/XmlPublishedContent.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Models /// [Serializable] [XmlType(Namespace = "http://umbraco.org/webservices/")] - internal class XmlPublishedContent : IPublishedContent + internal class XmlPublishedContent : IPublishedContent, IOwnerCollectionAware { /// /// Constructor @@ -43,6 +43,29 @@ namespace Umbraco.Web.Models Initialize(); } + private IEnumerable _ownersCollection; + + /// + /// Need to get/set the owner collection when an item is returned from the result set of a query + /// + /// + /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 + /// + IEnumerable IOwnerCollectionAware.OwnersCollection + { + get + { + //if the owners collection is null, we'll default to it's siblings + if (_ownersCollection == null) + { + //get the root docs if parent is null + _ownersCollection = this.Siblings(); + } + return _ownersCollection; + } + set { _ownersCollection = value; } + } + private bool _initialized = false; private readonly ICollection _children = new Collection(); private IPublishedContent _parent = null; @@ -71,7 +94,7 @@ namespace Umbraco.Web.Models { if (!_initialized) Initialize(); - return _children; + return _children.OrderBy(x => x.SortOrder); } } diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 8bcf54459f..5559b82c75 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; @@ -120,9 +121,138 @@ namespace Umbraco.Web } #endregion - #region List Extensions + + #region Linq Wrapping Extensions - public static IQueryable OrderBy(this IEnumerable list, string predicate) + //NOTE: These are all purely required to fix this issue: http://issues.umbraco.org/issue/U4-1797 which requires that any + // content item knows about it's containing collection. + + public static IEnumerable Where(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.Where(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Where(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.Where(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Take(this IEnumerable source, int count) + { + var internalResult = Enumerable.Take(source, count); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable TakeWhile(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.TakeWhile(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable TakeWhile(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.TakeWhile(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Skip(this IEnumerable source, int count) + { + var internalResult = Enumerable.Skip(source, count); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable SkipWhile(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.SkipWhile(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable SkipWhile(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.SkipWhile(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Concat(this IEnumerable first, IEnumerable second) + { + var internalResult = Enumerable.Concat(first, second); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Distinct(this IEnumerable source) + { + var internalResult = Enumerable.Distinct(source); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Distinct(this IEnumerable source, IEqualityComparer comparer) + { + var internalResult = Enumerable.Distinct(source, comparer); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Union(this IEnumerable first, IEnumerable second) + { + var internalResult = Enumerable.Union(first, second); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Union(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + { + var internalResult = Enumerable.Union(first, second, comparer); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Intersect(this IEnumerable first, IEnumerable second) + { + var internalResult = Enumerable.Intersect(first, second); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Intersect(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + { + var internalResult = Enumerable.Intersect(first, second, comparer); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Except(this IEnumerable first, IEnumerable second) + { + var internalResult = Enumerable.Except(first, second); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Except(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + { + var internalResult = Enumerable.Except(first, second, comparer); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Reverse(this IEnumerable source) + { + var internalResult = Enumerable.Reverse(source); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable DefaultIfEmpty(this IEnumerable source) + { + var internalResult = Enumerable.DefaultIfEmpty(source); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable DefaultIfEmpty(this IEnumerable source, IPublishedContent defaultValue) + { + var internalResult = Enumerable.DefaultIfEmpty(source, defaultValue); + return new DynamicPublishedContentList(internalResult); + } + + #endregion + + + #region Dynamic Linq Extensions + + public static IQueryable OrderBy(this IEnumerable list, string predicate) { var dList = new DynamicPublishedContentList(list); return dList.OrderBy(predicate); @@ -131,7 +261,11 @@ namespace Umbraco.Web public static IQueryable Where(this IEnumerable list, string predicate) { var dList = new DynamicPublishedContentList(list); - return dList.Where(predicate); + //we have to wrap the result in another DynamicPublishedContentList so that the OwnersList get's set on + //the individual items. See: http://issues.umbraco.org/issue/U4-1797 + return new DynamicPublishedContentList( + dList.Where(predicate)) + .AsQueryable(); } public static IEnumerable> GroupBy(this IEnumerable list, string predicate) @@ -184,14 +318,14 @@ namespace Umbraco.Web } return new HtmlString(valueIfFalse); } - public static bool Where(this IPublishedContent doc, string predicate) + + public static bool Where(this IPublishedContent doc, string predicate) { if (doc == null) throw new ArgumentNullException("doc"); - //Totally gonna cheat here var dynamicDocumentList = new DynamicPublishedContentList(); dynamicDocumentList.Add(doc.AsDynamicPublishedContent()); var filtered = dynamicDocumentList.Where(predicate); - if (Queryable.Count(filtered) == 1) + if (filtered.Count() == 1) { //this node matches the predicate return true; @@ -207,13 +341,8 @@ namespace Umbraco.Web return content.Index(); } public static int Index(this IPublishedContent content) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - - var container = ownersList.ToList(); + { + var container = content.GetOwnersList().ToList(); int currentIndex = container.FindIndex(n => n.Id == content.Id); if (currentIndex != -1) { @@ -242,159 +371,145 @@ namespace Umbraco.Web public static bool IsNull(this IPublishedContent content, string alias) { return content.IsNull(alias, false); - } - public static bool IsFirst(this IPublishedContent content) - { - return content.IsHelper(n => n.Index() == 0); - } - public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue) - { - return content.IsHelper(n => n.Index() == 0, valueIfTrue); - } - public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() == 0, valueIfTrue, valueIfFalse); - } - public static bool IsNotFirst(this IPublishedContent content) - { - return !content.IsHelper(n => n.Index() == 0); - } - public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue) - { - return content.IsHelper(n => n.Index() != 0, valueIfTrue); - } - public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() != 0, valueIfTrue, valueIfFalse); - } - public static bool IsPosition(this IPublishedContent content, int index) - { - return content.IsHelper(n => n.Index() == index); - } - public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue) - { - return content.IsHelper(n => n.Index() == index, valueIfTrue); - } - public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() == index, valueIfTrue, valueIfFalse); - } - public static bool IsModZero(this IPublishedContent content, int modulus) - { - return content.IsHelper(n => n.Index() % modulus == 0); - } - public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue) - { - return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue); - } - public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue, valueIfFalse); - } + } - public static bool IsNotModZero(this IPublishedContent content, int modulus) - { - return content.IsHelper(n => n.Index() % modulus != 0); - } - public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue) - { - return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue); - } - public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue, valueIfFalse); - } - public static bool IsNotPosition(this IPublishedContent content, int index) - { - return !content.IsHelper(n => n.Index() == index); - } - public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue) - { - return content.IsHelper(n => n.Index() != index, valueIfTrue); - } - public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() != index, valueIfTrue, valueIfFalse); - } - public static bool IsLast(this IPublishedContent content) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1); - } - public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1, valueIfTrue); - } - public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1, valueIfTrue, valueIfFalse); - } - public static bool IsNotLast(this IPublishedContent content) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return !content.IsHelper(n => n.Index() == count - 1); - } - public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() != count - 1, valueIfTrue); - } - public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() != count - 1, valueIfTrue, valueIfFalse); - } - public static bool IsEven(this IPublishedContent content) - { - return content.IsHelper(n => n.Index() % 2 == 0); - } - public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue) - { - return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue); - } - public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue, valueIfFalse); - } - public static bool IsOdd(this IPublishedContent content) - { - return content.IsHelper(n => n.Index() % 2 == 1); - } - public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue) - { - return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue); - } - public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue, valueIfFalse); - } - public static bool IsEqual(this IPublishedContent content, IPublishedContent other) + #region Position in list + + public static bool IsFirst(this IPublishedContent content) + { + return content.IsHelper(n => n.Index() == 0); + } + public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue) + { + return content.IsHelper(n => n.Index() == 0, valueIfTrue); + } + public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() == 0, valueIfTrue, valueIfFalse); + } + public static bool IsNotFirst(this IPublishedContent content) + { + return !content.IsHelper(n => n.Index() == 0); + } + public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue) + { + return content.IsHelper(n => n.Index() != 0, valueIfTrue); + } + public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() != 0, valueIfTrue, valueIfFalse); + } + public static bool IsPosition(this IPublishedContent content, int index) + { + return content.IsHelper(n => n.Index() == index); + } + public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue) + { + return content.IsHelper(n => n.Index() == index, valueIfTrue); + } + public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() == index, valueIfTrue, valueIfFalse); + } + public static bool IsModZero(this IPublishedContent content, int modulus) + { + return content.IsHelper(n => n.Index() % modulus == 0); + } + public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue) + { + return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue); + } + public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue, valueIfFalse); + } + public static bool IsNotModZero(this IPublishedContent content, int modulus) + { + return content.IsHelper(n => n.Index() % modulus != 0); + } + public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue) + { + return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue); + } + public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue, valueIfFalse); + } + public static bool IsNotPosition(this IPublishedContent content, int index) + { + return !content.IsHelper(n => n.Index() == index); + } + public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue) + { + return content.IsHelper(n => n.Index() != index, valueIfTrue); + } + public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() != index, valueIfTrue, valueIfFalse); + } + public static bool IsLast(this IPublishedContent content) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() == count - 1); + } + public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() == count - 1, valueIfTrue); + } + public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() == count - 1, valueIfTrue, valueIfFalse); + } + public static bool IsNotLast(this IPublishedContent content) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return !content.IsHelper(n => n.Index() == count - 1); + } + public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() != count - 1, valueIfTrue); + } + public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() != count - 1, valueIfTrue, valueIfFalse); + } + public static bool IsEven(this IPublishedContent content) + { + return content.IsHelper(n => n.Index() % 2 == 0); + } + public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue) + { + return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue); + } + public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue, valueIfFalse); + } + public static bool IsOdd(this IPublishedContent content) + { + return content.IsHelper(n => n.Index() % 2 == 1); + } + public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue) + { + return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue); + } + public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue, valueIfFalse); + } + #endregion + + public static bool IsEqual(this IPublishedContent content, IPublishedContent other) { return content.IsHelper(n => n.Id == other.Id); } @@ -490,6 +605,30 @@ namespace Umbraco.Web { return test(content) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); } + + /// + /// Return the owners collection of the current content item. + /// + /// + /// + /// + /// If the content item is of type PublishedContentBase we will have a property called OwnersCollection which will + /// be the collection of a resultant set (i.e. from a where clause, a call to Children(), etc...) otherwise it will + /// be the item's siblings. All relates to this issue: http://issues.umbraco.org/issue/U4-1797 + /// + private static IEnumerable GetOwnersList(this IPublishedContent content) + { + //Here we need to type check, we need to see if we have a real OwnersCollection list based on the result set + // of a query, otherwise we can only lookup among the item's siblings. All related to this issue here: + // http://issues.umbraco.org/issue/U4-1797 + + var publishedContentBase = content as IOwnerCollectionAware; + var ownersList = publishedContentBase != null + ? publishedContentBase.OwnersCollection + : content.Siblings(); + return ownersList; + } + #endregion #region Ancestors @@ -630,7 +769,10 @@ namespace Umbraco.Web } private static IEnumerable Descendants(this IPublishedContent content, Func func) { - return content.Children.Map(func, (IPublishedContent n) => n.Children); + //return content.Children.Map(func, (IPublishedContent n) => n.Children); + return content.Children.FlattenList(x => x.Children).Where(func) + .OrderBy(x => x.Level) //ensure its sorted by level and then by sort order + .ThenBy(x => x.SortOrder); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level) { @@ -653,8 +795,13 @@ namespace Umbraco.Web { thisNode.Add(content); } - var flattenedNodes = content.Children.Map(func, (IPublishedContent n) => n.Children); - return thisNode.Concat(flattenedNodes).ToList().ConvertAll(dynamicBackingItem => new DynamicPublishedContent(dynamicBackingItem)); + //var flattenedNodes = content.Children.Map(func, (IPublishedContent n) => n.Children); + var flattenedNodes = content.Children.FlattenList(n => n.Children).Where(func); + + return thisNode.Concat(flattenedNodes) + .Select(dynamicBackingItem => new DynamicPublishedContent(dynamicBackingItem)) + .OrderBy(x => x.Level) //ensure its sorted by level and then by sort order + .ThenBy(x => x.SortOrder); } return Enumerable.Empty(); } @@ -719,10 +866,7 @@ namespace Umbraco.Web } public static IPublishedContent Next(this IPublishedContent content, int number) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var ownersList = content.GetOwnersList(); var container = ownersList.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); @@ -735,10 +879,7 @@ namespace Umbraco.Web public static IPublishedContent Next(this IPublishedContent content, string nodeTypeAlias) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var ownersList = content.GetOwnersList(); var container = ownersList.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); @@ -757,10 +898,7 @@ namespace Umbraco.Web } public static IPublishedContent Previous(this IPublishedContent content, int number) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var ownersList = content.GetOwnersList(); var container = ownersList.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); @@ -772,10 +910,7 @@ namespace Umbraco.Web } public static IPublishedContent Previous(this IPublishedContent content, string nodeTypeAlias) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var ownersList = content.GetOwnersList(); var container = ownersList.ToList(); int currentIndex = container.FindIndex(n => n.Id == content.Id); @@ -793,12 +928,9 @@ namespace Umbraco.Web } public static IPublishedContent Sibling(this IPublishedContent content, int number) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var siblings = content.Siblings(); - var container = ownersList.ToList(); + var container = siblings.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); if (currentIndex != -1) { @@ -808,12 +940,9 @@ namespace Umbraco.Web } public static IPublishedContent Sibling(this IPublishedContent content, string nodeTypeAlias) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var siblings = content.Siblings(); - var container = ownersList.ToList(); + var container = siblings.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); if (currentIndex != -1) { @@ -835,7 +964,21 @@ namespace Umbraco.Web } throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); } - #endregion + + /// + /// Return the items siblings + /// + /// + /// + public static IEnumerable Siblings(this IPublishedContent content) + { + //get the root docs if parent is null + return content.Parent == null + ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) + : content.Parent.Children; + } + + #endregion /// /// Method to return the Children of the content item diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7781076be3..c7e3e2d192 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -259,6 +259,7 @@ + diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs index 47c7eee70d..8b424eb06a 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs @@ -1024,8 +1024,9 @@ namespace umbraco.MacroEngines } public DynamicNodeList Descendants(Func func) { - var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); - return new DynamicNodeList(flattenedNodes.ToList().ConvertAll(DynamicBackingItem => new DynamicNode(DynamicBackingItem))); + //var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); + var flattenedNodes = this.n.ChildrenAsList.FlattenList(item => item.ChildrenAsList).Where(func); + return new DynamicNodeList(flattenedNodes.Select(dynamicBackingItem => new DynamicNode(dynamicBackingItem))); } public DynamicNodeList DescendantsOrSelf(int level) { @@ -1048,8 +1049,9 @@ namespace umbraco.MacroEngines { thisNode.Add(this.n); } - var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); - return new DynamicNodeList(thisNode.Concat(flattenedNodes).ToList().ConvertAll(DynamicBackingItem => new DynamicNode(DynamicBackingItem))); + //var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); + var flattenedNodes = this.n.ChildrenAsList.FlattenList(item => item.ChildrenAsList).Where(func); + return new DynamicNodeList(thisNode.Concat(flattenedNodes).Select(dynamicBackingItem => new DynamicNode(dynamicBackingItem))); } return new DynamicNodeList(new List()); } diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs b/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs index df08e906e1..276b71354e 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs @@ -3,18 +3,20 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; +using Umbraco.Core; namespace umbraco.MacroEngines { public static class ExtensionMethods { - [Obsolete("This has been superceded by Umbraco.Core.Dynamics.ExtensionMethods.Map method")] + [Obsolete("This has been superceded by Umbraco.Core.EnumerableExtensions.FlattenList method")] public static IEnumerable Map( this IEnumerable source, Func selectorFunction, Func> getChildrenFunction) { - return Umbraco.Core.Dynamics.ExtensionMethods.Map(source, selectorFunction, getChildrenFunction); + //return Umbraco.Core.Dynamics.ExtensionMethods.Map(source, selectorFunction, getChildrenFunction); + return source.FlattenList(getChildrenFunction).Where(selectorFunction); } public static DynamicNodeList Random(this DynamicNodeList all, int Min, int Max) From b5cbe63a4ebb63fd9fbaccb32b82516cb176b906 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sat, 2 Mar 2013 02:43:22 +0600 Subject: [PATCH 15/15] Back merged from 6.1 the changes required for #U4-1822 and #U4-1797 --- src/Umbraco.Core/Dynamics/DynamicXml.cs | 6 +- src/Umbraco.Core/Dynamics/ExtensionMethods.cs | 59 +- .../DynamicDocumentTestsBase.cs | 49 +- .../PublishedContent/PublishedContentTests.cs | 148 ++++- .../StronglyTypedQueryTests.cs | 43 +- src/Umbraco.Web/Dynamics/Grouping.cs | 2 +- .../Models/DynamicPublishedContent.cs | 44 +- .../Models/DynamicPublishedContentList.cs | 50 +- .../Models/IOwnerCollectionAware.cs | 12 + .../Models/PublishedContentBase.cs | 36 +- src/Umbraco.Web/Models/XmlPublishedContent.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 539 +++++++++++------- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../RazorDynamicNode/DynamicNode.cs | 10 +- .../RazorDynamicNode/ExtensionMethods.cs | 6 +- 15 files changed, 730 insertions(+), 277 deletions(-) create mode 100644 src/Umbraco.Web/Models/IOwnerCollectionAware.cs diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs index a3f9ae7089..0e0d14d9c9 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXml.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs @@ -716,7 +716,8 @@ namespace Umbraco.Core.Dynamics } public IEnumerable Descendants(Func func) { - var flattenedNodes = this.BaseElement.Elements().Map(func, n => n.Elements()); + //var flattenedNodes = this.BaseElement.Elements().Map(func, n => n.Elements()); + var flattenedNodes = this.BaseElement.Elements().SelectMany(n => n.Elements()).Where(func); return flattenedNodes.ToList().ConvertAll(n => new DynamicXml(n)); } public IEnumerable DescendantsOrSelf() @@ -725,7 +726,8 @@ namespace Umbraco.Core.Dynamics } public IEnumerable DescendantsOrSelf(Func func) { - var flattenedNodes = this.BaseElement.Elements().Map(func, n => n.Elements()); + //var flattenedNodes = this.BaseElement.Elements().Map(func, n => n.Elements()); + var flattenedNodes = this.BaseElement.Elements().SelectMany(n => n.Elements()).Where(func); var list = new List(); list.Add(this); list.AddRange(flattenedNodes.ToList().ConvertAll(n => new DynamicXml(n))); diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs index f36d4f255d..0637864ceb 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs @@ -5,36 +5,35 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Dynamics { + [Obsolete("This class should not be used, it is just referenced by already obsoleted code and will be removed in the future")] internal static class ExtensionMethods { - public static IEnumerable Map( - this IEnumerable source, - Func selectorFunction, - Func> getChildrenFunction) - { - if (!source.Any()) - { - return source; - } - // Add what we have to the stack - var flattenedList = source.Where(selectorFunction); - // Go through the input enumerable looking for children, - // and add those if we have them - foreach (TSource element in source) - { - var secondInner = getChildrenFunction(element); - if (secondInner.Any()) - { - secondInner = secondInner.Map(selectorFunction, getChildrenFunction); - } - flattenedList = flattenedList.Concat(secondInner); - } - return flattenedList; - } - - - + //public static IEnumerable Map( + // this IEnumerable source, + // Func selectorFunction, + // Func> getChildrenFunction) + //{ + // if (!source.Any()) + // { + // return source; + // } + // // Add what we have to the stack + // var flattenedList = source.Where(selectorFunction); + // // Go through the input enumerable looking for children, + // // and add those if we have them + // foreach (TSource element in source) + // { + // var secondInner = getChildrenFunction(element); + // if (secondInner.Any()) + // { + // secondInner = secondInner.Map(selectorFunction, getChildrenFunction); + // } + // flattenedList = flattenedList.Concat(secondInner); + // } + // return flattenedList; + //} + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, IEnumerable needles) { if (haystack == null) throw new ArgumentNullException("haystack"); @@ -44,6 +43,7 @@ namespace Umbraco.Core.Dynamics } return false; } + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, params string[] needles) { if (haystack == null) throw new ArgumentNullException("haystack"); @@ -53,6 +53,7 @@ namespace Umbraco.Core.Dynamics } return false; } + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, StringComparison comparison, IEnumerable needles) { if (haystack == null) throw new ArgumentNullException("haystack"); @@ -62,6 +63,7 @@ namespace Umbraco.Core.Dynamics } return false; } + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, StringComparison comparison, params string[] needles) { if (haystack == null) throw new ArgumentNullException("haystack"); @@ -71,12 +73,13 @@ namespace Umbraco.Core.Dynamics } return false; } + [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsInsensitive(this string haystack, string needle) { if (haystack == null) throw new ArgumentNullException("haystack"); return haystack.IndexOf(needle, StringComparison.CurrentCultureIgnoreCase) >= 0; } - + [Obsolete("This method should not be used and will be removed in the future")] public static bool HasValue(this string s) { return !string.IsNullOrWhiteSpace(s); diff --git a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs index ac4b322aac..86a47aea86 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs @@ -46,17 +46,18 @@ namespace Umbraco.Tests.PublishedContent This is some content]]> - + - - + + + + + 1 - - @@ -77,6 +78,28 @@ namespace Umbraco.Tests.PublishedContent /// protected abstract dynamic GetDynamicNode(int id); + /// + /// Tests the IsLast method with the result set from a Where statement + /// + [Test] + public void Is_Last_From_Where_Filter() + { + var doc = GetDynamicNode(1173); + + foreach (var d in doc.Children.Where("Visible")) + { + if (d.Id != 1178) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + + } + [Test] public void Single() { @@ -222,11 +245,11 @@ namespace Umbraco.Tests.PublishedContent var doc = GetDynamicNode(1173); Assert.AreEqual(0, doc.Index()); doc = GetDynamicNode(1176); - Assert.AreEqual(1, doc.Index()); - doc = GetDynamicNode(1177); - Assert.AreEqual(2, doc.Index()); - doc = GetDynamicNode(1178); Assert.AreEqual(3, doc.Index()); + doc = GetDynamicNode(1177); + Assert.AreEqual(1, doc.Index()); + doc = GetDynamicNode(1178); + Assert.AreEqual(2, doc.Index()); } [Test] @@ -372,7 +395,7 @@ namespace Umbraco.Tests.PublishedContent var casted = (IEnumerable)skip; Assert.AreEqual(2, casted.Count()); - Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1177, 1178 })); + Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1178, 1176 })); } @@ -397,7 +420,7 @@ namespace Umbraco.Tests.PublishedContent var casted = (IEnumerable)take; Assert.AreEqual(2, casted.Count()); - Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1176 })); + Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1177 })); } [Test] @@ -615,7 +638,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Next_Without_Sibling() { - var asDynamic = GetDynamicNode(1178); + var asDynamic = GetDynamicNode(1176); Assert.IsNull(asDynamic.Next()); } @@ -637,7 +660,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); - Assert.AreEqual((int)1174, (int)result.Id); + Assert.AreEqual((int)1178, (int)result.Id); } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 9f652e4c22..6f50398cc7 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web; using NUnit.Framework; @@ -36,25 +37,26 @@ namespace Umbraco.Tests.PublishedContent 1 - + This is some content]]> - + - - + + + + + 1 - - - + - + @@ -82,7 +84,123 @@ namespace Umbraco.Tests.PublishedContent return doc; } - [Test] + [Test] + public void Is_Last_From_Where_Filter_Dynamic_Linq() + { + var doc = GetNode(1173); + + foreach (var d in doc.Children.Where("Visible")) + { + if (d.Id != 1178) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Is_Last_From_Where_Filter() + { + var doc = GetNode(1173); + + foreach (var d in doc.Children.Where(x => x.IsVisible())) + { + if (d.Id != 1178) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Is_Last_From_Take() + { + var doc = GetNode(1173); + + foreach (var d in doc.Children.Take(3)) + { + if (d.Id != 1178) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Is_Last_From_Skip() + { + var doc = GetNode(1173); + + foreach (var d in doc.Children.Skip(1)) + { + if (d.Id != 1176) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Is_Last_From_Concat() + { + var doc = GetNode(1173); + + + foreach (var d in doc.Children.Concat(new[] { GetNode(1175), GetNode(4444) })) + { + if (d.Id != 4444) + { + Assert.IsFalse(d.IsLast()); + } + else + { + Assert.IsTrue(d.IsLast()); + } + } + } + + [Test] + public void Descendants_Ordered_Properly() + { + var doc = GetNode(1046); + + var currentLevel = 0; + var lastSortOrder = 0; + var levelChangesAt = new[] { 1046, 1173, 1174 }; + + foreach (var d in doc.DescendantsOrSelf()) + { + if (levelChangesAt.Contains(d.Id)) + { + Assert.Greater(d.Level, currentLevel); + currentLevel = d.Level; + } + else + { + Assert.AreEqual(currentLevel, d.Level); + Assert.Greater(d.SortOrder, lastSortOrder); + } + lastSortOrder = d.SortOrder; + } + } + + [Test] public void Test_Get_Recursive_Val() { var doc = GetNode(1174); @@ -125,11 +243,11 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1173); Assert.AreEqual(0, doc.Index()); doc = GetNode(1176); - Assert.AreEqual(1, doc.Index()); - doc = GetNode(1177); - Assert.AreEqual(2, doc.Index()); - doc = GetNode(1178); Assert.AreEqual(3, doc.Index()); + doc = GetNode(1177); + Assert.AreEqual(1, doc.Index()); + doc = GetNode(1178); + Assert.AreEqual(2, doc.Index()); } [Test] @@ -360,7 +478,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Next_Without_Sibling() { - var doc = GetNode(1178); + var doc = GetNode(1176); Assert.IsNull(doc.Next()); } @@ -382,7 +500,7 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNotNull(result); - Assert.AreEqual((int)1174, (int)result.Id); + Assert.AreEqual((int)1178, (int)result.Id); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs index 4a319919df..ff618763ac 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Web; +using Umbraco.Web.Models; using Umbraco.Web.Routing; namespace Umbraco.Tests.PublishedContent @@ -217,7 +218,7 @@ namespace Umbraco.Tests.PublishedContent } } - public class PublishedContentWrapper : IPublishedContent + public class PublishedContentWrapper : IPublishedContent, IOwnerCollectionAware { protected IPublishedContent WrappedContent { get; private set; } @@ -323,6 +324,46 @@ namespace Umbraco.Tests.PublishedContent { return WrappedContent.GetProperty(alias); } + + private IEnumerable _ownersCollection; + + /// + /// Need to get/set the owner collection when an item is returned from the result set of a query + /// + /// + /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 + /// + IEnumerable IOwnerCollectionAware.OwnersCollection + { + get + { + var publishedContentBase = WrappedContent as IOwnerCollectionAware; + if (publishedContentBase != null) + { + return publishedContentBase.OwnersCollection; + } + + //if the owners collection is null, we'll default to it's siblings + if (_ownersCollection == null) + { + //get the root docs if parent is null + _ownersCollection = this.Siblings(); + } + return _ownersCollection; + } + set + { + var publishedContentBase = WrappedContent as IOwnerCollectionAware; + if (publishedContentBase != null) + { + publishedContentBase.OwnersCollection = value; + } + else + { + _ownersCollection = value; + } + } + } } public partial class HomeContentItem : ContentPageContentItem diff --git a/src/Umbraco.Web/Dynamics/Grouping.cs b/src/Umbraco.Web/Dynamics/Grouping.cs index e36ce9167f..4675b16e1b 100644 --- a/src/Umbraco.Web/Dynamics/Grouping.cs +++ b/src/Umbraco.Web/Dynamics/Grouping.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Dynamics internal class Grouping : IGrouping where T : DynamicObject { public K Key { get; set; } - public IEnumerable Elements; + public IEnumerable Elements { get; set; } public IEnumerator GetEnumerator() { diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs index 859b9ac93e..0cf3ed24a8 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContent.cs @@ -20,9 +20,9 @@ namespace Umbraco.Web.Models /// /// The base dynamic model for views /// - public class DynamicPublishedContent : DynamicObject, IPublishedContent + public class DynamicPublishedContent : DynamicObject, IPublishedContent, IOwnerCollectionAware { - protected IPublishedContent PublishedContent { get; private set; } + protected internal IPublishedContent PublishedContent { get; private set; } private DynamicPublishedContentList _cachedChildren; private readonly ConcurrentDictionary _cachedMemberOutput = new ConcurrentDictionary(); @@ -36,6 +36,46 @@ namespace Umbraco.Web.Models #endregion + private IEnumerable _ownersCollection; + + /// + /// Need to get/set the owner collection when an item is returned from the result set of a query + /// + /// + /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 + /// + IEnumerable IOwnerCollectionAware.OwnersCollection + { + get + { + var publishedContentBase = PublishedContent as IOwnerCollectionAware; + if (publishedContentBase != null) + { + return publishedContentBase.OwnersCollection; + } + + //if the owners collection is null, we'll default to it's siblings + if (_ownersCollection == null) + { + //get the root docs if parent is null + _ownersCollection = this.Siblings(); + } + return _ownersCollection; + } + set + { + var publishedContentBase = PublishedContent as IOwnerCollectionAware; + if (publishedContentBase != null) + { + publishedContentBase.OwnersCollection = value; + } + else + { + _ownersCollection = value; + } + } + } + public dynamic AsDynamic() { return this; diff --git a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs index bc24b99aca..3b323b004a 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs @@ -11,10 +11,19 @@ using Umbraco.Web.Dynamics; namespace Umbraco.Web.Models { - public class DynamicPublishedContentList : DynamicObject, IEnumerable + /// + /// A collection of DynamicPublishedContent items + /// + /// + /// Implements many of the dynamic methods required for execution against this list. It also ensures + /// that the correct OwnersCollection properties is assigned to the underlying PublishedContentBase object + /// of the DynamicPublishedContent item (so long as the IPublishedContent item is actually PublishedContentBase). + /// All relates to this issue here: http://issues.umbraco.org/issue/U4-1797 + /// + public class DynamicPublishedContentList : DynamicObject, IEnumerable, IEnumerable { internal List Items { get; set; } - + public DynamicPublishedContentList() { Items = new List(); @@ -22,14 +31,28 @@ namespace Umbraco.Web.Models public DynamicPublishedContentList(IEnumerable items) { var list = items.ToList(); + //set the owners list for each item + list.ForEach(x => SetOwnersList(x, this)); Items = list; } public DynamicPublishedContentList(IEnumerable items) { var list = items.Select(x => new DynamicPublishedContent(x)).ToList(); + //set the owners list for each item + list.ForEach(x => SetOwnersList(x, this)); Items = list; } + + private static void SetOwnersList(IPublishedContent content, IEnumerable list) + { + var publishedContentBase = content as IOwnerCollectionAware; + if (publishedContentBase != null) + { + publishedContentBase.OwnersCollection = list; + } + } + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { int index = (int)indexes[0]; @@ -134,12 +157,12 @@ namespace Umbraco.Web.Models } if (name == "Take") { - result = new DynamicPublishedContentList(this.Take((int)firstArg)); + result = new DynamicPublishedContentList(this.Take((int)firstArg)); return true; } if (name == "Skip") { - result = new DynamicPublishedContentList(this.Skip((int)firstArg)); + result = new DynamicPublishedContentList(this.Skip((int)firstArg)); return true; } if (name == "InGroupsOf") @@ -483,14 +506,26 @@ namespace Umbraco.Web.Models return DynamicQueryable.Select(Items.AsQueryable(), predicate, values); } + /// + /// Allows the adding of an item from the collection + /// + /// public void Add(DynamicPublishedContent publishedContent) { + SetOwnersList(publishedContent, this); this.Items.Add(publishedContent); } + + /// + /// Allows the removal of an item from the collection + /// + /// public void Remove(DynamicPublishedContent publishedContent) { if (this.Items.Contains(publishedContent)) { + //set owners list to null + SetOwnersList(publishedContent, null); this.Items.Remove(publishedContent); } } @@ -503,7 +538,12 @@ namespace Umbraco.Web.Models return true; } - public IEnumerator GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() { return Items.GetEnumerator(); } diff --git a/src/Umbraco.Web/Models/IOwnerCollectionAware.cs b/src/Umbraco.Web/Models/IOwnerCollectionAware.cs new file mode 100644 index 0000000000..a224f5c227 --- /dev/null +++ b/src/Umbraco.Web/Models/IOwnerCollectionAware.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Umbraco.Web.Models +{ + /// + /// An interface describing that the object should be aware of it's containing collection + /// + internal interface IOwnerCollectionAware + { + IEnumerable OwnersCollection { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 8ce6f65ac4..bb45ac12c4 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -9,17 +9,42 @@ using Umbraco.Web.Templates; namespace Umbraco.Web.Models { - - /// + /// /// An abstract base class to use for IPublishedContent which ensures that the Url and Indexed property return values /// are consistently returned. /// - public abstract class PublishedContentBase : IPublishedContent + /// + /// This also ensures that we have an OwnersCollection property so that the IsFirst/IsLast/Index helper methods work + /// when referenced inside the result of a collection. http://issues.umbraco.org/issue/U4-1797 + /// + public abstract class PublishedContentBase : IPublishedContent, IOwnerCollectionAware { private string _url; private readonly Dictionary _resolvePropertyValues = new Dictionary(); + private IEnumerable _ownersCollection; - /// + /// + /// Need to get/set the owner collection when an item is returned from the result set of a query + /// + /// + /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 + /// + IEnumerable IOwnerCollectionAware.OwnersCollection + { + get + { + //if the owners collection is null, we'll default to it's siblings + if (_ownersCollection == null) + { + //get the root docs if parent is null + _ownersCollection = this.Siblings(); + } + return _ownersCollection; + } + set { _ownersCollection = value; } + } + + /// /// Returns the Url for this content item /// /// @@ -98,5 +123,6 @@ namespace Umbraco.Web.Models public abstract IPublishedContentProperty GetProperty(string alias); public abstract IPublishedContent Parent { get; } public abstract IEnumerable Children { get; } - } + + } } diff --git a/src/Umbraco.Web/Models/XmlPublishedContent.cs b/src/Umbraco.Web/Models/XmlPublishedContent.cs index 18bfbe8115..867fc8c144 100644 --- a/src/Umbraco.Web/Models/XmlPublishedContent.cs +++ b/src/Umbraco.Web/Models/XmlPublishedContent.cs @@ -72,7 +72,7 @@ namespace Umbraco.Web.Models { if (!_initialized) Initialize(); - return _children; + return _children.OrderBy(x => x.SortOrder); } } diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 5c16ee50cd..82b7959ee1 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; @@ -272,9 +273,138 @@ namespace Umbraco.Web } #endregion - #region List Extensions + + #region Linq Wrapping Extensions - public static IQueryable OrderBy(this IEnumerable list, string predicate) + //NOTE: These are all purely required to fix this issue: http://issues.umbraco.org/issue/U4-1797 which requires that any + // content item knows about it's containing collection. + + public static IEnumerable Where(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.Where(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Where(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.Where(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Take(this IEnumerable source, int count) + { + var internalResult = Enumerable.Take(source, count); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable TakeWhile(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.TakeWhile(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable TakeWhile(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.TakeWhile(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Skip(this IEnumerable source, int count) + { + var internalResult = Enumerable.Skip(source, count); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable SkipWhile(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.SkipWhile(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable SkipWhile(this IEnumerable source, Func predicate) + { + var internalResult = Enumerable.SkipWhile(source, predicate); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Concat(this IEnumerable first, IEnumerable second) + { + var internalResult = Enumerable.Concat(first, second); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Distinct(this IEnumerable source) + { + var internalResult = Enumerable.Distinct(source); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Distinct(this IEnumerable source, IEqualityComparer comparer) + { + var internalResult = Enumerable.Distinct(source, comparer); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Union(this IEnumerable first, IEnumerable second) + { + var internalResult = Enumerable.Union(first, second); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Union(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + { + var internalResult = Enumerable.Union(first, second, comparer); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Intersect(this IEnumerable first, IEnumerable second) + { + var internalResult = Enumerable.Intersect(first, second); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Intersect(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + { + var internalResult = Enumerable.Intersect(first, second, comparer); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Except(this IEnumerable first, IEnumerable second) + { + var internalResult = Enumerable.Except(first, second); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Except(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + { + var internalResult = Enumerable.Except(first, second, comparer); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable Reverse(this IEnumerable source) + { + var internalResult = Enumerable.Reverse(source); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable DefaultIfEmpty(this IEnumerable source) + { + var internalResult = Enumerable.DefaultIfEmpty(source); + return new DynamicPublishedContentList(internalResult); + } + + public static IEnumerable DefaultIfEmpty(this IEnumerable source, IPublishedContent defaultValue) + { + var internalResult = Enumerable.DefaultIfEmpty(source, defaultValue); + return new DynamicPublishedContentList(internalResult); + } + + #endregion + + + #region Dynamic Linq Extensions + + public static IQueryable OrderBy(this IEnumerable list, string predicate) { var dList = new DynamicPublishedContentList(list); return dList.OrderBy(predicate); @@ -283,7 +413,11 @@ namespace Umbraco.Web public static IQueryable Where(this IEnumerable list, string predicate) { var dList = new DynamicPublishedContentList(list); - return dList.Where(predicate); + //we have to wrap the result in another DynamicPublishedContentList so that the OwnersList get's set on + //the individual items. See: http://issues.umbraco.org/issue/U4-1797 + return new DynamicPublishedContentList( + dList.Where(predicate)) + .AsQueryable(); } public static IEnumerable> GroupBy(this IEnumerable list, string predicate) @@ -336,14 +470,14 @@ namespace Umbraco.Web } return new HtmlString(valueIfFalse); } - public static bool Where(this IPublishedContent doc, string predicate) + + public static bool Where(this IPublishedContent doc, string predicate) { if (doc == null) throw new ArgumentNullException("doc"); - //Totally gonna cheat here var dynamicDocumentList = new DynamicPublishedContentList(); dynamicDocumentList.Add(doc.AsDynamicPublishedContent()); var filtered = dynamicDocumentList.Where(predicate); - if (Queryable.Count(filtered) == 1) + if (filtered.Count() == 1) { //this node matches the predicate return true; @@ -359,13 +493,8 @@ namespace Umbraco.Web return content.Index(); } public static int Index(this IPublishedContent content) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - - var container = ownersList.ToList(); + { + var container = content.GetOwnersList().ToList(); int currentIndex = container.FindIndex(n => n.Id == content.Id); if (currentIndex != -1) { @@ -376,6 +505,30 @@ namespace Umbraco.Web throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicDocumentList but could not retrieve the index for it's position in the list", content.Id)); } } + + /// + /// Return the owners collection of the current content item. + /// + /// + /// + /// + /// If the content item is of type PublishedContentBase we will have a property called OwnersCollection which will + /// be the collection of a resultant set (i.e. from a where clause, a call to Children(), etc...) otherwise it will + /// be the item's siblings. All relates to this issue: http://issues.umbraco.org/issue/U4-1797 + /// + private static IEnumerable GetOwnersList(this IPublishedContent content) + { + //Here we need to type check, we need to see if we have a real OwnersCollection list based on the result set + // of a query, otherwise we can only lookup among the item's siblings. All related to this issue here: + // http://issues.umbraco.org/issue/U4-1797 + + var publishedContentBase = content as IOwnerCollectionAware; + var ownersList = publishedContentBase != null + ? publishedContentBase.OwnersCollection + : content.Siblings(); + return ownersList; + } + #endregion #region Is Helpers @@ -394,159 +547,145 @@ namespace Umbraco.Web public static bool IsNull(this IPublishedContent content, string alias) { return content.IsNull(alias, false); - } - public static bool IsFirst(this IPublishedContent content) - { - return content.IsHelper(n => n.Index() == 0); - } - public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue) - { - return content.IsHelper(n => n.Index() == 0, valueIfTrue); - } - public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() == 0, valueIfTrue, valueIfFalse); - } - public static bool IsNotFirst(this IPublishedContent content) - { - return !content.IsHelper(n => n.Index() == 0); - } - public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue) - { - return content.IsHelper(n => n.Index() != 0, valueIfTrue); - } - public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() != 0, valueIfTrue, valueIfFalse); - } - public static bool IsPosition(this IPublishedContent content, int index) - { - return content.IsHelper(n => n.Index() == index); - } - public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue) - { - return content.IsHelper(n => n.Index() == index, valueIfTrue); - } - public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() == index, valueIfTrue, valueIfFalse); - } - public static bool IsModZero(this IPublishedContent content, int modulus) - { - return content.IsHelper(n => n.Index() % modulus == 0); - } - public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue) - { - return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue); - } - public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue, valueIfFalse); - } + } - public static bool IsNotModZero(this IPublishedContent content, int modulus) - { - return content.IsHelper(n => n.Index() % modulus != 0); - } - public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue) - { - return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue); - } - public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue, valueIfFalse); - } - public static bool IsNotPosition(this IPublishedContent content, int index) - { - return !content.IsHelper(n => n.Index() == index); - } - public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue) - { - return content.IsHelper(n => n.Index() != index, valueIfTrue); - } - public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() != index, valueIfTrue, valueIfFalse); - } - public static bool IsLast(this IPublishedContent content) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1); - } - public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1, valueIfTrue); - } - public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1, valueIfTrue, valueIfFalse); - } - public static bool IsNotLast(this IPublishedContent content) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return !content.IsHelper(n => n.Index() == count - 1); - } - public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() != count - 1, valueIfTrue); - } - public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() != count - 1, valueIfTrue, valueIfFalse); - } - public static bool IsEven(this IPublishedContent content) - { - return content.IsHelper(n => n.Index() % 2 == 0); - } - public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue) - { - return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue); - } - public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue, valueIfFalse); - } - public static bool IsOdd(this IPublishedContent content) - { - return content.IsHelper(n => n.Index() % 2 == 1); - } - public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue) - { - return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue); - } - public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue, valueIfFalse); - } - public static bool IsEqual(this IPublishedContent content, IPublishedContent other) + #region Position in list + + public static bool IsFirst(this IPublishedContent content) + { + return content.IsHelper(n => n.Index() == 0); + } + public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue) + { + return content.IsHelper(n => n.Index() == 0, valueIfTrue); + } + public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() == 0, valueIfTrue, valueIfFalse); + } + public static bool IsNotFirst(this IPublishedContent content) + { + return !content.IsHelper(n => n.Index() == 0); + } + public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue) + { + return content.IsHelper(n => n.Index() != 0, valueIfTrue); + } + public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() != 0, valueIfTrue, valueIfFalse); + } + public static bool IsPosition(this IPublishedContent content, int index) + { + return content.IsHelper(n => n.Index() == index); + } + public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue) + { + return content.IsHelper(n => n.Index() == index, valueIfTrue); + } + public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() == index, valueIfTrue, valueIfFalse); + } + public static bool IsModZero(this IPublishedContent content, int modulus) + { + return content.IsHelper(n => n.Index() % modulus == 0); + } + public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue) + { + return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue); + } + public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue, valueIfFalse); + } + public static bool IsNotModZero(this IPublishedContent content, int modulus) + { + return content.IsHelper(n => n.Index() % modulus != 0); + } + public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue) + { + return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue); + } + public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue, valueIfFalse); + } + public static bool IsNotPosition(this IPublishedContent content, int index) + { + return !content.IsHelper(n => n.Index() == index); + } + public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue) + { + return content.IsHelper(n => n.Index() != index, valueIfTrue); + } + public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() != index, valueIfTrue, valueIfFalse); + } + public static bool IsLast(this IPublishedContent content) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() == count - 1); + } + public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() == count - 1, valueIfTrue); + } + public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() == count - 1, valueIfTrue, valueIfFalse); + } + public static bool IsNotLast(this IPublishedContent content) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return !content.IsHelper(n => n.Index() == count - 1); + } + public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() != count - 1, valueIfTrue); + } + public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + var ownersList = content.GetOwnersList(); + var count = ownersList.Count(); + return content.IsHelper(n => n.Index() != count - 1, valueIfTrue, valueIfFalse); + } + public static bool IsEven(this IPublishedContent content) + { + return content.IsHelper(n => n.Index() % 2 == 0); + } + public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue) + { + return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue); + } + public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue, valueIfFalse); + } + public static bool IsOdd(this IPublishedContent content) + { + return content.IsHelper(n => n.Index() % 2 == 1); + } + public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue) + { + return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue); + } + public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + { + return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue, valueIfFalse); + } + #endregion + + public static bool IsEqual(this IPublishedContent content, IPublishedContent other) { return content.IsHelper(n => n.Id == other.Id); } @@ -782,7 +921,10 @@ namespace Umbraco.Web } private static IEnumerable Descendants(this IPublishedContent content, Func func) { - return content.Children.Map(func, (IPublishedContent n) => n.Children); + //return content.Children.Map(func, (IPublishedContent n) => n.Children); + return content.Children.FlattenList(x => x.Children).Where(func) + .OrderBy(x => x.Level) //ensure its sorted by level and then by sort order + .ThenBy(x => x.SortOrder); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level) { @@ -805,8 +947,13 @@ namespace Umbraco.Web { thisNode.Add(content); } - var flattenedNodes = content.Children.Map(func, (IPublishedContent n) => n.Children); - return thisNode.Concat(flattenedNodes).ToList().ConvertAll(dynamicBackingItem => new DynamicPublishedContent(dynamicBackingItem)); + //var flattenedNodes = content.Children.Map(func, (IPublishedContent n) => n.Children); + var flattenedNodes = content.Children.FlattenList(n => n.Children).Where(func); + + return thisNode.Concat(flattenedNodes) + .Select(dynamicBackingItem => new DynamicPublishedContent(dynamicBackingItem)) + .OrderBy(x => x.Level) //ensure its sorted by level and then by sort order + .ThenBy(x => x.SortOrder); } return Enumerable.Empty(); } @@ -871,10 +1018,7 @@ namespace Umbraco.Web } public static IPublishedContent Next(this IPublishedContent content, int number) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var ownersList = content.GetOwnersList(); var container = ownersList.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); @@ -887,10 +1031,7 @@ namespace Umbraco.Web public static IPublishedContent Next(this IPublishedContent content, string nodeTypeAlias) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var ownersList = content.GetOwnersList(); var container = ownersList.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); @@ -909,10 +1050,7 @@ namespace Umbraco.Web } public static IPublishedContent Previous(this IPublishedContent content, int number) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var ownersList = content.GetOwnersList(); var container = ownersList.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); @@ -924,10 +1062,7 @@ namespace Umbraco.Web } public static IPublishedContent Previous(this IPublishedContent content, string nodeTypeAlias) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var ownersList = content.GetOwnersList(); var container = ownersList.ToList(); int currentIndex = container.FindIndex(n => n.Id == content.Id); @@ -945,12 +1080,9 @@ namespace Umbraco.Web } public static IPublishedContent Sibling(this IPublishedContent content, int number) { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + var siblings = content.Siblings(); - var container = ownersList.ToList(); + var container = siblings.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); if (currentIndex != -1) { @@ -959,13 +1091,10 @@ namespace Umbraco.Web throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); } public static IPublishedContent Sibling(this IPublishedContent content, string nodeTypeAlias) - { - //get the root docs if parent is null - var ownersList = content.Parent == null - ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) - : content.Parent.Children; + { + var siblings = content.Siblings(); - var container = ownersList.ToList(); + var container = siblings.ToList(); var currentIndex = container.FindIndex(n => n.Id == content.Id); if (currentIndex != -1) { @@ -987,7 +1116,21 @@ namespace Umbraco.Web } throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); } - #endregion + + /// + /// Return the items siblings + /// + /// + /// + public static IEnumerable Siblings(this IPublishedContent content) + { + //get the root docs if parent is null + return content.Parent == null + ? PublishedContentStoreResolver.Current.PublishedContentStore.GetRootDocuments(UmbracoContext.Current) + : content.Parent.Children; + } + + #endregion /// /// Method to return the Children of the content item @@ -999,7 +1142,7 @@ namespace Umbraco.Web /// public static IEnumerable Children(this IPublishedContent p) { - return p.Children; + return p.Children.OrderBy(x => x.SortOrder); } /// @@ -1043,7 +1186,7 @@ namespace Umbraco.Web //create all row data var tableData = Umbraco.Core.DataTableExtensions.CreateTableData(); //loop through each child and create row data for it - foreach (var n in node.Children) + foreach (var n in node.Children.OrderBy(x => x.SortOrder)) { if (!nodeTypeAliasFilter.IsNullOrWhiteSpace()) { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ecf8b8d421..db8183fc03 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -262,6 +262,7 @@ + diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs index f5ed186acc..ad7dca8cc8 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs @@ -1025,8 +1025,9 @@ namespace umbraco.MacroEngines } public DynamicNodeList Descendants(Func func) { - var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); - return new DynamicNodeList(flattenedNodes.ToList().ConvertAll(DynamicBackingItem => new DynamicNode(DynamicBackingItem))); + //var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); + var flattenedNodes = this.n.ChildrenAsList.FlattenList(item => item.ChildrenAsList).Where(func); + return new DynamicNodeList(flattenedNodes.Select(dynamicBackingItem => new DynamicNode(dynamicBackingItem))); } public DynamicNodeList DescendantsOrSelf(int level) { @@ -1049,8 +1050,9 @@ namespace umbraco.MacroEngines { thisNode.Add(this.n); } - var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); - return new DynamicNodeList(thisNode.Concat(flattenedNodes).ToList().ConvertAll(DynamicBackingItem => new DynamicNode(DynamicBackingItem))); + //var flattenedNodes = this.n.ChildrenAsList.Map(func, (DynamicBackingItem n) => { return n.ChildrenAsList; }); + var flattenedNodes = this.n.ChildrenAsList.FlattenList(item => item.ChildrenAsList).Where(func); + return new DynamicNodeList(thisNode.Concat(flattenedNodes).Select(dynamicBackingItem => new DynamicNode(dynamicBackingItem))); } return new DynamicNodeList(new List()); } diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs b/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs index df08e906e1..276b71354e 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/ExtensionMethods.cs @@ -3,18 +3,20 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; +using Umbraco.Core; namespace umbraco.MacroEngines { public static class ExtensionMethods { - [Obsolete("This has been superceded by Umbraco.Core.Dynamics.ExtensionMethods.Map method")] + [Obsolete("This has been superceded by Umbraco.Core.EnumerableExtensions.FlattenList method")] public static IEnumerable Map( this IEnumerable source, Func selectorFunction, Func> getChildrenFunction) { - return Umbraco.Core.Dynamics.ExtensionMethods.Map(source, selectorFunction, getChildrenFunction); + //return Umbraco.Core.Dynamics.ExtensionMethods.Map(source, selectorFunction, getChildrenFunction); + return source.FlattenList(getChildrenFunction).Where(selectorFunction); } public static DynamicNodeList Random(this DynamicNodeList all, int Min, int Max)