Merge with 4.11.0

This commit is contained in:
Morten Christensen
2012-11-13 14:33:28 -01:00
34 changed files with 1147 additions and 381 deletions

View File

@@ -10,8 +10,5 @@ d03fcffb8834a9583a56813bb44b6abbd9f042cc Release-4.6.0
096f20bb0575d6199f20fcb3147b63554e765a74 Release-4.8.0
8f8a203857886b373148af29edd57460a42940be Release-4.8.1
de73e687ddf6086ed52b6554676c1632865d07f2 Release-4.9.0
de73e687ddf6086ed52b6554676c1632865d07f2 Release-4.9.0
0000000000000000000000000000000000000000 Release-4.9.0
0000000000000000000000000000000000000000 Release-4.9.0
09f5a69cf19ebf7ddd9501e5258bee1e6a125391 Release-4.9.0
8d7d8609e2e4b971da99cd97f72132ce85ce3333 Release-4.9.1
f6da531fbb4c251ff61d314e2a7effb13c71e74a Release-4.10.0

View File

@@ -23,7 +23,7 @@ namespace Umbraco.Core
{
var str = (string)value;
if (str == "1") return true;
if (str == "0") return false;
if (str == "0" || str == "") return false;
}
return base.ConvertFrom(context, culture, value);

View File

@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
namespace Umbraco.Core
{
/// <summary>
/// Used to create a hash code from multiple objects.
/// </summary>
/// <remarks>
/// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things
/// and is probably more stable in general, however we just need a quick easy class for this in order to create a unique
/// hash of plugins to see if they've changed.
/// </remarks>
internal class HashCodeCombiner
{
private int _combinedHash;
internal void AddInt(int i)
{
_combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i;
}
internal void AddObject(object o)
{
AddInt(o.GetHashCode());
}
internal void AddDateTime(DateTime d)
{
AddInt(d.GetHashCode());
}
internal void AddCaseInsensitiveString(string s)
{
if (s != null)
AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s));
}
internal void AddFile(FileInfo f)
{
AddCaseInsensitiveString(f.FullName);
AddDateTime(f.CreationTimeUtc);
AddDateTime(f.LastWriteTimeUtc);
AddInt(f.Length.GetHashCode());
}
internal void AddFolder(DirectoryInfo d)
{
AddCaseInsensitiveString(d.FullName);
AddDateTime(d.CreationTimeUtc);
AddDateTime(d.LastWriteTimeUtc);
foreach (var f in d.GetFiles())
{
AddFile(f);
}
foreach (var s in d.GetDirectories())
{
AddFolder(s);
}
}
/// <summary>
/// Returns the hex code of the combined hash code
/// </summary>
/// <returns></returns>
internal string GetCombinedHashCode()
{
return _combinedHash.ToString("x", CultureInfo.InvariantCulture);
}
}
}

View File

