())
{
- m.ConfigureMappings(configuration);
+ m.ConfigureMappings(configuration, ApplicationContext);
}
});
}
diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs
index 420831d299..4f52f61683 100644
--- a/src/Umbraco.Core/DatabaseContext.cs
+++ b/src/Umbraco.Core/DatabaseContext.cs
@@ -169,6 +169,20 @@ namespace Umbraco.Core
Initialize(providerName);
}
+ ///
+ /// Configures a ConnectionString for the Umbraco database that uses Microsoft SQL Server integrated security.
+ ///
+ /// Name or address of the database server
+ /// Name of the database
+ public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName)
+ {
+ const string providerName = "System.Data.SqlClient";
+ string connectionString = String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
+
+ SaveConnectionString(connectionString, providerName);
+ Initialize(providerName);
+ }
+
internal string BuildAzureConnectionString(string server, string databaseName, string user, string password)
{
if (server.Contains(".") && ServerStartsWithTcp(server) == false)
@@ -393,8 +407,8 @@ namespace Umbraco.Core
if (supportsCaseInsensitiveQueries == false)
{
message = "
The database you're trying to use does not support case insensitive queries. We currently do not support these types of databases.
" +
- "You can fix this by changing the following two settings in your my.ini file in your MySQL installation directory:
" +
- "lower_case_table_names=1\nlower_case_file_system=1 " +
+ "You can fix this by changing the following setting in your my.ini file in your MySQL installation directory:
" +
+ "lower_case_table_names=1 " +
"Note: Make sure to check with your hosting provider if they support case insensitive queries as well.
" +
"For more technical information on case sensitivity in MySQL, have a look at " +
"the documentation on the subject
";
@@ -404,8 +418,8 @@ namespace Umbraco.Core
else if (supportsCaseInsensitiveQueries == null)
{
message = "
Warning! Could not check if your database type supports case insensitive queries. We currently do not support these databases that do not support case insensitive queries.
" +
- "You can check this by looking for the following two settings in your my.ini file in your MySQL installation directory:
" +
- "lower_case_table_names=1\nlower_case_file_system=1 " +
+ "You can check this by looking for the following setting in your my.ini file in your MySQL installation directory:
" +
+ "lower_case_table_names=1 " +
"Note: Make sure to check with your hosting provider if they support case insensitive queries as well.
" +
"For more technical information on case sensitivity in MySQL, have a look at " +
"the documentation on the subject
";
@@ -418,8 +432,8 @@ namespace Umbraco.Core
"Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.
" +
"However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries
" +
"Make sure to check with your hosting provider if they support case insensitive queries as well.
" +
- "They can check this by looking for the following two settings in the my.ini file in their MySQL installation directory:
" +
- "lower_case_table_names=1\nlower_case_file_system=1 " +
+ "They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:
" +
+ "lower_case_table_names=1 " +
"For more technical information on case sensitivity in MySQL, have a look at " +
"the documentation on the subject
";
}
diff --git a/src/Umbraco.Core/DateTimeExtensions.cs b/src/Umbraco.Core/DateTimeExtensions.cs
new file mode 100644
index 0000000000..74460814b3
--- /dev/null
+++ b/src/Umbraco.Core/DateTimeExtensions.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Umbraco.Core
+{
+ public static class DateTimeExtensions
+ {
+
+ ///
+ /// Returns the DateTime as an ISO formatted string that is globally expectable
+ ///
+ ///
+ ///
+ public static string ToIsoString(this DateTime dt)
+ {
+ return dt.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ }
+}
diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs
index bc860705a2..22dc0814d5 100644
--- a/src/Umbraco.Core/IO/IOHelper.cs
+++ b/src/Umbraco.Core/IO/IOHelper.cs
@@ -45,6 +45,8 @@ namespace Umbraco.Core.IO
{
if (virtualPath.StartsWith("~"))
return virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/");
+ else if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute))
+ return virtualPath;
else
return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root);
}
diff --git a/src/Umbraco.Core/IO/ResizedImage.cs b/src/Umbraco.Core/IO/ResizedImage.cs
new file mode 100644
index 0000000000..bd96dcfb26
--- /dev/null
+++ b/src/Umbraco.Core/IO/ResizedImage.cs
@@ -0,0 +1,20 @@
+namespace Umbraco.Core.IO
+{
+ internal class ResizedImage
+ {
+ public ResizedImage()
+ {
+ }
+
+ public ResizedImage(int width, int height, string fileName)
+ {
+ Width = width;
+ Height = height;
+ FileName = fileName;
+ }
+
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public string FileName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/IO/UmbracoMediaFile.cs b/src/Umbraco.Core/IO/UmbracoMediaFile.cs
new file mode 100644
index 0000000000..e657d9507a
--- /dev/null
+++ b/src/Umbraco.Core/IO/UmbracoMediaFile.cs
@@ -0,0 +1,253 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Web;
+using Umbraco.Core.Configuration;
+
+namespace Umbraco.Core.IO
+{
+ public class UmbracoMediaFile
+ {
+ private readonly MediaFileSystem _fs;
+
+ #region Constructors
+
+ public UmbracoMediaFile()
+ {
+ _fs = FileSystemProviderManager.Current.GetFileSystemProvider();
+ }
+
+ public UmbracoMediaFile(string path)
+ {
+ _fs = FileSystemProviderManager.Current.GetFileSystemProvider();
+
+ Path = path;
+
+ Initialize();
+ }
+
+ #endregion
+
+ #region Static Methods
+
+ //MB: Do we really need all these overloads? looking through the code, only one of them is actually used
+
+ public static UmbracoMediaFile Save(HttpPostedFile file, string path)
+ {
+ return Save(file.InputStream, path);
+ }
+
+ public static UmbracoMediaFile Save(HttpPostedFileBase file, string path)
+ {
+ return Save(file.InputStream, path);
+ }
+
+ public static UmbracoMediaFile Save(Stream inputStream, string path)
+ {
+ var fs = FileSystemProviderManager.Current.GetFileSystemProvider();
+ fs.AddFile(path, inputStream);
+
+ return new UmbracoMediaFile(path);
+ }
+
+ public static UmbracoMediaFile Save(byte[] file, string relativePath)
+ {
+ return Save(new MemoryStream(file), relativePath);
+ }
+
+ public static UmbracoMediaFile Save(HttpPostedFile file)
+ {
+ var tempDir = System.IO.Path.Combine("uploads", Guid.NewGuid().ToString());
+ return Save(file, tempDir);
+ }
+
+ //filebase overload...
+ public static UmbracoMediaFile Save(HttpPostedFileBase file)
+ {
+ var tempDir = System.IO.Path.Combine("uploads", Guid.NewGuid().ToString());
+ return Save(file, tempDir);
+ }
+
+ #endregion
+
+ private long? _length;
+ private Size? _size;
+
+ ///
+ /// Initialized values that don't require opening the file.
+ ///
+ private void Initialize()
+ {
+ Filename = _fs.GetFileName(Path);
+ Extension = _fs.GetExtension(Path) != null
+ ? _fs.GetExtension(Path).Substring(1).ToLowerInvariant()
+ : "";
+ Url = _fs.GetUrl(Path);
+ }
+
+ public string Filename { get; private set; }
+
+ public string Extension { get; private set; }
+
+ public string Path { get; private set; }
+
+ public string Url { get; private set; }
+
+ ///
+ /// Get the length of the file in bytes
+ ///
+ ///
+ /// We are lazy loading this, don't go opening the file on ctor like we were doing.
+ ///
+ public long Length
+ {
+ get
+ {
+ if (_length == null)
+ {
+ _length = _fs.GetSize(Path);
+ }
+ return _length.Value;
+ }
+ }
+
+ public bool SupportsResizing
+ {
+ get
+ {
+ return ("," + UmbracoSettings.ImageFileTypes + ",").Contains(string.Format(",{0},", Extension));
+ }
+ }
+
+ public string GetFriendlyName()
+ {
+ return Filename.SplitPascalCasing().ToFirstUpperInvariant();
+ }
+
+ public Size GetDimensions()
+ {
+ if (_size == null)
+ {
+ EnsureFileSupportsResizing();
+
+ var fs = _fs.OpenFile(Path);
+ var image = Image.FromStream(fs);
+ var fileWidth = image.Width;
+ var fileHeight = image.Height;
+ fs.Close();
+ image.Dispose();
+
+ _size = new Size(fileWidth, fileHeight);
+ }
+ return _size.Value;
+ }
+
+ public string Resize(int width, int height)
+ {
+ EnsureFileSupportsResizing();
+
+ var fileNameThumb = DoResize(width, height, 0, string.Empty);
+
+ return _fs.GetUrl(fileNameThumb);
+ }
+
+ public string Resize(int maxWidthHeight, string fileNameAddition)
+ {
+ EnsureFileSupportsResizing();
+
+ var fileNameThumb = DoResize(GetDimensions().Width, GetDimensions().Height, maxWidthHeight, fileNameAddition);
+
+ return _fs.GetUrl(fileNameThumb);
+ }
+
+ private string DoResize(int width, int height, int maxWidthHeight, string fileNameAddition)
+ {
+ using (var fs = _fs.OpenFile(Path))
+ {
+ using (var image = Image.FromStream(fs))
+ {
+ var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition)
+ ? string.Format("{0}_UMBRACOSYSTHUMBNAIL.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)))
+ : string.Format("{0}_{1}.jpg", Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition);
+
+ var thumbnail = GenerateThumbnail(image, maxWidthHeight, width, height, fileNameThumb, maxWidthHeight == 0);
+
+ return thumbnail.FileName;
+ }
+ }
+ }
+
+ private void EnsureFileSupportsResizing()
+ {
+ if (SupportsResizing == false)
+ throw new InvalidOperationException(string.Format("The file {0} is not an image, so can't get dimensions", Filename));
+ }
+
+ private ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, int fileWidth, int fileHeight, string thumbnailFileName, bool useFixedDimensions)
+ {
+ // Generate thumbnail
+ float f = 1;
+ if (useFixedDimensions == false)
+ {
+ var fx = (float)image.Size.Width / (float)maxWidthHeight;
+ var fy = (float)image.Size.Height / (float)maxWidthHeight;
+
+ // must fit in thumbnail size
+ f = Math.Max(fx, fy);
+ }
+
+ var widthTh = (int)Math.Round((float)fileWidth / f);
+ var heightTh = (int)Math.Round((float)fileHeight / f);
+
+ // fixes for empty width or height
+ if (widthTh == 0)
+ widthTh = 1;
+ if (heightTh == 0)
+ heightTh = 1;
+
+ // Create new image with best quality settings
+ using (var bp = new Bitmap(widthTh, heightTh))
+ {
+ using (var g = Graphics.FromImage(bp))
+ {
+ g.SmoothingMode = SmoothingMode.HighQuality;
+ g.InterpolationMode = InterpolationMode.HighQualityBicubic;
+ g.PixelOffsetMode = PixelOffsetMode.HighQuality;
+ g.CompositingQuality = CompositingQuality.HighQuality;
+
+ // Copy the old image to the new and resized
+ var rect = new Rectangle(0, 0, widthTh, heightTh);
+ g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
+
+ // Copy metadata
+ var imageEncoders = ImageCodecInfo.GetImageEncoders();
+
+ var codec = Extension.ToLower() == "png" || Extension.ToLower() == "gif"
+ ? imageEncoders.Single(t => t.MimeType.Equals("image/png"))
+ : imageEncoders.Single(t => t.MimeType.Equals("image/jpeg"));
+
+ // Set compresion ratio to 90%
+ var ep = new EncoderParameters();
+ ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
+
+ // Save the new image using the dimensions of the image
+ var newFileName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", string.Format("{0}x{1}", widthTh, heightTh));
+ using (var ms = new MemoryStream())
+ {
+ bp.Save(ms, codec, ep);
+ ms.Seek(0, 0);
+
+ _fs.AddFile(newFileName, ms);
+ }
+
+ return new ResizedImage(widthTh, heightTh, newFileName);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs
index 4c00831cae..6414ea2116 100644
--- a/src/Umbraco.Core/Models/ContentBase.cs
+++ b/src/Umbraco.Core/Models/ContentBase.cs
@@ -27,6 +27,7 @@ namespace Umbraco.Core.Models
private bool _trashed;
private int _contentTypeId;
private PropertyCollection _properties;
+ private readonly List _lastInvalidProperties = new List();
///
/// Protected constructor for ContentBase (Base for Content and Media)
@@ -421,7 +422,17 @@ namespace Umbraco.Core.Models
/// True if content is valid otherwise false
public virtual bool IsValid()
{
- return Properties.Any(property => !property.IsValid()) == false;
+ _lastInvalidProperties.Clear();
+ _lastInvalidProperties.AddRange(Properties.Where(property => property.IsValid() == false));
+ return _lastInvalidProperties.Any() == false;
+ }
+
+ ///
+ /// Returns a collection of the result of the last validation process, this collection contains all invalid properties.
+ ///
+ internal IEnumerable LastInvalidProperties
+ {
+ get { return _lastInvalidProperties; }
}
public abstract void ChangeTrashedState(bool isTrashed, int parentId = -20);
diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs
index 860acdd852..1ca6ff4b7c 100644
--- a/src/Umbraco.Core/Models/ContentExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentExtensions.cs
@@ -158,7 +158,9 @@ namespace Umbraco.Core.Models
public static IEnumerable GetNonGroupedProperties(this IContentBase content)
{
var propertyIdsInTabs = content.PropertyGroups.SelectMany(pg => pg.PropertyTypes).Select(pt => pt.Id);
- return content.Properties.Where(property => propertyIdsInTabs.Contains(property.PropertyTypeId) == false);
+ return content.Properties
+ .Where(property => propertyIdsInTabs.Contains(property.PropertyTypeId) == false)
+ .OrderBy(x => x.PropertyType.SortOrder);
}
///
@@ -173,7 +175,8 @@ namespace Umbraco.Core.Models
return content.Properties
.Where(property => propertyGroup.PropertyTypes
.Select(propertyType => propertyType.Id)
- .Contains(property.PropertyTypeId));
+ .Contains(property.PropertyTypeId))
+ .OrderBy(x => x.PropertyType.SortOrder);
}
///
diff --git a/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs b/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs
index 0ca76417de..8ea7c46d64 100644
--- a/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs
+++ b/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs
@@ -17,6 +17,6 @@ namespace Umbraco.Core.Models.Mapping
///
internal interface IMapperConfiguration : IApplicationEventHandler
{
- void ConfigureMappings(IConfiguration config);
+ void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext);
}
}
diff --git a/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs b/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs
index 8bde82d13f..fcc3410f46 100644
--- a/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs
+++ b/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs
@@ -10,6 +10,6 @@ namespace Umbraco.Core.Models.Mapping
///
internal abstract class MapperConfiguration : ApplicationEventHandler, IMapperConfiguration
{
- public abstract void ConfigureMappings(IConfiguration config);
+ public abstract void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs
index 044ecd164e..94ed57569a 100644
--- a/src/Umbraco.Core/Models/PropertyGroup.cs
+++ b/src/Umbraco.Core/Models/PropertyGroup.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Specialized;
+using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
@@ -12,6 +13,7 @@ namespace Umbraco.Core.Models
///
[Serializable]
[DataContract(IsReference = true)]
+ [DebuggerDisplay("Id: {Id}, Name: {Name}")]
public class PropertyGroup : Entity, IEquatable
{
private string _name;
diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs
index 1941e19d40..c032e290b1 100644
--- a/src/Umbraco.Core/Models/Template.cs
+++ b/src/Umbraco.Core/Models/Template.cs
@@ -20,7 +20,6 @@ namespace Umbraco.Core.Models
private int _level;
private int _sortOrder;
private int _parentId;
- private string _nodePath;
private int _masterTemplateId;
private string _masterTemplateAlias;
@@ -28,7 +27,6 @@ namespace Umbraco.Core.Models
private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level);
private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder);
private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId);
- private static readonly PropertyInfo NodePathSelector = ExpressionHelper.GetPropertyInfo(x => x.NodePath);
//private static readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateId);
private static readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias);
@@ -104,21 +102,7 @@ namespace Umbraco.Core.Models
}, _parentId, ParentIdSelector);
}
}
-
- [DataMember]
- internal string NodePath
- {
- get { return _nodePath; }
- set
- {
- SetPropertyValueAndDetectChanges(o =>
- {
- _nodePath = value;
- return _nodePath;
- }, _nodePath, NodePathSelector);
- }
- }
-
+
[DataMember]
internal Lazy MasterTemplateId { get; set; }
diff --git a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs
index e79b61ed5d..06a624a771 100644
--- a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs
+++ b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs
@@ -1,9 +1,11 @@
using System;
+using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Threading;
+using System.Web;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Persistence.Caching
@@ -11,6 +13,23 @@ namespace Umbraco.Core.Persistence.Caching
///
/// The Runtime Cache provider looks up objects in the Runtime cache for fast retrival
///
+ ///
+ ///
+ /// If a web session is detected then the HttpRuntime.Cache will be used for the runtime cache, otherwise a custom
+ /// MemoryCache instance will be used. It is important to use the HttpRuntime.Cache when a web session is detected so
+ /// that the memory management of cache in IIS can be handled appopriately.
+ ///
+ /// When a web sessions is detected we will pre-fix all HttpRuntime.Cache entries so that when we clear it we are only
+ /// clearing items that have been inserted by this provider.
+ ///
+ /// NOTE: These changes are all temporary until we finalize the ApplicationCache implementation which will support static cache, runtime cache
+ /// and request based cache which will all live in one central location so it is easily managed.
+ ///
+ /// Also note that we don't always keep checking if HttpContext.Current == null and instead check for _memoryCache != null. This is because
+ /// when there are async requests being made even in the context of a web request, the HttpContext.Current will be null but the HttpRuntime.Cache will
+ /// always be available.
+ ///
+ ///
internal sealed class RuntimeCacheProvider : IRepositoryCacheProvider
{
#region Singleton
@@ -21,19 +40,25 @@ namespace Umbraco.Core.Persistence.Caching
private RuntimeCacheProvider()
{
+ if (HttpContext.Current == null)
+ {
+ _memoryCache = new MemoryCache("in-memory");
+ }
}
#endregion
//TODO Save this in cache as well, so its not limited to a single server usage
private readonly ConcurrentHashSet _keyTracker = new ConcurrentHashSet();
- private ObjectCache _memoryCache = new MemoryCache("in-memory");
+ private ObjectCache _memoryCache;
private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim();
public IEntity GetById(Type type, Guid id)
{
var key = GetCompositeId(type, id);
- var item = _memoryCache.Get(key);
+ var item = _memoryCache != null
+ ? _memoryCache.Get(key)
+ : HttpRuntime.Cache.Get(key);
return item as IEntity;
}
@@ -41,7 +66,11 @@ namespace Umbraco.Core.Persistence.Caching
{
foreach (var guid in ids)
{
- yield return _memoryCache.Get(GetCompositeId(type, guid)) as IEntity;
+ var item = _memoryCache != null
+ ? _memoryCache.Get(GetCompositeId(type, guid))
+ : HttpRuntime.Cache.Get(GetCompositeId(type, guid));
+
+ yield return item as IEntity;
}
}
@@ -49,9 +78,13 @@ namespace Umbraco.Core.Persistence.Caching
{
foreach (var key in _keyTracker)
{
- if (key.StartsWith(type.Name))
+ if (key.StartsWith(string.Format("{0}{1}-", CacheItemPrefix, type.Name)))
{
- yield return _memoryCache.Get(key) as IEntity;
+ var item = _memoryCache != null
+ ? _memoryCache.Get(key)
+ : HttpRuntime.Cache.Get(key);
+
+ yield return item as IEntity;
}
}
}
@@ -59,21 +92,34 @@ namespace Umbraco.Core.Persistence.Caching
public void Save(Type type, IEntity entity)
{
var key = GetCompositeId(type, entity.Id);
- var exists = _memoryCache.GetCacheItem(key) != null;
+
_keyTracker.TryAdd(key);
- if (exists)
+
+ //NOTE: Before we were checking if it already exists but the MemoryCache.Set handles this implicitly and does
+ // an add or update, same goes for HttpRuntime.Cache.Insert.
+
+ if (_memoryCache != null)
{
_memoryCache.Set(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) });
- return;
}
-
- _memoryCache.Add(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) });
+ else
+ {
+ HttpRuntime.Cache.Insert(key, entity, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5));
+ }
}
public void Delete(Type type, IEntity entity)
{
var key = GetCompositeId(type, entity.Id);
- _memoryCache.Remove(key);
+ if (_memoryCache != null)
+ {
+ _memoryCache.Remove(key);
+ }
+ else
+ {
+ HttpRuntime.Cache.Remove(key);
+ }
+
_keyTracker.Remove(key);
}
@@ -88,14 +134,21 @@ namespace Umbraco.Core.Persistence.Caching
var keys = new string[_keyTracker.Count];
_keyTracker.CopyTo(keys, 0);
var keysToRemove = new List();
- foreach (var key in keys.Where(x => x.StartsWith(string.Format("{0}-", type.Name))))
+ foreach (var key in keys.Where(x => x.StartsWith(string.Format("{0}{1}-", CacheItemPrefix, type.Name))))
{
_keyTracker.Remove(key);
keysToRemove.Add(key);
}
foreach (var key in keysToRemove)
{
- _memoryCache.Remove(key);
+ if (_memoryCache != null)
+ {
+ _memoryCache.Remove(key);
+ }
+ else
+ {
+ HttpRuntime.Cache.Remove(key);
+ }
}
}
}
@@ -105,19 +158,40 @@ namespace Umbraco.Core.Persistence.Caching
using (new WriteLock(ClearLock))
{
_keyTracker.Clear();
- _memoryCache.DisposeIfDisposable();
- _memoryCache = new MemoryCache("in-memory");
+
+ if (_memoryCache != null)
+ {
+ _memoryCache.DisposeIfDisposable();
+ _memoryCache = new MemoryCache("in-memory");
+ }
+ else
+ {
+ foreach (DictionaryEntry c in HttpRuntime.Cache)
+ {
+ if (c.Key is string && ((string)c.Key).InvariantStartsWith(CacheItemPrefix))
+ {
+ if (HttpRuntime.Cache[(string)c.Key] == null) return;
+ HttpRuntime.Cache.Remove((string)c.Key);
+ }
+ }
+ }
}
}
+ ///
+ /// We prefix all cache keys with this so that we know which ones this class has created when
+ /// using the HttpRuntime cache so that when we clear it we don't clear other entries we didn't create.
+ ///
+ private const string CacheItemPrefix = "umbrtmche_";
+
private string GetCompositeId(Type type, Guid id)
{
- return string.Format("{0}-{1}", type.Name, id.ToString());
+ return string.Format("{0}{1}-{2}", CacheItemPrefix, type.Name, id.ToString());
}
private string GetCompositeId(Type type, int id)
{
- return string.Format("{0}-{1}", type.Name, id.ToGuid());
+ return string.Format("{0}{1}-{2}", CacheItemPrefix, type.Name, id.ToGuid());
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs
index ae2c2831f8..a62e9d3c00 100644
--- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs
@@ -37,7 +37,7 @@ namespace Umbraco.Core.Persistence.Factories
Level = dto.NodeDto.Level,
ParentId = dto.NodeDto.ParentId,
SortOrder = dto.NodeDto.SortOrder,
- NodePath = dto.NodeDto.Path
+ Path = dto.NodeDto.Path
};
if(dto.Master.HasValue)
@@ -83,7 +83,7 @@ namespace Umbraco.Core.Persistence.Factories
Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)),
NodeObjectType = _nodeObjectTypeId,
ParentId = entity.ParentId,
- Path = entity.NodePath,
+ Path = entity.Path,
SortOrder = entity.SortOrder,
Text = entity.Name,
Trashed = false,
diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs
index 6faf4d637a..51ae2638b0 100644
--- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs
+++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs
@@ -74,7 +74,11 @@ namespace Umbraco.Core.Persistence
//get all columns but not the primary key if it is auto-incremental
var cols = string.Join(", ", (
from c in pd.Columns
- where c.Value.ResultColumn == false
+ where
+ //don't return ResultColumns
+ !c.Value.ResultColumn
+ //if the table is auto-incremental, don't return the primary key
+ && (pd.TableInfo.AutoIncrement && c.Key != pd.TableInfo.PrimaryKey)
select tableName + "." + db.EscapeSqlIdentifier(c.Key))
.ToArray());
@@ -87,10 +91,10 @@ namespace Umbraco.Core.Persistence
var values = new List();
foreach (var i in pd.Columns)
{
- //if (pd.TableInfo.AutoIncrement && i.Key == pd.TableInfo.PrimaryKey)
- //{
- // continue;
- //}
+ if (pd.TableInfo.AutoIncrement && i.Key == pd.TableInfo.PrimaryKey)
+ {
+ continue;
+ }
values.Add(string.Format("{0}{1}", "@", index++));
db.AddParam(cmd, i.Value.GetValue(poco), "@");
}
diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs
index 7a65fe33bc..470a098fd8 100644
--- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs
+++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs
@@ -455,8 +455,11 @@ namespace Umbraco.Core.Persistence.Querying
if (fieldType == typeof(decimal))
return ((decimal)value).ToString(CultureInfo.InvariantCulture);
- if (fieldType == typeof(DateTime))
- return "'" + EscapeParam(((DateTime)value).ToString(CultureInfo.InvariantCulture)) + "'";
+ if (fieldType == typeof (DateTime))
+ {
+ return "'" + EscapeParam(((DateTime)value).ToIsoString()) + "'";
+ }
+
if (fieldType == typeof(bool))
return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture);
diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs
index 456607a348..d065849102 100644
--- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs
+++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs
@@ -465,8 +465,11 @@ namespace Umbraco.Core.Persistence.Querying
if (fieldType == typeof(decimal))
return ((decimal)value).ToString(CultureInfo.InvariantCulture);
- if(fieldType == typeof(DateTime))
- return "'" + EscapeParam(((DateTime)value).ToString(CultureInfo.InvariantCulture)) + "'";
+ if (fieldType == typeof (DateTime))
+ {
+ return "'" + EscapeParam(((DateTime)value).ToIsoString()) + "'";
+ }
+
if (fieldType == typeof(bool))
return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture);
diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs
index 73c7b290f9..d8d4b1c7e2 100644
--- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs
+++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs
@@ -118,7 +118,7 @@ namespace Umbraco.Core.Persistence
public virtual ITemplateRepository CreateTemplateRepository(IDatabaseUnitOfWork uow)
{
- return new TemplateRepository(uow, NullCacheProvider.Current);
+ return new TemplateRepository(uow, RuntimeCacheProvider.Current);
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs
index 389650d606..52ea11e572 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs
@@ -317,11 +317,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax
{
db.OpenSharedConnection();
// Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice
- var lowerCaseFileSystem = db.Fetch("SELECT @@@@Global.lower_case_file_system");
var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names");
- if(lowerCaseFileSystem.Any() && lowerCaseTableNames.Any())
- supportsCaseInsensitiveQueries = lowerCaseFileSystem.First() == 1 && lowerCaseTableNames.First() == 1;
+ if(lowerCaseTableNames.Any())
+ supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1;
}
catch(Exception ex)
{
diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs
index 6bbaa59783..415b758965 100644
--- a/src/Umbraco.Core/Publishing/PublishStatus.cs
+++ b/src/Umbraco.Core/Publishing/PublishStatus.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core.Publishing
@@ -10,6 +11,11 @@ namespace Umbraco.Core.Publishing
public IContent ContentItem { get; private set; }
public PublishStatusType StatusType { get; internal set; }
+ ///
+ /// Gets sets the invalid properties if the status failed due to validation.
+ ///
+ public IEnumerable InvalidProperties { get; set; }
+
public PublishStatus(IContent content, PublishStatusType statusType)
{
ContentItem = content;
@@ -24,6 +30,5 @@ namespace Umbraco.Core.Publishing
{
}
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index 41a536fc46..6aaf82601c 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -1055,6 +1055,7 @@ namespace Umbraco.Core.Services
uow.Commit();
//Special case for the Upload DataType
+ //TODO: Should we handle this with events?
var uploadDataTypeId = new Guid(Constants.PropertyEditors.UploadField);
if (content.Properties.Any(x => x.PropertyType.DataTypeId == uploadDataTypeId))
{
@@ -1135,6 +1136,9 @@ namespace Umbraco.Core.Services
Audit.Add(AuditTypes.Copy, "Copy Content performed by user", content.WriterId, content.Id);
+
+ //TODO: Don't think we need this here because cache should be cleared by the event listeners
+ // and the correct ICacheRefreshers!?
RuntimeCacheProvider.Current.Clear();
return copy;
@@ -1562,6 +1566,8 @@ namespace Umbraco.Core.Services
publishStatus.StatusType = CheckAndLogIsPublishable(content);
//Content contains invalid property values and can therefore not be published - fire event?
publishStatus.StatusType = CheckAndLogIsValid(content);
+ //set the invalid properties (if there are any)
+ publishStatus.InvalidProperties = ((ContentBase) content).LastInvalidProperties;
//if we're still successful, then publish using the strategy
if (publishStatus.StatusType == PublishStatusType.Success)
diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs
index b0aa76b929..b53cedfd0c 100644
--- a/src/Umbraco.Core/Services/PackagingService.cs
+++ b/src/Umbraco.Core/Services/PackagingService.cs
@@ -9,6 +9,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
@@ -286,7 +287,7 @@ namespace Umbraco.Core.Services
var properties = from property in element.Elements()
where property.Attribute("isDoc") == null
select property;
-
+
IContent content = parent == null
? new Content(nodeName, parentId, contentType)
{
@@ -303,12 +304,33 @@ namespace Umbraco.Core.Services
{
string propertyTypeAlias = isLegacySchema ? property.Attribute("alias").Value : property.Name.LocalName;
if (content.HasProperty(propertyTypeAlias))
- content.SetValue(propertyTypeAlias, property.Value);
+ {
+ var propertyValue = property.Value;
+
+ var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias);
+ if (propertyType != null && propertyType.DataTypeId == new Guid(Constants.PropertyEditors.CheckBoxList))
+ {
+ var database = ApplicationContext.Current.DatabaseContext.Database;
+ var dtos = database.Fetch("WHERE datatypeNo" + "deId = @Id", new { Id = propertyType.DataTypeDefinitionId });
+
+ var propertyValueList = new List();
+ foreach (var preValue in propertyValue.Split(','))
+ {
+ propertyValueList.Add(dtos.Single(x => x.Value == preValue).Id.ToString(CultureInfo.InvariantCulture));
+ }
+
+ propertyValue = string.Join(",", propertyValueList.ToArray());
+ }
+
+ content.SetValue(propertyTypeAlias, propertyValue);
+ }
}
return content;
}
+
+
#endregion
#region ContentTypes
diff --git a/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs b/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs
index e037eb148d..5c1a8c2784 100644
--- a/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs
+++ b/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs
@@ -35,12 +35,20 @@ namespace Umbraco.Core.Sync
if (_addresses == null)
{
_addresses = new List();
- var nodes = _xmlServers.SelectNodes("./server");
- foreach (XmlNode n in nodes)
- {
- _addresses.Add(new ConfigServerAddress(n));
- }
+
+ if (_xmlServers != null)
+ {
+ var nodes = _xmlServers.SelectNodes("./server");
+ if (nodes != null)
+ {
+ foreach (XmlNode n in nodes)
+ {
+ _addresses.Add(new ConfigServerAddress(n));
+ }
+ }
+ }
}
+
return _addresses;
}
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 3e4db2e6ae..137a2bfbe5 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -149,6 +149,7 @@
+
@@ -172,6 +173,8 @@
+
+
diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs
index 45088aa1bf..923caf995a 100644
--- a/src/Umbraco.Core/UriExtensions.cs
+++ b/src/Umbraco.Core/UriExtensions.cs
@@ -2,6 +2,8 @@ using System;
using System.IO;
using System.Linq;
using System.Text;
+using System.Web;
+using Umbraco.Core.Configuration;
namespace Umbraco.Core
{
@@ -10,6 +12,23 @@ namespace Umbraco.Core
///
public static class UriExtensions
{
+ ///
+ /// Checks if the uri is a request for the default back office page
+ ///
+ ///
+ ///
+ internal static bool IsDefaultBackOfficeRequest(this Uri url)
+ {
+ if (url.AbsolutePath.InvariantEquals(GlobalSettings.Path.TrimEnd("/"))
+ || url.AbsolutePath.InvariantEquals(GlobalSettings.Path.EnsureEndsWith('/'))
+ || url.AbsolutePath.InvariantEquals(GlobalSettings.Path.EnsureEndsWith('/') + "Default")
+ || url.AbsolutePath.InvariantEquals(GlobalSettings.Path.EnsureEndsWith('/') + "Default/"))
+ {
+ return true;
+ }
+ return false;
+ }
+
///
/// This is a performance tweak to check if this is a .css, .js or .ico, .jpg, .jpeg, .png, .gif file request since
/// .Net will pass these requests through to the module when in integrated mode.
diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs
index 5c3c25e28b..b951380698 100644
--- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs
+++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs
@@ -3,12 +3,14 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using AutoMapper;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
+using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Models.Mapping;
namespace Umbraco.Tests.Models.Mapping
@@ -22,6 +24,11 @@ namespace Umbraco.Tests.Models.Mapping
}
+ protected override DatabaseBehavior DatabaseTestBehavior
+ {
+ get { return DatabaseBehavior.NewSchemaPerFixture; }
+ }
+
protected override void FreezeResolution()
{
PropertyEditorResolver.Current = new PropertyEditorResolver(
@@ -30,19 +37,73 @@ namespace Umbraco.Tests.Models.Mapping
base.FreezeResolution();
}
+ [Test]
+ public void To_Media_Item_Simple()
+ {
+ var contentType = MockedContentTypes.CreateImageMediaType();
+ var content = MockedMedia.CreateMediaImage(contentType, -1);
+
+ var result = Mapper.Map>(content);
+
+ AssertBasics(result, content);
+
+ foreach (var p in content.Properties)
+ {
+ AssertBasicProperty(result, p);
+ }
+ }
+
+ [Test]
+ public void To_Content_Item_Simple()
+ {
+ var contentType = MockedContentTypes.CreateSimpleContentType();
+ var content = MockedContent.CreateSimpleContent(contentType);
+
+ var result = Mapper.Map>(content);
+
+ AssertBasics(result, content);
+
+ foreach (var p in content.Properties)
+ {
+ AssertBasicProperty(result, p);
+ }
+ }
+
+ [Test]
+ public void To_Content_Item_Dto()
+ {
+ var contentType = MockedContentTypes.CreateSimpleContentType();
+ var content = MockedContent.CreateSimpleContent(contentType);
+
+ var result = Mapper.Map>(content);
+
+ AssertContentItem(result, content);
+ }
+
+ [Test]
+ public void To_Media_Item_Dto()
+ {
+ var contentType = MockedContentTypes.CreateImageMediaType();
+ var content = MockedMedia.CreateMediaImage(contentType, -1);
+
+ var result = Mapper.Map>(content);
+
+ AssertContentItem(result, content);
+ }
+
[Test]
public void To_Display_Model()
{
var contentType = MockedContentTypes.CreateSimpleContentType();
var content = MockedContent.CreateSimpleContent(contentType);
- var mapper = new ContentModelMapper(ApplicationContext, new UserModelMapper());
+ var result = Mapper.Map(content);
- var result = mapper.ToContentItemDisplay(content);
-
- Assert.AreEqual(content.Name, result.Name);
- Assert.AreEqual(content.Id, result.Id);
- Assert.AreEqual(content.Properties.Count(), result.Properties.Count());
+ AssertBasics(result, content);
+ foreach (var p in content.Properties)
+ {
+ AssertDisplayProperty(result, p, ApplicationContext);
+ }
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1);
Assert.IsTrue(result.Tabs.Any(x => x.Label == "Generic properties"));
Assert.IsTrue(result.Tabs.First().IsActive);
@@ -75,16 +136,86 @@ namespace Umbraco.Tests.Models.Mapping
//ensure that nothing is marked as dirty
content.ResetDirtyProperties(false);
- var mapper = new ContentModelMapper(ApplicationContext, new UserModelMapper());
+ var result = Mapper.Map(content);
- var result = mapper.ToContentItemDisplay(content);
-
- Assert.AreEqual(content.Name, result.Name);
- Assert.AreEqual(content.Id, result.Id);
- Assert.AreEqual(content.Properties.Count(), result.Properties.Count());
+ AssertBasics(result, content);
+ foreach (var p in content.Properties)
+ {
+ AssertDisplayProperty(result, p, ApplicationContext);
+ }
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1);
Assert.IsTrue(result.Tabs.Any(x => x.Label == "Generic properties"));
Assert.AreEqual(2, result.Tabs.Where(x => x.Label == "Generic properties").SelectMany(x => x.Properties).Count());
}
+
+ #region Assertions
+
+ private void AssertDisplayProperty(ContentItemBasic result, Property p, ApplicationContext applicationContext)
+ where T : ContentPropertyDisplay
+ where TPersisted : IContentBase
+ {
+ AssertBasicProperty(result, p);
+
+ var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias);
+ Assert.IsNotNull(pDto);
+ pDto.Alias = p.Alias;
+ pDto.Description = p.PropertyType.Description;
+ pDto.Label = p.PropertyType.Name;
+ pDto.Config = applicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(p.PropertyType.DataTypeDefinitionId);
+
+ }
+
+ private void AssertBasics(ContentItemBasic result, TPersisted content)
+ where T : ContentPropertyBasic
+ where TPersisted : IContentBase
+ {
+ Assert.AreEqual(content.Id, result.Id);
+ Assert.AreEqual(0, result.Owner.UserId);
+ Assert.AreEqual("admin", result.Owner.Name);
+ Assert.AreEqual(content.ParentId, result.ParentId);
+ Assert.AreEqual(content.UpdateDate, result.UpdateDate);
+ Assert.AreEqual(content.CreateDate, result.CreateDate);
+ Assert.AreEqual(content.Name, result.Name);
+ Assert.AreEqual(content.Properties.Count(), result.Properties.Count());
+ }
+
+ private void AssertBasicProperty(ContentItemBasic result, Property p)
+ where T : ContentPropertyBasic
+ where TPersisted : IContentBase
+ {
+ var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias);
+ Assert.IsNotNull(pDto);
+ Assert.AreEqual(p.Alias, pDto.Alias);
+ Assert.AreEqual(p.Id, pDto.Id);
+
+ Assert.IsTrue(p.Value == null ? pDto.Value == string.Empty : pDto.Value == p.Value);
+ }
+
+ private void AssertProperty(ContentItemBasic result, Property p)
+ where TPersisted : IContentBase
+ {
+ AssertBasicProperty(result, p);
+
+ var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias);
+ Assert.IsNotNull(pDto);
+ Assert.AreEqual(p.PropertyType.Mandatory, pDto.IsRequired);
+ Assert.AreEqual(p.PropertyType.ValidationRegExp, pDto.ValidationRegExp);
+ Assert.AreEqual(p.PropertyType.Description, pDto.Description);
+ Assert.AreEqual(p.PropertyType.Name, pDto.Label);
+ Assert.AreEqual(ApplicationContext.Services.DataTypeService.GetDataTypeDefinitionById(p.PropertyType.DataTypeDefinitionId), pDto.DataType);
+ Assert.AreEqual(PropertyEditorResolver.Current.GetById(p.PropertyType.DataTypeId), pDto.PropertyEditor);
+ }
+
+ private void AssertContentItem(ContentItemBasic result, T content)
+ where T : IContentBase
+ {
+ AssertBasics(result, content);
+
+ foreach (var p in content.Properties)
+ {
+ AssertProperty(result, p);
+ }
+ }
+ #endregion
}
}
diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs
index 5823970874..25fc2c3d35 100644
--- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs
+++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs
@@ -12,6 +12,22 @@ namespace Umbraco.Tests.Persistence.Querying
[TestFixture]
public class QueryBuilderTests : BaseUsingSqlCeSyntax
{
+ [Test]
+ public void Dates_Formatted_Properly()
+ {
+ var sql = new Sql();
+ sql.Select("*").From();
+
+ var dt = new DateTime(2013, 11, 21, 13, 25, 55);
+
+ var query = Query.Builder.Where(x => x.ExpireDate <= dt);
+ var translator = new SqlTranslator(sql, query);
+
+ var result = translator.Translate();
+
+ Assert.IsTrue(result.SQL.Contains("[expireDate] <= '2013-11-21 13:25:55'"));
+ }
+
[Test]
public void Can_Build_StartsWith_Query_For_IContent()
{
diff --git a/src/Umbraco.Tests/Services/Importing/CheckboxList-Content-Package.xml b/src/Umbraco.Tests/Services/Importing/CheckboxList-Content-Package.xml
new file mode 100644
index 0000000000..a0f4448393
--- /dev/null
+++ b/src/Umbraco.Tests/Services/Importing/CheckboxList-Content-Package.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+ CheckboxListTest
+ 1
+ MIT license
+ 1
+
+ 3
+ 0
+ 0
+
+
+
+ 1
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NewType
+ NewType
+ .sprTreeFolder
+ folder.png
+
+
+ True
+
+ NewType
+
+ NewType
+
+
+
+
+ testList
+ testList
+ b4471851-82b6-4c75-afa4-39fa9c6a75e9
+ 8dacee1a-1bbd-46c0-b07f-8ffd49fcca7c
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs b/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs
index fce3e04020..897c290bb6 100644
--- a/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs
+++ b/src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs
@@ -60,6 +60,34 @@ namespace Umbraco.Tests.Services.Importing {
}
}
+ ///
+ /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+ ///<umbPackage>
+ /// <files />
+ /// <info>
+ /// <package>
+ /// <name>CheckboxListTest</name>
+ /// <version>1</version>
+ /// <license url="http://www.opensource.org/licenses/mit-license.php">MIT license</license>
+ /// <url>1</url>
+ /// <requirements>
+ /// <major>3</major>
+ /// <minor>0</minor>
+ /// <patch>0</patch>
+ /// </requirements>
+ /// </package>
+ /// <author>
+ /// <name>1</name>
+ /// <website>1</website>
+ /// </author>
+ /// <r [rest of string was truncated]";.
+ ///
+ internal static string CheckboxList_Content_Package {
+ get {
+ return ResourceManager.GetString("CheckboxList_Content_Package", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8" standalone="no"?>
///<umbPackage>
diff --git a/src/Umbraco.Tests/Services/Importing/ImportResources.resx b/src/Umbraco.Tests/Services/Importing/ImportResources.resx
index b717a9600d..26f4589221 100644
--- a/src/Umbraco.Tests/Services/Importing/ImportResources.resx
+++ b/src/Umbraco.Tests/Services/Importing/ImportResources.resx
@@ -139,4 +139,7 @@
templateonly-updated-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
+ checkboxlist-content-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs
index 1fbfa01067..fa11f2aa78 100644
--- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs
+++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs
@@ -1,6 +1,9 @@
-using System.Linq;
+using System.Collections.Generic;
+using System.Linq;
using System.Xml.Linq;
using NUnit.Framework;
+using Umbraco.Core.Models.Rdbms;
+using umbraco.editorControls.MultiNodeTreePicker;
namespace Umbraco.Tests.Services.Importing
{
@@ -213,6 +216,42 @@ namespace Umbraco.Tests.Services.Importing
Assert.That(contents.Count(), Is.EqualTo(numberOfDocs));
}
+ [Test]
+ public void PackagingService_Can_Import_CheckboxList_Content_Package_Xml()
+ {
+ // Arrange
+ string strXml = ImportResources.CheckboxList_Content_Package;
+ var xml = XElement.Parse(strXml);
+ var dataTypeElement = xml.Descendants("DataTypes").First();
+ var docTypesElement = xml.Descendants("DocumentTypes").First();
+ var element = xml.Descendants("DocumentSet").First();
+ var packagingService = ServiceContext.PackagingService;
+
+ // Act
+ var dataTypeDefinitions = packagingService.ImportDataTypeDefinitions(dataTypeElement);
+ var contentTypes = packagingService.ImportContentTypes(docTypesElement);
+ var contents = packagingService.ImportContent(element);
+ var numberOfDocs = (from doc in element.Descendants()
+ where (string) doc.Attribute("isDoc") == ""
+ select doc).Count();
+
+ var database = ApplicationContext.DatabaseContext.Database;
+ var dtos = database.Fetch("WHERE datatypeNodeId = @Id", new { dataTypeDefinitions.First().Id });
+ int preValueId;
+ int.TryParse(contents.First().GetValue("testList"), out preValueId);
+ var preValueKey = dtos.SingleOrDefault(x => x.Id == preValueId);
+
+ // Assert
+ Assert.That(dataTypeDefinitions, Is.Not.Null);
+ Assert.That(dataTypeDefinitions.Any(), Is.True);
+ Assert.That(contents, Is.Not.Null);
+ Assert.That(contentTypes.Any(), Is.True);
+ Assert.That(contents.Any(), Is.True);
+ Assert.That(contents.Count(), Is.EqualTo(numberOfDocs));
+ Assert.That(preValueKey, Is.Not.Null);
+ Assert.That(preValueKey.Value, Is.EqualTo("test3"));
+ }
+
[Test]
public void PackagingService_Can_Import_Templates_Package_Xml_With_Invalid_Master()
{
diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
index 60fa93fd85..677a425fa9 100644
--- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
+++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
@@ -56,7 +56,7 @@ namespace Umbraco.Tests.TestHelpers
var mappers = PluginManager.Current.FindAndCreateInstances();
foreach (var mapper in mappers)
{
- mapper.ConfigureMappings(configuration);
+ mapper.ConfigureMappings(configuration, ApplicationContext);
}
});
}
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 7e6cf80bcd..86072083d6 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -519,6 +519,9 @@
+
+ Designer
+
diff --git a/src/Umbraco.Web.UI.Client/gruntFile.js b/src/Umbraco.Web.UI.Client/gruntFile.js
index 300155c4dc..a6ca3180ee 100644
--- a/src/Umbraco.Web.UI.Client/gruntFile.js
+++ b/src/Umbraco.Web.UI.Client/gruntFile.js
@@ -99,7 +99,14 @@ module.exports = function (grunt) {
files: [{ dest: '<%= distdir %>/js', src : '*.js', expand: true, cwd: 'src/common/mocks/' }]
},
vs: {
- files: [{ dest: '<%= vsdir %>/', src : '**', expand: true, cwd: '<%= distdir %>/' }]
+ files: [
+ //everything except the index.html root file!
+ //then we need to figure out how to not copy all the test stuff either!?
+ { dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' },
+ { dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' },
+ { dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' },
+ { dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' }
+ ]
},
packages: {
files: [{ dest: '<%= vsdir %>/../App_Plugins', src : '**', expand: true, cwd: 'src/packages/' }]
diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js
index 4f2494d3a7..e19438d3ef 100644
--- a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js
+++ b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js
@@ -3,16 +3,16 @@
//extensions to base classes such as String and extension methods for jquery.
//NOTE: jquery must be loaded before this file.
- //create guid object on the window (which makes it global)
- if (window.Guid == null) {
- window.Guid = {
- generate: function () {
- ///generates a new Guid
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
- var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
- return v.toString(16);
- });
- }
+
+
+ //create guid method on the String
+ if (String.CreateGuid == null) {
+ String.CreateGuid = function () {
+ ///generates a new Guid
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
};
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js
index 79b52c4cca..2a51738bbd 100644
--- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js
+++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js
@@ -15,6 +15,8 @@ Umbraco.Sys.ServerVariables = {
"legacyTreeJs": "/belle/lib/yepnope/empty.js",
},
umbracoSettings: {
- "umbracoPath": "/umbraco"
- }
+ "umbracoPath": "/umbraco",
+ "imageFileTypes": "jpeg,jpg,gif,bmp,png,tiff,tif"
+ },
+ isDebuggingEnabled: true
};
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js
new file mode 100644
index 0000000000..e2cac02d83
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js
@@ -0,0 +1,236 @@
+
+/**
+* @ngdoc service
+* @name umbraco.services.contentEditingHelper
+* @description A helper service for content controllers when editing/creating/saving content.
+**/
+function contentEditingHelper($location, $routeParams, notificationsService, serverValidationManager, dialogService) {
+
+ return {
+
+ /**
+ * @ngdoc function
+ * @name getAllProps
+ * @methodOf contentEditingHelper
+ * @function
+ *
+ * @description
+ * Returns all propertes contained for the content item (since the normal model has properties contained inside of tabs)
+ */
+ getAllProps: function (content) {
+ var allProps = [];
+
+ for (var i = 0; i < content.tabs.length; i++) {
+ for (var p = 0; p < content.tabs[i].properties.length; p++) {
+ allProps.push(content.tabs[i].properties[p]);
+ }
+ }
+
+ return allProps;
+ },
+
+ /**
+ * @ngdoc function
+ * @name reBindChangedProperties
+ * @methodOf contentEditingHelper
+ * @function
+ *
+ * @description
+ * re-binds all changed property values to the origContent object from the newContent object and returns an array of changed properties.
+ */
+ reBindChangedProperties: function (origContent, newContent) {
+
+ var changed = [];
+
+ //get a list of properties since they are contained in tabs
+ var allOrigProps = this.getAllProps(origContent);
+ var allNewProps = this.getAllProps(newContent);
+
+ function getNewProp(alias) {
+ return _.find(allNewProps, function (item) {
+ return item.alias === alias;
+ });
+ }
+
+ for (var p in allOrigProps) {
+ var newProp = getNewProp(allOrigProps[p].alias);
+ if (!_.isEqual(allOrigProps[p].value, newProp.value)) {
+ //they have changed so set the origContent prop's value to the new value
+ allOrigProps[p].value = newProp.value;
+ changed.push(allOrigProps[p]);
+ }
+ }
+
+ return changed;
+ },
+
+ /**
+ * @ngdoc function
+ * @name handleValidationErrors
+ * @methodOf contentEditingHelper
+ * @function
+ *
+ * @description
+ * A function to handle the validation (modelState) errors collection which will happen on a 403 error indicating validation errors
+ * It's worth noting that when a 403 occurs, the data is still saved just never published, though this depends on if the entity is a new
+ * entity and whether or not the data fulfils the absolute basic requirements like having a mandatory Name.
+ */
+ handleValidationErrors: function (content, modelState) {
+ //get a list of properties since they are contained in tabs
+ var allProps = this.getAllProps(content);
+
+ //find the content property for the current error, for use in the loop below
+ function findContentProp(props, propAlias) {
+ return _.find(props, function (item) {
+ return (item.alias === propAlias);
+ });
+ }
+
+ for (var e in modelState) {
+ //the alias in model state can be in dot notation which indicates
+ // * the first part is the content property alias
+ // * the second part is the field to which the valiation msg is associated with
+ //There will always be at least 2 parts since all model errors for properties are prefixed with "Properties"
+ var parts = e.split(".");
+ if (parts.length > 1) {
+ var propertyAlias = parts[1];
+
+ //find the content property for the current error
+ var contentProperty = findContentProp(allProps, propertyAlias);
+
+ if (contentProperty) {
+ //if it contains 2 '.' then we will wire it up to a property's field
+ if (parts.length > 2) {
+ //add an error with a reference to the field for which the validation belongs too
+ serverValidationManager.addPropertyError(contentProperty, parts[2], modelState[e][0]);
+ }
+ else {
+ //add a generic error for the property, no reference to a specific field
+ serverValidationManager.addPropertyError(contentProperty, "", modelState[e][0]);
+ }
+ }
+ }
+ else {
+ //the parts are only 1, this means its not a property but a native content property
+ serverValidationManager.addFieldError(parts[0], modelState[e][0]);
+ }
+
+ //add to notifications
+ notificationsService.error("Validation", modelState[e][0]);
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name handleSaveError
+ * @methodOf contentEditingHelper
+ * @function
+ *
+ * @description
+ * A function to handle what happens when we have validation issues from the server side
+ */
+ handleSaveError: function (err, scope) {
+ //When the status is a 403 status, we have validation errors.
+ //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something).
+ //Or, some strange server error
+ if (err.status === 403) {
+ //now we need to look through all the validation errors
+ if (err.data && (err.data.modelState)) {
+
+ this.handleValidationErrors(err.data, err.data.modelState);
+
+ if (!this.redirectToCreatedContent(err.data.id, err.data.modelState)) {
+ //we are not redirecting because this is not new content, it is existing content. In this case
+ // we need to detect what properties have changed and re-bind them with the server data. Then we need
+ // to re-bind any server validation errors after the digest takes place.
+ this.reBindChangedProperties(scope.content, err.data);
+ serverValidationManager.executeAndClearAllSubscriptions();
+ }
+
+ //indicates we've handled the server result
+ return true;
+ }
+ else {
+ dialogService.ysodDialog(err);
+ }
+ }
+ else {
+ dialogService.ysodDialog(err);
+ }
+
+ return false;
+ },
+
+ /**
+ * @ngdoc function
+ * @name handleSaveError
+ * @methodOf handleSuccessfulSave
+ * @function
+ *
+ * @description
+ * A function to handle when saving a content item is successful. This will rebind the values of the model that have changed
+ * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect
+ * when we're creating new content.
+ */
+ handleSuccessfulSave: function (args) {
+
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.scope) {
+ throw "args.scope cannot be null";
+ }
+ if (!args.scope.content) {
+ throw "args.scope.content cannot be null";
+ }
+ if (!args.newContent) {
+ throw "args.newContent cannot be null";
+ }
+
+ for (var i = 0; i < args.newContent.notifications.length; i++) {
+ notificationsService.showNotification(args.newContent.notifications[i]);
+ }
+
+ args.scope.$broadcast("saved", { scope: args.scope });
+ if (!this.redirectToCreatedContent(args.scope.content.id)) {
+ //we are not redirecting because this is not new content, it is existing content. In this case
+ // we need to detect what properties have changed and re-bind them with the server data
+ this.reBindChangedProperties(args.scope.content, args.newContent);
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name redirectToCreatedContent
+ * @methodOf contentEditingHelper
+ * @function
+ *
+ * @description
+ * Changes the location to be editing the newly created content after create was successful.
+ * We need to decide if we need to redirect to edito mode or if we will remain in create mode.
+ * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name.
+ */
+ redirectToCreatedContent: function (id, modelState) {
+
+ //only continue if we are currently in create mode and if there is no 'Name' modelstate errors
+ // since we need at least a name to create content.
+ if ($routeParams.create && (!modelState || !modelState["Name"])) {
+
+ //need to change the location to not be in 'create' mode. Currently the route will be something like:
+ // /belle/#/content/edit/1234?doctype=newsArticle&create=true
+ // but we need to remove everything after the query so that it is just:
+ // /belle/#/content/edit/9876 (where 9876 is the new id)
+
+ //clear the query strings
+ $location.search(null);
+ //change to new path
+ $location.path("/" + $routeParams.section + "/" + $routeParams.method + "/" + id);
+ //don't add a browser history for this
+ $location.replace();
+ return true;
+ }
+ return false;
+ }
+ };
+}
+angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js
index ed6fd2fcfa..5ffd67efad 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js
@@ -340,6 +340,27 @@ angular.module('umbraco.services')
template: 'views/common/dialogs/property.html',
show: true
});
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#ysodDialog
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a dialog to show a custom YSOD
+ */
+ ysodDialog: function (ysodError) {
+
+ var newScope = $rootScope.$new();
+ newScope.error = ysodError;
+ return openDialog({
+ modalClass: "umb-modal wide",
+ scope: newScope,
+ //callback: options.callback,
+ template: 'views/common/dialogs/ysod.html',
+ show: true
+ });
}
};
}]);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js
index e90ff8a244..f21909b048 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js
@@ -26,23 +26,82 @@ angular.module('umbraco.services')
var nArray = [];
- function add(item) {
- var index = nArray.length;
- nArray.push(item);
+ function add(item) {
+ angularHelper.safeApply($rootScope, function () {
+
+ //we need to ID the item, going by index isn't good enough because people can remove at different indexes
+ // whenever they want. Plus once we remove one, then the next index will be different. The only way to
+ // effectively remove an item is by an Id.
+ item.id = String.CreateGuid();
+
+ nArray.push(item);
+
+ $timeout(function () {
+ var found = _.find(nArray, function(i) {
+ return i.id === item.id;
+ });
+ if (found) {
+ var index = nArray.indexOf(found);
+ nArray.splice(index, 1);
+ }
+
+ }, 7000);
- $timeout(function () {
- angularHelper.safeApply($rootScope, function () {
- nArray.splice(index, 1);
- });
-
- }, 7000);
-
- return nArray[index];
+ return item;
+ });
}
return {
- /**
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.notificationsService#showNotification
+ * @methodOf umbraco.services.notificationsService
+ *
+ * @description
+ * Shows a notification based on the object passed in, normally used to render notifications sent back from the server
+ *
+ * @returns {Object} args notification object
+ */
+ showNotification: function(args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.type) {
+ throw "args.type cannot be null";
+ }
+ if (!args.header) {
+ throw "args.header cannot be null";
+ }
+ if (!args.message) {
+ throw "args.message cannot be null";
+ }
+ switch(args.type) {
+ case 0:
+ //save
+ this.success(args.header, args.message);
+ break;
+ case 1:
+ //info
+ this.success(args.header, args.message);
+ break;
+ case 2:
+ //error
+ this.error(args.header, args.message);
+ break;
+ case 3:
+ //success
+ this.success(args.header, args.message);
+ break;
+ case 4:
+ //warning
+ this.warning(args.header, args.message);
+ break;
+ }
+ },
+
+ /**
* @ngdoc method
* @name umbraco.services.notificationsService#success
* @methodOf umbraco.services.notificationsService
@@ -56,9 +115,8 @@ angular.module('umbraco.services')
* @returns {Object} notification object
*/
success: function (headline, message) {
- angularHelper.safeApply($rootScope, function () {
- return add({ headline: headline, message: message, type: 'success', time: new Date() });
- });
+ return add({ headline: headline, message: message, type: 'success', time: new Date() });
+
},
/**
* @ngdoc method
@@ -74,9 +132,7 @@ angular.module('umbraco.services')
* @returns {Object} notification object
*/
error: function (headline, message) {
- angularHelper.safeApply($rootScope, function() {
- return add({ headline: headline, message: message, type: 'error', time: new Date() });
- });
+ return add({ headline: headline, message: message, type: 'error', time: new Date() });
},
/**
@@ -94,9 +150,7 @@ angular.module('umbraco.services')
* @returns {Object} notification object
*/
warning: function (headline, message) {
- angularHelper.safeApply($rootScope, function() {
- return add({ headline: headline, message: message, type: 'warning', time: new Date() });
- });
+ return add({ headline: headline, message: message, type: 'warning', time: new Date() });
},
/**
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
new file mode 100644
index 0000000000..6574f078a4
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
@@ -0,0 +1,269 @@
+/**
+* @ngdoc service
+* @name umbraco.services.umbRequestHelper
+* @description A helper object used for sending requests to the server
+**/
+function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService) {
+ return {
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.umbRequestHelper#dictionaryToQueryString
+ * @methodOf umbraco.services.umbRequestHelper
+ * @function
+ *
+ * @description
+ * This will turn an array of key/value pairs into a query string
+ *
+ * @param {Array} queryStrings An array of key/value pairs
+ */
+ dictionaryToQueryString: function (queryStrings) {
+
+ if (!angular.isArray(queryStrings)) {
+ throw "The queryString parameter is not an array of key value pairs";
+ }
+
+ return _.map(queryStrings, function (item) {
+ var key = null;
+ var val = null;
+ for (var k in item) {
+ key = k;
+ val = item[k];
+ break;
+ }
+ if (key == null || val == null) {
+ throw "The object in the array was not formatted as a key/value pair";
+ }
+ return key + "=" + val;
+ }).join("&");
+
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.umbRequestHelper#getApiUrl
+ * @methodOf umbraco.services.umbRequestHelper
+ * @function
+ *
+ * @description
+ * This will return the webapi Url for the requested key based on the servervariables collection
+ *
+ * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary
+ * @param {string} actionName The webapi action name
+ * @param {object} queryStrings Can be either a string or an array containing key/value pairs
+ */
+ getApiUrl: function (apiName, actionName, queryStrings) {
+ if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) {
+ throw "No server variables defined!";
+ }
+
+ if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) {
+ throw "No url found for api name " + apiName;
+ }
+
+ return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName +
+ (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings)));
+
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.umbRequestHelper#resourcePromise
+ * @methodOf umbraco.services.umbRequestHelper
+ * @function
+ *
+ * @description
+ * This returns a promise with an underlying http call, it is a helper method to reduce
+ * the amount of duplicate code needed to query http resources and automatically handle any
+ * Http errors. See /docs/source/using-promises-resources.md
+ *
+ * @param {object} opts A mixed object which can either be a string representing the error message to be
+ * returned OR an object containing either:
+ * { success: successCallback, errorMsg: errorMessage }
+ * OR
+ * { success: successCallback, error: errorCallback }
+ * In both of the above, the successCallback must accept these parameters: data, status, headers, config
+ * If using the errorCallback it must accept these parameters: data, status, headers, config
+ * The success callback must return the data which will be resolved by the deferred object.
+ * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData }
+ */
+ resourcePromise: function (httpPromise, opts) {
+ var deferred = $q.defer();
+
+ /** The default success callback used if one is not supplied in the opts */
+ function defaultSuccess(data, status, headers, config) {
+ //when it's successful, just return the data
+ return data;
+ }
+
+ /** The default error callback used if one is not supplied in the opts */
+ function defaultError(data, status, headers, config) {
+ return {
+ //NOTE: the default error message here should never be used based on the above docs!
+ errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'),
+ data: data
+ };
+ }
+
+ //create the callbacs based on whats been passed in.
+ var callbacks = {
+ success: (!opts.success ? defaultSuccess : opts.success),
+ error: (!opts.error ? defaultError : opts.error)
+ };
+
+ httpPromise.success(function (data, status, headers, config) {
+
+ //invoke the callback
+ var result = callbacks.success.apply(this, [data, status, headers, config]);
+
+ //when it's successful, just return the data
+ deferred.resolve(result);
+
+ }).error(function (data, status, headers, config) {
+
+ //invoke the callback
+ var result = callbacks.error.apply(this, [data, status, headers, config]);
+
+ //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled.
+ if (status >= 500 && status < 600 && Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) {
+
+ dialogService.ysodDialog({
+ errorMsg: result.errorMsg,
+ data: result.data
+ });
+ }
+ else {
+
+ //return an error object including the error message for UI
+ deferred.reject({
+ errorMsg: result.errorMsg,
+ data: result.data
+ });
+
+ }
+
+ });
+
+ return deferred.promise;
+
+ },
+
+ /** Used for saving media/content specifically */
+ postSaveContent: function (restApiUrl, content, action, files) {
+
+ var deferred = $q.defer();
+
+ //save the active tab id so we can set it when the data is returned.
+ var activeTab = _.find(content.tabs, function (item) {
+ return item.active;
+ });
+ var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab));
+
+ //save the data
+ this.postMultiPartRequest(
+ restApiUrl,
+ { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) },
+ function (data, formData) {
+ //now add all of the assigned files
+ for (var f in files) {
+ //each item has a property id and the file object, we'll ensure that the id is suffixed to the key
+ // so we know which property it belongs to on the server side
+ formData.append("file_" + files[f].id, files[f].file);
+ }
+
+ },
+ function (data, status, headers, config) {
+ //success callback
+
+ //reset the tabs and set the active one
+ _.each(data.tabs, function (item) {
+ item.active = false;
+ });
+ data.tabs[activeTabIndex].active = true;
+
+ //the data returned is the up-to-date data so the UI will refresh
+ deferred.resolve(data);
+ },
+ function (data, status, headers, config) {
+ //failure callback
+
+ //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled.
+ if (status >= 500 && status < 600 && Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) {
+
+ dialogService.ysodDialog({
+ errorMsg: 'An error occurred',
+ data: data
+ });
+ }
+ else {
+
+ //return an error object including the error message for UI
+ deferred.reject({
+ errorMsg: 'An error occurred',
+ data: data,
+ status: status
+ });
+ }
+
+ });
+
+ return deferred.promise;
+ },
+
+ /** Posts a multi-part mime request to the server */
+ postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) {
+
+ //validate input, jsonData can be an array of key/value pairs or just one key/value pair.
+ if (!jsonData) { throw "jsonData cannot be null"; }
+
+ if (angular.isArray(jsonData)) {
+ _.each(jsonData, function (item) {
+ if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; }
+ });
+ }
+ else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; }
+
+
+ $http({
+ method: 'POST',
+ url: url,
+ //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files
+ // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request
+ // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false'
+ // will force the request to automatically populate the headers properly including the boundary parameter.
+ headers: { 'Content-Type': false },
+ transformRequest: function (data) {
+ var formData = new FormData();
+ //add the json data
+ if (angular.isArray(data)) {
+ _.each(data, function (item) {
+ formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value);
+ });
+ }
+ else {
+ formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value);
+ }
+
+ //call the callback
+ if (transformCallback) {
+ transformCallback.apply(this, [data, formData]);
+ }
+
+ return formData;
+ },
+ data: jsonData
+ }).
+ success(function (data, status, headers, config) {
+ if (successCallback) {
+ successCallback.apply(this, [data, status, headers, config]);
+ }
+ }).
+ error(function (data, status, headers, config) {
+ if (failureCallback) {
+ failureCallback.apply(this, [data, status, headers, config]);
+ }
+ });
+ }
+ };
+}
+angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
index 60d12f376a..ebf5ad030c 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
@@ -1,6 +1,5 @@
/*Contains multiple services for various helper tasks */
-
/**
* @ngdoc function
* @name umbraco.services.legacyJsLoader
@@ -209,15 +208,16 @@ function umbImageHelper() {
return item.alias === 'umbracoFile';
});
var imageVal;
- //Legacy images will be saved as a string, not an array so we will convert the legacy values
- // to our required structure.
- if (imageProp.value.startsWith('[')) {
- imageVal = options.scope.$eval(imageProp.value);
- }
- else {
- imageVal = [{ file: imageProp.value, isImage: this.detectIfImageByExtension(imageProp.value) }];
- }
+ //our default images might store one or many images (as csv)
+ var split = imageProp.value.split(',');
+ var self = this;
+ imageVal = _.map(split, function(item) {
+ return { file: item, isImage: self.detectIfImageByExtension(item) };
+ });
+
+ //for now we'll just return the first image in the collection.
+ //TODO: we should enable returning many to be displayed in the picker if the uploader supports many.
if (imageVal.length && imageVal.length > 0 && imageVal[0].isImage) {
return imageVal[0].file;
}
@@ -243,264 +243,13 @@ function umbImageHelper() {
},
detectIfImageByExtension: function(imagePath) {
var lowered = imagePath;
- if (lowered.endsWith(".jpg") || lowered.endsWith(".gif") || lowered.endsWith(".jpeg") || lowered.endsWith(".png")) {
- return true;
- }
- return false;
+ var ext = lowered.substr(lowered.lastIndexOf(".") + 1);
+ return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1;
}
};
}
angular.module('umbraco.services').factory('umbImageHelper', umbImageHelper);
-/**
-* @ngdoc service
-* @name umbraco.services.umbRequestHelper
-* @description A helper object used for sending requests to the server
-**/
-function umbRequestHelper($http, $q, umbDataFormatter, angularHelper) {
- return {
-
- /**
- * @ngdoc method
- * @name umbraco.services.umbRequestHelper#dictionaryToQueryString
- * @methodOf umbraco.services.umbRequestHelper
- * @function
- *
- * @description
- * This will turn an array of key/value pairs into a query string
- *
- * @param {Array} queryStrings An array of key/value pairs
- */
- dictionaryToQueryString: function(queryStrings) {
-
- if (!angular.isArray(queryStrings)) {
- throw "The queryString parameter is not an array of key value pairs";
- }
-
- return _.map(queryStrings, function (item) {
- var key = null;
- var val = null;
- for (var k in item) {
- key = k;
- val = item[k];
- break;
- }
- if (key == null || val == null) {
- throw "The object in the array was not formatted as a key/value pair";
- }
- return key + "=" + val;
- }).join("&");
-
- },
-
- /**
- * @ngdoc method
- * @name umbraco.services.umbRequestHelper#getApiUrl
- * @methodOf umbraco.services.umbRequestHelper
- * @function
- *
- * @description
- * This will return the webapi Url for the requested key based on the servervariables collection
- *
- * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary
- * @param {string} actionName The webapi action name
- * @param {object} queryStrings Can be either a string or an array containing key/value pairs
- */
- getApiUrl: function(apiName, actionName, queryStrings) {
- if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) {
- throw "No server variables defined!";
- }
-
- if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) {
- throw "No url found for api name " + apiName;
- }
-
- return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName +
- (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings)));
-
- },
-
- /**
- * @ngdoc function
- * @name umbraco.services.umbRequestHelper#resourcePromise
- * @methodOf umbraco.services.umbRequestHelper
- * @function
- *
- * @description
- * This returns a promise with an underlying http call, it is a helper method to reduce
- * the amount of duplicate code needed to query http resources and automatically handle any
- * Http errors. See /docs/source/using-promises-resources.md
- *
- * @param {object} opts A mixed object which can either be a string representing the error message to be
- * returned OR an object containing either:
- * { success: successCallback, errorMsg: errorMessage }
- * OR
- * { success: successCallback, error: errorCallback }
- * In both of the above, the successCallback must accept these parameters: data, status, headers, config
- * If using the errorCallback it must accept these parameters: data, status, headers, config
- * The success callback must return the data which will be resolved by the deferred object.
- * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData }
- */
- resourcePromise: function (httpPromise, opts) {
- var deferred = $q.defer();
-
- /** The default success callback used if one is not supplied in the opts */
- function defaultSuccess(data, status, headers, config) {
- //when it's successful, just return the data
- return data;
- }
-
- /** The default error callback used if one is not supplied in the opts */
- function defaultError(data, status, headers, config) {
- return {
- //NOTE: the default error message here should never be used based on the above docs!
- errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'),
- data: data
- };
- }
-
- //create the callbacs based on whats been passed in.
- var callbacks = {
- success: (!opts.success ? defaultSuccess : opts.success),
- error: (!opts.error ? defaultError : opts.error)
- };
-
- httpPromise.success(function (data, status, headers, config) {
-
- //invoke the callback
- var result = callbacks.success.apply(this, [data, status, headers, config]);
-
- //when it's successful, just return the data
- deferred.resolve(result);
-
- }).error(function (data, status, headers, config) {
-
- //invoke the callback
- var result = callbacks.error.apply(this, [data, status, headers, config]);
-
- //when there's an erorr...
- // TODO: Deal with the error in a global way
-
- //return an error object including the error message for UI
- deferred.reject({
- errorMsg: result.errorMsg,
- data: result.data
- });
-
- });
-
- return deferred.promise;
-
- },
-
- /** Used for saving media/content specifically */
- postSaveContent: function (restApiUrl, content, action, files) {
-
- var deferred = $q.defer();
-
- //save the active tab id so we can set it when the data is returned.
- var activeTab = _.find(content.tabs, function (item) {
- return item.active;
- });
- var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab));
-
- //save the data
- this.postMultiPartRequest(
- restApiUrl,
- { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) },
- function (data, formData) {
- //now add all of the assigned files
- for (var f in files) {
- //each item has a property id and the file object, we'll ensure that the id is suffixed to the key
- // so we know which property it belongs to on the server side
- formData.append("file_" + files[f].id, files[f].file);
- }
-
- },
- function (data, status, headers, config) {
- //success callback
-
- //reset the tabs and set the active one
- _.each(data.tabs, function (item) {
- item.active = false;
- });
- data.tabs[activeTabIndex].active = true;
-
- //the data returned is the up-to-date data so the UI will refresh
- deferred.resolve(data);
- },
- function (data, status, headers, config) {
- //failure callback
-
- deferred.reject({
- data: data,
- status: status,
- headers: headers,
- config: config
- });
- });
-
- return deferred.promise;
- },
-
- /** Posts a multi-part mime request to the server */
- postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) {
-
- //validate input, jsonData can be an array of key/value pairs or just one key/value pair.
- if (!jsonData) {throw "jsonData cannot be null";}
-
- if (angular.isArray(jsonData)) {
- _.each(jsonData, function (item) {
- if (!item.key || !item.value){throw "jsonData array item must have both a key and a value property";}
- });
- }
- else if (!jsonData.key || !jsonData.value){throw "jsonData object must have both a key and a value property";}
-
-
- $http({
- method: 'POST',
- url: url,
- //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files
- // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request
- // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false'
- // will force the request to automatically populate the headers properly including the boundary parameter.
- headers: { 'Content-Type': false },
- transformRequest: function (data) {
- var formData = new FormData();
- //add the json data
- if (angular.isArray(data)) {
- _.each(data, function (item) {
- formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value);
- });
- }
- else {
- formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value);
- }
-
- //call the callback
- if (transformCallback) {
- transformCallback.apply(this, [data, formData]);
- }
-
- return formData;
- },
- data: jsonData
- }).
- success(function (data, status, headers, config) {
- if (successCallback) {
- successCallback.apply(this, [data, status, headers, config]);
- }
- }).
- error(function (data, status, headers, config) {
- if (failureCallback) {
- failureCallback.apply(this, [data, status, headers, config]);
- }
- });
- }
- };
-}
-angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper);
-
/**
* @ngdoc service
* @name umbraco.services.umbDataFormatter
@@ -667,9 +416,9 @@ angular.module('umbraco.services').factory('iconHelper', iconHelper);
* @description A helper service for content controllers when editing/creating/saving content.
**/
function contentEditingHelper($location, $routeParams, notificationsService, serverValidationManager) {
+ return {
+
- return {
-
/**
* @ngdoc method
* @name umbraco.services.contentEditingHelper#handleValidationErrors
@@ -733,7 +482,7 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
},
/**
- * @ngdoc function
+ * @ngdoc method
* @name umbraco.services.contentEditingHelper#handleSaveError
* @methodOf umbraco.services.contentEditingHelper
* @function
@@ -775,7 +524,7 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
},
/**
- * @ngdoc function
+ * @ngdoc method
* @name umbraco.services.contentEditingHelper#redirectToCreatedContent
* @methodOf umbraco.services.contentEditingHelper
* @function
@@ -808,4 +557,4 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
}
};
}
-angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper);
\ No newline at end of file
+angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper);
diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less
index 89f761f4a0..07149a27a5 100644
--- a/src/Umbraco.Web.UI.Client/src/less/modals.less
+++ b/src/Umbraco.Web.UI.Client/src/less/modals.less
@@ -95,6 +95,10 @@
}
+.umb-modal.fade.in.wide {
+ margin-left: -640px;
+ width: 640px !important;
+}
/* MEDIA PICKER */
.umb-modal .umb-btn-toolbar {
@@ -132,9 +136,9 @@
border:none;
}
-.umb-modal .thumbnails > li:nth-child(2) {
+/*.umb-modal .thumbnails > li:nth-child(2) {
margin: 0 0 20px 0
-}
+}*/
.umb-modal .thumbnails > li {
margin: 0 20px 20px 0;
@@ -142,7 +146,7 @@
}
.umb-modal .thumbnail img {
- height: 120px
+ /*height: 100px;*/
}
.umb-modal .thumbnails .selected img, .umb-modal .thumbnails img:hover {
@@ -150,8 +154,8 @@
}
.umb-modal .thumbnails > li.folder {
- width: 120px;
- height: 120px;
+ width: 100px;
+ /*height: 100px;*/
display: block;
background: @grayLighter;
text-align: center;
@@ -161,15 +165,48 @@
.umb-modal .thumbnails > li.folder .icon-folder-close {
color: @grayLight;
display: block;
- font-size: 96px
+ font-size: 96px;
+ width:100px;
}
.umb-modal .thumbnails > li.folder a {
- width: 120px;
- height: 120px;
+ width: 100px;
+ height: 100px;
display: block
}
.umb-modal .thumbnails > li.folder a:hover {
text-decoration: none
}
+
+
+/* YSOD */
+
+/* These styles are an exact replica of a real .Net YSOD */
+.umb-modal .ysod {
+ font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;padding:5px;
+}
+.umb-modal .ysod > div {
+ font-family: Arial, Helvetica, Geneva, SunSans-Regular, sans-serif;
+ line-height: 13px;
+ font-size: 11px;
+}
+.umb-modal .ysod hr {
+ margin: 0px;
+ color: silver;
+ background-color: silver;
+ height: 1px;
+}
+.umb-modal .ysod p {font-family:"Verdana";font-weight:normal;color:black;}
+.umb-modal .ysod b {font-family:"Verdana";font-weight:bold;color:black;}
+.umb-modal .ysod h1 { font-family:"Verdana";font-weight:normal;font-size:18pt;color:red;padding: 0px;text-transform: none !important;margin: 0px; }
+.umb-modal .ysod h2 { font-style:italic; font-family:"Verdana";font-weight:normal;font-size:14pt;color:maroon; }
+.umb-modal .ysod pre {border:none;font-family:"Consolas","Lucida Console",Monospace;font-size:13px;margin:0;padding:0.5em;line-height:17px;}
+.umb-modal .ysod .marker {font-weight: bold; color: black;text-decoration: none;}
+.umb-modal .ysod .version {color: gray;}
+.umb-modal .ysod .error {margin-bottom: 10px;}
+.umb-modal .ysod .expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:pointer; }
+.umb-modal .ysod table {background-color:#ffffcc;width:100%;}
+.umb-modal .ysod table pre {background-color: inherit;}
+.umb-modal .ysod table code {background-color: inherit;}
+.umb-modal .ysod a.btn { margin-top:10px;margin-right:10px;float:right;}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js
index 880f190aa4..84bd4ce84f 100644
--- a/src/Umbraco.Web.UI.Client/src/routes.js
+++ b/src/Umbraco.Web.UI.Client/src/routes.js
@@ -48,4 +48,4 @@ app.run(['userService', function (userService) {
// Get the current user when the application starts
// (in case they are still logged in from a previous session)
userService.isAuthenticated();
-}]);
\ No newline at end of file
+}]);
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.controller.js
new file mode 100644
index 0000000000..46abf5c88a
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.controller.js
@@ -0,0 +1,22 @@
+/**
+ * @ngdoc controller
+ * @name Umbraco.Dialogs.LegacyDeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function YsodController($scope, legacyResource, treeService, navigationService) {
+
+ if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) {
+ //trim whitespace
+ $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim();
+ }
+
+ $scope.closeDialog = function() {
+ $scope.dismiss();
+ };
+
+}
+
+angular.module("umbraco").controller("Umbraco.Dialogs.YsodController", YsodController);
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.html
new file mode 100644
index 0000000000..a096546090
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/ysod.html
@@ -0,0 +1,28 @@
+
+
+
Close
+
+
{{error.errorMsg}}
+
+
{{error.data.ExceptionMessage || error.data.Message}}
+
+
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
+
+
+
Exception Details: {{error.data.ExceptionType}}: {{error.data.ExceptionMessage}}
+
+
+
Stack Trace:
+
+
+
+
+
+
+ {{error.data.StackTrace}}
+
+
+
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js
index b24c35e0db..3f047288ca 100644
--- a/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js
@@ -60,17 +60,12 @@ function ContentEditController($scope, $routeParams, $location, contentResource,
contentResource.publishContent(cnt, $routeParams.create, $scope.files)
.then(function (data) {
- //TODO: only update the content that has changed!
- $scope.content = data;
-
- notificationsService.success("Published", "Content has been saved and published");
- $scope.$broadcast("saved", { scope: $scope });
-
- contentEditingHelper.redirectToCreatedContent($scope.content.id);
- }, function (err) {
- //TODO: only update the content that has changed!
- //$scope.content = err.data;
- contentEditingHelper.handleSaveError(err);
+ contentEditingHelper.handleSuccessfulSave({
+ scope: $scope,
+ newContent: data
+ });
+ }, function (err) {
+ contentEditingHelper.handleSaveError(err, $scope);
});
};
@@ -85,17 +80,12 @@ function ContentEditController($scope, $routeParams, $location, contentResource,
contentResource.saveContent(cnt, $routeParams.create, $scope.files)
.then(function (data) {
- //TODO: only update the content that has changed!
- $scope.content = data;
-
- notificationsService.success("Saved", "Content has been saved");
- $scope.$broadcast("saved", { scope: $scope });
-
- contentEditingHelper.redirectToCreatedContent($scope.content.id);
+ contentEditingHelper.handleSuccessfulSave({
+ scope: $scope,
+ newContent: data
+ });
}, function (err) {
- //TODO: only update the content that has changed!
- //$scope.content = err.data;
- contentEditingHelper.handleSaveError(err);
+ contentEditingHelper.handleSaveError(err, $scope);
});
};
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html
index 8ff21f8015..dc4fe83b0c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html
+++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html
@@ -6,23 +6,12 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js
index 3fa3120372..76f9eaa52e 100644
--- a/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js
@@ -1,4 +1,4 @@
-function mediaEditController($scope, $routeParams, mediaResource, notificationsService) {
+function mediaEditController($scope, $routeParams, mediaResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper) {
if ($routeParams.create) {
@@ -13,6 +13,13 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS
.then(function (data) {
$scope.contentLoaded = true;
$scope.content = data;
+
+ //in one particular special case, after we've created a new item we redirect back to the edit
+ // route but there might be server validation errors in the collection which we need to display
+ // after the redirect, so we will bind all subscriptions which will show the server validation errors
+ // if there are any and then clear them so the collection no longer persists them.
+ serverValidationManager.executeAndClearAllSubscriptions();
+
});
}
@@ -28,22 +35,27 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS
}
};
- //TODO: Clean this up and share this code with the content editor
- $scope.saveAndPublish = function (cnt) {
- mediaResource.saveMedia(cnt, $routeParams.create, $scope.files)
- .then(function (data) {
- $scope.content = data;
- notificationsService.success("Published", "Media has been saved and published");
- });
- };
+ //ensure there is a form object assigned.
+ var currentForm = angularHelper.getRequiredCurrentForm($scope);
+
+ $scope.save = function (cnt) {
+
+ $scope.$broadcast("saving", { scope: $scope });
+
+ //don't continue if the form is invalid
+ if (currentForm.$invalid) return;
+
+ serverValidationManager.reset();
- //TODO: Clean this up and share this code with the content editor
- $scope.save = function (cnt) {
mediaResource.saveMedia(cnt, $routeParams.create, $scope.files)
.then(function (data) {
- $scope.content = data;
- notificationsService.success("Saved", "Media has been saved");
- });
+ contentEditingHelper.handleSuccessfulSave({
+ scope: $scope,
+ newContent: data
+ });
+ }, function (err) {
+ contentEditingHelper.handleSaveError(err, $scope);
+ });
};
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js
index 550336d28f..51b51de12d 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js
@@ -20,28 +20,24 @@ function fileUploadController($scope, $element, $compile, umbImageHelper) {
//clear the current files
$scope.files = [];
+
//create the property to show the list of files currently saved
if ($scope.model.value != "") {
- //for legacy data, this will not be an array, just a string so convert to an array
- if (!$scope.model.value.startsWith('[')) {
-
- //check if it ends with a common image extensions
- var isImage = umbImageHelper.detectIfImageByExtension($scope.model.value);
- $scope.model.value = "[{\"file\": \"" + $scope.model.value + "\",\"isImage\":" + isImage + "}]";
- }
-
- $scope.persistedFiles = angular.fromJson($scope.model.value);
+ var images = $scope.model.value.split(",");
+
+ $scope.persistedFiles = _.map(images, function (item) {
+ return { file: item, isImage: umbImageHelper.detectIfImageByExtension(item) };
+ });
}
else {
$scope.persistedFiles = [];
}
-
+
_.each($scope.persistedFiles, function (file) {
file.thumbnail = umbImageHelper.getThumbnailFromPath(file.file);
});
-
-
+
$scope.clearFiles = false;
//listen for clear files changes to set our model to be sent up to the server
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html
index 1bd79dadb3..9cbbf816af 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html
@@ -6,11 +6,10 @@
+
-
-
Current files
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/umbraco/readonlyvalue/readonlyvalue.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/umbraco/readonlyvalue/readonlyvalue.html
deleted file mode 100644
index 738adb1ff6..0000000000
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/umbraco/readonlyvalue/readonlyvalue.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
- {{model.value|json}}
-
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js
index e52bdd3f7e..dee7d6dfcf 100644
--- a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js
+++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js
@@ -22,10 +22,10 @@ describe('contentEditingHelper tests', function () {
data: content,
status: 403
};
- err.data.ModelState = {};
+ err.data.modelState = {};
//act
- var handled = contentEditingHelper.handleSaveError(err);
+ var handled = contentEditingHelper.handleSaveError(err, {content: content});
//assert
expect(handled).toBe(true);
@@ -39,7 +39,7 @@ describe('contentEditingHelper tests', function () {
};
//act
- var handled = contentEditingHelper.handleSaveError(err);
+ var handled = contentEditingHelper.handleSaveError(err, null);
//assert
expect(handled).toBe(false);
@@ -55,7 +55,7 @@ describe('contentEditingHelper tests', function () {
};
//act
- var handled = contentEditingHelper.handleSaveError(err);
+ var handled = contentEditingHelper.handleSaveError(err, { content: content });
//assert
expect(handled).toBe(false);
@@ -183,4 +183,32 @@ describe('contentEditingHelper tests', function () {
});
});
+
+ describe('wires up property values after saving', function () {
+
+ it('does not update un-changed values', function () {
+
+ //arrange
+ var origContent = mocksUtils.getMockContent(1234);
+ origContent.tabs[0].properties[0].value = { complex1: "origValue1a", complex2: "origValue1b" };
+ origContent.tabs[1].properties[0].value = "origValue2";
+ origContent.tabs[1].properties[1].value = "origValue3";
+ origContent.tabs[1].properties[2].value = "origValue4";
+
+ var newContent = mocksUtils.getMockContent(1234);
+ newContent.tabs[0].properties[0].value = { complex1: "origValue1a", complex2: "newValue1b" };
+ newContent.tabs[1].properties[0].value = "origValue2";
+ newContent.tabs[1].properties[1].value = "newValue3";
+ newContent.tabs[1].properties[2].value = "origValue4";
+
+ //act
+ var changed = contentEditingHelper.reBindChangedProperties(origContent, newContent);
+
+ //assert
+ expect(changed.length).toBe(2);
+ expect(changed[0].alias).toBe("list");
+ expect(changed[1].alias).toBe("textarea");
+ });
+
+ });
});
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/umb-image-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/umb-image-helper.spec.js
new file mode 100644
index 0000000000..55325397ac
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/umb-image-helper.spec.js
@@ -0,0 +1,38 @@
+describe('icon helper tests', function () {
+ var umbImageHelper;
+
+ beforeEach(module('umbraco.services'));
+ beforeEach(module('umbraco.mocks'));
+
+ beforeEach(inject(function ($injector) {
+ umbImageHelper = $injector.get('umbImageHelper');
+ }));
+
+ describe('basic utility methods return correct values', function () {
+
+ it('detects an image based file', function () {
+
+ var image1 = "a-jpeg.jpg";
+ var image2 = "a-png.png";
+ var image3 = "thisisagif.blah.gif";
+ var doc1 = "anormaldocument.doc";
+
+ expect(umbImageHelper.detectIfImageByExtension(image1)).toBe(true);
+ expect(umbImageHelper.detectIfImageByExtension(image2)).toBe(true);
+ expect(umbImageHelper.detectIfImageByExtension(image3)).toBe(true);
+ expect(umbImageHelper.detectIfImageByExtension(doc1)).toBe(false);
+ });
+
+ it('gets a thumbnail path', function () {
+
+ var image1 = "a-jpeg.jpg";
+ var image2 = "a-png.png";
+ var image3 = "thisisagif.blah.gif";
+
+ expect(umbImageHelper.getThumbnailFromPath(image1)).toBe("a-jpeg_thumb.jpg");
+ expect(umbImageHelper.getThumbnailFromPath(image2)).toBe("a-png_thumb.jpg");
+ expect(umbImageHelper.getThumbnailFromPath(image3)).toBe("thisisagif.blah_thumb.jpg");
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index 6e440d873b..9a8e3f7012 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -104,13 +104,13 @@
{07fbc26b-2927-4a22-8d96-d644c667fecc}
UmbracoExamine
-
+
False
- ..\packages\ClientDependency.1.7.0.3\lib\ClientDependency.Core.dll
+ ..\packages\ClientDependency.1.7.0.4\lib\ClientDependency.Core.dll
-
+
False
- ..\packages\ClientDependency-Mvc.1.7.0.3\lib\ClientDependency.Core.Mvc.dll
+ ..\packages\ClientDependency-Mvc.1.7.0.4\lib\ClientDependency.Core.Mvc.dll
False
@@ -409,6 +409,13 @@
ContentTypeControlNew.ascx
+
+ ASPXCodeBehind
+ ImageViewer.ascx
+
+
+ ImageViewer.ascx
+
create.aspx
ASPXCodeBehind
@@ -570,6 +577,14 @@
+
+
+
+
+
+
+
+
@@ -2175,7 +2190,6 @@
-
@@ -2585,7 +2599,7 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
True
6130
/
- http://localhost:6130
+ http://localhost:6230
False
False
diff --git a/src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_content.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_content.ico
similarity index 100%
rename from src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_content.ico
rename to src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_content.ico
diff --git a/src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_default.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_default.ico
similarity index 100%
rename from src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_default.ico
rename to src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_default.ico
diff --git a/src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_developer.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_developer.ico
similarity index 100%
rename from src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_developer.ico
rename to src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_developer.ico
diff --git a/src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_media.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_media.ico
similarity index 100%
rename from src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_media.ico
rename to src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_media.ico
diff --git a/src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_member.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_member.ico
similarity index 100%
rename from src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_member.ico
rename to src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_member.ico
diff --git a/src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_settings.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_settings.ico
similarity index 100%
rename from src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_settings.ico
rename to src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_settings.ico
diff --git a/src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_users.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_users.ico
similarity index 100%
rename from src/Umbraco.Web.UI/umbraco/images/pinnedIcons/task_users.ico
rename to src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_users.ico
diff --git a/src/Umbraco.Web.UI/umbraco/images/pinnedIcons/umb.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/umb.ico
similarity index 100%
rename from src/Umbraco.Web.UI/umbraco/images/pinnedIcons/umb.ico
rename to src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/umb.ico
diff --git a/src/Umbraco.Web.UI/config/splashes/noNodes.aspx b/src/Umbraco.Web.UI/config/splashes/noNodes.aspx
index 01b1505b43..6a71a740cb 100644
--- a/src/Umbraco.Web.UI/config/splashes/noNodes.aspx
+++ b/src/Umbraco.Web.UI/config/splashes/noNodes.aspx
@@ -9,7 +9,7 @@
Umbraco - no pages found
- " />
+ " />
diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
index 537e7ca4ed..1758488f54 100644
--- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
+++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
@@ -111,6 +111,9 @@
ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd
+
+
+ Textstring
diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config
index e5e88a781a..4510e6ae2f 100644
--- a/src/Umbraco.Web.UI/config/umbracoSettings.config
+++ b/src/Umbraco.Web.UI/config/umbracoSettings.config
@@ -105,6 +105,9 @@
ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd
+
+
+ Textstring
diff --git a/src/Umbraco.Web.UI/install/default.aspx b/src/Umbraco.Web.UI/install/default.aspx
index c644336cc1..03ff1c0b30 100644
--- a/src/Umbraco.Web.UI/install/default.aspx
+++ b/src/Umbraco.Web.UI/install/default.aspx
@@ -12,7 +12,7 @@
- " />
+ " />
diff --git a/src/Umbraco.Web.UI/install/steps/Database.ascx.cs b/src/Umbraco.Web.UI/install/steps/Database.ascx.cs
index 800a025756..e482c5c6fe 100644
--- a/src/Umbraco.Web.UI/install/steps/Database.ascx.cs
+++ b/src/Umbraco.Web.UI/install/steps/Database.ascx.cs
@@ -189,19 +189,31 @@ namespace Umbraco.Web.UI.Install.Steps
{
try
{
+ var dbContext = ApplicationContext.Current.DatabaseContext;
+
if (string.IsNullOrEmpty(ConnectionString.Text) == false)
{
- ApplicationContext.Current.DatabaseContext.ConfigureDatabaseConnection(ConnectionString.Text);
+ dbContext.ConfigureDatabaseConnection(ConnectionString.Text);
}
else if (IsEmbeddedDatabase)
{
- ApplicationContext.Current.DatabaseContext.ConfigureEmbeddedDatabaseConnection();
+ dbContext.ConfigureEmbeddedDatabaseConnection();
}
else
{
- ApplicationContext.Current.DatabaseContext.ConfigureDatabaseConnection(DatabaseServer.Text, DatabaseName.Text,
- DatabaseUsername.Text, DatabasePassword.Text,
- DatabaseType.SelectedValue);
+ var server = DatabaseServer.Text;
+ var databaseName = DatabaseName.Text;
+
+ if (DatabaseType.SelectedValue == "SqlServer" && DatabaseIntegratedSecurity.Checked == true)
+ {
+ dbContext.ConfigureIntegratedSecurityDatabaseConnection(server, databaseName);
+ }
+ else
+ {
+ dbContext.ConfigureDatabaseConnection(server, databaseName,
+ DatabaseUsername.Text, DatabasePassword.Text, DatabaseType.SelectedValue
+ );
+ }
}
}
catch (Exception ex)
diff --git a/src/Umbraco.Web.UI/install/steps/Database.ascx.designer.cs b/src/Umbraco.Web.UI/install/steps/Database.ascx.designer.cs
index 320e92760d..f32fdbd669 100644
--- a/src/Umbraco.Web.UI/install/steps/Database.ascx.designer.cs
+++ b/src/Umbraco.Web.UI/install/steps/Database.ascx.designer.cs
@@ -102,6 +102,15 @@ namespace Umbraco.Web.UI.Install.Steps {
///
protected global::System.Web.UI.WebControls.TextBox DatabaseName;
+ ///
+ /// DatabaseIntegratedSecurity control.
+ ///
+ ///
+ /// Auto-generated field.
+ /// To modify move field declaration from designer file to code-behind file.
+ ///
+ protected global::System.Web.UI.WebControls.CheckBox DatabaseIntegratedSecurity;
+
///
/// DatabaseUsernameItem control.
///
diff --git a/src/Umbraco.Web.UI/install/steps/database.ascx b/src/Umbraco.Web.UI/install/steps/database.ascx
index b1b46eeebd..834ed80b55 100644
--- a/src/Umbraco.Web.UI/install/steps/database.ascx
+++ b/src/Umbraco.Web.UI/install/steps/database.ascx
@@ -1,6 +1,6 @@
-<%@ Control Language="c#" AutoEventWireup="True" CodeBehind="database.ascx.cs" Inherits="Umbraco.Web.UI.Install.Steps.Database"
- TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
+<%@ Control Language="c#" AutoEventWireup="True" CodeBehind="database.ascx.cs" Inherits="Umbraco.Web.UI.Install.Steps.Database" %>
<%@ Import Namespace="Umbraco.Core.Configuration" %>
+
@@ -17,7 +17,6 @@