fix NiceUrl and add more tests

This commit is contained in:
Stephan
2012-09-30 12:24:23 -02:00
parent 926427ff68
commit 3e99dd0d55
8 changed files with 329 additions and 164 deletions

View File

@@ -18,7 +18,6 @@ namespace Umbraco.Web.Routing
/// </summary>
internal class NiceUrlProvider
{
internal const string NullUrl = "#";
/// <summary>
@@ -30,37 +29,56 @@ namespace Umbraco.Web.Routing
{
_umbracoContext = umbracoContext;
_publishedContentStore = publishedContentStore;
this.EnforceAbsoluteUrls = false;
}
private readonly UmbracoContext _umbracoContext;
private readonly IPublishedContentStore _publishedContentStore;
public bool EnforceAbsoluteUrls { get; set; }
#region GetNiceUrl
/// <summary>
/// Gets the nice url of a node.
/// </summary>
/// <param name="nodeId">The node id.</param>
/// <param name="nodeId">The node identifier.</param>
/// <returns>The nice url for the node.</returns>
/// <remarks>The url is absolute or relative depending on the current url.</remarks>
/// <remarks>The url is absolute or relative depending on the current url, settings, and options.</remarks>
public string GetNiceUrl(int nodeId)
{
return GetNiceUrl(nodeId, _umbracoContext.UmbracoUrl, false);
var absolute = UmbracoSettings.UseDomainPrefixes || this.EnforceAbsoluteUrls;
return GetNiceUrl(nodeId, _umbracoContext.UmbracoUrl, absolute);
}
/// <summary>
/// Gets the nice url of a node.
/// </summary>
/// <param name="nodeId">The node identifier.</param>
/// <param name="absolute">A value indicating whether the url should be absolute in any case.</param>
/// <returns>The nice url for the node.</returns>
/// <remarks>The url is absolute or relative depending on the current url, unless <c>absolute</c> is true, in which case the url is always absolute.</remarks>
public string GetNiceUrl(int nodeId, bool absolute)
{
return GetNiceUrl(nodeId, _umbracoContext.UmbracoUrl, absolute);
}
/// <summary>
/// Gets the nice url of a node.
/// </summary>
/// <param name="nodeId">The node id.</param>
/// <param name="current">The current url.</param>
/// <param name="current">The current absolute url.</param>
/// <param name="absolute">A value indicating whether the url should be absolute in any case.</param>
/// <returns>The nice url for the node.</returns>
/// <remarks>The url is absolute or relative depending on the current url, unless absolute is true, and then it is always absolute.</remarks>
/// <remarks>The url is absolute or relative depending on url indicated by <c>current</c>, unless <c>absolute</c> is true, in which case the url is always absolute.</remarks>
public string GetNiceUrl(int nodeId, Uri current, bool absolute)
{
Uri domainUri;
string path;
if (!current.IsAbsoluteUri)
throw new ArgumentException("Current url must be absolute.", "current");
// do not read cache if previewing
var route = _umbracoContext.InPreviewMode
? null
@@ -105,26 +123,7 @@ namespace Umbraco.Web.Routing
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
if (domainUri == null && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
{
// in theory if hideTopLevelNodeFromPath is true, then there should be only once
// top-level node, or else domains should be assigned. but for backward compatibility
// we add this check - we look for the document matching "/" and if it's not us, then
// we do not hide the top level path
// it has to be taken care of in IPublishedContentStore.GetDocumentByRoute too so if
// "/foo" fails (looking for "/*/foo") we try also "/foo".
// this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but
// that's the way it works pre-4.10 and we try to be backward compat for the time being
if (node.Parent == null)
{
var rootNode = _publishedContentStore.GetDocumentByRoute(_umbracoContext, "/", true);
if (rootNode.Id == node.Id) // remove only if we're the default node
pathParts.RemoveAt(pathParts.Count - 1);
}
else
{
pathParts.RemoveAt(pathParts.Count - 1);
}
}
ApplyHideTopLevelNodeFromPath(node, pathParts);
// assemble the route
pathParts.Reverse();
@@ -140,6 +139,15 @@ namespace Umbraco.Web.Routing
return AssembleUrl(domainUri, path, current, absolute).ToString();
}
#endregion
#region GetAlternateNiceUrls
public IEnumerable<string> GetAllAbsoluteNiceUrls(int nodeId)
{
return GetAlternateNiceUrls(nodeId, _umbracoContext.UmbracoUrl);
}
/// <summary>
/// Gets the nice urls of a node.
/// </summary>
@@ -147,7 +155,7 @@ namespace Umbraco.Web.Routing
/// <param name="current">The current url.</param>
/// <returns>An enumeration of all valid urls for the node.</returns>
/// <remarks>The urls are absolute. A node can have more than one url if more than one domain is defined.</remarks>
public IEnumerable<string> GetNiceUrls(int nodeId, Uri current)
public IEnumerable<string> GetAlternateNiceUrls(int nodeId, Uri current)
{
// this is for editContent.aspx which had its own, highly buggy, implementation of NiceUrl...
//TODO: finalize & test implementation then replace in editContent.aspx
@@ -160,55 +168,77 @@ namespace Umbraco.Web.Routing
? null
: _umbracoContext.RoutesCache.GetRoute(nodeId);
if (route != null)
if (!string.IsNullOrEmpty(route))
{
// route is <id>/<path> eg "-1/", "-1/foo", "123/", "123/foo/bar"...
// there was a route in the cache - extract domainUri and path
// route is /<path> or <domainRootId>/<path>
int pos = route.IndexOf('/');
path = route.Substring(pos);
int id = int.Parse(route.Substring(0, pos)); // will be -1 or 1234
domainUris = id > 0 ? DomainUrisAtNode(id, current) : new Uri[] { };
path = pos == 0 ? route : route.Substring(pos);
domainUris = pos == 0 ? new Uri[] { } : DomainUrisAtNode(int.Parse(route.Substring(0, pos)), current);
}
else
{
// there was no route in the cache - create a route
var node = _publishedContentStore.GetDocumentById(_umbracoContext, nodeId);
if (node == null)
return new string[] { NullUrl }; // legacy wrote to the log here...
var pathParts = new List<string>();
int id = nodeId;
domainUris = DomainUrisAtNode(id, current);
while (!domainUris.Any() && id > 0)
{
LogHelper.Warn<NiceUrlProvider>(
"Couldn't find any page with nodeId={0}. This is most likely caused by the page not being published.",
nodeId);
return new string[] { NullUrl };
}
// walk up from that node until we hit a node with domains,
// or we reach the content root, collecting urls in the way
var pathParts = new List<string>();
var n = node;
domainUris = DomainUrisAtNode(n.Id, current);
while (!domainUris.Any() && n != null) // n is null at root
{
// get the url
var urlName = node.UrlName;
pathParts.Add(urlName);
node = node.Parent; //set to parent node
id = node.Id;
domainUris = id > 0 ? DomainUrisAtNode(id, current) : new Uri[] { };
// move to parent node
n = n.Parent;
domainUris = n == null ? new Uri[] { } : DomainUrisAtNode(n.Id, current);
}
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
if (!domainUris.Any() && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
pathParts.RemoveAt(pathParts.Count - 1);
ApplyHideTopLevelNodeFromPath(node, pathParts);
// assemble the route
pathParts.Reverse();
path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
route = id.ToString() + path;
route = (n == null ? "" : n.Id.ToString()) + path;
// do not store if previewing
if (!_umbracoContext.InPreviewMode)
_umbracoContext.RoutesCache.Store(nodeId, route);
}
// assemble the alternate urls from domainUris (maybe empty) and path
return AssembleUrls(domainUris, path, current).Select(uri => uri.ToString());
}
#endregion
#region Utilities
Uri AssembleUrl(Uri domainUri, string path, Uri current, bool absolute)
{
Uri uri;
if (domainUri == null)
{
// no domain was found : return a relative url, add vdir if any
uri = new Uri(SystemDirectories.Root + path, UriKind.Relative);
// no domain was found : return an absolute or relative url
// handle vdir if any
if (!absolute || current == null)
uri = new Uri(UriUtility.ToAbsolute(path), UriKind.Relative);
else
uri = new Uri(current.GetLeftPart(UriPartial.Authority) + UriUtility.ToAbsolute(path));
}
else
{
@@ -229,17 +259,26 @@ namespace Umbraco.Web.Routing
return path == "/" ? path : path.TrimEnd('/');
}
// always build absolute urls unless we really cannot
IEnumerable<Uri> AssembleUrls(IEnumerable<Uri> domainUris, string path, Uri current)
{
if (domainUris.Any())
List<Uri> uris = new List<Uri>();
if (!domainUris.Any())
{
return domainUris.Select(domainUri => new Uri(domainUri.GetLeftPart(UriPartial.Path).TrimEnd('/') + path));
// no domain was found : return an absolute or relative url
// handle vdir if any
if (current == null)
uris.Add(new Uri(UriUtility.ToAbsolute(path), UriKind.Relative));
else
uris.Add(new Uri(current.GetLeftPart(UriPartial.Authority) + UriUtility.ToAbsolute(path)));
}
else
{
// no domain was found : return a relative url, add vdir if any
return new Uri[] { new Uri(SystemDirectories.Root + path, UriKind.Relative) };
// domains were found : return -- FIXME?
uris.AddRange(domainUris.Select(domainUri => new Uri(domainUri.GetLeftPart(UriPartial.Path).TrimEnd('/') + path)));
}
return uris.Select(uri => UriUtility.UriFromUmbraco(uri));
}
Uri DomainUriAtNode(int nodeId, Uri current)
@@ -263,6 +302,28 @@ namespace Umbraco.Web.Routing
return domainAndUris.Select(d => d.Uri);
}
void ApplyHideTopLevelNodeFromPath(Core.Models.IDocument node, List<string> pathParts)
{
// in theory if hideTopLevelNodeFromPath is true, then there should be only once
// top-level node, or else domains should be assigned. but for backward compatibility
// we add this check - we look for the document matching "/" and if it's not us, then
// we do not hide the top level path
// it has to be taken care of in IPublishedContentStore.GetDocumentByRoute too so if
// "/foo" fails (looking for "/*/foo") we try also "/foo".
// this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but
// that's the way it works pre-4.10 and we try to be backward compat for the time being
if (node.Parent == null)
{
var rootNode = _publishedContentStore.GetDocumentByRoute(_umbracoContext, "/", true);
if (rootNode.Id == node.Id) // remove only if we're the default node
pathParts.RemoveAt(pathParts.Count - 1);
}
else
{
pathParts.RemoveAt(pathParts.Count - 1);
}
}
#endregion
}
}