This commit is contained in:
Shannon Deminick
2013-03-12 22:58:53 +04:00
55 changed files with 2503 additions and 752 deletions

View File

@@ -25,7 +25,7 @@ namespace Umbraco.Tests.Routing
void InitializeLanguagesAndDomains()
{
var domains = Domain.GetDomains();
var domains = Domain.GetDomains(true); // we want wildcards too here
foreach (var d in domains)
d.Delete();

View File

@@ -26,6 +26,10 @@ namespace Umbraco.Tests.Routing
true);
SettingsForTests.SettingsFilePath = Core.IO.IOHelper.MapPath(Core.IO.SystemDirectories.Config + Path.DirectorySeparatorChar, false);
SiteDomainHelperResolver.Reset();
SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper());
FreezeResolution();
}
internal override IRoutesCache GetRoutesCache()
@@ -58,14 +62,14 @@ namespace Umbraco.Tests.Routing
foreach (var sample in samples)
{
var result = routingContext.NiceUrlProvider.GetNiceUrl(sample.Key);
var result = routingContext.UrlProvider.GetUrl(sample.Key);
Assert.AreEqual(sample.Value, result);
}
var randomSample = new KeyValuePair<int, string>(1177, "/home/sub1/custom-sub-1");
for (int i = 0; i < 5; i++)
{
var result = routingContext.NiceUrlProvider.GetNiceUrl(randomSample.Key);
var result = routingContext.UrlProvider.GetUrl(randomSample.Key);
Assert.AreEqual(randomSample.Value, result);
}
@@ -106,7 +110,7 @@ namespace Umbraco.Tests.Routing
SettingsForTests.HideTopLevelNodeFromPath = false;
SettingsForTests.UseDomainPrefixes = false;
var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId);
var result = routingContext.UrlProvider.GetUrl(nodeId);
Assert.AreEqual(niceUrlMatch, result);
}
@@ -129,7 +133,7 @@ namespace Umbraco.Tests.Routing
SettingsForTests.HideTopLevelNodeFromPath = true;
SettingsForTests.UseDomainPrefixes = false;
var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId);
var result = routingContext.UrlProvider.GetUrl(nodeId);
Assert.AreEqual(niceUrlMatch, result);
}
@@ -142,14 +146,14 @@ namespace Umbraco.Tests.Routing
SettingsForTests.HideTopLevelNodeFromPath = false;
SettingsForTests.UseDomainPrefixes = false;
Assert.AreEqual("/home/sub1/custom-sub-1/", routingContext.NiceUrlProvider.GetNiceUrl(1177));
Assert.AreEqual("/home/sub1/custom-sub-1/", routingContext.UrlProvider.GetUrl(1177));
SettingsForTests.UseDomainPrefixes = true;
Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", routingContext.NiceUrlProvider.GetNiceUrl(1177));
Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", routingContext.UrlProvider.GetUrl(1177));
SettingsForTests.UseDomainPrefixes = false;
routingContext.NiceUrlProvider.EnforceAbsoluteUrls = true;
Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", routingContext.NiceUrlProvider.GetNiceUrl(1177));
routingContext.UrlProvider.EnforceAbsoluteUrls = true;
Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", routingContext.UrlProvider.GetUrl(1177));
}
[Test]
@@ -161,12 +165,12 @@ namespace Umbraco.Tests.Routing
SettingsForTests.HideTopLevelNodeFromPath = false;
SettingsForTests.UseDomainPrefixes = false;
Assert.AreEqual("#", routingContext.NiceUrlProvider.GetNiceUrl(999999));
Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999));
SettingsForTests.UseDomainPrefixes = true;
Assert.AreEqual("#", routingContext.NiceUrlProvider.GetNiceUrl(999999));
Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999));
SettingsForTests.UseDomainPrefixes = false;
routingContext.NiceUrlProvider.EnforceAbsoluteUrls = true;
Assert.AreEqual("#", routingContext.NiceUrlProvider.GetNiceUrl(999999));
routingContext.UrlProvider.EnforceAbsoluteUrls = true;
Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999));
}
}
}

View File

@@ -20,6 +20,10 @@ namespace Umbraco.Tests.Routing
// ensure we can create them although the content is not in the database
TestHelper.DropForeignKeys("umbracoDomains");
SiteDomainHelperResolver.Reset();
SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper());
FreezeResolution();
}
internal override IRoutesCache GetRoutesCache()
@@ -202,7 +206,7 @@ namespace Umbraco.Tests.Routing
SetDomains1();
var currentUri = new Uri(currentUrl);
var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId, currentUri, absolute);
var result = routingContext.UrlProvider.GetUrl(nodeId, currentUri, absolute);
Assert.AreEqual(expected, result);
}
@@ -231,7 +235,7 @@ namespace Umbraco.Tests.Routing
SetDomains2();
var currentUri = new Uri(currentUrl);
var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId, currentUri, absolute);
var result = routingContext.UrlProvider.GetUrl(nodeId, currentUri, absolute);
Assert.AreEqual(expected, result);
}
@@ -252,7 +256,7 @@ namespace Umbraco.Tests.Routing
SetDomains3();
var currentUri = new Uri(currentUrl);
var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId, currentUri, absolute);
var result = routingContext.UrlProvider.GetUrl(nodeId, currentUri, absolute);
Assert.AreEqual(expected, result);
}
@@ -279,7 +283,7 @@ namespace Umbraco.Tests.Routing
SetDomains4();
var currentUri = new Uri(currentUrl);
var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId, currentUri, absolute);
var result = routingContext.UrlProvider.GetUrl(nodeId, currentUri, absolute);
Assert.AreEqual(expected, result);
}
@@ -296,17 +300,17 @@ namespace Umbraco.Tests.Routing
SetDomains4();
string ignore;
ignore = routingContext.NiceUrlProvider.GetNiceUrl(1001, new Uri("http://domain1.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(10011, new Uri("http://domain1.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(100111, new Uri("http://domain1.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(10012, new Uri("http://domain1.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(100121, new Uri("http://domain1.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(10013, new Uri("http://domain1.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(1002, new Uri("http://domain1.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(1001, new Uri("http://domain2.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(10011, new Uri("http://domain2.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(100111, new Uri("http://domain2.com"), false);
ignore = routingContext.NiceUrlProvider.GetNiceUrl(1002, new Uri("http://domain2.com"), false);
ignore = routingContext.UrlProvider.GetUrl(1001, new Uri("http://domain1.com"), false);
ignore = routingContext.UrlProvider.GetUrl(10011, new Uri("http://domain1.com"), false);
ignore = routingContext.UrlProvider.GetUrl(100111, new Uri("http://domain1.com"), false);
ignore = routingContext.UrlProvider.GetUrl(10012, new Uri("http://domain1.com"), false);
ignore = routingContext.UrlProvider.GetUrl(100121, new Uri("http://domain1.com"), false);
ignore = routingContext.UrlProvider.GetUrl(10013, new Uri("http://domain1.com"), false);
ignore = routingContext.UrlProvider.GetUrl(1002, new Uri("http://domain1.com"), false);
ignore = routingContext.UrlProvider.GetUrl(1001, new Uri("http://domain2.com"), false);
ignore = routingContext.UrlProvider.GetUrl(10011, new Uri("http://domain2.com"), false);
ignore = routingContext.UrlProvider.GetUrl(100111, new Uri("http://domain2.com"), false);
ignore = routingContext.UrlProvider.GetUrl(1002, new Uri("http://domain2.com"), false);
var cachedRoutes = ((DefaultRoutesCache)routingContext.RoutesCache).GetCachedRoutes();
Assert.AreEqual(7, cachedRoutes.Count);
@@ -323,15 +327,15 @@ namespace Umbraco.Tests.Routing
CheckRoute(cachedRoutes, cachedIds, 1002, "/1002");
// use the cache
Assert.AreEqual("/", routingContext.NiceUrlProvider.GetNiceUrl(1001, new Uri("http://domain1.com"), false));
Assert.AreEqual("/en/", routingContext.NiceUrlProvider.GetNiceUrl(10011, new Uri("http://domain1.com"), false));
Assert.AreEqual("/en/1001-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100111, new Uri("http://domain1.com"), false));
Assert.AreEqual("/fr/", routingContext.NiceUrlProvider.GetNiceUrl(10012, new Uri("http://domain1.com"), false));
Assert.AreEqual("/fr/1001-2-1/", routingContext.NiceUrlProvider.GetNiceUrl(100121, new Uri("http://domain1.com"), false));
Assert.AreEqual("/1001-3/", routingContext.NiceUrlProvider.GetNiceUrl(10013, new Uri("http://domain1.com"), false));
Assert.AreEqual("/1002/", routingContext.NiceUrlProvider.GetNiceUrl(1002, new Uri("http://domain1.com"), false));
Assert.AreEqual("/", routingContext.UrlProvider.GetUrl(1001, new Uri("http://domain1.com"), false));
Assert.AreEqual("/en/", routingContext.UrlProvider.GetUrl(10011, new Uri("http://domain1.com"), false));
Assert.AreEqual("/en/1001-1-1/", routingContext.UrlProvider.GetUrl(100111, new Uri("http://domain1.com"), false));
Assert.AreEqual("/fr/", routingContext.UrlProvider.GetUrl(10012, new Uri("http://domain1.com"), false));
Assert.AreEqual("/fr/1001-2-1/", routingContext.UrlProvider.GetUrl(100121, new Uri("http://domain1.com"), false));
Assert.AreEqual("/1001-3/", routingContext.UrlProvider.GetUrl(10013, new Uri("http://domain1.com"), false));
Assert.AreEqual("/1002/", routingContext.UrlProvider.GetUrl(1002, new Uri("http://domain1.com"), false));
Assert.AreEqual("http://domain1.com/fr/1001-2-1/", routingContext.NiceUrlProvider.GetNiceUrl(100121, new Uri("http://domain2.com"), false));
Assert.AreEqual("http://domain1.com/fr/1001-2-1/", routingContext.UrlProvider.GetUrl(100121, new Uri("http://domain2.com"), false));
}
void CheckRoute(IDictionary<int, string> routes, IDictionary<string, int> ids, int id, string route)
@@ -354,23 +358,23 @@ namespace Umbraco.Tests.Routing
SetDomains4();
SettingsForTests.UseDomainPrefixes = false;
Assert.AreEqual("/en/1001-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100111));
Assert.AreEqual("http://domain3.com/en/1003-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100311));
Assert.AreEqual("/en/1001-1-1/", routingContext.UrlProvider.GetUrl(100111));
Assert.AreEqual("http://domain3.com/en/1003-1-1/", routingContext.UrlProvider.GetUrl(100311));
SettingsForTests.UseDomainPrefixes = true;
Assert.AreEqual("http://domain1.com/en/1001-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100111));
Assert.AreEqual("http://domain3.com/en/1003-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100311));
Assert.AreEqual("http://domain1.com/en/1001-1-1/", routingContext.UrlProvider.GetUrl(100111));
Assert.AreEqual("http://domain3.com/en/1003-1-1/", routingContext.UrlProvider.GetUrl(100311));
SettingsForTests.UseDomainPrefixes = false;
routingContext.NiceUrlProvider.EnforceAbsoluteUrls = true;
Assert.AreEqual("http://domain1.com/en/1001-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100111));
Assert.AreEqual("http://domain3.com/en/1003-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100311));
routingContext.UrlProvider.EnforceAbsoluteUrls = true;
Assert.AreEqual("http://domain1.com/en/1001-1-1/", routingContext.UrlProvider.GetUrl(100111));
Assert.AreEqual("http://domain3.com/en/1003-1-1/", routingContext.UrlProvider.GetUrl(100311));
}
[Test]
public void Get_Nice_Url_Alternate()
{
var routingContext = GetRoutingContext("http://domain1.com/test", 1111);
var routingContext = GetRoutingContext("http://domain1.com/en/test", 1111);
SettingsForTests.UseDirectoryUrls = true;
SettingsForTests.HideTopLevelNodeFromPath = false;
@@ -378,13 +382,12 @@ namespace Umbraco.Tests.Routing
InitializeLanguagesAndDomains();
SetDomains5();
var result = routingContext.NiceUrlProvider.GetAllAbsoluteNiceUrls(100111);
var url = routingContext.UrlProvider.GetUrl(100111, true);
Assert.AreEqual("http://domain1.com/en/1001-1-1/", url);
var result = routingContext.UrlProvider.GetOtherUrls(100111).ToArray();
// will always get absolute urls
// all of them
// including the local one - duplicate?! - then must manually exclude?
Assert.AreEqual(3, result.Count());
Assert.IsTrue(result.Contains("http://domain1.com/en/1001-1-1/"));
Assert.AreEqual(2, result.Count());
Assert.IsTrue(result.Contains("http://domain1a.com/en/1001-1-1/"));
Assert.IsTrue(result.Contains("http://domain1b.com/en/1001-1-1/"));
}

View File

@@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web.Routing;
using umbraco.cms.businesslogic.web;
using System.Reflection;
namespace Umbraco.Tests.Routing
{
[TestFixture]
public class SiteDomainHelperTests
{
[SetUp]
public void SetUp()
{
SiteDomainHelper.Clear(); // assuming this works!
}
[Test]
public void AddSites()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
var sites = SiteDomainHelper.Sites;
Assert.AreEqual(2, sites.Count);
Assert.Contains("site1", sites.Keys);
Assert.Contains("site2", sites.Keys);
var domains = sites["site1"];
Assert.AreEqual(3, domains.Count());
Assert.Contains("domain1.com", domains);
Assert.Contains("domain1.net", domains);
Assert.Contains("domain1.org", domains);
domains = sites["site2"];
Assert.AreEqual(3, domains.Count());
Assert.Contains("domain2.com", domains);
Assert.Contains("domain2.net", domains);
Assert.Contains("domain2.org", domains);
}
[TestCase("foo")] // that one is suspect
[TestCase("domain.com")]
[TestCase("domain.com/")]
[TestCase("domain.com:12")]
[TestCase("domain.com:12/")]
[TestCase("http://www.domain.com")]
[TestCase("http://www.domain.com:12")]
[TestCase("http://www.domain.com:12/")]
[TestCase("https://foo.www.domain.com")]
[TestCase("https://foo.www.domain.com:5478/")]
public void AddValidSite(string domain)
{
SiteDomainHelper.AddSite("site1", domain);
}
[TestCase("domain.com/foo")]
[TestCase("http:/domain.com")]
[TestCase("*")]
public void AddInvalidSite(string domain)
{
Assert.Throws<ArgumentOutOfRangeException>(() => SiteDomainHelper.AddSite("site1", domain));
}
[Test]
public void AddRemoveSites()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
var sites = SiteDomainHelper.Sites;
SiteDomainHelper.RemoveSite("site1");
SiteDomainHelper.RemoveSite("site3");
Assert.AreEqual(1, sites.Count);
Assert.Contains("site2", sites.Keys);
}
[Test]
public void AddSiteAgain()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site1", "domain2.com", "domain1.net");
var sites = SiteDomainHelper.Sites;
Assert.AreEqual(1, sites.Count);
Assert.Contains("site1", sites.Keys);
var domains = sites["site1"];
Assert.AreEqual(2, domains.Count());
Assert.Contains("domain2.com", domains);
Assert.Contains("domain1.net", domains);
}
[Test]
public void BindSitesOnce()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org");
SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org");
SiteDomainHelper.BindSites("site1", "site2");
var bindings = SiteDomainHelper.Bindings;
Assert.AreEqual(2, bindings.Count);
Assert.Contains("site1", bindings.Keys);
Assert.Contains("site2", bindings.Keys);
var others = bindings["site1"];
Assert.AreEqual(1, others.Count);
Assert.Contains("site2", others);
others = bindings["site2"];
Assert.AreEqual(1, others.Count);
Assert.Contains("site1", others);
}
[Test]
public void BindMoreSites()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org");
SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org");
SiteDomainHelper.BindSites("site1", "site2");
SiteDomainHelper.BindSites("site1", "site3");
var bindings = SiteDomainHelper.Bindings;
Assert.AreEqual(3, bindings.Count);
Assert.Contains("site1", bindings.Keys);
Assert.Contains("site2", bindings.Keys);
Assert.Contains("site3", bindings.Keys);
var others = bindings["site1"];
Assert.AreEqual(2, others.Count);
Assert.Contains("site2", others);
Assert.Contains("site3", others);
others = bindings["site2"];
Assert.AreEqual(2, others.Count);
Assert.Contains("site1", others);
Assert.Contains("site3", others);
others = bindings["site3"];
Assert.AreEqual(2, others.Count);
Assert.Contains("site1", others);
Assert.Contains("site2", others);
}
[Test]
public void MapDomain()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org");
SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org");
//SiteDomainHelper.BindSites("site1", "site3");
//SiteDomainHelper.BindSites("site2", "site4");
// map methods are not static because we can override them
var helper = new SiteDomainHelper();
// current is a site1 uri, domains contain current
// so we'll get current
//
var current = new Uri("http://domain1.com/foo/bar");
var output = helper.MapDomain(current, new[]
{
new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp),
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp),
}).Uri.ToString();
Assert.AreEqual("http://domain1.com/", output);
// current is a site1 uri, domains do not contain current
// so we'll get the corresponding site1 domain
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomain(current, new[]
{
new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp),
new DomainAndUri(new MockDomain("domain2.net"), Uri.UriSchemeHttp)
}).Uri.ToString();
Assert.AreEqual("http://domain1.net/", output);
// current is a site1 uri, domains do not contain current
// so we'll get the corresponding site1 domain
// order does not matter
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomain(current, new[]
{
new DomainAndUri(new MockDomain("domain2.net"), Uri.UriSchemeHttp),
new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp)
}).Uri.ToString();
Assert.AreEqual("http://domain1.net/", output);
}
[Test]
public void MapDomains()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org");
SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org");
// map methods are not static because we can override them
var helper = new SiteDomainHelper();
// the rule is:
// - exclude the current domain
// - exclude what MapDomain would return
// - return all domains from same site, or bound sites
// current is a site1 uri, domains contains current
//
var current = new Uri("http://domain1.com/foo/bar");
var output = helper.MapDomains(current, new[]
{
new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp), // no: current + what MapDomain would pick
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup)
}, true).ToArray();
Assert.AreEqual(1, output.Count());
Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray());
// current is a site1 uri, domains does not contain current
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomains(current, new[]
{
new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp), // no: what MapDomain would pick
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup)
}, true).ToArray();
Assert.AreEqual(1, output.Count());
Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray());
SiteDomainHelper.BindSites("site1", "site3");
SiteDomainHelper.BindSites("site2", "site4");
// current is a site1 uri, domains contains current
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomains(current, new[]
{
new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp), // no: current + what MapDomain would pick
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // yes: bound site
new DomainAndUri(new MockDomain("domain3.org"), Uri.UriSchemeHttp), // yes: bound site
new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup)
}, true).ToArray();
Assert.AreEqual(3, output.Count());
Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray());
Assert.Contains("http://domain3.com/", output.Select(d => d.Uri.ToString()).ToArray());
Assert.Contains("http://domain3.org/", output.Select(d => d.Uri.ToString()).ToArray());
// current is a site1 uri, domains does not contain current
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomains(current, new[]
{
new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp), // no: what MapDomain would pick
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // yes: bound site
new DomainAndUri(new MockDomain("domain3.org"), Uri.UriSchemeHttp), // yes: bound site
new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup)
}, true).ToArray();
Assert.AreEqual(3, output.Count());
Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray());
Assert.Contains("http://domain3.com/", output.Select(d => d.Uri.ToString()).ToArray());
Assert.Contains("http://domain3.org/", output.Select(d => d.Uri.ToString()).ToArray());
}
class MockDomain : Domain
{
private static readonly FieldInfo NameField = typeof (Domain).GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic);
public MockDomain(string name)
{
NameField.SetValue(this, name);
}
}
}
}

