Merge with 4.11.0
This commit is contained in:
5
.hgtags
5
.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
|
||||
|
||||
@@ -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);
|
||||
|
||||
76
src/Umbraco.Core/HashCodeCombiner.cs
Normal file
76
src/Umbraco.Core/HashCodeCombiner.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
103
src/Umbraco.Core/PublishedContentHelper.cs
Normal file
103
src/Umbraco.Core/PublishedContentHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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")
|
||||
{
|
||||
|
||||
@@ -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]
|
||||
|
||||
154
src/Umbraco.Tests/HashCodeCombinerTests.cs
Normal file
154
src/Umbraco.Tests/HashCodeCombinerTests.cs
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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[]
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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[]
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user