@@ -12,6 +12,7 @@ namespace Umbraco.Core.IO
//all paths has a starting but no trailing /
public class SystemDirectories
{
//TODO: Why on earth is this even configurable? You cannot change the /Bin folder in ASP.Net
public static string Bin
{
get

View File

@@ -1,10 +1,18 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web.Caching;
using System.Web.Compilation;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
@@ -22,16 +30,67 @@ namespace Umbraco.Core
///
/// This class can expose extension methods to resolve custom plugins
///
/// Before this class resolves any plugins it checks if the hash has changed for the DLLs in the /bin folder, if it hasn't
/// it will use the cached resolved plugins that it has already found which means that no assembly scanning is necessary. This leads
/// to much faster startup times.
/// </remarks>
internal class PluginManager
{
private readonly ApplicationContext _appContext;
internal PluginManager()
/// <summary>
/// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml
/// file is cached temporarily until app startup completes.
/// </summary>
/// <param name="appContext"></param>
/// <param name="detectBinChanges"></param>
internal PluginManager(ApplicationContext appContext, bool detectBinChanges = true)
: this(detectBinChanges)
{
if (appContext == null) throw new ArgumentNullException("appContext");
_appContext = appContext;
}
/// <summary>
/// Creates a new PluginManager
/// </summary>
/// <param name="detectBinChanges">
/// If true will detect changes in the /bin folder and therefor load plugins from the
/// cached plugins file if one is found. If false will never use the cache file for plugins
/// </param>
internal PluginManager(bool detectBinChanges = true)
{
_tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
//create the folder if it doesn't exist
if (!Directory.Exists(_tempFolder))
{
Directory.CreateDirectory(_tempFolder);
}
if (detectBinChanges)
{
//first check if the cached hash is 0, if it is then we ne
//do the check if they've changed
HaveAssembliesChanged = (CachedAssembliesHash != CurrentAssembliesHash) || CachedAssembliesHash == 0;
//if they have changed, we need to write the new file
if (HaveAssembliesChanged)
{
WriteCachePluginsHash();
}
}
else
{
//always set to true if we're not detecting (generally only for testing)
HaveAssembliesChanged = true;
}
}
static PluginManager _resolver;
static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
private readonly string _tempFolder;
private long _cachedAssembliesHash = -1;
private long _currentAssembliesHash = -1;
/// <summary>
/// We will ensure that no matter what, only one of these is created, this is to ensure that caching always takes place
@@ -48,7 +107,9 @@ namespace Umbraco.Core
if (_resolver == null)
{
l.UpgradeToWriteLock();
_resolver = new PluginManager();
_resolver = ApplicationContext.Current == null
? new PluginManager()
: new PluginManager(ApplicationContext.Current);
}
return _resolver;
}
@@ -56,6 +117,215 @@ namespace Umbraco.Core
set { _resolver = value; }
}
#region Hash checking methods
/// <summary>
/// Returns a bool if the assemblies in the /bin have changed since they were last hashed.
/// </summary>
internal bool HaveAssembliesChanged { get; private set; }
/// <summary>
/// Returns the currently cached hash value of the scanned assemblies in the /bin folder. Returns 0
/// if no cache is found.
/// </summary>
/// <value> </value>
internal long CachedAssembliesHash
{
get
{
if (_cachedAssembliesHash != -1)
return _cachedAssembliesHash;
var filePath = Path.Combine(_tempFolder, "umbraco-plugins.hash");
if (!File.Exists(filePath))
return 0;
var hash = File.ReadAllText(filePath, Encoding.UTF8);
Int64 val;
if (Int64.TryParse(hash, out val))
{
_cachedAssembliesHash = val;
return _cachedAssembliesHash;
}
//it could not parse for some reason so we'll return 0.
return 0;
}
}
/// <summary>
/// Returns the current assemblies hash based on creating a hash from the assemblies in the /bin
/// </summary>
/// <value> </value>
internal long CurrentAssembliesHash
{
get
{
if (_currentAssembliesHash != -1)
return _currentAssembliesHash;
_currentAssembliesHash = GetAssembliesHash(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)).GetFiles("*.dll"));
return _currentAssembliesHash;
}
}
/// <summary>
/// Writes the assembly hash file
/// </summary>
private void WriteCachePluginsHash()
{
var filePath = Path.Combine(_tempFolder, "umbraco-plugins.hash");
File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8);
}
/// <summary>
/// Returns a unique hash for the combination of FileInfo objects passed in
/// </summary>
/// <param name="plugins"></param>
/// <returns></returns>
internal static long GetAssembliesHash(IEnumerable<FileInfo> plugins)
{
using (DisposableTimer.TraceDuration<PluginManager>("Determining hash of plugins on disk", "Hash determined"))
{
var hashCombiner = new HashCodeCombiner();
//add each unique folder to the hash
foreach (var i in plugins.Select(x => x.Directory).DistinctBy(x => x.FullName))
{
hashCombiner.AddFolder(i);
}
return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode());
}
}
/// <summary>
/// Converts the hash value of current plugins to long from string
/// </summary>
/// <param name="val"></param>
/// <returns></returns>
internal static long ConvertPluginsHashFromHex(string val)
{
long outVal;
if (Int64.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal))
{
return outVal;
}
return 0;
}
/// <summary>
/// Attempts to resolve the list of plugin + assemblies found in the runtime for the base type 'T' passed in.
/// If the cache file doesn't exist, fails to load, is corrupt or the type 'T' element is not found then
/// a false attempt is returned.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal Attempt<IEnumerable<string>> TryGetCachedPluginsFromFile<T>()
{
var filePath = Path.Combine(_tempFolder, "umbraco-plugins.list");
if (!File.Exists(filePath))
return Attempt<IEnumerable<string>>.False;
try
{
//we will load the xml document, if the app context exist, we will load it from the cache (which is only around for 5 minutes)
//while the app boots up, this should save some IO time on app startup when the app context is there (which is always unless in unit tests)
XDocument xml;
if (_appContext != null)
{
xml = _appContext.ApplicationCache.GetCacheItem("umbraco-plugins.list",
new TimeSpan(0, 0, 5, 0),
() => XDocument.Load(filePath));
}
else
{
xml = XDocument.Load(filePath);
}
if (xml.Root == null)
return Attempt<IEnumerable<string>>.False;
var typeElement = xml.Root.Elements()
.SingleOrDefault(x =>
x.Name.LocalName == "baseType"
&& ((string) x.Attribute("type")) == typeof (T).FullName);
if (typeElement == null)
return Attempt<IEnumerable<string>>.False;
//return success
return new Attempt<IEnumerable<string>>(
true,
typeElement.Elements("add")
.Select(x => (string) x.Attribute("type")));
}
catch (Exception)
{
//if the file is corrupted, etc... return false
return Attempt<IEnumerable<string>>.False;
}
}
/// <summary>
/// Adds/Updates the type list for the base type 'T' in the cached file
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="typesFound"></param>
/// <remarks>
/// THIS METHOD IS NOT THREAD SAFE
/// </remarks>
/// <example>
/// <![CDATA[
/// <plugins>
/// <baseType type="Test.Testing.Tester">
/// <add type="My.Assembly.MyTester" assembly="My.Assembly" />
/// <add type="Your.Assembly.YourTester" assembly="Your.Assembly" />
/// </baseType>
/// </plugins>
/// ]]>
/// </example>
internal void UpdateCachedPluginsFile<T>(IEnumerable<Type> typesFound)
{
var filePath = Path.Combine(_tempFolder, "umbraco-plugins.list");
XDocument xml;
try
{
xml = XDocument.Load(filePath);
}
catch
{
//if there's an exception loading then this is somehow corrupt, we'll just replace it.
File.Delete(filePath);
//create the document and the root
xml = new XDocument(new XElement("plugins"));
}
if (xml.Root == null)
{
//if for some reason there is no root, create it
xml.Add(new XElement("plugins"));
}
//find the type 'T' element to add or update
var typeElement = xml.Root.Elements()
.SingleOrDefault(x =>
x.Name.LocalName == "baseType"
&& ((string)x.Attribute("type")) == typeof(T).FullName);
if (typeElement == null)
{
//create the type element
typeElement = new XElement("baseType", new XAttribute("type", typeof(T).FullName));
//then add it to the root
xml.Root.Add(typeElement);
}
//now we have the type element, we need to clear any previous types as children and add/update it with new ones
typeElement.ReplaceNodes(typesFound
.Select(x =>
new XElement("add",
new XAttribute("type", x.AssemblyQualifiedName))));
//save the xml file
xml.Save(filePath);
}
#endregion
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly HashSet<TypeList> _types = new HashSet<TypeList>();
private IEnumerable<Assembly> _assemblies;
@@ -219,9 +489,47 @@ namespace Umbraco.Core
typeList = new TypeList<T>(resolutionType);
foreach (var t in finder())
//we first need to look into our cache file (this has nothing to do with the 'cacheResult' parameter which caches in memory).
if (!HaveAssembliesChanged)
{
typeList.AddType(t);
var fileCacheResult = TryGetCachedPluginsFromFile<T>();
if (fileCacheResult.Success)
{
var successfullyLoadedFromCache = true;
//we have a previous cache for this so we don't need to scan we just load what has been found in the file
foreach(var t in fileCacheResult.Result)
{
try
{
//we use the build manager to ensure we get all types loaded, this is slightly slower than
//Type.GetType but if the types in the assembly aren't loaded yet then we have problems with that.
var type = BuildManager.GetType(t, true);
typeList.AddType(type);
}
catch (Exception ex)
{
//if there are any exceptions loading types, we have to exist, this should never happen so
//we will need to revert to scanning for types.
successfullyLoadedFromCache = false;
LogHelper.Error<PluginManager>("Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof (T).FullName, ex);
break;
}
}
if (!successfullyLoadedFromCache )
{
//we need to manually load by scanning if loading from the file was not successful.
LoadViaScanningAndUpdateCacheFile<T>(typeList, finder);
}
else
{
LogHelper.Debug<PluginManager>("Loaded plugin types " + typeof(T).FullName + " from persisted cache");
}
}
}
else
{
//we don't have a cache for this so proceed to look them up by scanning
LoadViaScanningAndUpdateCacheFile<T>(typeList, finder);
}
//only add the cache if we are to cache the results
@@ -236,6 +544,25 @@ namespace Umbraco.Core
}
}
/// <summary>
/// This method invokes the finder which scans the assemblies for the types and then loads the result into the type finder.
/// Once the results are loaded, we update the cached type xml file
/// </summary>
/// <param name="typeList"></param>
/// <param name="finder"></param>
/// <remarks>
/// THIS METHODS IS NOT THREAD SAFE
/// </remarks>
private void LoadViaScanningAndUpdateCacheFile<T>(TypeList typeList, Func<IEnumerable<Type>> finder)
{
//we don't have a cache for this so proceed to look them up by scanning
foreach (var t in finder())
{
typeList.AddType(t);
}
UpdateCachedPluginsFile<T>(typeList.GetTypes());
}
/// <summary>
/// Generic method to find the specified type and cache the result
/// </summary>

View File

@@ -21,16 +21,7 @@ namespace Umbraco.Core.PropertyEditors
/// <returns></returns>
public Attempt<object> ConvertPropertyValue(object value)
{
if (value == null) return new Attempt<object>(false, null);
//if its already a DateTime
if (TypeHelper.IsTypeAssignableFrom<DateTime>(value))
return new Attempt<object>(true, value);
//convert to string
var asString = Convert.ToString(value);
DateTime dt;
return DateTime.TryParse(asString, out dt)
? new Attempt<object>(true, dt)
: new Attempt<object>(false, null);
return value.TryConvertTo(typeof(DateTime));
}
}
}

View File

@@ -16,18 +16,7 @@ namespace Umbraco.Core.PropertyEditors
/// <returns></returns>
public Attempt<object> ConvertPropertyValue(object value)
{
if (value == null) return new Attempt<object>(false, null);
//if its already a bool
if (TypeHelper.IsTypeAssignableFrom<bool>(value))
return new Attempt<object>(true, value);
//convert to string
var asString = Convert.ToString(value);
bool asBool;
return bool.TryParse(asString, out asBool)
? new Attempt<object>(true, asBool)
: bool.TryParse(asString.Replace("1", "true").Replace("0", "false"), out asBool) //convert 0 or 1 to true or false
? new Attempt<object>(true, asBool)
: new Attempt<object>(false, null);
return value.TryConvertTo(typeof(bool));
}
}
}

View File