View File

@@ -34,7 +34,7 @@ namespace Umbraco.Tests.Routing
// get the nice url for 100111
routingContext = GetRoutingContext(url);
Assert.AreEqual("http://domain2.com/1001-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100111, true));
Assert.AreEqual("http://domain2.com/1001-1-1/", routingContext.UrlProvider.GetUrl(100111, true));
// check that the proper route has been cached
var cachedRoutes = ((DefaultRoutesCache)routingContext.RoutesCache).GetCachedRoutes();
@@ -59,7 +59,7 @@ namespace Umbraco.Tests.Routing
//Assert.AreEqual("1001/1001-1/1001-1-1", cachedRoutes[100111]); // yes
// what's the nice url now?
Assert.AreEqual("http://domain2.com/1001-1-1/", routingContext.NiceUrlProvider.GetNiceUrl(100111)); // good
Assert.AreEqual("http://domain2.com/1001-1-1/", routingContext.UrlProvider.GetUrl(100111)); // good
//Assert.AreEqual("http://domain1.com/1001-1/1001-1-1", routingContext.NiceUrlProvider.GetNiceUrl(100111, true)); // bad
}
@@ -69,6 +69,10 @@ namespace Umbraco.Tests.Routing
// ensure we can create them although the content is not in the database
TestHelper.DropForeignKeys("umbracoDomains");
SiteDomainHelperResolver.Reset();
SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper());
FreezeResolution();
}
internal override IRoutesCache GetRoutesCache()

View File

@@ -34,13 +34,13 @@ namespace Umbraco.Tests.Routing
var umbracoContext = GetUmbracoContext(url, t.Id);
var contentStore = new DefaultPublishedContentStore();
var niceUrls = new NiceUrlProvider(contentStore, umbracoContext);
var urlProvider = new UrlProvider(umbracoContext, contentStore, new IUrlProvider[] { new DefaultUrlProvider() });
var routingContext = new RoutingContext(
umbracoContext,
lookups,
new FakeLastChanceFinder(),
contentStore,
niceUrls,
urlProvider,
GetRoutesCache());
//assign the routing context back to the umbraco context

View File

@@ -28,13 +28,13 @@ namespace Umbraco.Tests.TestHelpers
{
var umbracoContext = GetUmbracoContext(url, templateId, routeData);
var contentStore = new DefaultPublishedContentStore();
var niceUrls = new NiceUrlProvider(contentStore, umbracoContext);
var urlProvider = new UrlProvider(umbracoContext, contentStore, new IUrlProvider[] { new DefaultUrlProvider() });
var routingContext = new RoutingContext(
umbracoContext,
Enumerable.Empty<IContentFinder>(),
new FakeLastChanceFinder(),
contentStore,
niceUrls,
urlProvider,
GetRoutesCache());
//assign the routing context back to the umbraco context

View File

@@ -287,6 +287,7 @@
<Compile Include="Routing\ContentFinderByNiceUrlWithDomainsTests.cs" />
<Compile Include="Routing\DomainsAndCulturesTests.cs" />
<Compile Include="Routing\NiceUrlsProviderWithDomainsTests.cs" />
<Compile Include="Routing\SiteDomainHelperTests.cs" />
<Compile Include="Routing\uQueryGetNodeIdByUrlTests.cs" />
<Compile Include="Routing\UrlsWithNestedDomains.cs" />
<Compile Include="Services\BaseServiceTest.cs" />

View File

@@ -571,6 +571,7 @@
<Content Include="Umbraco\Developer\RelationTypes\RelationTypesWebService.asmx" />
<Content Include="Umbraco\Developer\RelationTypes\TreeMenu\ActionDeleteRelationType.js" />
<Content Include="Umbraco\Developer\RelationTypes\TreeMenu\ActionNewRelationType.js" />
<Content Include="Umbraco\Dialogs\AssignDomain2.aspx" />
<Content Include="Umbraco\Dialogs\EditMacro.aspx" />
<Content Include="Umbraco\Images\delete.gif" />
<Content Include="Umbraco\Images\delete.png" />
@@ -610,6 +611,7 @@
<Content Include="Umbraco\Webservices\Api\MemberService.asmx" />
<Content Include="Umbraco\Webservices\Api\StylesheetService.asmx" />
<Content Include="Umbraco\Webservices\Api\TemplateService.asmx" />
<Content Include="Umbraco_Client\Application\JQuery\jquery.validate.min.js" />
<Content Include="Umbraco_Client\CodeMirror\Js\Lib\codemirror.css" />
<Content Include="Umbraco_Client\CodeMirror\Js\Lib\codemirror.js" />
<Content Include="Umbraco_Client\CodeMirror\Js\Lib\Util\closetag.js" />
@@ -773,6 +775,8 @@
<Content Include="Umbraco_Client\ContextMenu\Js\jquery.contextMenu.js" />
<Content Include="Umbraco_Client\Dashboards\ExamineManagement.css" />
<Content Include="Umbraco_Client\Dashboards\ExamineManagement.js" />
<Content Include="Umbraco_Client\Dialogs\AssignDomain2.js" />
<Content Include="Umbraco_Client\Dialogs\AssignDomain2.css" />
<Content Include="Umbraco_Client\Dialogs\CreateDialog.css" />
<Content Include="Umbraco_Client\Dialogs\EditMacro.css" />
<Content Include="Umbraco_Client\Dialogs\EditMacro.js" />

View File

@@ -5,7 +5,7 @@
<link>http://umbraco.org</link>
</creator>
<area alias="actions">
<key alias="assignDomain">Manage hostnames</key>
<key alias="assignDomain">Culture and Hostnames</key>
<key alias="auditTrail">Audit Trail</key>
<key alias="browse">Browse Node</key>
<key alias="copy">Copy</key>
@@ -36,19 +36,31 @@
<key alias="update">Update</key>
</area>
<area alias="assignDomain">
<key alias="permissionDenied">Permission denied.</key>
<key alias="addNew">Add new Domain</key>
<key alias="invalidDomain">Invalid hostname</key>
<key alias="remove">remove</key>
<key alias="invalidNode">Invalid node.</key>
<key alias="invalidDomain">Invalid domain format.</key>
<key alias="duplicateDomain">Domain has already been assigned.</key>
<key alias="domain">Domain</key>
<key alias="language">Language</key>
<key alias="domainCreated">New domain '%0%' has been created</key>
<key alias="domainDeleted">Domain '%0%' is deleted</key>
<key alias="domainExists">Domain '%0%' has already been assigned</key>
<key alias="domainHelp">
<![CDATA[eg: example.com, www.example.com, example.com:8080,<br/>
https://www.example.com/, example.com/en, etc. Use * to match<br/>
any domain and just set the culture.]]>
<![CDATA[Valid domain names are: "example.com", "www.example.com", "example.com:8080" or
"https://www.example.com/".<br /><br />One-level paths in domains are supported, eg. "example.com/en". However, they
they should be avoided. Better use the culture setting above.]]>
</key>
<key alias="domainUpdated">Domain '%0%' has been updated</key>
<key alias="orEdit">Edit Current Domains</key>
<key alias="inherit">Inherit</key>
<key alias="setLanguage">Culture</key>
<key alias="setLanguageHelp">
<![CDATA[Set the culture for nodes below the current node,<br /> or inherit culture from parent nodes. Will also apply<br />
to the current node, unless a domain below applies too.]]>
</key>
<key alias="setDomains">Domains</key>
</area>
<area alias="auditTrails">
<key alias="atViewingFor">Viewing for</key>

View File

@@ -0,0 +1,77 @@
<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" Codebehind="AssignDomain2.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.AssignDomain2" %>
<%@ Import Namespace="Umbraco.Web" %>
<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>
<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
<asp:Content ContentPlaceHolderID="head" runat="server">
<umb:JsInclude runat="server" FilePath="Dialogs/AssignDomain2.js" PathNameAlias="UmbracoClient" />
<umb:JsInclude runat="server" FilePath="Application/JQuery/jquery.validate.min.js" PathNameAlias="UmbracoClient" />
<umb:CssInclude runat="server" FilePath="Dialogs/AssignDomain2.css" PathNameAlias="UmbracoClient" />
<script type="text/javascript">
(function ($) {
$(document).ready(function () {
var dialog = new Umbraco.Dialogs.AssignDomain2({
nodeId: <%=GetNodeId()%>,
restServiceLocation: '<%=GetRestServicePath() %>',
invalidDomain: '<%=umbraco.ui.Text("assignDomain", "invalidDomain") %>',
duplicateDomain: '<%=umbraco.ui.Text("assignDomain", "duplicateDomain") %>',
<asp:Literal runat="server" ID="data" />
});
dialog.init();
});
})(jQuery);
</script>
</asp:Content>
<asp:Content ContentPlaceHolderID="body" runat="server">
<cc1:Feedback ID="feedback" runat="server" />
<div id="komask"></div>
<div>
<cc1:Pane runat="server" ID="pane_language">
<cc1:PropertyPanel runat="server" ID="prop_language">
<select class="language" name="language" data-bind="options: languages, optionsText: 'Code', optionsValue: 'Id', value: language, optionsCaption: '<%=umbraco.ui.Text("assignDomain", "inherit") %>'"></select>
<br /><small><%=umbraco.ui.Text("assignDomain", "setLanguageHelp") %></small>
</cc1:PropertyPanel>
</cc1:Pane>
<cc1:Pane runat="server" ID="pane_domains">
<cc1:PropertyPanel runat="server">
<table class="domains" data-bind="visible: domains().length > 0">
<thead>
<tr>
<th><%=umbraco.ui.Text("assignDomain", "domain") %></th>
<th><%=umbraco.ui.Text("assignDomain", "language") %></th>
<th />
</tr>
</thead>
<tbody data-bind="foreach: domains">
<tr>
<td valign="top"><input class="domain duplicate" data-bind="value: Name, uniqueName: true" /><input type="hidden" value="0" /></td>
<td valign="top"><select class="language" data-bind="options: $parent.languages, optionsText: 'Code', optionsValue: 'Id', value: Lang, uniqueName: true"></select></td>
<td valign="top"><a href="#" class="remove" data-bind="click: $parent.removeDomain"><%=umbraco.ui.Text("assignDomain", "remove") %></a></td>
</tr>
</tbody>
</table>
<table class="addDomain">
<tr>
<td valign="top"><button data-bind="click: addDomain"><%=umbraco.ui.Text("assignDomain", "addNew") %></button></td>
<td class="help"><small><%=umbraco.ui.Text("assignDomain", "domainHelp") %></small></td>
</tr>
</table>
</cc1:PropertyPanel>
</cc1:Pane>
<p>
<asp:PlaceHolder runat="server" ID="phSave">
<button id="btnSave"><%=umbraco.ui.Text("buttons", "save") %></button>
<em><%=umbraco.ui.Text("general", "or")%></em>
</asp:PlaceHolder>
<a href="#" style="color: #0000ff;" onclick="UmbClientMgr.closeModalWindow()"><%=umbraco.ui.Text("general", "cancel")%></a>
</p>
</div>
</asp:Content>

File diff suppressed because one or more lines are too long

View File

@@ -257,7 +257,7 @@ Umbraco.Application.Actions = function () {
/// <summary></summary>
if (UmbClientMgr.mainTree().getActionNode().nodeId != '-1' && UmbClientMgr.mainTree().getActionNode().nodeType != '') {
UmbClientMgr.openModalWindow("dialogs/assignDomain.aspx?id=" + UmbClientMgr.mainTree().getActionNode().nodeId, uiKeys['actions_assignDomain'], true, 500, 420);
UmbClientMgr.openModalWindow("dialogs/assignDomain2.aspx?id=" + UmbClientMgr.mainTree().getActionNode().nodeId, uiKeys['actions_assignDomain'], true, 500, 620);
}
},

View File

@@ -0,0 +1,58 @@
/* Custom styles for AssignDomain2.aspx dialog */
button {
font-size: 11px;
color: #333333;
font-family: Trebuchet MS, Lucida Grande, verdana, arial;
}
table.addDomain {
width: 100%;
margin-top: 8px;
}
table.domains {
width: 100%;
}
table.addDomain td.help {
padding-left:48px;
padding-top:4px;
}
button {
white-space: nowrap;
}
#komask {
background: #ffffff;
opacity: .6;
z-index: 99;
display: none;
position: absolute;
}
input.domain {
width: 296px;
}
input.domain.error {
padding: 0;
margin: 0;
}
select.language {
width: 100px;
}
label.error {
padding: 0 0 6px 0;
margin: 0;
background: none;
border:none;
}
a.remove {
color: #ff0000;
padding-left: 8px;
}

View File

