diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 662f0b666a..e46b9f2d3c 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyFileVersion("7.7.2")]
-[assembly: AssemblyInformationalVersion("7.7.2")]
\ No newline at end of file
+[assembly: AssemblyFileVersion("7.7.4")]
+[assembly: AssemblyInformationalVersion("7.7.4")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs
index 02f3322ec9..395502d1b2 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs
@@ -538,24 +538,41 @@ namespace Umbraco.Core.Configuration
internal static bool ContentCacheXmlStoredInCodeGen
{
- get { return ContentCacheXmlStorageLocation == ContentXmlStorage.AspNetTemp; }
+ get { return LocalTempStorageLocation == LocalTempStorage.AspNetTemp; }
}
-
- internal static ContentXmlStorage ContentCacheXmlStorageLocation
+
+ ///
+ /// This is the location type to store temporary files such as cache files or other localized files for a given machine
+ ///
+ ///
+ /// Currently used for the xml cache file and the plugin cache files
+ ///
+ internal static LocalTempStorage LocalTempStorageLocation
{
get
- {
+ {
+ //there's a bunch of backwards compat config checks here....
+
+ //This is the current one
+ if (ConfigurationManager.AppSettings.ContainsKey("umbracoLocalTempStorage"))
+ {
+ return Enum.Parse(ConfigurationManager.AppSettings["umbracoLocalTempStorage"]);
+ }
+
+ //This one is old
if (ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLStorage"))
{
- return Enum.Parse(ConfigurationManager.AppSettings["umbracoContentXMLStorage"]);
- }
+ return Enum.Parse(ConfigurationManager.AppSettings["umbracoContentXMLStorage"]);
+ }
+
+ //This one is older
if (ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLUseLocalTemp"))
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoContentXMLUseLocalTemp"])
- ? ContentXmlStorage.AspNetTemp
- : ContentXmlStorage.Default;
+ ? LocalTempStorage.AspNetTemp
+ : LocalTempStorage.Default;
}
- return ContentXmlStorage.Default;
+ return LocalTempStorage.Default;
}
}
diff --git a/src/Umbraco.Core/Configuration/ContentXmlStorage.cs b/src/Umbraco.Core/Configuration/LocalTempStorage.cs
similarity index 75%
rename from src/Umbraco.Core/Configuration/ContentXmlStorage.cs
rename to src/Umbraco.Core/Configuration/LocalTempStorage.cs
index 7cbbc70675..d41f7d1925 100644
--- a/src/Umbraco.Core/Configuration/ContentXmlStorage.cs
+++ b/src/Umbraco.Core/Configuration/LocalTempStorage.cs
@@ -1,6 +1,6 @@
namespace Umbraco.Core.Configuration
{
- internal enum ContentXmlStorage
+ internal enum LocalTempStorage
{
Default,
AspNetTemp,
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index 3bd45dabf2..43bfbc64a0 100644
--- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
- private static readonly Version Version = new Version("7.7.2");
+ private static readonly Version Version = new Version("7.7.4");
///
/// Gets the current version of Umbraco.
diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs
index d017505b4c..a58d983667 100644
--- a/src/Umbraco.Core/IO/IOHelper.cs
+++ b/src/Umbraco.Core/IO/IOHelper.cs
@@ -13,11 +13,28 @@ using Umbraco.Core.Configuration;
namespace Umbraco.Core.IO
{
public static class IOHelper
- {
+ {
+ ///
+ /// Gets or sets a value forcing Umbraco to consider it is non-hosted.
+ ///
+ /// This should always be false, unless unit testing.
+ public static bool ForceNotHosted { get; set; }
+
private static string _rootDir = "";
// static compiled regex for faster performance
private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
+
+ ///
+ /// Gets a value indicating whether Umbraco is hosted.
+ ///
+ public static bool IsHosted
+ {
+ get
+ {
+ return ForceNotHosted == false && (HttpContext.Current != null || HostingEnvironment.IsHosted);
+ }
+ }
public static char DirSepChar
{
@@ -72,14 +89,14 @@ namespace Umbraco.Core.IO
internal static string ResolveUrlsFromTextString(string text)
{
if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString)
- {
+ {
using (DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete"))
{
// find all relative urls (ie. urls that contain ~)
var tags = ResolveUrlPattern.Matches(text);
-
+
foreach (Match tag in tags)
- {
+ {
string url = "";
if (tag.Groups[1].Success)
url = tag.Groups[1].Value;
@@ -97,7 +114,8 @@ namespace Umbraco.Core.IO
public static string MapPath(string path, bool useHttpContext)
{
- if (path == null) throw new ArgumentNullException("path");
+ if (path == null) throw new ArgumentNullException("path");
+ useHttpContext = useHttpContext && IsHosted;
// Check if the path is already mapped
if ((path.Length >= 2 && path[1] == Path.VolumeSeparatorChar)
@@ -303,7 +321,7 @@ namespace Umbraco.Core.IO
var debugFolder = Path.Combine(binFolder, "debug");
if (Directory.Exists(debugFolder))
return debugFolder;
-#endif
+#endif
var releaseFolder = Path.Combine(binFolder, "release");
if (Directory.Exists(releaseFolder))
return releaseFolder;
diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs
index 437ddd3ef7..ccf8ea5b2c 100644
--- a/src/Umbraco.Core/IO/SystemFiles.cs
+++ b/src/Umbraco.Core/IO/SystemFiles.cs
@@ -73,19 +73,19 @@ namespace Umbraco.Core.IO
{
get
{
- switch (GlobalSettings.ContentCacheXmlStorageLocation)
+ switch (GlobalSettings.LocalTempStorageLocation)
{
- case ContentXmlStorage.AspNetTemp:
+ case LocalTempStorage.AspNetTemp:
return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config");
- case ContentXmlStorage.EnvironmentTemp:
+ case LocalTempStorage.EnvironmentTemp:
var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
- var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoXml",
- //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
- // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
- // utilizing an old path
+ var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData",
+ //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
+ // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
+ // utilizing an old path
appDomainHash);
return Path.Combine(cachePath, "umbraco.config");
- case ContentXmlStorage.Default:
+ case LocalTempStorage.Default:
return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config");
default:
throw new ArgumentOutOfRangeException();
diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs
index 012e9b6cc2..7042c63ec1 100644
--- a/src/Umbraco.Core/Models/Membership/UserGroup.cs
+++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Models.Membership
///
[Serializable]
[DataContract(IsReference = true)]
- internal class UserGroup : Entity, IUserGroup, IReadOnlyUserGroup
+ public class UserGroup : Entity, IUserGroup, IReadOnlyUserGroup
{
private int? _startContentId;
private int? _startMediaId;
diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs
index f90163d181..aa318de287 100644
--- a/src/Umbraco.Core/PluginManager.cs
+++ b/src/Umbraco.Core/PluginManager.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
+using System.Web;
using System.Web.Compilation;
using Umbraco.Core.Cache;
using Umbraco.Core.IO;
@@ -15,6 +16,7 @@ using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Profiling;
using Umbraco.Core.PropertyEditors;
using umbraco.interfaces;
+using Umbraco.Core.Configuration;
using File = System.IO.File;
namespace Umbraco.Core
@@ -39,7 +41,8 @@ namespace Umbraco.Core
private readonly IServiceProvider _serviceProvider;
private readonly IRuntimeCacheProvider _runtimeCache;
private readonly ProfilingLogger _logger;
- private readonly string _tempFolder;
+ private readonly Lazy _pluginListFilePath = new Lazy(GetPluginListFilePath);
+ private readonly Lazy _pluginHashFilePath = new Lazy(GetPluginHashFilePath);
private readonly object _typesLock = new object();
private readonly Dictionary _types = new Dictionary();
@@ -65,14 +68,7 @@ namespace Umbraco.Core
_serviceProvider = serviceProvider;
_runtimeCache = runtimeCache;
_logger = logger;
-
- // the temp folder where the cache file lives
- _tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
- if (Directory.Exists(_tempFolder) == false)
- Directory.CreateDirectory(_tempFolder);
-
- var pluginListFile = GetPluginListFilePath();
-
+
if (detectChanges)
{
//first check if the cached hash is string.Empty, if it is then we need
@@ -84,7 +80,8 @@ namespace Umbraco.Core
// if the hash has changed, clear out the persisted list no matter what, this will force
// rescanning of all plugin types including lazy ones.
// http://issues.umbraco.org/issue/U4-4789
- File.Delete(pluginListFile);
+ if(File.Exists(_pluginListFilePath.Value))
+ File.Delete(_pluginListFilePath.Value);
WriteCachePluginsHash();
}
@@ -94,7 +91,8 @@ namespace Umbraco.Core
// if the hash has changed, clear out the persisted list no matter what, this will force
// rescanning of all plugin types including lazy ones.
// http://issues.umbraco.org/issue/U4-4789
- File.Delete(pluginListFile);
+ if (File.Exists(_pluginListFilePath.Value))
+ File.Delete(_pluginListFilePath.Value);
// always set to true if we're not detecting (generally only for testing)
RequiresRescanning = true;
@@ -187,11 +185,10 @@ namespace Umbraco.Core
{
if (_cachedAssembliesHash != null)
return _cachedAssembliesHash;
+
+ if (File.Exists(_pluginHashFilePath.Value) == false) return string.Empty;
- var filePath = GetPluginHashFilePath();
- if (File.Exists(filePath) == false) return string.Empty;
-
- var hash = File.ReadAllText(filePath, Encoding.UTF8);
+ var hash = File.ReadAllText(_pluginHashFilePath.Value, Encoding.UTF8);
_cachedAssembliesHash = hash;
return _cachedAssembliesHash;
@@ -229,9 +226,8 @@ namespace Umbraco.Core
/// Writes the assembly hash file.
///
private void WriteCachePluginsHash()
- {
- var filePath = GetPluginHashFilePath();
- File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8);
+ {
+ File.WriteAllText(_pluginHashFilePath.Value, CurrentAssembliesHash, Encoding.UTF8);
}
///
@@ -274,7 +270,7 @@ namespace Umbraco.Core
}
}
}
- return generator.GenerateHash();
+ return generator.GenerateHash();
}
}
}
@@ -314,7 +310,7 @@ namespace Umbraco.Core
uniqInfos.Add(fileOrFolder.FullName);
generator.AddFileSystemItem(fileOrFolder);
}
- return generator.GenerateHash();
+ return generator.GenerateHash();
}
}
}
@@ -347,12 +343,11 @@ namespace Umbraco.Core
{
return ReadCache();
}
- catch
+ catch (Exception ex)
{
try
{
- var filePath = GetPluginListFilePath();
- File.Delete(filePath);
+ File.Delete(_pluginListFilePath.Value);
}
catch
{
@@ -366,12 +361,11 @@ namespace Umbraco.Core
internal Dictionary, IEnumerable> ReadCache()
{
var cache = new Dictionary, IEnumerable>();
-
- var filePath = GetPluginListFilePath();
- if (File.Exists(filePath) == false)
+
+ if (File.Exists(_pluginListFilePath.Value) == false)
return cache;
- using (var stream = GetFileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, ListFileOpenReadTimeout))
+ using (var stream = GetFileStream(_pluginListFilePath.Value, FileMode.Open, FileAccess.Read, FileShare.Read, ListFileOpenReadTimeout))
using (var reader = new StreamReader(stream))
{
while (true)
@@ -414,38 +408,86 @@ namespace Umbraco.Core
/// Generally only used for resetting cache, for example during the install process.
public void ClearPluginCache()
{
- var path = GetPluginListFilePath();
- if (File.Exists(path))
- File.Delete(path);
-
- path = GetPluginHashFilePath();
- if (File.Exists(path))
- File.Delete(path);
+ if (File.Exists(_pluginListFilePath.Value))
+ File.Delete(_pluginListFilePath.Value);
+
+ if (File.Exists(_pluginHashFilePath.Value))
+ File.Delete(_pluginHashFilePath.Value);
_runtimeCache.ClearCacheItem(CacheKey);
}
- private string GetPluginListFilePath()
+ private static string GetPluginListFilePath()
{
- var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".list";
- return Path.Combine(_tempFolder, filename);
+ string pluginListFilePath;
+ switch (GlobalSettings.LocalTempStorageLocation)
+ {
+ case LocalTempStorage.AspNetTemp:
+ pluginListFilePath = Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco-plugins.list");
+ break;
+ case LocalTempStorage.EnvironmentTemp:
+ var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
+ var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData",
+ //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
+ // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
+ // utilizing an old path
+ appDomainHash);
+ pluginListFilePath = Path.Combine(cachePath, "umbraco-plugins.list");
+ break;
+ case LocalTempStorage.Default:
+ default:
+ var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
+ pluginListFilePath = Path.Combine(tempFolder, "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".list");
+ break;
+ }
+
+ //ensure the folder exists
+ var folder = Path.GetDirectoryName(pluginListFilePath);
+ if (folder == null)
+ throw new InvalidOperationException("The folder could not be determined for the file " + pluginListFilePath);
+ if (Directory.Exists(folder) == false)
+ Directory.CreateDirectory(folder);
+
+ return pluginListFilePath;
}
- private string GetPluginHashFilePath()
+ private static string GetPluginHashFilePath()
{
- var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".hash";
- return Path.Combine(_tempFolder, filename);
+ string pluginHashFilePath;
+ switch (GlobalSettings.LocalTempStorageLocation)
+ {
+ case LocalTempStorage.AspNetTemp:
+ pluginHashFilePath = Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco-plugins.hash");
+ break;
+ case LocalTempStorage.EnvironmentTemp:
+ var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
+ var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData",
+ //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
+ // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
+ // utilizing an old path
+ appDomainHash);
+ pluginHashFilePath = Path.Combine(cachePath, "umbraco-plugins.hash");
+ break;
+ case LocalTempStorage.Default:
+ default:
+ var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
+ pluginHashFilePath = Path.Combine(tempFolder, "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".hash");
+ break;
+ }
+
+ //ensure the folder exists
+ var folder = Path.GetDirectoryName(pluginHashFilePath);
+ if (folder == null)
+ throw new InvalidOperationException("The folder could not be determined for the file " + pluginHashFilePath);
+ if (Directory.Exists(folder) == false)
+ Directory.CreateDirectory(folder);
+
+ return pluginHashFilePath;
}
internal void WriteCache()
{
- // be absolutely sure
- if (Directory.Exists(_tempFolder) == false)
- Directory.CreateDirectory(_tempFolder);
-
- var filePath = GetPluginListFilePath();
-
- using (var stream = GetFileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout))
+ using (var stream = GetFileStream(_pluginListFilePath.Value, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout))
using (var writer = new StreamWriter(stream))
{
foreach (var typeList in _types.Values)
@@ -461,8 +503,7 @@ namespace Umbraco.Core
internal void UpdateCache()
{
- // note
- // at the moment we write the cache to disk every time we update it. ideally we defer the writing
+ // TODO: at the moment we write the cache to disk every time we update it. ideally we defer the writing
// since all the updates are going to happen in a row when Umbraco starts. that being said, the
// file is small enough, so it is not a priority.
WriteCache();
@@ -475,13 +516,13 @@ namespace Umbraco.Core
while (true)
{
try
- {
+ {
return new FileStream(path, fileMode, fileAccess, fileShare);
}
- catch
+ catch (Exception ex)
{
if (--attempts == 0)
- throw;
+ throw;
LogHelper.Debug(string.Format("Attempted to get filestream for file {0} failed, {1} attempts left, pausing for {2} milliseconds", path, attempts, pauseMilliseconds));
Thread.Sleep(pauseMilliseconds);
@@ -691,7 +732,7 @@ namespace Umbraco.Core
// else proceed,
typeList = new TypeList(baseType, attributeType);
- var scan = RequiresRescanning || File.Exists(GetPluginListFilePath()) == false;
+ var scan = RequiresRescanning || File.Exists(_pluginListFilePath.Value) == false;
if (scan)
{
diff --git a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs
index d773eed2e9..0c8bec8f2e 100644
--- a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs
@@ -178,29 +178,25 @@ namespace Umbraco.Core.PropertyEditors
return result;
}
- private void ConvertItemsToJsonIfDetected(IDictionary result)
+ protected void ConvertItemsToJsonIfDetected(IDictionary result)
{
- //now we're going to try to see if any of the values are JSON, if they are we'll convert them to real JSON objects
- // so they can be consumed as real json in angular!
+ // convert values that are Json to true Json objects that can be consumed by Angular
var keys = result.Keys.ToArray();
for (var i = 0; i < keys.Length; i++)
{
- if (result[keys[i]] is string)
+ if ((result[keys[i]] is string) == false) continue;
+
+ var asString = result[keys[i]].ToString();
+ if (asString.DetectIsJson() == false) continue;
+
+ try
{
- var asString = result[keys[i]].ToString();
- if (asString.DetectIsJson())
- {
- try
- {
- var json = JsonConvert.DeserializeObject(asString);
- result[keys[i]] = json;
- }
- catch
- {
- //swallow this exception, we thought it was json but it really isn't so continue returning a string
- }
- }
+ result[keys[i]] = JsonConvert.DeserializeObject(asString);
+ }
+ catch
+ {
+ // swallow this exception, we thought it was Json but it really isn't so continue returning a string
}
}
}
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs
index 987640716b..b0d2e0809d 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs
@@ -1,13 +1,14 @@
using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.PropertyEditors.ValueConverters
{
[DefaultPropertyValueConverter]
- [PropertyValueType(typeof(string))]
- [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)]
- public class ColorPickerValueConverter : PropertyValueConverterBase
+ public class ColorPickerValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta
{
public override bool IsConverter(PublishedPropertyType propertyType)
{
@@ -18,11 +19,60 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
return false;
}
+ public Type GetPropertyValueType(PublishedPropertyType propertyType)
+ {
+ return UseLabel(propertyType) ? typeof(PickedColor) : typeof(string);
+ }
+
+ public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue)
+ {
+ return PropertyCacheLevel.Content;
+ }
+
+ private bool UseLabel(PublishedPropertyType propertyType)
+ {
+ var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId);
+ PreValue preValue;
+ return preValues.PreValuesAsDictionary.TryGetValue("useLabel", out preValue) && preValue.Value == "1";
+ }
+
public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
{
- // make sure it's a string
- return source == null ? string.Empty : source.ToString();
+ var useLabel = UseLabel(propertyType);
+
+ if (source == null) return useLabel ? null : string.Empty;
+
+ var ssource = source.ToString();
+ if (ssource.DetectIsJson())
+ {
+ try
+ {
+ var jo = JsonConvert.DeserializeObject(ssource);
+ if (useLabel) return new PickedColor(jo["value"].ToString(), jo["label"].ToString());
+ return jo["value"].ToString();
+ }
+ catch { /* not json finally */ }
+ }
+
+ if (useLabel) return new PickedColor(ssource, ssource);
+ return ssource;
+ }
+
+ public class PickedColor
+ {
+ public PickedColor(string color, string label)
+ {
+ Color = color;
+ Label = label;
+ }
+
+ public string Color { get; private set; }
+ public string Label { get; private set; }
+
+ public override string ToString()
+ {
+ return Color;
+ }
}
-
}
}
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs
index 029d1c3546..85599767d6 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs
@@ -43,8 +43,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
var gridConfig = UmbracoConfig.For.GridConfig(
ApplicationContext.Current.ProfilingLogger.Logger,
ApplicationContext.Current.ApplicationCache.RuntimeCache,
- new DirectoryInfo(HttpContext.Current.Server.MapPath(SystemDirectories.AppPlugins)),
- new DirectoryInfo(HttpContext.Current.Server.MapPath(SystemDirectories.Config)),
+ new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)),
+ new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config)),
HttpContext.Current.IsDebuggingEnabled);
var sections = GetArray(obj, "sections");
diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
index 12cb465b50..6f1fc03281 100644
--- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
+++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs
@@ -14,6 +14,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using umbraco.interfaces;
+using Umbraco.Core.Configuration;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Sync
@@ -39,6 +40,7 @@ namespace Umbraco.Core.Sync
private bool _syncing;
private bool _released;
private readonly ProfilingLogger _profilingLogger;
+ private readonly Lazy _distCacheFilePath = new Lazy(GetDistCacheFilePath);
protected DatabaseServerMessengerOptions Options { get; private set; }
protected ApplicationContext ApplicationContext { get { return _appContext; } }
@@ -460,10 +462,9 @@ namespace Umbraco.Core.Sync
///
private void ReadLastSynced()
{
- var path = SyncFilePath;
- if (File.Exists(path) == false) return;
+ if (File.Exists(_distCacheFilePath.Value) == false) return;
- var content = File.ReadAllText(path);
+ var content = File.ReadAllText(_distCacheFilePath.Value);
int last;
if (int.TryParse(content, out last))
_lastId = last;
@@ -478,7 +479,7 @@ namespace Umbraco.Core.Sync
///
private void SaveLastSynced(int id)
{
- File.WriteAllText(SyncFilePath, id.ToString(CultureInfo.InvariantCulture));
+ File.WriteAllText(_distCacheFilePath.Value, id.ToString(CultureInfo.InvariantCulture));
_lastId = id;
}
@@ -498,20 +499,40 @@ namespace Umbraco.Core.Sync
+ "/D" + AppDomain.CurrentDomain.Id // eg 22
+ "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique
- ///
- /// Gets the sync file path for the local server.
- ///
- /// The sync file path for the local server.
- private static string SyncFilePath
+ private static string GetDistCacheFilePath()
{
- get
- {
- var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/DistCache/" + NetworkHelper.FileSafeMachineName);
- if (Directory.Exists(tempFolder) == false)
- Directory.CreateDirectory(tempFolder);
+ var fileName = HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt";
- return Path.Combine(tempFolder, HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt");
+ string distCacheFilePath;
+ switch (GlobalSettings.LocalTempStorageLocation)
+ {
+ case LocalTempStorage.AspNetTemp:
+ distCacheFilePath = Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData", fileName);
+ break;
+ case LocalTempStorage.EnvironmentTemp:
+ var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
+ var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData",
+ //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
+ // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
+ // utilizing an old path
+ appDomainHash);
+ distCacheFilePath = Path.Combine(cachePath, fileName);
+ break;
+ case LocalTempStorage.Default:
+ default:
+ var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/DistCache");
+ distCacheFilePath = Path.Combine(tempFolder, fileName);
+ break;
}
+
+ //ensure the folder exists
+ var folder = Path.GetDirectoryName(distCacheFilePath);
+ if (folder == null)
+ throw new InvalidOperationException("The folder could not be determined for the file " + distCacheFilePath);
+ if (Directory.Exists(folder) == false)
+ Directory.CreateDirectory(folder);
+
+ return distCacheFilePath;
}
#endregion
diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs
index ab2b24392e..16dbd49e73 100644
--- a/src/Umbraco.Core/TypeFinder.cs
+++ b/src/Umbraco.Core/TypeFinder.cs
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
-using System.Web;
using System.Web.Compilation;
-using System.Web.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -21,6 +20,38 @@ namespace Umbraco.Core
{
private static volatile HashSet _localFilteredAssemblyCache;
private static readonly object LocalFilteredAssemblyCacheLocker = new object();
+ private static readonly List NotifiedLoadExceptionAssemblies = new List();
+ private static string[] _assembliesAcceptingLoadExceptions;
+
+ private static string[] AssembliesAcceptingLoadExceptions
+ {
+ get
+ {
+ if (_assembliesAcceptingLoadExceptions != null)
+ return _assembliesAcceptingLoadExceptions;
+
+ var s = ConfigurationManager.AppSettings["Umbraco.AssembliesAcceptingLoadExceptions"];
+ return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s)
+ ? new string[0]
+ : s.Split(',').Select(x => x.Trim()).ToArray();
+ }
+ }
+
+ private static bool AcceptsLoadExceptions(Assembly a)
+ {
+ if (AssembliesAcceptingLoadExceptions.Length == 0)
+ return false;
+ if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*")
+ return true;
+ var name = a.GetName().Name; // simple name of the assembly
+ return AssembliesAcceptingLoadExceptions.Any(pattern =>
+ {
+ if (pattern.Length > name.Length) return false; // pattern longer than name
+ if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical
+ if (pattern[pattern.Length] != '.') return false; // pattern is shorter than name, must end with dot
+ return name.StartsWith(pattern); // and name must start with pattern
+ });
+ }
///
/// lazily load a reference to all assemblies and only local assemblies.
@@ -45,7 +76,7 @@ namespace Umbraco.Core
HashSet assemblies = null;
try
{
- var isHosted = HttpContext.Current != null || HostingEnvironment.IsHosted;
+ var isHosted = IOHelper.IsHosted;
try
{
@@ -528,9 +559,22 @@ namespace Umbraco.Core
AppendCouldNotLoad(sb, a, getAll);
foreach (var loaderException in rex.LoaderExceptions.WhereNotNull())
AppendLoaderException(sb, loaderException);
+
+ var ex = new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString());
- // rethrow with new message
- throw new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString());
+ // rethrow with new message, unless accepted
+ if (AcceptsLoadExceptions(a) == false) throw ex;
+
+ // log a warning, and return what we can
+ lock (NotifiedLoadExceptionAssemblies)
+ {
+ if (NotifiedLoadExceptionAssemblies.Contains(a.FullName) == false)
+ {
+ NotifiedLoadExceptionAssemblies.Add(a.FullName);
+ LogHelper.WarnWithException(typeof(TypeFinder), "Could not load all types from " + a.GetName().Name + ".", ex);
+ }
+ }
+ return rex.Types.WhereNotNull().ToArray();
}
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index c9f3259181..5b49e40c47 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -198,7 +198,7 @@
-
+
diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs
index 4244188697..aa9fc35524 100644
--- a/src/Umbraco.Tests/Models/ContentTests.cs
+++ b/src/Umbraco.Tests/Models/ContentTests.cs
@@ -20,7 +20,7 @@ using Umbraco.Tests.TestHelpers.Entities;
namespace Umbraco.Tests.Models
{
[TestFixture]
- public class ContentTests : BaseUmbracoConfigurationTest
+ public class ContentTests : BaseUmbracoApplicationTest
{
[SetUp]
public void Init()
diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs
index 6db3ee793b..6bc45914a0 100644
--- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs
+++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs
@@ -388,13 +388,12 @@ namespace Umbraco.Tests.TestHelpers
return ctx;
}
- protected FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null)
+ protected virtual FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null)
{
var factory = routeData != null
? new FakeHttpContextFactory(url, routeData)
: new FakeHttpContextFactory(url);
-
//set the state helper
StateHelper.HttpContext = factory.HttpContext;
diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
index 49895ed453..6444638928 100644
--- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
+++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
@@ -118,25 +118,12 @@ namespace Umbraco.Tests.TestHelpers
});
}
- ///
- /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan
- /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs
- /// to be set to true if the SetupPluginManager has been overridden.
- ///
- protected virtual bool PluginManagerResetRequired
- {
- get { return false; }
- }
-
///
/// Inheritors can resset the plugin manager if they choose to on teardown
///
protected virtual void ResetPluginManager()
{
- if (PluginManagerResetRequired)
- {
- PluginManager.Current = null;
- }
+ PluginManager.Current = null;
}
protected virtual CacheHelper CreateCacheHelper()
@@ -185,26 +172,23 @@ namespace Umbraco.Tests.TestHelpers
///
protected virtual void SetupPluginManager()
{
- if (PluginManager.Current == null || PluginManagerResetRequired)
+ PluginManager.Current = new PluginManager(
+ new ActivatorServiceProvider(),
+ CacheHelper.RuntimeCache, ProfilingLogger, false)
{
- PluginManager.Current = new PluginManager(
- new ActivatorServiceProvider(),
- CacheHelper.RuntimeCache, ProfilingLogger, false)
+ AssembliesToScan = new[]
{
- AssembliesToScan = new[]
- {
- Assembly.Load("Umbraco.Core"),
- Assembly.Load("umbraco"),
- Assembly.Load("Umbraco.Tests"),
- Assembly.Load("businesslogic"),
- Assembly.Load("cms"),
- Assembly.Load("controls"),
- Assembly.Load("umbraco.editorControls"),
- Assembly.Load("umbraco.MacroEngines"),
- Assembly.Load("umbraco.providers"),
- }
- };
- }
+ Assembly.Load("Umbraco.Core"),
+ Assembly.Load("umbraco"),
+ Assembly.Load("Umbraco.Tests"),
+ Assembly.Load("businesslogic"),
+ Assembly.Load("cms"),
+ Assembly.Load("controls"),
+ Assembly.Load("umbraco.editorControls"),
+ Assembly.Load("umbraco.MacroEngines"),
+ Assembly.Load("umbraco.providers"),
+ }
+ };
}
///
diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs
index 75f4536fd4..82608a5513 100644
--- a/src/Umbraco.Tests/UdiTests.cs
+++ b/src/Umbraco.Tests/UdiTests.cs
@@ -7,11 +7,12 @@ using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Deploy;
using Umbraco.Core.Serialization;
+using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests
{
[TestFixture]
- public class UdiTests
+ public class UdiTests : BaseUmbracoApplicationTest
{
[Test]
public void StringUdiCtorTest()
diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs
index c84d578d14..cdca8645f3 100644
--- a/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs
+++ b/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs
@@ -22,5 +22,40 @@ namespace Umbraco.Tests.Web.Mvc
var expected = "
hello world
hello world hello world hello world hello world
";
Assert.AreEqual(expected, output);
}
+
+ [Test]
+ public void TruncateWithElipsis()
+ {
+ var output = _htmlStringUtilities.Truncate("hello world", 5, true, false).ToString();
+ var expected = "hello…";
+ Assert.AreEqual(expected, output);
+ }
+
+ [Test]
+ public void TruncateWithoutElipsis()
+ {
+ var output = _htmlStringUtilities.Truncate("hello world", 5, false, false).ToString();
+ var expected = "hello";
+ Assert.AreEqual(expected, output);
+ }
+
+ [Test]
+ public void TruncateShorterWordThanHellip()
+ {
+ //http://issues.umbraco.org/issue/U4-10478
+ var output = _htmlStringUtilities.Truncate("hi", 5, true, false).ToString();
+ var expected = "hi";
+ Assert.AreEqual(expected, output);
+ }
+
+ [Test]
+ public void TruncateAndRemoveSpaceBetweenHellipAndWord()
+ {
+ var output = _htmlStringUtilities.Truncate("hello world", 6 /* hello plus space */, true, false).ToString();
+ var expected = "hello…";
+ Assert.AreEqual(expected, output);
+ }
+
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js
index d23fdcbf84..cc45df2ce2 100644
--- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js
@@ -7,12 +7,22 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider',
.controller("Umbraco.canvasdesignerController", function ($scope, $http, $window, $timeout, $location, dialogService) {
+ var isInit = $location.search().init;
+ if (isInit === "true") {
+ //do not continue, this is the first load of this new window, if this is passed in it means it's been
+ //initialized by the content editor and then the content editor will actually re-load this window without
+ //this flag. This is a required trick to get around chrome popup mgr. We don't want to double load preview.aspx
+ //since that will double prepare the preview documents
+ return;
+ }
+
$scope.isOpen = false;
$scope.frameLoaded = false;
$scope.enableCanvasdesigner = 0;
$scope.googleFontFamilies = {};
- $scope.pageId = $location.search().id;
- $scope.pageUrl = "../dialogs/Preview.aspx?id=" + $location.search().id;
+ var pageId = $location.search().id;
+ $scope.pageId = pageId;
+ $scope.pageUrl = "../dialogs/Preview.aspx?id=" + pageId;
$scope.valueAreLoaded = false;
$scope.devices = [
{ name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" },
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
index a0218c266a..402885a1dc 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js
@@ -230,9 +230,9 @@
if (!$scope.busy) {
// Chromes popup blocker will kick in if a window is opened
- // outwith the initial scoped request. This trick will fix that.
+ // without the initial scoped request. This trick will fix that.
//
- var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview');
+ var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview');
// Build the correct path so both /#/ and #/ work.
var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id;
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js
index 7fad3e8a74..7dd2f0d7a3 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js
@@ -66,9 +66,13 @@ Use this directive to render an avatar.
function getNameInitials(name) {
if(name) {
- var initials = name.match(/\b\w/g) || [];
- initials = ((initials.shift() || '') + (initials.pop() || '')).toUpperCase();
- return initials;
+ var names = name.split(' '),
+ initials = names[0].substring(0, 1);
+
+ if (names.length > 1) {
+ initials += names[names.length - 1].substring(0, 1);
+ }
+ return initials.toUpperCase();
}
return null;
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js
new file mode 100644
index 0000000000..d75b9e2de0
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js
@@ -0,0 +1,26 @@
+(function () {
+ 'use strict';
+
+ // comes from https://codepen.io/jakob-e/pen/eNBQaP
+ // works fine with Angular 1.6.5 - alas not with 1.1.5 - binding issue
+
+ function PasswordToggleDirective($compile) {
+
+ var directive = {
+ restrict: 'A',
+ scope: {},
+ link: function(scope, elem, attrs) {
+ scope.tgl = function () { elem.attr("type", (elem.attr("type") === "text" ? "password" : "text")); }
+ var lnk = angular.element("Toggle");
+ $compile(lnk)(scope);
+ elem.wrap("
").after(lnk);
+ }
+ };
+
+ return directive;
+
+ }
+
+ angular.module('umbraco.directives').directive('umbPasswordToggle', PasswordToggleDirective);
+
+})();
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js
index a84be208ba..16330f5493 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js
@@ -2,12 +2,12 @@
* @ngdoc service
* @name umbraco.services.assetsService
*
- * @requires $q
+ * @requires $q
* @requires angularHelper
- *
+ *
* @description
* Promise-based utillity service to lazy-load client-side dependencies inside angular controllers.
- *
+ *
* ##usage
* To use, simply inject the assetsService into any controller that needs it, and make
* sure the umbraco.services module is accesible - which it should be by default.
@@ -18,7 +18,7 @@
* //this code executes when the dependencies are done loading
* });
* });
- *
+ *
*
* You can also load individual files, which gives you greater control over what attibutes are passed to the file, as well as timeout
*
@@ -38,13 +38,14 @@
* //loadcss cannot determine when the css is done loading, so this will trigger instantly
* });
* });
- *
+ *
*/
angular.module('umbraco.services')
.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http) {
var initAssetsLoaded = false;
- var appendRnd = function (url) {
+
+ function appendRnd (url) {
//if we don't have a global umbraco obj yet, the app is bootstrapping
if (!Umbraco.Sys.ServerVariables.application) {
return url;
@@ -77,7 +78,8 @@ angular.module('umbraco.services')
return this.loadedAssets[path];
}
},
- /**
+
+ /**
Internal method. This is called when the application is loading and the user is already authenticated, or once the user is authenticated.
There's a few assets the need to be loaded for the application to function but these assets require authentication to load.
*/
@@ -108,10 +110,10 @@ angular.module('umbraco.services')
*
* @description
* Injects a file as a stylesheet into the document head
- *
+ *
* @param {String} path path to the css file to load
* @param {Scope} scope optional scope to pass into the loader
- * @param {Object} keyvalue collection of attributes to pass to the stylesheet element
+ * @param {Object} keyvalue collection of attributes to pass to the stylesheet element
* @param {Number} timeout in milliseconds
* @returns {Promise} Promise object which resolves when the file has loaded
*/
@@ -149,10 +151,10 @@ angular.module('umbraco.services')
*
* @description
* Injects a file as a javascript into the document
- *
+ *
* @param {String} path path to the js file to load
* @param {Scope} scope optional scope to pass into the loader
- * @param {Object} keyvalue collection of attributes to pass to the script element
+ * @param {Object} keyvalue collection of attributes to pass to the script element
* @param {Number} timeout in milliseconds
* @returns {Promise} Promise object which resolves when the file has loaded
*/
@@ -192,8 +194,8 @@ angular.module('umbraco.services')
* @methodOf umbraco.services.assetsService
*
* @description
- * Injects a collection of files, this can be ONLY js files
- *
+ * Injects a collection of css and js files
+ *
*
* @param {Array} pathArray string array of paths to the files to load
* @param {Scope} scope optional scope to pass into the loader
@@ -206,61 +208,72 @@ angular.module('umbraco.services')
throw "pathArray must be an array";
}
+ // Check to see if there's anything to load, resolve promise if not
var nonEmpty = _.reject(pathArray, function (item) {
return item === undefined || item === "";
- });
+ });
-
- //don't load anything if there's nothing to load
- if (nonEmpty.length > 0) {
- var promises = [];
- var assets = [];
-
- //compile a list of promises
- //blocking
- _.each(nonEmpty, function (path) {
-
- path = convertVirtualPath(path);
-
- var asset = service._getAssetPromise(path);
- //if not previously loaded, add to list of promises
- if (asset.state !== "loaded") {
- if (asset.state === "new") {
- asset.state = "loading";
- assets.push(asset);
- }
-
- //we need to always push to the promises collection to monitor correct
- //execution
- promises.push(asset.deferred.promise);
- }
- });
-
-
- //gives a central monitoring of all assets to load
- promise = $q.all(promises);
-
- _.each(assets, function (asset) {
- LazyLoad.js(appendRnd(asset.path), function () {
- asset.state = "loaded";
- if (!scope) {
- asset.deferred.resolve(true);
- }
- else {
- angularHelper.safeApply(scope, function () {
- asset.deferred.resolve(true);
- });
- }
- });
- });
- }
- else {
- //return and resolve
+ if (nonEmpty.length === 0) {
var deferred = $q.defer();
promise = deferred.promise;
deferred.resolve(true);
+ return promise;
}
+ //compile a list of promises
+ //blocking
+ var promises = [];
+ var assets = [];
+ _.each(nonEmpty, function (path) {
+ path = convertVirtualPath(path);
+ var asset = service._getAssetPromise(path);
+ //if not previously loaded, add to list of promises
+ if (asset.state !== "loaded") {
+ if (asset.state === "new") {
+ asset.state = "loading";
+ assets.push(asset);
+ }
+
+ //we need to always push to the promises collection to monitor correct
+ //execution
+ promises.push(asset.deferred.promise);
+ }
+ });
+
+ //gives a central monitoring of all assets to load
+ promise = $q.all(promises);
+
+ // Split into css and js asset arrays, and use LazyLoad on each array
+ var cssAssets = _.filter(assets,
+ function (asset) {
+ return asset.path.match(/(\.css$|\.css\?)/ig);
+ });
+ var jsAssets = _.filter(assets,
+ function (asset) {
+ return asset.path.match(/(\.js$|\.js\?)/ig);
+ });
+
+ function assetLoaded(asset) {
+ asset.state = "loaded";
+ if (!scope) {
+ asset.deferred.resolve(true);
+ return;
+ }
+ angularHelper.safeApply(scope,
+ function () {
+ asset.deferred.resolve(true);
+ });
+ }
+
+ if (cssAssets.length > 0) {
+ var cssPaths = _.map(cssAssets, function (asset) { return appendRnd(asset.path) });
+ LazyLoad.css(cssPaths, function() { _.each(cssAssets, assetLoaded); });
+ }
+
+ if (jsAssets.length > 0) {
+ var jsPaths = _.map(jsAssets, function (asset) { return appendRnd(asset.path) });
+ LazyLoad.js(jsPaths, function () { _.each(jsAssets, assetLoaded); });
+ }
return promise;
}
diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less
index e76ffc682c..1eaf285119 100644
--- a/src/Umbraco.Web.UI.Client/src/less/buttons.less
+++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less
@@ -261,6 +261,9 @@ input[type="submit"].btn {
*padding-top: 1px;
*padding-bottom: 1px;
}
+
+ // Safari defaults to 1px for input. Ref U4-7721.
+ margin: 0px;
}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less
index d25744a108..47ed97fde2 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less
@@ -35,3 +35,10 @@
margin-right: 5px;
color: @gray-7;
}
+
+input.umb-breadcrumbs__add-ancestor {
+ height: 25px;
+ margin-top: -2px;
+ margin-left: 3px;
+ width: 100px;
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less
index 3551d9d31a..2e5d1ef77d 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less
@@ -636,9 +636,19 @@
clear: both;
}
-.umb-grid .mce-btn button {
- padding: 8px 6px;
- line-height: inherit;
+.umb-grid .mce-btn {
+ button {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ padding-left: 6px;
+ line-height: inherit;
+ }
+
+ &:not(.mce-menubtn) {
+ button {
+ padding-right: 6px;
+ }
+ }
}
.umb-grid .mce-toolbar {
diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less
index 924f9b20bb..2fe214bf07 100644
--- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less
+++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less
@@ -66,7 +66,7 @@
margin-bottom: auto;
}
-.login-overlay .form input[type="text"],
+.login-overlay .form input[type="text"],
.login-overlay .form input[type="password"],
.login-overlay .form input[type="email"] {
height: 36px;
@@ -114,8 +114,44 @@
line-height: 36px;
}
-.login-overlay .text-error,
-.login-overlay .text-info
+.login-overlay .text-error,
+.login-overlay .text-info
{
font-weight:bold;
-}
+}
+
+.password-toggle {
+ position: relative;
+ display: block;
+ user-select: none;
+
+ input::-ms-clear, input::-ms-reveal {
+ display: none;
+ }
+
+ a {
+ opacity: .5;
+ cursor: pointer;
+ display: inline-block;
+ position: absolute;
+ height: 1px;
+ width: 45px;
+ height: 75%;
+ font-size: 0;
+ background-repeat: no-repeat;
+ background-size: 50%;
+ background-position: center;
+ top: 0;
+ margin-left: -45px;
+ z-index: 1;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ [type="text"] + a {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M29.6.4C29 0 28 0 27.4.4L21 6.8c-1.4-.5-3-.8-5-.8C9 6 3 10 0 16c1.3 2.6 3 4.8 5.4 6.5l-5 5c-.5.5-.5 1.5 0 2 .3.4.7.5 1 .5s1 0 1.2-.4l27-27C30 2 30 1 29.6.4zM13 10c1.3 0 2.4 1 2.8 2L12 15.8c-1-.4-2-1.5-2-2.8 0-1.7 1.3-3 3-3zm-9.6 6c1.2-2 2.8-3.5 4.7-4.7l.7-.2c-.4 1-.6 2-.6 3 0 1.8.6 3.4 1.6 4.7l-2 2c-1.6-1.2-3-2.7-4-4.4zM24 13.8c0-.8 0-1.7-.4-2.4l-10 10c.7.3 1.6.4 2.4.4 4.4 0 8-3.6 8-8z'/%3E%3Cpath fill='%23444' d='M26 9l-2.2 2.2c2 1.3 3.6 3 4.8 4.8-1.2 2-2.8 3.5-4.7 4.7-2.7 1.5-5.4 2.3-8 2.3-1.4 0-2.6 0-3.8-.4L10 25c2 .6 4 1 6 1 7 0 13-4 16-10-1.4-2.8-3.5-5.2-6-7z'/%3E%3C/svg%3E");
+ }
+
+ [type="password"] + a {
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M16 6C9 6 3 10 0 16c3 6 9 10 16 10s13-4 16-10c-3-6-9-10-16-10zm8 5.3c1.8 1.2 3.4 2.8 4.6 4.7-1.2 2-2.8 3.5-4.7 4.7-3 1.5-6 2.3-8 2.3s-6-.8-8-2.3C6 19.5 4 18 3 16c1.5-2 3-3.5 5-4.7l.6-.2C8 12 8 13 8 14c0 4.5 3.5 8 8 8s8-3.5 8-8c0-1-.3-2-.6-2.6l.4.3zM16 13c0 1.7-1.3 3-3 3s-3-1.3-3-3 1.3-3 3-3 3 1.3 3 3z'/%3E%3C/svg%3E");
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
index 8b10b2f91b..ffaa1a6a92 100644
--- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less
+++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less
@@ -109,16 +109,48 @@ ul.color-picker li a {
}
/* pre-value editor */
-
+/*.control-group.color-picker-preval:before {
+ content: "";
+ display: inline-block;
+ vertical-align: middle;
+ height: 100%;
+}*/
+
+/*.control-group.color-picker-preval div.thumbnail {
+ display: inline-block;
+ vertical-align: middle;
+}*/
+.control-group.color-picker-preval div.color-picker-prediv {
+ display: inline-block;
+ width: 60%;
+}
+
.control-group.color-picker-preval pre {
display: inline;
margin-right: 20px;
margin-left: 10px;
+ width: 50%;
+ white-space: nowrap;
+ overflow: hidden;
+ margin-bottom: 0;
+ vertical-align: middle;
}
+.control-group.color-picker-preval btn {
+ //vertical-align: middle;
+}
+
+.control-group.color-picker-preval input[type="text"] {
+ min-width: 40%;
+ width: 40%;
+ display: inline-block;
+ margin-right: 20px;
+ margin-top: 1px;
+}
+
.control-group.color-picker-preval label {
- border:solid @white 1px;
- padding:6px;
+ border: solid @white 1px;
+ padding: 6px;
}
@@ -126,21 +158,21 @@ ul.color-picker li a {
// Media picker
// --------------------------------------------------
.umb-mediapicker .add-link {
- display: inline-block;
- height: 120px;
- width: 120px;
- text-align: center;
- color: @gray-8;
- border: 2px @gray-8 dashed;
- line-height: 120px;
- text-decoration: none;
+ display: flex;
+ justify-content:center;
+ align-items:center;
+ width: 120px;
+ text-align: center;
+ color: @gray-8;
+ border: 2px @gray-8 dashed;
+ text-decoration: none;
- transition: all 150ms ease-in-out;
+ transition: all 150ms ease-in-out;
- &:hover {
- color: @turquoise-d1;
- border-color: @turquoise;
- }
+ &:hover {
+ color: @turquoise-d1;
+ border-color: @turquoise;
+ }
}
.umb-mediapicker .picked-image {
@@ -165,6 +197,10 @@ ul.color-picker li a {
text-decoration: none;
}
+.umb-mediapicker .add-link-square {
+ height: 120px;
+}
+
.umb-thumbnails{
@@ -207,11 +243,10 @@ ul.color-picker li a {
.umb-mediapicker .umb-sortable-thumbnails li {
flex-direction: column;
- margin: 0;
+ margin: 0 5px 0 0;
padding: 5px;
}
-
.umb-sortable-thumbnails li:hover a {
display: flex;
justify-content: center;
@@ -219,16 +254,20 @@ ul.color-picker li a {
}
.umb-sortable-thumbnails li img {
- max-width:100%;
- max-height:100%;
- margin:auto;
- display:block;
- background-image: url(../img/checkered-background.png);
+ max-width:100%;
+ max-height:100%;
+ margin:auto;
+ display:block;
+ background-image: url(../img/checkered-background.png);
}
-.umb-sortable-thumbnails li img.noScale{
- max-width: none !important;
- max-height: none !important;
+.umb-sortable-thumbnails li img.trashed {
+ opacity:0.3;
+}
+
+.umb-sortable-thumbnails li img.noScale {
+ max-width: none !important;
+ max-height: none !important;
}
.umb-sortable-thumbnails .umb-icon-holder {
@@ -254,8 +293,8 @@ ul.color-picker li a {
}
.umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__actions {
- opacity: 1;
- visibility: visible;
+ opacity: 1;
+ visibility: visible;
}
.umb-sortable-thumbnails .umb-sortable-thumbnails__action {
@@ -285,27 +324,27 @@ ul.color-picker li a {
// -------------------------------------------------
.umb-cropper{
- position: relative;
+ position: relative;
}
.umb-cropper img, .umb-cropper-gravity img{
- position: relative;
- max-width: 100%;
- height: auto;
- top: 0;
- left: 0;
+ position: relative;
+ max-width: 100%;
+ height: auto;
+ top: 0;
+ left: 0;
}
.umb-cropper img {
- max-width: none;
+ max-width: none;
}
.umb-cropper .overlay, .umb-cropper-gravity .overlay {
- top: 0;
- left: 0;
- cursor: move;
- z-index: @zindexCropperOverlay;
- position: absolute;
+ top: 0;
+ left: 0;
+ cursor: move;
+ z-index: @zindexCropperOverlay;
+ position: absolute;
}
.umb-cropper .viewport{
@@ -317,43 +356,43 @@ ul.color-picker li a {
}
.umb-cropper-gravity .viewport{
- overflow: hidden;
- position: relative;
- width: 100%;
- height: 100%;
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+ height: 100%;
}
.umb-cropper .viewport:after {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: @zindexCropperOverlay - 1;
- -moz-opacity: .75;
- opacity: .75;
- filter: alpha(opacity=7);
- -webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
- -moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
- box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: @zindexCropperOverlay - 1;
+ -moz-opacity: .75;
+ opacity: .75;
+ filter: alpha(opacity=7);
+ -webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
+ -moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
+ box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2);
}
.umb-cropper-gravity .overlay{
- width: 14px;
- height: 14px;
- text-align: center;
- border-radius: 20px;
- background: @turquoise;
- border: 3px solid @white;
- opacity: 0.8;
+ width: 14px;
+ height: 14px;
+ text-align: center;
+ border-radius: 20px;
+ background: @turquoise;
+ border: 3px solid @white;
+ opacity: 0.8;
}
.umb-cropper-gravity .overlay i {
- font-size: 26px;
- line-height: 26px;
- opacity: 0.8 !important;
+ font-size: 26px;
+ line-height: 26px;
+ opacity: 0.8 !important;
}
.umb-cropper .crop-container {
@@ -361,16 +400,16 @@ ul.color-picker li a {
}
.umb-cropper .crop-slider {
- padding: 10px;
- border-top: 1px solid @gray-10;
- margin-top: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-wrap: wrap;
- @media (min-width: 769px) {
+ padding: 10px;
+ border-top: 1px solid @gray-10;
+ margin-top: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+ @media (min-width: 769px) {
padding: 10px 50px 10px 50px;
- }
+ }
}
.umb-cropper .crop-slider i {
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js
index 18158a5ff2..34dbf90de6 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js
@@ -16,7 +16,11 @@
maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB",
acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes),
uploaded: false
- }
+ }
+ $scope.togglePassword = function () {
+ var elem = $("form[name='loginForm'] input[name='password']");
+ elem.attr("type", (elem.attr("type") === "text" ? "password" : "text"));
+ }
function init() {
// Check if it is a new user
@@ -48,7 +52,7 @@
]).then(function () {
$scope.inviteStep = Number(inviteVal);
-
+
});
}
}
@@ -82,7 +86,7 @@
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10);
// set percentage property on file
- $scope.avatarFile.uploadProgress = progressPercentage;
+ $scope.avatarFile.uploadProgress = progressPercentage;
}
}).success(function (data, status, headers, config) {
@@ -149,11 +153,11 @@
//error
formHelper.handleError(err);
-
+
$scope.invitedUserPasswordModel.buttonState = "error";
});
- }
+ }
};
var setFieldFocus = function (form, field) {
@@ -180,7 +184,7 @@
}
function resetInputValidation() {
- $scope.confirmPassword = "";
+ $scope.confirmPassword = "";
$scope.password = "";
$scope.login = "";
if ($scope.loginForm) {
@@ -255,7 +259,7 @@
//TODO: Do validation properly like in the invite password update
- //if the login and password are not empty we need to automatically
+ //if the login and password are not empty we need to automatically
// validate them - this is because if there are validation errors on the server
// then the user has to change both username & password to resubmit which isn't ideal,
// so if they're not empty, we'll just make sure to set them to valid.
@@ -289,7 +293,7 @@
});
//setup a watch for both of the model values changing, if they change
- // while the form is invalid, then revalidate them so that the form can
+ // while the form is invalid, then revalidate them so that the form can
// be submitted again.
$scope.loginForm.username.$viewChangeListeners.push(function () {
if ($scope.loginForm.username.$invalid) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html
index 61d04d30fb..6505af4de9 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html
@@ -24,8 +24,8 @@
Your new password cannot be blank!
- Minimum {{invitedUserPasswordModel.passwordPolicies.minPasswordLength}} characters
-
+ Minimum {{invitedUserPasswordModel.passwordPolicies.minPasswordLength}} characters
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js
index a6a2ddcbab..ccb033a57c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js
@@ -56,6 +56,46 @@ angular.module("umbraco")
$scope.target = dialogOptions.currentTarget;
}
+ function onInit() {
+ if ($scope.startNodeId !== -1) {
+ entityResource.getById($scope.startNodeId, "media")
+ .then(function (ent) {
+ $scope.startNodeId = ent.id;
+ run();
+ });
+ } else {
+ run();
+ }
+ }
+
+ function run() {
+ //default root item
+ if (!$scope.target) {
+ if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) {
+ entityResource.getById($scope.lastOpenedNode, "media")
+ .then(ensureWithinStartNode, gotoStartNode);
+ } else {
+ gotoStartNode();
+ }
+ } else {
+ //if a target is specified, go look it up - generally this target will just contain ids not the actual full
+ //media object so we need to look it up
+ var id = $scope.target.udi ? $scope.target.udi : $scope.target.id
+ var altText = $scope.target.altText;
+ mediaResource.getById(id)
+ .then(function (node) {
+ $scope.target = node;
+ if (ensureWithinStartNode(node)) {
+ selectImage(node);
+ $scope.target.url = mediaHelper.resolveFile(node);
+ $scope.target.altText = altText;
+ $scope.openDetailsDialog();
+ }
+ },
+ gotoStartNode);
+ }
+ }
+
$scope.upload = function(v) {
angular.element(".umb-file-dropzone-directive .file-select").click();
};
@@ -107,7 +147,7 @@ angular.module("umbraco")
if (folder.id > 0) {
entityResource.getAncestors(folder.id, "media")
- .then(function(anc) {
+ .then(function(anc) {
$scope.path = _.filter(anc,
function(f) {
return f.path.indexOf($scope.startNodeId) !== -1;
@@ -218,32 +258,6 @@ angular.module("umbraco")
$scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" });
}
- //default root item
- if (!$scope.target) {
- if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) {
- entityResource.getById($scope.lastOpenedNode, "media")
- .then(ensureWithinStartNode, gotoStartNode);
- } else {
- gotoStartNode();
- }
- } else {
- //if a target is specified, go look it up - generally this target will just contain ids not the actual full
- //media object so we need to look it up
- var id = $scope.target.udi ? $scope.target.udi : $scope.target.id
- var altText = $scope.target.altText;
- mediaResource.getById(id)
- .then(function(node) {
- $scope.target = node;
- if (ensureWithinStartNode(node)) {
- selectImage(node);
- $scope.target.url = mediaHelper.resolveFile(node);
- $scope.target.altText = altText;
- $scope.openDetailsDialog();
- }
- },
- gotoStartNode);
- }
-
$scope.openDetailsDialog = function() {
$scope.mediaPickerDetailsOverlay = {};
@@ -368,4 +382,7 @@ angular.module("umbraco")
}
}
}
+
+ onInit();
+
});
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html
index 8c726f4a17..73b0915161 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html
@@ -48,14 +48,14 @@
/
-
+
0;
+
+ if ($scope.isConfigured) {
+
+ for (var key in $scope.model.config.items) {
+ if (!$scope.model.config.items[key].hasOwnProperty("value"))
+ $scope.model.config.items[key] = { value: $scope.model.config.items[key], label: $scope.model.config.items[key] };
+ }
+
+ $scope.model.useLabel = isTrue($scope.model.config.useLabel);
+ initActiveColor();
+ }
+
+ $scope.toggleItem = function (color) {
+
+ var currentColor = $scope.model.value.hasOwnProperty("value")
+ ? $scope.model.value.value
+ : $scope.model.value;
+
+ var newColor;
+ if (currentColor === color.value) {
+ // deselect
+ $scope.model.value = $scope.model.useLabel ? { value: "", label: "" } : "";
+ newColor = "";
+ }
+ else {
+ // select
+ $scope.model.value = $scope.model.useLabel ? { value: color.value, label: color.label } : color.value;
+ newColor = color.value;
+ }
+
+ // this is required to re-validate
+ $scope.propertyForm.modelValue.$setViewValue(newColor);
+ };
+
// Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
- $scope.validateMandatory = function () {
+ $scope.validateMandatory = function () {
+ var isValid = !$scope.model.validation.mandatory || (
+ $scope.model.value != null
+ && $scope.model.value != ""
+ && (!$scope.model.value.hasOwnProperty("value") || $scope.model.value.value !== "")
+ );
return {
- isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value != ""),
+ isValid: isValid,
errorMsg: "Value cannot be empty",
errorKey: "required"
};
}
- $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
+ $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
+
+ // A color is active if it matches the value and label of the model.
+ // If the model doesn't store the label, ignore the label during the comparison.
+ $scope.isActiveColor = function (color) {
+
+ // no value
+ if (!$scope.model.value)
+ return false;
+
+ // Complex color (value and label)?
+ if (!$scope.model.value.hasOwnProperty("value"))
+ return $scope.model.value === color.value;
+
+ return $scope.model.value.value === color.value && $scope.model.value.label === color.label;
+ };
+
+ // Finds the color best matching the model's color,
+ // and sets the model color to that one. This is useful when
+ // either the value or label was changed on the data type.
+ function initActiveColor() {
+
+ // no value
+ if (!$scope.model.value)
+ return;
+
+ // Complex color (value and label)?
+ if (!$scope.model.value.hasOwnProperty("value"))
+ return;
+
+ var modelColor = $scope.model.value.value;
+ var modelLabel = $scope.model.value.label;
+
+ // Check for a full match or partial match.
+ var foundItem = null;
+
+ // Look for a fully matching color.
+ for (var key in $scope.model.config.items) {
+ var item = $scope.model.config.items[key];
+ if (item.value == modelColor && item.label == modelLabel) {
+ foundItem = item;
+ break;
+ }
+ }
+
+ // Look for a color with a matching value.
+ if (!foundItem) {
+ for (var key in $scope.model.config.items) {
+ var item = $scope.model.config.items[key];
+ if (item.value == modelColor) {
+ foundItem = item;
+ break;
+ }
+ }
+ }
+
+ // Look for a color with a matching label.
+ if (!foundItem) {
+ for (var key in $scope.model.config.items) {
+ var item = $scope.model.config.items[key];
+ if (item.label == modelLabel) {
+ foundItem = item;
+ break;
+ }
+ }
+ }
+
+ // If a match was found, set it as the active color.
+ if (foundItem) {
+ $scope.model.value.value = foundItem.value;
+ $scope.model.value.label = foundItem.label;
+ }
+ }
+
+ // figures out if a value is trueish enough
+ function isTrue(bool) {
+ return !!bool && bool !== "0" && angular.lowercase(bool) !== "false";
+ }
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.ColorPickerController", ColorPickerController);
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html
index a493fffdd8..46b624adcc 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html
@@ -5,10 +5,10 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
index 8a7b20498d..91777bfc3e 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
@@ -1,7 +1,7 @@
//this controller simply tells the dialogs service to open a mediaPicker window
//with a specified callback, this callback will receive an object with a selection on it
angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController",
- function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location) {
+ function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) {
//check the pre-values for multi-picker
var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
@@ -26,17 +26,47 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
// the mediaResource has server side auth configured for which the user must have
// access to the media section, if they don't they'll get auth errors. The entityResource
// acts differently in that it allows access if the user has access to any of the apps that
- // might require it's use. Therefore we need to use the metatData property to get at the thumbnail
+ // might require it's use. Therefore we need to use the metaData property to get at the thumbnail
// value.
- entityResource.getByIds(ids, "Media").then(function (medias) {
+ entityResource.getByIds(ids, "Media").then(function(medias) {
- _.each(medias, function (media, i) {
+ // The service only returns item results for ids that exist (deleted items are silently ignored).
+ // This results in the picked items value to be set to contain only ids of picked items that could actually be found.
+ // Since a referenced item could potentially be restored later on, instead of changing the selected values here based
+ // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently
+ // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending
+ // on whether it is simply resaved or not.
+ // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders
+ // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before.
+
+ medias = _.map(ids,
+ function(id) {
+ var found = _.find(medias,
+ function(m) {
+ // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and
+ // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString()
+ // compares and be completely sure it works.
+ return m.udi.toString() === id.toString() || m.id.toString() === id.toString();
+ });
+ if (found) {
+ return found;
+ } else {
+ return {
+ name: localizationService.dictionary.mediaPicker_deletedItem,
+ id: $scope.model.config.idType !== "udi" ? id : null,
+ udi: $scope.model.config.idType === "udi" ? id : null,
+ icon: "icon-picture",
+ thumbnail: null,
+ trashed: true
+ };
+ }
+ });
- //only show non-trashed items
- if (media.parentId >= -1) {
-
- if (!media.thumbnail) {
+ _.each(medias,
+ function(media, i) {
+ // if there is no thumbnail, try getting one if the media is not a placeholder item
+ if (!media.thumbnail && media.id && media.metaData) {
media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
}
@@ -44,12 +74,10 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
if ($scope.model.config.idType === "udi") {
$scope.ids.push(media.udi);
- }
- else {
+ } else {
$scope.ids.push(media.id);
}
- }
- });
+ });
$scope.sync();
});
@@ -82,8 +110,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
submit: function(model) {
_.each(model.selectedImages, function(media, i) {
-
- if (!media.thumbnail) {
+ // if there is no thumbnail, try getting one if the media is not a placeholder item
+ if (!media.thumbnail && media.id && media.metaData) {
media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
}
@@ -101,10 +129,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
$scope.mediaPickerOverlay.show = false;
$scope.mediaPickerOverlay = null;
-
}
};
-
};
$scope.sortableOptions = {
@@ -142,5 +168,4 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
//update the display val again if it has changed from the server
setupViewModel();
};
-
});
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html
index f3aab992dd..3ef1430cd3 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html
@@ -1,47 +1,47 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js
index 702d19a509..65a62f599c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js
@@ -76,6 +76,7 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
icon.isCustom = false;
break;
case "styleselect":
+ case "fontsizeselect":
icon.name = "icon-list";
icon.isCustom = true;
break;
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index da10edda09..a915fc726c 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -1027,9 +1027,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\"
TrueTrue
- 7720
+ 7740/
- http://localhost:7720
+ http://localhost:7740FalseFalse
diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml
index 8b189ae1a0..1437d4f14b 100644
--- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml
+++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml
@@ -60,21 +60,29 @@
JObject cfg = contentItem.config;
if(cfg != null)
- foreach (JProperty property in cfg.Properties()) {
- attrs.Add(property.Name + "='" + property.Value.ToString() + "'");
+ foreach (JProperty property in cfg.Properties())
+ {
+ var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString());
+ attrs.Add(property.Name + "=\"" + propertyValue + "\"");
}
-
+
JObject style = contentItem.styles;
- if (style != null) {
- var cssVals = new List();
- foreach (JProperty property in style.Properties())
- cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
+ if (style != null) {
+ var cssVals = new List();
+ foreach (JProperty property in style.Properties())
+ {
+ var propertyValue = property.Value.ToString();
+ if (string.IsNullOrWhiteSpace(propertyValue) == false)
+ {
+ cssVals.Add(property.Name + ":" + propertyValue + ";");
+ }
+ }
- if (cssVals.Any())
- attrs.Add("style='" + string.Join(" ", cssVals) + "'");
+ if (cssVals.Any())
+ attrs.Add("style=\"" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "\"");
}
-
+
return new MvcHtmlString(string.Join(" ", attrs));
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml
index e672aa2a11..7b4f602b26 100644
--- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml
+++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml
@@ -60,21 +60,29 @@
JObject cfg = contentItem.config;
if(cfg != null)
- foreach (JProperty property in cfg.Properties()) {
- attrs.Add(property.Name + "='" + property.Value.ToString() + "'");
+ foreach (JProperty property in cfg.Properties())
+ {
+ var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString());
+ attrs.Add(property.Name + "=\"" + propertyValue + "\"");
}
-
+
JObject style = contentItem.styles;
- if (style != null) {
- var cssVals = new List();
- foreach (JProperty property in style.Properties())
- cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
+ if (style != null) {
+ var cssVals = new List();
+ foreach (JProperty property in style.Properties())
+ {
+ var propertyValue = property.Value.ToString();
+ if (string.IsNullOrWhiteSpace(propertyValue) == false)
+ {
+ cssVals.Add(property.Name + ":" + propertyValue + ";");
+ }
+ }
- if (cssVals.Any())
- attrs.Add("style='" + string.Join(" ", cssVals) + "'");
+ if (cssVals.Any())
+ attrs.Add("style=\"" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "\"");
}
-
+
return new MvcHtmlString(string.Join(" ", attrs));
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml
index 0cac4eb1ff..4cf2e73658 100644
--- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml
+++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml
@@ -4,9 +4,13 @@
@if (Model.editor.config.markup != null)
{
string markup = Model.editor.config.markup.ToString();
-
- markup = markup.Replace("#value#", Model.value.ToString());
- markup = markup.Replace("#style#", Model.editor.config.style.ToString());
+ var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
+ markup = markup.Replace("#value#", umbracoHelper.ReplaceLineBreaksForHtml(HttpUtility.HtmlEncode(Model.value.ToString())));
+
+ if (Model.editor.config.style != null)
+ {
+ markup = markup.Replace("#style#", Model.editor.config.style.ToString());
+ }
@Html.Raw(markup)
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
index 23895db95b..16ed8de775 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml
@@ -935,6 +935,15 @@ Mange hilsner fra Umbraco robotten
Du har ikke konfigureret nogen godkendte farver
+
+ Du har valgt et dokument som er slettet eller lagt i papirkurven
+ Du har valgt dokumenter som er slettede eller lagt i papirkurven
+
+
+ Du har valgt et medie som er slettet eller lagt i papirkurven
+ Du har valgt medier som er slettede eller lagt i papirkurven
+ Slettet medie
+
indtast eksternt linkvælg en intern side
@@ -1251,6 +1260,9 @@ Mange hilsner fra Umbraco robotten
Vælg standarder tilføjet
+ Maksimalt emner
+ Efterlad blank eller sat til 0 ubegrænset for
+
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
index 30ebc137b9..3cb67797de 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
@@ -87,14 +87,14 @@
Domain '%0%' has been updatedEdit Current Domains
-
InheritCulture
- or inherit culture from parent nodes. Will also apply
+ or inherit culture from parent nodes. Will also apply
to the current node, unless a domain below applies too.]]>
Domains
@@ -351,11 +351,11 @@
Number of columnsNumber of rows
- Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates,
+ Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates,
by referring this ID using a <asp:content /> element.]]>
- Select a placeholder id from the list below. You can only
+ Select a placeholder id from the list below. You can only
choose Id's from the current template's master.]]>
Click on the image to see full size
@@ -397,15 +397,15 @@
- %0%' below You can add additional languages under the 'languages' in the menu on the left
+ %0%' below You can add additional languages under the 'languages' in the menu on the left
]]>
Culture NameEdit the key of the dictionary item.
-
@@ -1081,6 +1081,15 @@ To manage your website, simply open the Umbraco back office and start adding con
You have not configured any approved colours
+
+ You have picked a content item currently deleted or in the recycle bin
+ You have picked content items currently deleted or in the recycle bin
+
+
+ You have picked a media item currently deleted or in the recycle bin
+ You have picked media items currently deleted or in the recycle bin
+ Deleted item
+
enter external linkchoose internal page
@@ -1401,6 +1410,8 @@ To manage your website, simply open the Umbraco back office and start adding con
Allow all editorsAllow all row configurations
+ Leave blank or set to 0 for unlimited
+ Maximum itemsSet as defaultChoose extraChoose default
@@ -1869,4 +1880,4 @@ To manage your website, simply open the Umbraco back office and start adding con
characters left
-
\ No newline at end of file
+
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
index e69f2dcaf2..dce2ef165e 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
@@ -1087,6 +1087,15 @@ To manage your website, simply open the Umbraco back office and start adding con
You have not configured any approved colors
+
+ You have picked a content item currently deleted or in the recycle bin
+ You have picked content items currently deleted or in the recycle bin
+
+
+ You have picked a media item currently deleted or in the recycle bin
+ You have picked media items currently deleted or in the recycle bin
+ Deleted item
+
enter external linkchoose internal page
@@ -1405,6 +1414,8 @@ To manage your website, simply open the Umbraco back office and start adding con
Allow all editorsAllow all row configurations
+ Maximum items
+ Leave blank or set to 0 for unlimitedSet as defaultChoose extraChoose default
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml
index 2de944a7df..99283482aa 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml
@@ -789,6 +789,9 @@
Permitir todos los controles de ediciónPermitir todas las configuraciones de fila
+ Artículos máximos
+ Laat dit leeg of is ingesteld op -1 voor onbeperkt
+ Dejar en blanco o se establece en 0 para ilimitadaCampo opcional
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml
index c480373fd6..7b170cdc41 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml
@@ -1077,7 +1077,9 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
Alle editors toelatenAlle rijconfiguraties toelaten
- Instellen als standaard
+ Maximale artikelen
+ Laat dit leeg of is ingesteld op -1 voor onbeperkt
+ Instellen als standaardKies extraKies standaardzijn toegevoegd
diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/Publish.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/Publish.aspx.cs
index 7e09d0b425..ababea628a 100644
--- a/src/Umbraco.Web.UI/umbraco/dialogs/Publish.aspx.cs
+++ b/src/Umbraco.Web.UI/umbraco/dialogs/Publish.aspx.cs
@@ -30,7 +30,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs
}
DocumentId = doc.Id;
- PageName = doc.Name;
+ PageName = Server.HtmlEncode(doc.Name);
DocumentPath = doc.Path;
}
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs
index 511b33f812..d53ee84e7c 100644
--- a/src/Umbraco.Web/Editors/ContentController.cs
+++ b/src/Umbraco.Web/Editors/ContentController.cs
@@ -643,9 +643,7 @@ namespace Umbraco.Web.Editors
ShowMessageForPublishStatus(publishStatus.Result, display);
break;
}
-
- UpdatePreviewContext(contentItem.PersistedContent.Id);
-
+
//If the item is new and the operation was cancelled, we need to return a different
// status code so the UI can handle it since it won't be able to redirect since there
// is no Id to redirect to!
@@ -875,24 +873,6 @@ namespace Umbraco.Web.Editors
}
}
- ///
- /// Checks if the user is currently in preview mode and if so will update the preview content for this item
- ///
- ///
- private void UpdatePreviewContext(int contentId)
- {
- var previewId = Request.GetPreviewCookieValue();
- if (previewId.IsNullOrWhiteSpace()) return;
- Guid id;
- if (Guid.TryParse(previewId, out id))
- {
- var d = new Document(contentId);
- var pc = new PreviewContent(UmbracoUser, id, false);
- pc.PrepareDocument(UmbracoUser, d, true);
- pc.SavePreviewSet();
- }
- }
-
///
/// Maps the dto property values to the persisted model
///
diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs
index 0b1a697eae..1364a8e2af 100644
--- a/src/Umbraco.Web/Editors/MediaController.cs
+++ b/src/Umbraco.Web/Editors/MediaController.cs
@@ -599,12 +599,14 @@ namespace Umbraco.Web.Editors
throw;
}
}
-
- [EnsureUserPermissionForMedia("folder.ParentId")]
- public MediaItemDisplay PostAddFolder(EntityBasic folder)
+
+ public MediaItemDisplay PostAddFolder(PostedFolder folder)
{
+ var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true);
+
var mediaService = ApplicationContext.Services.MediaService;
- var f = mediaService.CreateMedia(folder.Name, folder.ParentId, Constants.Conventions.MediaTypes.Folder);
+
+ var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder);
mediaService.Save(f, Security.CurrentUser.Id);
return Mapper.Map(f);
@@ -636,66 +638,15 @@ namespace Umbraco.Web.Editors
if (result.FileData.Count == 0)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
- }
-
+ }
+
//get the string json from the request
- int parentId; bool entityFound; GuidUdi parentUdi;
- string currentFolderId = result.FormData["currentFolder"];
- // test for udi
- if (GuidUdi.TryParse(currentFolderId, out parentUdi))
- {
- currentFolderId = parentUdi.Guid.ToString();
- }
-
- if (int.TryParse(currentFolderId, out parentId) == false)
- {
- // if a guid then try to look up the entity
- Guid idGuid;
- if (Guid.TryParse(currentFolderId, out idGuid))
- {
- var entity = Services.EntityService.GetByKey(idGuid);
- if (entity != null)
- {
- entityFound = true;
- parentId = entity.Id;
- }
- else
- {
- throw new EntityNotFoundException(currentFolderId, "The passed id doesn't exist");
- }
- }
- else
- {
- return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer or Guid");
- }
-
- if (entityFound == false)
- {
- return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer or Guid");
- }
- }
-
-
- //ensure the user has access to this folder by parent id!
- if (CheckPermissions(
- new Dictionary(),
- Security.CurrentUser,
- Services.MediaService,
- Services.EntityService,
- parentId) == false)
- {
- return Request.CreateResponse(
- HttpStatusCode.Forbidden,
- new SimpleNotificationModel(new Notification(
- Services.TextService.Localize("speechBubbles/operationFailedHeader"),
- Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"),
- SpeechBubbleIcon.Warning)));
- }
-
+ string currentFolderId = result.FormData["currentFolder"];
+ int parentId = GetParentIdAsInt(currentFolderId, validatePermissions: true);
+
var tempFiles = new PostedFiles();
var mediaService = ApplicationContext.Services.MediaService;
-
-
+
//in case we pass a path with a folder in it, we will create it and upload media to it.
if (result.FormData.ContainsKey("path"))
{
@@ -830,6 +781,69 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK, tempFiles);
}
+ ///
+ /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT
+ ///
+ ///
+ ///
+ /// If true, this will check if the current user has access to the resolved integer parent id
+ /// and if that check fails an unauthorized exception will occur
+ ///
+ ///
+ private int GetParentIdAsInt(string parentId, bool validatePermissions)
+ {
+ int intParentId;
+ GuidUdi parentUdi;
+
+ // test for udi
+ if (GuidUdi.TryParse(parentId, out parentUdi))
+ {
+ parentId = parentUdi.Guid.ToString();
+ }
+
+ //if it's not an INT then we'll check for GUID
+ if (int.TryParse(parentId, out intParentId) == false)
+ {
+ // if a guid then try to look up the entity
+ Guid idGuid;
+ if (Guid.TryParse(parentId, out idGuid))
+ {
+ var entity = Services.EntityService.GetByKey(idGuid);
+ if (entity != null)
+ {
+ intParentId = entity.Id;
+ }
+ else
+ {
+ throw new EntityNotFoundException(parentId, "The passed id doesn't exist");
+ }
+ }
+ else
+ {
+ throw new HttpResponseException(
+ Request.CreateValidationErrorResponse("The request was not formatted correctly, the parentId is not an integer, Guid or UDI"));
+ }
+ }
+
+ //ensure the user has access to this folder by parent id!
+ if (validatePermissions && CheckPermissions(
+ new Dictionary(),
+ Security.CurrentUser,
+ Services.MediaService,
+ Services.EntityService,
+ intParentId) == false)
+ {
+ throw new HttpResponseException(Request.CreateResponse(
+ HttpStatusCode.Forbidden,
+ new SimpleNotificationModel(new Notification(
+ Services.TextService.Localize("speechBubbles/operationFailedHeader"),
+ Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"),
+ SpeechBubbleIcon.Warning))));
+ }
+
+ return intParentId;
+ }
+
///
/// Ensures the item can be moved/copied to the new location
///
diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs
index c4ae1c4eac..2caeb012bc 100644
--- a/src/Umbraco.Web/HtmlStringUtilities.cs
+++ b/src/Umbraco.Web/HtmlStringUtilities.cs
@@ -92,6 +92,8 @@ namespace Umbraco.Web
using (var outputms = new MemoryStream())
{
+ bool lengthReached = false;
+
using (var outputtw = new StreamWriter(outputms))
{
using (var ms = new MemoryStream())
@@ -106,7 +108,6 @@ namespace Umbraco.Web
using (TextReader tr = new StreamReader(ms))
{
bool isInsideElement = false,
- lengthReached = false,
insideTagSpaceEncountered = false,
isTagClose = false;
@@ -254,10 +255,15 @@ namespace Umbraco.Web
//Check to see if there is an empty char between the hellip and the output string
//if there is, remove it
- if (string.IsNullOrWhiteSpace(firstTrim) == false)
+ if (addElipsis && lengthReached && string.IsNullOrWhiteSpace(firstTrim) == false)
{
result = firstTrim[firstTrim.Length - hellip.Length - 1] == ' ' ? firstTrim.Remove(firstTrim.Length - hellip.Length - 1, 1) : firstTrim;
}
+ else
+ {
+ result = firstTrim;
+ }
+
return new HtmlString(result);
}
}
diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs
index d0194ac009..1e59aa1c93 100644
--- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs
@@ -27,7 +27,7 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "template")]
public string TemplateAlias { get; set; }
-
+
[DataMember(Name = "allowedTemplates")]
public IDictionary AllowedTemplates { get; set; }
@@ -54,7 +54,7 @@ namespace Umbraco.Web.Models.ContentEditing
///
[DataMember(Name = "allowedActions")]
public IEnumerable AllowedActions { get; set; }
-
+
[DataMember(Name = "isBlueprint")]
public bool IsBlueprint { get; set; }
}
diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs
index 9f6e5b28da..12afceea05 100644
--- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs
@@ -2,10 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
-using System.Linq;
using System.Runtime.Serialization;
-using System.Text;
-using System.Threading.Tasks;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Models.Validation;
diff --git a/src/Umbraco.Web/Models/ContentEditing/PostedFolder.cs b/src/Umbraco.Web/Models/ContentEditing/PostedFolder.cs
new file mode 100644
index 0000000000..35cd908787
--- /dev/null
+++ b/src/Umbraco.Web/Models/ContentEditing/PostedFolder.cs
@@ -0,0 +1,17 @@
+using System.Runtime.Serialization;
+
+namespace Umbraco.Web.Models.ContentEditing
+{
+ ///
+ /// Used to create a folder with the MediaController
+ ///
+ [DataContract]
+ public class PostedFolder
+ {
+ [DataMember(Name = "parentId")]
+ public string ParentId { get; set; }
+
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs
index 9741a2cff4..107ea590c2 100644
--- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs
+++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs
@@ -43,7 +43,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(display => display.Urls,
expression => expression.MapFrom(content =>
UmbracoContext.Current == null
- ? new[] {"Cannot generate urls without a current Umbraco Context"}
+ ? new[] { "Cannot generate urls without a current Umbraco Context" }
: content.GetContentUrls(UmbracoContext.Current)))
.ForMember(display => display.Properties, expression => expression.Ignore())
.ForMember(display => display.AllowPreview, expression => expression.Ignore())
@@ -85,8 +85,8 @@ namespace Umbraco.Web.Models.Mapping
private static DateTime? GetPublishedDate(IContent content)
{
- var date = ((Content) content).PublishedDate;
- return date == default (DateTime) ? (DateTime?) null : date;
+ var date = ((Content)content).PublishedDate;
+ return date == default(DateTime) ? (DateTime?)null : date;
}
///
@@ -110,16 +110,22 @@ namespace Umbraco.Web.Models.Mapping
var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null));
display.TreeNodeUrl = url;
- }
-
+ }
+
+ //set default template if template isn't set
+ if (string.IsNullOrEmpty(display.TemplateAlias))
+ display.TemplateAlias = content.ContentType.DefaultTemplate == null
+ ? string.Empty
+ : content.ContentType.DefaultTemplate.Alias;
+
if (content.ContentType.IsContainer)
{
TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText);
- }
-
+ }
+
TabsAndPropertiesResolver.MapGenericProperties(content, display, localizedText);
}
-
+
///
/// Resolves a from the item and checks if the current user
/// has access to see this data
@@ -127,7 +133,7 @@ namespace Umbraco.Web.Models.Mapping
private class ContentTypeBasicResolver : ValueResolver
{
protected override ContentTypeBasic ResolveCore(IContent source)
- {
+ {
//TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons
//If this is a web request and there's a user signed in and the
// user has access to the settings section, we will
@@ -136,11 +142,11 @@ namespace Umbraco.Web.Models.Mapping
{
var contentTypeBasic = Mapper.Map(source.ContentType);
return contentTypeBasic;
- }
+ }
//no access
return null;
}
- }
+ }
///
/// Creates the list of action buttons allowed for this user - Publish, Send to publish, save, unpublish returned as the button's 'letter'
@@ -163,13 +169,13 @@ namespace Umbraco.Web.Models.Mapping
}
var svc = _userService.Value;
- var permissions = svc.GetPermissions(
- //TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is
- // with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null
- // refrence exception :(
- UmbracoContext.Current.Security.CurrentUser,
- // Here we need to do a special check since this could be new content, in which case we need to get the permissions
- // from the parent, not the existing one otherwise permissions would be coming from the root since Id is 0.
+ var permissions = svc.GetPermissions(
+ //TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is
+ // with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null
+ // refrence exception :(
+ UmbracoContext.Current.Security.CurrentUser,
+ // Here we need to do a special check since this could be new content, in which case we need to get the permissions
+ // from the parent, not the existing one otherwise permissions would be coming from the root since Id is 0.
source.HasIdentity ? source.Id : source.ParentId)
.GetAllPermissions();
diff --git a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs
index 273dbd34f3..ec72c9839d 100644
--- a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs
+++ b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs
@@ -6,7 +6,6 @@ using Examine;
using Examine.LuceneEngine.Providers;
using Umbraco.Core;
using Umbraco.Core.Models;
-using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Mapping;
using Umbraco.Core.Models.Membership;
using Umbraco.Web.Models.ContentEditing;
@@ -21,7 +20,7 @@ namespace Umbraco.Web.Models.Mapping
config.CreateMap()
.ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(x.NodeObjectTypeId), x.Key)))
.ForMember(basic => basic.Icon, expression => expression.MapFrom(entity => entity.ContentTypeIcon))
- .ForMember(dto => dto.Trashed, expression => expression.Ignore())
+ .ForMember(dto => dto.Trashed, expression => expression.MapFrom(x => x.Trashed))
.ForMember(x => x.Alias, expression => expression.Ignore())
.AfterMap((entity, basic) =>
{
@@ -98,8 +97,8 @@ namespace Umbraco.Web.Models.Mapping
else if (entity.NodeObjectTypeId == Constants.ObjectTypes.TemplateTypeGuid)
basic.Icon = "icon-newspaper-alt";
}
- });
-
+ });
+
config.CreateMap()
//default to document icon
.ForMember(x => x.Score, expression => expression.MapFrom(result => result.Score))
diff --git a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs
index e7750c4c65..a504ed0431 100644
--- a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs
@@ -1,9 +1,12 @@
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
+using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
@@ -11,7 +14,7 @@ namespace Umbraco.Web.PropertyEditors
{
internal class ColorListPreValueEditor : ValueListPreValueEditor
{
-
+
public ColorListPreValueEditor()
{
var field = Fields.First();
@@ -23,14 +26,98 @@ namespace Umbraco.Web.PropertyEditors
//change the label
field.Name = "Add color";
//need to have some custom validation happening here
- field.Validators.Add(new ColorListValidator());
+ field.Validators.Add(new ColorListValidator());
+
+ Fields.Insert(0, new PreValueField
+ {
+ Name = "Include labels?",
+ View = "boolean",
+ Key = "useLabel",
+ Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string."
+ });
}
public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals)
{
var dictionary = persistedPreVals.FormatAsDictionary();
- var arrayOfVals = dictionary.Select(item => item.Value).ToList();
- return new Dictionary { { "items", arrayOfVals.ToDictionary(x => x.Id, x => x.Value) } };
+ var items = dictionary
+ .Where(x => x.Key != "useLabel")
+ .ToDictionary(x => x.Value.Id, x => x.Value.Value);
+
+ var items2 = new Dictionary();
+ foreach (var item in items)
+ {
+ if (item.Value.DetectIsJson() == false)
+ {
+ items2[item.Key] = item.Value;
+ continue;
+ }
+
+ try
+ {
+ items2[item.Key] = JsonConvert.DeserializeObject(item.Value);
+ }
+ catch
+ {
+ // let's say parsing Json failed, so what we have is the string - build json
+ items2[item.Key] = new JObject { { "color", item.Value }, { "label", item.Value } };
+ }
+ }
+
+ var result = new Dictionary { { "items", items2 } };
+ var useLabel = dictionary.ContainsKey("useLabel") && dictionary["useLabel"].Value == "1";
+ if (useLabel)
+ result["useLabel"] = dictionary["useLabel"].Value;
+
+ return result;
+ }
+
+ public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue)
+ {
+ var val = editorValue["items"] as JArray;
+ var result = new Dictionary();
+ if (val == null) return result;
+
+ try
+ {
+ object useLabelObj;
+ var useLabel = false;
+ if (editorValue.TryGetValue("useLabel", out useLabelObj))
+ {
+ useLabel = useLabelObj is string && (string) useLabelObj == "1";
+ result["useLabel"] = new PreValue(useLabel ? "1" : "0");
+ }
+
+ // get all non-empty values
+ var index = 0;
+ foreach (var preValue in val.OfType()
+ .Where(x => x["value"] != null)
+ .Select(x =>
+ {
+ var idString = x["id"] == null ? "0" : x["id"].ToString();
+ int id;
+ if (int.TryParse(idString, out id) == false) id = 0;
+
+ var color = x["value"].ToString();
+ if (string.IsNullOrWhiteSpace(color)) return null;
+
+ var label = x["label"].ToString();
+ return new PreValue(id, useLabel
+ ? JsonConvert.SerializeObject(new { value = color, label = label })
+ : color);
+ })
+ .WhereNotNull())
+ {
+ result.Add(index.ToInvariantString(), preValue);
+ index++;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("Could not deserialize the posted value: " + val, ex);
+ }
+
+ return result;
}
internal class ColorListValidator : IPropertyValidator
@@ -39,7 +126,7 @@ namespace Umbraco.Web.PropertyEditors
{
var json = value as JArray;
if (json == null) yield break;
-
+
//validate each item which is a json object
for (var index = 0; index < json.Count; index++)
{
diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs
index 763e28b608..f4beb9d5b8 100644
--- a/src/Umbraco.Web/Scheduling/KeepAlive.cs
+++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
+using Umbraco.Core.Sync;
namespace Umbraco.Web.Scheduling
{
@@ -20,9 +21,21 @@ namespace Umbraco.Web.Scheduling
_appContext = appContext;
}
+ private ILogger Logger { get { return _appContext.ProfilingLogger.Logger; } }
+
public override async Task PerformRunAsync(CancellationToken token)
{
if (_appContext == null) return true; // repeat...
+
+ switch (_appContext.GetCurrentServerRole())
+ {
+ case ServerRole.Slave:
+ Logger.Debug("Does not run on slave servers.");
+ return true; // DO repeat, server role can change
+ case ServerRole.Unknown:
+ Logger.Debug("Does not run on servers with unknown role.");
+ return true; // DO repeat, server role can change
+ }
// ensure we do not run if not main domain, but do NOT lock it
if (_appContext.MainDom.IsMainDom == false)
@@ -31,7 +44,7 @@ namespace Umbraco.Web.Scheduling
return false; // do NOT repeat, going down
}
- using (DisposableTimer.DebugDuration(() => "Keep alive executing", () => "Keep alive complete"))
+ using (_appContext.ProfilingLogger.DebugDuration("Keep alive executing", "Keep alive complete"))
{
string umbracoAppUrl = null;
diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
index 46a756e0ad..919a531549 100644
--- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
+++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
@@ -31,7 +31,7 @@ namespace Umbraco.Web.Scheduling
private ILogger Logger { get { return _appContext.ProfilingLogger.Logger; } }
- public override async Task PerformRunAsync(CancellationToken token)
+ public override bool PerformRun()
{
if (_appContext == null) return true; // repeat...
@@ -94,7 +94,7 @@ namespace Umbraco.Web.Scheduling
public override bool IsAsync
{
- get { return true; }
+ get { return false; }
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/UI/LegacyDialogHandler.cs b/src/Umbraco.Web/UI/LegacyDialogHandler.cs
index c51a8bb08b..0c3d09f1a2 100644
--- a/src/Umbraco.Web/UI/LegacyDialogHandler.cs
+++ b/src/Umbraco.Web/UI/LegacyDialogHandler.cs
@@ -126,8 +126,10 @@ namespace Umbraco.Web.UI
{
var task = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType);
if (task == null)
- throw new InvalidOperationException(
- string.Format("Could not task for operation {0} for node type {1}", Operation.Create, nodeType));
+ {
+ //if no task was found it will use the default task and we cannot validate the application assigned so return true
+ return true;
+ }
var dialogTask = task as LegacyDialogTask;
if (dialogTask != null)
@@ -154,8 +156,10 @@ namespace Umbraco.Web.UI
{
var task = GetTaskForOperation(httpContext, umbracoUser, Operation.Delete, nodeType);
if (task == null)
- throw new InvalidOperationException(
- string.Format("Could not task for operation {0} for node type {1}", Operation.Delete, nodeType));
+ {
+ //if no task was found it will use the default task and we cannot validate the application assigned so return true
+ return true;
+ }
var dialogTask = task as LegacyDialogTask;
if (dialogTask != null)
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 0a40af560d..ec3dbcf043 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -393,6 +393,7 @@
+
diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
index 5773d88f73..342b8daa16 100644
--- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
+++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
@@ -46,12 +46,11 @@ namespace Umbraco.Web
///
protected virtual void ConfigureMiddleware(IAppBuilder app)
{
- //Ensure owin is configured for Umbraco back office authentication. If you have any front-end OWIN
- // cookie configuration, this must be declared after it.
+
+ // Configure OWIN for authentication.
+ ConfigureUmbracoAuthentication(app);
+
app
- .UseUmbracoBackOfficeCookieAuthentication(ApplicationContext, PipelineStage.Authenticate)
- .UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext, PipelineStage.Authenticate)
- .UseUmbracoPreviewAuthentication(ApplicationContext, PipelineStage.Authorize)
.UseSignalR()
.FinalizeMiddlewareConfiguration();
}
@@ -68,6 +67,20 @@ namespace Umbraco.Web
Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider());
}
+ ///
+ /// Configure external/OAuth login providers
+ ///
+ ///
+ protected virtual void ConfigureUmbracoAuthentication(IAppBuilder app)
+ {
+ // Ensure owin is configured for Umbraco back office authentication.
+ // Front-end OWIN cookie configuration must be declared after this code.
+ app
+ .UseUmbracoBackOfficeCookieAuthentication(ApplicationContext, PipelineStage.Authenticate)
+ .UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext, PipelineStage.Authenticate)
+ .UseUmbracoPreviewAuthentication(ApplicationContext, PipelineStage.Authorize);
+ }
+
///
/// Raised when the middleware has been configured
///
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs
index 1bd3918ec2..969550d9a9 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs
@@ -162,6 +162,13 @@ namespace umbraco.presentation.developer.packages
/*Data types */
cms.businesslogic.datatype.DataTypeDefinition[] umbDataType = cms.businesslogic.datatype.DataTypeDefinition.GetAll();
+
+ // sort array by name
+ Array.Sort(umbDataType, delegate(cms.businesslogic.datatype.DataTypeDefinition umbDataType1, cms.businesslogic.datatype.DataTypeDefinition umbDataType2)
+ {
+ return umbDataType1.Text.CompareTo(umbDataType2.Text);
+ });
+
foreach (cms.businesslogic.datatype.DataTypeDefinition umbDtd in umbDataType)
{
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs
index 27c1724bff..147e7604c1 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs
@@ -70,10 +70,11 @@ namespace umbraco.presentation.umbraco.dialogs
private void import_Click(object sender, EventArgs e)
{
var xd = new XmlDocument();
+ xd.XmlResolver = null;
xd.Load(tempFile.Value);
var userId = base.getUser().Id;
-
+
var element = XElement.Parse(xd.InnerXml);
var importContentTypes = ApplicationContext.Current.Services.PackagingService.ImportContentTypes(element, userId);
var contentType = importContentTypes.FirstOrDefault();
@@ -104,7 +105,8 @@ namespace umbraco.presentation.umbraco.dialogs
documentTypeFile.PostedFile.SaveAs(fileName);
var xd = new XmlDocument();
- xd.Load(fileName);
+ xd.XmlResolver = null;
+ xd.Load(fileName);
dtName.Text = xd.DocumentElement.SelectSingleNode("//DocumentType/Info/Name").FirstChild.Value;
dtAlias.Text = xd.DocumentElement.SelectSingleNode("//DocumentType/Info/Alias").FirstChild.Value;
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs
index 70137da920..195ac15ec8 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs
@@ -27,7 +27,7 @@ namespace umbraco.dialogs
protected void Page_Load(object sender, EventArgs e)
{
Button1.Text = ui.Text("update");
- pane_form.Text = ui.Text("notifications", "editNotifications", node.Text, base.getUser());
+ pane_form.Text = ui.Text("notifications", "editNotifications", Server.HtmlEncode(node.Text), base.getUser());
}
#region Web Form Designer generated code
diff --git a/src/umbraco.cms/businesslogic/member/Member.cs b/src/umbraco.cms/businesslogic/member/Member.cs
index 71c8eda701..c46e4d914c 100644
--- a/src/umbraco.cms/businesslogic/member/Member.cs
+++ b/src/umbraco.cms/businesslogic/member/Member.cs
@@ -773,14 +773,20 @@ namespace umbraco.cms.businesslogic.member
{
var temp = new Hashtable();
+ var groupIds = new List();
+
using (var sqlHelper = Application.SqlHelper)
using (var dr = sqlHelper.ExecuteReader(
"select memberGroup from cmsMember2MemberGroup where member = @id",
sqlHelper.CreateParameter("@id", Id)))
{
while (dr.Read())
- temp.Add(dr.GetInt("memberGroup"),
- new MemberGroup(dr.GetInt("memberGroup")));
+ groupIds.Add(dr.GetInt("memberGroup"));
+ }
+
+ foreach (var groupId in groupIds)
+ {
+ temp.Add(groupId, new MemberGroup(groupId));
}
_groups = temp;
}