@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net.Mime;
using System.Web;
using Umbraco.Core.Dynamics;
using Umbraco.Core.Models;
using umbraco.interfaces;
@@ -16,6 +15,7 @@ namespace Umbraco.Core
{
#region GetProperty
public static IPublishedContentProperty GetProperty(this IPublishedContent content, string alias, bool recursive)
{
@@ -41,24 +41,33 @@ namespace Umbraco.Core
#endregion
#region GetPropertyValue
public static string GetPropertyValue(this IPublishedContent doc, string alias)
public static object GetPropertyValue(this IPublishedContent doc, string alias)
{
return doc.GetPropertyValue(alias, false);
}
public static string GetPropertyValue(this IPublishedContent doc, string alias, string fallback)
public static object GetPropertyValue(this IPublishedContent doc, string alias, string fallback)
{
var prop = doc.GetPropertyValue(alias);
return !prop.IsNullOrWhiteSpace() ? prop : fallback;
return (prop != null && !Convert.ToString(prop).IsNullOrWhiteSpace()) ? prop : fallback;
}
public static string GetPropertyValue(this IPublishedContent doc, string alias, bool recursive)
public static object GetPropertyValue(this IPublishedContent doc, string alias, bool recursive)
{
var p = doc.GetProperty(alias, recursive);
return p == null ? null : Convert.ToString(p.Value);
if (p == null) return null;
//Here we need to put the value through the IPropertyEditorValueConverter's
//get the data type id for the current property
var dataType = PublishedContentHelper.GetDataType(doc.DocumentTypeAlias, alias);
//convert the string value to a known type
var converted = PublishedContentHelper.ConvertPropertyValue(p.Value, dataType, doc.DocumentTypeAlias, alias);
return converted.Success
? converted.Result
: p.Value;
}
public static string GetPropertyValue(this IPublishedContent doc, string alias, bool recursive, string fallback)
public static object GetPropertyValue(this IPublishedContent doc, string alias, bool recursive, string fallback)
{
var prop = doc.GetPropertyValue(alias, recursive);
return !prop.IsNullOrWhiteSpace() ? prop : fallback;
return (prop != null && !Convert.ToString(prop).IsNullOrWhiteSpace()) ? prop : fallback;
}
#endregion
@@ -125,23 +134,51 @@ namespace Umbraco.Core
/// then the default value of type T is returned.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="prop"></param>
/// <param name="doc"></param>
/// <param name="alias"></param>
/// <returns></returns>
public static T GetPropertyValue<T>(this IPublishedContent prop, string alias)
public static T GetPropertyValue<T>(this IPublishedContent doc, string alias)
{
return prop.GetPropertyValue<T>(alias, default(T));
return doc.GetPropertyValue<T>(alias, default(T));
}
public static T GetPropertyValue<T>(this IPublishedContent prop, string alias, bool recursive, T ifCannotConvert)
{
var p = prop.GetProperty(alias, recursive);
if (p == null)
return ifCannotConvert;
//before we try to convert it manually, lets see if the PropertyEditorValueConverter does this for us
//Here we need to put the value through the IPropertyEditorValueConverter's
//get the data type id for the current property
var dataType = PublishedContentHelper.GetDataType(prop.DocumentTypeAlias, alias);
//convert the value to a known type
var converted = PublishedContentHelper.ConvertPropertyValue(p.Value, dataType, prop.DocumentTypeAlias, alias);
if (converted.Success)
{
//if its successful, check if its the correct type and return it
if (converted.Result is T)
{
return (T)converted.Result;
}
//if that's not correct, try converting the converted type
var reConverted = converted.Result.TryConvertTo<T>();
if (reConverted.Success)
{
return reConverted.Result;
}
}
//last, if all the above has failed, we'll just try converting the raw value straight to 'T'
var manualConverted = p.Value.TryConvertTo<T>();
if (manualConverted.Success)
return manualConverted.Result;
return ifCannotConvert;
}
public static T GetPropertyValue<T>(this IPublishedContent prop, string alias, T ifCannotConvert)
{
var p = prop.GetProperty(alias);
if (p == null)
return default(T);
var converted = p.Value.TryConvertTo<T>();
if (converted.Success)
return converted.Result;
return ifCannotConvert;
return prop.GetPropertyValue<T>(alias, false, ifCannotConvert);
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dynamics;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core
{
internal class PublishedContentHelper
{
/// <summary>
/// This callback is used only so we can set it dynamically because in the "Core" project currently we don't have
/// access to the business logic layer.
/// TODO: Once 6.0 is released we need to change this to use the new business logic layer that we can access from
/// this proejct. Until then this will return a Guid.Empty but the callback will need to be set in the WebBootManager
/// to work in the website. if people use this in a non-web aspect without the WebBootManager, the the IPropertyEditorValueConverters
/// will not be executed.
/// </summary>
internal static Func<string, string, Guid> GetDataTypeCallback = (docTypeAlias, propertyAlias) => Guid.Empty;
internal static Guid GetDataType(string docTypeAlias, string propertyAlias)
{
return GetDataTypeCallback(docTypeAlias, propertyAlias);
}
/// <summary>
/// Converts the currentValue to a correctly typed value based on known registered converters, then based on known standards.
/// </summary>
/// <param name="currentValue"></param>
/// <param name="dataType"></param>
/// <param name="docTypeAlias"></param>
/// <param name="propertyTypeAlias"></param>
/// <returns></returns>
internal static Attempt<object> ConvertPropertyValue(object currentValue, Guid dataType, string docTypeAlias, string propertyTypeAlias)
{
if (currentValue == null) return Attempt<object>.False;
//First lets check all registered converters for this data type.
var converters = PropertyEditorValueConvertersResolver.Current.Converters
.Where(x => x.IsConverterFor(dataType, docTypeAlias, propertyTypeAlias))
.ToArray();
//try to convert the value with any of the converters:
foreach (var converted in converters
.Select(p => p.ConvertPropertyValue(currentValue))
.Where(converted => converted.Success))
{
return new Attempt<object>(true, converted.Result);
}
//if none of the converters worked, then we'll process this from what we know
var sResult = Convert.ToString(currentValue).Trim();
//this will eat csv strings, so only do it if the decimal also includes a decimal seperator (according to the current culture)
if (sResult.Contains(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator))
{
decimal dResult;
if (decimal.TryParse(sResult, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.CurrentCulture, out dResult))
{
return new Attempt<object>(true, dResult);
}
}
//process string booleans as booleans
if (sResult.InvariantEquals("true"))
{
return new Attempt<object>(true, true);
}
if (sResult.InvariantEquals("false"))
{
return new Attempt<object>(true, false);
}
//a really rough check to see if this may be valid xml
//TODO: This is legacy code, I'm sure there's a better and nicer way
if (sResult.StartsWith("<") && sResult.EndsWith(">") && sResult.Contains("/"))
{
try
{
var e = XElement.Parse(DynamicXml.StripDashesInElementOrAttributeNames(sResult), LoadOptions.None);
//check that the document element is not one of the disallowed elements
//allows RTE to still return as html if it's valid xhtml
var documentElement = e.Name.LocalName;
//TODO: See note against this setting, pretty sure we don't need this
if (!UmbracoSettings.NotDynamicXmlDocumentElements.Any(
tag => string.Equals(tag, documentElement, StringComparison.CurrentCultureIgnoreCase)))
{
return new Attempt<object>(true, new DynamicXml(e));
}
return Attempt<object>.False;
}
catch (Exception)
{
return Attempt<object>.False;
}
}
return Attempt<object>.False;
}
}
}

View File

@@ -84,6 +84,7 @@
<Compile Include="Models\ContentBase.cs" />
<Compile Include="Models\ContentExtensions.cs" />
<Compile Include="Enum.cs" />
<Compile Include="HashCodeCombiner.cs" />
<Compile Include="IO\FileSystemWrapper.cs" />
<Compile Include="Models\ContentTypeBase.cs" />
<Compile Include="Models\ContentTypeCompositionBase.cs" />
@@ -399,6 +400,7 @@
<Compile Include="ObjectResolution\Resolution.cs" />
<Compile Include="ObjectResolution\ResolverBase.cs" />
<Compile Include="ObjectResolution\SingleObjectResolverBase.cs" />
<Compile Include="PublishedContentHelper.cs" />
<Compile Include="Publishing\BasePublishingStrategy.cs" />
<Compile Include="Publishing\IPublishingStrategy.cs" />
<Compile Include="Publishing\PublishingStrategy.cs" />

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
PluginManager.Current = new PluginManager();
PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
PluginManager.Current = new PluginManager();
PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Web;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Dynamics;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
@@ -34,7 +35,7 @@ namespace Umbraco.Tests.DynamicDocument
});
//need to specify a custom callback for unit tests
DynamicPublishedContent.GetDataTypeCallback = (docTypeAlias, propertyAlias) =>
PublishedContentHelper.GetDataTypeCallback = (docTypeAlias, propertyAlias) =>
{
if (propertyAlias == "content")
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Web;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
@@ -72,7 +73,7 @@ namespace Umbraco.Tests.DynamicDocument
});
//need to specify a custom callback for unit tests
DynamicPublishedContent.GetDataTypeCallback = (docTypeAlias, propertyAlias) =>
PublishedContentHelper.GetDataTypeCallback = (docTypeAlias, propertyAlias) =>
{
if (propertyAlias == "content")
{
@@ -105,6 +106,20 @@ namespace Umbraco.Tests.DynamicDocument
return doc;
}
[Test]
public void Get_Property_Value_Uses_Converter()
{
var doc = GetNode(1173);
var propVal = doc.GetPropertyValue("content");
Assert.IsTrue(TypeHelper.IsTypeAssignableFrom<IHtmlString>(propVal.GetType()));
Assert.AreEqual("<div>This is some content</div>", propVal.ToString());
var propVal2 = doc.GetPropertyValue<IHtmlString>("content");
Assert.IsTrue(TypeHelper.IsTypeAssignableFrom<IHtmlString>(propVal2.GetType()));
Assert.AreEqual("<div>This is some content</div>", propVal2.ToString());
}
[Test]
public void Complex_Linq()
{
@@ -113,7 +128,7 @@ namespace Umbraco.Tests.DynamicDocument
var result = doc.Ancestors().OrderBy(x => x.Level)
.Single()
.Descendants()
.FirstOrDefault(x => x.GetPropertyValue("selectedNodes", "").Split(',').Contains("1173"));
.FirstOrDefault(x => x.GetPropertyValue<string>("selectedNodes", "").Split(',').Contains("1173"));
Assert.IsNotNull(result);
}
@@ -255,16 +270,6 @@ namespace Umbraco.Tests.DynamicDocument
Assert.IsTrue(visible.IsVisible());
}
[Test]
public void Ensure_TinyMCE_Converted_Type_User_Property()
{
var doc = GetNode(1173);
throw new NotImplementedException("We currently don't have an extension method to return the formatted value using IPropertyValueConverter! This currently only works in the dynamic implementation");
//Assert.IsTrue(TypeHelper.IsTypeAssignableFrom<IHtmlString>(doc.GetPropertyValue().Content.GetType()));
//Assert.AreEqual("<div>This is some content</div>", doc.Content.ToString());
}
[Test]
public void Ancestor_Or_Self()
@@ -326,8 +331,8 @@ namespace Umbraco.Tests.DynamicDocument
Assert.IsNotNull(result);
Assert.AreEqual(6, result.Count());
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175 }));
Assert.AreEqual(7, result.Count());
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 }));
}
[Test]

