diff --git a/.hgtags b/.hgtags
index 2c22ddad48..8a944ec680 100644
--- a/.hgtags
+++ b/.hgtags
@@ -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
diff --git a/src/Umbraco.Core/CustomBooleanTypeConverter.cs b/src/Umbraco.Core/CustomBooleanTypeConverter.cs
index b7b30d1fa1..76c8f75076 100644
--- a/src/Umbraco.Core/CustomBooleanTypeConverter.cs
+++ b/src/Umbraco.Core/CustomBooleanTypeConverter.cs
@@ -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);
diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs
new file mode 100644
index 0000000000..78f5eb6839
--- /dev/null
+++ b/src/Umbraco.Core/HashCodeCombiner.cs
@@ -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
+{
+ ///
+ /// Used to create a hash code from multiple objects.
+ ///
+ ///
+ /// .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.
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Returns the hex code of the combined hash code
+ ///
+ ///
+ internal string GetCombinedHashCode()
+ {
+ return _combinedHash.ToString("x", CultureInfo.InvariantCulture);
+ }
+
+ }
+}
diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs
index e7f6b29c60..6828b0e353 100644
--- a/src/Umbraco.Core/IO/SystemDirectories.cs
+++ b/src/Umbraco.Core/IO/SystemDirectories.cs
@@ -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
diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs
index 0d86f41725..770363336b 100644
--- a/src/Umbraco.Core/PluginManager.cs
+++ b/src/Umbraco.Core/PluginManager.cs
@@ -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.
///
internal class PluginManager
{
+ private readonly ApplicationContext _appContext;
- internal PluginManager()
+ ///
+ /// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml
+ /// file is cached temporarily until app startup completes.
+ ///
+ ///
+ ///
+ internal PluginManager(ApplicationContext appContext, bool detectBinChanges = true)
+ : this(detectBinChanges)
{
+ if (appContext == null) throw new ArgumentNullException("appContext");
+ _appContext = appContext;
+ }
+
+ ///
+ /// Creates a new PluginManager
+ ///
+ ///
+ /// 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
+ ///
+ 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;
///
/// 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
+
+ ///
+ /// Returns a bool if the assemblies in the /bin have changed since they were last hashed.
+ ///
+ internal bool HaveAssembliesChanged { get; private set; }
+
+ ///
+ /// Returns the currently cached hash value of the scanned assemblies in the /bin folder. Returns 0
+ /// if no cache is found.
+ ///
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Returns the current assemblies hash based on creating a hash from the assemblies in the /bin
+ ///
+ ///
+ internal long CurrentAssembliesHash
+ {
+ get
+ {
+ if (_currentAssembliesHash != -1)
+ return _currentAssembliesHash;
+
+ _currentAssembliesHash = GetAssembliesHash(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)).GetFiles("*.dll"));
+ return _currentAssembliesHash;
+ }
+ }
+
+ ///
+ /// Writes the assembly hash file
+ ///
+ private void WriteCachePluginsHash()
+ {
+ var filePath = Path.Combine(_tempFolder, "umbraco-plugins.hash");
+ File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8);
+ }
+
+ ///
+ /// Returns a unique hash for the combination of FileInfo objects passed in
+ ///
+ ///
+ ///
+ internal static long GetAssembliesHash(IEnumerable plugins)
+ {
+ using (DisposableTimer.TraceDuration("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());
+ }
+ }
+
+ ///
+ /// Converts the hash value of current plugins to long from string
+ ///
+ ///
+ ///
+ internal static long ConvertPluginsHashFromHex(string val)
+ {
+ long outVal;
+ if (Int64.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal))
+ {
+ return outVal;
+ }
+ return 0;
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ internal Attempt> TryGetCachedPluginsFromFile()
+ {
+ var filePath = Path.Combine(_tempFolder, "umbraco-plugins.list");
+ if (!File.Exists(filePath))
+ return Attempt>.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>.False;
+
+ var typeElement = xml.Root.Elements()
+ .SingleOrDefault(x =>
+ x.Name.LocalName == "baseType"
+ && ((string) x.Attribute("type")) == typeof (T).FullName);
+ if (typeElement == null)
+ return Attempt>.False;
+
+ //return success
+ return new Attempt>(
+ true,
+ typeElement.Elements("add")
+ .Select(x => (string) x.Attribute("type")));
+ }
+ catch (Exception)
+ {
+ //if the file is corrupted, etc... return false
+ return Attempt>.False;
+ }
+ }
+
+ ///
+ /// Adds/Updates the type list for the base type 'T' in the cached file
+ ///
+ ///
+ ///
+ ///
+ /// THIS METHOD IS NOT THREAD SAFE
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ internal void UpdateCachedPluginsFile(IEnumerable 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 _types = new HashSet();
private IEnumerable _assemblies;
@@ -208,7 +478,7 @@ namespace Umbraco.Core
using (DisposableTimer.TraceDuration(
String.Format("Starting resolution types of {0}", typeof(T).FullName),
String.Format("Completed resolution of types of {0}", typeof(T).FullName)))
- {
+ {
//check if the TypeList already exists, if so return it, if not we'll create it
var typeList = _types.SingleOrDefault(x => x.IsTypeList(resolutionType));
//if we're not caching the result then proceed, or if the type list doesn't exist then proceed
@@ -219,9 +489,47 @@ namespace Umbraco.Core
typeList = new TypeList(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();
+ 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("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(typeList, finder);
+ }
+ else
+ {
+ LogHelper.Debug("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(typeList, finder);
}
//only add the cache if we are to cache the results
@@ -236,6 +544,25 @@ namespace Umbraco.Core
}
}
+ ///
+ /// 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
+ ///
+ ///
+ ///
+ ///
+ /// THIS METHODS IS NOT THREAD SAFE
+ ///
+ private void LoadViaScanningAndUpdateCacheFile(TypeList typeList, Func> 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(typeList.GetTypes());
+ }
+
///
/// Generic method to find the specified type and cache the result
///
diff --git a/src/Umbraco.Core/PropertyEditors/DatePickerPropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/DatePickerPropertyEditorValueConverter.cs
index 9c1f72e473..5d437f5d91 100644
--- a/src/Umbraco.Core/PropertyEditors/DatePickerPropertyEditorValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/DatePickerPropertyEditorValueConverter.cs
@@ -21,16 +21,7 @@ namespace Umbraco.Core.PropertyEditors
///
public Attempt
public static class PublishedContentExtensions
{
+
#region GetProperty
@@ -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.
///
///
- ///
+ ///
///
///
- public static T GetPropertyValue(this IPublishedContent prop, string alias)
+ public static T GetPropertyValue(this IPublishedContent doc, string alias)
{
- return prop.GetPropertyValue(alias, default(T));
+ return doc.GetPropertyValue(alias, default(T));
+ }
+
+ public static T GetPropertyValue(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();
+ 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();
+ if (manualConverted.Success)
+ return manualConverted.Result;
+ return ifCannotConvert;
}
public static T GetPropertyValue(this IPublishedContent prop, string alias, T ifCannotConvert)
{
- var p = prop.GetProperty(alias);
- if (p == null)
- return default(T);
- var converted = p.Value.TryConvertTo();
- if (converted.Success)
- return converted.Result;
- return ifCannotConvert;
+ return prop.GetPropertyValue(alias, false, ifCannotConvert);
}
}
diff --git a/src/Umbraco.Core/PublishedContentHelper.cs b/src/Umbraco.Core/PublishedContentHelper.cs
new file mode 100644
index 0000000000..94fc293e4c
--- /dev/null
+++ b/src/Umbraco.Core/PublishedContentHelper.cs
@@ -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
+ {
+ ///
+ /// 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.
+ ///
+ internal static Func GetDataTypeCallback = (docTypeAlias, propertyAlias) => Guid.Empty;
+
+ internal static Guid GetDataType(string docTypeAlias, string propertyAlias)
+ {
+ return GetDataTypeCallback(docTypeAlias, propertyAlias);
+ }
+
+ ///
+ /// Converts the currentValue to a correctly typed value based on known registered converters, then based on known standards.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static Attempt ConvertPropertyValue(object currentValue, Guid dataType, string docTypeAlias, string propertyTypeAlias)
+ {
+ if (currentValue == null) return Attempt.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(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(true, dResult);
+ }
+ }
+ //process string booleans as booleans
+ if (sResult.InvariantEquals("true"))
+ {
+ return new Attempt(true, true);
+ }
+ if (sResult.InvariantEquals("false"))
+ {
+ return new Attempt(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(true, new DynamicXml(e));
+ }
+ return Attempt.False;
+ }
+ catch (Exception)
+ {
+ return Attempt.False;
+ }
+ }
+ return Attempt.False;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 5f4bde8b72..b88d8fb917 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -84,6 +84,7 @@
+
@@ -399,6 +400,7 @@
+
diff --git a/src/Umbraco.Tests/CacheRefresherFactoryTests.cs b/src/Umbraco.Tests/CacheRefresherFactoryTests.cs
index e9a2c075fb..8fdef3f09a 100644
--- a/src/Umbraco.Tests/CacheRefresherFactoryTests.cs
+++ b/src/Umbraco.Tests/CacheRefresherFactoryTests.cs
@@ -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[]
diff --git a/src/Umbraco.Tests/DataTypeFactoryTests.cs b/src/Umbraco.Tests/DataTypeFactoryTests.cs
index d836b8f836..e0de0a4af6 100644
--- a/src/Umbraco.Tests/DataTypeFactoryTests.cs
+++ b/src/Umbraco.Tests/DataTypeFactoryTests.cs
@@ -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[]
diff --git a/src/Umbraco.Tests/DynamicDocument/DynamicPublishedContentTests.cs b/src/Umbraco.Tests/DynamicDocument/DynamicPublishedContentTests.cs
index 4fd97c45d5..e9c0dfa3b3 100644
--- a/src/Umbraco.Tests/DynamicDocument/DynamicPublishedContentTests.cs
+++ b/src/Umbraco.Tests/DynamicDocument/DynamicPublishedContentTests.cs
@@ -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")
{
diff --git a/src/Umbraco.Tests/DynamicDocument/PublishedContentTests.cs b/src/Umbraco.Tests/DynamicDocument/PublishedContentTests.cs
index 12d9670501..3c55138a75 100644
--- a/src/Umbraco.Tests/DynamicDocument/PublishedContentTests.cs
+++ b/src/Umbraco.Tests/DynamicDocument/PublishedContentTests.cs
@@ -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(propVal.GetType()));
+ Assert.AreEqual("
", 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("selectedNodes", "").Split(',').Contains("1173"));
Assert.IsNotNull(result);
}
@@ -255,17 +270,7 @@ 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(doc.GetPropertyValue().Content.GetType()));
- //Assert.AreEqual("
This is some content
", 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]
diff --git a/src/Umbraco.Tests/HashCodeCombinerTests.cs b/src/Umbraco.Tests/HashCodeCombinerTests.cs
new file mode 100644
index 0000000000..b1c808e7f8
--- /dev/null
+++ b/src/Umbraco.Tests/HashCodeCombinerTests.cs
@@ -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
+ {
+
+ 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()
+ {
+
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/MacroEngineFactoryTests.cs b/src/Umbraco.Tests/MacroEngineFactoryTests.cs
index 4a058e272e..f38a825f6f 100644
--- a/src/Umbraco.Tests/MacroEngineFactoryTests.cs
+++ b/src/Umbraco.Tests/MacroEngineFactoryTests.cs
@@ -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[]
diff --git a/src/Umbraco.Tests/MediaFactoryTests.cs b/src/Umbraco.Tests/MediaFactoryTests.cs
index 58fd81d446..11bf2de2fe 100644
--- a/src/Umbraco.Tests/MediaFactoryTests.cs
+++ b/src/Umbraco.Tests/MediaFactoryTests.cs
@@ -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[]
diff --git a/src/Umbraco.Tests/ObjectExtensionsTests.cs b/src/Umbraco.Tests/ObjectExtensionsTests.cs
index 022a84e144..8a786ded6f 100644
--- a/src/Umbraco.Tests/ObjectExtensionsTests.cs
+++ b/src/Umbraco.Tests/ObjectExtensionsTests.cs
@@ -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
+ {
+ {"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();
+
+ Assert.IsTrue(result.Success);
+ Assert.AreEqual(DateTime.Equals(dateTime.Date, result.Result.Date), testCase.Value);
+ }
+ }
+
///
/// Run once before each test in derived test fixtures.
///
diff --git a/src/Umbraco.Tests/PluginManagerTests.cs b/src/Umbraco.Tests/PluginManagerTests.cs
index 69fb1afa9a..f7262c8814 100644
--- a/src/Umbraco.Tests/PluginManagerTests.cs
+++ b/src/Umbraco.Tests/PluginManagerTests.cs
@@ -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(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(types);
+
+ var plugins = manager.TryGetCachedPluginsFromFile();
+ 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()
{
diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs
new file mode 100644
index 0000000000..c8616bb7c7
--- /dev/null
+++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs
@@ -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
+ {
+ {"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
+ {
+ {"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);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs b/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs
index d9bd05c7ce..34b834753b 100644
--- a/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs
+++ b/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs
@@ -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[]
diff --git a/src/Umbraco.Tests/Resolvers/MacroFieldEditorsResolverTests.cs b/src/Umbraco.Tests/Resolvers/MacroFieldEditorsResolverTests.cs
index eff9f82b53..6f7ae14ce9 100644
--- a/src/Umbraco.Tests/Resolvers/MacroFieldEditorsResolverTests.cs
+++ b/src/Umbraco.Tests/Resolvers/MacroFieldEditorsResolverTests.cs
@@ -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[]
diff --git a/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs b/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs
index b126fde180..a12890a36f 100644
--- a/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs
+++ b/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs
@@ -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[]
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index a59f45a0d3..7d04ea49ef 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -92,9 +92,11 @@
+
+
@@ -255,6 +257,7 @@
Umbraco.Web
+ False
diff --git a/src/Umbraco.Web/DefaultPublishedMediaStore.cs b/src/Umbraco.Web/DefaultPublishedMediaStore.cs
index 2283c2a18f..6237912c9e 100644
--- a/src/Umbraco.Web/DefaultPublishedMediaStore.cs
+++ b/src/Umbraco.Web/DefaultPublishedMediaStore.cs
@@ -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");
diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs
index 4e3c423863..990abd2296 100644
--- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs
+++ b/src/Umbraco.Web/Models/DynamicPublishedContent.cs
@@ -22,12 +22,6 @@ namespace Umbraco.Web.Models
///
public class DynamicPublishedContent : DynamicObject, IPublishedContent
{
- ///
- /// This callback is used only so we can set it dynamically for use in unit tests
- ///
- internal static Func GetDataTypeCallback = (docTypeAlias, propertyAlias) =>
- ContentType.GetDataType(docTypeAlias, propertyAlias);
-
protected IPublishedContent PublishedContent { get; private set; }
private DynamicPublishedContentList _cachedChildren;
private readonly ConcurrentDictionary _cachedMemberOutput = new ConcurrentDictionary();
@@ -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,87 +818,7 @@ namespace Umbraco.Web.Models
}
#endregion
- private static Guid GetDataType(string docTypeAlias, string propertyAlias)
- {
- return GetDataTypeCallback(docTypeAlias, propertyAlias);
- }
-
- ///
- /// Converts the currentValue to a correctly typed value based on known registered converters, then based on known standards.
- ///
- ///
- ///
- ///
- ///
- ///
- private Attempt ConvertPropertyValue(object currentValue, Guid dataType, string docTypeAlias, string propertyTypeAlias)
- {
- if (currentValue == null) return Attempt.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(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(true, dResult);
- }
- }
- //process string booleans as booleans
- if (sResult.InvariantEquals("true"))
- {
- return new Attempt(true, true);
- }
- if (sResult.InvariantEquals("false"))
- {
- return new Attempt(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(true, new DynamicXml(e));
- }
- return Attempt.False;
- }
- catch (Exception)
- {
- return Attempt.False;
- }
- }
- return Attempt.False;
- }
-
+
#region Index/Position
public int Position()
{
diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs b/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs
index c1fe63605e..140f0d1de5 100644
--- a/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs
+++ b/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs
@@ -312,7 +312,15 @@ namespace Umbraco.Web.Routing
{
LogHelper.Debug("{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("{0}Membership.GetUser returned ArgumentException", () => tracePrefix);
+ }
if (user == null || !Member.IsLoggedOn())
{
diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs
index bf722a4300..7c97ecce6b 100644
--- a/src/Umbraco.Web/UmbracoContext.cs
+++ b/src/Umbraco.Web/UmbracoContext.cs
@@ -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
}
}
+ ///
+ /// 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.
+ ///
+ internal DateTime ObjectCreated { get; private set; }
+
///
/// Gets the current ApplicationContext
///
diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs
index e8b5c81562..4be9eba95f 100644
--- a/src/Umbraco.Web/UmbracoHelper.cs
+++ b/src/Umbraco.Web/UmbracoHelper.cs
@@ -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();
diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs
index 24292936cf..cad2639599 100644
--- a/src/Umbraco.Web/UmbracoModule.cs
+++ b/src/Umbraco.Web/UmbracoModule.cs
@@ -37,7 +37,10 @@ namespace Umbraco.Web
{
// do not process if client-side request
if (IsClientSideRequest(httpContext.Request.Url))
- return;
+ return;
+
+ //write the trace output for diagnostics at the end of the request
+ httpContext.Trace.Write("UmbracoModule", "Umbraco request begins");
// ok, process
@@ -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;
@@ -400,7 +405,7 @@ namespace Umbraco.Web
{
app.BeginRequest += (sender, e) =>
{
- var httpContext = ((HttpApplication)sender).Context;
+ var httpContext = ((HttpApplication)sender).Context;
BeginRequest(new HttpContextWrapper(httpContext));
};
@@ -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("Total milliseconds for umbraco request to process: " + DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds);
+ }
+ };
}
public void Dispose()
diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs
index 788330bc19..58d3c7fbab 100644
--- a/src/Umbraco.Web/WebBootManager.cs
+++ b/src/Umbraco.Web/WebBootManager.cs
@@ -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());
diff --git a/src/umbraco.cms/businesslogic/template/Template.cs b/src/umbraco.cms/businesslogic/template/Template.cs
index 936f45b6e5..32763615c9 100644
--- a/src/umbraco.cms/businesslogic/template/Template.cs
+++ b/src/umbraco.cms/businesslogic/template/Template.cs
@@ -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" + Environment.NewLine;
+ masterPageContent += Environment.NewLine
+ + ""
+ + Environment.NewLine;
return masterPageContent;
}
@@ -683,98 +661,14 @@ 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, "(?\\S*)=\"(?[^\"]*)\"",
- 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)
{
Match formElement = Regex.Match(design, GetElementRegExp("?ASPNET_FORM", false), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
diff --git a/src/umbraco.editorControls/tags/DataEditor.cs b/src/umbraco.editorControls/tags/DataEditor.cs
index ab80d352c4..4281e1f12a 100644
--- a/src/umbraco.editorControls/tags/DataEditor.cs
+++ b/src/umbraco.editorControls/tags/DataEditor.cs
@@ -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);
-
- string _alias = ((umbraco.cms.businesslogic.datatype.DefaultData)_data).PropertyId.ToString();
+ 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));
+ }
- //making sure that we have a ID for context
- string pageId = UmbracoContext.Current.Request["id"];
+ var tagsAutoCompleteScript = TagsAutocompleteScript(pageId);
+ var tagsAutoCompleteInitScript = string.Format("jQuery(document).ready(function(){{{0}}});", tagsAutoCompleteScript);
- if (pageId != null) pageId = pageId.Trim();
+ // register it as a startup script
+ Page.ClientScript.RegisterStartupScript(GetType(), ClientID + "_tagsinit", tagsAutoCompleteInitScript, true);
+ }
+ 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 GetTagsFromNode(int nodeId)
+ public System.Collections.Generic.List GetTagsFromNode(int nodeId)
{
- return library.GetTagsFromNodeAsITags(nodeId);
+ return cms.businesslogic.Tags.Tag.GetTags(nodeId).Cast().ToList();
}
- public System.Collections.Generic.List GetAllTags()
+ public System.Collections.Generic.List GetAllTags()
{
- return library.GetTagsFromGroupAsITags(_group);
+ return cms.businesslogic.Tags.Tag.GetTags(_group).Cast().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().ToList())
+ cms.businesslogic.Tags.Tag.RemoveTagFromNode(nodeId, tag.TagCaption, tag.Group);
}
#endregion
diff --git a/src/umbraco.editorControls/tinyMCE3/TinyMCE.cs b/src/umbraco.editorControls/tinyMCE3/TinyMCE.cs
index 232caab2ed..96c9af703d 100644
--- a/src/umbraco.editorControls/tinyMCE3/TinyMCE.cs
+++ b/src/umbraco.editorControls/tinyMCE3/TinyMCE.cs
@@ -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() == "")
+ 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("").Replace("]]>", "");
-
- // if text is an empty parargraph, make it all empty
- if (parsedString.ToLower() == "")
- parsedString = "";
}
+
_data.Value = parsedString;
// update internal webcontrol value with parsed result
- base.Text = parsedString;
+ base.Text = parsedStringForTinyMce;
}
#endregion