@@ -0,0 +1,130 @@
Umbraco.Sys.registerNamespace("Umbraco.Dialogs");
(function ($) {
// register AssignDomain dialog
Umbraco.Dialogs.AssignDomain2 = base2.Base.extend({
_opts: null,
_isRepeated: function (element) {
var inputs = $('#form1 input.domain');
var elementName = element.attr('name');
var repeated = false;
inputs.each(function() {
var input = $(this);
if (input.attr('name') != elementName && input.val() == element.val())
repeated = true;
});
return repeated;
},
// constructor
constructor: function (opts) {
// merge options with default
this._opts = $.extend({
invalidDomain: 'Invalid domain.',
duplicateDomain: 'Domain has already been assigned.'
}, opts);
},
// public methods/variables
languages: null,
language: null,
domains: null,
addDomain: function () {
this.domains.push({
Name: "",
Lang: ""
});
},
init: function () {
var self = this;
self.domains = ko.observableArray(self._opts.domains);
self.languages = self._opts.languages;
self.language = self._opts.language;
self.removeDomain = function() { self.domains.remove(this); };
ko.applyBindings(self);
$.validator.addMethod("domain", function (value, element, param) {
var re = /^(http[s]?:\/\/)?([-\w]+(\.[-\w]+)*)(:\d+)?(\/[-\w]*)?$/gi;
return this.optional(element) || re.test(value);
}, self._opts.invalidDomain);
$.validator.addMethod("duplicate", function (value, element, param) {
return $(element).nextAll('input').val() == 0 && !self._isRepeated($(element));
}, self._opts.duplicateDomain);
$.validator.addClassRules({
domain: { domain: true },
duplicate: { duplicate: true }
});
$('#form1').validate({
debug: true,
focusCleanup: true,
onkeyup: false
});
$('#form1 input.domain').live('focus', function(event) {
if (event.type != 'focusin') return;
$(this).nextAll('input').val(0);
});
// force validation *now*
$('#form1').valid();
$('#btnSave').click(function () {
if (!$('#form1').valid())
return false;
var mask = $('#komask');
var masked = mask.next();
mask.height(masked.height());
mask.width(masked.width());
mask.show();
var data = { nodeId: self._opts.nodeId, language: self.language ? self.language : 0, domains: self.domains };
$.post(self._opts.restServiceLocation + 'SaveLanguageAndDomains', ko.toJSON(data), function (json) {
mask.hide();
if (json.Valid) {
UmbClientMgr.closeModalWindow();
}
else {
var inputs = $('#form1 input.domain');
inputs.each(function() { $(this).nextAll('input').val(0); });
for (var i = 0; i < json.Domains.length; i++) {
var d = json.Domains[i];
if (d.Duplicate == 1)
inputs.each(function() {
var input = $(this);
if (input.val() == d.Name)
input.nextAll('input').val(1);
});
}
$('#form1').valid();
}
})
.fail(function (xhr, textStatus, errorThrown) {
mask.css('opacity', 1).css('color', "#ff0000").html(xhr.responseText);
});
return false;
});
}
});
// set defaults for jQuery ajax calls
$.ajaxSetup({
dataType: 'json',
cache: false,
contentType: 'application/json; charset=utf-8'
});
})(jQuery);

View File

@@ -63,9 +63,9 @@ namespace Umbraco.Web.Models
case PublishedItemType.Content:
if (UmbracoContext.Current == null)
throw new InvalidOperationException("Cannot resolve a Url for a content item with a null UmbracoContext.Current reference");
if (UmbracoContext.Current.NiceUrlProvider == null)
if (UmbracoContext.Current.UrlProvider == null)
throw new InvalidOperationException("Cannot resolve a Url for a content item with a null UmbracoContext.Current.NiceUrlProvider reference");
_url= UmbracoContext.Current.NiceUrlProvider.GetNiceUrl(this.Id);
_url= UmbracoContext.Current.UrlProvider.GetUrl(this.Id);
break;
case PublishedItemType.Media:
var prop = GetProperty("umbracoFile");

View File

@@ -26,8 +26,8 @@ namespace Umbraco.Web.Mvc
throw new InvalidOperationException("Cannot redirect, no entity was found for id " + _pageId);
}
var result = _umbracoContext.RoutingContext.NiceUrlProvider.GetNiceUrl(PublishedContent.Id);
if (result != NiceUrlProvider.NullUrl)
var result = _umbracoContext.RoutingContext.UrlProvider.GetUrl(PublishedContent.Id);
if (result != "#")
{
_url = result;
return _url;

View File

@@ -1173,7 +1173,7 @@ namespace Umbraco.Web
if (firstNode == null)
return new DataTable(); //no children found
var urlProvider = UmbracoContext.Current.RoutingContext.NiceUrlProvider;
var urlProvider = UmbracoContext.Current.RoutingContext.UrlProvider;
//use new utility class to create table so that we don't have to maintain code in many places, just one
var dt = Umbraco.Core.DataTableExtensions.GenerateDataTable(
@@ -1204,7 +1204,7 @@ namespace Umbraco.Web
{"UpdateDate", n.UpdateDate},
{"CreatorName", n.CreatorName},
{"WriterName", n.WriterName},
{"Url", urlProvider.GetNiceUrl(n.Id)}
{"Url", urlProvider.GetUrl(n.Id)}
};
var userVals = new Dictionary<string, object>();
foreach (var p in from IPublishedContentProperty p in n.Properties where p.Value != null select p)

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides urls using the <c>umbracoUrlAlias</c> property.
/// </summary>
internal class AliasUrlProvider : IUrlProvider
{
// note - at the moment we seem to accept pretty much anything as an alias
// without any form of validation ... could even prob. kill the XPath ...
// ok, this is somewhat experimental and is NOT enabled by default
#region GetUrl
/// <summary>
/// Gets the nice url of a published content.
/// </summary>
/// <param name="umbracoContext">The Umbraco context.</param>
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</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 url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on url indicated by <c>current</c> and settings, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>If the provider is unable to provide a url, it should return <c>null</c>.</para>
/// </remarks>
public string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute)
{
return null; // we have nothing to say
}
#endregion
#region GetOtherUrls
/// <summary>
/// Gets the other urls of a published content.
/// </summary>
/// <param name="umbracoContext">The Umbraco context.</param>
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</param>
/// <param name="current">The current absolute url.</param>
/// <returns>The other urls for the published content.</returns>
/// <remarks>
/// <para>Other urls are those that <c>GetUrl</c> would not return in the current context, but would be valid
/// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...).</para>
/// </remarks>
public IEnumerable<string> GetOtherUrls(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current)
{
if (!FindByUrlAliasEnabled)
return Enumerable.Empty<string>(); // we have nothing to say
var node = contentCache.GetDocumentById(umbracoContext, id);
string umbracoUrlName = null;
if (node.HasProperty(UmbracoUrlAlias))
umbracoUrlName = node.GetPropertyValue<string>(UmbracoUrlAlias);
if (string.IsNullOrWhiteSpace(umbracoUrlName))
return Enumerable.Empty<string>();
var n = node;
var domainUris = DomainHelper.DomainsForNode(n.Id, current, false);
while (domainUris == null && n != null) // n is null at root
{
// move to parent node
n = n.Parent;
domainUris = n == null ? null : DomainHelper.DomainsForNode(n.Id, current, false);
}
var path = "/" + umbracoUrlName;
if (domainUris == null)
{
var uri = new Uri(path, UriKind.Relative);
return new[] { UriUtility.UriFromUmbraco(uri).ToString() };
}
return domainUris
.Select(domainUri => new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)))
.Select(uri => UriUtility.UriFromUmbraco(uri).ToString());
}
#endregion
#region Utilities
const string UmbracoUrlAlias = "umbracoUrlAlias";
private bool FindByUrlAliasEnabled
{
get
{
var hasFinder = ContentFinderResolver.Current.ContainsType<ContentFinderByUrlAlias>();
var hasHandler = ContentFinderResolver.Current.ContainsType<ContentFinderByNotFoundHandlers>()
&& NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias));
return hasFinder || hasHandler;
}
}
string CombinePaths(string path1, string path2)
{
string path = path1.TrimEnd('/') + path2;
return path == "/" ? path : path.TrimEnd('/');
}
#endregion
}
}

View File

@@ -1,9 +1,6 @@
using System;
using System.Diagnostics;
using System.Xml;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using umbraco.interfaces;
using Umbraco.Core;
namespace Umbraco.Web.Routing
@@ -26,10 +23,10 @@ namespace Umbraco.Web.Routing
IPublishedContent node = null;
var path = docRequest.Uri.GetAbsolutePathDecoded();
int nodeId = -1;
var nodeId = -1;
if (path != "/") // no id if "/"
{
string noSlashPath = path.Substring(1);
var noSlashPath = path.Substring(1);
if (!Int32.TryParse(noSlashPath, out nodeId))
nodeId = -1;

View File

@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
namespace Umbraco.Web.Routing
@@ -37,10 +31,9 @@ namespace Umbraco.Web.Routing
pcr.RoutingContext.UmbracoContext,
id);
if (content == null)
LogHelper.Debug<ContentFinderByLegacy404>("Could not find content with that id.");
else
LogHelper.Debug<ContentFinderByLegacy404>("Found corresponding content.");
LogHelper.Debug<ContentFinderByLegacy404>(content == null
? "Could not find content with that id."
: "Found corresponding content.");
}
else
{

View File

@@ -1,8 +1,5 @@
using System.Diagnostics;
using System.Xml;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using umbraco.interfaces;
using Umbraco.Core;
namespace Umbraco.Web.Routing
@@ -97,7 +94,8 @@ namespace Umbraco.Web.Routing
docreq.PublishedContent = node;
LogHelper.Debug<ContentFinderByNiceUrl>("Query matches, id={0}", () => docreq.PublishedContent.Id);
var iscanon = _doDomainLookup && !DomainHelper.ExistsDomainInPath(docreq.Domain, node.Path);
var rootNodeId = docreq.Domain == null ? (int?) null : docreq.Domain.RootNodeId;
var iscanon = _doDomainLookup && !DomainHelper.ExistsDomainInPath(DomainHelper.GetAllDomains(false), node.Path, rootNodeId);
if (!iscanon)
LogHelper.Debug<ContentFinderByNiceUrl>("Non canonical url");

View File

@@ -1,10 +1,6 @@
using System.Diagnostics;
using System.Xml;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using umbraco.cms.businesslogic.template;
using Umbraco.Core;
using Template = umbraco.cms.businesslogic.template.Template;
namespace Umbraco.Web.Routing
{
@@ -15,7 +11,7 @@ namespace Umbraco.Web.Routing
/// <para>Handles <c>/foo/bar/template</c> where <c>/foo/bar</c> is the nice url of a document, and <c>template</c> a template alias.</para>
/// <para>If successful, then the template of the document request is also assigned.</para>
/// </remarks>
internal class ContentFinderByNiceUrlAndTemplate : ContentFinderByNiceUrl, IContentFinder
internal class ContentFinderByNiceUrlAndTemplate : ContentFinderByNiceUrl
{
/// <summary>
/// Tries to find and assign an Umbraco document to a <c>PublishedContentRequest</c>.

View File

@@ -1,13 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using umbraco;
using umbraco.interfaces;
namespace Umbraco.Web.Routing
@@ -16,7 +8,7 @@ namespace Umbraco.Web.Routing
/// Provides an implementation of <see cref="IContentFinder"/> that runs a legacy NotFoundHandler.
/// </summary>
/// <remarks>Provided for backward compatibility.</remarks>
internal class ContentFinderByNotFoundHandler<Thandler> : IContentFinder
internal class ContentFinderByNotFoundHandler<THandler> : IContentFinder
{
/// <summary>
/// Tries to find and assign an Umbraco document to a <c>PublishedContentRequest</c>.
@@ -25,36 +17,33 @@ namespace Umbraco.Web.Routing
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
public bool TryFindDocument(PublishedContentRequest pcr)
{
var type = typeof(Thandler);
var type = typeof(THandler);
var handler = GetHandler(type);
if (handler == null)
return false;
var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers();
LogHelper.Debug<ContentFinderByNotFoundHandler<Thandler>>("Running for legacy url='{0}'.", () => url);
LogHelper.Debug<ContentFinderByNotFoundHandler<THandler>>("Running for legacy url='{0}'.", () => url);
if (handler.Execute(url) && handler.redirectID > 0)
{
LogHelper.Debug<ContentFinderByNotFoundHandler<Thandler>>("Handler '{0}' returned id={1}.", () => type.FullName, () => handler.redirectID);
LogHelper.Debug<ContentFinderByNotFoundHandler<THandler>>("Handler '{0}' returned id={1}.", () => type.FullName, () => handler.redirectID);
var content = pcr.RoutingContext.PublishedContentStore.GetDocumentById(
pcr.RoutingContext.UmbracoContext,
handler.redirectID);
if (content == null)
LogHelper.Debug<ContentFinderByNotFoundHandler<Thandler>>("Could not find content with that id.");
else
LogHelper.Debug<ContentFinderByNotFoundHandler<Thandler>>("Found corresponding content.");
LogHelper.Debug<ContentFinderByNotFoundHandler<THandler>>(content == null
? "Could not find content with that id."
: "Found corresponding content.");
pcr.PublishedContent = content;
pcr.PublishedContent = content;
return content != null;
}
else
{
LogHelper.Debug<ContentFinderByNotFoundHandler<Thandler>>("Handler '{0}' returned nothing.", () => type.FullName);
return false;
}
LogHelper.Debug<ContentFinderByNotFoundHandler<THandler>>("Handler '{0}' returned nothing.", () => type.FullName);
return false;
}
INotFoundHandler GetHandler(Type type)
@@ -65,7 +54,7 @@ namespace Umbraco.Web.Routing
}
catch (Exception e)
{
LogHelper.Error<ContentFinderByNotFoundHandler<Thandler>>(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e);
LogHelper.Error<ContentFinderByNotFoundHandler<THandler>>(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e);
return null;
}
}

View File

@@ -1,12 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Web;
using System.Xml;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using umbraco.IO;
using umbraco.interfaces;
namespace Umbraco.Web.Routing
@@ -35,8 +29,6 @@ namespace Umbraco.Web.Routing
#region Copied over and adapted from presentation.requestHandler
//FIXME: this is temporary and should be obsoleted
void HandlePageNotFound(PublishedContentRequest docRequest)
{
var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers();
@@ -45,8 +37,9 @@ namespace Umbraco.Web.Routing
foreach (var handler in GetNotFoundHandlers())
{
IContentFinder finder = null;
var handlerName = handler.GetType().FullName;
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}'.", () => handler.GetType().FullName);
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}'.", () => handlerName);
// replace with our own implementation
if (handler is global::umbraco.SearchForAlias)
@@ -60,14 +53,15 @@ namespace Umbraco.Web.Routing
if (finder != null)
{
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Replace handler '{0}' by new finder '{1}'.", () => handler.GetType().FullName, () => finder.GetType().FullName);
var finderName = finder.GetType().FullName;
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName);
if (finder.TryFindDocument(docRequest))
{
// do NOT set docRequest.PublishedContent again here as
// it would clear any template that the finder might have set
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Finder '{0}' found node with id={1}.", () => finder.GetType().FullName, () => docRequest.PublishedContent.Id);
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id);
if (docRequest.Is404)
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Finder '{0}' set status to 404.", () => finder.GetType().FullName);
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Finder '{0}' set status to 404.", () => finderName);
// if we found a document, break, don't look at more handler -- we're done
break;
@@ -80,22 +74,23 @@ namespace Umbraco.Web.Routing
// else it's a legacy handler, run
if (handler.Execute(url) && handler.redirectID > 0)
{
{
var redirectId = handler.redirectID;
docRequest.PublishedContent = docRequest.RoutingContext.PublishedContentStore.GetDocumentById(
docRequest.RoutingContext.UmbracoContext,
handler.redirectID);
redirectId);
if (!docRequest.HasPublishedContent)
{
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found node with id={1} which is not valid.", () => handler.GetType().FullName, () => handler.redirectID);
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId);
break;
}
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found valid node with id={1}.", () => handler.GetType().FullName, () => handler.redirectID);
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId);
if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404)
{
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' set status code to 404.", () => handler.GetType().FullName);
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' set status code to 404.", () => handlerName);
docRequest.Is404 = true;
}

View File

@@ -1,5 +1,3 @@
using System.Diagnostics;
using System.Xml;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using umbraco;

