diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index 1304b284de..2c7e2a00d3 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -148,6 +148,11 @@ namespace Umbraco.Core.Configuration get { return "~/default.aspx"; } } + internal static string NoContentSplashPage + { + get { return "~/config/splashes/noNodes.aspx"; } + } + /// /// Gets a value indicating whether logging is enabled in umbracoSettings.config (/settings/logging/enableLogging). /// diff --git a/src/Umbraco.Web/DefaultPublishedContentStore.cs b/src/Umbraco.Web/DefaultPublishedContentStore.cs index 69df99713e..52fba94835 100644 --- a/src/Umbraco.Web/DefaultPublishedContentStore.cs +++ b/src/Umbraco.Web/DefaultPublishedContentStore.cs @@ -88,6 +88,13 @@ namespace Umbraco.Web return ConvertToDocument(GetXml(umbracoContext).SelectSingleNode(xpath)); } + public bool HasContent(UmbracoContext umbracoContext) + { + const string xpath = "/root/*[@isDoc]"; + var node = GetXml(umbracoContext).SelectSingleNode(xpath); + return node != null; + } + XmlDocument GetXml(UmbracoContext umbracoContext) { if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); diff --git a/src/Umbraco.Web/IPublishedContentStore.cs b/src/Umbraco.Web/IPublishedContentStore.cs index 9195e8bc7d..c603b58c44 100644 --- a/src/Umbraco.Web/IPublishedContentStore.cs +++ b/src/Umbraco.Web/IPublishedContentStore.cs @@ -9,5 +9,6 @@ namespace Umbraco.Web { IDocument GetDocumentByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null); IDocument GetDocumentByUrlAlias(UmbracoContext umbracoContext, int rootNodeId, string alias); + bool HasContent(UmbracoContext umbracoContext); } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/DocumentRequestBuilder.cs b/src/Umbraco.Web/Routing/DocumentRequestBuilder.cs index 6ab2df3e38..0a12206f84 100644 --- a/src/Umbraco.Web/Routing/DocumentRequestBuilder.cs +++ b/src/Umbraco.Web/Routing/DocumentRequestBuilder.cs @@ -231,7 +231,10 @@ namespace Umbraco.Web.Routing /// Follows internal redirections through the umbracoInternalRedirectId document property. /// /// A value indicating whether redirection took place and led to a new published document. - /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. + /// + /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. + /// As per legacy, if the redirect does not work, we just ignore it. + /// private bool FollowInternalRedirects() { const string tracePrefix = "FollowInternalRedirects: "; @@ -252,8 +255,8 @@ namespace Umbraco.Web.Routing if (internalRedirectId <= 0) { - // bad redirect - _documentRequest.Document = null; + // bad redirect - log and display the current page (legacy behavior) + //_documentRequest.Document = null; // no! that would be to force a 404 LogHelper.Debug("{0}Failed to redirect to id={1}: invalid value", () => tracePrefix, () => internalRedirect); } else if (internalRedirectId == _documentRequest.DocumentId) @@ -375,18 +378,22 @@ namespace Umbraco.Web.Routing if (!_documentRequest.HasTemplate) { LogHelper.Debug("{0}No template was found."); - // do not do it if we're already 404 else it creates an infinite loop - if (Umbraco.Core.Configuration.UmbracoSettings.HandleMissingTemplateAs404 && !_documentRequest.Is404) - { - LogHelper.Debug("{0}Assume page not found (404)."); - _documentRequest.Document = null; - } + + // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true + // then reset _documentRequest.Document to null to force a 404. + // + // but: because we want to let MVC hijack routes even though no template is defined, we decide that + // a missing template is OK but the request will then be forwarded to MVC, which will need to take + // care of everything. + // + // so, don't set _documentRequest.Document to null here } } /// /// Follows external redirection through umbracoRedirect document property. /// + /// As per legacy, if the redirect does not work, we just ignore it. private void FollowRedirect() { if (_documentRequest.HasNode) diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index beccdf1f12..d35cd99f91 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -34,9 +34,12 @@ namespace Umbraco.Web /// void BeginRequest(HttpContextBase httpContext) { + // do not process if client-side request if (IsClientSideRequest(httpContext.Request.Url)) return; + // ok, process + // create the LegacyRequestInitializer // and initialize legacy stuff var legacyRequestInitializer = new LegacyRequestInitializer(httpContext.Request.Url, httpContext); @@ -68,95 +71,100 @@ namespace Umbraco.Web /// void ProcessRequest(HttpContextBase httpContext) { + // do not process if client-side request if (IsClientSideRequest(httpContext.Request.Url)) return; var umbracoContext = UmbracoContext.Current; var routingContext = umbracoContext.RoutingContext; - // remap to handler if it is a base rest request + // do not process but remap to handler if it is a base rest request if (BaseRest.BaseRestHandler.IsBaseRestRequest(umbracoContext.RequestUrl)) { httpContext.RemapHandler(new BaseRest.BaseRestHandler()); + return; } - else - //do not continue if this request is not a front-end routable page - if (EnsureUmbracoRoutablePage(umbracoContext, httpContext)) + // do not process if this request is not a front-end routable page + if (!EnsureUmbracoRoutablePage(umbracoContext, httpContext)) + return; + + // ok, process + + var uri = umbracoContext.RequestUrl; + + // legacy - no idea what this is + LegacyCleanUmbPageFromQueryString(ref uri); + + // create the new document request since we're rendering a document on the front-end + var docreq = new DocumentRequest( + umbracoContext.UmbracoUrl, //very important to use this url! it is the path only lowercased version of the current URL. + routingContext); + //assign the document request to the umbraco context now that we know its a front end request + umbracoContext.DocumentRequest = docreq; + + // note - at that point the original legacy module did something do handle IIS custom 404 errors + // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support + // "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain + // to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk. + // + // to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors + // so that they point to a non-existing page eg /redirect-404.aspx + // TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means. + + //create the searcher + var searcher = new DocumentRequestBuilder(docreq); + //find domain + searcher.LookupDomain(); + // redirect if it has been flagged + if (docreq.IsRedirect) + httpContext.Response.Redirect(docreq.RedirectUrl, true); + //set the culture on the thread + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = docreq.Culture; + //find the document, found will be true if the doc request has found BOTH a node and a template + // though currently we don't use this value. + var found = searcher.LookupDocument(); + //this could be called in the LookupDocument method, but I've just put it here for clarity. + searcher.DetermineRenderingEngine(); + + //TODO: here we should launch an event so that people can modify the doc request to do whatever they want. + + // redirect if it has been flagged + if (docreq.IsRedirect) + httpContext.Response.Redirect(docreq.RedirectUrl, true); + + // handle 404 + if (docreq.Is404) + { + httpContext.Response.StatusCode = 404; + + if (!docreq.HasNode) { - var uri = umbracoContext.RequestUrl; - - // legacy - no idea what this is - LegacyCleanUmbPageFromQueryString(ref uri); - - //Create a document request since we are rendering a document on the front-end - - // create the new document request - var docreq = new DocumentRequest( - umbracoContext.UmbracoUrl, //very important to use this url! it is the path only lowercased version of the current URL. - routingContext); - //assign the document request to the umbraco context now that we know its a front end request - umbracoContext.DocumentRequest = docreq; - - // note - at that point the original legacy module did something do handle IIS custom 404 errors - // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support - // "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain - // to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk. - // - // to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors - // so that they point to a non-existing page eg /redirect-404.aspx - // TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means. - - //create the searcher - var searcher = new DocumentRequestBuilder(docreq); - //find domain - searcher.LookupDomain(); - // redirect if it has been flagged - if (docreq.IsRedirect) - httpContext.Response.Redirect(docreq.RedirectUrl, true); - //set the culture on the thread - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = docreq.Culture; - //find the document, found will be true if the doc request has found BOTH a node and a template - // though currently we don't use this value. - var found = searcher.LookupDocument(); - //this could be called in the LookupDocument method, but I've just put it here for clarity. - searcher.DetermineRenderingEngine(); - - //TODO: here we should launch an event so that people can modify the doc request to do whatever they want. - - // redirect if it has been flagged - if (docreq.IsRedirect) - httpContext.Response.Redirect(docreq.RedirectUrl, true); - - // handle 404 - if (docreq.Is404) - { - httpContext.Response.StatusCode = 404; - - if (!docreq.HasNode) - httpContext.RemapHandler(new DocumentNotFoundHandler()); - else if (!docreq.HasTemplate) - httpContext.RemapHandler(new NoTemplateHandler()); - - // else we have a document to render - } - - if (docreq.HasNode && docreq.HasTemplate) - { - // everything is ready to pass off to our handlers (mvc or webforms) - // still need to setup a few things to deal with legacy code - - // assign the legagcy page back to the docrequest - // handlers like default.aspx will want it - docreq.UmbracoPage = new page(docreq); - - // these two are used by many legacy objects - httpContext.Items["pageID"] = docreq.DocumentId; - httpContext.Items["pageElements"] = docreq.UmbracoPage.Elements; - - RewriteToUmbracoHandler(HttpContext.Current, uri.Query, docreq.RenderingEngine); - } + httpContext.RemapHandler(new DocumentNotFoundHandler()); + return; } + + // else we have a document to render + // not having a template is ok here, MVC will take care of it + } + + // just be safe - should never ever happen + if (!docreq.HasNode) + throw new Exception("No document to render."); + + // render even though we might have no template + // to give MVC a chance to hijack routes + // pass off to our handlers (mvc or webforms) + + // assign the legacy page back to the docrequest + // handlers like default.aspx will want it + docreq.UmbracoPage = new page(docreq); + + // these two are used by many legacy objects + httpContext.Items["pageID"] = docreq.DocumentId; + httpContext.Items["pageElements"] = docreq.UmbracoPage.Elements; + + RewriteToUmbracoHandler(HttpContext.Current, uri.Query, docreq.RenderingEngine); } /// @@ -212,6 +220,9 @@ namespace Umbraco.Web // ensure Umbraco is properly configured to serve documents if (!EnsureIsConfigured(httpContext, uri)) return false; + // ensure Umbraco has documents to serve + if (!EnsureHasContent(httpContext)) + return false; return true; } @@ -298,6 +309,27 @@ namespace Umbraco.Web return true; } + // ensures Umbraco has at least one published node + // if not, rewrites to splash and return false + // if yes, return true + bool EnsureHasContent(HttpContextBase httpContext) + { + var context = UmbracoContext.Current; + var store = context.RoutingContext.PublishedContentStore; + if (!store.HasContent(context)) + { + LogHelper.Warn("Umbraco has not content"); + + var noContentUrl = UriUtility.ToAbsolute(UmbracoSettings.NoContentSplashPage); + httpContext.RewritePath(noContentUrl); + return false; + } + else + { + return true; + } + } + // ensures Umbraco is configured // if not, redirect to install and return false // if yes, return true