diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 8698aaeb09..295d41b097 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -27,6 +27,7 @@ + @@ -59,8 +60,6 @@ - - @@ -68,5 +67,4 @@ - \ No newline at end of file diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 19af134c2a..46a9df0a4a 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -22,6 +22,12 @@ if ($project) { $umbracoFilesPath = Join-Path $rootPath "UmbracoFiles\*" Copy-Item $umbracoFilesPath $projectDestinationPath -recurse -force + # Remove Newtonsoft.Json if present in the bin folder + # If we don't do this, the correct version never gets copied in + $projectDestinationPath = Split-Path $project.FullName -Parent + $jsonDllFile = Join-Path $projectDestinationPath "bin\Newtonsoft.Json.dll" + Remove-Item $jsonDllFile -Confirm:$false + # Open readme.txt file $DTE.ItemOperations.OpenFile($toolsPath + '\Readme.txt') } \ No newline at end of file diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index e66b26786c..79e4a72756 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -28,12 +28,11 @@ namespace Umbraco.Core /// /// /// - internal ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache) + public ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache) { if (dbContext == null) throw new ArgumentNullException("dbContext"); if (serviceContext == null) throw new ArgumentNullException("serviceContext"); if (cache == null) throw new ArgumentNullException("cache"); - _databaseContext = dbContext; _services = serviceContext; ApplicationCache = cache; @@ -43,12 +42,60 @@ namespace Umbraco.Core /// Creates a basic app context /// /// - internal ApplicationContext(CacheHelper cache) + public ApplicationContext(CacheHelper cache) { ApplicationCache = cache; } - /// + /// + /// A method used to set and/or ensure that a global ApplicationContext singleton is created. + /// + /// + /// The instance to set on the global application singleton + /// + /// If set to true and the singleton is already set, it will be replaced + /// + /// + /// This is NOT thread safe + /// + public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext) + { + if (ApplicationContext.Current != null) + { + if (!replaceContext) + return ApplicationContext.Current; + } + ApplicationContext.Current = appContext; + return ApplicationContext.Current; + } + + /// + /// A method used to create and ensure that a global ApplicationContext singleton is created. + /// + /// + /// + /// If set to true will replace the current singleton instance - This should only be used for unit tests or on app + /// startup if for some reason the boot manager is not the umbraco boot manager. + /// + /// + /// + /// + /// + /// This is NOT thread safe + /// + public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, bool replaceContext) + { + if (ApplicationContext.Current != null) + { + if (!replaceContext) + return ApplicationContext.Current; + } + var ctx = new ApplicationContext(dbContext, serviceContext, cache); + ApplicationContext.Current = ctx; + return ApplicationContext.Current; + } + + /// /// Singleton accessor /// public static ApplicationContext Current { get; internal set; } diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 8bb80670d8..601e2819ff 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core /// /// Good for unit testing /// - internal static CacheHelper CreateDisabledCacheHelper() + public static CacheHelper CreateDisabledCacheHelper() { return new CacheHelper(null, null, null, false); } diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index c3916a2e87..660a8073c9 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -99,6 +99,10 @@ namespace Umbraco.Core /// public static class Member { + public static readonly string UmbracoMemberProviderName = "UmbracoMembershipProvider"; + + public static readonly string UmbracoRoleProviderName = "UmbracoRoleProvider"; + /// /// Property alias for a Members Password Question /// diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 3c2a8d6752..e423f58c62 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -411,6 +411,11 @@ namespace Umbraco.Core /// Alias for the XPath DropDownList datatype. /// public const string XPathDropDownListAlias = "Umbraco.XPathDropDownList"; + + /// + /// Alias for the email address property editor + /// + public const string EmailAddressAlias = "Umbraco.EmailAddress"; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 4062e2a52f..64a40f32e4 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core private string _providerName; private DatabaseSchemaResult _result; - internal DatabaseContext(IDatabaseFactory factory) + public DatabaseContext(IDatabaseFactory factory) { _factory = factory; } diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 47dbfacb45..49839828fd 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Manifest @@ -18,7 +19,8 @@ namespace Umbraco.Core.Manifest private readonly DirectoryInfo _pluginsDir; //used to strip comments - private static readonly Regex Comments = new Regex("(/\\*.*\\*/)", RegexOptions.Compiled); + private static readonly Regex CommentsSurround = new Regex(@"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/", RegexOptions.Compiled); + private static readonly Regex CommentsLine = new Regex(@"//.*?$", RegexOptions.Compiled | RegexOptions.Multiline); public ManifestParser(DirectoryInfo pluginsDir) { @@ -116,11 +118,20 @@ namespace Umbraco.Core.Manifest { if (m.IsNullOrWhiteSpace()) continue; - //remove any comments first, NOTE: I think JSON.Net will do this for us! but we'll leave it here for now - Comments.Replace(m, match => ""); + //remove any comments first + var replaced = CommentsSurround.Replace(m, match => " "); + replaced = CommentsLine.Replace(replaced, match => ""); - - var deserialized = JsonConvert.DeserializeObject(m); + JObject deserialized; + try + { + deserialized = JsonConvert.DeserializeObject(replaced); + } + catch (Exception ex) + { + LogHelper.Error("An error occurred parsing manifest with contents: " + m, ex); + continue; + } //validate the javascript var init = deserialized.Properties().Where(x => x.Name == "javascript").ToArray(); @@ -130,10 +141,10 @@ namespace Umbraco.Core.Manifest } //validate the css - var cssinit = deserialized.Properties().Where(x => x.Name == "stylesheet").ToArray(); + var cssinit = deserialized.Properties().Where(x => x.Name == "css").ToArray(); if (cssinit.Length > 1) { - throw new FormatException("The manifest is not formatted correctly contains more than one 'stylesheet' element"); + throw new FormatException("The manifest is not formatted correctly contains more than one 'css' element"); } //validate the property editors section @@ -146,7 +157,7 @@ namespace Umbraco.Core.Manifest var jConfig = init.Any() ? (JArray)deserialized["javascript"] : new JArray(); ReplaceVirtualPaths(jConfig); - var cssConfig = cssinit.Any() ? (JArray)deserialized["stylesheet"] : new JArray(); + var cssConfig = cssinit.Any() ? (JArray)deserialized["css"] : new JArray(); ReplaceVirtualPaths(cssConfig); //replace virtual paths for each property editor @@ -168,7 +179,7 @@ namespace Umbraco.Core.Manifest var manifest = new PackageManifest() { JavaScriptInitialize = jConfig, - StyleSheetInitialize = cssConfig, + StylesheetInitialize = cssConfig, PropertyEditors = propEditors.Any() ? (JArray)deserialized["propertyEditors"] : new JArray(), ParameterEditors = propEditors.Any() ? (JArray)deserialized["parameterEditors"] : new JArray() }; diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 4426bb36fb..3475a60312 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Manifest /// /// The json array used to initialize the application with the CSS dependencies required /// - public JArray StyleSheetInitialize { get; set; } + public JArray StylesheetInitialize { get; set; } /// /// The json array of property editors diff --git a/src/Umbraco.Core/Models/ApplicationTree.cs b/src/Umbraco.Core/Models/ApplicationTree.cs index d964dc2bff..cc754ec694 100644 --- a/src/Umbraco.Core/Models/ApplicationTree.cs +++ b/src/Umbraco.Core/Models/ApplicationTree.cs @@ -4,7 +4,7 @@ using System.Diagnostics; namespace Umbraco.Core.Models { [DebuggerDisplay("Tree - {Title} ({ApplicationAlias})")] - internal class ApplicationTree + public class ApplicationTree { /// /// Initializes a new instance of the class. @@ -23,7 +23,7 @@ namespace Umbraco.Core.Models /// The icon closed. /// The icon opened. /// The tree type. - public ApplicationTree(bool initialize, byte sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) + public ApplicationTree(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) { this.Initialize = initialize; this.SortOrder = sortOrder; @@ -45,7 +45,7 @@ namespace Umbraco.Core.Models /// Gets or sets the sort order. /// /// The sort order. - public byte SortOrder { get; set; } + public int SortOrder { get; set; } /// /// Gets the application alias. diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index c63c8eba1e..011726e615 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -50,7 +51,7 @@ namespace Umbraco.Core.Models _contentTypeId = int.Parse(contentType.Id.ToString(CultureInfo.InvariantCulture)); _properties = properties; _properties.EnsurePropertyTypes(PropertyTypes); - AdditionalData = new Dictionary(); + _additionalData = new Dictionary(); } /// @@ -74,7 +75,7 @@ namespace Umbraco.Core.Models _contentTypeId = int.Parse(contentType.Id.ToString(CultureInfo.InvariantCulture)); _properties = properties; _properties.EnsurePropertyTypes(PropertyTypes); - AdditionalData = new Dictionary(); + _additionalData = new Dictionary(); } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); @@ -255,6 +256,16 @@ namespace Umbraco.Core.Models } } + private readonly IDictionary _additionalData; + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + [EditorBrowsable(EditorBrowsableState.Never)] + IDictionary IUmbracoEntity.AdditionalData + { + get { return _additionalData; } + } + /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// @@ -453,7 +464,7 @@ namespace Umbraco.Core.Models /// also reset the dirty changes made to the content's Properties (user defined) /// /// - internal override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) { base.ResetDirtyProperties(rememberPreviouslyChangedProperties); diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 0457c113b7..87acf07d45 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -42,7 +43,7 @@ namespace Umbraco.Core.Models _allowedContentTypes = new List(); _propertyGroups = new PropertyGroupCollection(); _propertyTypes = new PropertyTypeCollection(); - AdditionalData = new Dictionary(); + _additionalData = new Dictionary(); } protected ContentTypeBase(IContentTypeBase parent) @@ -53,7 +54,7 @@ namespace Umbraco.Core.Models _allowedContentTypes = new List(); _propertyGroups = new PropertyGroupCollection(); _propertyTypes = new PropertyTypeCollection(); - AdditionalData = new Dictionary(); + _additionalData = new Dictionary(); } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); @@ -316,6 +317,16 @@ namespace Umbraco.Core.Models } } + private readonly IDictionary _additionalData; + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + [EditorBrowsable(EditorBrowsableState.Never)] + IDictionary IUmbracoEntity.AdditionalData + { + get { return _additionalData; } + } + /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index a930bf94c4..8fcc8b6a13 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; @@ -34,14 +35,14 @@ namespace Umbraco.Core.Models { _parentId = parentId; _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, true); - AdditionalData = new Dictionary(); + + _additionalData = new Dictionary(); } - public DataTypeDefinition(int parentId, string propertyEditorAlias) { _parentId = parentId; _propertyEditorAlias = propertyEditorAlias; - AdditionalData = new Dictionary(); + _additionalData = new Dictionary(); } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); @@ -173,9 +174,11 @@ namespace Umbraco.Core.Models _trashed = value; return _trashed; }, _trashed, TrashedSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["Trashed"] = value; } } - + [DataMember] public string PropertyEditorAlias { @@ -209,7 +212,7 @@ namespace Umbraco.Core.Models var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); PropertyEditorAlias = alias; //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["DatabaseType"] = value; + _additionalData["ControlId"] = value; } } @@ -229,10 +232,20 @@ namespace Umbraco.Core.Models }, _databaseType, DatabaseTypeSelector); //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["DatabaseType"] = value; + _additionalData["DatabaseType"] = value; } } + private readonly IDictionary _additionalData; + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + [EditorBrowsable(EditorBrowsableState.Never)] + IDictionary IUmbracoEntity.AdditionalData + { + get { return _additionalData; } + } + /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// diff --git a/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs b/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs index 94a88d312e..efebbd7c74 100644 --- a/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs +++ b/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs @@ -9,5 +9,6 @@ bool WasDirty(); bool WasPropertyDirty(string propertyName); void ForgetPreviouslyDirtyProperties(); + void ResetDirtyProperties(bool rememberPreviouslyChangedProperties); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs index dc65c583bc..cf34296281 100644 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs @@ -108,7 +108,7 @@ namespace Umbraco.Core.Models.EntityBase /// Please note that resetting the dirty properties could potentially /// obstruct the saving of a new or updated entity. /// - internal virtual void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public virtual void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) { if (rememberPreviouslyChangedProperties) { diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index c754cab326..a0f0bd9651 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -172,7 +172,7 @@ namespace Umbraco.Core.Models OnPropertyChanged(PropertiesSelector); } - internal override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) { _addedProperties.Clear(); _removedProperties.Clear(); diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 335beb9ee4..3530e94c59 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -20,6 +20,17 @@ namespace Umbraco.Core.Models private object _providerUserKey; private Type _userTypeKey; + /// + /// Constructor for creating a Member object + /// + /// Name of the content + /// ContentType for the current Content object + public Member(string name, IMemberType contentType) + : base(name, -1, contentType, new PropertyCollection()) + { + _contentType = contentType; + } + public Member(string name, string email, string username, string password, int parentId, IMemberType contentType) : base(name, parentId, contentType, new PropertyCollection()) { @@ -427,7 +438,7 @@ namespace Umbraco.Core.Models public override void ChangeTrashedState(bool isTrashed, int parentId = -20) { - throw new NotImplementedException("Members can't be trashed as no Recycle Bin exists, so use of this method is invalid"); + throw new NotSupportedException("Members can't be trashed as no Recycle Bin exists, so use of this method is invalid"); } /* Internal experiment - only used for mapping queries. diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs index 06e50ecd9b..d55cc77f66 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs @@ -2,6 +2,8 @@ namespace Umbraco.Core.Models.Membership { + //TODO: THere's still a bunch of properties that don't exist in this use that need to be mapped somehow. + internal class UmbracoMembershipMember : MembershipUser { private readonly IMember _member; @@ -21,5 +23,44 @@ namespace Umbraco.Core.Models.Membership get { return _member.Email; } set { _member.Email = value; } } + + public override object ProviderUserKey + { + get { return _member.Key; } + } + + public override System.DateTime CreationDate + { + get { return _member.CreateDate; } + } + + public override string UserName + { + get { return _member.Username; } + } + + public override string Comment + { + get { return _member.Comments; } + set { _member.Comments = value; } + } + + public override bool IsApproved + { + get { return _member.IsApproved; } + set { _member.IsApproved = value; } + } + + public override bool IsLockedOut + { + get { return _member.IsLockedOut; } + } + + public override System.DateTime LastLoginDate + { + get { return _member.LastLoginDate; } + set { _member.LastLoginDate = value; } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/UserProfile.cs b/src/Umbraco.Core/Models/Membership/UserProfile.cs index 1e3ab541e7..61a0d68ba7 100644 --- a/src/Umbraco.Core/Models/Membership/UserProfile.cs +++ b/src/Umbraco.Core/Models/Membership/UserProfile.cs @@ -133,7 +133,7 @@ namespace Umbraco.Core.Models.Membership /// be flagged as dirty. /// /// - internal override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) { _addedSections.Clear(); _removedSections.Clear(); diff --git a/src/Umbraco.Web/Models/PagedResult.cs b/src/Umbraco.Core/Models/PagedResult.cs similarity index 95% rename from src/Umbraco.Web/Models/PagedResult.cs rename to src/Umbraco.Core/Models/PagedResult.cs index 6c2c764c6e..2e35da9a96 100644 --- a/src/Umbraco.Web/Models/PagedResult.cs +++ b/src/Umbraco.Core/Models/PagedResult.cs @@ -1,63 +1,63 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Umbraco.Web.Models -{ - /// - /// Represents a paged result for a model collection - /// - /// - [DataContract(Name = "pagedCollection", Namespace = "")] - public class PagedResult - { - public PagedResult(long totalItems, long pageNumber, long pageSize) - { - TotalItems = totalItems; - PageNumber = pageNumber; - PageSize = pageSize; - - if (pageSize > 0) - { - TotalPages = (long) Math.Ceiling(totalItems/(Decimal) pageSize); - } - else - { - TotalPages = 1; - } - } - - [DataMember(Name = "pageNumber")] - public long PageNumber { get; private set; } - - [DataMember(Name = "pageSize")] - public long PageSize { get; private set; } - - [DataMember(Name = "totalPages")] - public long TotalPages { get; private set; } - - [DataMember(Name = "totalItems")] - public long TotalItems { get; private set; } - - [DataMember(Name = "items")] - public IEnumerable Items { get; set; } - - /// - /// Calculates the skip size based on the paged parameters specified - /// - /// - /// Returns 0 if the page number or page size is zero - /// - internal int SkipSize - { - get - { - if (PageNumber > 0 && PageSize > 0) - { - return Convert.ToInt32((PageNumber - 1)*PageSize); - } - return 0; - } - } - } +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a paged result for a model collection + /// + /// + [DataContract(Name = "pagedCollection", Namespace = "")] + public class PagedResult + { + public PagedResult(long totalItems, long pageNumber, long pageSize) + { + TotalItems = totalItems; + PageNumber = pageNumber; + PageSize = pageSize; + + if (pageSize > 0) + { + TotalPages = (long) Math.Ceiling(totalItems/(Decimal) pageSize); + } + else + { + TotalPages = 1; + } + } + + [DataMember(Name = "pageNumber")] + public long PageNumber { get; private set; } + + [DataMember(Name = "pageSize")] + public long PageSize { get; private set; } + + [DataMember(Name = "totalPages")] + public long TotalPages { get; private set; } + + [DataMember(Name = "totalItems")] + public long TotalItems { get; private set; } + + [DataMember(Name = "items")] + public IEnumerable Items { get; set; } + + /// + /// Calculates the skip size based on the paged parameters specified + /// + /// + /// Returns 0 if the page number or page size is zero + /// + internal int SkipSize + { + get + { + if (PageNumber > 0 && PageSize > 0) + { + return Convert.ToInt32((PageNumber - 1)*PageSize); + } + return 0; + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/IEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/IEntityFactory.cs index 6053090178..a0d8823d68 100644 --- a/src/Umbraco.Core/Persistence/Factories/IEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/IEntityFactory.cs @@ -1,5 +1,7 @@ namespace Umbraco.Core.Persistence.Factories { + //TODO: Not sure why we need this interface as it's never referenced in code. + internal interface IEntityFactory where TEntity : class where TDto : class diff --git a/src/Umbraco.Core/Persistence/IDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IDatabaseFactory.cs index 87c146fa0f..b0efb7f94a 100644 --- a/src/Umbraco.Core/Persistence/IDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/IDatabaseFactory.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Persistence /// /// Used to create the UmbracoDatabase for use in the DatabaseContext /// - internal interface IDatabaseFactory : IDisposable + public interface IDatabaseFactory : IDisposable { UmbracoDatabase CreateDatabase(); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs index 9dab3920e9..7dffece05a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven .ForeignColumn("propertyTypeId") .ToTable("cmsPropertyType") .PrimaryColumn("id") - .OnDelete(Rule.Cascade) + .OnDelete(Rule.None) .OnUpdate(Rule.None); //now we need to add a foreign key to the nodeId column to cmsContent (intead of the original umbracoNode) @@ -109,7 +109,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven .ForeignColumn("nodeId") .ToTable("cmsContent") .PrimaryColumn("nodeId") - .OnDelete(Rule.Cascade) + .OnDelete(Rule.None) .OnUpdate(Rule.None); } diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index eb10bf7f87..f249ca4577 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using System.Text.RegularExpressions; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -17,6 +18,19 @@ namespace Umbraco.Core.Persistence internal static event CreateTableEventHandler NewTable; + /// + /// This will escape single @ symbols for peta poco values so it doesn't think it's a parameter + /// + /// + /// + /// + public static string EscapeAtSymbols(this Database db, string value) + { + //this fancy regex will only match a single @ not a double, etc... + var regex = new Regex("(?(this Database db) where T : new() { diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index db9a1b4f19..895124f363 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -11,5 +11,6 @@ namespace Umbraco.Core.Persistence.Repositories /// /// IEnumerable GetByMemberGroup(string groupName); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 8854f169cd..6175c2d494 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -265,8 +265,10 @@ namespace Umbraco.Core.Persistence.Repositories //Updates Modified date ((Member)entity).UpdatingEntity(); + var dirtyEntity = (ICanBeDirty) entity; + //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed - if (((ICanBeDirty)entity).IsPropertyDirty("ParentId")) + if (dirtyEntity.IsPropertyDirty("ParentId")) { var parent = Database.First("WHERE id = @ParentId", new { ParentId = ((IUmbracoEntity)entity).ParentId }); ((IUmbracoEntity)entity).Path = string.Concat(parent.Path, ",", entity.Id); @@ -302,17 +304,44 @@ namespace Umbraco.Core.Persistence.Repositories //Updates the current version - cmsContentVersion //Assumes a Version guid exists and Version date (modified date) has been set/updated Database.Update(dto.ContentVersionDto); - //Updates the cmsMember entry - Database.Update(dto); + + //Updates the cmsMember entry if it has changed + var changedCols = new List(); + if (dirtyEntity.IsPropertyDirty("Email")) + { + changedCols.Add("Email"); + } + if (dirtyEntity.IsPropertyDirty("Username")) + { + changedCols.Add("LoginName"); + } + // DO NOT update the password if it is null or empty + if (dirtyEntity.IsPropertyDirty("Password") && entity.Password.IsNullOrWhiteSpace() == false) + { + changedCols.Add("Password"); + } + //only update the changed cols + if (changedCols.Count > 0) + { + Database.Update(dto, changedCols); + } //TODO ContentType for the Member entity //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType, entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(((Member)entity).Properties); + var propertyFactory = new PropertyFactory(entity.ContentType, entity.Version, entity.Id); var keyDictionary = new Dictionary(); //Add Properties + // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type + // - this can occur if the member type doesn't contain the built-in properties that the + // - member object contains. + var existingProperties = entity.Properties + .Where(property => entity.ContentType.PropertyTypes.Any(x => x.Alias == property.Alias && x.HasIdentity)) + .ToList(); + + var propertyDataDtos = propertyFactory.BuildDto(existingProperties); + foreach (var propertyDataDto in propertyDataDtos) { if (propertyDataDto.Id > 0) @@ -337,7 +366,7 @@ namespace Umbraco.Core.Persistence.Repositories UpdatePropertyTags(entity, _tagRepository); - ((ICanBeDirty)entity).ResetDirtyProperties(); + dirtyEntity.ResetDirtyProperties(); } protected override void PersistDeletedItem(IMember entity) diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 89d04bf4e7..f502e28d32 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence } - internal RepositoryFactory(bool disableAllCache) + public RepositoryFactory(bool disableAllCache) : this(disableAllCache, UmbracoConfig.For.UmbracoSettings()) { diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index fabcb7fe98..2104841349 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -40,6 +40,7 @@ using System.Security.Permissions; [assembly: InternalsVisibleTo("Concorde.Sync")] [assembly: InternalsVisibleTo("Umbraco.Belle")] [assembly: InternalsVisibleTo("Umbraco.VisualStudio")] +[assembly: InternalsVisibleTo("umbraco.providers")] //allow this to be mocked in our unit tests [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/EmailValidator.cs b/src/Umbraco.Core/PropertyEditors/EmailValidator.cs new file mode 100644 index 0000000000..0fb6a227be --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/EmailValidator.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Umbraco.Core.Models; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// A validator that validates an email address + /// + [ValueValidator("Email")] + internal sealed class EmailValidator : ManifestValueValidator, IPropertyValidator + { + public override IEnumerable Validate(object value, string config, PreValueCollection preValues, PropertyEditor editor) + { + var asString = value.ToString(); + + var emailVal = new EmailAddressAttribute(); + + if (emailVal.IsValid(asString) == false) + { + //TODO: localize these! + yield return new ValidationResult("Email is invalid", new[] { "value" }); + } + } + + public IEnumerable Validate(object value, PreValueCollection preValues, PropertyEditor editor) + { + return Validate(value, null, preValues, editor); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs new file mode 100644 index 0000000000..cfa7174aae --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.PropertyEditors +{ + public interface IParameterEditor + { + /// + /// The id of the property editor + /// + string Alias { get; } + + /// + /// The name of the property editor + /// + string Name { get; } + + /// + /// Allows a parameter editor to be re-used based on the configuration specified. + /// + IDictionary Configuration { get; } + + IValueEditor ValueEditor { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditor.cs index 189fd59b8a..be98218808 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditor.cs @@ -5,26 +5,6 @@ using Umbraco.Core.IO; namespace Umbraco.Core.PropertyEditors { - public interface IParameterEditor - { - /// - /// The id of the property editor - /// - string Alias { get; } - - /// - /// The name of the property editor - /// - string Name { get; } - - /// - /// Allows a parameter editor to be re-used based on the configuration specified. - /// - IDictionary Configuration { get; } - - IValueEditor ValueEditor { get; } - } - /// /// Basic definition of a macro parameter editor /// diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs index 611f180758..cdc38f7cc2 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs @@ -116,6 +116,7 @@ namespace Umbraco.Core.PropertyEditors editor.View = _attribute.EditorView; editor.ValueType = _attribute.ValueType; + editor.HideLabel = _attribute.HideLabel; return editor; } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs index 50dfd1d062..714ff31abd 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorAttribute.cs @@ -52,5 +52,10 @@ namespace Umbraco.Core.PropertyEditors public string EditorView { get; private set; } public string ValueType { get; set; } public bool IsParameterEditor { get; set; } + + /// + /// If this is is true than the editor will be displayed full width without a label + /// + public bool HideLabel { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs index 0649a03d40..ec4cd52284 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs @@ -123,6 +123,12 @@ namespace Umbraco.Core.PropertyEditors } } + /// + /// If this is is true than the editor will be displayed full width without a label + /// + [JsonProperty("hideLabel")] + public bool HideLabel { get; set; } + /// /// Set this to true if the property editor is for display purposes only /// diff --git a/src/Umbraco.Core/Publishing/PublishStatus.cs b/src/Umbraco.Core/Publishing/PublishStatus.cs index aee9a1fafe..865f00c58c 100644 --- a/src/Umbraco.Core/Publishing/PublishStatus.cs +++ b/src/Umbraco.Core/Publishing/PublishStatus.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Publishing /// /// The result of publishing a content item /// - internal class PublishStatus + public class PublishStatus { public PublishStatus() { diff --git a/src/Umbraco.Core/Publishing/PublishStatusType.cs b/src/Umbraco.Core/Publishing/PublishStatusType.cs index c3cb76e245..0d9ffcfa02 100644 --- a/src/Umbraco.Core/Publishing/PublishStatusType.cs +++ b/src/Umbraco.Core/Publishing/PublishStatusType.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Publishing /// /// Anything less than 10 = Success! /// - internal enum PublishStatusType + public enum PublishStatusType { /// /// The publishing was successful. diff --git a/src/Umbraco.Core/Services/ApplicationTreeService.cs b/src/Umbraco.Core/Services/ApplicationTreeService.cs index 33a86fa070..dbf5c9b85a 100644 --- a/src/Umbraco.Core/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/ApplicationTreeService.cs @@ -12,7 +12,7 @@ using File = System.IO.File; namespace Umbraco.Core.Services { - internal class ApplicationTreeService + internal class ApplicationTreeService : IApplicationTreeService { private readonly CacheHelper _cache; @@ -95,6 +95,26 @@ namespace Umbraco.Core.Services }); } + public void Intitialize(IEnumerable existingTrees) + { + LoadXml(doc => + { + foreach (var tree in existingTrees) + { + doc.Root.Add(new XElement("add", + new XAttribute("initialize", tree.Initialize), + new XAttribute("sortOrder", tree.SortOrder), + new XAttribute("alias", tree.Alias), + new XAttribute("application", tree.ApplicationAlias), + new XAttribute("title", tree.Title), + new XAttribute("iconClosed", tree.IconClosed), + new XAttribute("iconOpen", tree.IconOpened), + new XAttribute("type", tree.Type))); + } + + }, true); + } + /// /// Creates a new application tree. /// @@ -205,14 +225,14 @@ namespace Umbraco.Core.Services /// Gets the application tree for the applcation with the specified alias /// /// The application alias. - /// + /// /// Returns a ApplicationTree Array - public IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitializedApplications) + public IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized) { var list = GetAppTrees().FindAll( t => { - if (onlyInitializedApplications) + if (onlyInitialized) return (t.ApplicationAlias == applicationAlias && t.Initialize); return (t.ApplicationAlias == applicationAlias); } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index d4437a5000..1df2133891 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -237,7 +237,7 @@ namespace Umbraco.Core.Services /// /// Ids of the Content to retrieve /// - internal IEnumerable GetByIds(IEnumerable ids) + public IEnumerable GetByIds(IEnumerable ids) { using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) { @@ -627,28 +627,53 @@ namespace Umbraco.Core.Services /// The to publish /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithStatus instead, that method will provide more detailed information on the outcome")] public bool Publish(IContent content, int userId = 0) { var result = SaveAndPublishDo(content, userId); return result.Success; } + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + public Attempt PublishWithStatus(IContent content, int userId = 0) + { + return SaveAndPublishDo(content, userId); + } + /// /// Publishes a object and all its children /// /// The to publish along with its children /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] public bool PublishWithChildren(IContent content, int userId = 0) { var result = PublishWithChildrenDo(content, userId, true); - + //This used to just return false only when the parent content failed, otherwise would always return true so we'll // do the same thing for the moment - if (!result.Any(x => x.Result.ContentItem.Id == content.Id)) - return false; + if (!result.Any(x => x.Result.ContentItem.Id == content.Id)) + return false; - return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; + return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// set to true if you want to also publish children that are currently unpublished + /// True if publishing succeeded, otherwise False + public IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false) + { + return PublishWithChildrenDo(content, userId, includeUnpublished); } /// @@ -669,12 +694,25 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the publishing /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) { var result = SaveAndPublishDo(content, userId, raiseEvents); return result.Success; } + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) + { + return SaveAndPublishDo(content, userId, raiseEvents); + } + /// /// Saves a single object /// @@ -1329,42 +1367,30 @@ namespace Umbraco.Core.Services } #region Internal Methods - - /// - /// Internal method that Publishes a single object for legacy purposes. - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - internal Attempt PublishInternal(IContent content, int userId = 0) - { - return SaveAndPublishDo(content, userId); - } - /// - /// Internal method that Publishes a object and all its children for legacy purposes. - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published - /// True if publishing succeeded, otherwise False - internal IEnumerable> PublishWithChildrenInternal( - IContent content, int userId = 0, bool includeUnpublished = false) - { - return PublishWithChildrenDo(content, userId, includeUnpublished); - } + ///// + ///// Internal method that Publishes a single object for legacy purposes. + ///// + ///// The to publish + ///// Optional Id of the User issueing the publishing + ///// True if publishing succeeded, otherwise False + //internal Attempt PublishInternal(IContent content, int userId = 0) + //{ + // return SaveAndPublishDo(content, userId); + //} - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - internal Attempt SaveAndPublishInternal(IContent content, int userId = 0, bool raiseEvents = true) - { - return SaveAndPublishDo(content, userId, raiseEvents); - } + ///// + ///// Internal method that Publishes a object and all its children for legacy purposes. + ///// + ///// The to publish along with its children + ///// Optional Id of the User issueing the publishing + ///// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published + ///// True if publishing succeeded, otherwise False + //internal IEnumerable> PublishWithChildrenInternal( + // IContent content, int userId = 0, bool includeUnpublished = false) + //{ + // return PublishWithChildrenDo(content, userId, includeUnpublished); + //} /// /// Gets a collection of descendants by the first Parent. @@ -1462,7 +1488,7 @@ namespace Umbraco.Core.Services /// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that /// are to be published. /// - private IEnumerable> PublishWithChildrenDo( + private IEnumerable> PublishWithChildrenDo( IContent content, int userId = 0, bool includeUnpublished = false) { if (content == null) throw new ArgumentNullException("content"); diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index afd7da9efe..a2f27f58a2 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -346,36 +346,7 @@ namespace Umbraco.Core.Services Audit.Add(AuditTypes.Delete, string.Format("Delete ContentTypes performed by user"), userId, -1); } } - - /// - /// Gets an object by its Id - /// - /// Id of the to retrieve - /// - public IMemberType GetMemberType(int id) - { - using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) - { - return repository.Get(id); - } - } - - /// - /// Gets an object by its Alias - /// - /// Alias of the to retrieve - /// - public IMemberType GetMemberType(string alias) - { - using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); - } - } - + /// /// Gets an object by its Id /// diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index f41ac769fa..230d01cbde 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -127,25 +127,7 @@ namespace Umbraco.Core.Services return list; } } - - /// - /// Gets all prevalues for an - /// - /// - /// This method should be kept internal until a proper PreValue object model is introduced. - /// - /// Id of the to retrieve prevalues from - /// An enumerable list of Tuples containing Id, Alias, SortOrder, Value - internal IEnumerable> GetDetailedPreValuesByDataTypeId(int id) - { - using (var uow = _uowProvider.GetUnitOfWork()) - { - var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = id }); - var list = dtos.Select(x => new Tuple(x.Id, x.Alias, x.SortOrder, x.Value)).ToList(); - return list; - } - } - + /// /// Returns the PreValueCollection for the specified data type /// @@ -313,7 +295,7 @@ namespace Umbraco.Core.Services /// /// /// - internal void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) + public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) { if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) return; diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 882aa86b4e..7e103c687b 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { - public class EntityService : IService + public class EntityService : IService, IEntityService { private readonly IDatabaseUnitOfWorkProvider _uowProvider; private readonly RepositoryFactory _repositoryFactory; diff --git a/src/Umbraco.Core/Services/IApplicationTreeService.cs b/src/Umbraco.Core/Services/IApplicationTreeService.cs new file mode 100644 index 0000000000..bb7bb0f790 --- /dev/null +++ b/src/Umbraco.Core/Services/IApplicationTreeService.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IApplicationTreeService + { + void Intitialize(IEnumerable existingTrees); + + /// + /// Creates a new application tree. + /// + /// if set to true [initialize]. + /// The sort order. + /// The application alias. + /// The alias. + /// The title. + /// The icon closed. + /// The icon opened. + /// The type. + void MakeNew(bool initialize, byte sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type); + + /// + /// Saves this instance. + /// + void SaveTree(ApplicationTree tree); + + /// + /// Deletes this instance. + /// + void DeleteTree(ApplicationTree tree); + + /// + /// Gets an ApplicationTree by it's tree alias. + /// + /// The tree alias. + /// An ApplicationTree instance + ApplicationTree GetByAlias(string treeAlias); + + /// + /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. + /// + /// Returns a ApplicationTree Array + IEnumerable GetAll(); + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// Returns a ApplicationTree Array + IEnumerable GetApplicationTrees(string applicationAlias); + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// + /// Returns a ApplicationTree Array + IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index c954219dbf..a265b15092 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { @@ -10,7 +11,9 @@ namespace Umbraco.Core.Services /// public interface IContentService : IService { - + + IEnumerable GetByIds(IEnumerable ids); + /// /// Creates an object using the alias of the /// that this Content should based on. @@ -244,16 +247,35 @@ namespace Umbraco.Core.Services /// The to publish /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithStatus instead, that method will provide more detailed information on the outcome")] bool Publish(IContent content, int userId = 0); + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt PublishWithStatus(IContent content, int userId = 0); + /// /// Publishes a object and all its children /// /// The to publish along with its children /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] bool PublishWithChildren(IContent content, int userId = 0); + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// + /// The list of statuses for all published items + IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false); + /// /// UnPublishes a single object /// @@ -269,8 +291,18 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the publishing /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); + /// /// Permanently deletes an object. /// diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 9f596e465e..9e7c8d53f9 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -66,21 +66,7 @@ namespace Umbraco.Core.Services /// Deleting a will delete all the objects based on this /// Optional Id of the User deleting the ContentTypes void Delete(IEnumerable contentTypes, int userId = 0); - - /// - /// Gets an object by its Id - /// - /// Id of the to retrieve - /// - IMemberType GetMemberType(int id); - - /// - /// Gets an object by its Alias - /// - /// Alias of the to retrieve - /// - IMemberType GetMemberType(string alias); - + /// /// Gets an object by its Id /// diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 6df168d9d3..9b6f23bce3 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -10,6 +10,8 @@ namespace Umbraco.Core.Services /// public interface IDataTypeService : IService { + void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0); + /// /// Gets a by its Id /// diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs new file mode 100644 index 0000000000..c32d755c3f --- /dev/null +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Services +{ + public interface IEntityService + { + /// + /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// Optional bool to load the complete object graph when set to False. + /// An + IUmbracoEntity Get(int id, bool loadBaseType = true); + + /// + /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// UmbracoObjectType of the entity to retrieve + /// Optional bool to load the complete object graph when set to False. + /// An + IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true); + + /// + /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Type of the model to retrieve. Must be based on an + /// Id of the object to retrieve + /// Optional bool to load the complete object graph when set to False. + /// An + IUmbracoEntity Get(int id, bool loadBaseType = true) where T : IUmbracoEntity; + + /// + /// Gets the parent of entity by its id + /// + /// Id of the entity to retrieve the Parent for + /// An + IUmbracoEntity GetParent(int id); + + /// + /// Gets the parent of entity by its id and UmbracoObjectType + /// + /// Id of the entity to retrieve the Parent for + /// UmbracoObjectType of the parent to retrieve + /// An + IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of children by the parents Id + /// + /// Id of the parent to retrieve children for + /// An enumerable list of objects + IEnumerable GetChildren(int parentId); + + /// + /// Gets a collection of children by the parents Id and UmbracoObjectType + /// + /// Id of the parent to retrieve children for + /// UmbracoObjectType of the children to retrieve + /// An enumerable list of objects + IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// An enumerable list of objects + IEnumerable GetDescendents(int id); + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// UmbracoObjectType of the descendents to retrieve + /// An enumerable list of objects + IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. + /// + /// UmbracoObjectType of the root entities to retrieve + /// An enumerable list of objects + IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of all of a given type. + /// + /// Type of the entities to retrieve + /// An enumerable list of objects + IEnumerable GetAll() where T : IUmbracoEntity; + + /// + /// Gets a collection of all of a given type. + /// + /// UmbracoObjectType of the entities to return + /// An enumerable list of objects + IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of + /// + /// Guid id of the UmbracoObjectType + /// An enumerable list of objects + IEnumerable GetAll(Guid objectTypeId); + + /// + /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. + /// + /// Id of the entity + /// + UmbracoObjectTypes GetObjectType(int id); + + /// + /// Gets the UmbracoObjectType from an IUmbracoEntity. + /// + /// + /// + UmbracoObjectTypes GetObjectType(IUmbracoEntity entity); + + /// + /// Gets the Type of an entity by its Id + /// + /// Id of the entity + /// Type of the entity + Type GetEntityType(int id); + + /// + /// Gets the Type of an entity by its + /// + /// + /// Type of the entity + Type GetEntityType(UmbracoObjectTypes umbracoObjectType); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index c8b9db6967..a8eb2dcae6 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -9,6 +9,8 @@ namespace Umbraco.Core.Services /// public interface IMediaService : IService { + IEnumerable GetByIds(IEnumerable ids); + /// /// Creates an object using the alias of the /// that this Media should based on. diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 737e142c3b..c01a80fb9f 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Services IEnumerable GetMembersByMemberType(string memberTypeAlias); IEnumerable GetMembersByGroup(string memberGroupName); IEnumerable GetAllMembers(params int[] ids); - + //TODO: Need to get all members that start with a certain letter } @@ -37,5 +37,7 @@ namespace Umbraco.Core.Services void Delete(IMember membershipUser); void Save(IMember membershipUser); + + IEnumerable FindMembersByEmail(string emailStringToMatch); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMemberTypeService.cs b/src/Umbraco.Core/Services/IMemberTypeService.cs index 9c45d83e68..7857e1dda0 100644 --- a/src/Umbraco.Core/Services/IMemberTypeService.cs +++ b/src/Umbraco.Core/Services/IMemberTypeService.cs @@ -12,7 +12,18 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetAllMemberTypes(params int[] ids); - IMemberType GetMemberType(string alias); + /// + /// Gets an object by its Id + /// + /// Id of the to retrieve + /// IMemberType GetMemberType(int id); + + /// + /// Gets an object by its Alias + /// + /// Alias of the to retrieve + /// + IMemberType GetMemberType(string alias); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ISectionService.cs b/src/Umbraco.Core/Services/ISectionService.cs new file mode 100644 index 0000000000..1df38977f0 --- /dev/null +++ b/src/Umbraco.Core/Services/ISectionService.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface ISectionService + { + /// + /// Ensures all available sections exist in the storage medium + /// + /// + void Initialize(IEnumerable
existingSections); + + /// + /// The cache storage for all applications + /// + IEnumerable
GetSections(); + + /// + /// Get the user's allowed sections + /// + /// + /// + IEnumerable
GetAllowedSections(int userId); + + /// + /// Gets the application by its alias. + /// + /// The application alias. + /// + Section GetByAlias(string appAlias); + + /// + /// Creates a new applcation if no application with the specified alias is found. + /// + /// The application name. + /// The application alias. + /// The application icon, which has to be located in umbraco/images/tray folder. + void MakeNew(string name, string alias, string icon); + + /// + /// Makes the new. + /// + /// The name. + /// The alias. + /// The icon. + /// The sort order. + void MakeNew(string name, string alias, string icon, int sortOrder); + + /// + /// Deletes the section + /// + void DeleteSection(Section section); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index f6a42aea9b..57b943b711 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -211,7 +211,7 @@ namespace Umbraco.Core.Services ///
/// Ids of the Media to retrieve /// - internal IEnumerable GetByIds(IEnumerable ids) + public IEnumerable GetByIds(IEnumerable ids) { using (var repository = _repositoryFactory.CreateMediaRepository(_uowProvider.GetUnitOfWork())) { diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 914ef70e60..87eee84863 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -108,6 +108,25 @@ namespace Umbraco.Core.Services } } + /// + /// Does a search for members that contain the specified string in their email address + /// + /// + /// + public IEnumerable FindMembersByEmail(string emailStringToMatch) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateMemberRepository(uow)) + { + var query = new Query(); + + + query.Where(member => member.Email.Contains(emailStringToMatch)); + + return repository.GetByQuery(query); + } + } + /// /// Gets a list of Members with a certain string property value /// diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index cc5eada678..81a231f7c5 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -37,17 +37,11 @@ namespace Umbraco.Core.Services } } - public IMemberType GetMemberType(string alias) - { - using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Alias == alias); - var memberTypes = repository.GetByQuery(query); - - return memberTypes.FirstOrDefault(); - } - } - + /// + /// Gets an object by its Id + /// + /// Id of the to retrieve + /// public IMemberType GetMemberType(int id) { using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) @@ -56,5 +50,21 @@ namespace Umbraco.Core.Services } } + /// + /// Gets an object by its Alias + /// + /// Alias of the to retrieve + /// + public IMemberType GetMemberType(string alias) + { + using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == alias); + var contentTypes = repository.GetByQuery(query); + + return contentTypes.FirstOrDefault(); + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 7d182e153e..001502ad34 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -729,15 +729,19 @@ namespace Umbraco.Core.Services { var prevalues = new XElement("PreValues"); - var prevalueList = ((DataTypeService)_dataTypeService).GetDetailedPreValuesByDataTypeId(dataTypeDefinition.Id); - foreach (var tuple in prevalueList) + var prevalueList = _dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id) + .FormatAsDictionary(); + + var sort = 0; + foreach (var pv in prevalueList) { var prevalue = new XElement("PreValue"); - prevalue.Add(new XAttribute("Id", tuple.Item1)); - prevalue.Add(new XAttribute("Value", tuple.Item4)); - prevalue.Add(new XAttribute("Alias", tuple.Item2)); - prevalue.Add(new XAttribute("SortOrder", tuple.Item3)); + prevalue.Add(new XAttribute("Id", pv.Value.Id)); + prevalue.Add(new XAttribute("Value", pv.Value.Value)); + prevalue.Add(new XAttribute("Alias", pv.Key)); + prevalue.Add(new XAttribute("SortOrder", sort)); prevalues.Add(prevalue); + sort++; } var xml = new XElement("DataType", prevalues); diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index 83d09023ab..29a42da115 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -13,10 +13,10 @@ namespace Umbraco.Core.Services { private readonly IDatabaseUnitOfWorkProvider _uowProvider; private readonly RepositoryFactory _repositoryFactory; - private readonly EntityService _entityService; + private readonly IEntityService _entityService; public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, - EntityService entityService) + IEntityService entityService) { _uowProvider = uowProvider; _repositoryFactory = repositoryFactory; diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index aaca16c541..00fa28e38c 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -14,15 +14,15 @@ using File = System.IO.File; namespace Umbraco.Core.Services { - internal class SectionService + internal class SectionService : ISectionService { private readonly IUserService _userService; - private readonly ApplicationTreeService _applicationTreeService; + private readonly IApplicationTreeService _applicationTreeService; private readonly CacheHelper _cache; public SectionService( - IUserService userService, - ApplicationTreeService applicationTreeService, + IUserService userService, + IApplicationTreeService applicationTreeService, CacheHelper cache) { if (applicationTreeService == null) throw new ArgumentNullException("applicationTreeService"); @@ -56,10 +56,29 @@ namespace Umbraco.Core.Services set { _appConfig = value; } } + /// + /// Ensures all available sections exist in the storage medium + /// + /// + public void Initialize(IEnumerable
existingSections) + { + LoadXml(doc => + { + foreach (var attr in existingSections) + { + doc.Root.Add(new XElement("add", + new XAttribute("alias", attr.Alias), + new XAttribute("name", attr.Name), + new XAttribute("icon", attr.Icon), + new XAttribute("sortOrder", attr.SortOrder))); + } + }, true); + } + /// /// The cache storage for all applications /// - internal IEnumerable
GetSections() + public IEnumerable
GetSections() { return _cache.GetCacheItem( CacheKeys.ApplicationsCacheKey, diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index ce5a974de8..1bfeeeb8ad 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -12,46 +12,86 @@ namespace Umbraco.Core.Services /// public class ServiceContext { - private Lazy _contentService; - private Lazy _userService; - private Lazy _memberService; - private Lazy _mediaService; - private Lazy _contentTypeService; - private Lazy _dataTypeService; - private Lazy _fileService; - private Lazy _localizationService; + private Lazy _contentService; + private Lazy _userService; + private Lazy _memberService; + private Lazy _mediaService; + private Lazy _contentTypeService; + private Lazy _dataTypeService; + private Lazy _fileService; + private Lazy _localizationService; private Lazy _packagingService; private Lazy _serverRegistrationService; - private Lazy _entityService; + private Lazy _entityService; private Lazy _relationService; - private Lazy _treeService; - private Lazy _sectionService; - private Lazy _macroService; - private Lazy _memberTypeService; + private Lazy _treeService; + private Lazy _sectionService; + private Lazy _macroService; + private Lazy _memberTypeService; - /// - /// Constructor - /// - /// - /// - /// + /// + /// public ctor - will generally just be used for unit testing + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public ServiceContext( + IContentService contentService, + IMediaService mediaService, + IContentTypeService contentTypeService, + IDataTypeService dataTypeService, + IFileService fileService, + ILocalizationService localizationService, + PackagingService packagingService, + IEntityService entityService, + RelationService relationService, + ISectionService sectionService, + IApplicationTreeService treeService) + { + _contentService = new Lazy(() => contentService); + _mediaService = new Lazy(() => mediaService); + _contentTypeService = new Lazy(() => contentTypeService); + _dataTypeService = new Lazy(() => dataTypeService); + _fileService = new Lazy(() => fileService); + _localizationService = new Lazy(() => localizationService); + _packagingService = new Lazy(() => packagingService); + _entityService = new Lazy(() => entityService); + _relationService = new Lazy(() => relationService); + _sectionService = new Lazy(() => sectionService); + _treeService = new Lazy(() => treeService); + } + + /// + /// Constructor used to instantiate the core services + /// + /// + /// + /// internal ServiceContext(IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, BasePublishingStrategy publishingStrategy, CacheHelper cache) - { + { BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, cache, - //this needs to be lazy because when we create the service context it's generally before the - //resolvers have been initialized! - new Lazy(() => RepositoryResolver.Current.Factory)); - } + //this needs to be lazy because when we create the service context it's generally before the + //resolvers have been initialized! + new Lazy(() => RepositoryResolver.Current.Factory)); + } /// /// Builds the various services /// - private void BuildServiceCache( - IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, - IUnitOfWorkProvider fileUnitOfWorkProvider, - BasePublishingStrategy publishingStrategy, + private void BuildServiceCache( + IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, + IUnitOfWorkProvider fileUnitOfWorkProvider, + BasePublishingStrategy publishingStrategy, CacheHelper cache, - Lazy repositoryFactory) + Lazy repositoryFactory) { var provider = dbUnitOfWorkProvider; var fileProvider = fileUnitOfWorkProvider; @@ -59,50 +99,50 @@ namespace Umbraco.Core.Services if (_serverRegistrationService == null) _serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory.Value)); - if (_userService == null) - _userService = new Lazy(() => new UserService(provider, repositoryFactory.Value)); + if (_userService == null) + _userService = new Lazy(() => new UserService(provider, repositoryFactory.Value)); if (_memberService == null) - _memberService = new Lazy(() => new MemberService(provider, repositoryFactory.Value)); + _memberService = new Lazy(() => new MemberService(provider, repositoryFactory.Value)); if (_contentService == null) - _contentService = new Lazy(() => new ContentService(provider, repositoryFactory.Value, publishingStrategy)); + _contentService = new Lazy(() => new ContentService(provider, repositoryFactory.Value, publishingStrategy)); - if(_mediaService == null) - _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory.Value)); + if (_mediaService == null) + _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory.Value)); - if(_contentTypeService == null) - _contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory.Value, _contentService.Value, _mediaService.Value)); + if (_contentTypeService == null) + _contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory.Value, _contentService.Value, _mediaService.Value)); - if(_dataTypeService == null) - _dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory.Value)); + if (_dataTypeService == null) + _dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory.Value)); - if(_fileService == null) - _fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory.Value)); + if (_fileService == null) + _fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory.Value)); - if(_localizationService == null) - _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory.Value)); + if (_localizationService == null) + _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory.Value)); - if(_packagingService == null) + if (_packagingService == null) _packagingService = new Lazy(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, repositoryFactory.Value, provider)); if (_entityService == null) - _entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value)); + _entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value)); - if(_relationService == null) + if (_relationService == null) _relationService = new Lazy(() => new RelationService(provider, repositoryFactory.Value, _entityService.Value)); if (_treeService == null) - _treeService = new Lazy(() => new ApplicationTreeService(cache)); + _treeService = new Lazy(() => new ApplicationTreeService(cache)); if (_sectionService == null) - _sectionService = new Lazy(() => new SectionService(_userService.Value, _treeService.Value, cache)); + _sectionService = new Lazy(() => new SectionService(_userService.Value, _treeService.Value, cache)); if (_macroService == null) - _macroService = new Lazy(() => new MacroService(provider, repositoryFactory.Value)); + _macroService = new Lazy(() => new MacroService(provider, repositoryFactory.Value)); if (_memberTypeService == null) - _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory.Value)); + _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory.Value)); } /// @@ -116,7 +156,7 @@ namespace Umbraco.Core.Services /// /// Gets the /// - internal MacroService MacroService + internal IMacroService MacroService { get { return _macroService.Value; } } @@ -124,7 +164,7 @@ namespace Umbraco.Core.Services /// /// Gets the /// - public EntityService EntityService + public IEntityService EntityService { get { return _entityService.Value; } } @@ -150,7 +190,7 @@ namespace Umbraco.Core.Services /// public IContentTypeService ContentTypeService { - get { return _contentTypeService.Value; } + get { return _contentTypeService.Value; } } /// @@ -158,7 +198,7 @@ namespace Umbraco.Core.Services /// public IDataTypeService DataTypeService { - get { return _dataTypeService.Value; } + get { return _dataTypeService.Value; } } /// @@ -166,7 +206,7 @@ namespace Umbraco.Core.Services /// public IFileService FileService { - get { return _fileService.Value; } + get { return _fileService.Value; } } /// @@ -174,7 +214,7 @@ namespace Umbraco.Core.Services /// public ILocalizationService LocalizationService { - get { return _localizationService.Value; } + get { return _localizationService.Value; } } /// @@ -182,7 +222,7 @@ namespace Umbraco.Core.Services /// public IMediaService MediaService { - get { return _mediaService.Value; } + get { return _mediaService.Value; } } /// @@ -198,7 +238,7 @@ namespace Umbraco.Core.Services /// internal IUserService UserService { - get { return _userService.Value; } + get { return _userService.Value; } } /// @@ -212,7 +252,7 @@ namespace Umbraco.Core.Services /// /// Gets the /// - internal SectionService SectionService + public ISectionService SectionService { get { return _sectionService.Value; } } @@ -220,7 +260,7 @@ namespace Umbraco.Core.Services /// /// Gets the /// - internal ApplicationTreeService ApplicationTreeService + internal IApplicationTreeService ApplicationTreeService { get { return _treeService.Value; } } @@ -228,9 +268,10 @@ namespace Umbraco.Core.Services /// /// Gets the MemberTypeService /// - internal MemberTypeService MemberTypeService + internal IMemberTypeService MemberTypeService { get { return _memberTypeService.Value; } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 53ae287a88..e2db559396 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -60,9 +60,7 @@ ..\packages\Newtonsoft.Json.5.0.6\lib\net45\Newtonsoft.Json.dll - - ..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.ComponentModel.DataAnnotations.dll - + @@ -383,6 +381,7 @@ + @@ -474,6 +473,8 @@ + + @@ -938,9 +939,11 @@ + + @@ -948,6 +951,7 @@ + diff --git a/src/Umbraco.Tests/Controllers/WebApiEditors/ContentControllerUnitTests.cs b/src/Umbraco.Tests/Controllers/WebApiEditors/ContentControllerUnitTests.cs index 679a55897a..45dd854f74 100644 --- a/src/Umbraco.Tests/Controllers/WebApiEditors/ContentControllerUnitTests.cs +++ b/src/Umbraco.Tests/Controllers/WebApiEditors/ContentControllerUnitTests.cs @@ -13,7 +13,7 @@ namespace Umbraco.Tests.Controllers.WebApiEditors public class ContentControllerUnitTests { [Test] - public void Does_Not_Throw_Exception_When_Access_Allowed_By_Path() + public void Access_Allowed_By_Path() { //arrange var userMock = new Mock(); @@ -26,13 +26,9 @@ namespace Umbraco.Tests.Controllers.WebApiEditors var contentServiceMock = new Mock(); contentServiceMock.Setup(x => x.GetById(1234)).Returns(content); var contentService = contentServiceMock.Object; - var userServiceMock = new Mock(); - var permissions = new List(); - userServiceMock.Setup(x => x.GetPermissions(user, 1234)).Returns(permissions); - var userService = userServiceMock.Object; //act - var result = ContentController.CheckPermissions(new Dictionary(), user, userService, contentService, 1234, 'F'); + var result = ContentController.CheckPermissions(new Dictionary(), user, null, contentService, 1234); //assert Assert.IsTrue(result); @@ -62,7 +58,7 @@ namespace Umbraco.Tests.Controllers.WebApiEditors } [Test] - public void Throws_Exception_When_No_Access_By_Path() + public void No_Access_By_Path() { //arrange var userMock = new Mock(); @@ -88,7 +84,7 @@ namespace Umbraco.Tests.Controllers.WebApiEditors } [Test] - public void Throws_Exception_When_No_Access_By_Permission() + public void No_Access_By_Permission() { //arrange var userMock = new Mock(); @@ -117,7 +113,7 @@ namespace Umbraco.Tests.Controllers.WebApiEditors } [Test] - public void Does_Not_Throw_Exception_When_Access_Allowed_By_Permission() + public void Access_Allowed_By_Permission() { //arrange var userMock = new Mock(); @@ -146,34 +142,166 @@ namespace Umbraco.Tests.Controllers.WebApiEditors } [Test] - public void Does_Not_Throw_Exception_When_No_Permissions_Assigned() + public void Access_To_Root_By_Path() { //arrange var userMock = new Mock(); - userMock.Setup(u => u.Id).Returns(9); + userMock.Setup(u => u.Id).Returns(0); userMock.Setup(u => u.StartContentId).Returns(-1); var user = userMock.Object; - var contentMock = new Mock(); - contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); - var content = contentMock.Object; - var contentServiceMock = new Mock(); - contentServiceMock.Setup(x => x.GetById(1234)).Returns(content); - var contentService = contentServiceMock.Object; - var userServiceMock = new Mock(); - var permissions = new List(); - userServiceMock.Setup(x => x.GetPermissions(user, 1234)).Returns(permissions); - var userService = userServiceMock.Object; - + //act - var result = ContentController.CheckPermissions(new Dictionary(), user, userService, contentService, 1234, 'F'); + var result = ContentController.CheckPermissions(new Dictionary(), user, null, null, -1); //assert Assert.IsTrue(result); } - } + [Test] + public void Access_To_Recycle_Bin_By_Path() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartContentId).Returns(-1); + var user = userMock.Object; + + //act + var result = ContentController.CheckPermissions(new Dictionary(), user, null, null, -20); - //we REALLY need a way to nicely mock the service context, etc... so we don't have to do integration tests... coming soon. + //assert + Assert.IsTrue(result); + } + + [Test] + public void No_Access_To_Recycle_Bin_By_Path() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartContentId).Returns(1234); + var user = userMock.Object; + + //act + var result = ContentController.CheckPermissions(new Dictionary(), user, null, null, -20); + + //assert + Assert.IsFalse(result); + } + + [Test] + public void No_Access_To_Root_By_Path() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartContentId).Returns(1234); + var user = userMock.Object; + + //act + var result = ContentController.CheckPermissions(new Dictionary(), user, null, null, -1); + + //assert + Assert.IsFalse(result); + } + + [Test] + public void Access_To_Root_By_Permission() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartContentId).Returns(-1); + var user = userMock.Object; + + var userServiceMock = new Mock(); + var permissions = new List + { + new EntityPermission(9, 1234, new string[]{ "A" }) + }; + userServiceMock.Setup(x => x.GetPermissions(user, -1)).Returns(permissions); + var userService = userServiceMock.Object; + + //act + var result = ContentController.CheckPermissions(new Dictionary(), user, userService, null, -1, 'A'); + + //assert + Assert.IsTrue(result); + } + + [Test] + public void No_Access_To_Root_By_Permission() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartContentId).Returns(-1); + var user = userMock.Object; + + var userServiceMock = new Mock(); + var permissions = new List + { + new EntityPermission(9, 1234, new string[]{ "A" }) + }; + userServiceMock.Setup(x => x.GetPermissions(user, -1)).Returns(permissions); + var userService = userServiceMock.Object; + + //act + var result = ContentController.CheckPermissions(new Dictionary(), user, userService, null, -1, 'B'); + + //assert + Assert.IsFalse(result); + } + + [Test] + public void Access_To_Recycle_Bin_By_Permission() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartContentId).Returns(-1); + var user = userMock.Object; + + var userServiceMock = new Mock(); + var permissions = new List + { + new EntityPermission(9, 1234, new string[]{ "A" }) + }; + userServiceMock.Setup(x => x.GetPermissions(user, -20)).Returns(permissions); + var userService = userServiceMock.Object; + + //act + var result = ContentController.CheckPermissions(new Dictionary(), user, userService, null, -20, 'A'); + + //assert + Assert.IsTrue(result); + } + + [Test] + public void No_Access_To_Recycle_Bin_By_Permission() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartContentId).Returns(-1); + var user = userMock.Object; + + var userServiceMock = new Mock(); + var permissions = new List + { + new EntityPermission(9, 1234, new string[]{ "A" }) + }; + userServiceMock.Setup(x => x.GetPermissions(user, -20)).Returns(permissions); + var userService = userServiceMock.Object; + + //act + var result = ContentController.CheckPermissions(new Dictionary(), user, userService, null, -20, 'B'); + + //assert + Assert.IsFalse(result); + } + + } //NOTE: The below self hosted stuff does work so need to get some tests written. Some are not possible atm because // of the legacy SQL calls like checking permissions. diff --git a/src/Umbraco.Tests/Controllers/WebApiEditors/MediaControllerUnitTests.cs b/src/Umbraco.Tests/Controllers/WebApiEditors/MediaControllerUnitTests.cs index 0a81bc1b4b..c24b5059a2 100644 --- a/src/Umbraco.Tests/Controllers/WebApiEditors/MediaControllerUnitTests.cs +++ b/src/Umbraco.Tests/Controllers/WebApiEditors/MediaControllerUnitTests.cs @@ -13,7 +13,7 @@ namespace Umbraco.Tests.Controllers.WebApiEditors public class MediaControllerUnitTests { [Test] - public void Does_Not_Throw_Exception_When_Access_Allowed_By_Path() + public void Access_Allowed_By_Path() { //arrange var userMock = new Mock(); @@ -54,7 +54,7 @@ namespace Umbraco.Tests.Controllers.WebApiEditors } [Test] - public void Throws_Exception_When_No_Access_By_Path() + public void No_Access_By_Path() { //arrange var userMock = new Mock(); @@ -75,5 +75,68 @@ namespace Umbraco.Tests.Controllers.WebApiEditors Assert.IsFalse(result); } + [Test] + public void Access_To_Root_By_Path() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartMediaId).Returns(-1); + var user = userMock.Object; + + //act + var result = MediaController.CheckPermissions(new Dictionary(), user, null, -1); + + //assert + Assert.IsTrue(result); + } + + [Test] + public void No_Access_To_Root_By_Path() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartMediaId).Returns(1234); + var user = userMock.Object; + + //act + var result = MediaController.CheckPermissions(new Dictionary(), user, null, -1); + + //assert + Assert.IsFalse(result); + } + + [Test] + public void Access_To_Recycle_Bin_By_Path() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartMediaId).Returns(-1); + var user = userMock.Object; + + //act + var result = MediaController.CheckPermissions(new Dictionary(), user, null, -21); + + //assert + Assert.IsTrue(result); + } + + [Test] + public void No_Access_To_Recycle_Bin_By_Path() + { + //arrange + var userMock = new Mock(); + userMock.Setup(u => u.Id).Returns(0); + userMock.Setup(u => u.StartMediaId).Returns(1234); + var user = userMock.Object; + + //act + var result = MediaController.CheckPermissions(new Dictionary(), user, null, -21); + + //assert + Assert.IsFalse(result); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 154b4a5b5a..b51d5b84c9 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -12,7 +12,7 @@ namespace Umbraco.Tests.Manifest [TestFixture] public class ManifestParserTests { - + [Test] public void Parse_Property_Editors_With_Pre_Vals() { @@ -82,16 +82,19 @@ namespace Umbraco.Tests.Manifest }, { alias: 'Test.Test2', - name: 'Test 2', + name: 'Test 2', defaultConfig: { key1: 'some default pre val' }, editor: { - view: '~/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html' + view: '~/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html', + hideLabel: true } } ]"); var parser = ManifestParser.GetPropertyEditors(a); Assert.AreEqual(2, parser.Count()); + + Assert.AreEqual(false, parser.ElementAt(0).ValueEditor.HideLabel); Assert.AreEqual("Test.Test1", parser.ElementAt(0).Alias); Assert.AreEqual("Test 1", parser.ElementAt(0).Name); Assert.AreEqual("/App_Plugins/MyPackage/PropertyEditors/MyEditor.html", parser.ElementAt(0).ValueEditor.View); @@ -104,6 +107,7 @@ namespace Umbraco.Tests.Manifest Assert.IsNotNull(manifestValidator2); Assert.AreEqual("regex", manifestValidator2.Type); + Assert.AreEqual(true, parser.ElementAt(1).ValueEditor.HideLabel); Assert.AreEqual("Test.Test2", parser.ElementAt(1).Alias); Assert.AreEqual("Test 2", parser.ElementAt(1).Name); Assert.IsTrue(parser.ElementAt(1).DefaultPreValues.ContainsKey("key1")); @@ -255,6 +259,46 @@ namespace Umbraco.Tests.Manifest //} + [Test] + public void Create_Manifest_With_Line_Comments() + { + var content4 = @"{ +//here's the property editors +propertyEditors: [], +//and here's the javascript +javascript: ['~/test.js', '~/test2.js']}"; + + var result = ManifestParser.CreateManifests(null, content4); + + Assert.AreEqual(1, result.Count()); + } + + [Test] + public void Create_Manifest_With_Surround_Comments() + { + var content4 = @"{ +propertyEditors: []/*we have empty property editors**/, +javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2.js']}"; + + var result = ManifestParser.CreateManifests(null, content4); + + Assert.AreEqual(1, result.Count()); + } + + [Test] + public void Create_Manifest_With_Error() + { + //NOTE: This is missing the final closing ] + var content4 = @"{ +propertyEditors: []/*we have empty property editors**/, +javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2.js' }"; + + var result = ManifestParser.CreateManifests(null, content4); + + //an error has occurred and been logged but processing continues + Assert.AreEqual(0, result.Count()); + } + [Test] public void Create_Manifest_From_File_Content() { @@ -282,9 +326,9 @@ namespace Umbraco.Tests.Manifest var result = ManifestParser.CreateManifests(null, content1, content2, content3, content4); Assert.AreEqual(4, result.Count()); - Assert.AreEqual(0, result.ElementAt(1).StyleSheetInitialize.Count); - Assert.AreEqual(2, result.ElementAt(2).StyleSheetInitialize.Count); - Assert.AreEqual(2, result.ElementAt(3).StyleSheetInitialize.Count); + Assert.AreEqual(0, result.ElementAt(1).StylesheetInitialize.Count); + Assert.AreEqual(2, result.ElementAt(2).StylesheetInitialize.Count); + Assert.AreEqual(2, result.ElementAt(3).StylesheetInitialize.Count); } diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs new file mode 100644 index 0000000000..27c6a2d533 --- /dev/null +++ b/src/Umbraco.Tests/MockTests.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; +using Moq; +using Umbraco.Web; + +namespace Umbraco.Tests +{ + [TestFixture] + public class MockTests + { + + [Test] + public void Can_Create_Empty_App_Context() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + Assert.Pass(); + } + + [Test] + public void Can_Create_Service_Context() + { + var svcCtx = new ServiceContext( + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new PackagingService( + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new RepositoryFactory(true), + new Mock().Object), + new Mock().Object, + new RelationService( + new Mock().Object, + new RepositoryFactory(true), + new Mock().Object), + new Mock().Object, + new Mock().Object); + Assert.Pass(); + } + + [Test] + public void Can_Create_Db_Context() + { + var dbCtx = new DatabaseContext(new Mock().Object); + Assert.Pass(); + } + + [Test] + public void Can_Create_App_Context_With_Services() + { + var appCtx = new ApplicationContext( + new DatabaseContext(new Mock().Object), + new ServiceContext( + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new PackagingService( + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new RepositoryFactory(true), + new Mock().Object), + new Mock().Object, + new RelationService( + new Mock().Object, + new RepositoryFactory(true), + new Mock().Object), + new Mock().Object, + new Mock().Object), + CacheHelper.CreateDisabledCacheHelper()); + Assert.Pass(); + } + + [Test] + public void Can_Assign_App_Context_Singleton() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var result = ApplicationContext.EnsureContext(appCtx, true); + Assert.AreEqual(appCtx, result); + } + + [Test] + public void Does_Not_Overwrite_App_Context_Singleton() + { + ApplicationContext.EnsureContext(new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()), true); + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + var result = ApplicationContext.EnsureContext(appCtx, false); + Assert.AreNotEqual(appCtx, result); + } + + [NUnit.Framework.Ignore("Need to fix more stuff up, this is ignore because an exception occurs because it wants to ensure we have a resolver initialized - need to make that process better for testability")] + [Test] + public void Can_Get_Umbraco_Context() + { + var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper()); + ApplicationContext.EnsureContext(appCtx, true); + + var umbCtx = UmbracoContext.EnsureContext( + new Mock().Object, + appCtx, + true); + + Assert.AreEqual(umbCtx, UmbracoContext.Current); + } + + } +} diff --git a/src/Umbraco.Tests/Auditing/AuditTests.cs b/src/Umbraco.Tests/Persistence/Auditing/AuditTests.cs similarity index 92% rename from src/Umbraco.Tests/Auditing/AuditTests.cs rename to src/Umbraco.Tests/Persistence/Auditing/AuditTests.cs index ff5e6bb164..68e7bcb003 100644 --- a/src/Umbraco.Tests/Auditing/AuditTests.cs +++ b/src/Umbraco.Tests/Persistence/Auditing/AuditTests.cs @@ -1,35 +1,35 @@ -using System.Linq; -using NUnit.Framework; -using Umbraco.Core.Auditing; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Auditing -{ - [TestFixture] - public class AuditTests : BaseDatabaseFactoryTest - { - [SetUp] - public override void Initialize() - { - base.Initialize(); - } - - [Test] - public void Can_Add_Audit_Entry() - { - Audit.Add(AuditTypes.System, "This is a System audit trail", 0, -1); - - var dtos = DatabaseContext.Database.Fetch("WHERE id > -1"); - - Assert.That(dtos.Any(), Is.True); - Assert.That(dtos.First().Comment, Is.EqualTo("This is a System audit trail")); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - } +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Auditing; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Persistence.Auditing +{ + [TestFixture] + public class AuditTests : BaseDatabaseFactoryTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [Test] + public void Can_Add_Audit_Entry() + { + Audit.Add(AuditTypes.System, "This is a System audit trail", 0, -1); + + var dtos = DatabaseContext.Database.Fetch("WHERE id > -1"); + + Assert.That(dtos.Any(), Is.True); + Assert.That(dtos.First().Comment, Is.EqualTo("This is a System audit trail")); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index fa75070ad4..49a63a007e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -189,6 +189,63 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void MemberRepository_Does_Not_Replace_Password_When_Null() + { + IMember sut; + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MemberTypeRepository memberTypeRepository; + using (var repository = CreateRepository(unitOfWork, out memberTypeRepository)) + { + var memberType = MockedContentTypes.CreateSimpleMemberType(); + memberTypeRepository.AddOrUpdate(memberType); + unitOfWork.Commit(); + + var member = MockedMember.CreateSimpleContent(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty", -1); + repository.AddOrUpdate(member); + unitOfWork.Commit(); + + sut = repository.Get(member.Id); + //when the password is null it will not overwrite what is already there. + sut.Password = null; + repository.AddOrUpdate(sut); + unitOfWork.Commit(); + sut = repository.Get(member.Id); + + Assert.That(sut.Password, Is.EqualTo("123")); + } + } + + [Test] + public void MemberRepository_Can_Update_Email_And_Login_When_Changed() + { + IMember sut; + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MemberTypeRepository memberTypeRepository; + using (var repository = CreateRepository(unitOfWork, out memberTypeRepository)) + { + var memberType = MockedContentTypes.CreateSimpleMemberType(); + memberTypeRepository.AddOrUpdate(memberType); + unitOfWork.Commit(); + + var member = MockedMember.CreateSimpleContent(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty", -1); + repository.AddOrUpdate(member); + unitOfWork.Commit(); + + sut = repository.Get(member.Id); + sut.Username = "This is new"; + sut.Email = "thisisnew@hello.com"; + repository.AddOrUpdate(sut); + unitOfWork.Commit(); + sut = repository.Get(member.Id); + + Assert.That(sut.Email, Is.EqualTo("thisisnew@hello.com")); + Assert.That(sut.Username, Is.EqualTo("This is new")); + } + } + [Test] public void Can_Create_Correct_Subquery() { diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 2b00ea75cf..4b13acf7ee 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -3,8 +3,8 @@ using System.Web.Routing; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Tests.Stubs; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.Models; using Umbraco.Web.Mvc; diff --git a/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs b/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs index 0798da0770..375964c00d 100644 --- a/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/uQueryGetNodeIdByUrlTests.cs @@ -1,7 +1,7 @@ using Moq; using NUnit.Framework; -using Umbraco.Tests.Stubs; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web.Routing; using umbraco.BusinessLogic; using umbraco.cms.businesslogic.template; diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 54c6284748..5c300ab7a4 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -22,7 +22,6 @@ using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Publishing; using Umbraco.Core.Services; -using Umbraco.Tests.Stubs; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.XmlPublishedCache; diff --git a/src/Umbraco.Tests/TestHelpers/BaseRoutingTest.cs b/src/Umbraco.Tests/TestHelpers/BaseRoutingTest.cs index ab787bf105..d55a437976 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseRoutingTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseRoutingTest.cs @@ -4,7 +4,7 @@ using System.Web.Routing; using NUnit.Framework; using Umbraco.Core.Configuration; using Umbraco.Core.Models; -using Umbraco.Tests.Stubs; +using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index 064e48dd7f..36619e5f77 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -79,4 +79,4 @@ namespace Umbraco.Tests.TestHelpers } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Stubs/FakeLastChanceFinder.cs b/src/Umbraco.Tests/TestHelpers/Stubs/FakeLastChanceFinder.cs similarity index 81% rename from src/Umbraco.Tests/Stubs/FakeLastChanceFinder.cs rename to src/Umbraco.Tests/TestHelpers/Stubs/FakeLastChanceFinder.cs index 9090909baa..c9f5dbe024 100644 --- a/src/Umbraco.Tests/Stubs/FakeLastChanceFinder.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/FakeLastChanceFinder.cs @@ -1,12 +1,12 @@ -using Umbraco.Web.Routing; - -namespace Umbraco.Tests.Stubs -{ - internal class FakeLastChanceFinder : IContentFinder - { - public bool TryFindContent(PublishedContentRequest docRequest) - { - return false; - } - } +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.TestHelpers.Stubs +{ + internal class FakeLastChanceFinder : IContentFinder + { + public bool TryFindContent(PublishedContentRequest docRequest) + { + return false; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs similarity index 94% rename from src/Umbraco.Tests/Stubs/TestControllerFactory.cs rename to src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index 872093f896..5796ee5bc3 100644 --- a/src/Umbraco.Tests/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -1,40 +1,40 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Web.Mvc; -using System.Web.Routing; -using System.Web.SessionState; -using Umbraco.Core; - -namespace Umbraco.Tests.Stubs -{ - /// - /// Used in place of the UmbracoControllerFactory which relies on BuildManager which throws exceptions in a unit test context - /// - internal class TestControllerFactory : IControllerFactory - { - - public IController CreateController(RequestContext requestContext, string controllerName) - { - var types = TypeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); - - var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); - var t = controllerTypes.SingleOrDefault(); - - if (t == null) - return null; - - return Activator.CreateInstance(t) as IController; - } - - public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) - { - return SessionStateBehavior.Disabled; - } - - public void ReleaseController(IController controller) - { - controller.DisposeIfDisposable(); - } - } +using System; +using System.Linq; +using System.Reflection; +using System.Web.Mvc; +using System.Web.Routing; +using System.Web.SessionState; +using Umbraco.Core; + +namespace Umbraco.Tests.TestHelpers.Stubs +{ + /// + /// Used in place of the UmbracoControllerFactory which relies on BuildManager which throws exceptions in a unit test context + /// + internal class TestControllerFactory : IControllerFactory + { + + public IController CreateController(RequestContext requestContext, string controllerName) + { + var types = TypeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); + + var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); + var t = controllerTypes.SingleOrDefault(); + + if (t == null) + return null; + + return Activator.CreateInstance(t) as IController; + } + + public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) + { + return SessionStateBehavior.Disabled; + } + + public void ReleaseController(IController controller) + { + controller.DisposeIfDisposable(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index b5ad5b00e3..515cfd653c 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -164,7 +164,8 @@ - + + @@ -426,7 +427,7 @@ - + @@ -463,7 +464,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/docs/src/tutorials/Creating-Editors-Trees.ngdoc b/src/Umbraco.Web.UI.Client/docs/src/tutorials/Creating-Editors-Trees.ngdoc index 095719b05f..50596e4c7f 100644 --- a/src/Umbraco.Web.UI.Client/docs/src/tutorials/Creating-Editors-Trees.ngdoc +++ b/src/Umbraco.Web.UI.Client/docs/src/tutorials/Creating-Editors-Trees.ngdoc @@ -99,14 +99,14 @@ The main angular route to load in editors is */:section/:tree/:method/:id* Umbraco will load in views for this route based on these conventions: * If it is a core tree - views will be loaded from: */umbraco/views/{treetype}/{method}.html* - * If it is a custom (package) tree - views will be loaded from: */App_Plugins/{mypackage}/umbraco/{treetype}/{method}.html* + * If it is a custom (package) tree - views will be loaded from: */App_Plugins/{mypackage}/BackOffice/{treetype}/{method}.html* ###Editor locations By default each tree node's 'method' is assigned as 'edit' therefore these are the view paths for an editor when a tree node is clicked: * If it is a core tree - views will be loaded from: */umbraco/views/{treetype}/edit.html* - * If it is a custom (package) tree - views will be loaded from: */App_Plugins/{mypackage}/umbraco/{treetype}/edit.html* + * If it is a custom (package) tree - views will be loaded from: */App_Plugins/{mypackage}/BackOffice/{treetype}/edit.html* Developers can specify a custom `RoutePath` for any tree node which will cause umbraco to route to that specific location. @@ -115,7 +115,7 @@ Developers can specify a custom `RoutePath` for any tree node which will cause u Dialog view path locations are similar to editors: * If it is a core tree - views will be loaded from: umbraco/views/{treetype}/{action}.html - * If it is a custom (package) tree - views will be loaded from: /App_Plugins/{mypackage}/umbraco/{treetype}/{action}.html + * If it is a custom (package) tree - views will be loaded from: /App_Plugins/{mypackage}/BackOffice/{treetype}/{action}.html 'action' is the alias of your menu item, for example in the menu item in the example above this would be 'create'. @@ -127,7 +127,7 @@ An editor is simply an angular view (html file) so you can really do whatever yo First thing we'll do is create an angular controller for the editor, this controller will be contained in a file found beside the view - *the file naming conventions are based on the controller file naming conventions in the Umbraco core*. -/App_Plugins/MyPackage/Umbraco/MyTree/mypackage.mytree.edit.controller.js +/App_Plugins/MyPackage/BackOffice/MyTree/mypackage.mytree.edit.controller.js The controller is super simple, at it is going to do is assign a property to the $scope which shows the current item id being edited: @@ -146,7 +146,7 @@ The controller is super simple, at it is going to do is assign a property to the As per the conventions above our editor view will need to be located at: -/App_Plugins/MyPackage/Umbraco/MyTree/edit.html +/App_Plugins/MyPackage/BackOffice/MyTree/edit.html The view is simple, it is just going to show the current id being edited @@ -161,5 +161,5 @@ The view is simple, it is just going to show the current id being edited This is the same principle as an editor, you just need to follow conventions. Based on the above conventions the 'create' dialog view will be located here: -/App_Plugins/MyPackage/Umbraco/MyTree/create.html +/App_Plugins/MyPackage/BackOffice/MyTree/create.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbcontentname.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbcontentname.directive.js index 5ccfeb047b..1a8839171d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbcontentname.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/editors/umbcontentname.directive.js @@ -19,9 +19,11 @@ angular.module("umbraco.directives") }, link: function(scope, element, attrs, ngModel) { + var inputElement = element.find("input"); + ngModel.$render = function(){ $timeout(function(){ - if(scope.model === ""){ + if(!scope.model){ scope.goEdit(); } }, 100); @@ -29,15 +31,21 @@ angular.module("umbraco.directives") scope.goEdit = function(){ scope.editMode = true; - $timeout(function(){ - element.find("input").focus(); + $timeout(function () { + inputElement.focus(); + if (inputElement.val() === "Empty...") { + inputElement.select(); + } }, 100); }; scope.exitEdit = function(){ scope.editMode = false; - if(scope.model === ""){ + if (!scope.model) { + //TODO: This will not solve the problem of showing validation! + // if this is a duplicate name the server will return a server side valiation + // message - and we still have no place for this to display. scope.model = "Empty..."; } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js index 89adf18abf..5432add07a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js @@ -4,10 +4,10 @@ * @restrict E **/ angular.module("umbraco.directives.html") - .directive('umbControlGroup', function () { + .directive('umbControlGroup', function (localizationService) { return { scope: { - label: "@", + label: "@label", description: "@", hideLabel: "@", alias: "@" @@ -15,6 +15,13 @@ angular.module("umbraco.directives.html") transclude: true, restrict: 'E', replace: true, - templateUrl: 'views/directives/html/umb-control-group.html' + templateUrl: 'views/directives/html/umb-control-group.html', + link: function (scope, element, attr){ + if(scope.label && scope.label[0] === "@"){ + scope.labelstring = localizationService.localize(scope.label.substring(1)); + }else{ + scope.labelstring = scope.label; + } + } }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/localize.directive.js index 70c412465b..578258a13b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/localize.directive.js @@ -8,13 +8,37 @@ angular.module("umbraco.directives") replace: true, link: function (scope, element, attrs) { var key = scope.key; - if(key[0] === '#') - { - key = key.slice(1); - } - - var value = localizationService.getLocalizedString(key); + var value = localizationService.localize(key); element.html(value); } }; +}) +.directive('localize', function ($log, localizationService) { + return { + restrict: 'A', + link: function (scope, element, attrs) { + var keys = attrs.localize.split(','); + + for (var i = keys.length - 1; i >= 0; i--) { + var attr = element.attr(keys[i]); + + if(attr){ + var localizer = attr.split(':'); + var tokens; + var key = localizer[0]; + + if(localizer.length > 0){ + tokens = localizer[1].split(','); + for (var x = 0; x < tokens.length; x++) { + tokens[x] = scope.$eval(tokens[x]); + } + } + + if(key[0] === '@'){ + element.attr(keys[i], localizationService.localize(key.substring(1), tokens)); + } + } + } + } + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js new file mode 100644 index 0000000000..f9c83d2d52 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js @@ -0,0 +1,71 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbSections +* @restrict E +**/ +function sectionsDirective($timeout, $window, navigationService, sectionResource) { + return { + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + templateUrl: 'views/directives/umb-sections.html', + link: function (scope, element, attr, ctrl) { + + scope.maxSections = 7; + scope.overflowingSections = 0; + scope.sections = []; + + function loadSections(){ + sectionResource.getSections() + .then(function (result) { + scope.sections = result; + calculateHeight(); + }); + } + + function calculateHeight(){ + $timeout(function(){ + //total height minus room for avatar and help icon + var height = $(window).height()-200; + scope.totalSections = scope.sections.length; + scope.maxSections = Math.floor(height / 70); + scope.needTray = false; + + if(scope.totalSections > scope.maxSections){ + scope.needTray = true; + scope.overflowingSections = scope.maxSections - scope.totalSections; + } + }); + } + + //When the user logs in + scope.$on("authenticated", function (evt, data) { + //populate their sections if the user has changed + if (data.lastUserId !== data.user.id) { + loadSections(); + } + }); + + //on page resize + window.onresize = calculateHeight; + + scope.avatarClick = function(){ + navigationService.showUserDialog(); + }; + + scope.sectionClick = function(section){ + navigationService.showTree(section.alias); + }; + + scope.sectionDblClick = function(section){ + navigationService.changeSection(section.alias); + }; + + scope.trayClick = function(){ + navigationService.showTray(); + }; + + } + }; +} + +angular.module('umbraco.directives').directive("umbSections", sectionsDirective); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js index a95d12a315..f273f67556 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js @@ -13,12 +13,13 @@ angular.module("umbraco.directives") scope: { section: '@', + treealias: '@', + path: '@', activetree: '@', showoptions: '@', showheader: '@', cachekey: '@', - eventhandler: '=', - path: '@' + eventhandler: '=' }, compile: function (element, attrs) { @@ -31,12 +32,12 @@ angular.module("umbraco.directives") if(!hideheader){ template +='
' + - '
{{tree.name}}
' + + '
{{tree.name}}
' + '' + '
'; } template += '
    ' + - '' + + '' + '
' + '' + ''; @@ -65,24 +66,29 @@ angular.module("umbraco.directives") /** Method to load in the tree data */ function loadTree() { - if (scope.section) { + if (!scope.loading && scope.section) { + + scope.loading = true; //anytime we want to load the tree we need to disable the delete animations enableDeleteAnimations = false; //use $q.when because a promise OR raw data might be returned. - $q.when(treeService.getTree({ section: scope.section, cachekey: scope.cachekey })) + $q.when(treeService.getTree({ section: scope.section, tree: scope.treealias, cachekey: scope.cachekey })) .then(function (data) { //set the data once we have it scope.tree = data; //do timeout so that it re-enables them after this digest $timeout(function() { + //enable delete animations enableDeleteAnimations = true; }); + scope.loading = false; }, function (reason) { + scope.loading = false; notificationsService.error("Tree Error", reason); }); } @@ -123,21 +129,23 @@ angular.module("umbraco.directives") //watch for section changes scope.$watch("section", function (newVal, oldVal) { - if(!scope.tree){ - loadTree(); - } + + if(!scope.tree){ + loadTree(); + } - if (!newVal) { - //store the last section loaded - lastSection = oldVal; - }else if (newVal !== oldVal && newVal !== lastSection) { - //only reload the tree data and Dom if the newval is different from the old one - // and if the last section loaded is different from the requested one. - loadTree(); - - //store the new section to be loaded as the last section - lastSection = newVal; - } + if (!newVal) { + //store the last section loaded + lastSection = oldVal; + }else if (newVal !== oldVal && newVal !== lastSection) { + //only reload the tree data and Dom if the newval is different from the old one + // and if the last section loaded is different from the requested one. + loadTree(); + + //store the new section to be loaded as the last section + lastSection = newVal; + } + }); //watch for path changes diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js index 1fe91ae975..427399fbea 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js @@ -29,10 +29,11 @@ angular.module("umbraco.directives") eventhandler: '=', path: '@', node:'=', - activetree:'@' + activetree:'@', + tree:'=' }, - template: '
  • ' + + template: '
  • ' + '' + '' + '' + @@ -61,7 +62,7 @@ angular.module("umbraco.directives") about it. */ scope.options = function(e, n, ev){ - emitEvent("treeOptionsClick", {element: e, node: n, event: ev}); + emitEvent("treeOptionsClick", {element: e, tree: scope.tree, node: n, event: ev}); }; /** @@ -71,7 +72,7 @@ angular.module("umbraco.directives") defined on the tree */ scope.select = function(e,n,ev){ - emitEvent("treeNodeSelect", { element: e, node: n, event: ev }); + emitEvent("treeNodeSelect", { element: e, tree: scope.tree, node: n, event: ev }); }; /** method to set the current animation for the node. @@ -95,7 +96,7 @@ angular.module("umbraco.directives") scope.load = function(arrow, node) { if (node.expanded) { enableDeleteAnimations = false; - emitEvent("treeNodeCollapsing", { element: arrow, node: node }); + emitEvent("treeNodeCollapsing", { element: arrow, tree: scope.tree, node: node }); node.expanded = false; } else { @@ -106,19 +107,19 @@ angular.module("umbraco.directives") /* helper to force reloading children of a tree node */ scope.loadChildren = function(arrow, node, forceReload){ //emit treeNodeExpanding event, if a callback object is set on the tree - emitEvent("treeNodeExpanding", { element: arrow, node: node }); + emitEvent("treeNodeExpanding", { element: arrow, tree: scope.tree, node: node }); if (node.hasChildren && (forceReload || !node.children || (angular.isArray(node.children) && node.children.length === 0))) { //get the children from the tree service treeService.loadNodeChildren({ node: node, section: scope.section }) .then(function(data) { //emit expanded event - emitEvent("treeNodeExpanded", { element: arrow, node: node, children: data }); + emitEvent("treeNodeExpanded", { element: arrow, tree: scope.tree, node: node, children: data }); enableDeleteAnimations = true; }); } else { - emitEvent("treeNodeExpanded", { element: arrow, node: node, children: node.children }); + emitEvent("treeNodeExpanded", { element: arrow, tree: scope.tree, node: node, children: node.children }); node.expanded = true; enableDeleteAnimations = true; } @@ -139,6 +140,10 @@ angular.module("umbraco.directives") scope.loadChildren(null, scope.node, true); }else if( !node.metaData.treeAlias && activePath.indexOf(node.id) >= 0){ scope.loadChildren(null, scope.node, true); + + scope.path = activePath.filter( function(element) { + return listToDelete.indexOf(obj.id) === -1; + }); } } }; @@ -146,8 +151,9 @@ angular.module("umbraco.directives") //if the current path contains the node id, we will auto-expand the tree item children scope.expandActivePath(scope.node, scope.activetree, scope.path); + scope.node.stateCssClass = scope.node.cssClasses.join(" "); - var template = '
    '; + var template = '
    '; var newElement = angular.element(template); $compile(newElement)(scope); element.append(newElement); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js index 5e7f042825..1a36dcc24f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js @@ -1,8 +1,10 @@ angular.module('umbraco.directives.validation') .directive('valCompare',function () { return { - require: "ngModel", - link: function(scope, elem, attrs, ctrl) { + require: "ngModel", + link: function (scope, elem, attrs, ctrl) { + + //TODO: Pretty sure this should be done using a requires ^form in the directive declaration var otherInput = elem.inheritedData("$formController")[attrs.valCompare]; ctrl.$parsers.push(function(value) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 75ca905c4a..ad1b268618 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -5,8 +5,9 @@ * @element textarea * @requires formController * @description This directive is used to control the display of the property level validation message. -* We will listen for server side validation changes -* and when an error is detected for this property we'll show the error message +* We will listen for server side validation changes +* and when an error is detected for this property we'll show the error message. +* In order for this directive to work, the valStatusChanged directive must be placed on the containing form. **/ function valPropertyMsg(serverValidationManager) { return { @@ -32,17 +33,16 @@ function valPropertyMsg(serverValidationManager) { scope.errorMsg = ""; //listen for form error changes - scope.$watch(function() { - return formCtrl.$error; - }, function() { - if (formCtrl.$invalid) { + scope.$on("valStatusChanged", function(evt, args) { + if (args.form.$invalid) { //first we need to check if the valPropertyMsg validity is invalid if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { //since we already have an error we'll just return since this means we've already set the // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe return; - } else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) { + } + else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) { //check if it's one of the properties that is invalid in the current content property hasError = true; //update the validation message if we don't already have one assigned. @@ -54,18 +54,20 @@ function valPropertyMsg(serverValidationManager) { } scope.errorMsg = err ? err.errorMsg : "Property has errors"; } - } else { + } + else { hasError = false; scope.errorMsg = ""; } - } else { + } + else { hasError = false; scope.errorMsg = ""; } }, true); //listen for the forms saving event - scope.$on("saving", function (ev, args) { + scope.$on("saving", function(ev, args) { showValidation = true; if (hasError && scope.errorMsg === "") { var err; @@ -73,7 +75,7 @@ function valPropertyMsg(serverValidationManager) { if (scope.property) { err = serverValidationManager.getPropertyError(scope.property.alias, ""); } - scope.errorMsg = err ? err.errorMsg : "Property has errors"; + scope.errorMsg = err ? err.errorMsg : "Property has errors"; } else if (!hasError) { scope.errorMsg = ""; @@ -93,14 +95,16 @@ function valPropertyMsg(serverValidationManager) { // So once a field is changed that has a server error assigned to it // we need to re-validate it for the server side validator so the user can resubmit // the form. Of course normal client-side validators will continue to execute. - scope.$watch("property.value", function (newValue) { + scope.$watch("property.value", function(newValue) { //we are explicitly checking for valServer errors here, since we shouldn't auto clear // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg // is the only one, then we'll clear. var errCount = 0; for (var e in formCtrl.$error) { - errCount++; + if (e) { + errCount++; + } } if ((errCount === 1 && formCtrl.$error.valPropertyMsg !== undefined) || diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js index bf37d73392..d1103cdbc3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js @@ -6,17 +6,36 @@ * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string **/ function valRegex() { + return { require: 'ngModel', restrict: "A", link: function (scope, elm, attrs, ctrl) { + var flags = ""; + if (attrs.valRegexFlags) { + try { + flags = scope.$eval(attrs.valRegexFlags); + if (!flags) { + flags = attrs.valRegexFlags; + } + } + catch (e) { + flags = attrs.valRegexFlags; + } + } var regex; try { - regex = new RegExp(scope.$eval(attrs.valRegex)); + var resolved = scope.$eval(attrs.valRegex); + if (resolved) { + regex = new RegExp(resolved, flags); + } + else { + regex = new RegExp(attrs.valRegex, flags); + } } catch(e) { - regex = new RegExp(attrs.valRegex); + regex = new RegExp(attrs.valRegex, flags); } var patternValidator = function (viewValue) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valstatuschanged.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valstatuschanged.directive.js new file mode 100644 index 0000000000..aeb3b39ff7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valstatuschanged.directive.js @@ -0,0 +1,24 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:valStatusChanged +* @restrict A +* @description Used to broadcast an event to all elements inside this one to notify that form validation has +* changed. If we don't use this that means you have to put a watch for each directive on a form's validation +* changing which would result in much higher processing. We need to actually watch the whole $error collection of a form +* because just watching $valid or $invalid doesn't acurrately trigger form validation changing. +**/ +function valStatusChanged() { + return { + require: "form", + restrict: "A", + link: function (scope, element, attr, formCtrl) { + + scope.$watch(function () { + return formCtrl.$error; + }, function () { + scope.$broadcast("valStatusChanged", { form: formCtrl }); + }, true); + } + }; +} +angular.module('umbraco.directives').directive("valStatusChanged", valStatusChanged); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js index 9740adfabc..c38466ac45 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js @@ -4,6 +4,7 @@ * @name umbraco.directives.directive:valTab * @restrict A * @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data. +* In order for this directive to work, the valStatusChanged directive must be placed on the containing form. **/ function valTab() { return { @@ -15,23 +16,22 @@ function valTab() { scope.tabHasError = false; - //watch the current form's validation for the current field name - scope.$watch(function() { - return formCtrl.$valid; - }, function() { - var tabContent = element.closest(".umb-panel").find("#" + tabId); - - if (formCtrl.$invalid) { + //listen for form validation changes + scope.$on("valStatusChanged", function(evt, args) { + if (!args.form.$valid) { + var tabContent = element.closest(".umb-panel").find("#" + tabId); //check if the validation messages are contained inside of this tabs if (tabContent.find(".ng-invalid").length > 0) { scope.tabHasError = true; } else { scope.tabHasError = false; } - } else { + } + else { scope.tabHasError = false; } }); + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 7cf9c61111..72cbfe9cd2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -531,8 +531,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { */ publish: function (content, isNew, files) { return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); + }, + + publishById: function(id){ + + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); + } + }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index a23697344b..e008f3b69f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -95,7 +95,7 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetById", - [{ id: id, type: type }])), + [{ id: id}, {type: type }])), 'Failed to retreive entity data for id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index bf726be952..3f27e308a5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -30,7 +30,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { * @methodOf umbraco.resources.memberResource * * @description - * Gets a member item with a given id + * Gets a member item with a given key * * ##usage *
    @@ -41,7 +41,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
              *    });
              * 
    * - * @param {Int} id id of member item to return + * @param {Guid} key key of member item to return * @returns {Promise} resourcePromise object containing the member item. * */ @@ -62,7 +62,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { * @methodOf umbraco.resources.memberResource * * @description - * Deletes a member item with a given id + * Deletes a member item with a given key * * ##usage *
    @@ -72,7 +72,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
              *    });
              * 
    * - * @param {Int} id id of member item to delete + * @param {Guid} key id of member item to delete * @returns {Promise} resourcePromise object. * */ diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js index 66391893ea..9938012cc3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js @@ -39,12 +39,19 @@ function treeResource($q, $http, umbRequestHelper) { throw "The object specified for does not contain a 'section' property"; } + if(!options.tree){ + options.tree = ""; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "treeApplicationApiBaseUrl", "GetApplicationTrees", - [{ application: options.section }])), + [ + {application: options.section}, + {tree: options.tree} + ])), 'Failed to retreive data for application tree ' + options.section); }, 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 index 03fd6b2946..25faf0b75c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -213,7 +213,7 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser } args.scope.$broadcast("saved", { scope: args.scope }); - if (!this.redirectToCreatedContent(args.newContent.id)) { + if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.newContent.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. 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 08f1690a1b..842f6ff7c3 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 @@ -66,7 +66,7 @@ angular.module('umbraco.services') var scope = options.scope || $rootScope.$new(); //Modal dom obj and unique id - dialog.element = $('
    '); + dialog.element = $('
    '); var id = dialog.template.replace('.html', '').replace('.aspx', '').replace(/[\/|\.|:\&\?\=]/g, "-") + '-' + scope.$id; if (options.inline) { @@ -355,6 +355,25 @@ angular.module('umbraco.services') return openDialog(options); }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#memberPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a member picker in a modal, the callback returns a object representing the selected member + * @param {Object} options member picker dialog options object + * @param {$scope} options.scope dialog scope + * @param {$scope} options.multiPicker should the tree pick one or multiple members before returning + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + memberPicker: function (options) { + options.template = 'views/common/dialogs/memberPicker.html'; + options.show = true; + return openDialog(options); + }, + /** * @ngdoc method * @name umbraco.services.dialogService#iconPicker @@ -383,6 +402,7 @@ angular.module('umbraco.services') * @param {Object} options iconpicker dialog options object * @param {$scope} options.scope dialog scope * @param {$scope} options.section tree section to display + * @param {$scope} options.treeAlias specific tree to display * @param {$scope} options.multiPicker should the tree pick one or multiple items before returning * @param {Function} options.callback callback function * @returns {Object} modal object diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index b7b7b99b98..4f09611ef5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -1,8 +1,6 @@ angular.module('umbraco.services') .factory('localizationService', function ($http, $q, $rootScope, $window, $filter, userService) { - var localize = { - // use the $window service to get the language of the user's browser - language: userService.getCurrentUser().locale, + var service = { // array to hold the localized resource string entries dictionary:[], // location of the resource file @@ -13,61 +11,76 @@ angular.module('umbraco.services') // success handler for all server communication successCallback:function (data) { // store the returned array in the dictionary - localize.dictionary = data; + service.dictionary = data; // set the flag that the resource are loaded - localize.resourceFileLoaded = true; + service.resourceFileLoaded = true; // broadcast that the file has been loaded $rootScope.$broadcast('localizeResourcesUpdates'); }, // allows setting of language on the fly setLanguage: function(value) { - localize.language = value; - localize.initLocalizedResources(); + service.initLocalizedResources(); }, // allows setting of resource url on the fly setUrl: function(value) { - localize.url = value; - localize.initLocalizedResources(); - }, - - // builds the url for locating the resource file - buildUrl: function() { - return '/i18n/resources-locale_' + localize.language + '.js'; + service.url = value; + service.initLocalizedResources(); }, // loads the language resource file from the server initLocalizedResources:function () { var deferred = $q.defer(); // build the url to retrieve the localized resource file - $http({ method:"GET", url:localize.url, cache:false }) + $http({ method:"GET", url:service.url, cache:false }) .then(function(response){ - localize.resourceFileLoaded = true; - localize.dictionary = response.data; + service.resourceFileLoaded = true; + service.dictionary = response.data; $rootScope.$broadcast('localizeResourcesUpdates'); - return deferred.resolve(localize.dictionary); + return deferred.resolve(service.dictionary); }, function(err){ return deferred.reject("Something broke"); }); return deferred.promise; }, + //helper to tokenize and compile a localization string + tokenize: function(value,scope) { + if(value){ + var localizer = value.split(':'); + var retval = {tokens: undefined, key: localizer[0].substring(0)}; + if(localizer.length > 0){ + retval.tokens = localizer[1].split(','); + for (var x = 0; x < retval.tokens.length; x++) { + retval.tokens[x] = scope.$eval(retval.tokens[x]); + } + } + + return retval; + } + }, + // checks the dictionary for a localized resource string - getLocalizedString: function(value) { - if(localize.resourceFileLoaded){ - return _lookup(value); + localize: function(value,tokens) { + if(service.resourceFileLoaded){ + return service._lookup(value,tokens); }else{ - localize.initLocalizedResources().then(function(dic){ - return _lookup(value); + service.initLocalizedResources().then(function(dic){ + return service._lookup(value,tokens); }); } }, - _lookup: function(value){ - var entry = localize.dictionary[value]; + _lookup: function(value,tokens){ + var entry = service.dictionary[value]; if(entry){ + if(tokens){ + for (var i = 0; i < tokens.length; i++) { + entry = entry.replace("%"+i+"%", tokens[i]); + } + } return entry; } return "[" + value + "]"; @@ -77,8 +90,8 @@ angular.module('umbraco.services') }; // force the load of the resource file - localize.initLocalizedResources(); + service.initLocalizedResources(); // return the local instance when called - return localize; + return service; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 2495bbf00c..326f4f51d6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -31,7 +31,7 @@ angular.module('umbraco.services') ui.showContextMenu = false; ui.showContextMenuDialog = false; ui.stickyNavigation = false; - + ui.showTray = false; service.hideUserDialog(); //$("#search-form input").focus(); break; @@ -53,6 +53,11 @@ angular.module('umbraco.services') ui.showContextMenu = false; ui.showSearchResults = true; ui.showContextMenuDialog = false; + + $timeout(function(){ + $("#search-field").focus(); + }); + break; default: ui.showNavigation = false; @@ -60,6 +65,7 @@ angular.module('umbraco.services') ui.showContextMenuDialog = false; ui.showSearchResults = false; ui.stickyNavigation = false; + ui.showTray = false; break; } } @@ -121,7 +127,14 @@ angular.module('umbraco.services') } setMode("tree"); }, - + + showTray: function () { + ui.showTray = true; + }, + + hideTray: function () { + ui.showTray = false; + }, /** * @ngdoc method * @name umbraco.services.navigationService#syncTree @@ -160,6 +173,10 @@ angular.module('umbraco.services') //TODO: investicate if we need to halt watch triggers //and instead pause them and then manually tell the tree to digest path changes //as this might be a bit heavy loading + if(!angular.isArray(path)){ + path = path.split(","); + } + this.ui.currentPath = path; }, @@ -192,7 +209,6 @@ angular.module('umbraco.services') return; } - service.active = false; $timeout(function(){ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 39dda8f5d5..a5461a20cd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -1,56 +1,77 @@ angular.module('umbraco.services') -.factory('searchService', function ($q, $log, entityResource) { +.factory('searchService', function ($q, $log, entityResource, contentResource) { var m = {results: []}; - return { + var service = { results: m, - search: function(term){ - m.results.length = 0; - var deferred = $q.defer(); - var i = 0; - - entityResource.search(term, "Document").then(function(data){ + searchMembers: function(args){ + entityResource.search(args.term, "Member").then(function(data){ _.each(data, function(el){ - el.menuUrl = "UmbracoTrees/ContentTree/GetMenu?id=" + el.id + "&application=content"; + el.menuUrl = "UmbracoTrees/MemberTree/GetMenu?id=" + el.Id + "&application=member"; + el.metaData = {treeAlias: "member"}; + el.title = el.Fields.nodeName; + el.subTitle = el.Fields.email; + el.id = el.Id; }); - m.results.push({ + args.results.push({ + icon: "icon-user", + editor: "member/member/edit/", + matches: data + }); + }); + }, + searchContent: function(args){ + entityResource.search(args.term, "Document").then(function(data){ + + _.each(data, function(el){ + el.menuUrl = "UmbracoTrees/ContentTree/GetMenu?id=" + el.Id + "&application=content"; + el.metaData = {treeAlias: "content"}; + el.title = el.Fields.nodeName; + el.id = el.Id; + + contentResource.getNiceUrl(el.Id).then(function(url){ + el.subTitle = angular.fromJson(url); + }); + }); + + args.results.push({ icon: "icon-document", editor: "content/content/edit/", matches: data }); - i++; - - - if(i === 2){ - deferred.resolve(m); - } }); - - entityResource.search(term, "Media").then(function(data){ + }, + searchMedia: function(args){ + entityResource.search(args.term, "Media").then(function(data){ _.each(data, function(el){ - el.menuUrl = "UmbracoTrees/MediaTree/GetMenu?id=" + el.id + "&application=media"; + el.menuUrl = "UmbracoTrees/MediaTree/GetMenu?id=" + el.Id + "&application=media"; + el.metaData = {treeAlias: "media"}; + el.title = el.Fields.nodeName; + el.id = el.Id; }); - m.results.push({ + args.results.push({ icon: "icon-picture", editor: "media/media/edit/", matches: data }); - i++; - - if(i === 2){ - deferred.resolve(m); - } }); + }, + search: function(term){ + m.results.length = 0; - return deferred.promise; + service.searchMedia({term:term, results:m.results}); + service.searchContent({term:term, results:m.results}); + service.searchMembers({term:term, results:m.results}); }, setCurrent: function(sectionAlias){ currentSection = sectionAlias; } }; + + return service; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index cdeef6c823..038bf00113 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -27,6 +27,22 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro 'Failed to retreive entity data for id '); }, + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#defaultPrevalues + * @methodOf umbraco.services.tinyMceService + * + * @description + * Returns a default configration to fallback on in case none is provided + * + */ + defaultPrevalues: function () { + var cfg = {}; + cfg.toolbar = ["code", "bold", "italic", "umbracocss","alignleft", "aligncenter", "alignright", "bullist","numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"]; + cfg.stylesheets = []; + cfg.dimensions = {height: 400, width: 600}; + return cfg; + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index b34cd72c79..ff079dde36 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -234,7 +234,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc .then(function(data) { //this will be called once the tree app data has loaded var result = { - name: section, + name: data.name, alias: section, root: data }; 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 index d44bb9ccb5..c39b56a5e9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -19,24 +19,31 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ */ dictionaryToQueryString: function (queryStrings) { - if (!angular.isArray(queryStrings)) { - throw "The queryString parameter is not an array of key value pairs"; + + if (angular.isArray(queryStrings)) { + 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("&"); } - 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("&"); + /* + //if we have a simple object, we can simply map with $.param + //but with the current structure we cant since an array is an object and an object is an array + if(angular.isObject(queryStrings)){ + return decodeURIComponent($.param(queryStrings)); + }*/ + throw "The queryString parameter is not an array of key value pairs"; }, /** 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 1deb464037..1fc040527a 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 @@ -203,7 +203,7 @@ function umbDataFormatter() { /** formats the display model used to display the member to the model used to save the member */ formatMemberPostData: function(displayModel, action) { - //this is basically the same as for media but we need to explicitly add the username,email to the save model + //this is basically the same as for media but we need to explicitly add the username,email, password to the save model var saveModel = this.formatMediaPostData(displayModel, action); var genericTab = _.find(displayModel.tabs, function (item) { @@ -216,8 +216,12 @@ function umbDataFormatter() { var propEmail = _.find(genericTab.properties, function (item) { return item.alias === "_umb_email"; }); + var propPass = _.find(genericTab.properties, function (item) { + return item.alias === "_umb_password"; + }); saveModel.email = propEmail.value; saveModel.username = propLogin.value; + saveModel.password = propPass.value; return saveModel; }, diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index 7b1800850e..34656d0759 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -10,7 +10,7 @@ -
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 79aeca4333..3a35c06ad5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -4,12 +4,8 @@ // UMBRACO STYLES // -------------- -.hideLabel label { - display: none; -} -.hideLabel .controls-row { - margin-left: 0px !Important; -} + + small.umb-detail, label small, .guiDialogTiny { diff --git a/src/Umbraco.Web.UI.Client/src/less/grid.less b/src/Umbraco.Web.UI.Client/src/less/grid.less index dc2c1b5345..1734cba772 100644 --- a/src/Umbraco.Web.UI.Client/src/less/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/grid.less @@ -66,14 +66,26 @@ body { position: absolute; } -/* refactor colors into a seperate class */ #applications { z-index: 1000; height: 100%; - position: relative; + left: 0px; + top: 0; + bottom: 0; + position: absolute; text-align: center } +#applications-tray { + z-index: 900; + left: 80px; + top: 0; + bottom: 0; + position: absolute; + height: 100%; + text-align: center; +} + #search-form { display: block; margin: 0px; @@ -105,7 +117,6 @@ body { padding-top: 100px; } - #dialog { min-width: 500px; left: 100%; diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index fc932805ce..a82d72b1b0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -86,6 +86,10 @@ color: @grayLight } +.umb-listview .selected i.icon, .umb-listview tr:hover i.icon{display: none} +.umb-listview .selected input[type="checkbox"], .umb-listview tr:hover input[type="checkbox"]{display: inline-block;} +.umb-listview .inactive{color: @grayLight;} + .umb-listview table thead { font-size: 11px; font-weight: 600; diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index dc67ac1d3a..fe8c76bd19 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -113,7 +113,6 @@ h5{ padding-bottom: 15px; margin-bottom: 10px !important; } - .umb-pane > .umb-control-group:last-child { border: none; padding-bottom: 0 !important; @@ -146,7 +145,7 @@ h5{ display: inline-block } -.hidelabel .controls-row { +.hidelabel > div > .controls-row, .hidelabel > .controls-row { padding: 0px; border: none; margin: 0px !important; diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index ae854db66a..dba73d1c07 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -58,7 +58,7 @@ h1.headline{height: 40px; padding: 30px 0 0 20px;} position: absolute; padding: 0px 0px 0px 20px; } -.umb-panel-header h1, { +.umb-panel-header h1 { margin: 0; font-size: @fontSizeMedium; font-weight: 400; @@ -71,7 +71,6 @@ h1.headline{height: 40px; padding: 30px 0 0 20px;} .umb-headline-editor-wrapper input { background: none; border: none; - width: auto; margin: -9px 0 0 0; padding: 0 0 2px 0; border-radius: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index dcb134bd85..b0472e8e76 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -17,6 +17,13 @@ width: 95%; } +.umb-dialog .umb-editor { + width: 95%; +} +.umb-dialog .umb-control-group .help-block { + width: 95%; +} + .umb-codeeditor{ width: 99%; } diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 19ce8cb0c3..3f6f10ae74 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -6,6 +6,7 @@ ul.sections { display: block; background: @blackLight; height: 100%; + width: 80px; } ul.sections li { @@ -97,8 +98,7 @@ ul.sections li.current a{ padding-left: 0px; } -ul.sections li.help { - +ul.sections li.help { margin: 0; position: absolute; bottom: 0; @@ -110,3 +110,12 @@ ul.sections li.help { ul.sections li.help a { border-bottom: none; } + +// Section slide-out tray for additional apps +// ------------------------- +li.expand a, li.expand{border: none !Important;} + +ul.sections-tray { + padding-top: 100px; + width: 80px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 78aca06998..2aa2256860 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -138,6 +138,12 @@ background: @grayLighter } +.umb-tree small.search-subtitle{ + color: @grayLight; + display: block; + padding-left: 35px; +} + a.umb-options { visibility: hidden; cursor: pointer; @@ -208,6 +214,32 @@ li.root > div > a.umb-options { float: left } + +// Tree item states +// ------------------------- +div.not-published > i.icon,div.not-published > a{ + color: @grayLight; +} +div.protected:before{ + content:"\e256"; + font-family: 'icomoon'; + color: @red; + position: absolute; + font-size: 20px; + padding-left: 7px; + padding-top: 7px; +} + +div.has-unpublished-version:before{ + content:"\e25a"; + font-family: 'icomoon'; + color: @green; + position: absolute; + font-size: 20px; + padding-left: 7px; + padding-top: 7px; +} + // Tree context menu // ------------------------- .umb-actions { diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 9ef8f48768..3d4b1dbd8a 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -95,7 +95,7 @@ app.config(function ($routeProvider) { // Here we need to figure out if this route is for a package tree and if so then we need // to change it's convention view path to: - // /App_Plugins/{mypackage}/umbraco/{treetype}/{method}.html + // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html // otherwise if it is a core tree we use the core paths: // views/{treetype}/{method}.html @@ -105,7 +105,7 @@ app.config(function ($routeProvider) { if (packageTreeFolder) { $scope.templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + "/" + packageTreeFolder + - "/umbraco/" + $routeParams.tree + "/" + $routeParams.method + ".html"; + "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"; } else { $scope.templateUrl = 'views/' + $routeParams.tree + '/' + $routeParams.method + '.html'; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.controller.js index 4861499cf6..a172a482fb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.controller.js @@ -1,8 +1,41 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Dialogs.ContentPickerController", - function ($scope, eventsService, $log) { + function ($scope, eventsService, entityResource, searchService, $log) { var dialogOptions = $scope.$parent.dialogOptions; $scope.dialogTreeEventHandler = $({}); + $scope.results = []; + + $scope.select = function(result){ + entityResource.getById(result.id, "Document").then(function(ent){ + if(dialogOptions && dialogOptions.multipicker){ + + $scope.showSearch = false; + $scope.results = []; + $scope.term = ""; + $scope.oldTerm = undefined; + + $scope.select(ent); + }else{ + $scope.submit(ent); + } + }); + }; + + $scope.performSearch = function(){ + if($scope.term){ + if($scope.oldTerm !== $scope.term){ + $scope.results = []; + searchService.searchContent({term: $scope.term, results: $scope.results}); + $scope.showSearch = true; + $scope.oldTerm = $scope.term; + } + }else{ + $scope.oldTerm = ""; + $scope.showSearch = false; + $scope.results = []; + } + }; + $scope.dialogTreeEventHandler.bind("treeNodeSelect", function(ev, args){ args.event.preventDefault(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.html index 57549f01da..32109e5fcd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/contentpicker.html @@ -1,19 +1,54 @@
    -
    - - - - +
    +
    + +
    +
    +
  • + + + - -