View File

@@ -1,8 +1,5 @@
using System.Diagnostics;
using System.Xml;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using umbraco.interfaces;
using Umbraco.Core;
namespace Umbraco.Web.Routing

View File

@@ -1,10 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Umbraco.Core;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Web.Routing

View File

@@ -0,0 +1,262 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core;
using Umbraco.Core.Logging;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides urls.
/// </summary>
internal class DefaultUrlProvider : IUrlProvider
{
#region GetUrl
/// <summary>
/// Gets the nice url of a published content.
/// </summary>
/// <param name="umbracoContext">The Umbraco context.</param>
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</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 url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on url indicated by <c>current</c> and settings, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>If the provider is unable to provide a url, it should return <c>null</c>.</para>
/// </remarks>
public virtual string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute)
{
DomainAndUri domainUri;
string path;
if (!current.IsAbsoluteUri)
// ReSharper disable LocalizableElement
throw new ArgumentException("Current url must be absolute.", "current");
// ReSharper restore LocalizableElement
// do not read cache if previewing
var route = umbracoContext.InPreviewMode
? null
: umbracoContext.RoutingContext.RoutesCache.GetRoute(id);
if (!string.IsNullOrEmpty(route))
{
// there was a route in the cache - extract domainUri and path
// route is /<path> or <domainRootId>/<path>
int pos = route.IndexOf('/');
path = pos == 0 ? route : route.Substring(pos);
domainUri = pos == 0 ? null : DomainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current);
}
else
{
// there was no route in the cache - create a route
var node = contentCache.GetDocumentById(umbracoContext, id);
if (node == null)
{
LogHelper.Warn<DefaultUrlProvider>(
"Couldn't find any page with nodeId={0}. This is most likely caused by the page not being published.",
() => id);
return null;
}
// walk up from that node until we hit a node with a domain,
// or we reach the content root, collecting urls in the way
var pathParts = new List<string>();
var n = node;
domainUri = DomainHelper.DomainForNode(n.Id, current);
while (domainUri == null && n != null) // n is null at root
{
// get the url
var urlName = n.UrlName;
pathParts.Add(urlName);
// move to parent node
n = n.Parent;
domainUri = n == null ? null : DomainHelper.DomainForNode(n.Id, current);
}
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
if (domainUri == null && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
ApplyHideTopLevelNodeFromPath(umbracoContext, contentCache, node, pathParts);
// assemble the route
pathParts.Reverse();
path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
route = (n == null ? "" : n.Id.ToString()) + path;
// do not store if previewing
if (!umbracoContext.InPreviewMode)
umbracoContext.RoutingContext.RoutesCache.Store(id, route);
}
// assemble the url from domainUri (maybe null) and path
return AssembleUrl(domainUri, path, current, absolute).ToString();
}
#endregion
#region GetOtherUrls
/// <summary>
/// Gets the other urls of a published content.
/// </summary>
/// <param name="umbracoContext">The Umbraco context.</param>
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</param>
/// <param name="current">The current absolute url.</param>
/// <returns>The other urls for the published content.</returns>
/// <remarks>
/// <para>Other urls are those that <c>GetUrl</c> would not return in the current context, but would be valid
/// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...).</para>
/// </remarks>
public virtual IEnumerable<string> GetOtherUrls(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current)
{
string path;
IEnumerable<DomainAndUri> domainUris;
// will not read cache if previewing!
var route = umbracoContext.InPreviewMode
? null
: umbracoContext.RoutingContext.RoutesCache.GetRoute(id);
if (!string.IsNullOrEmpty(route))
{
// there was a route in the cache - extract domainUri and path
// route is /<path> or <domainRootId>/<path>
int pos = route.IndexOf('/');
path = pos == 0 ? route : route.Substring(pos);
domainUris = pos == 0 ? null : DomainHelper.DomainsForNode(int.Parse(route.Substring(0, pos)), current);
}
else
{
// there was no route in the cache - create a route
var node = contentCache.GetDocumentById(umbracoContext, id);
if (node == null)
{
LogHelper.Warn<DefaultUrlProvider>(
"Couldn't find any page with nodeId={0}. This is most likely caused by the page not being published.",
() => id);
return null;
}
// 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 = DomainHelper.DomainsForNode(n.Id, current);
while (domainUris == null && n != null) // n is null at root
{
// get the url
var urlName = node.UrlName;
pathParts.Add(urlName);
// move to parent node
n = n.Parent;
domainUris = n == null ? null : DomainHelper.DomainsForNode(n.Id, current);
}
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
if (domainUris == null && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
ApplyHideTopLevelNodeFromPath(umbracoContext, contentCache, node, pathParts);
// assemble the route
pathParts.Reverse();
path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
route = (n == null ? "" : n.Id.ToString()) + path;
// do not store if previewing
if (!umbracoContext.InPreviewMode)
umbracoContext.RoutingContext.RoutesCache.Store(id, route);
}
// assemble the alternate urls from domainUris (maybe empty) and path
return AssembleUrls(domainUris, path).Select(uri => uri.ToString());
}
#endregion
#region Utilities
Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, bool absolute)
{
Uri uri;
if (domainUri == null)
{
// no domain was found : return an absolute or relative url
// ignore vdir at that point
if (!absolute || current == null)
uri = new Uri(path, UriKind.Relative);
else
uri = new Uri(current.GetLeftPart(UriPartial.Authority) + path);
}
else
{
// a domain was found : return an absolute or relative url
// ignore vdir at that point
if (!absolute && current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority))
uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); // relative
else
uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); // absolute
}
// UriFromUmbraco will handle vdir
// meaning it will add vdir into domain urls too!
return UriUtility.UriFromUmbraco(uri);
}
string CombinePaths(string path1, string path2)
{
string path = path1.TrimEnd('/') + path2;
return path == "/" ? path : path.TrimEnd('/');
}
// always build absolute urls unless we really cannot
IEnumerable<Uri> AssembleUrls(IEnumerable<DomainAndUri> domainUris, string path)
{
// no domain == no "other" url
if (domainUris == null)
return Enumerable.Empty<Uri>();
// if no domain was found and then we have no "other" url
// else return absolute urls, ignoring vdir at that point
var uris = domainUris.Select(domainUri => new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)));
// UriFromUmbraco will handle vdir
// meaning it will add vdir into domain urls too!
return uris.Select(UriUtility.UriFromUmbraco);
}
static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, IPublishedContentStore contentCache, Core.Models.IPublishedContent node, IList<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 = contentCache.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
}
}

View File

@@ -0,0 +1,45 @@
using System;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Represents an Umbraco domain and its normalized uri.
/// </summary>
/// <remarks>
/// <para>In Umbraco it is valid to create domains with name such as <c>example.com</c>, <c>https://www.example.com</c>, <c>example.com/foo/</c>.</para>
/// <para>The normalized uri of a domain begins with a scheme and ends with no slash, eg <c>http://example.com/</c>, <c>https://www.example.com/</c>, <c>http://example.com/foo/</c>.</para>
/// </remarks>
internal class DomainAndUri
{
/// <summary>
/// Initializes a new instance of the <see cref="DomainAndUri"/> class with a Domain and a uri scheme.
/// </summary>
/// <param name="domain">The domain.</param>
/// <param name="scheme">The uri scheme.</param>
public DomainAndUri(Domain domain, string scheme)
{
Domain = domain;
Uri = new Uri(UriUtility.TrimPathEndSlash(UriUtility.StartWithScheme(domain.Name, scheme)));
}
/// <summary>
/// Gets or sets the Umbraco domain.
/// </summary>
public Domain Domain { get; private set; }
/// <summary>
/// Gets or sets the normalized uri of the domain.
/// </summary>
public Uri Uri { get; private set; }
/// <summary>
/// Gets a string that represents the <see cref="DomainAndUri"/> instance.
/// </summary>
/// <returns>A string that represents the current <see cref="DomainAndUri"/> instance.</returns>
public override string ToString()
{
return string.Format("{{ \"{0}\", \"{1}\" }}", Domain.Name, Uri);
}
}
}

View File

@@ -1,185 +1,272 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Umbraco.Core;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides utilities to handle domains.
/// </summary>
/// <summary>
/// Provides utilities to handle domains.
/// </summary>
internal class DomainHelper
{
/// <summary>
/// Represents an Umbraco domain and its normalized uri.
/// </summary>
/// <remarks>
/// <para>In Umbraco it is valid to create domains with name such as <c>example.com</c>, <c>https://www.example.com</c>, <c>example.com/foo/</c>.</para>
/// <para>The normalized uri of a domain begins with a scheme and ends with no slash, eg <c>http://example.com/</c>, <c>https://www.example.com/</c>, <c>http://example.com/foo/</c>.</para>
/// </remarks>
internal class DomainAndUri
{
/// <summary>
/// The Umbraco domain.
/// </summary>
public Domain Domain;
#region Temp. abstract Umbraco's API
/// <summary>
/// The normalized uri of the domain.
/// </summary>
public Uri Uri;
/// <summary>
/// Gets all domains defined in the system.
/// </summary>
/// <param name="includeWildcards">A value indicating whether to include wildcard domains.</param>
/// <returns>All domains defined in the system.</returns>
/// <remarks>This is to temporarily abstract Umbraco's API.</remarks>
internal static Domain[] GetAllDomains(bool includeWildcards)
{
return Domain.GetDomains(includeWildcards).ToArray();
}
/// <summary>
/// Gets a string that represents the <see cref="DomainAndUri"/> instance.
/// </summary>
/// <returns>A string that represents the current <see cref="DomainAndUri"/> instance.</returns>
public override string ToString()
{
return string.Format("{{ \"{0}\", \"{1}\" }}", Domain.Name, Uri);
}
}
/// <summary>
/// Gets all domains defined in the system at a specified node.
/// </summary>
/// <param name="nodeId">The node identifier.</param>
/// <param name="includeWildcards">A value indicating whether to include wildcard domains.</param>
/// <returns>All domains defined in the system at the specified node.</returns>
/// <remarks>This is to temporarily abstract Umbraco's API.</remarks>
internal static Domain[] GetNodeDomains(int nodeId, bool includeWildcards)
{
return Domain.GetDomains(includeWildcards).Where(d => d.RootNodeId == nodeId).ToArray();
}
private static bool IsWildcardDomain(Domain d)
{
// supporting null or whitespace for backward compatibility,
// although we should not allow ppl to create them anymore
return string.IsNullOrWhiteSpace(d.Name) || d.Name.StartsWith("*");
}
#endregion
private static Domain SanitizeForBackwardCompatibility(Domain d)
{
// this is a _really_ nasty one that should be removed in 6.x
// some people were using hostnames such as "/en" which happened to work pre-4.10
// but make _no_ sense at all... and 4.10 throws on them, so here we just try
// to find a way so 4.11 does not throw.
// but, really.
// no.
var context = System.Web.HttpContext.Current;
if (context != null && d.Name.StartsWith("/"))
{
// turn /en into http://whatever.com/en so it becomes a parseable uri
var authority = context.Request.Url.GetLeftPart(UriPartial.Authority);
d.Name = authority + d.Name;
}
return d;
}
#region Domain for Node
/// <summary>
/// Finds the domain that best matches the current uri, into an enumeration of domains.
/// </summary>
/// <param name="domains">The enumeration of Umbraco domains.</param>
/// <param name="current">The uri of the current request, or null.</param>
/// <param name="defaultToFirst">A value indicating whether to return the first domain of the list when no domain matches.</param>
/// <returns>The domain and its normalized uri, that best matches the current uri, else the first domain (if <c>defaultToFirst</c> is <c>true</c>), else null.</returns>
public static DomainAndUri DomainMatch(IEnumerable<Domain> domains, Uri current, bool defaultToFirst)
{
// sanitize the list to have proper uris for comparison (scheme, path end with /)
// we need to end with / because example.com/foo cannot match example.com/foobar
// we need to order so example.com/foo matches before example.com/
var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme;
var domainsAndUris = domains
.Where(d => !IsWildcardDomain(d))
.Select(d => SanitizeForBackwardCompatibility(d))
.Select(d => new { Domain = d, UriString = UriUtility.EndPathWithSlash(UriUtility.StartWithScheme(d.Name, scheme)) })
.OrderByDescending(t => t.UriString)
.Select(t => new DomainAndUri { Domain = t.Domain, Uri = new Uri(t.UriString) });
/// <summary>
/// Finds the domain for the specified node, if any, that best matches a specified uri.
/// </summary>
/// <param name="nodeId">The node identifier.</param>
/// <param name="current">The uri, or null.</param>
/// <returns>The domain and its uri, if any, that best matches the specified uri, else null.</returns>
/// <remarks>If at least a domain is set on the node then the method returns the domain that
/// best matches the specified uri, else it returns null.</remarks>
internal static DomainAndUri DomainForNode(int nodeId, Uri current)
{
// be safe
if (nodeId <= 0)
return null;
if (!domainsAndUris.Any())
return null;
// get the domains on that node
var domains = GetNodeDomains(nodeId, false);
DomainAndUri domainAndUri;
if (current == null)
{
// take the first one by default
domainAndUri = domainsAndUris.First();
}
else
{
// look for a domain that would be the base of the hint
// else take the first one by default
var hintWithSlash = current.EndPathWithSlash();
domainAndUri = domainsAndUris
.FirstOrDefault(t => t.Uri.IsBaseOf(hintWithSlash));
if (domainAndUri == null && defaultToFirst)
domainAndUri = domainsAndUris.First();
}
// none?
if (!domains.Any())
return null;
if (domainAndUri != null)
domainAndUri.Uri = domainAndUri.Uri.TrimPathEndSlash();
return domainAndUri;
}
// else filter
var helper = SiteDomainHelperResolver.Current.Helper;
var domainAndUri = DomainForUri(domains, current, domainAndUris => helper.MapDomain(current, domainAndUris));
/// <summary>
/// Gets an enumeration of <see cref="DomainAndUri"/> matching an enumeration of Umbraco domains.
/// </summary>
/// <param name="domains">The enumeration of Umbraco domains.</param>
/// <param name="current">The uri of the current request, or null.</param>
/// <returns>The enumeration of <see cref="DomainAndUri"/> matching the enumeration of Umbraco domains.</returns>
public static IEnumerable<DomainAndUri> DomainMatches(IEnumerable<Domain> domains, Uri current)
{
var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme;
var domainsAndUris = domains
.Where(d => !IsWildcardDomain(d))
.Select(d => SanitizeForBackwardCompatibility(d))
.Select(d => new { Domain = d, UriString = UriUtility.TrimPathEndSlash(UriUtility.StartWithScheme(d.Name, scheme)) })
.OrderByDescending(t => t.UriString)
.Select(t => new DomainAndUri { Domain = t.Domain, Uri = new Uri(t.UriString) });
return domainsAndUris;
}
if (domainAndUri == null)
throw new Exception("DomainForUri returned null.");
/// <summary>
/// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node.
/// </summary>
/// <param name="current">The current domain.</param>
/// <param name="path">The path to a node under the current domain's root node.</param>
/// <returns>A value indicating if there is another domain defined down in the path.</returns>
public static bool ExistsDomainInPath(Domain current, string path)
{
var domains = Domain.GetDomains();
var stopNodeId = current == null ? -1 : current.RootNodeId;
return domainAndUri;
}
return path.Split(',')
.Reverse()
.Select(id => int.Parse(id))
.TakeWhile(id => id != stopNodeId)
.Any(id => domains.Any(d => d.RootNodeId == id && !IsWildcardDomain(d)));
}
/// <summary>
/// Find the domains for the specified node, if any, that match a specified uri.
/// </summary>
/// <param name="nodeId">The node identifier.</param>
/// <param name="current">The uri, or null.</param>
/// <param name="excludeDefault">A value indicating whether to exclude the current/default domain. True by default.</param>
/// <returns>The domains and their uris, that match the specified uri, else null.</returns>
/// <remarks>If at least a domain is set on the node then the method returns the domains that
/// best match the specified uri, else it returns null.</remarks>
internal static IEnumerable<DomainAndUri> DomainsForNode(int nodeId, Uri current, bool excludeDefault = true)
{
// be safe
if (nodeId <= 0)
return null;
/// <summary>
/// Gets the deepest wildcard <see cref="Domain"/> in a node path.
/// </summary>
/// <param name="domains">The enumeration of Umbraco domains.</param>
/// <param name="path">The node path.</param>
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
/// <returns>The deepest wildcard <see cref="Domain"/> in the path, or null.</returns>
public static Domain LookForWildcardDomain(IEnumerable<Domain> domains, string path, int? rootNodeId)
{
// "When you perform comparisons with nullable types, if the value of one of the nullable
// types is null and the other is not, all comparisons evaluate to false."
// get the domains on that node
var domains = GetNodeDomains(nodeId, false);
return path
.Split(',')
.Select(int.Parse)
.Skip(1)
.Reverse()
.TakeWhile(id => !rootNodeId.HasValue || id != rootNodeId)
.Select(nodeId => domains.FirstOrDefault(d => d.RootNodeId == nodeId && IsWildcardDomain(d)))
.FirstOrDefault(domain => domain != null);
}
// none?
if (!domains.Any())
return null;
/// <summary>
/// Returns the part of a path relative to the uri of a domain.
/// </summary>
/// <param name="domainUri">The normalized uri of the domain.</param>
/// <param name="path">The full path of the uri.</param>
/// <returns>The path part relative to the uri of the domain.</returns>
/// <remarks>Eg the relative part of <c>/foo/bar/nil</c> to domain <c>example.com/foo</c> is <c>/bar/nil</c>.</remarks>
public static string PathRelativeToDomain(Uri domainUri, string path)
{
return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/');
}
}
// get the domains and their uris
var domainAndUris = DomainsForUri(domains, current).ToArray();
// filter
var helper = SiteDomainHelperResolver.Current.Helper;
return helper.MapDomains(current, domainAndUris, excludeDefault).ToArray();
}
#endregion
#region Domain for Uri
/// <summary>
/// Finds the domain that best matches a specified uri, into a group of domains.
/// </summary>
/// <param name="domains">The group of domains.</param>
/// <param name="current">The uri, or null.</param>
/// <param name="filter">A function to filter the list of domains, if more than one applies, or <c>null</c>.</param>
/// <returns>The domain and its normalized uri, that best matches the specified uri.</returns>
/// <remarks>
/// <para>If more than one domain matches, then the <paramref name="filter"/> function is used to pick
/// the right one, unless it is <c>null</c>, in which case the method returns <c>null</c>.</para>
/// <para>The filter, if any, will be called only with a non-empty argument, and _must_ return something.</para>
/// </remarks>
internal static DomainAndUri DomainForUri(Domain[] domains, Uri current, Func<DomainAndUri[], DomainAndUri> filter = null)
{
// sanitize the list to have proper uris for comparison (scheme, path end with /)
// we need to end with / because example.com/foo cannot match example.com/foobar
// we need to order so example.com/foo matches before example.com/
var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme;
var domainsAndUris = domains
.Where(d => !d.IsWildcard)
.Select(SanitizeForBackwardCompatibility)
.Select(d => new DomainAndUri(d, scheme))
.OrderByDescending(d => d.Uri.ToString())
.ToArray();
if (!domainsAndUris.Any())
return null;
DomainAndUri domainAndUri;
if (current == null)
{
// take the first one by default (what else can we do?)
domainAndUri = domainsAndUris.First(); // .First() protected by .Any() above
}
else
{
// look for the first domain that would be the base of the hint
var hintWithSlash = current.EndPathWithSlash();
domainAndUri = domainsAndUris
.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash));
// if none matches, then try to run the filter to pick a domain
if (domainAndUri == null && filter != null)
{
domainAndUri = filter(domainsAndUris);
// if still nothing, pick the first one?
// no: move that constraint to the filter, but check
if (domainAndUri == null)
throw new InvalidOperationException("The filter returned null.");
}
}
return domainAndUri;
}
/// <summary>
/// Gets the domains that match a specified uri, into a group of domains.
/// </summary>
/// <param name="domains">The group of domains.</param>
/// <param name="current">The uri, or null.</param>
/// <returns>The domains and their normalized uris, that match the specified uri.</returns>
internal static IEnumerable<DomainAndUri> DomainsForUri(Domain[] domains, Uri current)
{
var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme;
return domains
.Where(d => !d.IsWildcard)
.Select(SanitizeForBackwardCompatibility)
.Select(d => new DomainAndUri(d, scheme))
.OrderByDescending(d => d.Uri.ToString());
}
#endregion
#region Utilities
/// <summary>
/// Sanitize a Domain.
/// </summary>
/// <param name="domain">The Domain to sanitize.</param>
/// <returns>The sanitized domain.</returns>
/// <remarks>This is a _really_ nasty one that should be removed at some point. Some people were
/// using hostnames such as "/en" which happened to work pre-4.10 but really make no sense at
/// all... and 4.10 throws on them, so here we just try to find a way so 4.11 does not throw.
/// But really... no.</remarks>
private static Domain SanitizeForBackwardCompatibility(Domain domain)
{
var context = System.Web.HttpContext.Current;
if (context != null && domain.Name.StartsWith("/"))
{
// turn "/en" into "http://whatever.com/en" so it becomes a parseable uri
var authority = context.Request.Url.GetLeftPart(UriPartial.Authority);
domain.Name = authority + domain.Name;
}
return domain;
}
/// <summary>
/// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node.
/// </summary>
/// <param name="domains">The domains.</param>
/// <param name="path">The path to a node under the current domain's root node eg '-1,1234,5678'.</param>
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
/// <returns>A value indicating if there is another domain defined down in the path.</returns>
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
internal static bool ExistsDomainInPath(Domain[] domains, string path, int? rootNodeId)
{
return FindDomainInPath(domains, path, rootNodeId) != null;
}
/// <summary>
/// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path.
/// </summary>
/// <param name="domains">The domains.</param>
/// <param name="path">The node path eg '-1,1234,5678'.</param>
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
/// <returns>The deepest non-wildcard Domain in the path, or null.</returns>
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
internal static Domain FindDomainInPath(Domain[] domains, string path, int? rootNodeId)
{
var stopNodeId = rootNodeId ?? -1;
return path.Split(',')
.Reverse()
.Select(int.Parse)
.TakeWhile(id => id != stopNodeId)
.Select(id => domains.FirstOrDefault(d => d.RootNodeId == id && !d.IsWildcard))
.SkipWhile(domain => domain == null)
.FirstOrDefault();
}
/// <summary>
/// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path.
/// </summary>
/// <param name="domains">The domains.</param>
/// <param name="path">The node path eg '-1,1234,5678'.</param>
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
/// <returns>The deepest wildcard Domain in the path, or null.</returns>
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
internal static Domain FindWildcardDomainInPath(Domain[] domains, string path, int? rootNodeId)
{
var stopNodeId = rootNodeId ?? -1;
return path.Split(',')
.Reverse()
.Select(int.Parse)
.TakeWhile(id => id != stopNodeId)
.Select(id => domains.FirstOrDefault(d => d.RootNodeId == id && d.IsWildcard))
.FirstOrDefault(domain => domain != null);
}
/// <summary>
/// Returns the part of a path relative to the uri of a domain.
/// </summary>
/// <param name="domainUri">The normalized uri of the domain.</param>
/// <param name="path">The full path of the uri.</param>
/// <returns>The path part relative to the uri of the domain.</returns>
/// <remarks>Eg the relative part of <c>/foo/bar/nil</c> to domain <c>example.com/foo</c> is <c>/bar/nil</c>.</remarks>
internal static string PathRelativeToDomain(Uri domainUri, string path)
{
return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/');
}
#endregion
}
}

