diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index cbc52e79de..eae9cdcf32 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -257,17 +257,17 @@ namespace Umbraco.Core MigrationResolver.Current = new MigrationResolver( () => PluginManager.Current.ResolveMigrationTypes()); - // fixme - remove that one eventually + // fixme - remove support for obsolete PropertyEditorValueConverter PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( PluginManager.Current.ResolvePropertyEditorValueConverters()); // initialize the new property value converters - // fixme - discuss: explicit registration vs. discovery? + // fixme - discuss property converters explicit registration vs. discovery PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( PluginManager.Current.ResolveTypes()); // add the internal ones - // fixme - should be public not internal? + // fixme - property converters should be public, not internal, and auto-discovered PropertyValueConvertersResolver.Current.AddType(); PropertyValueConvertersResolver.Current.AddType(); PropertyValueConvertersResolver.Current.AddType(); diff --git a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs index 1338ee03e2..91004fa114 100644 --- a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs +++ b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs @@ -112,7 +112,8 @@ namespace Umbraco.Core.Dynamics { //don't log here, we return this exception because the caller may need to do something specific when //this exception occurs. - return Attempt.Fail(ext); + var mresult = new TryInvokeMemberResult(null, TryInvokeMemberSuccessReason.FoundExtensionMethod); + return Attempt.Fail(mresult, ext); } catch (Exception ex) { @@ -124,7 +125,8 @@ namespace Umbraco.Core.Dynamics sb.Append(t + ","); } LogHelper.Error(sb.ToString(), ex); - return Attempt.Fail(ex); + var mresult = new TryInvokeMemberResult(null, TryInvokeMemberSuccessReason.FoundExtensionMethod); + return Attempt.Fail(mresult, ex); } } return Attempt.Fail(); diff --git a/src/Umbraco.Core/Dynamics/DynamicNull.cs b/src/Umbraco.Core/Dynamics/DynamicNull.cs index 91d50ce545..593808e867 100644 --- a/src/Umbraco.Core/Dynamics/DynamicNull.cs +++ b/src/Umbraco.Core/Dynamics/DynamicNull.cs @@ -38,6 +38,11 @@ namespace Umbraco.Core.Dynamics return this; } + public DynamicNull ToContentSet() + { + return this; + } + public int Count() { return 0; diff --git a/src/Umbraco.Core/Models/IPublishedContent.cs b/src/Umbraco.Core/Models/IPublishedContent.cs index 5c93b32c16..1e56cb5822 100644 --- a/src/Umbraco.Core/Models/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/IPublishedContent.cs @@ -148,7 +148,7 @@ namespace Umbraco.Core.Models /// The recursive syntax (eg "_title") is _not_ supported here. /// The alias is case-insensitive. /// - object this[string alias] { get; } // fixme - kill in v7 + object this[string alias] { get; } // fixme - should obsolete this[alias] #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs index c4b49d84cf..bf4e983c7c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs @@ -83,7 +83,8 @@ namespace Umbraco.Core.Models.PublishedContent // indicates that the source has changed // so the set can clear its inner caches - public void SourceChanged() + // should only be used by DynamicPublishedContentList + internal void SourceChanged() { // reset the cached enumeration so it's enumerated again if (_enumerated == null) return; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 779fc267e9..70276ce15b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -99,22 +99,18 @@ namespace Umbraco.Core.Models.PublishedContent // and not the alias. Also, we cannot hook into the cache refresher event here, because it belongs // to Umbraco.Web, so we do it in Umbraco.Web.Models.PublishedContentTypeCaching. - // fixme - // how do we know a content type has changed? if just the property has changed, do we trigger an event? - // must run in debug mode to figure out... what happens when a DATATYPE changes? how do I get the type - // of a content right with the content and be sure it's OK? - // ******** HERE IS THE REAL ISSUE ******* + // fixme - must refactor PublishedContentType cache refresh static readonly ConcurrentDictionary ContentTypes = new ConcurrentDictionary(); - // fixme - should not be public + // internal, called by PublishedContentTypeCaching internal static void ClearAll() { Logging.LogHelper.Debug("Clear all."); ContentTypes.Clear(); } - // fixme - should not be public + // internal, called by PublishedContentTypeCaching internal static void ClearContentType(int id) { Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); @@ -124,7 +120,7 @@ namespace Umbraco.Core.Models.PublishedContent ContentTypes.RemoveAll(kvp => kvp.Value.Id == id); } - // fixme + // internal, called by PublishedContentTypeCaching internal static void ClearDataType(int id) { Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id); diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs index 1fd767da53..f816b85437 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.PropertyEditors /// /// Maps a property source value to a data object. /// - // fixme - should obsolete, use IPropertyValueConverter instead + // fixme - should obsolete that class public interface IPropertyEditorValueConverter { /// diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index c69ad86efa..991ca86322 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -24,9 +24,15 @@ namespace Umbraco.Core.PropertyEditors /// A value indicating whether conversion should take place in preview mode. /// The result of the conversion. /// - /// fixme - /// The converter should know how to convert a null raw value into the default value for the property type. - /// Raw values may come from the database or from the XML cache (thus being strings). + /// The converter should know how to convert a null raw value, meaning that no + /// value has been assigned to the property. The source value can be null. + /// With the XML cache, raw values come from the XML cache and therefore are strings. + /// With objects caches, raw values would come from the database and therefore be either + /// ints, DateTimes, or strings. + /// The converter should be prepared to handle both situations. + /// When raw values are strings, the converter must handle empty strings, whitespace + /// strings, and xml-whitespace strings appropriately, ie it should know whether to preserve + /// whitespaces. /// object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview); @@ -48,8 +54,9 @@ namespace Umbraco.Core.PropertyEditors /// The source value. /// A value indicating whether conversion should take place in preview mode. /// The result of the conversion. - /// fixme - /// The converter should know how to convert a null source value into the default value for the property type. + /// The converter should know how to convert a null source value, or any source value + /// indicating that no value has been assigned to the property. It is up to the converter to determine + /// what to return in that case: either null, or the default value... object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview); #endregion @@ -71,9 +78,14 @@ namespace Umbraco.Core.PropertyEditors /// A value indicating whether conversion should take place in preview mode. /// The result of the conversion. /// - /// fixme - /// The converter should know how to convert a null source value into the default value for the property type. - /// If successful, the result should be either null, a non-empty string, or an XPathNavigator instance. + /// The converter should know how to convert a null source value, or any source value + /// indicating that no value has been assigned to the property. It is up to the converter to determine + /// what to return in that case: either null, or the default value... + /// If successful, the result should be either null, a string, or an XPathNavigator + /// instance. Whether an xml-whitespace string should be returned as null or litterally, is + /// up to the converter. + /// The converter may want to return an XML fragment that represent a part of the content tree, + /// but should pay attention not to create infinite loops that would kill XPath and XSLT. /// object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview); diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 9de8cf4867..da9498c96d 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -56,7 +56,8 @@ namespace Umbraco.Core /// internal static bool IsClientSideRequest(this Uri url) { - // fixme - but really, is this OK? we should accept either no url, or .aspx, and everything else is out + // fixme - IsClientSideRequest should not use an hard-coded list of extensions + // a client-side request is anything that has an extension that is not .aspx? var toIgnore = new[] { ".js", ".css", ".ico", ".png", ".jpg", ".jpeg", ".gif", ".html", ".svg" }; return toIgnore.Any(x => Path.GetExtension(url.LocalPath).InvariantEquals(x)); } diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs index 6d0dc6a012..6c38383490 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContent.cs @@ -1,4 +1,4 @@ -// fixme - should #define +// fixme - should #define - but when will it be OK? #undef FIX_GET_PROPERTY_VALUE using System; @@ -26,26 +26,13 @@ namespace Umbraco.Web.Models public class DynamicPublishedContent : DynamicObject, IPublishedContent { protected internal IPublishedContent PublishedContent { get; private set; } + private DynamicPublishedContentList _contentList; + // must implement that one if we implement IPublishedContent public IEnumerable ContentSet { - get - { - // fixme - what's this? - throw new NotImplementedException("What shall we do?"); - /* - if (_contentSet != null) return _contentSet; - - // siblings = parent.Children - var parent = PublishedContent.Parent; - var dynamicParent = parent == null ? null : parent.AsDynamicOrNull(); - if (dynamicParent != null) return dynamicParent.Children; - - // silbings = content at root - var atRoot = new DynamicPublishedContentList(UmbracoContext.Current.ContentCache.GetAtRoot()); - return atRoot; - */ - } + // that is a definitively non-efficient way of doing it, though it should work + get { return _contentList ?? (_contentList = new DynamicPublishedContentList(PublishedContent.ContentSet)); } } public PublishedContentType ContentType { get { return PublishedContent.ContentType; } } @@ -56,13 +43,13 @@ namespace Umbraco.Web.Models { if (content == null) throw new ArgumentNullException("content"); PublishedContent = content; - } + } - //internal DynamicPublishedContent(IPublishedContent content, IEnumerable contentSet) - //{ - // PublishedContent = content; - // _contentSet = contentSet; - //} + internal DynamicPublishedContent(IPublishedContent content, DynamicPublishedContentList contentList) + { + PublishedContent = content; + _contentList = contentList; + } #endregion @@ -73,7 +60,6 @@ namespace Umbraco.Web.Models #region DynamicObject - // fixme - so everywhere else we use a basic dictionary but here we use a concurrent one? why? private readonly ConcurrentDictionary _cachedMemberOutput = new ConcurrentDictionary(); /// @@ -118,9 +104,9 @@ namespace Umbraco.Web.Models } //this is the result of an extension method execution gone wrong so we return dynamic null - //fixme - throws a NullRef, wrong order of checks?! - if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod - && attempt.Exception != null && attempt.Exception is TargetInvocationException) + if (attempt.Result != null + && attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod + && attempt.Exception is TargetInvocationException) { result = DynamicNull.Null; return true; @@ -137,9 +123,9 @@ namespace Umbraco.Web.Models /// protected virtual Attempt TryGetCustomMember(GetMemberBinder binder) { - // FIXME that one makes NO SENSE - // why not let the NORMAL BINDER do the work?! - // see below, that should be enough! + // as of 4.5 the CLR is case-sensitive which means that the default binder + // will handle those methods only when using the proper casing. So what + // this method does is ensure that any casing is supported. if (binder.Name.InvariantEquals("ChildrenAsList") || binder.Name.InvariantEquals("Children")) { @@ -155,6 +141,7 @@ namespace Umbraco.Web.Models } return Attempt.Succeed(parent.Id); } + return Attempt.Fail(); } @@ -190,7 +177,7 @@ namespace Umbraco.Web.Models { var reflectedProperty = GetReflectedProperty(binder.Name); var result = reflectedProperty != null - ? reflectedProperty.RawValue // fixme - why use the raw value? + ? reflectedProperty.Value : null; return Attempt.If(result != null, result); @@ -337,8 +324,9 @@ namespace Umbraco.Web.Models //reflect - // FIXME but if it exists here, then the original BINDER should have found it ALREADY? - // and if it's just a casing issue then there's BindingFlags.IgnoreCase ?!! + // as of 4.5 the CLR is case-sensitive which means that the default binder + // can handle properties only when using the proper casing. So what this + // does is ensure that any casing is supported. Func> getMember = memberAlias => @@ -790,11 +778,6 @@ namespace Umbraco.Web.Models #endregion - // fixme - #region IPublishedContent extension methods - ToContentSet - #endregion - - // fixme #region IPublishedContent extension methods - AsDynamic public dynamic AsDynamic() @@ -1300,7 +1283,7 @@ namespace Umbraco.Web.Models #endregion - // fixme - cleanup + // should probably cleanup what's below #region Where diff --git a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs index 548fc38342..94ce82799c 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs @@ -34,14 +34,24 @@ namespace Umbraco.Web.Models { _content = items.ToList(); _contentSet = new PublishedContentSet(_content); - Items = _contentSet.Select(x => new DynamicPublishedContent(x)).ToList(); + Items = _contentSet.Select(x => new DynamicPublishedContent(x, this)).ToList(); } public DynamicPublishedContentList(IEnumerable items) { _content = items.Select(x => x.PublishedContent).ToList(); _contentSet = new PublishedContentSet(_content); - Items = _contentSet.Select(x => new DynamicPublishedContent(x)).ToList(); + Items = _contentSet.Select(x => new DynamicPublishedContent(x, this)).ToList(); + } + + #endregion + + #region ContentSet + + // so we are ~compatible with strongly typed syntax + public DynamicPublishedContentList ToContentSet() + { + return this; } #endregion @@ -59,7 +69,7 @@ namespace Umbraco.Web.Models _contentSet.SourceChanged(); var setContent = _contentSet.MapContent(content); - Items.Add(new DynamicPublishedContent(setContent)); + Items.Add(new DynamicPublishedContent(setContent, this)); } /// diff --git a/src/Umbraco.Web/Models/PublishedContentTypeCaching.cs b/src/Umbraco.Web/Models/PublishedContentTypeCaching.cs index 36624704f5..a9cefa9fc6 100644 --- a/src/Umbraco.Web/Models/PublishedContentTypeCaching.cs +++ b/src/Umbraco.Web/Models/PublishedContentTypeCaching.cs @@ -13,8 +13,7 @@ namespace Umbraco.Web.Models // events from the ContentTypeCacheRefresher - however as of may 1st, 2013 that eventing system is not // fully operational and Shannon prefers that the refresh code is hard-wired into the refresher. so this // is commented out and the refresher calls PublishedContentType.Clear() directly. - // TODO refactor this when the refresher is ready - // FIXME should use the right syntax NOW + // FIXME - must refactor this class to use proper cache refresher class PublishedContentTypeCaching : ApplicationEventHandler { diff --git a/src/Umbraco.Web/PropertyEditors/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/RteMacroRenderingValueConverter.cs index 20cac816ea..ada571e2ef 100644 --- a/src/Umbraco.Web/PropertyEditors/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/RteMacroRenderingValueConverter.cs @@ -6,6 +6,7 @@ using Umbraco.Core; using Umbraco.Core.Macros; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Templates; namespace Umbraco.Web.PropertyEditors { @@ -22,10 +23,15 @@ namespace Umbraco.Web.PropertyEditors [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Request)] internal class RteMacroRenderingValueConverter : TinyMceValueConverter { - string RenderRteMacros(string source) + // NOT thread-safe over a request because it modifies the + // global UmbracoContext.Current.InPreviewMode status. So it + // should never execute in // over the same UmbracoContext with + // different preview modes. + static string RenderRteMacros(string source, bool preview) { - // fixme - not taking 'preview' into account here - // but we should, when running the macros... how?! + // save and set for macro rendering + var inPreviewMode = UmbracoContext.Current.InPreviewMode; + UmbracoContext.Current.InPreviewMode = preview; var sb = new StringBuilder(); var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); @@ -37,7 +43,11 @@ namespace Umbraco.Web.PropertyEditors (macroAlias, macroAttributes) => sb.Append(umbracoHelper.RenderMacro( macroAlias, //needs to be explicitly casted to Dictionary - macroAttributes.ConvertTo(x => (string)x, x => (object)x)).ToString())); + macroAttributes.ConvertTo(x => (string)x, x => x)).ToString())); + + // restore + UmbracoContext.Current.InPreviewMode = inPreviewMode; + return sb.ToString(); } @@ -46,8 +56,11 @@ namespace Umbraco.Web.PropertyEditors if (source == null) return null; var sourceString = source.ToString(); - sourceString = TextValueConverterHelper.ParseStringValueSource(sourceString); // fixme - must handle preview - sourceString = RenderRteMacros(sourceString); // fixme - must handle preview + // ensures string is parsed for {localLink} and urls are resolved correctly + sourceString = TemplateUtilities.ParseInternalLinks(sourceString, preview); + sourceString = TemplateUtilities.ResolveUrlsFromTextString(sourceString); + // ensure string is parsed for macros and macros are executed correctly + sourceString = RenderRteMacros(sourceString, preview); return sourceString; } diff --git a/src/Umbraco.Web/PropertyEditors/TextValueConverterHelper.cs b/src/Umbraco.Web/PropertyEditors/TextValueConverterHelper.cs deleted file mode 100644 index 63843d6d0e..0000000000 --- a/src/Umbraco.Web/PropertyEditors/TextValueConverterHelper.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Web.Templates; - -namespace Umbraco.Web.PropertyEditors -{ - class TextValueConverterHelper - { - // ensures string value sources are parsed for {localLink} and urls are resolved correctly - // fixme - but then that one should get "previewing" too? - public static string ParseStringValueSource(string stringValueSource) - { - return TemplateUtilities.ResolveUrlsFromTextString(TemplateUtilities.ParseInternalLinks(stringValueSource)); - } - } -} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs index 1775d37fe0..9c8eed322b 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache global::umbraco.content.AfterUpdateDocumentCache += (sender, e) => Clear(); global::umbraco.content.AfterClearDocumentCache += (sender, e) => Clear(); - // fixme - refactor when content events are refactored + // fixme - should refactor once content events are refactored // the content class needs to be refactored - at the moment // content.XmlContentInternal setter does not trigger any event // content.UpdateDocumentCache(List Documents) does not trigger any event diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 87c809532d..7623f6ef13 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1,4 +1,4 @@ -// fixme - should #define +// fixme - should #define - but when will it be OK? // axes navigation is broken in many ways... but fixes would not be 100% // backward compatible... so keep them for v7 or whenever appropriate. #undef FIX_AXES @@ -467,7 +467,7 @@ namespace Umbraco.Web #region Dynamic Linq Extensions - // TODO cleanup... do we really want dynamics here? + // todo - we should keep this file clean and remove dynamic linq stuff from it public static IQueryable OrderBy(this IEnumerable source, string predicate) { @@ -477,13 +477,13 @@ namespace Umbraco.Web public static IQueryable Where(this IEnumerable list, string predicate) { - // fixme - but wait... ?! - var dList = new DynamicPublishedContentList(list); - //we have to wrap the result in another DynamicPublishedContentList so that the OwnersList get's set on - //the individual items. See: http://issues.umbraco.org/issue/U4-1797 - return new DynamicPublishedContentList( - dList.Where(predicate)) - .AsQueryable(); + // wrap in DynamicPublishedContentList so that the ContentSet is correct + // though that code is somewhat ugly. + + var dlist = new DynamicPublishedContentList(new DynamicPublishedContentList(list) + .Where(predicate)); + + return dlist.AsQueryable(); } public static IEnumerable> GroupBy(this IEnumerable list, string predicate) diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index e25e75eb4a..9972430f21 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -15,12 +15,26 @@ namespace Umbraco.Web.Templates /// public static class TemplateUtilities { - /// - /// Parses the string looking for the {localLink} syntax and updates them to their correct links. - /// - /// - /// - public static string ParseInternalLinks(string text) + internal static string ParseInternalLinks(string text, bool preview) + { + // save and set for url provider + var inPreviewMode = UmbracoContext.Current.InPreviewMode; + UmbracoContext.Current.InPreviewMode = preview; + + text = ParseInternalLinks(text); + + // restore + UmbracoContext.Current.InPreviewMode = inPreviewMode; + + return text; + } + + /// + /// Parses the string looking for the {localLink} syntax and updates them to their correct links. + /// + /// + /// + public static string ParseInternalLinks(string text) { //don't attempt to proceed without a context as we cannot lookup urls without one if (UmbracoContext.Current == null || UmbracoContext.Current.RoutingContext == null) @@ -31,57 +45,59 @@ namespace Umbraco.Web.Templates var urlProvider = UmbracoContext.Current.UrlProvider; // Parse internal links - MatchCollection tags = Regex.Matches(text, @"href=""[/]?(?:\{|\%7B)localLink:([0-9]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + var tags = Regex.Matches(text, @"href=""[/]?(?:\{|\%7B)localLink:([0-9]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); foreach (Match tag in tags) if (tag.Groups.Count > 0) { - string id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); - string newLink = urlProvider.GetUrl(int.Parse(id)); - text = text.Replace(tag.Value.ToString(), "href=\"" + newLink); + var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); + var newLink = urlProvider.GetUrl(int.Parse(id)); + text = text.Replace(tag.Value, "href=\"" + newLink); } - return text; + + return text; } // static compiled regex for faster performance private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - /// - /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. - /// - /// - /// - /// - /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly. - /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs. - /// - public static string ResolveUrlsFromTextString(string text) - { - if (UmbracoSettings.ResolveUrlsFromTextString) - { - using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) - { - // find all relative urls (ie. urls that contain ~) - var tags = ResolveUrlPattern.Matches(text); - LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count); - foreach (Match tag in tags) - { - string url = ""; - if (tag.Groups[1].Success) - url = tag.Groups[1].Value; - // The richtext editor inserts a slash in front of the url. That's why we need this little fix - // if (url.StartsWith("/")) - // text = text.Replace(url, ResolveUrl(url.Substring(1))); - // else - if (!String.IsNullOrEmpty(url)) - { - string resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url); - text = text.Replace(url, resolvedUrl); - } - } - } - } - return text; + /// + /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. + /// + /// + /// + /// + /// + /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly. + /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs. + /// + public static string ResolveUrlsFromTextString(string text) + { + if (UmbracoSettings.ResolveUrlsFromTextString == false) return text; + + using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) + { + // find all relative urls (ie. urls that contain ~) + var tags = ResolveUrlPattern.Matches(text); + LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count); + foreach (Match tag in tags) + { + var url = ""; + if (tag.Groups[1].Success) + url = tag.Groups[1].Value; + + // The richtext editor inserts a slash in front of the url. That's why we need this little fix + // if (url.StartsWith("/")) + // text = text.Replace(url, ResolveUrl(url.Substring(1))); + // else + if (String.IsNullOrEmpty(url) == false) + { + var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url); + text = text.Replace(url, resolvedUrl); + } + } + } + + return text; } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2687d752a6..e9ea0b16dc 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -294,7 +294,6 @@ - @@ -2091,4 +2090,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 6e45375c33..1900a17f97 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -334,7 +334,8 @@ 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; private set; } + /// Can be internally set by the RTE macro rendering to render macros in the appropriate mode. + public bool InPreviewMode { get; internal set; } private bool DetectInPreviewModeFromRequest() {