View File

@@ -0,0 +1,154 @@
using System;
using System.IO;
using System.Reflection;
using NUnit.Framework;
using Umbraco.Core;
namespace Umbraco.Tests
{
[TestFixture]
public class HashCodeCombinerTests : PartialTrust.AbstractPartialTrustFixture<HashCodeCombinerTests>
{
private DirectoryInfo PrepareFolder()
{
var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", Guid.NewGuid().ToString("N")));
foreach (var f in dir.GetFiles())
{
f.Delete();
}
return dir;
}
[Test]
public void HashCombiner_Test_String()
{
var combiner1 = new HashCodeCombiner();
combiner1.AddCaseInsensitiveString("Hello");
var combiner2 = new HashCodeCombiner();
combiner2.AddCaseInsensitiveString("hello");
Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
combiner2.AddCaseInsensitiveString("world");
Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
}
[Test]
public void HashCombiner_Test_Int()
{
var combiner1 = new HashCodeCombiner();
combiner1.AddInt(1234);
var combiner2 = new HashCodeCombiner();
combiner2.AddInt(1234);
Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
combiner2.AddInt(1);
Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
}
[Test]
public void HashCombiner_Test_DateTime()
{
var dt = DateTime.Now;
var combiner1 = new HashCodeCombiner();
combiner1.AddDateTime(dt);
var combiner2 = new HashCodeCombiner();
combiner2.AddDateTime(dt);
Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
combiner2.AddDateTime(DateTime.Now);
Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
}
[Test]
public void HashCombiner_Test_File()
{
var dir = PrepareFolder();
var file1Path = Path.Combine(dir.FullName, "hastest1.txt");
File.Delete(file1Path);
using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt")))
{
file1.WriteLine("hello");
}
var file2Path = Path.Combine(dir.FullName, "hastest2.txt");
File.Delete(file2Path);
using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt")))
{
//even though files are the same, the dates are different
file2.WriteLine("hello");
}
var combiner1 = new HashCodeCombiner();
combiner1.AddFile(new FileInfo(file1Path));
var combiner2 = new HashCodeCombiner();
combiner2.AddFile(new FileInfo(file1Path));
var combiner3 = new HashCodeCombiner();
combiner3.AddFile(new FileInfo(file2Path));
Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner3.GetCombinedHashCode());
combiner2.AddFile(new FileInfo(file2Path));
Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
}
[Test]
public void HashCombiner_Test_Folder()
{
var dir = PrepareFolder();
var file1Path = Path.Combine(dir.FullName, "hastest1.txt");
File.Delete(file1Path);
using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt")))
{
file1.WriteLine("hello");
}
//first test the whole folder
var combiner1 = new HashCodeCombiner();
combiner1.AddFolder(dir);
var combiner2 = new HashCodeCombiner();
combiner2.AddFolder(dir);
Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
//now add a file to the folder
var file2Path = Path.Combine(dir.FullName, "hastest2.txt");
File.Delete(file2Path);
using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt")))
{
//even though files are the same, the dates are different
file2.WriteLine("hello");
}
var combiner3 = new HashCodeCombiner();
combiner3.AddFolder(dir);
Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner3.GetCombinedHashCode());
}
public override void TestSetup()
{
}
public override void TestTearDown()
{
}
}
}

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
PluginManager.Current = new PluginManager();
PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
PluginManager.Current = new PluginManager();
PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Umbraco.Core;
@@ -67,7 +68,8 @@ namespace Umbraco.Tests
{"FALSE", false},
{"False", false},
{"false", false},
{"0", false}
{"0", false},
{"", false}
};
foreach (var testCase in testCases)
@@ -79,6 +81,34 @@ namespace Umbraco.Tests
}
}
[Test]
[TestOnlyInFullTrust]
public virtual void CanConvertStringToDateTime()
{
var dateTime = new DateTime(2012, 11, 10, 13, 14, 15);
var testCases = new Dictionary<string, bool>
{
{"2012-11-10", true},
{"2012/11/10", true},
{"10/11/2012", true},
{"11/10/2012", false},
{"Sat 10, Nov 2012", true},
{"Saturday 10, Nov 2012", true},
{"Sat 10, November 2012", true},
{"Saturday 10, November 2012", true},
{"2012-11-10 13:14:15", true},
{"", false}
};
foreach (var testCase in testCases)
{
var result = testCase.Key.TryConvertTo<DateTime>();
Assert.IsTrue(result.Success);
Assert.AreEqual(DateTime.Equals(dateTime.Date, result.Result.Date), testCase.Value);
}
}
/// <summary>
/// Run once before each test in derived test fixtures.
/// </summary>

View File

