diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 14e6c0e9a6..294f869c3f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -624,13 +624,13 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .Where(x => x.Published) + .Where(x => x.Published, SqlSyntax) .OrderBy(x => x.Level, SqlSyntax) .OrderBy(x => x.SortOrder, SqlSyntax); return ProcessQuery(sql, true); } - + /// /// This builds the Xml document used for the XML cache /// @@ -662,10 +662,14 @@ namespace Umbraco.Core.Persistence.Repositories parent.Attributes.Append(pIdAtt); xmlDoc.AppendChild(parent); - const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode + //Ensure that only nodes that have published versions are selected + var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.{0}, umbracoNode.{1} from umbracoNode inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) -order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; +order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", + SqlSyntax.GetQuotedColumnName("xml"), + SqlSyntax.GetQuotedColumnName("level"), + SqlSyntax.GetQuotedColumnName("level")); XmlElement last = null; diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs index 256ef36c05..b318223ca7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs @@ -74,8 +74,9 @@ namespace Umbraco.Core.Persistence.Repositories /// Path starts with /// Page number /// Page size + /// /// Total records the query would return without paging /// A paged enumerable of XML entries of content items - IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords); + IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index df15ab049c..19f4b8fdfb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -139,24 +139,34 @@ namespace Umbraco.Core.Persistence.Repositories #endregion /// - /// Gets paged content descendants as XML by path + /// Gets paged document descendants as XML by path /// /// Path starts with /// Page number /// Page size + /// /// Total records the query would return without paging /// A paged enumerable of XML entries of content items - public IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords) + public virtual IEnumerable GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords) { - Sql query; + var query = new Sql().Select(string.Format("umbracoNode.id, cmsContentXml.{0}", SqlSyntax.GetQuotedColumnName("xml"))) + .From("umbracoNode") + .InnerJoin("cmsContentXml").On("cmsContentXml.nodeId = umbracoNode.id"); + if (path == "-1") { - query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE nodeObjectType = @0)", NodeObjectTypeId).OrderBy("nodeId"); + query.Where("umbracoNode.nodeObjectType = @type", new { type = NodeObjectTypeId }); } else { - query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE path LIKE (@0))", path.EnsureEndsWith(",%")).OrderBy("nodeId"); + query.Where(string.Format("umbracoNode.{0} LIKE (@0)", SqlSyntax.GetQuotedColumnName("path")), path.EnsureEndsWith(",%")); } + + //each order by param needs to be in a bracket! see: https://github.com/toptensoftware/PetaPoco/issues/177 + query.OrderBy(orderBy == null + ? "(umbracoNode.id)" + : string.Join(",", orderBy.Select(x => string.Format("({0})", SqlSyntax.GetQuotedColumnName(x))))); + var pagedResult = Database.Page(pageIndex + 1, pageSize, query); totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml)); diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index fff0ac61c8..6f2007912f 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1780,7 +1780,11 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateContentRepository(uow)) { - var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords); + var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, + //This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and + // UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order + new[] {"level", "parentID", "sortOrder"}, + out totalRecords); return contents; } } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 50b323ed0a..9a3a30c8bd 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1228,7 +1228,7 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMediaRepository(uow)) { - var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords); + var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, null, out totalRecords); return contents; } } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index cf833dcd28..70d45926fc 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -627,7 +627,7 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) { - var contents = repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, out totalRecords); + var contents = repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, null, out totalRecords); return contents; } } diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index c2dbecf83d..32fa2607ac 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -399,13 +399,58 @@ namespace UmbracoExamine if (SupportUnpublishedContent == false && DisableXmlDocumentLookup == false) { + //get all node Ids that have a published version - this is a fail safe check, in theory + // only document nodes that have a published version would exist in the cmsContentXml table + var allNodesWithPublishedVersions = ApplicationContext.Current.DatabaseContext.Database.Fetch( + "select DISTINCT cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1"); + + XElement last = null; + var trackedIds = new HashSet(); + ReindexWithXmlEntries(type, contentParentId, () => _contentTypeService.GetAllContentTypes().ToArray(), (path, pIndex, pSize) => { long totalContent; + + //sorted by: umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder var result = _contentService.GetPagedXmlEntries(path, pIndex, pSize, out totalContent).ToArray(); - return new Tuple(totalContent, result); + + //then like we do in the ContentRepository.BuildXmlCache we need to track what Parents have been processed + // already so that we can then exclude implicitly unpublished content items + var filtered = new List(); + + foreach (var xml in result) + { + var id = xml.AttributeValue("id"); + + //don't include this if it doesn't have a published version + if (allNodesWithPublishedVersions.Contains(id) == false) + continue; + + var parentId = xml.AttributeValue("parentID"); + + if (parentId == null) continue; //this shouldn't happen + + //if the parentid is changing + if (last != null && last.AttributeValue("parentID") != parentId) + { + var found = trackedIds.Contains(parentId); + if (found == false) + { + //Need to short circuit here, if the parent is not there it means that the parent is unpublished + // and therefore the child is not published either so cannot be included in the xml cache + continue; + } + } + + last = xml; + trackedIds.Add(xml.AttributeValue("id")); + + filtered.Add(xml); + } + + return new Tuple(totalContent, filtered.ToArray()); }, i => _contentService.GetById(i)); }