diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index f60265940d..9f34756af8 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -58,9 +58,14 @@ namespace Umbraco.Core.Strings static void InitializeLegacyUrlReplaceCharacters() { var replaceChars = UmbracoSettings.UrlReplaceCharacters; - foreach (var node in replaceChars.SelectNodes("char").Cast()) + if (replaceChars == null) return; + var nodes = replaceChars.SelectNodes("char"); + if (nodes == null) return; + foreach (var node in nodes.Cast()) { - var org = node.Attributes.GetNamedItem("org"); + var attributes = node.Attributes; + if (attributes == null) continue; + var org = attributes.GetNamedItem("org"); if (org != null && org.Value != "") UrlReplaceCharacters[org.Value] = XmlHelper.GetNodeValue(node); } diff --git a/src/Umbraco.Tests/CoreXml/FrameworkXmlTests.cs b/src/Umbraco.Tests/CoreXml/FrameworkXmlTests.cs new file mode 100644 index 0000000000..7ac4af2d20 --- /dev/null +++ b/src/Umbraco.Tests/CoreXml/FrameworkXmlTests.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.XPath; + +using NUnit.Framework; + +namespace Umbraco.Tests.CoreXml +{ + [TestFixture] + public class FrameworkXmlTests + { + const string Xml1 = @" + + + + text21 + + + text31 + + + + + +"; + + + // Umbraco : the following test shows that when legacy imports the whole tree in a + // "contentAll" xslt macro parameter, the entire collection of nodes is cloned ie is + // duplicated. + // + // What is the impact on memory? + // What happens for non-xslt macros? + + [Test] + public void ImportNodeClonesImportedNode() + { + var doc1 = new XmlDocument(); + doc1.LoadXml(Xml1); + + var node1 = doc1.SelectSingleNode("//item2"); + Assert.IsNotNull(node1); + + var doc2 = new XmlDocument(); + doc2.LoadXml(""); + var node2 = doc2.ImportNode(node1, true); + var root2 = doc2.DocumentElement; + Assert.IsNotNull(root2); + root2.AppendChild(node2); + + var node3 = doc2.SelectSingleNode("//item2"); + + Assert.AreNotSame(node1, node2); // has been cloned + Assert.AreSame(node2, node3); // has been appended + + Assert.AreNotSame(node1.FirstChild, node2.FirstChild); // deep clone + } + + + // Umbraco: the CanRemove...NodeAndNavigate tests shows that if the underlying XmlDocument + // is modified while navigating, then strange situations can be created. For xslt macros, + // the result depends on what the xslt engine is doing at the moment = unpredictable. + // + // What happens for non-xslt macros? + + [Test] + public void CanRemoveCurrentNodeAndNavigate() + { + var doc1 = new XmlDocument(); + doc1.LoadXml(Xml1); + var nav1 = doc1.CreateNavigator(); + + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("root", nav1.Name); + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("items", nav1.Name); + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("item1", nav1.Name); + Assert.IsTrue(nav1.MoveToNext()); + Assert.AreEqual("item2", nav1.Name); + + var node1 = doc1.SelectSingleNode("//item2"); + Assert.IsNotNull(node1); + var parent1 = node1.ParentNode; + Assert.IsNotNull(parent1); + parent1.RemoveChild(node1); + + // navigator now navigates on an isolated fragment + // that is rooted on the node that was removed + + Assert.AreEqual("item2", nav1.Name); + Assert.IsFalse(nav1.MoveToPrevious()); + Assert.IsFalse(nav1.MoveToNext()); + Assert.IsFalse(nav1.MoveToParent()); + + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("item21", nav1.Name); + Assert.IsTrue(nav1.MoveToParent()); + Assert.AreEqual("item2", nav1.Name); + + nav1.MoveToRoot(); + Assert.AreEqual("item2", nav1.Name); + } + + [Test] + public void CanRemovePathNodeAndNavigate() + { + var doc1 = new XmlDocument(); + doc1.LoadXml(Xml1); + var nav1 = doc1.CreateNavigator(); + + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("root", nav1.Name); + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("items", nav1.Name); + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("item1", nav1.Name); + Assert.IsTrue(nav1.MoveToNext()); + Assert.AreEqual("item2", nav1.Name); + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("item21", nav1.Name); + + var node1 = doc1.SelectSingleNode("//item2"); + Assert.IsNotNull(node1); + var parent1 = node1.ParentNode; + Assert.IsNotNull(parent1); + parent1.RemoveChild(node1); + + // navigator now navigates on an isolated fragment + // that is rooted on the node that was removed + + Assert.AreEqual("item21", nav1.Name); + Assert.IsTrue(nav1.MoveToParent()); + Assert.AreEqual("item2", nav1.Name); + Assert.IsFalse(nav1.MoveToPrevious()); + Assert.IsFalse(nav1.MoveToNext()); + Assert.IsFalse(nav1.MoveToParent()); + + nav1.MoveToRoot(); + Assert.AreEqual("item2", nav1.Name); + } + + [Test] + public void CanRemoveOutOfPathNodeAndNavigate() + { + var doc1 = new XmlDocument(); + doc1.LoadXml(Xml1); + var nav1 = doc1.CreateNavigator(); + + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("root", nav1.Name); + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("items", nav1.Name); + Assert.IsTrue(nav1.MoveToFirstChild()); + Assert.AreEqual("item1", nav1.Name); + Assert.IsTrue(nav1.MoveToNext()); + Assert.AreEqual("item2", nav1.Name); + Assert.IsTrue(nav1.MoveToNext()); + Assert.AreEqual("item3", nav1.Name); + Assert.IsTrue(nav1.MoveToNext()); + Assert.AreEqual("item4", nav1.Name); + + var node1 = doc1.SelectSingleNode("//item2"); + Assert.IsNotNull(node1); + var parent1 = node1.ParentNode; + Assert.IsNotNull(parent1); + parent1.RemoveChild(node1); + + // navigator sees the change + + Assert.AreEqual("item4", nav1.Name); + Assert.IsTrue(nav1.MoveToPrevious()); + Assert.AreEqual("item3", nav1.Name); + Assert.IsTrue(nav1.MoveToPrevious()); + Assert.AreEqual("item1", nav1.Name); + } + + // Umbraco: the following test shows that if the underlying XmlDocument is modified while + // iterating, then strange situations can be created. For xslt macros, the result depends + // on what the xslt engine is doing at the moment = unpredictable. + // + // What happens for non-xslt macros? + + [Test] + public void CanRemoveNodeAndIterate() + { + var doc1 = new XmlDocument(); + doc1.LoadXml(Xml1); + var nav1 = doc1.CreateNavigator(); + + var iter1 = nav1.Select("//items/*"); + var iter2 = nav1.Select("//items/*"); + + Assert.AreEqual(6, iter1.Count); + + var node1 = doc1.SelectSingleNode("//item2"); + Assert.IsNotNull(node1); + var parent1 = node1.ParentNode; + Assert.IsNotNull(parent1); + parent1.RemoveChild(node1); + + // iterator partially sees the change + + Assert.AreEqual(6, iter1.Count); // has been cached, not updated + Assert.AreEqual(5, iter2.Count); // not calculated yet, correct value + + var count = 0; + while (iter1.MoveNext()) + count++; + + Assert.AreEqual(5, count); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 2ab62ab199..15e87f1b40 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -175,6 +175,7 @@ + diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 0e31bf0bc3..6fac44e2cb 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -209,12 +209,15 @@ namespace Umbraco.Web.Cache if (payloads.Any(x => x.Type == typeof(IContentType).Name) && !payloads.All(x => x.IsNew)) //if they are all new then don't proceed { - //we need to clear the routes cache here! - var contentCache = PublishedContentCacheResolver.Current.ContentCache as PublishedContentCache; + // SD: we need to clear the routes cache here! + // + // zpqrtbnk: no, not here, in fact the caches should subsribe to refresh events else we + // are creating a nasty dependency - but keep it like that for the time being while + // SD is cleaning cache refreshers up. + + var contentCache = PublishedCachesResolver.Current.Caches.ContentCache as PublishedContentCache; if (contentCache != null) - { contentCache.RoutesCache.Clear(); - } } } } diff --git a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs index 3de428658f..c08d2f37a7 100644 --- a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs @@ -41,12 +41,16 @@ namespace Umbraco.Web.Cache private void ClearCache() { ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.DomainCacheKey); - //we need to clear the routes cache here! + + // SD: we need to clear the routes cache here! + // + // zpqrtbnk: no, not here, in fact the caches should subsribe to refresh events else we + // are creating a nasty dependency - but keep it like that for the time being while + // SD is cleaning cache refreshers up. + var contentCache = PublishedContentCacheResolver.Current.ContentCache as PublishedContentCache; if (contentCache != null) - { contentCache.RoutesCache.Clear(); - } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 8451bcb6f2..4f87a3532e 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -87,14 +87,16 @@ namespace Umbraco.Web.Routing if (_pcr.IsRedirect) return; - // safety if (!_pcr.HasPublishedContent) - _pcr.Is404 = true; + { + // safety + _pcr.Is404 = true; - // handle 404 : return - // whoever called us is in charge of doing what's appropriate - if (_pcr.Is404) - return; + // whoever called us is in charge of doing what's appropriate + return; + } + + // we may be 404 _and_ have a content // can't go beyond that point without a PublishedContent to render // it's ok not to have a template, in order to give MVC a chance to hijack routes diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 7bc3cee4ae..96677f7e62 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -114,11 +114,13 @@ namespace Umbraco.Web /// /// The published content cache. /// The published media cache. + /// An optional value overriding detection of preview mode. internal UmbracoContext( HttpContextBase httpContext, ApplicationContext applicationContext, IPublishedContentCache contentCache, - IPublishedMediaCache mediaCache) + IPublishedMediaCache mediaCache, + bool? preview = null) { if (httpContext == null) throw new ArgumentNullException("httpContext"); if (applicationContext == null) throw new ArgumentNullException("applicationContext"); @@ -131,6 +133,7 @@ namespace Umbraco.Web ContentCache = new ContextualPublishedContentCache(contentCache, this); MediaCache = new ContextualPublishedMediaCache(mediaCache, this); + InPreviewMode = preview ?? DetectInPreviewModeFromRequest(); // set the urls... //original request url @@ -319,22 +322,21 @@ namespace Umbraco.Web /// /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) /// - public bool InPreviewMode - { - get - { - var request = GetRequestFromContext(); - if (request == null || request.Url == null) - return false; + public bool InPreviewMode { get; private set; } - var currentUrl = request.Url.AbsolutePath; - // zb-00004 #29956 : refactor cookies names & handling - return - StateHelper.Cookies.Preview.HasValue // has preview cookie - && UmbracoUser != null // has user - && !currentUrl.StartsWith(Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco)); // is not in admin UI - } - } + private bool DetectInPreviewModeFromRequest() + { + var request = GetRequestFromContext(); + if (request == null || request.Url == null) + return false; + + var currentUrl = request.Url.AbsolutePath; + // zb-00004 #29956 : refactor cookies names & handling + return + StateHelper.Cookies.Preview.HasValue // has preview cookie + && UmbracoUser != null // has user + && !currentUrl.StartsWith(Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco)); // is not in admin UI + } private HttpRequestBase GetRequestFromContext() {