@@ -1,4 +1,10 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.Compilation;
using NUnit.Framework;
using SqlCE4Umbraco;
using Umbraco.Core;
@@ -11,6 +17,7 @@ using umbraco.MacroEngines.Iron;
using umbraco.businesslogic;
using umbraco.cms.businesslogic;
using umbraco.editorControls;
using umbraco.interfaces;
using umbraco.uicontrols;
using umbraco.cms;
@@ -27,7 +34,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
PluginManager.Current = new PluginManager();
PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
//TODO: Should probably update this so it only searches this assembly and add custom types to be found
@@ -54,6 +61,160 @@ namespace Umbraco.Tests
};
}
private DirectoryInfo PrepareFolder()
{
var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "PluginManager", Guid.NewGuid().ToString("N")));
foreach (var f in dir.GetFiles())
{
f.Delete();
}
return dir;
}
//[Test]
//public void Scan_Vs_Load_Benchmark()
//{
// var pluginManager = new PluginManager(false);
// var watch = new Stopwatch();
// watch.Start();
// for (var i = 0; i < 1000; i++)
// {
// var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
// var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
// var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
// var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
// }
// watch.Stop();
// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
// watch.Start();
// for (var i = 0; i < 1000; i++)
// {
// var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
// var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
// var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
// var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
// }
// watch.Stop();
// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
// watch.Reset();
// watch.Start();
// for (var i = 0; i < 1000; i++)
// {
// var refreshers = pluginManager.ResolveTypes<ICacheRefresher>(false);
// }
// watch.Stop();
// Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds);
//}
////NOTE: This test shows that Type.GetType is 100% faster than Assembly.Load(..).GetType(...) so we'll use that :)
//[Test]
//public void Load_Type_Benchmark()
//{
// var watch = new Stopwatch();
// watch.Start();
// for (var i = 0; i < 1000; i++)
// {
// var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
// var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
// var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
// var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
// }
// watch.Stop();
// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
// watch.Reset();
// watch.Start();
// for (var i = 0; i < 1000; i++)
// {
// var type2 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
// .GetType("umbraco.macroCacheRefresh");
// var type3 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
// .GetType("umbraco.templateCacheRefresh");
// var type4 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
// .GetType("umbraco.presentation.cache.MediaLibraryRefreshers");
// var type5 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
// .GetType("umbraco.presentation.cache.pageRefresher");
// }
// watch.Stop();
// Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds);
// watch.Reset();
// watch.Start();
// for (var i = 0; i < 1000; i++)
// {
// var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
// var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
// var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
// var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
// }
// watch.Stop();
// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
//}
[Test]
public void Create_Cached_Plugin_File()
{
var types = new[] {typeof (PluginManager), typeof (PluginManagerTests), typeof (UmbracoContext)};
var manager = new PluginManager(false);
//yes this is silly, none of these types inherit from string, but this is just to test the xml file format
manager.UpdateCachedPluginsFile<string>(types);
var plugins = manager.TryGetCachedPluginsFromFile<string>();
Assert.IsTrue(plugins.Success);
Assert.AreEqual(3, plugins.Result.Count());
var shouldContain = types.Select(x => x.AssemblyQualifiedName);
//ensure they are all found
Assert.IsTrue(plugins.Result.ContainsAll(shouldContain));
}
[Test]
public void PluginHash_From_String()
{
var s = "hello my name is someone".GetHashCode().ToString("x", CultureInfo.InvariantCulture);
var output = PluginManager.ConvertPluginsHashFromHex(s);
Assert.AreNotEqual(0, output);
}
[Test]
public void Get_Plugins_Hash()
{
//Arrange
var dir = PrepareFolder();
var d1 = dir.CreateSubdirectory("1");
var d2 = dir.CreateSubdirectory("2");
var d3 = dir.CreateSubdirectory("3");
var d4 = dir.CreateSubdirectory("4");
var f1 = new FileInfo(Path.Combine(d1.FullName, "test1.dll"));
var f2 = new FileInfo(Path.Combine(d1.FullName, "test2.dll"));
var f3 = new FileInfo(Path.Combine(d2.FullName, "test1.dll"));
var f4 = new FileInfo(Path.Combine(d2.FullName, "test2.dll"));
var f5 = new FileInfo(Path.Combine(d3.FullName, "test1.dll"));
var f6 = new FileInfo(Path.Combine(d3.FullName, "test2.dll"));
var f7 = new FileInfo(Path.Combine(d4.FullName, "test1.dll"));
f1.CreateText().Close();
f2.CreateText().Close();
f3.CreateText().Close();
f4.CreateText().Close();
f5.CreateText().Close();
f6.CreateText().Close();
f7.CreateText().Close();
var list1 = new[] { f1, f2, f3, f4, f5, f6 };
var list2 = new[] { f1, f3, f5 };
var list3 = new[] { f1, f3, f5, f7 };
//Act
var hash1 = PluginManager.GetAssembliesHash(list1);
var hash2 = PluginManager.GetAssembliesHash(list2);
var hash3 = PluginManager.GetAssembliesHash(list3);
//Assert
//both should be the same since we only create the hash based on the unique folder of the list passed in, yet
//all files will exist in those folders still
Assert.AreEqual(hash1, hash2);
Assert.AreNotEqual(hash1, hash3);
}
[Test]
public void Ensure_Only_One_Type_List_Created()
{

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.PropertyEditors
{
[TestFixture]
public class PropertyEditorValueConverterTests
{
[Test]
public void CanConvertDatePickerPropertyEditor()
{
var converter = new DatePickerPropertyEditorValueConverter();
var dateTime = new DateTime(2012, 11, 10, 13, 14, 15);
var testCases = new Dictionary<string, bool>
{
{"2012-11-10", true},
{"2012/11/10", true},
{"10/11/2012", true},
{"11/10/2012", false},
{"Sat 10, Nov 2012", true},
{"Saturday 10, Nov 2012", true},
{"Sat 10, November 2012", true},
{"Saturday 10, November 2012", true},
{"2012-11-10 13:14:15", true},
{"", false}
};
foreach (var testCase in testCases)
{
var result = converter.ConvertPropertyValue(testCase.Key);
Assert.IsTrue(result.Success);
Assert.AreEqual(DateTime.Equals(dateTime.Date, ((DateTime)result.Result).Date), testCase.Value);
}
}
[Test]
public void CanConvertYesNoPropertyEditor()
{
var converter = new YesNoPropertyEditorValueConverter();
var testCases = new Dictionary<string, bool>
{
{"TRUE", true},
{"True", true},
{"true", true},
{"1", true},
{"FALSE", false},
{"False", false},
{"false", false},
{"0", false},
{"", false}
};
foreach (var testCase in testCases)
{
var result = converter.ConvertPropertyValue(testCase.Key);
Assert.IsTrue(result.Success);
Assert.AreEqual(testCase.Value, result.Result);
}
}
}
}

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
TestHelper.SetupLog4NetForTests();
//this ensures its reset
PluginManager.Current = new PluginManager();
PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
TestHelper.SetupLog4NetForTests();
//this ensures its reset
PluginManager.Current = new PluginManager();
PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
TestHelper.SetupLog4NetForTests();
//this ensures its reset
PluginManager.Current = new PluginManager();
PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]

View File

@@ -92,9 +92,11 @@
<Compile Include="DynamicDocument\PublishedContentDataTableTests.cs" />
<Compile Include="DynamicDocument\PublishedContentTests.cs" />
<Compile Include="DynamicDocument\StronglyTypedQueryTests.cs" />
<Compile Include="HashCodeCombinerTests.cs" />
<Compile Include="HtmlHelperExtensionMethodsTests.cs" />
<Compile Include="IO\IOHelperTest.cs" />
<Compile Include="LibraryTests.cs" />
<Compile Include="PropertyEditors\PropertyEditorValueConverterTests.cs" />
<Compile Include="Models\ContentTests.cs" />
<Compile Include="Models\ContentXmlTest.cs" />
<Compile Include="Models\MacroTests.cs" />
@@ -255,6 +257,7 @@
<Name>Umbraco.Web</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0">
<Visible>False</Visible>

View File