View File

@@ -5,12 +5,12 @@ namespace Umbraco.Web.Routing
/// </summary>
internal interface IContentFinder
{
/// <summary>
/// Tries to find and assign an Umbraco document to a <c>PublishedContentRequest</c>.
/// </summary>
/// <param name="docRequest">The <c>PublishedContentRequest</c>.</param>
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
/// <remarks>Optionally, can also assign the template or anything else on the document request, although that is not required.</remarks>
bool TryFindDocument(PublishedContentRequest contentRequest);
/// <summary>
/// Tries to find and assign an Umbraco document to a <c>PublishedContentRequest</c>.
/// </summary>
/// <param name="contentRequest">The <c>PublishedContentRequest</c>.</param>
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
/// <remarks>Optionally, can also assign the template or anything else on the document request, although that is not required.</remarks>
bool TryFindDocument(PublishedContentRequest contentRequest);
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides utilities to handle site domains.
/// </summary>
internal interface ISiteDomainHelper
{
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick one that best matches the current request.
/// </summary>
/// <param name="current">The Uri of the current request.</param>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <returns>The selected <c>DomainAndUri</c>.</returns>
/// <remarks>
/// <para>If the filter is invoked then <paramref name="domainAndUris"/> is _not_ empty and
/// <paramref name="current"/> is _not_ null, and <paramref name="current"/> could not be
/// matched with anything in <paramref name="domainAndUris"/>.</para>
/// <para>The filter _must_ return something else an exception will be thrown.</para>
/// </remarks>
DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris);
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick those that best matches the current request.
/// </summary>
/// <param name="current">The Uri of the current request.</param>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <param name="excludeDefault">A value indicating whether to exclude the current/default domain.</param>
/// <returns>The selected <c>DomainAndUri</c> items.</returns>
/// <remarks>The filter must return something, even empty, else an exception will be thrown.</remarks>
IEnumerable<DomainAndUri> MapDomains(Uri current, DomainAndUri[] domainAndUris, bool excludeDefault);
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides urls.
/// </summary>
internal interface IUrlProvider
{
/// <summary>
/// Gets the nice url of a published content.
/// </summary>
/// <param name="umbracoContext">The Umbraco context.</param>
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</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 url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on url indicated by <c>current</c> and settings, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>If the provider is unable to provide a url, it should return <c>null</c>.</para>
/// </remarks>
string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute);
/// <summary>
/// Gets the other urls of a published content.
/// </summary>
/// <param name="umbracoContext">The Umbraco context.</param>
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</param>
/// <param name="current">The current absolute url.</param>
/// <returns>The other urls for the published content.</returns>
/// <remarks>
/// <para>Other urls are those that <c>GetUrl</c> would not return in the current context, but would be valid
/// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...).</para>
/// </remarks>
IEnumerable<string> GetOtherUrls(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current);
}
}

View File

@@ -1,334 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Web.Routing;
using umbraco;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides nice urls for a nodes.
/// </summary>
internal class NiceUrlProvider
{
internal const string NullUrl = "#";
/// <summary>
/// Initializes a new instance of the <see cref="NiceUrlProvider"/> class.
/// </summary>
/// <param name="publishedContentStore">The content store.</param>
/// <param name="umbracoContext">The Umbraco context.</param>
public NiceUrlProvider(IPublishedContentStore publishedContentStore, UmbracoContext umbracoContext)
{
_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 identifier.</param>
/// <returns>The nice url for the node.</returns>
/// <remarks>The url is absolute or relative depending on the current url, settings, and options.</remarks>
public string GetNiceUrl(int nodeId)
{
var absolute = UmbracoSettings.UseDomainPrefixes || this.EnforceAbsoluteUrls;
return GetNiceUrl(nodeId, _umbracoContext.CleanedUmbracoUrl, 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.CleanedUmbracoUrl, absolute);
}
/// <summary>
/// Gets the nice url of a node.
/// </summary>
/// <param name="nodeId">The node id.</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 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
: _umbracoContext.RoutingContext.RoutesCache.GetRoute(nodeId);
if (!string.IsNullOrEmpty(route))
{
// there was a route in the cache - extract domainUri and path
// route is /<path> or <domainRootId>/<path>
int pos = route.IndexOf('/');
path = pos == 0 ? route : route.Substring(pos);
domainUri = pos == 0 ? null : DomainUriAtNode(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)
{
LogHelper.Warn<NiceUrlProvider>(
"Couldn't find any page with nodeId={0}. This is most likely caused by the page not being published.",
() => nodeId);
return NullUrl;
}
// walk up from that node until we hit a node with a domain,
// or we reach the content root, collecting urls in the way
var pathParts = new List<string>();
var n = node;
domainUri = DomainUriAtNode(n.Id, current);
while (domainUri == null && n != null) // n is null at root
{
// get the url
var urlName = n.UrlName;
pathParts.Add(urlName);
// move to parent node
n = n.Parent;
domainUri = n == null ? null : DomainUriAtNode(n.Id, current);
}
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
if (domainUri == null && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
ApplyHideTopLevelNodeFromPath(node, pathParts);
// assemble the route
pathParts.Reverse();
path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
route = (n == null ? "" : n.Id.ToString()) + path;
// do not store if previewing
if (!_umbracoContext.InPreviewMode)
_umbracoContext.RoutingContext.RoutesCache.Store(nodeId, route);
}
// assemble the url from domainUri (maybe null) and path
return AssembleUrl(domainUri, path, current, absolute).ToString();
}
#endregion
#region GetAlternateNiceUrls
public IEnumerable<string> GetAllAbsoluteNiceUrls(int nodeId)
{
return GetAllAbsoluteNiceUrls(nodeId, _umbracoContext.CleanedUmbracoUrl);
}
/// <summary>
/// Gets the nice urls of a node.
/// </summary>
/// <param name="nodeId">The node id.</param>
/// <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> GetAllAbsoluteNiceUrls(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
string path;
IEnumerable<Uri> domainUris;
// will not read cache if previewing!
var route = _umbracoContext.InPreviewMode
? null
: _umbracoContext.RoutingContext.RoutesCache.GetRoute(nodeId);
if (!string.IsNullOrEmpty(route))
{
// there was a route in the cache - extract domainUri and path
// route is /<path> or <domainRootId>/<path>
int pos = route.IndexOf('/');
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)
{
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);
// 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)
ApplyHideTopLevelNodeFromPath(node, pathParts);
// assemble the route
pathParts.Reverse();
path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
route = (n == null ? "" : n.Id.ToString()) + path;
// do not store if previewing
if (!_umbracoContext.InPreviewMode)
_umbracoContext.RoutingContext.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 an absolute or relative url
// ignore vdir at that point
if (!absolute || current == null)
uri = new Uri(path, UriKind.Relative);
else
uri = new Uri(current.GetLeftPart(UriPartial.Authority) + path);
}
else
{
// a domain was found : return an absolute or relative url
// ignore vdir at that point
if (!absolute && current != null && domainUri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority))
uri = new Uri(CombinePaths(domainUri.AbsolutePath, path), UriKind.Relative); // relative
else
uri = new Uri(CombinePaths(domainUri.GetLeftPart(UriPartial.Path), path)); // absolute
}
// UriFromUmbraco will handle vdir
// meaning it will add vdir into domain urls too!
return UriUtility.UriFromUmbraco(uri);
}
string CombinePaths(string path1, string path2)
{
string path = path1.TrimEnd('/') + path2;
return path == "/" ? path : path.TrimEnd('/');
}
// always build absolute urls unless we really cannot
IEnumerable<Uri> AssembleUrls(IEnumerable<Uri> domainUris, string path, Uri current)
{
List<Uri> uris = new List<Uri>();
if (!domainUris.Any())
{
// no domain was found : return an absolute or relative url
// ignore vdir at that point
if (current == null)
uris.Add(new Uri(path, UriKind.Relative));
else
uris.Add(new Uri(current.GetLeftPart(UriPartial.Authority) + path));
}
else
{
// domains were found : return absolute urls
// ignore vdir at that point
uris.AddRange(domainUris.Select(domainUri => new Uri(CombinePaths(domainUri.GetLeftPart(UriPartial.Path), path))));
}
// UriFromUmbraco will handle vdir
// meaning it will add vdir into domain urls too!
return uris.Select(uri => UriUtility.UriFromUmbraco(uri));
}
Uri DomainUriAtNode(int nodeId, Uri current)
{
// be safe
if (nodeId <= 0)
return null;
// apply filter on domains defined on that node
var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomainsById(nodeId), current, true);
return domainAndUri == null ? null : domainAndUri.Uri;
}
IEnumerable<Uri> DomainUrisAtNode(int nodeId, Uri current)
{
// be safe
if (nodeId <= 0)
return new Uri[] { };
var domainAndUris = DomainHelper.DomainMatches(Domain.GetDomainsById(nodeId), current);
return domainAndUris.Select(d => d.Uri);
}
void ApplyHideTopLevelNodeFromPath(Core.Models.IPublishedContent 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
}
}

