diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index e869b2b8c3..6e9d3019ff 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -61,7 +61,7 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.DocumentType: case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: - return uow.Database.ExecuteScalar(new Sql().Select("id").From().Where(dto => dto.UniqueId == key)); + return uow.Database.ExecuteScalar("SELECT id FROM umbracoNode WHERE uniqueID = @uniqueId", new {uniqueId = key}); case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: case UmbracoObjectTypes.MemberGroup: @@ -99,7 +99,7 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.DocumentType: case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: - return uow.Database.ExecuteScalar(new Sql().Select("uniqueID").From().Where(dto => dto.NodeId == id)); + return uow.Database.ExecuteScalar("SELECT uniqueID FROM umbracoNode WHERE id = @id", new { id = id }); case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: case UmbracoObjectTypes.MemberGroup: diff --git a/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs b/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs index fcf1b3a18a..f818f7941e 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs @@ -1,8 +1,17 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Web; +using System.Xml; +using umbraco.cms.businesslogic.web; +using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Xml; namespace Umbraco.Web.Routing { @@ -11,14 +20,17 @@ namespace Umbraco.Web.Routing /// public class ContentFinderByLegacy404 : IContentFinder { + private readonly ILogger _logger; + private readonly IContentSection _contentConfigSection; - public ContentFinderByLegacy404(ILogger logger) + public ContentFinderByLegacy404(ILogger logger, IContentSection contentConfigSection) { _logger = logger; + _contentConfigSection = contentConfigSection; } - /// + /// /// Tries to find and assign an Umbraco document to a PublishedContentRequest. /// /// The PublishedContentRequest. @@ -27,10 +39,8 @@ namespace Umbraco.Web.Routing { _logger.Debug("Looking for a page to handle 404."); - // TODO - replace the whole logic var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( - //TODO: The IContentSection should be ctor injected into this class in v8! - UmbracoConfig.For.UmbracoSettings().Content.Error404Collection.ToArray(), + _contentConfigSection.Error404Collection.ToArray(), //TODO: Is there a better way to extract this value? at least we're not relying on singletons here though pcr.RoutingContext.UmbracoContext.HttpContext.Request.ServerVariables["SERVER_NAME"], pcr.RoutingContext.UmbracoContext.Application.Services.EntityService, @@ -58,4 +68,124 @@ namespace Umbraco.Web.Routing return content != null; } } + + internal class NotFoundHandlerHelper + { + /// + /// Returns the Umbraco page id to use as the Not Found page based on the configured 404 pages and the current request + /// + /// + /// + /// The server name attached to the request, normally would be the source of HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + /// + /// + /// + /// + internal static int? GetCurrentNotFoundPageId( + IContentErrorPage[] error404Collection, + string requestServerName, + IEntityService entityService, + PublishedContentQuery publishedContentQuery) + { + if (error404Collection.Count() > 1) + { + // try to get the 404 based on current culture (via domain) + IContentErrorPage cultureErr; + + //TODO: Remove the dependency on this legacy Domain service, + // in 7.3 the real domain service should be passed in as a parameter. + if (Domain.Exists(requestServerName)) + { + var d = Domain.GetDomain(requestServerName); + + // test if a 404 page exists with current culture + cultureErr = error404Collection + .FirstOrDefault(x => x.Culture == d.Language.CultureAlias); + + if (cultureErr != null) + { + return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery); + } + } + + // test if a 404 page exists with current culture thread + cultureErr = error404Collection + .FirstOrDefault(x => x.Culture == System.Threading.Thread.CurrentThread.CurrentUICulture.Name); + if (cultureErr != null) + { + return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery); + } + + // there should be a default one! + cultureErr = error404Collection + .FirstOrDefault(x => x.Culture == "default"); + + if (cultureErr != null) + { + return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery); + } + } + else + { + return GetContentIdFromErrorPageConfig(error404Collection.First(), entityService, publishedContentQuery); + } + + return null; + } + + /// + /// Returns the content id based on the configured IContentErrorPage section + /// + /// + /// + /// + /// + internal static int? GetContentIdFromErrorPageConfig(IContentErrorPage errorPage, IEntityService entityService, PublishedContentQuery publishedContentQuery) + { + if (errorPage.HasContentId) return errorPage.ContentId; + + if (errorPage.HasContentKey) + { + //need to get the Id for the GUID + //TODO: When we start storing GUIDs into the IPublishedContent, then we won't have to look this up + // but until then we need to look it up in the db. For now we've implemented a cached service for + // converting Int -> Guid and vice versa. + var found = entityService.GetIdForKey(errorPage.ContentKey, UmbracoObjectTypes.Document); + if (found) + { + return found.Result; + } + return null; + } + + if (errorPage.ContentXPath.IsNullOrWhiteSpace() == false) + { + try + { + //we have an xpath statement to execute + var xpathResult = UmbracoXPathPathSyntaxParser.ParseXPathQuery( + xpathExpression: errorPage.ContentXPath, + nodeContextId: null, + getPath: nodeid => + { + var ent = entityService.Get(nodeid); + return ent.Path.Split(',').Reverse(); + }, + publishedContentExists: i => publishedContentQuery.TypedContent(i) != null); + + //now we'll try to execute the expression + var nodeResult = publishedContentQuery.TypedContentSingleAtXPath(xpathResult); + if (nodeResult != null) + return nodeResult.Id; + } + catch (Exception ex) + { + LogHelper.Error("Could not parse xpath expression: " + errorPage.ContentXPath, ex); + return null; + } + } + return null; + } + + } }