@@ -330,9 +330,9 @@ namespace Umbraco.Web
ValidateAndSetProperty(valueDictionary, val => DocumentTypeAlias = val, "nodeTypeAlias", "__NodeTypeAlias");
ValidateAndSetProperty(valueDictionary, val => DocumentTypeId = int.Parse(val), "nodeType");
ValidateAndSetProperty(valueDictionary, val => WriterName = val, "writerName");
ValidateAndSetProperty(valueDictionary, val => CreatorName = val, "creatorName");
ValidateAndSetProperty(valueDictionary, val => CreatorName = val, "creatorName", "writerName"); //this is a bit of a hack fix for: U4-1132
ValidateAndSetProperty(valueDictionary, val => WriterId = int.Parse(val), "writerID");
ValidateAndSetProperty(valueDictionary, val => CreatorId = int.Parse(val), "creatorID");
ValidateAndSetProperty(valueDictionary, val => CreatorId = int.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132
ValidateAndSetProperty(valueDictionary, val => Path = val, "path", "__Path");
ValidateAndSetProperty(valueDictionary, val => CreateDate = DateTime.Parse(val), "createDate");
ValidateAndSetProperty(valueDictionary, val => UpdateDate = DateTime.Parse(val), "updateDate");

View File

@@ -22,12 +22,6 @@ namespace Umbraco.Web.Models
/// </summary>
public class DynamicPublishedContent : DynamicObject, IPublishedContent
{
/// <summary>
/// This callback is used only so we can set it dynamically for use in unit tests
/// </summary>
internal static Func<string, string, Guid> GetDataTypeCallback = (docTypeAlias, propertyAlias) =>
ContentType.GetDataType(docTypeAlias, propertyAlias);
protected IPublishedContent PublishedContent { get; private set; }
private DynamicPublishedContentList _cachedChildren;
private readonly ConcurrentDictionary<string, object> _cachedMemberOutput = new ConcurrentDictionary<string, object>();
@@ -263,10 +257,10 @@ namespace Umbraco.Web.Models
}
//get the data type id for the current property
var dataType = GetDataType(userProperty.DocumentTypeAlias, userProperty.Alias);
var dataType = Umbraco.Core.PublishedContentHelper.GetDataType(userProperty.DocumentTypeAlias, userProperty.Alias);
//convert the string value to a known type
var converted = ConvertPropertyValue(result, dataType, userProperty.DocumentTypeAlias, userProperty.Alias);
var converted = Umbraco.Core.PublishedContentHelper.ConvertPropertyValue(result, dataType, userProperty.DocumentTypeAlias, userProperty.Alias);
if (converted.Success)
{
result = converted.Result;
@@ -824,86 +818,6 @@ namespace Umbraco.Web.Models
}
#endregion
private static Guid GetDataType(string docTypeAlias, string propertyAlias)
{
return GetDataTypeCallback(docTypeAlias, propertyAlias);
}
/// <summary>
/// Converts the currentValue to a correctly typed value based on known registered converters, then based on known standards.
/// </summary>
/// <param name="currentValue"></param>
/// <param name="dataType"></param>
/// <param name="docTypeAlias"></param>
/// <param name="propertyTypeAlias"></param>
/// <returns></returns>
private Attempt<object> ConvertPropertyValue(object currentValue, Guid dataType, string docTypeAlias, string propertyTypeAlias)
{
if (currentValue == null) return Attempt<object>.False;
//First lets check all registered converters for this data type.
var converters = PropertyEditorValueConvertersResolver.Current.Converters
.Where(x => x.IsConverterFor(dataType, docTypeAlias, propertyTypeAlias))
.ToArray();
//try to convert the value with any of the converters:
foreach (var converted in converters
.Select(p => p.ConvertPropertyValue(currentValue))
.Where(converted => converted.Success))
{
return new Attempt<object>(true, converted.Result);
}
//if none of the converters worked, then we'll process this from what we know
var sResult = Convert.ToString(currentValue).Trim();
//this will eat csv strings, so only do it if the decimal also includes a decimal seperator (according to the current culture)
if (sResult.Contains(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator))
{
decimal dResult;
if (decimal.TryParse(sResult, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.CurrentCulture, out dResult))
{
return new Attempt<object>(true, dResult);
}
}
//process string booleans as booleans
if (sResult.InvariantEquals("true"))
{
return new Attempt<object>(true, true);
}
if (sResult.InvariantEquals("false"))
{
return new Attempt<object>(true, false);
}
//a really rough check to see if this may be valid xml
//TODO: This is legacy code, I'm sure there's a better and nicer way
if (sResult.StartsWith("<") && sResult.EndsWith(">") && sResult.Contains("/"))
{
try
{
var e = XElement.Parse(DynamicXml.StripDashesInElementOrAttributeNames(sResult), LoadOptions.None);
//check that the document element is not one of the disallowed elements
//allows RTE to still return as html if it's valid xhtml
var documentElement = e.Name.LocalName;
//TODO: See note against this setting, pretty sure we don't need this
if (!UmbracoSettings.NotDynamicXmlDocumentElements.Any(
tag => string.Equals(tag, documentElement, StringComparison.CurrentCultureIgnoreCase)))
{
return new Attempt<object>(true, new DynamicXml(e));
}
return Attempt<object>.False;
}
catch (Exception)
{
return Attempt<object>.False;
}
}
return Attempt<object>.False;
}
#region Index/Position
public int Position()

View File

@@ -312,7 +312,15 @@ namespace Umbraco.Web.Routing
{
LogHelper.Debug<PublishedContentRequest>("{0}Page is protected, check for access", () => tracePrefix);
var user = System.Web.Security.Membership.GetUser();
System.Web.Security.MembershipUser user = null;
try
{
user = System.Web.Security.Membership.GetUser();
}
catch (ArgumentException)
{
LogHelper.Debug<PublishedContentRequest>("{0}Membership.GetUser returned ArgumentException", () => tracePrefix);
}
if (user == null || !Member.IsLoggedOn())
{

View File

@@ -48,6 +48,8 @@ namespace Umbraco.Web
if (httpContext == null) throw new ArgumentNullException("httpContext");
if (applicationContext == null) throw new ArgumentNullException("applicationContext");
ObjectCreated = DateTime.Now;
HttpContext = httpContext;
Application = applicationContext;
RoutesCache = routesCache;
@@ -100,6 +102,13 @@ namespace Umbraco.Web
}
}
/// <summary>
/// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this
/// object is instantiated which in the web site is created during the BeginRequest phase.
/// We can then determine complete rendering time from that.
/// </summary>
internal DateTime ObjectCreated { get; private set; }
/// <summary>
/// Gets the current ApplicationContext
/// </summary>

View File

@@ -260,8 +260,8 @@ namespace Umbraco.Web
};
//this is here to figure out if this request is in the context of a partial
if (_umbracoContext.PublishedContentRequest.PublishedContent.Id != _currentPage.Id)
item.NodeId = _currentPage.Id.ToString();
if (_umbracoContext.PublishedContentRequest.PublishedContent.Id != currentPage.Id)
item.NodeId = currentPage.Id.ToString();
var containerPage = new FormlessPage();

View File

@@ -39,6 +39,9 @@ namespace Umbraco.Web
if (IsClientSideRequest(httpContext.Request.Url))
return;
//write the trace output for diagnostics at the end of the request
httpContext.Trace.Write("UmbracoModule", "Umbraco request begins");
// ok, process
// create the LegacyRequestInitializer
@@ -94,6 +97,8 @@ namespace Umbraco.Web
if (!EnsureUmbracoRoutablePage(umbracoContext, httpContext))
return;
httpContext.Trace.Write("UmbracoModule", "Umbraco request confirmed");
// ok, process
var uri = umbracoContext.OriginalRequestUrl;
@@ -417,7 +422,16 @@ namespace Umbraco.Web
PersistXmlCache(new HttpContextWrapper(httpContext));
};
// todo: initialize request errors handler
app.EndRequest += (sender, args) =>
{
var httpContext = ((HttpApplication)sender).Context;
if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest)
{
//write the trace output for diagnostics at the end of the request
httpContext.Trace.Write("UmbracoModule", "Umbraco request completed");
LogHelper.Debug<UmbracoModule>("Total milliseconds for umbraco request to process: " + DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds);
}
};
}
public void Dispose()

View File