View File

@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml;
using System.Reflection;
using Umbraco.Core;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Routing
@@ -47,7 +43,7 @@ namespace Umbraco.Web.Routing
// auth. Paul Sterling confirmed in jan. 2013 that we can get rid of it.
// code from requestHandler.cleanUrl
string root = Umbraco.Core.IO.SystemDirectories.Root.ToLower();
string root = Core.IO.SystemDirectories.Root.ToLower();
if (!string.IsNullOrEmpty(root) && tmp.StartsWith(root))
tmp = tmp.Substring(root.Length);
tmp = tmp.TrimEnd('/');
@@ -81,7 +77,7 @@ namespace Umbraco.Web.Routing
return tmp;
}
static IEnumerable<Type> _customHandlerTypes = null;
static IEnumerable<Type> _customHandlerTypes;
static void InitializeNotFoundHandlers()
{
@@ -93,7 +89,7 @@ namespace Umbraco.Web.Routing
var customHandlerTypes = new List<Type>();
var customHandlers = new XmlDocument();
customHandlers.Load(Umbraco.Core.IO.IOHelper.MapPath(Umbraco.Core.IO.SystemFiles.NotFoundhandlersConfig));
customHandlers.Load(Core.IO.IOHelper.MapPath(Core.IO.SystemFiles.NotFoundhandlersConfig));
foreach (XmlNode n in customHandlers.DocumentElement.SelectNodes("notFound"))
{

View File

@@ -166,7 +166,7 @@ namespace Umbraco.Web.Routing
LogHelper.Debug<PublishedContentRequestEngine>("{0}Uri=\"{1}\"", () => tracePrefix, () => _pcr.Uri);
// try to find a domain matching the current request
var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomains().ToArray(), _pcr.Uri, false);
var domainAndUri = DomainHelper.DomainForUri(DomainHelper.GetAllDomains(false), _pcr.Uri);
// handle domain
if (domainAndUri != null)
@@ -216,7 +216,7 @@ namespace Umbraco.Web.Routing
var nodePath = _pcr.PublishedContent.Path;
LogHelper.Debug<PublishedContentRequestEngine>("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath);
var rootNodeId = _pcr.HasDomain ? _pcr.Domain.RootNodeId : (int?)null;
var domain = DomainHelper.LookForWildcardDomain(Domain.GetDomains().ToArray(), nodePath, rootNodeId);
var domain = DomainHelper.FindWildcardDomainInPath(DomainHelper.GetAllDomains(true), nodePath, rootNodeId);
if (domain != null)
{
@@ -270,24 +270,22 @@ namespace Umbraco.Web.Routing
// recurse
var subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault();
alias = alias.Substring(pos + 1);
return subdir == null ? false : FindTemplateRenderingEngineInDirectory(subdir, alias, extensions);
}
else
{
// look here
return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e)));
return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions);
}
// look here
return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e)));
}
#endregion
#region Document and template
/// <summary>
/// Finds the Umbraco document (if any) matching the request, and updates the PublishedContentRequest accordingly.
/// </summary>
/// <returns>A value indicating whether a document and template were found.</returns>
private bool FindPublishedContentAndTemplate()
/// <summary>
/// Finds the Umbraco document (if any) matching the request, and updates the PublishedContentRequest accordingly.
/// </summary>
/// <returns>A value indicating whether a document and template were found.</returns>
private void FindPublishedContentAndTemplate()
{
const string tracePrefix = "FindPublishedContentAndTemplate: ";
LogHelper.Debug<PublishedContentRequestEngine>("{0}Path=\"{1}\"", () => tracePrefix, () => _pcr.Uri.AbsolutePath);
@@ -299,7 +297,7 @@ namespace Umbraco.Web.Routing
// whoever called us is in charge of actually redirecting
// -- do not process anything any further --
if (_pcr.IsRedirect)
return true;
return;
// not handling umbracoRedirect here but after LookupDocument2
// so internal redirect, 404, etc has precedence over redirect
@@ -315,14 +313,13 @@ namespace Umbraco.Web.Routing
// handle wildcard domains
HandleWildcardDomains();
return _pcr.HasPublishedContent && _pcr.HasTemplate;
}
/// <summary>
/// Tries to find the document matching the request, by running the IPublishedContentFinder instances.
/// </summary>
internal void FindPublishedContent()
/// <summary>
/// Tries to find the document matching the request, by running the IPublishedContentFinder instances.
/// </summary>
/// <exception cref="InvalidOperationException">There is no finder collection.</exception>
internal void FindPublishedContent()
{
const string tracePrefix = "FindPublishedContent: ";
@@ -334,10 +331,12 @@ namespace Umbraco.Web.Routing
() => string.Format("{0}Begin finders", tracePrefix),
() => string.Format("{0}End finders, {1}", tracePrefix, (_pcr.HasPublishedContent ? "a document was found" : "no document was found"))))
{
_routingContext.PublishedContentFinders.Any(lookup => lookup.TryFindDocument(_pcr));
if (_routingContext.PublishedContentFinders == null)
throw new InvalidOperationException("There is no finder collection.");
_routingContext.PublishedContentFinders.Any(finder => finder.TryFindDocument(_pcr));
}
// indicate that the published content (if any) we have at the moment is the
// indicate that the published content (if any) we have at the moment is the
// one that was found by the standard finders before anything else took place.
_pcr.SetIsInitialPublishedContent();
}
@@ -622,7 +621,7 @@ namespace Umbraco.Web.Routing
var redirectId = _pcr.PublishedContent.GetPropertyValue("umbracoRedirect", -1);
var redirectUrl = "#";
if (redirectId > 0)
redirectUrl = _routingContext.NiceUrlProvider.GetNiceUrl(redirectId);
redirectUrl = _routingContext.UrlProvider.GetUrl(redirectId);
if (redirectUrl != "#")
_pcr.SetRedirect(redirectUrl);
}

View File

@@ -32,7 +32,7 @@ namespace Umbraco.Web.Routing
/// </summary>
public IRoutesCache RoutesCache
{
get { return this.Value; }
get { return Value; }
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Web.Routing
{
@@ -9,28 +8,29 @@ namespace Umbraco.Web.Routing
/// </summary>
public class RoutingContext
{
/// <summary>
/// Initializes a new instance of the <see cref="RoutingContext"/> class.
/// </summary>
/// <param name="umbracoContext"> </param>
/// <param name="contentFinders">The document lookups resolver.</param>
/// <param name="contentLastChanceFinder"> </param>
/// <param name="publishedContentStore">The content store.</param>
/// <param name="niceUrlResolver">The nice urls resolver.</param>
internal RoutingContext(
/// <summary>
/// Initializes a new instance of the <see cref="RoutingContext"/> class.
/// </summary>
/// <param name="umbracoContext"> </param>
/// <param name="contentFinders">The document lookups resolver.</param>
/// <param name="contentLastChanceFinder"> </param>
/// <param name="publishedContentStore">The content store.</param>
/// <param name="urlProvider">The nice urls provider.</param>
/// <param name="routesCache">The routes cache.</param>
internal RoutingContext(
UmbracoContext umbracoContext,
IEnumerable<IContentFinder> contentFinders,
IContentFinder contentLastChanceFinder,
IPublishedContentStore publishedContentStore,
NiceUrlProvider niceUrlResolver,
UrlProvider urlProvider,
IRoutesCache routesCache)
{
this.UmbracoContext = umbracoContext;
this.PublishedContentFinders = contentFinders;
this.PublishedContentLastChanceFinder = contentLastChanceFinder;
this.PublishedContentStore = publishedContentStore;
this.NiceUrlProvider = niceUrlResolver;
this.RoutesCache = routesCache;
UmbracoContext = umbracoContext;
PublishedContentFinders = contentFinders;
PublishedContentLastChanceFinder = contentLastChanceFinder;
PublishedContentStore = publishedContentStore;
UrlProvider = urlProvider;
RoutesCache = routesCache;
}
/// <summary>
@@ -54,9 +54,9 @@ namespace Umbraco.Web.Routing
internal IPublishedContentStore PublishedContentStore { get; private set; }
/// <summary>
/// Gets the nice urls provider.
/// Gets the urls provider.
/// </summary>
internal NiceUrlProvider NiceUrlProvider { get; private set; }
internal UrlProvider UrlProvider { get; private set; }
/// <summary>
/// Gets the <see cref="IRoutesCache"/>

View File

@@ -0,0 +1,336 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Text.RegularExpressions;
using Umbraco.Core;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides utilities to handle site domains.
/// </summary>
internal class SiteDomainHelper : ISiteDomainHelper
{
#region Configure
private static readonly ReaderWriterLockSlim ConfigLock = new ReaderWriterLockSlim();
private static Dictionary<string, string[]> _sites;
private static Dictionary<string, List<string>> _bindings;
private static Dictionary<string, Dictionary<string, string[]>> _qualifiedSites;
// these are for unit tests *only*
internal static Dictionary<string, string[]> Sites { get { return _sites; } }
internal static Dictionary<string, List<string>> Bindings { get { return _bindings; } }
// these are for validation
//private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$";
private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$";
private static readonly Regex DomainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Returns a disposable object that represents safe write access to config.
/// </summary>
/// <remarks>Should be used in a <c>using(SiteDomainHelper.ConfigWriteLock) { ... }</c> mode.</remarks>
protected static IDisposable ConfigWriteLock
{
get { return new WriteLock(ConfigLock); }
}
/// <summary>
/// Returns a disposable object that represents safe read access to config.
/// </summary>
/// <remarks>Should be used in a <c>using(SiteDomainHelper.ConfigWriteLock) { ... }</c> mode.</remarks>
protected static IDisposable ConfigReadLock
{
get { return new ReadLock(ConfigLock); }
}
/// <summary>
/// Clears the entire configuration.
/// </summary>
public static void Clear()
{
using (ConfigWriteLock)
{
_sites = null;
_bindings = null;
_qualifiedSites = null;
}
}
private static IEnumerable<string> ValidateDomains(IEnumerable<string> domains)
{
// must use authority format w/optional scheme and port, but no path
// any domain should appear only once
return domains.Select(domain =>
{
if (!DomainValidation.IsMatch(domain))
throw new ArgumentOutOfRangeException("domains", string.Format("Invalid domain: \"{0}\"", domain));
return domain;
});
}
/// <summary>
/// Adds a site.
/// </summary>
/// <param name="key">A key uniquely identifying the site.</param>
/// <param name="domains">The site domains.</param>
/// <remarks>At the moment there is no public way to remove a site. Clear and reconfigure.</remarks>
public static void AddSite(string key, IEnumerable<string> domains)
{
using (ConfigWriteLock)
{
_sites = _sites ?? new Dictionary<string, string[]>();
_sites[key] = ValidateDomains(domains).ToArray();
_qualifiedSites = null;
}
}
/// <summary>
/// Adds a site.
/// </summary>
/// <param name="key">A key uniquely identifying the site.</param>
/// <param name="domains">The site domains.</param>
/// <remarks>At the moment there is no public way to remove a site. Clear and reconfigure.</remarks>
public static void AddSite(string key, params string[] domains)
{
using (ConfigWriteLock)
{
_sites = _sites ?? new Dictionary<string, string[]>();
_sites[key] = ValidateDomains(domains).ToArray();
_qualifiedSites = null;
}
}
/// <summary>
/// Removes a site.
/// </summary>
/// <param name="key">A key uniquely identifying the site.</param>
internal static void RemoveSite(string key)
{
using (ConfigWriteLock)
{
if (_sites != null && _sites.ContainsKey(key))
{
_sites.Remove(key);
if (_sites.Count == 0)
_sites = null;
if (_bindings != null && _bindings.ContainsKey(key))
{
foreach (var b in _bindings[key])
{
_bindings[b].Remove(key);
if (_bindings[b].Count == 0)
_bindings.Remove(b);
}
_bindings.Remove(key);
if (_bindings.Count > 0)
_bindings = null;
}
_qualifiedSites = null;
}
}
}
/// <summary>
/// Binds some sites.
/// </summary>
/// <param name="keys">The keys uniquely identifying the sites to bind.</param>
/// <remarks>
/// <para>At the moment there is no public way to unbind sites. Clear and reconfigure.</para>
/// <para>If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3.</para>
/// </remarks>
public static void BindSites(params string[] keys)
{
using (ConfigWriteLock)
{
foreach (var key in keys.Where(key => !_sites.ContainsKey(key)))
throw new ArgumentException(string.Format("Not an existing site key: {0}", key), "keys");
_bindings = _bindings ?? new Dictionary<string, List<string>>();
var allkeys = _bindings
.Where(kvp => keys.Contains(kvp.Key))
.SelectMany(kvp => kvp.Value)
.Union(keys)
.ToArray();
foreach (var key in allkeys)
{
if (!_bindings.ContainsKey(key))
_bindings[key] = new List<string>();
var xkey = key;
var addKeys = allkeys.Where(k => k != xkey).Except(_bindings[key]);
_bindings[key].AddRange(addKeys);
}
}
}
#endregion
#region Map domains
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick one that best matches the current request.
/// </summary>
/// <param name="current">The Uri of the current request.</param>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <returns>The selected <c>DomainAndUri</c>.</returns>
/// <remarks>
/// <para>If the filter is invoked then <paramref name="domainAndUris"/> is _not_ empty and
/// <paramref name="current"/> is _not_ null, and <paramref name="current"/> could not be
/// matched with anything in <paramref name="domainAndUris"/>.</para>
/// <para>The filter _must_ return something else an exception will be thrown.</para>
/// </remarks>
public virtual DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris)
{
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
var qualifiedSites = GetQualifiedSites(current);
return MapDomain(domainAndUris, qualifiedSites, currentAuthority);
}
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick those that best matches the current request.
/// </summary>
/// <param name="current">The Uri of the current request.</param>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <param name="excludeDefault">A value indicating whether to exclude the current/default domain.</param>
/// <returns>The selected <c>DomainAndUri</c> items.</returns>
/// <remarks>The filter must return something, even empty, else an exception will be thrown.</remarks>
public virtual IEnumerable<DomainAndUri> MapDomains(Uri current, DomainAndUri[] domainAndUris, bool excludeDefault)
{
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
KeyValuePair<string, string[]>[] candidateSites = null;
IEnumerable<DomainAndUri> ret = domainAndUris;
using (ConfigReadLock) // so nothing changes between GetQualifiedSites and access to bindings
{
var qualifiedSites = GetQualifiedSitesInsideLock(current);
if (excludeDefault)
{
// exclude the current one (avoid producing the absolute equivalent of what GetUrl returns)
var hintWithSlash = current.EndPathWithSlash();
var hinted = domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash));
if (hinted != null)
ret = ret.Where(d => d != hinted);
// exclude the default one (avoid producing a possible duplicate of what GetUrl returns)
// only if the default one cannot be the current one ie if hinted is not null
if (hinted == null && domainAndUris.Any())
{
// it is illegal to call MapDomain if domainAndUris is empty
// also, domainAndUris should NOT contain current, hence the test on hinted
var mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority); // what GetUrl would get
ret = ret.Where(d => d != mainDomain);
}
}
// we do our best, but can't do the impossible
if (qualifiedSites == null)
return ret;
// find a site that contains the current authority
var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
// if current belongs to a site, pick every element from domainAndUris that also belong
// to that site -- or to any site bound to that site
if (!currentSite.Equals(default(KeyValuePair<string, string[]>)))
{
candidateSites = new[] { currentSite };
if (_bindings != null && _bindings.ContainsKey(currentSite.Key))
{
var boundSites = qualifiedSites.Where(site => _bindings[currentSite.Key].Contains(site.Key));
candidateSites = candidateSites.Union(boundSites).ToArray();
// .ToArray ensures it is evaluated before the configuration lock is exited
}
}
}
// if we are able to filter, then filter, else return the whole lot
return candidateSites == null ? ret : ret.Where(d =>
{
var authority = d.Uri.GetLeftPart(UriPartial.Authority);
return candidateSites.Any(site => site.Value.Contains(authority));
});
}
private static Dictionary<string, string[]> GetQualifiedSites(Uri current)
{
using (ConfigReadLock)
{
return GetQualifiedSitesInsideLock(current);
}
}
private static Dictionary<string, string[]> GetQualifiedSitesInsideLock(Uri current)
{
// we do our best, but can't do the impossible
if (_sites == null)
return null;
// cached?
if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme))
return _qualifiedSites[current.Scheme];
_qualifiedSites = _qualifiedSites ?? new Dictionary<string, Dictionary<string, string[]>>();
// convert sites into authority sites based upon current scheme
// because some domains in the sites might not have a scheme -- and cache
return _qualifiedSites[current.Scheme] = _sites
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Select(d => new Uri(UriUtility.StartWithScheme(d, current.Scheme)).GetLeftPart(UriPartial.Authority)).ToArray()
);
// .ToDictionary will evaluate and create the dictionary immediately
// the new value is .ToArray so it will also be evaluated immediately
// therefore it is safe to return and exit the configuration lock
}
private static DomainAndUri MapDomain(DomainAndUri[] domainAndUris, Dictionary<string, string[]> qualifiedSites, string currentAuthority)
{
if (domainAndUris == null)
throw new ArgumentNullException("domainAndUris");
if (!domainAndUris.Any())
throw new ArgumentException("Cannot be empty.", "domainAndUris");
// we do our best, but can't do the impossible
if (qualifiedSites == null)
return domainAndUris.First();
// find a site that contains the current authority
var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
// if current belongs to a site - try to pick the first element
// from domainAndUris that also belongs to that site
var ret = currentSite.Equals(default(KeyValuePair<string, string[]>))
? null
: domainAndUris.FirstOrDefault(d => currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority)));
// no match means that either current does not belong to a site, or the site it belongs to
// does not contain any of domainAndUris. Yet we have to return something. here, it becomes
// a bit arbitrary.
// look through sites in order and pick the first domainAndUri that belongs to a site
ret = ret ?? qualifiedSites
.Where(site => site.Key != currentSite.Key)
.Select(site => domainAndUris.FirstOrDefault(domainAndUri => site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority))))
.FirstOrDefault(domainAndUri => domainAndUri != null);
// random, really
ret = ret ?? domainAndUris.First();
return ret;
}
#endregion
}
}

View File

@@ -0,0 +1,38 @@
using System;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Resolves the <see cref="ISiteDomainHelper"/> implementation.
/// </summary>
internal sealed class SiteDomainHelperResolver : SingleObjectResolverBase<SiteDomainHelperResolver, ISiteDomainHelper>
{
/// <summary>
/// Initializes a new instance of the <see cref="SiteDomainHelperResolver"/> class with an <see cref="ISiteDomainHelper"/> implementation.
/// </summary>
/// <param name="helper">The <see cref="ISiteDomainHelper"/> implementation.</param>
internal SiteDomainHelperResolver(ISiteDomainHelper helper)
: base(helper)
{ }
/// <summary>
/// Can be used by developers at runtime to set their IDomainHelper at app startup
/// </summary>
/// <param name="helper"></param>
public void SetHelper(ISiteDomainHelper helper)
{
Value = helper;
}
/// <summary>
/// Gets or sets the <see cref="ISiteDomainHelper"/> implementation.
/// </summary>
public ISiteDomainHelper Helper
{
get { return Value; }
}
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Configuration;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides urls.
/// </summary>
internal class UrlProvider
{
#region Ctor and configuration
/// <summary>
/// Initializes a new instance of the <see cref="UrlProvider"/> class with an Umbraco context, a content cache, and a list of url providers.
/// </summary>
/// <param name="umbracoContext">The Umbraco context.</param>
/// <param name="contentCache">The content cache.</param>
/// <param name="urlProviders">The list of url providers.</param>
public UrlProvider(UmbracoContext umbracoContext, IPublishedContentStore contentCache,
IEnumerable<IUrlProvider> urlProviders)
{
_umbracoContext = umbracoContext;
_contentCache = contentCache;
_urlProviders = urlProviders;
EnforceAbsoluteUrls = false;
}
private readonly UmbracoContext _umbracoContext;
private readonly IPublishedContentStore _contentCache;
private readonly IEnumerable<IUrlProvider> _urlProviders;
/// <summary>
/// Gets or sets a value indicating whether the provider should enforce absolute urls.
/// </summary>
public bool EnforceAbsoluteUrls { get; set; }
#endregion
#region GetUrl
/// <summary>
/// Gets the url of a published content.
/// </summary>
/// <param name="id">The published content identifier.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on the current url, settings, and options.</para>
/// <para>If the provider is unable to provide a url, it returns "#".</para>
/// </remarks>
public string GetUrl(int id)
{
var absolute = UmbracoSettings.UseDomainPrefixes | EnforceAbsoluteUrls;
return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, absolute);
}
/// <summary>
/// Gets the nice url of a published content.
/// </summary>
/// <param name="id">The published content identifier.</param>
/// <param name="absolute">A value indicating whether the url should be absolute in any case.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on the current url and settings, unless <c>absolute</c> is true,
/// in which case the url is always absolute.</para>
/// <para>If the provider is unable to provide a url, it returns "#".</para>
/// </remarks>
public string GetUrl(int id, bool absolute)
{
absolute = absolute | EnforceAbsoluteUrls;
return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, absolute);
}
/// <summary>
/// Gets the nice url of a published content.
/// </summary>
/// <param name="id">The published content id.</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 url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on url indicated by <c>current</c> and settings, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>If the provider is unable to provide a url, it returns "#".</para>
/// </remarks>
public string GetUrl(int id, Uri current, bool absolute)
{
absolute = absolute | EnforceAbsoluteUrls;
var url = _urlProviders.Select(provider => provider.GetUrl(_umbracoContext, _contentCache, id, current, absolute)).FirstOrDefault(u => u != null);
return url ?? "#"; // legacy wants this
}
#endregion
#region GetOtherUrls
/// <summary>
/// Gets the other urls of a published content.
/// </summary>
/// <param name="id">The published content id.</param>
/// <returns>The other urls for the published content.</returns>
/// <remarks>
/// <para>Other urls are those that <c>GetUrl</c> would not return in the current context, but would be valid
/// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...).</para>
/// <para>The results depend on the current url.</para>
/// </remarks>
public IEnumerable<string> GetOtherUrls(int id)
{
return GetOtherUrls(id, _umbracoContext.CleanedUmbracoUrl);
}
/// <summary>
/// Gets the other urls of a published content.
/// </summary>
/// <param name="id">The published content id.</param>
/// <param name="current">The current absolute url.</param>
/// <returns>The other urls for the published content.</returns>
/// <remarks>
/// <para>Other urls are those that <c>GetUrl</c> would not return in the current context, but would be valid
/// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...).</para>
/// </remarks>
public IEnumerable<string> GetOtherUrls(int id, Uri current)
{
// providers can return null or an empty list or a non-empty list, be prepared
var urls = _urlProviders.SelectMany(provider => provider.GetOtherUrls(_umbracoContext, _contentCache, id, current) ?? Enumerable.Empty<string>());
return urls;
}
#endregion
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Resolves IUrlProvider objects.
/// </summary>
internal sealed class UrlProviderResolver : ManyObjectsResolverBase<UrlProviderResolver, IUrlProvider>
{
/// <summary>
/// Initializes a new instance of the <see cref="UrlProviderResolver"/> class with an initial list of provider types.
/// </summary>
/// <param name="providerTypes">The list of provider types.</param>
/// <remarks>The resolver is created by the <c>WebBootManager</c> and thus the constructor remains internal.</remarks>
internal UrlProviderResolver(IEnumerable<Type> providerTypes)
: base(providerTypes)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="UrlProviderResolver"/> class with an initial list of provider types.
/// </summary>
/// <param name="providerTypes">The list of provider types.</param>
/// <remarks>The resolver is created by the <c>WebBootManager</c> and thus the constructor remains internal.</remarks>
internal UrlProviderResolver(params Type[] providerTypes)
: base(providerTypes)
{ }
/// <summary>
/// Gets the providers.
/// </summary>
public IEnumerable<IUrlProvider> Providers
{
get { return Values; }
}
}
}

View File

@@ -28,7 +28,7 @@ namespace Umbraco.Web.Templates
return text;
}
var niceUrlsProvider = UmbracoContext.Current.NiceUrlProvider;
var urlProvider = UmbracoContext.Current.UrlProvider;
// Parse internal links
MatchCollection tags = Regex.Matches(text, @"href=""[/]?(?:\{|\%7B)localLink:([0-9]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
@@ -36,7 +36,7 @@ namespace Umbraco.Web.Templates
if (tag.Groups.Count > 0)
{
string id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1);
string newLink = niceUrlsProvider.GetNiceUrl(int.Parse(id));
string newLink = urlProvider.GetUrl(int.Parse(id));
text = text.Replace(tag.Value.ToString(), "href=\"" + newLink);
}
return text;

View File

@@ -326,6 +326,13 @@
<Compile Include="Mvc\SurfaceRouteHandler.cs" />
<Compile Include="Search\ExamineIndexerModel.cs" />
<Compile Include="Search\LuceneIndexerExtensions.cs" />
<Compile Include="umbraco.presentation\umbraco\dialogs\AssignDomain2.aspx.cs">
<DependentUpon>AssignDomain2.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="umbraco.presentation\umbraco\dialogs\AssignDomain2.aspx.designer.cs">
<DependentUpon>AssignDomain2.aspx</DependentUpon>
</Compile>
<Compile Include="UrlHelperExtensions.cs" />
<Compile Include="WebApi\MemberAuthorizeAttribute.cs" />
<Compile Include="WebApi\UmbracoApiController.cs" />
@@ -379,15 +386,24 @@
<Compile Include="RenderFieldCaseType.cs" />
<Compile Include="RenderFieldEncodingType.cs" />
<Compile Include="RouteCollectionExtensions.cs" />
<Compile Include="Routing\AliasUrlProvider.cs" />
<Compile Include="Routing\ContentFinderByLegacy404.cs" />
<Compile Include="Routing\ContentFinderByNotFoundHandler.cs" />
<Compile Include="Routing\ContentFinderByPageIdQuery.cs" />
<Compile Include="Mvc\SurfaceControllerResolver.cs" />
<Compile Include="Routing\ContentFinderByNotFoundHandlers.cs" />
<Compile Include="Routing\ISiteDomainHelper.cs" />
<Compile Include="Routing\RoutableAttemptEventArgs.cs" />
<Compile Include="Routing\DefaultUrlProvider.cs" />
<Compile Include="Routing\DomainAndUri.cs" />
<Compile Include="Routing\IUrlProvider.cs" />
<Compile Include="Routing\SiteDomainHelper.cs" />
<Compile Include="Routing\SiteDomainHelperResolver.cs" />
<Compile Include="Routing\UrlProviderResolver.cs" />
<Compile Include="Routing\NotFoundHandlerHelper.cs" />
<Compile Include="Routing\EnsureRoutableOutcome.cs" />
<Compile Include="Routing\PublishedContentRequestEngine.cs" />
<Compile Include="Routing\UrlProvider.cs" />
<Compile Include="Routing\WebServicesRouteConstraint.cs" />
<Compile Include="Search\ExamineSearcherModel.cs" />
<Compile Include="Search\ExamineEvents.cs" />
@@ -569,7 +585,6 @@
<Compile Include="Routing\ContentFinderByNiceUrl.cs" />
<Compile Include="Routing\ContentFinderByNiceUrlAndTemplate.cs" />
<Compile Include="Routing\ContentFinderByProfile.cs" />
<Compile Include="Routing\NiceUrlProvider.cs" />
<Compile Include="PluginManagerExtensions.cs" />
<Compile Include="Routing\DomainHelper.cs" />
<Compile Include="Routing\ContentFinderResolver.cs" />
@@ -1763,6 +1778,7 @@
</Compile>
<Compile Include="WebServices\BulkPublishController.cs" />
<Compile Include="WebServices\CoreStringsController.cs" />
<Compile Include="WebServices\DomainsApiController.cs" />
<Compile Include="WebServices\ExamineManagementApiController.cs" />
<Compile Include="WebServices\FolderBrowserService.cs" />
<Compile Include="WebServices\SaveFileController.cs" />
@@ -1786,6 +1802,9 @@
<Content Include="umbraco.presentation\umbraco\dashboard\StartupDashboardIntro.ascx" />
<Content Include="umbraco.presentation\umbraco\dashboard\StartupDashboardKits.ascx" />
<Content Include="umbraco.presentation\umbraco\dashboard\StartupDashboardVideos.ascx" />
<Content Include="umbraco.presentation\umbraco\dialogs\AssignDomain2.aspx">
<SubType>ASPXCodeBehind</SubType>
</Content>
<Content Include="umbraco.presentation\umbraco\dialogs\TemplateSkinning.aspx" />
<Content Include="umbraco.presentation\umbraco\LiveEditing\Modules\SkinModule\CssParser.aspx" />
<Content Include="umbraco.presentation\umbraco\LiveEditing\Modules\SkinModule\ImageUploader.aspx" />

View File

@@ -84,7 +84,11 @@ namespace Umbraco.Web
var umbracoContext = new UmbracoContext(httpContext, applicationContext);
// create the nice urls provider
var niceUrls = new NiceUrlProvider(PublishedContentStoreResolver.Current.PublishedContentStore, umbracoContext);
// there's one per request because there are some behavior parameters that can be changed
var urlProvider = new UrlProvider(
umbracoContext,
PublishedContentStoreResolver.Current.PublishedContentStore,
UrlProviderResolver.Current.Providers);
// create the RoutingContext, and assign
var routingContext = new RoutingContext(
@@ -92,7 +96,7 @@ namespace Umbraco.Web
ContentFinderResolver.Current.Finders,
ContentLastChanceFinderResolver.Current.Finder,
PublishedContentStoreResolver.Current.PublishedContentStore,
niceUrls,
urlProvider,
RoutesCacheResolver.Current.RoutesCache);
//assign the routing context back
@@ -270,13 +274,13 @@ namespace Umbraco.Web
/// <remarks>
/// If the RoutingContext is null, this will throw an exception.
/// </remarks>
internal NiceUrlProvider NiceUrlProvider
internal UrlProvider UrlProvider
{
get
{
if (RoutingContext == null)
throw new InvalidOperationException("Cannot access the NiceUrlProvider when the UmbracoContext's RoutingContext is null");
return RoutingContext.NiceUrlProvider;
throw new InvalidOperationException("Cannot access the UrlProvider when the UmbracoContext's RoutingContext is null");
return RoutingContext.UrlProvider;
}
}

View File

@@ -416,8 +416,8 @@ namespace Umbraco.Web
/// <returns>String with a friendly url from a node</returns>
public string NiceUrl(int nodeId)
{
var niceUrlsProvider = UmbracoContext.Current.NiceUrlProvider;
return niceUrlsProvider.GetNiceUrl(nodeId);
var urlProvider = UmbracoContext.Current.UrlProvider;
return urlProvider.GetUrl(nodeId);
}
/// <summary>
@@ -427,8 +427,8 @@ namespace Umbraco.Web
/// <returns>String with a friendly url with full domain from a node</returns>
public string NiceUrlWithDomain(int nodeId)
{
var niceUrlsProvider = UmbracoContext.Current.NiceUrlProvider;
return niceUrlsProvider.GetNiceUrl(nodeId, true);
var urlProvider = UmbracoContext.Current.UrlProvider;
return urlProvider.GetUrl(nodeId, true);
}
#endregion

View File