@@ -15,6 +15,7 @@ using Umbraco.Web.Mvc;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.Routing;
using umbraco.businesslogic;
using umbraco.cms.businesslogic;
namespace Umbraco.Web
@@ -195,6 +196,10 @@ namespace Umbraco.Web
{
base.InitializeResolvers();
//TODO: This needs to be removed in future versions (i.e. 6.0 when the PublishedContentHelper can access the business logic)
// see the TODO noted in the PublishedContentHelper.
PublishedContentHelper.GetDataTypeCallback = ContentType.GetDataType;
SurfaceControllerResolver.Current = new SurfaceControllerResolver(
PluginManager.Current.ResolveSurfaceControllers());

View File

@@ -263,11 +263,6 @@ namespace umbraco.cms.businesslogic.template
FlushCache();
_design = value.Trim(Environment.NewLine.ToCharArray());
// NH: Removing an generating the directive can mess up code behind
// We don't store the masterpage directive in the design value
// if (_design.StartsWith("<%@"))
// _design = _design.Substring(_design.IndexOf("%>") + 3).Trim(Environment.NewLine.ToCharArray());
//we only switch to MVC View editing if the template has a view file, and MVC editing is enabled
if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc && !MasterPageHelper.IsMasterPageSyntax(_design))
@@ -549,26 +544,7 @@ namespace umbraco.cms.businesslogic.template
// NH: Changed this; if you delete a template we'll remove all references instead of
// throwing an exception
if (DocumentType.GetAllAsList().Where(x => x.allowedTemplates.Select(t => t.Id).Contains(this.Id)).Count() > 0)
{
// the uncommented code below have been refactored into removeAllReferences method that clears template
// from documenttypes, subtemplates and documents.
RemoveAllReferences();
/*
// Added to remove template doctype relationship
SqlHelper.ExecuteNonQuery("delete from cmsDocumentType where templateNodeId =" + this.Id);
// Need to update any other template that references this one as it's master to NULL
SqlHelper.ExecuteNonQuery("update cmsTemplate set [master] = NULL where [master] = " + this.Id);
*/
// don't allow template deletion if it is in use
// (get all doc types and filter based on any that have the template id of this one)
/*
Log.Add(LogTypes.Error, this.Id, "Can't delete a template that is assigned to existing content");
throw new InvalidOperationException("Can't delete a template that is assigned to existing content");
*/
}
DeleteEventArgs e = new DeleteEventArgs();
FireBeforeDelete(e);
@@ -645,7 +621,7 @@ namespace umbraco.cms.businesslogic.template
public string ConvertToMasterPageSyntax(string templateDesign)
{
string masterPageContent = GetMasterContentElement(MasterTemplate) + "\n";
string masterPageContent = GetMasterContentElement(MasterTemplate) + Environment.NewLine;
masterPageContent += templateDesign;
@@ -653,7 +629,9 @@ namespace umbraco.cms.businesslogic.template
masterPageContent = EnsureMasterPageSyntax(masterPageContent);
// append ending asp:content element
masterPageContent += "\n</asp:Content>" + Environment.NewLine;
masterPageContent += Environment.NewLine
+ "</asp:Content>"
+ Environment.NewLine;
return masterPageContent;
}
@@ -683,96 +661,12 @@ namespace umbraco.cms.businesslogic.template
public void ImportDesign(string design)
{
Design = design;
/*
if (!isMasterPageSyntax(design))
{
Design = ConvertToMasterPageSyntax(design);
}
else
{
Design = design;
}*/
}
public void SaveMasterPageFile(string masterPageContent)
{
//this will trigger the helper and store everything
this.Design = masterPageContent;
/*
// Add header to master page if it doesn't exist
if (!masterPageContent.StartsWith("<%@"))
{
masterPageContent = getMasterPageHeader() + "\n" + masterPageContent;
}
else
{
// verify that the masterpage attribute is the same as the masterpage
string masterHeader = masterPageContent.Substring(0, masterPageContent.IndexOf("%>") + 2).Trim(Environment.NewLine.ToCharArray());
// find the masterpagefile attribute
MatchCollection m = Regex.Matches(masterHeader, "(?<attributeName>\\S*)=\"(?<attributeValue>[^\"]*)\"",
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
foreach (Match attributeSet in m)
{
if (attributeSet.Groups["attributeName"].Value.ToLower() == "masterpagefile")
{
// validate the masterpagefile
string currentMasterPageFile = attributeSet.Groups["attributeValue"].Value;
string currentMasterTemplateFile = currentMasterTemplateFileName();
if (currentMasterPageFile != currentMasterTemplateFile)
{
masterPageContent =
masterPageContent.Replace(
attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterPageFile + "\"",
attributeSet.Groups["attributeName"].Value + "=\"" + currentMasterTemplateFile + "\"");
}
}
}
}
//we have a Old Alias if the alias and therefor the masterpage file name has changed...
//so before we save the new masterfile, we'll clear the old one, so we don't up with
//Unused masterpage files
if (!string.IsNullOrEmpty(_oldAlias) && _oldAlias != _alias)
{
//Ensure that child templates have the right master masterpage file name
if (HasChildren)
{
//store children array here because iterating over an Array property object is very inneficient.
var c = Children;
foreach (CMSNode cmn in c)
{
Template t = new Template(cmn.Id);
t.SaveAsMasterPage();
t.Save();
}
}
//then kill the old file..
string _oldFile = IOHelper.MapPath(SystemDirectories.Masterpages + "/" + _oldAlias.Replace(" ", "") + ".master");
if (System.IO.File.Exists(_oldFile))
System.IO.File.Delete(_oldFile);
}
// save the file in UTF-8
File.WriteAllText(MasterPageFile, masterPageContent, System.Text.Encoding.UTF8);
* */
}
private string CurrentMasterTemplateFileName()
{
if (MasterTemplate != 0)
return SystemDirectories.Masterpages + "/" + new Template(MasterTemplate).Alias.Replace(" ", "") + ".master";
else
return UmbracoMasterTemplate;
}
private void GetAspNetMasterPageForm(ref string design)

View File