@@ -1,14 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Compilation;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.UI;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Web.Routing;
@@ -205,7 +199,7 @@ namespace Umbraco.Web
{
var uri = context.OriginalRequestUrl;
var reason = EnsureRoutableOutcome.IsRoutable;;
var reason = EnsureRoutableOutcome.IsRoutable;
// ensure this is a document request
if (!EnsureDocumentRequest(httpContext, uri))
@@ -244,9 +238,9 @@ namespace Umbraco.Web
// handle directory-urls used for asmx
// legacy - what's the point really?
if (maybeDoc && GlobalSettings.UseDirectoryUrls)
if (/*maybeDoc &&*/ GlobalSettings.UseDirectoryUrls)
{
int asmxPos = lpath.IndexOf(".asmx/");
int asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase);
if (asmxPos >= 0)
{
// use uri.AbsolutePath, not path, 'cos path has been lowercased
@@ -320,41 +314,36 @@ namespace Umbraco.Web
// ensures Umbraco has at least one published node
// if not, rewrites to splash and return false
// if yes, return true
bool EnsureHasContent(UmbracoContext context, HttpContextBase httpContext)
private static bool EnsureHasContent(UmbracoContext context, HttpContextBase httpContext)
{
var store = context.RoutingContext.PublishedContentStore;
if (!store.HasContent(context))
{
LogHelper.Warn<UmbracoModule>("Umbraco has no content");
if (store.HasContent(context))
return true;
httpContext.Response.StatusCode = 503;
LogHelper.Warn<UmbracoModule>("Umbraco has no content");
var noContentUrl = "~/config/splashes/noNodes.aspx";
httpContext.RewritePath(UriUtility.ToAbsolute(noContentUrl));
httpContext.Response.StatusCode = 503;
return false;
}
else
{
return true;
}
const string noContentUrl = "~/config/splashes/noNodes.aspx";
httpContext.RewritePath(UriUtility.ToAbsolute(noContentUrl));
return false;
}
// ensures Umbraco is configured
// if not, redirect to install and return false
// if yes, return true
bool EnsureIsConfigured(HttpContextBase httpContext, Uri uri)
{
if (!ApplicationContext.Current.IsConfigured)
{
LogHelper.Warn<UmbracoModule>("Umbraco is not configured");
private static bool EnsureIsConfigured(HttpContextBase httpContext, Uri uri)
{
if (ApplicationContext.Current.IsConfigured)
return true;
string installPath = UriUtility.ToAbsolute(SystemDirectories.Install);
string installUrl = string.Format("{0}/default.aspx?redir=true&url={1}", installPath, HttpUtility.UrlEncode(uri.ToString()));
httpContext.Response.Redirect(installUrl, true);
return false;
}
return true;
LogHelper.Warn<UmbracoModule>("Umbraco is not configured");
var installPath = UriUtility.ToAbsolute(Core.IO.SystemDirectories.Install);
var installUrl = string.Format("{0}/default.aspx?redir=true&url={1}", installPath, HttpUtility.UrlEncode(uri.ToString()));
httpContext.Response.Redirect(installUrl, true);
return false;
}
#endregion
@@ -433,7 +422,7 @@ namespace Umbraco.Web
BeginRequest(new HttpContextWrapper(httpContext));
};
app.PostResolveRequestCache += (sender, e) =>
app.PostResolveRequestCache += (sender, e) =>
{
var httpContext = ((HttpApplication)sender).Context;
ProcessRequest(new HttpContextWrapper(httpContext));

View File

@@ -275,22 +275,25 @@ namespace Umbraco.Web
PublishedMediaStoreResolver.Current = new PublishedMediaStoreResolver(new DefaultPublishedMediaStore());
FilteredControllerFactoriesResolver.Current = new FilteredControllerFactoriesResolver(
//add all known factories, devs can then modify this list on application startup either by binding to events
//or in their own global.asax
// add all known factories, devs can then modify this list on application
// startup either by binding to events or in their own global.asax
new[]
{
typeof (RenderControllerFactory)
});
UrlProviderResolver.Current = new UrlProviderResolver(
//typeof(AliasUrlProvider), // not enabled by default
typeof(DefaultUrlProvider)
);
// the legacy 404 will run from within ContentFinderByNotFoundHandlers below
// so for the time being there is no last chance finder
ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver();
ContentFinderResolver.Current = new ContentFinderResolver(
//add all known resolvers in the correct order, devs can then modify this list on application startup either by binding to events
//or in their own global.asax
new[]
{
// add all known resolvers in the correct order, devs can then modify this list
// on application startup either by binding to events or in their own global.asax
typeof (ContentFinderByPageIdQuery),
typeof (ContentFinderByNiceUrl),
typeof (ContentFinderByIdPath),
@@ -300,7 +303,9 @@ namespace Umbraco.Web
//typeof (ContentFinderByProfile),
//typeof (ContentFinderByUrlAlias),
typeof (ContentFinderByNotFoundHandlers)
});
);
SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper());
RoutesCacheResolver.Current = new RoutesCacheResolver(new DefaultRoutesCache(_isForTesting == false));

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Umbraco.Core;
using Umbraco.Web.WebApi;
//using umbraco.cms.businesslogic.language;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.WebServices
{
/// <summary>
/// A REST controller used for managing domains.
/// </summary>
/// <remarks>Nothing to do with Active Directory.</remarks>
public class DomainsApiController : UmbracoAuthorizedApiController
{
[HttpPost]
// can't pass multiple complex args in json post request...
public PostBackModel SaveLanguageAndDomains(PostBackModel model)
{
var node = ApplicationContext.Current.Services.ContentService.GetById(model.NodeId);
if (node == null)
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(string.Format("There is no content node with id {0}.", model.NodeId)),
ReasonPhrase = "Node Not Found."
});
if (!UmbracoUser.GetPermissions(node.Path).Contains(global::umbraco.BusinessLogic.Actions.ActionAssignDomain.Instance.Letter))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent("You do not have permission to assign domains on that node."),
ReasonPhrase = "Permission Denied."
});
model.Valid = true;
var domains = Routing.DomainHelper.GetNodeDomains(model.NodeId, true);
var languages = global::umbraco.cms.businesslogic.language.Language.GetAllAsList().ToArray();
var language = model.Language > 0 ? languages.FirstOrDefault(l => l.id == model.Language) : null;
// process wildcard
if (language != null)
{
var wildcard = domains.FirstOrDefault(d => d.IsWildcard);
if (wildcard != null)
wildcard.Language = language;
else // yet there is a race condition here...
Domain.MakeNew("*" + model.NodeId, model.NodeId, model.Language);
}
else
{
var wildcard = domains.FirstOrDefault(d => d.IsWildcard);
if (wildcard != null)
wildcard.Delete();
}
// process domains
foreach (var domain in domains.Where(d => model.Domains.All(m => !m.Name.Equals(d.Name, StringComparison.OrdinalIgnoreCase))))
domain.Delete();
var names = new List<string>();
foreach (var domainModel in model.Domains.Where(m => !string.IsNullOrWhiteSpace(m.Name)))
{
language = languages.FirstOrDefault(l => l.id == domainModel.Lang);
if (language == null)
continue;
var name = domainModel.Name.ToLowerInvariant();
if (names.Contains(name))
{
domainModel.Duplicate = true;
continue;
}
names.Add(name);
var domain = domains.FirstOrDefault(d => d.Name.Equals(domainModel.Name, StringComparison.OrdinalIgnoreCase));
if (domain != null)
domain.Language = language;
else if (Domain.Exists(domainModel.Name))
domainModel.Duplicate = true;
else // yet there is a race condition here...
Domain.MakeNew(name, model.NodeId, domainModel.Lang);
}
model.Valid = model.Domains.All(m => !m.Duplicate);
return model;
}
#region Models
public class PostBackModel
{
public bool Valid { get; set; }
public int NodeId { get; set; }
public int Language { get; set; }
public DomainModel[] Domains { get; set; }
}
public class DomainModel
{
public DomainModel(string name, int lang)
{
Name = name;
Lang = lang;
}
public string Name { get; private set; }
public int Lang { get; private set; }
public bool Duplicate { get; set; }
}
#endregion
}
}

View File

@@ -0,0 +1,77 @@
<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" Codebehind="AssignDomain2.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.AssignDomain2" %>
<%@ Import Namespace="Umbraco.Web" %>
<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>
<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
<asp:Content ContentPlaceHolderID="head" runat="server">
<umb:JsInclude runat="server" FilePath="Dialogs/AssignDomain2.js" PathNameAlias="UmbracoClient" />
<umb:JsInclude runat="server" FilePath="Application/JQuery/jquery.validate.min.js" PathNameAlias="UmbracoClient" />
<umb:CssInclude runat="server" FilePath="Dialogs/AssignDomain2.css" PathNameAlias="UmbracoClient" />
<script type="text/javascript">
(function ($) {
$(document).ready(function () {
var dialog = new Umbraco.Dialogs.AssignDomain2({
nodeId: <%=GetNodeId()%>,
restServiceLocation: '<%=GetRestServicePath() %>',
invalidDomain: '<%=umbraco.ui.Text("assignDomain", "invalidDomain") %>',
duplicateDomain: '<%=umbraco.ui.Text("assignDomain", "duplicateDomain") %>',
<asp:Literal runat="server" ID="data" />
});
dialog.init();
});
})(jQuery);
</script>
</asp:Content>
<asp:Content ContentPlaceHolderID="body" runat="server">
<cc1:Feedback ID="feedback" runat="server" />
<div id="komask"></div>
<div>
<cc1:Pane runat="server" ID="pane_language">
<cc1:PropertyPanel runat="server" ID="prop_language">
<select class="language" name="language" data-bind="options: languages, optionsText: 'Code', optionsValue: 'Id', value: language, optionsCaption: '<%=umbraco.ui.Text("assignDomain", "inherit") %>'"></select>
<br /><small><%=umbraco.ui.Text("assignDomain", "setLanguageHelp") %></small>
</cc1:PropertyPanel>
</cc1:Pane>
<cc1:Pane runat="server" ID="pane_domains">
<cc1:PropertyPanel runat="server">
<table class="domains" data-bind="visible: domains().length > 0">
<thead>
<tr>
<th><%=umbraco.ui.Text("assignDomain", "domain") %></th>
<th><%=umbraco.ui.Text("assignDomain", "language") %></th>
<th />
</tr>
</thead>
<tbody data-bind="foreach: domains">
<tr>
<td valign="top"><input class="domain duplicate" data-bind="value: Name, uniqueName: true" /><input type="hidden" value="0" /></td>
<td valign="top"><select class="language" data-bind="options: $parent.languages, optionsText: 'Code', optionsValue: 'Id', value: Lang, uniqueName: true"></select></td>
<td valign="top"><a href="#" class="remove" data-bind="click: $parent.removeDomain"><%=umbraco.ui.Text("assignDomain", "remove") %></a></td>
</tr>
</tbody>
</table>
<table class="addDomain">
<tr>
<td valign="top"><button data-bind="click: addDomain"><%=umbraco.ui.Text("assignDomain", "addNew") %></button></td>
<td class="help"><small><%=umbraco.ui.Text("assignDomain", "domainHelp") %></small></td>
</tr>
</table>
</cc1:PropertyPanel>
</cc1:Pane>
<p>
<asp:PlaceHolder runat="server" ID="phSave">
<button id="btnSave"><%=umbraco.ui.Text("buttons", "save") %></button>
<em><%=umbraco.ui.Text("general", "or")%></em>
</asp:PlaceHolder>
<a href="#" style="color: #0000ff;" onclick="UmbClientMgr.closeModalWindow()"><%=umbraco.ui.Text("general", "cancel")%></a>
</p>
</div>
</asp:Content>

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.UI.Pages;
using Umbraco.Web;
using Umbraco.Web.Routing;
using Umbraco.Web.WebServices;
namespace umbraco.dialogs
{
public partial class AssignDomain2 : UmbracoEnsuredPage
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var nodeId = GetNodeId();
var node = ApplicationContext.Current.Services.ContentService.GetById(nodeId);
if (node == null)
{
feedback.Text = ui.Text("assignDomain", "invalidNode");
pane_language.Visible = false;
pane_domains.Visible = false;
phSave.Visible = false;
return;
}
if (!UmbracoUser.GetPermissions(node.Path).Contains(BusinessLogic.Actions.ActionAssignDomain.Instance.Letter))
{
feedback.Text = ui.Text("assignDomain", "permissionDenied");
pane_language.Visible = false;
pane_domains.Visible = false;
phSave.Visible = false;
return;
}
pane_language.Text = ui.Text("assignDomain", "setLanguage");
pane_domains.Text = ui.Text("assignDomain", "setDomains");
prop_language.Text = ui.Text("assignDomain", "language");
var nodeDomains = DomainHelper.GetNodeDomains(nodeId, true);
var wildcard = nodeDomains.FirstOrDefault(d => d.IsWildcard);
var sb = new StringBuilder();
sb.Append("languages: [");
var i = 0;
foreach (var language in ApplicationContext.Current.Services.LocalizationService.GetAllLanguages())
sb.AppendFormat("{0}{{ \"Id\": {1}, \"Code\": \"{2}\" }}", (i++ == 0 ? "" : ","), language.Id, language.IsoCode);
sb.Append("]\r\n");
sb.AppendFormat(",language: {0}", wildcard == null ? "undefined" : wildcard.Language.id.ToString());
sb.Append(",domains: [");
i = 0;
foreach (var domain in nodeDomains.Where(d => !d.IsWildcard))
sb.AppendFormat("{0}{{ \"Name\": \"{1}\", \"Lang\": \"{2}\" }}", (i++ == 0 ? "" :","), domain.Name, domain.Language.id);
sb.Append("]\r\n");
data.Text = sb.ToString();
}
protected int GetNodeId()
{
int nodeId;
if (!int.TryParse(Request.QueryString["id"], out nodeId))
nodeId = -1;
return nodeId;
}
protected string GetRestServicePath()
{
const string action = "ListDomains";
var path = Url.GetUmbracoApiService<DomainsApiController>(action);
return path.TrimEnd(action).EnsureEndsWith('/');
}
}
}

View File

@@ -0,0 +1,69 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace umbraco.dialogs {
public partial class AssignDomain2 {
/// <summary>
/// data control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Literal data;
/// <summary>
/// feedback control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.Feedback feedback;
/// <summary>
/// pane_language control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.Pane pane_language;
/// <summary>
/// prop_language control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.PropertyPanel prop_language;
/// <summary>
/// pane_domains control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.Pane pane_domains;
/// <summary>
/// phSave control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.PlaceHolder phSave;
}
}

View File

@@ -390,8 +390,8 @@ namespace umbraco.cms.presentation
return;
}
var niceUrlProvider = Umbraco.Web.UmbracoContext.Current.RoutingContext.NiceUrlProvider;
var url = niceUrlProvider.GetNiceUrl(_document.Id);
var urlProvider = Umbraco.Web.UmbracoContext.Current.RoutingContext.UrlProvider;
var url = urlProvider.GetUrl(_document.Id);
string niceUrlText = null;
var altUrlsText = new System.Text.StringBuilder();
@@ -415,8 +415,8 @@ namespace umbraco.cms.presentation
{
niceUrlText = string.Format("<a href=\"{0}\" target=\"_blank\">{0}</a>", url);
foreach (var altUrl in niceUrlProvider.GetAllAbsoluteNiceUrls(_document.Id).Where(u => u != url))
altUrlsText.AppendFormat("<a href=\"{0}\" target=\"_blank\">{0}</a><br />", altUrl);
foreach (var otherUrl in urlProvider.GetOtherUrls(_document.Id))
altUrlsText.AppendFormat("<a href=\"{0}\" target=\"_blank\">{0}</a><br />", otherUrl);
}
UpdateNiceUrlProperties(niceUrlText, altUrlsText.ToString());

View File

@@ -147,7 +147,12 @@ namespace umbraco.cms.businesslogic.web
internal static List<Domain> GetDomains()
{
return Cache.GetCacheItem<List<Domain>>("UmbracoDomainList", getDomainsSyncLock, TimeSpan.FromMinutes(30),
return GetDomains(false);
}
internal static List<Domain> GetDomains(bool includeWildcards)
{
var domains = Cache.GetCacheItem<List<Domain>>("UmbracoDomainList", getDomainsSyncLock, TimeSpan.FromMinutes(30),
delegate
{
List<Domain> result = new List<Domain>();
@@ -170,6 +175,11 @@ namespace umbraco.cms.businesslogic.web
}
return result;
});
if (!includeWildcards)
domains = domains.Where(d => !d.IsWildcard).ToList();
return domains;
}
public static Domain GetDomain(string DomainName)
@@ -259,5 +269,23 @@ namespace umbraco.cms.businesslogic.web
if (AfterDelete != null)
AfterDelete(this, e);
}
#region Pipeline Refactoring
// NOTE: the wildcard name thing should be managed by the Domain class
// internally but that would break too much backward compatibility, so
// we don't do it now. Will do it when the Domain class migrates to the
// new Core.Models API.
/// <summary>
/// Gets a value indicating whether the domain is a wildcard domain.
/// </summary>
/// <returns>A value indicating whether the domain is a wildcard domain.</returns>
public bool IsWildcard
{
get { return string.IsNullOrWhiteSpace(Name) || Name.StartsWith("*"); }
}
#endregion
}
}