@@ -3,21 +3,20 @@ using System.Collections;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using umbraco.BusinessLogic;
using ClientDependency.Core.Controls;
using ClientDependency.Core;
using umbraco.presentation;
using umbraco.interfaces;
namespace umbraco.editorControls.tags
{
public class DataEditor : System.Web.UI.UpdatePanel, umbraco.interfaces.IDataEditor, umbraco.interfaces.IUseTags
public class DataEditor : TextBox, IDataEditor, IUseTags
{
#region IDataEditor Members
cms.businesslogic.datatype.DefaultData _data;
string _group = "";
private readonly cms.businesslogic.datatype.DefaultData _data;
readonly string _group = "";
public DataEditor(umbraco.interfaces.IData Data, SortedList Prevalues)
public DataEditor(IData Data, SortedList Prevalues)
{
_data = (cms.businesslogic.datatype.DefaultData)Data;
@@ -25,41 +24,22 @@ namespace umbraco.editorControls.tags
_group = Prevalues["group"].ToString();
}
public TextBox tagBox = new TextBox();
public Control Editor { get { return this; } }
public void Save()
{
int _nodeID;
int.TryParse(_data.NodeId.ToString(), out _nodeID);
string allTags = "";
int nodeId = _data.NodeId;
//first clear out all items associated with this ID...
umbraco.cms.businesslogic.Tags.Tag.RemoveTagsFromNode(_nodeID, _group);
cms.businesslogic.Tags.Tag.RemoveTagsFromNode(nodeId, _group);
var items = tagBox.Text.Trim().Split(',');
foreach (var item in items)
{
var tagName = item.Trim();
if(string.IsNullOrEmpty(tagName))
continue;
var tagId = cms.businesslogic.Tags.Tag.GetTagId(tagName, _group);
if(tagId == 0)
tagId = cms.businesslogic.Tags.Tag.AddTag(tagName, _group);
if (tagId > 0)
{
umbraco.cms.businesslogic.Tags.Tag.AssociateTagToNode(_nodeID, tagId);
}
}
UpdateOrAddTags(nodeId);
//and just in case, we'll save the tags as plain text on the node itself...
_data.Value = tagBox.Text;
_data.Value = this.Text;
}
public bool ShowLabel
{
get { return true; }
@@ -74,35 +54,54 @@ namespace umbraco.editorControls.tags
[Obsolete("use the umbraco.cms.businesslogic.Tags.Tag class instead")]
public int getTagId(string tag, string group)
{
return umbraco.cms.businesslogic.Tags.Tag.GetTagId(tag, group);
return cms.businesslogic.Tags.Tag.GetTagId(tag, group);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
//ClientDependencyLoader.Instance.RegisterDependency("Application/JQuery/jquery.autocomplete.js", "UmbracoClient", ClientDependencyType.Javascript);
// register all dependencies - only registered once
ClientDependencyLoader.Instance.RegisterDependency("ui/ui-lightness/jquery-ui.custom.css", "UmbracoClient", ClientDependencyType.Css);
ClientDependencyLoader.Instance.RegisterDependency("css/umbracoGui.css", "UmbracoRoot", ClientDependencyType.Css);
ClientDependencyLoader.Instance.RegisterDependency("tags/css/jquery.tagsinput.css", "UmbracoClient", ClientDependencyType.Css);
ClientDependencyLoader.Instance.RegisterDependency("tags/js/jquery.tagsinput.min.js", "UmbracoClient", ClientDependencyType.Javascript);
var pageId = GetPageId();
if (!Page.IsPostBack && !String.IsNullOrEmpty(pageId) && string.IsNullOrWhiteSpace(this.Text))
{
var tags = cms.businesslogic.Tags.Tag.GetTags(int.Parse(pageId), _group);
this.Text = string.Join(",", tags.Select(x => x.TagCaption));
}
string _alias = ((umbraco.cms.businesslogic.datatype.DefaultData)_data).PropertyId.ToString();
var tagsAutoCompleteScript = TagsAutocompleteScript(pageId);
var tagsAutoCompleteInitScript = string.Format("jQuery(document).ready(function(){{{0}}});", tagsAutoCompleteScript);
//making sure that we have a ID for context
string pageId = UmbracoContext.Current.Request["id"];
// register it as a startup script
Page.ClientScript.RegisterStartupScript(GetType(), ClientID + "_tagsinit", tagsAutoCompleteInitScript, true);
}
if (pageId != null) pageId = pageId.Trim();
private string TagsAutocompleteScript(string pageId)
{
var tagsAutoCompleteScript = string.Format("jQuery('#{0}').tagsInput({{ width: '400px', defaultText: 'Add a tag', minChars: 2, autocomplete_url: '{1}/webservices/TagsAutoCompleteHandler.ashx?group={2}&id={3}&rnd={4}&format=json' }});",
this.ClientID,
Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco),
_group,
pageId,
DateTime.Now.Ticks);
return tagsAutoCompleteScript;
}
private static string GetPageId()
{
var pageId = Umbraco.Web.UmbracoContext.Current.HttpContext.Request["id"];
if (string.IsNullOrEmpty(pageId))
{
// we need an empty try/catch as Node.GetCurrent() will throw an exception if we're outside of Umbraco Context
try
{
NodeFactory.Node currentNode = NodeFactory.Node.GetCurrent();
var currentNode = NodeFactory.Node.GetCurrent();
if (currentNode != null)
{
pageId = currentNode.Id.ToString();
@@ -112,38 +111,28 @@ namespace umbraco.editorControls.tags
{
}
}
if (pageId != null) pageId = pageId.Trim();
return pageId;
}
tagBox.ID = "tagBox2_" + _alias;
tagBox.CssClass = "umbEditorTextField umbTagBox";
if (!String.IsNullOrEmpty(pageId))
private void UpdateOrAddTags(int nodeId)
{
var items = this.Text.Trim().Split(',');
foreach (var item in items)
{
var tags = umbraco.cms.businesslogic.Tags.Tag.GetTags(int.Parse(pageId), _group);
var tagName = item.Trim();
if (string.IsNullOrEmpty(tagName))
continue;
tagBox.Text = string.Join(",", tags.Select(x => x.TagCaption));
var tagId = cms.businesslogic.Tags.Tag.GetTagId(tagName, _group);
if (tagId == 0)
tagId = cms.businesslogic.Tags.Tag.AddTag(tagName, _group);
if (tagId > 0)
{
cms.businesslogic.Tags.Tag.AssociateTagToNode(nodeId, tagId);
}
}
this.ContentTemplateContainer.Controls.Add(tagBox);
string tagsAutoCompleteScript =
"jQuery('#" + tagBox.ClientID + "').tagsInput({ width: '400px', defaultText: 'Add a tag', minChars: 2, autocomplete_url: '" +
umbraco.IO.IOHelper.ResolveUrl(umbraco.IO.SystemDirectories.Umbraco)
+ "/webservices/TagsAutoCompleteHandler.ashx?group=" + _group + "&id=" + pageId + "&rnd=" +
DateTime.Now.Ticks + "&format=json' });";
string tagsAutoCompleteInitScript =
"jQuery(document).ready(function(){"
+ tagsAutoCompleteScript
+ "});";
Page.ClientScript.RegisterStartupScript(GetType(), ClientID + "_tagsinit", tagsAutoCompleteInitScript, true);
if (Page.IsPostBack)
{
ScriptManager.RegisterClientScriptBlock(this, GetType(), ClientID + "_tags", tagsAutoCompleteScript, true);
}
}
#endregion
@@ -155,50 +144,35 @@ namespace umbraco.editorControls.tags
get { return _group; }
}
#endregion
#region IUseTags Members
public void RemoveTag(int nodeId, string tag)
{
library.RemoveTagFromNode(nodeId, tag, _group);
cms.businesslogic.Tags.Tag.RemoveTagFromNode(nodeId, tag, _group);
}
public System.Collections.Generic.List<umbraco.interfaces.ITag> GetTagsFromNode(int nodeId)
public System.Collections.Generic.List<ITag> GetTagsFromNode(int nodeId)
{
return library.GetTagsFromNodeAsITags(nodeId);
return cms.businesslogic.Tags.Tag.GetTags(nodeId).Cast<ITag>().ToList();
}
public System.Collections.Generic.List<umbraco.interfaces.ITag> GetAllTags()
public System.Collections.Generic.List<ITag> GetAllTags()
{
return library.GetTagsFromGroupAsITags(_group);
return cms.businesslogic.Tags.Tag.GetTags(_group).Cast<ITag>().ToList();
}
#endregion
#region IUseTags Members
public void AddTag(string tag)
{
library.AddTag(tag, _group);
cms.businesslogic.Tags.Tag.AddTag(tag, _group);
}
public void AddTagToNode(int nodeId, string tag)
{
library.addTagsToNode(nodeId, tag, _group);
cms.businesslogic.Tags.Tag.AddTagsToNode(nodeId, tag, _group);
}
#endregion
#region IUseTags Members
public void RemoveTagsFromNode(int nodeId)
{
foreach (umbraco.interfaces.ITag t in library.GetTagsFromNodeAsITags(nodeId))
library.RemoveTagFromNode(nodeId, t.TagCaption, t.Group);
foreach (var tag in cms.businesslogic.Tags.Tag.GetTags(nodeId).Cast<ITag>().ToList())
cms.businesslogic.Tags.Tag.RemoveTagFromNode(nodeId, tag.TagCaption, tag.Group);
}
#endregion

View File

@@ -324,6 +324,7 @@ namespace umbraco.editorControls.tinyMCE3
public virtual void Save()
{
string parsedString = base.Text.Trim();
string parsedStringForTinyMce = parsedString;
if (parsedString != string.Empty)
{
parsedString = replaceMacroTags(parsedString).Trim();
@@ -370,19 +371,22 @@ namespace umbraco.editorControls.tinyMCE3
parsedString = parsedString.Replace(helper.GetBaseUrl(HttpContext.Current) + "/#", "#");
parsedString = parsedString.Replace(helper.GetBaseUrl(HttpContext.Current), "");
}
// if a paragraph is empty, remove it
if (parsedString.ToLower() == "<p></p>")
parsedString = "";
// save string after all parsing is done, but before CDATA replacement - to put back into TinyMCE
parsedStringForTinyMce = parsedString;
//Allow CDATA nested into RTE without exceptions
// GE 2012-01-18
parsedString = parsedString.Replace("<![CDATA[", "<!--CDATAOPENTAG-->").Replace("]]>", "<!--CDATACLOSETAG-->");
// if text is an empty parargraph, make it all empty
if (parsedString.ToLower() == "<p></p>")
parsedString = "";
}
_data.Value = parsedString;
// update internal webcontrol value with parsed result
base.Text = parsedString;
base.Text = parsedStringForTinyMce;
}
#endregion