diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs index c3a1df301d..c44c0cf0df 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs @@ -6,6 +6,8 @@ bool HideDisabledUsersInBackoffice { get; } + bool AllowPasswordReset { get; } + string AuthCookieName { get; } string AuthCookieDomain { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs index 34642c8c90..f280b3e20c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs @@ -28,6 +28,18 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } + [ConfigurationProperty("allowPasswordReset")] + internal InnerTextConfigurationElement AllowPasswordReset + { + get + { + return new OptionalInnerTextConfigurationElement( + (InnerTextConfigurationElement)this["allowPasswordReset"], + //set the default + true); + } + } + [ConfigurationProperty("authCookieName")] internal InnerTextConfigurationElement AuthCookieName { @@ -62,6 +74,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return HideDisabledUsersInBackoffice; } } + bool ISecuritySection.AllowPasswordReset + { + get { return AllowPasswordReset; } + } + string ISecuritySection.AuthCookieName { get { return AuthCookieName; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index f2428129c9..f40969e033 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -252,7 +252,7 @@ namespace Umbraco.Core.Persistence.Repositories // want latest content items because a pulished content item might not actually be the latest. // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982 var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, - MapQueryDtos, "Path", Direction.Ascending); + MapQueryDtos, "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -777,10 +777,11 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { var filterSql = Sql().Append("AND (cmsDocument.newest = 1)"); if (filter.IsNullOrWhiteSpace() == false) @@ -788,7 +789,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, MapQueryDtos, - orderBy, orderDirection, + orderBy, orderDirection, orderBySystemField, filterSql); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 32961ccf7c..d0bc046055 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -84,10 +84,11 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); /// /// Returns the persisted content's preview XML structure diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index b712622e63..816cb12f44 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -34,9 +34,10 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index 9cb74d1806..a24116f0e2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -44,16 +44,17 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Gets paged member results /// - /// - /// - /// - /// - /// - /// - /// + /// The query. + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query /// IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); //IEnumerable GetPagedResultsByQuery( // Sql sql, int pageIndex, int pageSize, out int totalRecords, diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 7afa96c746..e32a81b6b3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -242,7 +242,7 @@ namespace Umbraco.Core.Persistence.Repositories var processed = 0; do { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -488,17 +488,18 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { var filterSql = filter.IsNullOrWhiteSpace() ? null : Sql().Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @0)", "%" + filter + "%"); return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - MapQueryDtos, orderBy, orderDirection, + MapQueryDtos, orderBy, orderDirection, orderBySystemField, filterSql); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 5052784485..95bebc17b8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -278,7 +278,7 @@ namespace Umbraco.Core.Persistence.Repositories //Ensure that strings don't contain characters that are invalid in XML entity.SanitizeEntityPropertiesForXmlStorage(); - var dirtyEntity = (ICanBeDirty) entity; + var dirtyEntity = (ICanBeDirty)entity; //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed if (dirtyEntity.IsPropertyDirty("ParentId")) @@ -456,7 +456,7 @@ namespace Umbraco.Core.Persistence.Repositories var processed = 0; do { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -611,18 +611,19 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The where clause, if this is null all records are queried /// - /// - /// - /// - /// - /// - /// + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query /// /// /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using NPoco paging (SQL paging) /// public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { var filterSql = filter.IsNullOrWhiteSpace() ? null @@ -630,7 +631,7 @@ namespace Umbraco.Core.Persistence.Repositories "OR (cmsMember.LoginName LIKE @0))", "%" + filter + "%"); return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - MapQueryDtos, orderBy, orderDirection, + MapQueryDtos, orderBy, orderDirection, orderBySystemField, filterSql); } @@ -668,7 +669,7 @@ namespace Umbraco.Core.Persistence.Repositories var dtosWithContentTypes = dtos //This select into and null check are required because we don't have a foreign damn key on the contentType column // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new {dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId)}) + .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) .Where(x => x.contentType != null) .ToArray(); diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index f5954d1821..0e6fb58dea 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -255,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories } - private Sql PrepareSqlForPagedResults(Sql sql, Sql filterSql, string orderBy, Direction orderDirection) + private Sql PrepareSqlForPagedResults(Sql sql, Sql filterSql, string orderBy, Direction orderDirection, bool orderBySystemField) { if (filterSql == null && string.IsNullOrEmpty(orderBy)) return sql; @@ -271,29 +271,70 @@ namespace Umbraco.Core.Persistence.Repositories // apply sort if (string.IsNullOrEmpty(orderBy) == false) { - // get the database field eg "[table].[column]" - var dbfield = GetDatabaseFieldNameForOrderBy(orderBy); + if (orderBySystemField) + { + // get the database field eg "[table].[column]" + var dbfield = GetDatabaseFieldNameForOrderBy(orderBy); - // for SqlServer pagination to work, the "order by" field needs to be the alias eg if - // the select statement has "umbracoNode.text AS NodeDto__Text" then the order field needs - // to be "NodeDto__Text" and NOT "umbracoNode.text". - // not sure about SqlCE nor MySql, so better do it too. initially thought about patching - // NPoco but that would be expensive and not 100% possible, so better give NPoco proper - // queries to begin with. - // thought about maintaining a map of columns-to-aliases in the sql context but that would - // be expensive and most of the time, useless. so instead we parse the SQL looking for the - // alias. somewhat expensive too but nothing's free. + // for SqlServer pagination to work, the "order by" field needs to be the alias eg if + // the select statement has "umbracoNode.text AS NodeDto__Text" then the order field needs + // to be "NodeDto__Text" and NOT "umbracoNode.text". + // not sure about SqlCE nor MySql, so better do it too. initially thought about patching + // NPoco but that would be expensive and not 100% possible, so better give NPoco proper + // queries to begin with. + // thought about maintaining a map of columns-to-aliases in the sql context but that would + // be expensive and most of the time, useless. so instead we parse the SQL looking for the + // alias. somewhat expensive too but nothing's free. - var matches = VersionableRepositoryBaseAliasRegex.For(SqlSyntax).Matches(sql.SQL); - var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value == dbfield); - if (match != null) - dbfield = match.Groups[2].Value; + var matches = VersionableRepositoryBaseAliasRegex.For(SqlSyntax).Matches(sql.SQL); + var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value == dbfield); + if (match != null) + dbfield = match.Groups[2].Value; - var orderByParams = new object[] { dbfield }; - if (orderDirection == Direction.Ascending) - psql.OrderBy(orderByParams); + var orderByParams = new object[] {dbfield}; + if (orderDirection == Direction.Ascending) + psql.OrderBy(orderByParams); + else + psql.OrderByDescending(orderByParams); + } else - psql.OrderByDescending(orderByParams); + { + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // from most recent content version for the given order by field + var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); + var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); + var sortedString = string.Format("COALESCE({0},'')", "dataNvarchar"); + var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataDecimal"); + + var innerJoinTempTable = string.Format(@"INNER JOIN ( + SELECT CASE + WHEN dataInt Is Not Null THEN {0} + WHEN dataDecimal Is Not Null THEN {1} + WHEN dataDate Is Not Null THEN {2} + ELSE {3} + END AS CustomPropVal, + cd.nodeId AS CustomPropValContentId + FROM cmsDocument cd + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = cd.nodeId AND cpd.versionId = cd.versionId + INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId + WHERE cpt.Alias = @2 AND cd.newest = 1) AS CustomPropData + ON CustomPropData.CustomPropValContentId = umbracoNode.id +", sortedInt, sortedDecimal, sortedDate, sortedString); + + //insert this just above the LEFT OUTER JOIN + var newSql = psql.SQL.Insert(psql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); + var newArgs = psql.Arguments.ToList(); + newArgs.Add(orderBy); + + psql = new Sql(psql.SqlContext, newSql, newArgs.ToArray()); + + psql.OrderBy("CustomPropData.CustomPropVal"); + //psql.Append(innerJoinTempTable, orderBy); + if (orderDirection == Direction.Descending) + { + psql.Append(" DESC"); + } + } } return psql; @@ -301,7 +342,7 @@ namespace Umbraco.Core.Persistence.Repositories protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Func, IEnumerable> mapper, - string orderBy, Direction orderDirection, + string orderBy, Direction orderDirection, bool orderBySystemField, Sql filterSql = null) { if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); @@ -313,7 +354,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlNodeIds = translator.Translate(); // sort and filter - sqlNodeIds = PrepareSqlForPagedResults(sqlNodeIds, filterSql, orderBy, orderDirection); + sqlNodeIds = PrepareSqlForPagedResults(sqlNodeIds, filterSql, orderBy, orderDirection, orderBySystemField); // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIds); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 5778b4e618..fc26bcd7c9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -70,6 +70,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax bool SupportsClustered(); bool SupportsIdentityInsert(); bool? SupportsCaseInsensitiveQueries(Database db); + + string ConvertIntegerToOrderableString { get; } + string ConvertDateToOrderableString { get; } + string ConvertDecimalToOrderableString { get; } IEnumerable GetTablesInSchema(Database db); IEnumerable GetColumnsInSchema(Database db); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index e294b3bc78..479d575fc1 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax public MySqlSyntaxProvider(ILogger logger) { _logger = logger; - + AutoIncrementDefinition = "AUTO_INCREMENT"; IntColumnDefinition = "int(11)"; BoolColumnDefinition = "tinyint(1)"; @@ -27,7 +27,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax TimeColumnDefinition = "time"; DecimalColumnDefinition = "decimal(38,6)"; GuidColumnDefinition = "char(36)"; - + DefaultValueFormat = "DEFAULT {0}"; InitColumnTypeMap(); @@ -327,13 +327,13 @@ ORDER BY TABLE_NAME, INDEX_NAME", { case SystemMethods.NewGuid: return null; // NOT SUPPORTED! - //return "NEWID()"; + //return "NEWID()"; case SystemMethods.CurrentDateTime: return "CURRENT_TIMESTAMP"; - //case SystemMethods.NewSequentialId: - // return "NEWSEQUENTIALID()"; - //case SystemMethods.CurrentUTCDateTime: - // return "GETUTCDATE()"; + //case SystemMethods.NewSequentialId: + // return "NEWSEQUENTIALID()"; + //case SystemMethods.CurrentUTCDateTime: + // return "GETUTCDATE()"; } return null; @@ -361,6 +361,9 @@ ORDER BY TABLE_NAME, INDEX_NAME", public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } + public override string ConvertIntegerToOrderableString { get { return "LPAD(FORMAT({0}, 0), 8, '0')"; } } + public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } + public override string ConvertDecimalToOrderableString { get { return "LPAD(FORMAT({0}, 9), 20, '0')"; } } public override bool? SupportsCaseInsensitiveQueries(Database db) { diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 0bf8a0a0f7..81db82a890 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -209,7 +209,6 @@ ORDER BY TABLE_NAME, INDEX_NAME"); public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index bc1b12a22d..a992cdd231 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -323,7 +323,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax GetQuotedColumnName(foreignKey.ForeignColumns.First()), GetQuotedTableName(foreignKey.PrimaryTable), GetQuotedColumnName(foreignKey.PrimaryColumns.First()), - FormatCascade("DELETE", foreignKey.OnDelete), + FormatCascade("DELETE", foreignKey.OnDelete), FormatCascade("UPDATE", foreignKey.OnUpdate)); } @@ -332,7 +332,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var sb = new StringBuilder(); foreach (var column in columns) { - sb.Append(Format(column) +",\n"); + sb.Append(Format(column) + ",\n"); } return sb.ToString().TrimEnd(",\n"); } @@ -432,11 +432,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax return GetSpecialDbType(column.DbType); } - Type type = column.Type.HasValue + Type type = column.Type.HasValue ? DbTypeMap.ColumnDbTypeMap.First(x => x.Value == column.Type.Value).Key : column.PropertyType; - if (type == typeof (string)) + if (type == typeof(string)) { var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength; return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault); @@ -537,5 +537,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string CreateConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; } } public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } + + public virtual string ConvertIntegerToOrderableString { get { return "REPLACE(STR({0}, 8), SPACE(1), '0')"; } } + public virtual string ConvertDateToOrderableString { get { return "CONVERT(nvarchar, {0}, 102)"; } } + public virtual string ConvertDecimalToOrderableString { get { return "REPLACE(STR({0}, 20, 9), SPACE(1), '0')"; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 50c89aedb7..f6cc1f692b 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Data.Common; +using System.Text; using NPoco; using StackExchange.Profiling; using Umbraco.Core.Logging; @@ -123,15 +124,25 @@ namespace Umbraco.Core.Persistence // if no timeout is specified, and the connection has a longer timeout, use it if (OneTimeCommandTimeout == 0 && CommandTimeout == 0 && cmd.Connection.ConnectionTimeout > 30) cmd.CommandTimeout = cmd.Connection.ConnectionTimeout; + + if (EnableSqlTrace) + { + var sb = new StringBuilder(); + sb.Append(cmd.CommandText); + foreach (DbParameter p in cmd.Parameters) + { + sb.Append(" - "); + sb.Append(p.Value); + } + + _logger.Debug(sb.ToString()); + } + base.OnExecutingCommand(cmd); } protected override void OnExecutedCommand(DbCommand cmd) { - if (EnableSqlTrace) - { - _logger.Debug(cmd.CommandText); - } if (_enableCount) { SqlCount++; diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index dfe59e1783..e48079c10b 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -118,6 +118,8 @@ namespace Umbraco.Core.Security //custom identity factory for creating the identity object for which we auth against in the back office manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); + manager.EmailService = new EmailService(); + //NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to suport it //// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user @@ -132,7 +134,6 @@ namespace Umbraco.Core.Security // BodyFormat = "Your security code is: {0}" //}); - //manager.EmailService = new EmailService(); //manager.SmsService = new SmsService(); } diff --git a/src/Umbraco.Core/Security/EmailService.cs b/src/Umbraco.Core/Security/EmailService.cs new file mode 100644 index 0000000000..93864aa37c --- /dev/null +++ b/src/Umbraco.Core/Security/EmailService.cs @@ -0,0 +1,26 @@ +using System.Net.Mail; +using System.Threading.Tasks; +using Microsoft.AspNet.Identity; + +namespace Umbraco.Core.Security +{ + public class EmailService : IIdentityMessageService + { + public async Task SendAsync(IdentityMessage message) + { + using (var client = new SmtpClient()) + using (var mailMessage = new MailMessage()) + { + mailMessage.Body = message.Body; + mailMessage.To.Add(message.Destination); + mailMessage.Subject = message.Subject; + + //TODO: This check could be nicer but that is the way it is currently + mailMessage.IsBodyHtml = message.Body.IsNullOrWhiteSpace() == false + && message.Body.Contains("<") && message.Body.Contains("/>"); + + await client.SendMailAsync(mailMessage); + } + } + } +} diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 6a9d447938..f213a584be 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -236,7 +236,7 @@ namespace Umbraco.Core.Services public IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0) { var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parentId, contentType); + var content = new Content(name, parentId, contentType); //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. @@ -487,27 +487,6 @@ namespace Umbraco.Core.Services } } - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - //if the id is System Root, then just get all - if (id != Constants.System.Root) - { - repository.Query.Where(x => x.ParentId == id); - } - long total; - var contents = repository.GetPagedResultsByQuery(repository.Query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - /// /// Gets a collection of objects by Parent Id /// @@ -521,6 +500,24 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, string filter = "") + { + return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -533,32 +530,12 @@ namespace Umbraco.Core.Services { query.Where(x => x.ParentId == id); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } } - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - //if the id is System Root, then just get all - if (id != Constants.System.Root) - { - repository.Query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - long total; - var contents = repository.GetPagedResultsByQuery(repository.Query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - /// /// Gets a collection of objects by Parent Id /// @@ -569,8 +546,25 @@ namespace Umbraco.Core.Services /// Field to order by /// Direction to order by /// Search text filter - /// An Enumerable list of objects + /// An Enumerable list of objects public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, string filter) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -583,7 +577,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } @@ -907,7 +901,7 @@ namespace Umbraco.Core.Services { var originalPath = content.Path; - if (Trashing.IsRaisedEventCancelled( + if (Trashing.IsRaisedEventCancelled( new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), this)) { @@ -1023,7 +1017,7 @@ namespace Umbraco.Core.Services /// True if unpublishing succeeded, otherwise False public bool UnPublish(IContent content, int userId = 0) { - return ((IContentServiceOperations) this).UnPublish(content, userId).Success; + return ((IContentServiceOperations)this).UnPublish(content, userId).Success; } /// @@ -1144,7 +1138,7 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { - if (Deleting.IsRaisedEventCancelled( + if (Deleting.IsRaisedEventCancelled( new DeleteEventArgs(content, evtMsgs), this)) { @@ -1169,10 +1163,10 @@ namespace Umbraco.Core.Services { repository.Delete(content); uow.Commit(); - + var args = new DeleteEventArgs(content, false, evtMsgs); Deleted.RaiseEvent(args, this); - + //remove any flagged media files repository.DeleteMediaFiles(args.MediaFilesToDelete); } @@ -1351,7 +1345,7 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the Content public void MoveToRecycleBin(IContent content, int userId = 0) { - ((IContentServiceOperations) this).MoveToRecycleBin(content, userId); + ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); } /// @@ -1655,7 +1649,7 @@ namespace Umbraco.Core.Services //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! _publishingStrategy.PublishingFinalized(shouldBePublished, false); } - + Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); @@ -1932,13 +1926,13 @@ namespace Umbraco.Core.Services content = newest; var evtMsgs = EventMessagesFactory.Get(); - + var published = content.Published ? content : GetPublishedVersion(content.Id); // get the published version if (published == null) { return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.SuccessAlreadyUnPublished, evtMsgs)); // already unpublished } - + var unpublished = _publishingStrategy.UnPublish(content, userId); if (unpublished == false) return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs)); @@ -2076,7 +2070,7 @@ namespace Umbraco.Core.Services if (raiseEvents) { - if (Saving.IsRaisedEventCancelled( + if (Saving.IsRaisedEventCancelled( new SaveEventArgs(content, evtMsgs), this)) { diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index fd46bf1008..625f917ffe 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -215,12 +215,7 @@ namespace Umbraco.Core.Services /// Id of the Parent to retrieve Children from /// An Enumerable list of objects IEnumerable GetChildren(int id); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - + /// /// Gets a collection of objects by Parent Id /// @@ -235,11 +230,21 @@ namespace Umbraco.Core.Services IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); - + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); + /// /// Gets a collection of objects by Parent Id /// @@ -253,7 +258,22 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); - + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); + /// /// Gets a collection of an objects versions by its Id /// @@ -284,7 +304,7 @@ namespace Umbraco.Core.Services /// /// An Enumerable list of objects IEnumerable GetContentInRecycleBin(); - + /// /// Saves a single object /// @@ -483,7 +503,7 @@ namespace Umbraco.Core.Services /// 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/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index d104b95ddc..645bcfd1e0 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -117,12 +117,7 @@ namespace Umbraco.Core.Services /// Id of the Parent to retrieve Children from /// An Enumerable list of objects IEnumerable GetChildren(int id); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - + /// /// Gets a collection of objects by Parent Id /// @@ -137,11 +132,21 @@ namespace Umbraco.Core.Services IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); - + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); + /// /// Gets a collection of objects by Parent Id /// @@ -156,6 +161,21 @@ namespace Umbraco.Core.Services IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); + /// /// Gets descendants of a object by its Id /// @@ -220,7 +240,7 @@ namespace Umbraco.Core.Services /// The to delete /// Id of the User deleting the Media void Delete(IMedia media, int userId = 0); - + /// /// Saves a single object /// diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 20eef54b3c..a893d89feb 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -34,14 +34,30 @@ namespace Umbraco.Core.Services /// Current page index /// Size of the page /// Total number of records found (out) - /// - /// + /// Field to order by + /// Direction to order by /// - /// + /// Search text filter /// IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); - + + /// + /// Gets a list of paged objects + /// + /// An can be of type + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// + /// Search text filter + /// + IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter); + /// /// Creates an object without persisting it /// @@ -91,7 +107,7 @@ namespace Umbraco.Core.Services /// MemberType the Member should be based on /// IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType); - + /// /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method /// @@ -115,7 +131,7 @@ namespace Umbraco.Core.Services /// Id of the Member /// True if the Member exists otherwise False bool Exists(int id); - + /// /// Gets a Member by the unique key /// @@ -160,7 +176,7 @@ namespace Umbraco.Core.Services /// Optional list of Member Ids /// IEnumerable GetAllMembers(params int[] ids); - + /// /// Delete Members of the specified MemberType id /// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 794765d864..2a459174c0 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -395,26 +395,6 @@ namespace Umbraco.Core.Services } } - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = repository.Query; - query.Where(x => x.ParentId == id); - - long total; - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - - totalChildren = Convert.ToInt32(total); - return medias; - } - } - /// /// Gets a collection of objects by Parent Id /// @@ -427,7 +407,25 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, string filter = "") + { + return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -435,34 +433,12 @@ namespace Umbraco.Core.Services { var query = repository.Query; query.Where(x => x.ParentId == id); - - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + + var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return medias; } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - - var query = repository.Query; - //if the id is -1, then just get all - if (id != -1) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } + } /// /// Gets a collection of objects by Parent Id @@ -476,6 +452,23 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, string filter) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -488,7 +481,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } @@ -699,7 +692,7 @@ namespace Umbraco.Core.Services /// Id of the User deleting the Media public void MoveToRecycleBin(IMedia media, int userId = 0) { - ((IMediaServiceOperations) this).MoveToRecycleBin(media, userId); + ((IMediaServiceOperations)this).MoveToRecycleBin(media, userId); } /// @@ -716,7 +709,7 @@ namespace Umbraco.Core.Services //TODO: IT would be much nicer to mass delete all in one trans in the repo level! var evtMsgs = EventMessagesFactory.Get(); - if (Deleting.IsRaisedEventCancelled( + if (Deleting.IsRaisedEventCancelled( new DeleteEventArgs(media, evtMsgs), this)) { return OperationStatus.Cancelled(evtMsgs); @@ -997,7 +990,7 @@ namespace Umbraco.Core.Services ((IMediaServiceOperations)this).Delete(media, userId); } - + /// /// Permanently deletes versions from an object prior to a specific date. @@ -1053,7 +1046,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); } - + /// /// Saves a single object /// @@ -1062,7 +1055,7 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise events. public void Save(IMedia media, int userId = 0, bool raiseEvents = true) { - ((IMediaServiceOperations)this).Save (media, userId, raiseEvents); + ((IMediaServiceOperations)this).Save(media, userId, raiseEvents); } /// diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 753d21db86..b0b31e7279 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Services private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - + public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) : base(provider, repositoryFactory, logger, eventMessagesFactory) { @@ -52,7 +52,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) { - var types = repository.GetAll(new int[]{}).Select(x => x.Alias).ToArray(); + var types = repository.GetAll(new int[] { }).Select(x => x.Alias).ToArray(); if (types.Any() == false) { @@ -289,7 +289,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, true); } } @@ -340,7 +340,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, true); } } @@ -391,7 +391,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); } } @@ -688,7 +688,7 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending); + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); } } @@ -698,23 +698,29 @@ namespace Umbraco.Core.Services string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") { long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, memberTypeAlias, filter); + var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, memberTypeAlias, filter); totalRecords = Convert.ToInt32(total); return result; } public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") + { + return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); + } + + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) { if (memberTypeAlias == null) { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filter); + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); } var query = repository.Query.Where(x => x.ContentTypeAlias == memberTypeAlias); - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filter); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); } } @@ -1253,52 +1259,52 @@ namespace Umbraco.Core.Services var memType = new MemberType(-1); var propGroup = new PropertyGroup - { - Name = "Membership", - Id = --identity - }; + { + Name = "Membership", + Id = --identity + }; propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, Constants.Conventions.Member.Comments) - { - Name = Constants.Conventions.Member.CommentsLabel, - SortOrder = 0, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.CommentsLabel, + SortOrder = 0, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsApproved) - { - Name = Constants.Conventions.Member.IsApprovedLabel, - SortOrder = 3, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.IsApprovedLabel, + SortOrder = 3, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsLockedOut) - { - Name = Constants.Conventions.Member.IsLockedOutLabel, - SortOrder = 4, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.IsLockedOutLabel, + SortOrder = 4, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLockoutDate) - { - Name = Constants.Conventions.Member.LastLockoutDateLabel, - SortOrder = 5, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.LastLockoutDateLabel, + SortOrder = 5, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLoginDate) - { - Name = Constants.Conventions.Member.LastLoginDateLabel, - SortOrder = 6, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.LastLoginDateLabel, + SortOrder = 6, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastPasswordChangeDate) - { - Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, - SortOrder = 7, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, + SortOrder = 7, + Id = --identity, + Key = identity.ToGuid() + }); memType.PropertyGroups.Add(propGroup); diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index a00e7307b8..0017df01e9 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -1,55 +1,43 @@ using System; -using System.Collections.Generic; -using log4net; -using Umbraco.Core.Logging; -using System.IO; -using System.Linq; -using Umbraco.Core.IO; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; -using Umbraco.Core.Events; -using Umbraco.Core.Strings; namespace Umbraco.Core.Services { /// - /// The Umbraco ServiceContext, which provides access to the following services: - /// , , , - /// , and . + /// Represents the Umbraco Service context, which provides access to all services. /// public class ServiceContext { - private Lazy _migrationEntryService; - private Lazy _publicAccessService; - private Lazy _taskService; - private Lazy _domainService; - private Lazy _auditService; - private Lazy _localizedTextService; - private Lazy _tagService; - 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 _relationService; - private Lazy _treeService; - private Lazy _sectionService; - private Lazy _macroService; - private Lazy _memberTypeService; - private Lazy _memberGroupService; - private Lazy _notificationService; - private Lazy _externalLoginService; + private readonly Lazy _migrationEntryService; + private readonly Lazy _publicAccessService; + private readonly Lazy _taskService; + private readonly Lazy _domainService; + private readonly Lazy _auditService; + private readonly Lazy _localizedTextService; + private readonly Lazy _tagService; + private readonly Lazy _contentService; + private readonly Lazy _userService; + private readonly Lazy _memberService; + private readonly Lazy _mediaService; + private readonly Lazy _contentTypeService; + private readonly Lazy _dataTypeService; + private readonly Lazy _fileService; + private readonly Lazy _localizationService; + private readonly Lazy _packagingService; + private readonly Lazy _serverRegistrationService; + private readonly Lazy _entityService; + private readonly Lazy _relationService; + private readonly Lazy _treeService; + private readonly Lazy _sectionService; + private readonly Lazy _macroService; + private readonly Lazy _memberTypeService; + private readonly Lazy _memberGroupService; + private readonly Lazy _notificationService; + private readonly Lazy _externalLoginService; /// - /// Constructor used for IoC + /// Initializes a new instance of the class with lazy services. /// + /// Used by IoC. Note that LightInject will favor lazy args when picking a constructor. public ServiceContext(Lazy migrationEntryService, Lazy publicAccessService, Lazy taskService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy treeService, Lazy sectionService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService) { _migrationEntryService = migrationEntryService; @@ -81,34 +69,9 @@ namespace Umbraco.Core.Services } /// - /// public ctor - will generally just be used for unit testing all items are optional and if not specified, the defaults will be used + /// Initializes a new instance of the class with services. /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// + /// Used in tests. All items are optional and remain null if not specified. public ServiceContext( IContentService contentService = null, IMediaService mediaService = null, @@ -165,378 +128,131 @@ namespace Umbraco.Core.Services if (publicAccessService != null) _publicAccessService = new Lazy(() => publicAccessService); } - /// - /// Creates a service context with a RepositoryFactory which is used to construct Services - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ServiceContext( - RepositoryFactory repositoryFactory, - IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, - IUnitOfWorkProvider fileUnitOfWorkProvider, - IPublishingStrategy publishingStrategy, - CacheHelper cache, - ILogger logger, - IEventMessagesFactory eventMessagesFactory, - IEnumerable urlSegmentProviders) - { - if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory"); - if (dbUnitOfWorkProvider == null) throw new ArgumentNullException("dbUnitOfWorkProvider"); - if (fileUnitOfWorkProvider == null) throw new ArgumentNullException("fileUnitOfWorkProvider"); - if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); - if (cache == null) throw new ArgumentNullException("cache"); - if (logger == null) throw new ArgumentNullException("logger"); - if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); - - BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, cache, - repositoryFactory, - logger, eventMessagesFactory, - urlSegmentProviders); - } - - /// - /// Builds the various services - /// - private void BuildServiceCache( - IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, - IUnitOfWorkProvider fileUnitOfWorkProvider, - IPublishingStrategy publishingStrategy, - CacheHelper cache, - RepositoryFactory repositoryFactory, - ILogger logger, - IEventMessagesFactory eventMessagesFactory, - IEnumerable urlSegmentProviders) - { - var provider = dbUnitOfWorkProvider; - var fileProvider = fileUnitOfWorkProvider; - - if (_migrationEntryService == null) - _migrationEntryService = new Lazy(() => new MigrationEntryService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_externalLoginService == null) - _externalLoginService = new Lazy(() => new ExternalLoginService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_publicAccessService == null) - _publicAccessService = new Lazy(() => new PublicAccessService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_taskService == null) - _taskService = new Lazy(() => new TaskService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_domainService == null) - _domainService = new Lazy(() => new DomainService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_auditService == null) - _auditService = new Lazy(() => new AuditService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_localizedTextService == null) - { - - _localizedTextService = new Lazy(() => new LocalizedTextService( - new Lazy(() => - { - var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); - var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); - var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/")); - - var pluginLangFolders = appPlugins.Exists == false - ? Enumerable.Empty() - : appPlugins.GetDirectories() - .SelectMany(x => x.GetDirectories("Lang")) - .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); - - //user defined langs that overwrite the default, these should not be used by plugin creators - var userLangFolders = configLangFolder.Exists == false - ? Enumerable.Empty() - : configLangFolder - .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); - - return new LocalizedTextServiceFileSources( - logger, - cache.RuntimeCache, - mainLangFolder, - pluginLangFolders.Concat(userLangFolders)); - - }), - logger)); - } - - - if (_notificationService == null) - _notificationService = new Lazy(() => new NotificationService(provider, _userService.Value, _contentService.Value, repositoryFactory, logger)); - - if (_serverRegistrationService == null) - _serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_userService == null) - _userService = new Lazy(() => new UserService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_memberService == null) - _memberService = new Lazy(() => new MemberService(provider, repositoryFactory, logger, eventMessagesFactory, _memberGroupService.Value, _dataTypeService.Value)); - - if (_contentService == null) - _contentService = new Lazy(() => new ContentService(provider, repositoryFactory, logger, eventMessagesFactory, publishingStrategy, _dataTypeService.Value, _userService.Value, urlSegmentProviders)); - - if (_mediaService == null) - _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory, logger, eventMessagesFactory, _dataTypeService.Value, _userService.Value, urlSegmentProviders)); - - if (_contentTypeService == null) - _contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory, logger, eventMessagesFactory, _contentService.Value, _mediaService.Value)); - - if (_dataTypeService == null) - _dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_fileService == null) - _fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_localizationService == null) - _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_entityService == null) - _entityService = new Lazy(() => new EntityService( - provider, repositoryFactory, logger, eventMessagesFactory, - _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _memberService.Value, _memberTypeService.Value, - //TODO: Consider making this an isolated cache instead of using the global one - cache.RuntimeCache)); - - if (_packagingService == null) - _packagingService = new Lazy(() => new PackagingService(logger, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _macroService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, _entityService.Value, _userService.Value, repositoryFactory, provider, urlSegmentProviders)); - - if (_relationService == null) - _relationService = new Lazy(() => new RelationService(provider, repositoryFactory, logger, eventMessagesFactory, _entityService.Value)); - - if (_treeService == null) - _treeService = new Lazy(() => new EmptyApplicationTreeService()); - - if (_sectionService == null) - _sectionService = new Lazy(() => new EmptySectionService()); - - if (_macroService == null) - _macroService = new Lazy(() => new MacroService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_memberTypeService == null) - _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory, logger, eventMessagesFactory, _memberService.Value)); - - if (_tagService == null) - _tagService = new Lazy(() => new TagService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_memberGroupService == null) - _memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory, logger, eventMessagesFactory)); - - } - /// /// Gets the /// - public IMigrationEntryService MigrationEntryService - { - get { return _migrationEntryService.Value; } - } + public IMigrationEntryService MigrationEntryService => _migrationEntryService.Value; /// /// Gets the /// - public IPublicAccessService PublicAccessService - { - get { return _publicAccessService.Value; } - } + public IPublicAccessService PublicAccessService => _publicAccessService.Value; /// /// Gets the /// - public ITaskService TaskService - { - get { return _taskService.Value; } - } + public ITaskService TaskService => _taskService.Value; /// /// Gets the /// - public IDomainService DomainService - { - get { return _domainService.Value; } - } + public IDomainService DomainService => _domainService.Value; /// /// Gets the /// - public IAuditService AuditService - { - get { return _auditService.Value; } - } + public IAuditService AuditService => _auditService.Value; /// /// Gets the /// - public ILocalizedTextService TextService - { - get { return _localizedTextService.Value; } - } + public ILocalizedTextService TextService => _localizedTextService.Value; /// /// Gets the /// - public INotificationService NotificationService - { - get { return _notificationService.Value; } - } + public INotificationService NotificationService => _notificationService.Value; /// /// Gets the /// - public IServerRegistrationService ServerRegistrationService - { - get { return _serverRegistrationService.Value; } - } + public IServerRegistrationService ServerRegistrationService => _serverRegistrationService.Value; /// /// Gets the /// - public ITagService TagService - { - get { return _tagService.Value; } - } + public ITagService TagService => _tagService.Value; /// /// Gets the /// - public IMacroService MacroService - { - get { return _macroService.Value; } - } + public IMacroService MacroService => _macroService.Value; /// /// Gets the /// - public IEntityService EntityService - { - get { return _entityService.Value; } - } + public IEntityService EntityService => _entityService.Value; /// /// Gets the /// - public IRelationService RelationService - { - get { return _relationService.Value; } - } + public IRelationService RelationService => _relationService.Value; /// /// Gets the /// - public IContentService ContentService - { - get { return _contentService.Value; } - } + public IContentService ContentService => _contentService.Value; /// /// Gets the /// - public IContentTypeService ContentTypeService - { - get { return _contentTypeService.Value; } - } + public IContentTypeService ContentTypeService => _contentTypeService.Value; /// /// Gets the /// - public IDataTypeService DataTypeService - { - get { return _dataTypeService.Value; } - } + public IDataTypeService DataTypeService => _dataTypeService.Value; /// /// Gets the /// - public IFileService FileService - { - get { return _fileService.Value; } - } + public IFileService FileService => _fileService.Value; /// /// Gets the /// - public ILocalizationService LocalizationService - { - get { return _localizationService.Value; } - } + public ILocalizationService LocalizationService => _localizationService.Value; /// /// Gets the /// - public IMediaService MediaService - { - get { return _mediaService.Value; } - } + public IMediaService MediaService => _mediaService.Value; /// /// Gets the /// - public IPackagingService PackagingService - { - get { return _packagingService.Value; } - } + public IPackagingService PackagingService => _packagingService.Value; /// /// Gets the /// - public IUserService UserService - { - get { return _userService.Value; } - } + public IUserService UserService => _userService.Value; /// /// Gets the /// - public IMemberService MemberService - { - get { return _memberService.Value; } - } + public IMemberService MemberService => _memberService.Value; /// /// Gets the /// - public ISectionService SectionService - { - get { return _sectionService.Value; } - } + public ISectionService SectionService => _sectionService.Value; /// /// Gets the /// - public IApplicationTreeService ApplicationTreeService - { - get { return _treeService.Value; } - } + public IApplicationTreeService ApplicationTreeService => _treeService.Value; /// /// Gets the MemberTypeService /// - public IMemberTypeService MemberTypeService - { - get { return _memberTypeService.Value; } - } + public IMemberTypeService MemberTypeService => _memberTypeService.Value; /// /// Gets the MemberGroupService /// - public IMemberGroupService MemberGroupService - { - get { return _memberGroupService.Value; } - } + public IMemberGroupService MemberGroupService => _memberGroupService.Value; - public IExternalLoginService ExternalLoginService - { - get { return _externalLoginService.Value; } - } + public IExternalLoginService ExternalLoginService => _externalLoginService.Value; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs b/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs deleted file mode 100644 index 901c9ee299..0000000000 --- a/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs +++ /dev/null @@ -1,531 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using Umbraco.Core.Configuration; - -namespace Umbraco.Core.Strings -{ - /// - /// Legacy implementation of string functions for short strings such as aliases or url segments. - /// - /// - /// Not necessarily optimized to work on large bodies of text. - /// Can expose surprising or bogus behavior. - /// Uses invariant culture everywhere. - /// - internal class LegacyShortStringHelper : IShortStringHelper - { - #region Ctor and vars - - /// - /// Freezes the helper so it can prevents its configuration from being modified. - /// - /// Will be called by ShortStringHelperResolver when resolution freezes. - public void Freeze() - { - // we have nothing to protect. - } - - const string UmbracoValidAliasCharacters = "_-abcdefghijklmnopqrstuvwxyz1234567890"; - const string UmbracoInvalidFirstCharacters = "0123456789"; - - #endregion - - #region Short string services JavaScript - - const string SssjsValidCharacters = "_-abcdefghijklmnopqrstuvwxyz1234567890"; - const string SssjsInvalidFirstCharacters = "0123456789"; - - private const string SssjsFormat = @" -var UMBRACO_FORCE_SAFE_ALIAS = {0}; -var UMBRACO_FORCE_SAFE_ALIAS_VALIDCHARS = '{1}'; -var UMBRACO_FORCE_SAFE_ALIAS_INVALID_FIRST_CHARS = '{2}'; - -function safeAlias(alias) {{ - if (UMBRACO_FORCE_SAFE_ALIAS) {{ - var safeAlias = ''; - var aliasLength = alias.length; - for (var i = 0; i < aliasLength; i++) {{ - currentChar = alias.substring(i, i + 1); - if (UMBRACO_FORCE_SAFE_ALIAS_VALIDCHARS.indexOf(currentChar.toLowerCase()) > -1) {{ - // check for camel (if previous character is a space, we'll upper case the current one - if (safeAlias == '' && UMBRACO_FORCE_SAFE_ALIAS_INVALID_FIRST_CHARS.indexOf(currentChar.toLowerCase()) > 0) {{ - currentChar = ''; - }} else {{ - // first char should always be lowercase (camel style) - if (safeAlias.length == 0) - currentChar = currentChar.toLowerCase(); - - if (i < aliasLength - 1 && safeAlias != '' && alias.substring(i - 1, i) == ' ') - currentChar = currentChar.toUpperCase(); - - safeAlias += currentChar; - }} - }} - }} - - alias = safeAlias; - }} - return alias; -}} - -function getSafeAlias(input, value, immediate, callback) {{ - callback(safeAlias(value)); -}} - -function validateSafeAlias(input, value, immediate, callback) {{ - callback(value == safeAlias(value)); -}} - -// legacy backward compatibility requires that one -function isValidAlias(alias) {{ - return alias == safeAlias(alias); -}} -"; - - /// - /// Gets the JavaScript code defining client-side short string services. - /// - public string GetShortStringServicesJavaScript(string controllerPath) - { - return string.Format(SssjsFormat, - UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? "true" : "false", SssjsValidCharacters, SssjsInvalidFirstCharacters); - } - - #endregion - - #region IShortStringHelper CleanFor... - - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The safe alias. - /// The string will be cleaned in the context of invariant culture. - public string CleanStringForSafeAlias(string text) - { - // ported from StringExtensions.ToSafeAlias() - - const string validAliasCharacters = UmbracoValidAliasCharacters; - const string invalidFirstCharacters = UmbracoInvalidFirstCharacters; - var safeString = new StringBuilder(); - int aliasLength = text.Length; - for (var i = 0; i < aliasLength; i++) - { - var currentChar = text.Substring(i, 1); - if (validAliasCharacters.Contains(currentChar.ToLowerInvariant())) - { - // check for camel (if previous character is a space, we'll upper case the current one - if (safeString.Length == 0 && invalidFirstCharacters.Contains(currentChar.ToLowerInvariant())) - { - //currentChar = ""; - } - else - { - if (i < aliasLength - 1 && i > 0 && text.Substring(i - 1, 1) == " ") - currentChar = currentChar.ToUpperInvariant(); - - safeString.Append(currentChar); - } - } - } - - return safeString.ToString(); - } - - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The culture. - /// The safe alias. - /// Legacy does not support culture contexts. - public string CleanStringForSafeAlias(string text, CultureInfo culture) - { - return CleanStringForSafeAlias(text); - } - - /// - /// Cleans a string to produce a string that can safely be used in an url segment, in the context of the invariant culture. - /// - /// The text to filter. - /// The safe url segment. - public string CleanStringForUrlSegment(string text) - { - return LegacyFormatUrl(text); - } - - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used in an url segment. - /// - /// The text to filter. - /// The culture. - /// The safe url segment. - /// Legacy does not support culture contexts. - public string CleanStringForUrlSegment(string text, CultureInfo culture) - { - return CleanStringForUrlSegment(text); - } - - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). - /// - /// The text to filter. - /// The safe filename. - /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented. - public string CleanStringForSafeFileName(string text) - { - var filePath = text; - - // ported from Core.IO.IOHelper.SafeFileName() - - if (String.IsNullOrEmpty(filePath)) - return String.Empty; - - if (!String.IsNullOrWhiteSpace(filePath)) - { - foreach (var character in Path.GetInvalidFileNameChars()) - { - filePath = filePath.Replace(character, '-'); - } - } - else - { - filePath = String.Empty; - } - - //Break up the file in name and extension before applying the UrlReplaceCharacters - var fileNamePart = filePath.Substring(0, filePath.LastIndexOf('.')); - var ext = filePath.Substring(filePath.LastIndexOf('.')); - - //Because the file usually is downloadable as well we check characters against 'UmbracoSettings.UrlReplaceCharacters' - foreach (var n in UmbracoConfig.For.UmbracoSettings().RequestHandler.CharCollection) - { - if (n.Char.IsNullOrWhiteSpace() == false) - fileNamePart = fileNamePart.Replace(n.Char, n.Replacement); - } - - filePath = string.Concat(fileNamePart, ext); - - // Adapted from: http://stackoverflow.com/a/4827510/5018 - // Combined both Reserved Characters and Character Data - // from http://en.wikipedia.org/wiki/Percent-encoding - var stringBuilder = new StringBuilder(); - - const string reservedCharacters = "!*'();:@&=+$,/?%#[]-~{}\"<>\\^`| "; - - foreach (var character in filePath) - { - if (reservedCharacters.IndexOf(character) == -1) - stringBuilder.Append(character); - else - stringBuilder.Append("-"); - } - - // Remove repeating dashes - // From: http://stackoverflow.com/questions/5111967/regex-to-remove-a-specific-repeated-character - var reducedString = Regex.Replace(stringBuilder.ToString(), "-+", "-"); - - return reducedString; - } - - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). - /// - /// The text to filter. - /// The culture. - /// The safe filename. - /// Legacy does not support culture contexts. - public string CleanStringForSafeFileName(string text, CultureInfo culture) - { - return CleanStringForSafeFileName(text); - } - - #endregion - - #region CleanString - - // legacy does not implement these - - public string CleanString(string text, CleanStringType stringType) - { - return text; - } - - public string CleanString(string text, CleanStringType stringType, char separator) - { - return text; - } - - public string CleanString(string text, CleanStringType stringType, CultureInfo culture) - { - return text; - } - - public string CleanString(string text, CleanStringType stringType, char separator, CultureInfo culture) - { - return text; - } - - #endregion - - #region SplitPascalCasing - - /// - /// Splits a pascal-cased string by inserting a separator in between each term. - /// - /// The text to split. - /// The separator. - /// The splitted string. - /// Probably only supports Ascii strings. - public string SplitPascalCasing(string text, char separator) - { - // ported from StringExtensions.SplitPascalCasing() - - var replacement = "$1" + separator; - var result = Regex.Replace(text, "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", replacement); - return result; - } - - #endregion - - #region Legacy - - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The safe alias. - /// The string will be cleaned in the context of invariant culture. - public string LegacyCleanStringForUmbracoAlias(string text) - { - // ported from StringExtensions.ToUmbracoAlias() - // kept here for reference, not used anymore - - if (string.IsNullOrEmpty(text)) return string.Empty; - - //convert case first - //var tmp = text.ConvertCase(caseType); - // note: always Camel anyway - var tmp = LegacyConvertStringCase(text, CleanStringType.CamelCase); - - //remove non-alphanumeric chars - var result = Regex.Replace(tmp, @"[^a-zA-Z0-9\s\.-]+", "", RegexOptions.Compiled); - - // note: spaces are always removed anyway - //if (removeSpaces) - // result = result.Replace(" ", ""); - - return result; - } - - /// - /// Filters a string to convert case, and more. - /// - /// the text to filter. - /// The string case type. - /// The filtered text. - /// - /// This is the legacy method, so we can't really change it, although it has issues (see unit tests). - /// It does more than "converting the case", and also remove spaces, etc. - /// - public string LegacyConvertStringCase(string phrase, CleanStringType cases) - { - // ported from StringExtensions.ConvertCase - - cases &= CleanStringType.CaseMask; - - var splittedPhrase = Regex.Split(phrase, @"[^a-zA-Z0-9\']", RegexOptions.Compiled); - - if (cases == CleanStringType.Unchanged) - return string.Join("", splittedPhrase); - - //var splittedPhrase = phrase.Split(' ', '-', '.'); - var sb = new StringBuilder(); - - foreach (var splittedPhraseChars in splittedPhrase.Select(s => s.ToCharArray())) - { - if (splittedPhraseChars.Length > 0) - { - splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpperInvariant().ToCharArray())[0]; - } - sb.Append(new String(splittedPhraseChars)); - } - - var result = sb.ToString(); - - if (cases == CleanStringType.CamelCase) - { - if (result.Length > 1) - { - var pattern = new Regex("^([A-Z]*)([A-Z].*)$", RegexOptions.Singleline | RegexOptions.Compiled); - var match = pattern.Match(result); - if (match.Success) - { - result = match.Groups[1].Value.ToLowerInvariant() + match.Groups[2].Value; - - return result.Substring(0, 1).ToLowerInvariant() + result.Substring(1); - } - - return result; - } - - return result.ToLowerInvariant(); - } - - return result; - } - - /// - /// Converts string to a URL alias. - /// - /// The value. - /// The char replacements. - /// if set to true replace double dashes. - /// if set to true strip non ASCII. - /// if set to true URL encode. - /// - /// - /// This ensures that ONLY ascii chars are allowed and of those ascii chars, only digits and lowercase chars, all - /// punctuation, etc... are stripped out, however this method allows you to pass in string's to replace with the - /// specified replacement character before the string is converted to ascii and it has invalid characters stripped out. - /// This allows you to replace strings like & , etc.. with your replacement character before the automatic - /// reduction. - /// - public string LegacyToUrlAlias(string value, IDictionary charReplacements, bool replaceDoubleDashes, bool stripNonAscii, bool urlEncode) - { - // to lower case invariant - // replace chars one by one using charReplacements - // (opt) convert to ASCII then remove anything that's not ASCII - // trim - and _ then (opt) remove double - - // (opt) url-encode - - // charReplacement is actually *string* replacement ie it can replace " " by a non-breaking space - // so it's kind of a pre-filter actually... - // we need pre-filters, and post-filters, within each token... - // not so... we may want to replace   with a space BEFORE cutting into tokens... - - //first to lower case - value = value.ToLowerInvariant(); - - //then replacement chars - value = charReplacements.Aggregate(value, (current, kvp) => current.Replace(kvp.Key, kvp.Value)); - - //then convert to only ascii, this will remove the rest of any invalid chars - if (stripNonAscii) - { - value = Encoding.ASCII.GetString( - Encoding.Convert( - Encoding.UTF8, - Encoding.GetEncoding( - Encoding.ASCII.EncodingName, - new EncoderReplacementFallback(String.Empty), - new DecoderExceptionFallback()), - Encoding.UTF8.GetBytes(value))); - - //remove all characters that do not fall into the following categories (apart from the replacement val) - var validCodeRanges = - //digits - Enumerable.Range(48, 10).Concat( - //lowercase chars - Enumerable.Range(97, 26)); - - var sb = new StringBuilder(); - foreach (var c in value.Where(c => charReplacements.Values.Contains(c.ToString(CultureInfo.InvariantCulture)) || validCodeRanges.Contains(c))) - { - sb.Append(c); - } - - value = sb.ToString(); - } - - //trim dashes from end - value = value.Trim('-', '_'); - - //replace double occurances of - or _ - value = replaceDoubleDashes ? Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled) : value; - - //url encode result - return urlEncode ? System.Web.HttpUtility.UrlEncode(value) : value; - } - - /// - /// Cleans a string to produce a string that can safely be used in an url segment. - /// - /// The text to filter. - /// The safe url segment. - /// - /// Uses UmbracoSettings.UrlReplaceCharacters - /// and UmbracoSettings.RemoveDoubleDashesFromUrlReplacing. - /// - public string LegacyFormatUrl(string url) - { - var newUrl = url.ToLowerInvariant(); - foreach (var n in UmbracoConfig.For.UmbracoSettings().RequestHandler.CharCollection) - { - if (n.Char != "") - newUrl = newUrl.Replace(n.Char, n.Replacement); - } - - // check for double dashes - if (UmbracoConfig.For.UmbracoSettings().RequestHandler.RemoveDoubleDashes) - { - newUrl = Regex.Replace(newUrl, @"[-]{2,}", "-"); - } - - return newUrl; - } - - #endregion - - #region ReplaceMany - - /// - /// Returns a new string in which all occurences of specified strings are replaced by other specified strings. - /// - /// The string to filter. - /// The replacements definition. - /// The filtered string. - public string ReplaceMany(string text, IDictionary replacements) - { - // Have done various tests, implementing my own "super fast" state machine to handle - // replacement of many items, or via regexes, but on short strings and not too - // many replacements (which prob. is going to be our case) nothing can beat this... - // (at least with safe and checked code -- we don't want unsafe/unchecked here) - - // Note that it will do chained-replacements ie replaced items can be replaced - // in turn by another replacement (ie the order of replacements is important) - - return replacements.Aggregate(text, (current, kvp) => current.Replace(kvp.Key, kvp.Value)); - } - - /// - /// Returns a new string in which all occurences of specified characters are replaced by a specified character. - /// - /// The string to filter. - /// The characters to replace. - /// The replacement character. - /// The filtered string. - public string ReplaceMany(string text, char[] chars, char replacement) - { - // be safe - if (text == null) - throw new ArgumentNullException("text"); - if (chars == null) - throw new ArgumentNullException("chars"); - - // see note above - - return chars.Aggregate(text, (current, c) => current.Replace(c, replacement)); - } - - #endregion - } -} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0ed691e96e..4434f85717 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -497,6 +497,7 @@ + @@ -1304,7 +1305,6 @@ - diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs index 8fbf4a1523..58a9a438a2 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs @@ -11,16 +11,25 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.Security.KeepUserLoggedIn == true); } + [Test] public void HideDisabledUsersInBackoffice() { Assert.IsTrue(SettingsSection.Security.HideDisabledUsersInBackoffice == false); } + + [Test] + public void AllowPasswordReset() + { + Assert.IsTrue(SettingsSection.Security.AllowPasswordReset == true); + } + [Test] public void AuthCookieDomain() { Assert.IsTrue(SettingsSection.Security.AuthCookieDomain == null); } + [Test] public void AuthCookieName() { diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config index 9bc8e99b66..7cc941d671 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config @@ -102,7 +102,10 @@ false - + + + true + diff --git a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs index a59a500ac6..ab87151da7 100644 --- a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs +++ b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs @@ -75,11 +75,17 @@ namespace Umbraco.Tests.Persistence.Migrations public int CountExecuted { get; set; } } - public class TestMigrationHandler : MigrationStartupHander + public class TestMigrationHandler : MigrationStartupHandler { private readonly string _prodName; private readonly Args _changed; + // need that one else it breaks IoC + public TestMigrationHandler() + { + _changed = new Args(); + } + public TestMigrationHandler(Args changed) { _changed = changed; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 30d99ec9c5..ba5b60c02d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Xml.Linq; using Moq; using NUnit.Framework; @@ -71,7 +72,7 @@ namespace Umbraco.Tests.Persistence.Repositories //create 100 non published for (var i = 0; i < 100; i++) - { + { var c1 = MockedContent.CreateSimpleContent(contentType1); repository.AddOrUpdate(c1); allCreated.Add(c1); @@ -175,7 +176,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 30; i++) { //These will be non-published so shouldn't show up - var c1 = MockedContent.CreateSimpleContent(contentType1); + var c1 = MockedContent.CreateSimpleContent(contentType1); repository.AddOrUpdate(c1); allCreated.Add(c1); } @@ -274,7 +275,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(textpage.HasIdentity, Is.True); - + } } @@ -298,7 +299,7 @@ namespace Umbraco.Tests.Persistence.Repositories Content textpage = MockedContent.CreateSimpleContent(contentType); // Act - + contentTypeRepository.AddOrUpdate(contentType); repository.AddOrUpdate(textpage); unitOfWork.Commit(); @@ -519,7 +520,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { + { var result = repository.GetAll().ToArray(); foreach (var content in result) { @@ -556,6 +557,41 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("[alias2]", matches[1].Groups[2].Value); } + [Test] + public void Can_Perform_GetPagedResultsByQuery_Sorting_On_Custom_Property() + { + // Arrange + var provider = new NPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = repository.Query.Where(x => x.Name.Contains("Text")); + long totalRecords; + + try + { + DatabaseContext.Database.EnableSqlTrace = true; + DatabaseContext.Database.EnableSqlCount(); + + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "title", Direction.Ascending, false); + + Assert.AreEqual(3, totalRecords); + Assert.AreEqual(2, result.Count()); + + result = repository.GetPagedResultsByQuery(query, 1, 2, out totalRecords, "title", Direction.Ascending, false); + + Assert.AreEqual(1, result.Count()); + } + finally + { + DatabaseContext.Database.EnableSqlTrace = false; + DatabaseContext.Database.DisableSqlCount(); + } + } + } + [Test] public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() { @@ -568,12 +604,23 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = repository.Query.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending); - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + try + { + DatabaseContext.Database.EnableSqlTrace = true; + DatabaseContext.Database.EnableSqlCount(); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + finally + { + DatabaseContext.Database.EnableSqlTrace = false; + DatabaseContext.Database.DisableSqlCount(); + } } } @@ -589,7 +636,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = repository.Query.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -610,7 +657,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = repository.Query.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -631,7 +678,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = repository.Query.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -652,7 +699,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = repository.Query.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, "Page 2"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page 2"); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -673,7 +720,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = repository.Query.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, "Page"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page"); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 0635b29ef3..67fe82089c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories [SetUp] public override void Initialize() - { + { base.Initialize(); CreateTestData(); @@ -51,12 +51,12 @@ namespace Umbraco.Tests.Persistence.Repositories { var mediaType = mediaTypeRepository.Get(1032); - + for (var i = 0; i < 100; i++) { var image = MockedMedia.CreateMediaImage(mediaType, -1); repository.AddOrUpdate(image); - } + } unitOfWork.Commit(); //delete all xml @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Persistence.Repositories var imageMediaType = mediaTypeRepository.Get(1032); var fileMediaType = mediaTypeRepository.Get(1033); var folderMediaType = mediaTypeRepository.Get(1031); - + for (var i = 0; i < 30; i++) { var image = MockedMedia.CreateMediaImage(imageMediaType, -1); @@ -103,12 +103,12 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] {1032, 1033}); + repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { 1032, 1033 }); Assert.AreEqual(62, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); } } - + [Test] public void Can_Perform_Add_On_MediaRepository() { @@ -207,7 +207,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var media = repository.Get(NodeDto.NodeIdSeed + 1); - bool dirty = ((ICanBeDirty) media).IsDirty(); + bool dirty = ((ICanBeDirty)media).IsDirty(); // Assert Assert.That(dirty, Is.False); @@ -319,7 +319,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -340,7 +340,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -361,7 +361,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -382,7 +382,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -403,7 +403,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -424,7 +424,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, "File"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "File"); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -445,7 +445,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, "Test"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "Test"); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index 975259fe32..b8f5e305a6 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -263,7 +263,7 @@ namespace Umbraco.Tests.Plugins public void Resolves_Assigned_Mappers() { var foundTypes1 = _manager.ResolveAssignedMapperTypes(); - Assert.AreEqual(28, foundTypes1.Count()); + Assert.AreEqual(29, foundTypes1.Count()); // 29 classes in the solution implement BaseMapper } [Test] @@ -277,14 +277,14 @@ namespace Umbraco.Tests.Plugins public void Resolves_Actions() { var actions = _manager.ResolveActions(); - Assert.AreEqual(37, actions.Count()); + Assert.AreEqual(36, actions.Count()); // 36 classes in the solution implement IAction } [Test] public void Resolves_Trees() { var trees = _manager.ResolveTrees(); - Assert.AreEqual(39, trees.Count()); + Assert.AreEqual(8, trees.Count()); // 8 classes in the solution implement BaseTree } [Test] diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index be2a462cfe..d2161bdfe5 100644 --- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs @@ -19,6 +19,7 @@ using Umbraco.Core; using Umbraco.Core.IO; using umbraco.DataLayer; using umbraco.uicontrols; +using Umbraco.Web; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; @@ -73,17 +74,29 @@ namespace Umbraco.Tests.Plugins var typesFound = TypeFinder.FindClassesOfType(_assemblies); var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); - Assert.AreEqual(originalTypesFound.Count(), typesFound.Count()); - Assert.AreEqual(9, typesFound.Count()); - Assert.AreEqual(9, originalTypesFound.Count()); + foreach (var type in typesFound) + Console.WriteLine(type); + Console.WriteLine(); + foreach (var type in originalTypesFound) + Console.WriteLine(type); + + // 6 classes in _assemblies implement IApplicationEventHandler + Assert.AreEqual(6, typesFound.Count()); + + // however, + // Umbraco.Core.Profiling.WebProfiler is internal and is not returned by TypeFinderOriginal, + // that's a known issue of the legacy type finder, so we have to tweak the count here. + Assert.AreEqual(5, originalTypesFound.Count()); } [Test] public void Find_Classes_With_Attribute() { var typesFound = TypeFinder.FindClassesWithAttribute(_assemblies); - //TODO: Fix this with the correct count - Assert.AreEqual(1, typesFound.Count()); + Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] + + typesFound = TypeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); + Assert.AreEqual(23, typesFound.Count()); // 23 classes in Umbraco.Web are marked with [Tree] } [Ignore] diff --git a/src/Umbraco.Tests/PropertyEditors/ColorListValidatorTest.cs b/src/Umbraco.Tests/PropertyEditors/ColorListValidatorTest.cs index 900b8399e7..a6ef28a40e 100644 --- a/src/Umbraco.Tests/PropertyEditors/ColorListValidatorTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ColorListValidatorTest.cs @@ -3,6 +3,7 @@ using Moq; using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; +using Umbraco.Core.Services; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.PropertyEditors @@ -14,7 +15,7 @@ namespace Umbraco.Tests.PropertyEditors public void Only_Tests_On_JArray() { var validator = new ColorListPreValueEditor.ColorListValidator(); - var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -22,7 +23,7 @@ namespace Umbraco.Tests.PropertyEditors public void Only_Tests_On_JArray_Of_Item_JObject() { var validator = new ColorListPreValueEditor.ColorListValidator(); - var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -35,7 +36,7 @@ namespace Umbraco.Tests.PropertyEditors JObject.FromObject(new { value = "zxcvzxcvxzcv" }), JObject.FromObject(new { value = "ABC" }), JObject.FromObject(new { value = "1234567" })), - null, new ColorPickerPropertyEditor(Mock.Of())); + null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(2, result.Count()); } } diff --git a/src/Umbraco.Tests/PropertyEditors/EnsureUniqueValuesValidatorTest.cs b/src/Umbraco.Tests/PropertyEditors/EnsureUniqueValuesValidatorTest.cs index c24aa65846..bd50d92a94 100644 --- a/src/Umbraco.Tests/PropertyEditors/EnsureUniqueValuesValidatorTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/EnsureUniqueValuesValidatorTest.cs @@ -3,6 +3,7 @@ using Moq; using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; +using Umbraco.Core.Services; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.PropertyEditors @@ -14,7 +15,7 @@ namespace Umbraco.Tests.PropertyEditors public void Only_Tests_On_JArray() { var validator = new ValueListPreValueEditor.EnsureUniqueValuesValidator(); - var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -22,7 +23,7 @@ namespace Umbraco.Tests.PropertyEditors public void Only_Tests_On_JArray_Of_Item_JObject() { var validator = new ValueListPreValueEditor.EnsureUniqueValuesValidator(); - var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -30,7 +31,7 @@ namespace Umbraco.Tests.PropertyEditors public void Allows_Unique_Values() { var validator = new ValueListPreValueEditor.EnsureUniqueValuesValidator(); - var result = validator.Validate(new JArray(JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "world" })), null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate(new JArray(JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "world" })), null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -39,7 +40,7 @@ namespace Umbraco.Tests.PropertyEditors { var validator = new ValueListPreValueEditor.EnsureUniqueValuesValidator(); var result = validator.Validate(new JArray(JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "hello" })), - null, new ColorPickerPropertyEditor(Mock.Of())); + null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(1, result.Count()); } @@ -52,7 +53,7 @@ namespace Umbraco.Tests.PropertyEditors JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "world" }), JObject.FromObject(new { value = "world" })), - null, new ColorPickerPropertyEditor(Mock.Of())); + null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(2, result.Count()); } } diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index 42b9d174df..6fe7259b92 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -1,10 +1,21 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Profiling; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Publishing; using Umbraco.Core.Services; +using Umbraco.Core.Strings; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.PropertyEditors @@ -88,6 +99,22 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void DropDownPreValueEditor_Format_Data_For_Editor() { + // editor wants ApplicationContext.Current.Services.TextService + // (that should be fixed with proper injection) + var logger = Mock.Of(); + var textService = new Mock(); + textService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())).Returns("blah"); + var appContext = new ApplicationContext( + new DatabaseContext(Mock.Of(), logger, new SqlSyntaxProviders(Enumerable.Empty())), + new ServiceContext( + localizedTextService: textService.Object + ), + Mock.Of(), + new ProfilingLogger(logger, Mock.Of())) + { + IsReady = true + }; + ApplicationContext.Current = appContext; var defaultVals = new Dictionary(); var persisted = new PreValueCollection(new Dictionary @@ -97,7 +124,7 @@ namespace Umbraco.Tests.PropertyEditors {"item3", new PreValue(3, "Item 3")} }); - var editor = new ValueListPreValueEditor(); + var editor = new ValueListPreValueEditor(Mock.Of()); var result = editor.ConvertDbToEditor(defaultVals, persisted); diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 8faa22ae25..4b750b6894 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Routing UmbracoApiControllerResolver.Current = new UmbracoApiControllerResolver( new ActivatorServiceProvider(), Logger, PluginManager.Current.ResolveUmbracoApiControllers()); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new LegacyShortStringHelper()); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); base.FreezeResolution(); } diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs index f506933631..c69efed5d8 100644 --- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs @@ -147,15 +147,20 @@ namespace Umbraco.Tests.Services.Importing var xml = XElement.Parse(strXml); var element = xml.Descendants("Templates").First(); var packagingService = ServiceContext.PackagingService; + var init = ServiceContext.FileService.GetTemplates().Count(); // Act var templates = packagingService.ImportTemplates(element); var numberOfTemplates = (from doc in element.Elements("Template") select doc).Count(); + var allTemplates = ServiceContext.FileService.GetTemplates(); // Assert Assert.That(templates, Is.Not.Null); Assert.That(templates.Any(), Is.True); Assert.That(templates.Count(), Is.EqualTo(numberOfTemplates)); + + Assert.AreEqual(init + numberOfTemplates, allTemplates.Count()); + Assert.IsTrue(allTemplates.All(x => x.Content.Contains("UmbracoTemplatePage"))); } [Test] diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index bea1110155..df7788e85f 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -55,7 +55,7 @@ namespace Umbraco.Tests.Services //we need a new Database object for each thread. var repositoryFactory = new RepositoryFactory(Container); var evtMsgs = new TransientMessagesFactory(); - ApplicationContext.Services = new ServiceContext( + ApplicationContext.Services = ServiceContextHelper.GetServiceContext( repositoryFactory, _uowProvider, new FileUnitOfWorkProvider(), diff --git a/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs index 77682661b2..dc39650baa 100644 --- a/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs +++ b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs @@ -30,19 +30,6 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(expected, output); } - [TestCase("thisIsTheEnd", "This Is The End")] - [TestCase("th", "Th")] - [TestCase("t", "t")] - [TestCase("thisis", "Thisis")] - [TestCase("ThisIsTheEnd", "This Is The End")] - [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6In The Village")] // we're happy to reproduce the issue - public void CompatibleLegacyReplacement(string input, string expected) - { - var helper = new LegacyShortStringHelper(); - var output = input.Length < 2 ? input : helper.SplitPascalCasing(input, ' ').ToFirstUpperInvariant(); - Assert.AreEqual(expected, output); - } - [TestCase("thisIsTheEnd", "This Is The End")] [TestCase("th", "Th")] [TestCase("t", "t")] diff --git a/src/Umbraco.Tests/Strings/LegacyShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/LegacyShortStringHelperTests.cs deleted file mode 100644 index 073b7add49..0000000000 --- a/src/Umbraco.Tests/Strings/LegacyShortStringHelperTests.cs +++ /dev/null @@ -1,358 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Strings -{ - [TestFixture] - public class LegacyShortStringHelperTests - { - private LegacyShortStringHelper _helper; - - [SetUp] - public void Setup() - { - var config = SettingsForTests.GetDefault(); - SettingsForTests.ConfigureSettings(config); - _helper = new LegacyShortStringHelper(); - } - - [TearDown] - public void TearDown() - { - } - - - #region Cases - [TestCase("foo", "foo")] - [TestCase(" foo ", "Foo")] - [TestCase("Foo", "Foo")] - [TestCase("FoO", "FoO")] - [TestCase("FoO bar", "FoOBar")] - [TestCase("FoO bar NIL", "FoOBarNIL")] - [TestCase("FoO 33bar 22NIL", "FoO33bar22NIL")] - [TestCase("FoO 33bar 22NI", "FoO33bar22NI")] - [TestCase("0foo", "foo")] - [TestCase("2foo bar", "fooBar")] - [TestCase("9FOO", "FOO")] - [TestCase("foo-BAR", "foo-BAR")] - [TestCase("foo-BA-dang", "foo-BA-dang")] - [TestCase("foo_BAR", "foo_BAR")] - [TestCase("foo'BAR", "fooBAR")] - [TestCase("sauté dans l'espace", "sauteDansLespace", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("foo\"\"bar", "foobar")] - [TestCase("-foo-", "-foo-")] - [TestCase("_foo_", "_foo_")] - [TestCase("spécial", "special", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("brô dëk ", "broDek", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("1235brô dëk ", "broDek", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("汉#字*/漢?字", "")] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEFGXKLMNOPQrst")] - [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEFGXKLMNOPQrst")] - [TestCase("AAA db cd EFG X KLMN OP qrst", "AAADbCdEFGXKLMNOPQrst")] - [TestCase("4 ways selector", "WaysSelector")] - [TestCase("WhatIfWeDoItAgain", "WhatIfWeDoItAgain")] - [TestCase("whatIfWeDoItAgain", "whatIfWeDoItAgain")] - [TestCase("WhatIfWEDOITAgain", "WhatIfWEDOITAgain")] - [TestCase("WhatIfWe doItAgain", "WhatIfWeDoItAgain")] - #endregion - public void CleanStringForSafeAlias(string input, string expected) - { - // NOTE legacy CleanStringForSafeAlias has issues w/some cases - // -> ignore test cases - // also, some aliases are strange... how can "-foo-" be a valid alias? - var output = _helper.CleanStringForSafeAlias(input); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("Tab 1", "tab1")] - [TestCase("Home - Page", "homePage")] - [TestCase("Home.Page", "homePage")] - [TestCase("Shannon's Document Type", "shannonsDocumentType")] // look, lowercase s and the end of shannons - [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType")] - [TestCase("i %Want!thisTo end up In Proper@case", "iWantThisToEndUpInProperCase")] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("TRii", "tRii")] - [TestCase("**TRii", "tRii")] - [TestCase("trII", "trII")] - [TestCase("**trII", "trII")] - [TestCase("trIIX", "trIIX")] - [TestCase("**trIIX", "trIIX")] - #endregion - public void LegacyCleanStringForUmbracoAlias(string input, string expected) - { - // NOTE ToUmbracoAlias has issues w/non-ascii, and a few other things - // -> ignore test cases - // also all those tests should, in theory, fail because removeSpaces is false by default - var output = _helper.LegacyCleanStringForUmbracoAlias(input); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("Home Page", "home-page")] - [TestCase("Shannon's Home Page!", "shannons-home-page!")] - [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-$h1zn")] - [TestCase("Räksmörgås", "raeksmoergaas")] - [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there,-aregoin-a-littlebit-crazy-eh!!-)")] - [TestCase("汉#字*/漢?字", "汉字star漢字")] - [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-lo-sk", IgnoreReason = "cannot handle it")] - #endregion - public void LegacyFormatUrl(string input, string expected) - { - // NOTE CleanStringForUrlSegment has issues with a few cases - // -> ignore test cases - // also some results are a bit strange... - var output = _helper.LegacyFormatUrl(input); - Assert.AreEqual(expected, output); - - // NOTE: not testing the overload with culture - // in legacy, they are the same - } - - #region Cases - [TestCase("Home Page", "home-page", true, true, false)] - [TestCase("Shannon's Home Page!", "shannons-home-page", true, true, false)] - [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-h1zn", true, true, false)] - [TestCase("Räksmörgås", "rksmrgs", true, true, false)] - [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there-aregoin-a-littlebit-crazy-eh", true, true, false)] - [TestCase("汉#字*/漢?字", "", true, true, false)] - [TestCase("汉#字*/漢?字", "汉字漢字", true, false, false)] - [TestCase("汉#字*/漢?字", "%e6%b1%89%e5%ad%97%e6%bc%a2%e5%ad%97", true, false, true)] - [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-lo-sk", true, true, false, IgnoreReason = "cannot handle it")] - #endregion - public void LegacyToUrlAlias(string input, string expected, bool replaceDoubleDashes, bool stripNonAscii, bool urlEncode) - { - var replacements = new Dictionary - { - {" ", "-"}, - {"\"", ""}, - {""", ""}, - {"@", ""}, - {"%", ""}, - {".", ""}, - {";", ""}, - {"/", ""}, - {":", ""}, - {"#", ""}, - {"+", ""}, - {"*", ""}, - {"&", ""}, - {"?", ""} - }; - - // NOTE CleanStringForUrlSegment has issues with a few cases - // -> ignore test cases - // also some results are a bit strange... - var output = _helper.LegacyToUrlAlias(input, replacements, replaceDoubleDashes, stripNonAscii, urlEncode); - Assert.AreEqual(expected, output); - - // NOTE: not testing the overload with culture - // in legacy, they are the same - } - - #region Cases - [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] - [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] - [TestCase("Shannon's document type", "shannon'sDocumentType", CleanStringType.CamelCase)] - [TestCase("This is the FIRSTTIME of TheDay.", "ThisistheFIRSTTIMEofTheDay", CleanStringType.Unchanged)] - [TestCase("Sépàyô lüx.", "Sepayolux", CleanStringType.Unchanged, IgnoreReason = "non-supported non-ascii chars")] - [TestCase("This is the FIRSTTIME of TheDay.", "ThisIsTheFIRSTTIMEOfTheDay", CleanStringType.PascalCase)] - [TestCase("This is the FIRSTTIME of TheDay.", "thisIsTheFIRSTTIMEOfTheDay", CleanStringType.CamelCase)] - #endregion - public void LegacyConvertStringCase(string input, string expected, CleanStringType caseType) - { - // NOTE LegacyConvertStringCase has issues with a few cases - // -> ignore test cases - // also it removes symbols, etc... except the quote? - var output = _helper.LegacyConvertStringCase(input, caseType); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("ThisIsTheEndMyFriend", "This Is The End My Friend")] - [TestCase("ThisIsTHEEndMyFriend", "This Is THE End My Friend")] - [TestCase("THISIsTHEEndMyFriend", "THIS Is THE End My Friend")] - [TestCase("This33I33sThe33EndMyFriend", "This33 I33s The33 End My Friend", IgnoreReason = "fails")] - [TestCase("ThisIsTHEEndMyFriendX", "This Is THE End My Friend X")] - [TestCase("ThisIsTHEEndMyFriendXYZ", "This Is THE End My Friend XYZ")] - [TestCase("ThisIsTHEEndMyFriendXYZt", "This Is THE End My Friend XY Zt")] - [TestCase("UneÉlévationÀPartir", "Une Élévation À Partir", IgnoreReason = "non-supported non-ascii chars")] - #endregion - public void SplitPascalCasing(string input, string expected) - { - // NOTE legacy SplitPascalCasing has issues w/some cases - // -> ignore test cases - var output = _helper.SplitPascalCasing(input, ' '); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("foo", "foo")] - [TestCase(" foo ", "foo")] - [TestCase("Foo", "foo")] - [TestCase("FoO", "foO")] - [TestCase("FoO bar", "foOBar")] - [TestCase("FoO bar NIL", "foOBarNil")] - [TestCase("FoO 33bar 22NIL", "foO33bar22Nil")] - [TestCase("FoO 33bar 22NI", "foO33bar22NI")] - [TestCase("0foo", "foo")] - [TestCase("2foo bar", "fooBar")] - [TestCase("9FOO", "foo")] - [TestCase("foo-BAR", "fooBar")] - [TestCase("foo-BA-dang", "fooBADang")] - [TestCase("foo_BAR", "fooBar")] - [TestCase("foo'BAR", "fooBar")] - [TestCase("sauté dans l'espace", "sauteDansLEspace")] - [TestCase("foo\"\"bar", "fooBar")] - [TestCase("-foo-", "foo")] - [TestCase("_foo_", "foo")] - [TestCase("spécial", "special")] - [TestCase("brô dëk ", "broDek")] - [TestCase("1235brô dëk ", "broDek")] - [TestCase("汉#字*/漢?字", "")] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst")] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst")] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst")] - #endregion - public void CleanStringToAscii(string input, string expected) - { - var output = _helper.CleanString(input, CleanStringType.Ascii | CleanStringType.CamelCase); - // legacy does nothing - Assert.AreEqual(input, output); - } - - #region Cases - [TestCase("1235brô dëK tzARlan ban123!pOo", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] - [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BroDeKTzARLanBan123POo", CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "broDeKTzARLanBan123POo", CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BRODEKTZARLANBAN123POO", CleanStringType.UpperCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "brodektzarlanban123poo", CleanStringType.LowerCase)] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("aaa DB cd EFG X KLMN OP qrst", "aaaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("aa DB cd EFG X KLMN OP qrst", "AaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("aaa DB cd EFG X KLMN OP qrst", "AaaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("AAA db cd EFG X KLMN OP qrst", "AaaDbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("We store some HTML in the DB for performance", "WeStoreSomeHtmlInTheDBForPerformance", CleanStringType.PascalCase)] - [TestCase("We store some HTML in the DB for performance", "weStoreSomeHtmlInTheDBForPerformance", CleanStringType.CamelCase)] - [TestCase("X is true", "XIsTrue", CleanStringType.PascalCase)] - [TestCase("X is true", "xIsTrue", CleanStringType.CamelCase)] - [TestCase("IO are slow", "IOAreSlow", CleanStringType.PascalCase)] - [TestCase("IO are slow", "ioAreSlow", CleanStringType.CamelCase)] - [TestCase("RAM is fast", "RamIsFast", CleanStringType.PascalCase)] - [TestCase("RAM is fast", "ramIsFast", CleanStringType.CamelCase)] - [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] - [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannonsDocumentType", CleanStringType.CamelCase)] - [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType", CleanStringType.CamelCase)] - [TestCase(" !BADDLY nam-ed Document Type", "BADDLYnamedDocumentType", CleanStringType.Unchanged)] - [TestCase("!BADDLY nam-ed Document Type", "BaddlyNamEdDocumentType", CleanStringType.PascalCase)] - [TestCase("i %Want!thisTo end up In Proper@case", "IWantThisToEndUpInProperCase", CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", CleanStringType.CamelCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgasKeKe", CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgaskeKe", CleanStringType.Unchanged)] - [TestCase("TRii", "TRii", CleanStringType.Unchanged)] - [TestCase("**TRii", "TRii", CleanStringType.Unchanged)] - [TestCase("TRii", "trIi", CleanStringType.CamelCase)] - [TestCase("**TRii", "trIi", CleanStringType.CamelCase)] - [TestCase("TRii", "TRIi", CleanStringType.PascalCase)] - [TestCase("**TRii", "TRIi", CleanStringType.PascalCase)] - [TestCase("trII", "trII", CleanStringType.Unchanged)] - [TestCase("**trII", "trII", CleanStringType.Unchanged)] - [TestCase("trII", "trII", CleanStringType.CamelCase)] - [TestCase("**trII", "trII", CleanStringType.CamelCase)] - [TestCase("trII", "TrII", CleanStringType.PascalCase)] - [TestCase("**trII", "TrII", CleanStringType.PascalCase)] - [TestCase("trIIX", "trIix", CleanStringType.CamelCase)] - [TestCase("**trIIX", "trIix", CleanStringType.CamelCase)] - [TestCase("trIIX", "TrIix", CleanStringType.PascalCase)] - [TestCase("**trIIX", "TrIix", CleanStringType.PascalCase)] - #endregion - public void CleanStringToAsciiWithCase(string input, string expected, CleanStringType caseType) - { - var output = _helper.CleanString(input, caseType | CleanStringType.Ascii); - // legacy does nothing - Assert.AreEqual(input, output); - } - - #region Cases - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)] - [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-De-K-Tz-AR-Lan-Ban123-P-Oo", '-', CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BRO-DE-K-TZ-AR-LAN-BAN123-P-OO", '-', CleanStringType.UpperCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-de-k-tz-ar-lan-ban123-p-oo", '-', CleanStringType.LowerCase)] - [TestCase("Tab 1", "tab 1", ' ', CleanStringType.CamelCase)] - [TestCase("Home - Page", "home Page", ' ', CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannons Document Type", ' ', CleanStringType.CamelCase)] - [TestCase("!BADDLY nam-ed Document Type", "baddly Nam Ed Document Type", ' ', CleanStringType.CamelCase)] - [TestCase(" !BADDLY nam-ed Document Type", "BADDLY nam ed Document Type", ' ', CleanStringType.Unchanged)] - [TestCase("!BADDLY nam-ed Document Type", "Baddly Nam Ed Document Type", ' ', CleanStringType.PascalCase)] - [TestCase("i %Want!thisTo end up In Proper@case", "I Want This To End Up In Proper Case", ' ', CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgas Ke Ke", ' ', CleanStringType.CamelCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas Ke Ke", ' ', CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas ke Ke", ' ', CleanStringType.Unchanged)] - #endregion - public void CleanStringToAsciiWithCaseAndSeparator(string input, string expected, char separator, CleanStringType caseType) - { - var output = _helper.CleanString(input, caseType | CleanStringType.Ascii, separator); - // legacy does nothing - Assert.AreEqual(input, output); - } - - [Test] // can't do cases with an IDictionary - public void ReplaceManyWithCharMap() - { - const string input = "télévisiön tzvâr ßup   pof"; - const string expected = "television tzvar ssup pof"; - IDictionary replacements = new Dictionary - { - { "é", "e" }, - { "ö", "o" }, - { "â", "a" }, - { "ß", "ss" }, - { " ", " " }, - }; - var output = _helper.ReplaceMany(input, replacements); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("val$id!ate|this|str'ing", "$!'", '-', "val-id-ate|this|str-ing")] - [TestCase("val$id!ate|this|str'ing", "$!'", '*', "val*id*ate|this|str*ing")] - #endregion - public void ReplaceManyByOneChar(string input, string toReplace, char replacement, string expected) - { - var output = _helper.ReplaceMany(input, toReplace.ToArray(), replacement); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("foo.txt", "foo.txt")] - [TestCase("foo", "foo", IgnoreReason = "fails when no extension")] - [TestCase(".txt", ".txt")] - [TestCase("nag*dog/poo:xit.txt", "nag-dog-poo-xit.txt")] - [TestCase("the dog is in the house.txt", "the-dog-is-in-the-house.txt")] - [TestCase("nil.nil.nil.txt", "nilnilnil.txt")] // because of chars map - [TestCase("taradabum", "taradabum", IgnoreReason = "fails when no extension")] - [TestCase("tara$$da:b/u - { - {" ", "-"}, - {"\"", ""}, - {""", ""}, - {"@", ""}, - {"%", ""}, - {".", ""}, - {";", ""}, - {"/", ""}, - {":", ""}, - {"#", ""}, - {"+", ""}, - {"*", ""}, - {"&", ""}, - {"?", ""} - }; - - var name1 = "Home Page"; - var name2 = "Shannon's Home Page!"; - var name3 = "#Someones's Twitter $h1z%n"; - var name4 = "Räksmörgås"; - var name5 = "'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)"; - var name6 = "汉#字*/漢?字"; - - var url1 = name1.ToUrlSegment(); - var url2 = name2.ToUrlSegment(); - var url3 = name3.ToUrlSegment(); - var url4 = name4.ToUrlSegment(); - var url5 = name5.ToUrlSegment(); - var url6 = name6.ToUrlSegment(); - var url7 = name6.ToUrlSegment(); - var url8 = name6.ToUrlSegment(); - - Assert.AreEqual("home-page", url1); - Assert.AreEqual("shannons-home-page", url2); - Assert.AreEqual("someoness-twitter-h1zn", url3); - Assert.AreEqual("rksmrgs", url4); - Assert.AreEqual("em-guys-over-there-aregoin-a-littlebit-crazy-eh", url5); - Assert.AreEqual("", url6); - Assert.AreEqual("汉字漢字", url7); - Assert.AreEqual("%e6%b1%89%e5%ad%97%e6%bc%a2%e5%ad%97", url8); - - } - - [TestCase] - public void StringExtensions_To_Camel_Case() - { - //Arrange - - var name1 = "Tab 1"; - var name2 = "Home - Page"; - var name3 = "Shannon's document type"; - - //Act - - var camelCase1 = name1.ToCleanString(CleanStringType.CamelCase); - var camelCase2 = name2.ToCleanString(CleanStringType.CamelCase); - var camelCase3 = name3.ToCleanString(CleanStringType.CamelCase); - - //Assert - - Assert.AreEqual("tab1", camelCase1); - Assert.AreEqual("homePage", camelCase2); - Assert.AreEqual("shannon'sDocumentType", camelCase3); - } - - [TestCase] - public void StringExtensions_To_Entity_Alias() - { - //Arrange - - var name1 = "Tab 1"; - var name2 = "Home - Page"; - var name3 = "Shannon's Document Type"; - var name4 = "!BADDLY nam-ed Document Type"; - var name5 = "i %Want!thisTo end up In Proper@case"; - - //Act - - var alias1 = name1.ToSafeAlias(); - var alias2 = name2.ToSafeAlias(); - var alias3 = name3.ToSafeAlias(); - var alias4 = name4.ToSafeAlias(); - var alias5 = name5.ToSafeAlias(/*StringAliasCaseType.PascalCase*/); - - //Assert - - Assert.AreEqual("tab1", alias1); - Assert.AreEqual("homePage", alias2); - Assert.AreEqual("shannonsDocumentType", alias3); - Assert.AreEqual("baddlyNamEdDocumentType", alias4); - - // disable: does not support PascalCase anymore - //Assert.AreEqual("IWantThisToEndUpInProperCase", alias5); - } - - } -} diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 34127d7560..463d5eb072 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -116,6 +116,7 @@ namespace Umbraco.Tests.TestHelpers var evtMsgs = new TransientMessagesFactory(); var databaseContext = new DatabaseContext(databaseFactory, Logger); var serviceContext = new ServiceContext( + ServiceContextHelper.GetServiceContext( Container.GetInstance(), new NPocoUnitOfWorkProvider(databaseFactory), new FileUnitOfWorkProvider(), diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index e0c5ab9c38..3db6cd3be5 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -72,7 +72,7 @@ namespace Umbraco.Tests.TestHelpers SetupApplicationContext(); - InitializeMappers(); + InitializeMappers(); FreezeResolution(); @@ -82,7 +82,7 @@ namespace Umbraco.Tests.TestHelpers public override void TearDown() { base.TearDown(); - + //reset settings SettingsForTests.Reset(); UmbracoContext.Current = null; @@ -99,9 +99,10 @@ namespace Umbraco.Tests.TestHelpers protected virtual void ConfigureContainer() { + // oh no! should not use a container in unit tests? var settings = SettingsForTests.GetDefault(); - //register mappers + //register mappers Container.RegisterFrom(); Container.RegisterFrom(); @@ -111,7 +112,7 @@ namespace Umbraco.Tests.TestHelpers //Default Datalayer/Repositories/SQL/Database/etc... Container.RegisterFrom(); - //register basic stuff that might need to be there for some container resolvers to work, we can + //register basic stuff that might need to be there for some container resolvers to work, we can // add more to this in base classes in resolution freezing Container.RegisterSingleton(factory => Logger); Container.Register(factory => CacheHelper); @@ -129,8 +130,12 @@ namespace Umbraco.Tests.TestHelpers Container.RegisterSingleton(factory => Mock.Of(), "PartialViewFileSystem"); Container.RegisterSingleton(factory => Mock.Of(), "PartialViewMacroFileSystem"); Container.RegisterSingleton(factory => Mock.Of(), "StylesheetFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "MasterpageFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "ViewFileSystem"); + + // need real file systems here as templates content is on-disk only + //Container.RegisterSingleton(factory => Mock.Of(), "MasterpageFileSystem"); + //Container.RegisterSingleton(factory => Mock.Of(), "ViewFileSystem"); + Container.RegisterSingleton(factory => new PhysicalFileSystem("Views", "/views"), "ViewFileSystem"); + Container.RegisterSingleton(factory => new PhysicalFileSystem("MasterPages", "/masterpages"), "MasterpageFileSystem"); } private static readonly object Locker = new object(); @@ -175,7 +180,7 @@ namespace Umbraco.Tests.TestHelpers } /// - /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan + /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs /// to be set to true if the SetupPluginManager has been overridden. /// @@ -209,20 +214,20 @@ namespace Umbraco.Tests.TestHelpers /// Inheritors can override this if they wish to create a custom application context /// protected virtual void SetupApplicationContext() - { + { var evtMsgs = new TransientMessagesFactory(); ApplicationContext.Current = new ApplicationContext( //assign the db context new DatabaseContext(new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, TestObjects.GetDefaultSqlSyntaxProviders(Logger), Logger), Logger), //assign the service context - new ServiceContext( - Container.GetInstance(), - new NPocoUnitOfWorkProvider(Logger), + ServiceContextHelper.GetServiceContext( + Container.GetInstance(), + new NPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), - new PublishingStrategy(evtMsgs, Logger), - CacheHelper, - Logger, - evtMsgs, + new PublishingStrategy(evtMsgs, Logger), + CacheHelper, + Logger, + evtMsgs, Enumerable.Empty()), CacheHelper, ProfilingLogger) @@ -268,7 +273,7 @@ namespace Umbraco.Tests.TestHelpers protected ProfilingLogger ProfilingLogger { get; private set; } protected CacheHelper CacheHelper { get; private set; } - //I know tests shouldn't use IoC, but for all these tests inheriting from this class are integration tests + //I know tests shouldn't use IoC, but for all these tests inheriting from this class are integration tests // and the number of these will hopefully start getting greatly reduced now that most things are mockable. internal IServiceContainer Container { get; private set; } diff --git a/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs b/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs new file mode 100644 index 0000000000..468c4b19b3 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Web.Services; + +namespace Umbraco.Tests.TestHelpers +{ + class ServiceContextHelper + { + //NOTE: Should be used sparingly for integration tests only - for unit tests you can just mock the services to be passed to the + // ctor of the ServiceContext. + public static ServiceContext GetServiceContext(RepositoryFactory repositoryFactory, + IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, + IUnitOfWorkProvider fileUnitOfWorkProvider, + IPublishingStrategy publishingStrategy, + CacheHelper cache, + ILogger logger, + IEventMessagesFactory eventMessagesFactory, + IEnumerable urlSegmentProviders) + { + if (repositoryFactory == null) throw new ArgumentNullException(nameof(repositoryFactory)); + if (dbUnitOfWorkProvider == null) throw new ArgumentNullException(nameof(dbUnitOfWorkProvider)); + if (fileUnitOfWorkProvider == null) throw new ArgumentNullException(nameof(fileUnitOfWorkProvider)); + if (publishingStrategy == null) throw new ArgumentNullException(nameof(publishingStrategy)); + if (cache == null) throw new ArgumentNullException(nameof(cache)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (eventMessagesFactory == null) throw new ArgumentNullException(nameof(eventMessagesFactory)); + + var provider = dbUnitOfWorkProvider; + var fileProvider = fileUnitOfWorkProvider; + + var migrationEntryService = new Lazy(() => new MigrationEntryService(provider, repositoryFactory, logger, eventMessagesFactory)); + var externalLoginService = new Lazy(() => new ExternalLoginService(provider, repositoryFactory, logger, eventMessagesFactory)); + var publicAccessService = new Lazy(() => new PublicAccessService(provider, repositoryFactory, logger, eventMessagesFactory)); + var taskService = new Lazy(() => new TaskService(provider, repositoryFactory, logger, eventMessagesFactory)); + var domainService = new Lazy(() => new DomainService(provider, repositoryFactory, logger, eventMessagesFactory)); + var auditService = new Lazy(() => new AuditService(provider, repositoryFactory, logger, eventMessagesFactory)); + + var localizedTextService = new Lazy(() => new LocalizedTextService( + new Lazy(() => + { + var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); + var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); + var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/")); + + var pluginLangFolders = appPlugins.Exists == false + ? Enumerable.Empty() + : appPlugins.GetDirectories() + .SelectMany(x => x.GetDirectories("Lang")) + .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) + .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) + .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); + + //user defined langs that overwrite the default, these should not be used by plugin creators + var userLangFolders = configLangFolder.Exists == false + ? Enumerable.Empty() + : configLangFolder + .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) + .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) + .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); + + return new LocalizedTextServiceFileSources( + logger, + cache.RuntimeCache, + mainLangFolder, + pluginLangFolders.Concat(userLangFolders)); + + }), + logger)); + + var userService = new Lazy(() => new UserService(provider, repositoryFactory, logger, eventMessagesFactory)); + var dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory, logger, eventMessagesFactory)); + var contentService = new Lazy(() => new ContentService(provider, repositoryFactory, logger, eventMessagesFactory, publishingStrategy, dataTypeService.Value, userService.Value, urlSegmentProviders)); + var notificationService = new Lazy(() => new NotificationService(provider, userService.Value, contentService.Value, repositoryFactory, logger)); + var serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory, logger, eventMessagesFactory)); + var memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory, logger, eventMessagesFactory)); + var memberService = new Lazy(() => new MemberService(provider, repositoryFactory, logger, eventMessagesFactory, memberGroupService.Value, dataTypeService.Value)); + var mediaService = new Lazy(() => new MediaService(provider, repositoryFactory, logger, eventMessagesFactory, dataTypeService.Value, userService.Value, urlSegmentProviders)); + var contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory, logger, eventMessagesFactory, contentService.Value, mediaService.Value)); + var fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory, logger, eventMessagesFactory)); + var localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory, logger, eventMessagesFactory)); + + var memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory, logger, eventMessagesFactory, memberService.Value)); + var entityService = new Lazy(() => new EntityService( + provider, repositoryFactory, logger, eventMessagesFactory, + contentService.Value, contentTypeService.Value, mediaService.Value, dataTypeService.Value, memberService.Value, memberTypeService.Value, + //TODO: Consider making this an isolated cache instead of using the global one + cache.RuntimeCache)); + + var macroService = new Lazy(() => new MacroService(provider, repositoryFactory, logger, eventMessagesFactory)); + var packagingService = new Lazy(() => new PackagingService(logger, contentService.Value, contentTypeService.Value, mediaService.Value, macroService.Value, dataTypeService.Value, fileService.Value, localizationService.Value, entityService.Value, userService.Value, repositoryFactory, provider, urlSegmentProviders)); + var relationService = new Lazy(() => new RelationService(provider, repositoryFactory, logger, eventMessagesFactory, entityService.Value)); + var treeService = new Lazy(() => new ApplicationTreeService(logger, cache)); + var tagService = new Lazy(() => new TagService(provider, repositoryFactory, logger, eventMessagesFactory)); + var sectionService = new Lazy(() => new SectionService(userService.Value, treeService.Value, provider, cache)); + + return new ServiceContext( + migrationEntryService, + publicAccessService, + taskService, + domainService, + auditService, + localizedTextService, + tagService, + contentService, + userService, + memberService, + mediaService, + contentTypeService, + dataTypeService, + fileService, + localizationService, + packagingService, + serverRegistrationService, + entityService, + relationService, + treeService, + sectionService, + macroService, + memberTypeService, + memberGroupService, + notificationService, + externalLoginService); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index afb12b3f3f..644cc7cdb7 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -186,6 +186,7 @@ + @@ -355,9 +356,7 @@ - - diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index d045645726..5dbb1ee29f 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -21,69 +21,75 @@ using Version = Lucene.Net.Util.Version; namespace Umbraco.Tests.UmbracoExamine { - /// - /// Used internally by test classes to initialize a new index from the template - /// - internal static class IndexInitializer - { - public static UmbracoContentIndexer GetUmbracoIndexer( - Directory luceneDir, - Analyzer analyzer = null, - IDataService dataService = null, - IContentService contentService = null, - IMediaService mediaService = null, - IDataTypeService dataTypeService = null, - IMemberService memberService = null, - IUserService userService = null) - { + /// + /// Used internally by test classes to initialize a new index from the template + /// + internal static class IndexInitializer + { + public static UmbracoContentIndexer GetUmbracoIndexer( + Directory luceneDir, + Analyzer analyzer = null, + IDataService dataService = null, + IContentService contentService = null, + IMediaService mediaService = null, + IDataTypeService dataTypeService = null, + IMemberService memberService = null, + IUserService userService = null) + { if (dataService == null) { dataService = new TestDataService(); } - if (contentService == null) - { + if (contentService == null) + { contentService = Mock.Of(); - } - if (userService == null) - { - userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); - } + } + if (userService == null) + { + userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); + } if (mediaService == null) { - long totalRecs; + long longTotalRecs; + int intTotalRecs; var allRecs = dataService.MediaService.GetLatestMediaByXpath("//node") .Root .Elements() .Select(x => Mock.Of( m => - m.Id == (int) x.Attribute("id") && - m.ParentId == (int) x.Attribute("parentID") && - m.Level == (int) x.Attribute("level") && + m.Id == (int)x.Attribute("id") && + m.ParentId == (int)x.Attribute("parentID") && + m.Level == (int)x.Attribute("level") && m.CreatorId == 0 && - m.SortOrder == (int) x.Attribute("sortOrder") && - m.CreateDate == (DateTime) x.Attribute("createDate") && - m.UpdateDate == (DateTime) x.Attribute("updateDate") && - m.Name == (string) x.Attribute("nodeName") && - m.Path == (string) x.Attribute("path") && + m.SortOrder == (int)x.Attribute("sortOrder") && + m.CreateDate == (DateTime)x.Attribute("createDate") && + m.UpdateDate == (DateTime)x.Attribute("updateDate") && + m.Name == (string)x.Attribute("nodeName") && + m.Path == (string)x.Attribute("path") && m.Properties == new PropertyCollection() && m.ContentType == Mock.Of(mt => - mt.Alias == (string) x.Attribute("nodeTypeAlias") && - mt.Id == (int) x.Attribute("nodeType")))) + mt.Alias == (string)x.Attribute("nodeTypeAlias") && + mt.Id == (int)x.Attribute("nodeType")))) .ToArray(); - + mediaService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs + && x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) == allRecs); + } if (dataTypeService == null) { dataTypeService = Mock.Of(); } - + if (memberService == null) { memberService = Mock.Of(); @@ -94,52 +100,52 @@ namespace Umbraco.Tests.UmbracoExamine analyzer = new StandardAnalyzer(Version.LUCENE_29); } - var indexSet = new IndexSet(); + var indexSet = new IndexSet(); var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); - var i = new UmbracoContentIndexer(indexCriteria, - luceneDir, //custom lucene directory - dataService, - contentService, - mediaService, - dataTypeService, - userService, + var i = new UmbracoContentIndexer(indexCriteria, + luceneDir, //custom lucene directory + dataService, + contentService, + mediaService, + dataTypeService, + userService, new[] { new DefaultUrlSegmentProvider() }, - analyzer, - false); + analyzer, + false); - //i.IndexSecondsInterval = 1; + //i.IndexSecondsInterval = 1; - i.IndexingError += IndexingError; + i.IndexingError += IndexingError; - return i; - } + return i; + } public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null) - { + { if (analyzer == null) { analyzer = new StandardAnalyzer(Version.LUCENE_29); } return new UmbracoExamineSearcher(luceneDir, analyzer); - } - - public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) - { - return new LuceneSearcher(luceneDir, new StandardAnalyzer(Version.LUCENE_29)); - } - - public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) - { - var i = new MultiIndexSearcher(new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); - return i; - } + } + + public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) + { + return new LuceneSearcher(luceneDir, new StandardAnalyzer(Version.LUCENE_29)); + } + + public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) + { + var i = new MultiIndexSearcher(new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); + return i; + } - internal static void IndexingError(object sender, IndexingErrorEventArgs e) - { - throw new ApplicationException(e.Message, e.InnerException); - } + internal static void IndexingError(object sender, IndexingErrorEventArgs e) + { + throw new ApplicationException(e.Message, e.InnerException); + } - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index fe80d915da..bf593397b6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -1,71 +1,71 @@ -(function() { - 'use strict'; - - function TableDirective() { - - function link(scope, el, attr, ctrl) { - - scope.clickItem = function(item, $event) { - if(scope.onClick) { - scope.onClick(item); - $event.stopPropagation(); - } - }; - - scope.selectItem = function(item, $index, $event) { - if(scope.onSelect) { - scope.onSelect(item, $index, $event); - $event.stopPropagation(); - } - }; - - scope.selectAll = function($event) { - if(scope.onSelectAll) { - scope.onSelectAll($event); - } - }; - - scope.isSelectedAll = function() { - if(scope.onSelectedAll && scope.items && scope.items.length > 0) { - return scope.onSelectedAll(); - } - }; - - scope.isSortDirection = function (col, direction) { - if (scope.onSortingDirection) { - return scope.onSortingDirection(col, direction); - } - }; - - scope.sort = function(field, allow) { - if(scope.onSort) { - scope.onSort(field, allow); - } - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-table.html', - scope: { - items: '=', - itemProperties: '=', - allowSelectAll: '=', - onSelect: '=', - onClick: '=', - onSelectAll: '=', - onSelectedAll: '=', - onSortingDirection: '=', - onSort: '=' - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTable', TableDirective); - -})(); +(function () { + 'use strict'; + + function TableDirective() { + + function link(scope, el, attr, ctrl) { + + scope.clickItem = function (item, $event) { + if (scope.onClick) { + scope.onClick(item); + $event.stopPropagation(); + } + }; + + scope.selectItem = function (item, $index, $event) { + if (scope.onSelect) { + scope.onSelect(item, $index, $event); + $event.stopPropagation(); + } + }; + + scope.selectAll = function ($event) { + if (scope.onSelectAll) { + scope.onSelectAll($event); + } + }; + + scope.isSelectedAll = function () { + if (scope.onSelectedAll && scope.items && scope.items.length > 0) { + return scope.onSelectedAll(); + } + }; + + scope.isSortDirection = function (col, direction) { + if (scope.onSortingDirection) { + return scope.onSortingDirection(col, direction); + } + }; + + scope.sort = function (field, allow, isSystem) { + if (scope.onSort) { + scope.onSort(field, allow, isSystem); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-table.html', + scope: { + items: '=', + itemProperties: '=', + allowSelectAll: '=', + onSelect: '=', + onClick: '=', + onSelectAll: '=', + onSelectedAll: '=', + onSortingDirection: '=', + onSort: '=' + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbTable', TableDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index f32602bda6..20ebaa10c0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -51,7 +51,162 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { }), 'Login failed for user ' + username); }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performRequestPasswordReset + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided email address is a valid user account and sends a link + * to allow them to reset their password + * + * ##usage + *
+         * authResource.performRequestPasswordReset(email)
+         *    .then(function(data) {
+         *        //Do stuff for password reset request...
+         *    });
+         * 
+ * @param {string} email Email address of backoffice user + * @returns {Promise} resourcePromise object + * + */ + performRequestPasswordReset: function (email) { + + if (!email) { + return angularHelper.rejectedPromise({ + errorMsg: 'Email address cannot be empty' + }); + } + + //TODO: This validation shouldn't really be done here, the validation on the login dialog + // is pretty hacky which is why this is here, ideally validation on the login dialog would + // be done properly. + var emailRegex = /\S+@\S+\.\S+/; + if (!emailRegex.test(email)) { + return angularHelper.rejectedPromise({ + errorMsg: 'Email address is not valid' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostRequestPasswordReset"), { + email: email + }), + 'Request password reset failed for email ' + email); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#performValidatePasswordResetCode + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided password reset code is valid + * + * ##usage + *
+         * authResource.performValidatePasswordResetCode(resetCode)
+         *    .then(function(data) {
+         *        //Allow reset of password
+         *    });
+         * 
+ * @param {integer} userId User Id + * @param {string} resetCode Password reset code + * @returns {Promise} resourcePromise object + * + */ + performValidatePasswordResetCode: function (userId, resetCode) { + + if (!userId) { + return angularHelper.rejectedPromise({ + errorMsg: 'User Id cannot be empty' + }); + } + + if (!resetCode) { + return angularHelper.rejectedPromise({ + errorMsg: 'Reset code cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostValidatePasswordResetCode"), + { + userId: userId, + resetCode: resetCode + }), + 'Password reset code validation failed for userId ' + userId + ', code' + resetCode); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performSetPassword + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided password reset code is valid and sets the user's password + * + * ##usage + *
+         * authResource.performSetPassword(userId, password, confirmPassword, resetCode)
+         *    .then(function(data) {
+         *        //Password set
+         *    });
+         * 
+ * @param {integer} userId User Id + * @param {string} password New password + * @param {string} confirmPassword Confirmation of new password + * @param {string} resetCode Password reset code + * @returns {Promise} resourcePromise object + * + */ + performSetPassword: function (userId, password, confirmPassword, resetCode) { + + if (userId === undefined || userId === null) { + return angularHelper.rejectedPromise({ + errorMsg: 'User Id cannot be empty' + }); + } + + if (!password) { + return angularHelper.rejectedPromise({ + errorMsg: 'Password cannot be empty' + }); + } + + if (password !== confirmPassword) { + return angularHelper.rejectedPromise({ + errorMsg: 'Password and confirmation do not match' + }); + } + + if (!resetCode) { + return angularHelper.rejectedPromise({ + errorMsg: 'Reset code cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostSetPassword"), + { + userId: userId, + password: password, + resetCode: resetCode + }), + 'Password reset code validation failed for userId ' + userId); + }, + unlinkLogin: function (loginProvider, providerKey) { if (!loginProvider || !providerKey) { return angularHelper.rejectedPromise({ 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 2cf7127707..3c1c1e2252 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 @@ -29,8 +29,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { function saveContentItem(content, action, files) { return umbRequestHelper.postSaveContent({ restApiUrl: umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"), + "contentApiBaseUrl", + "PostSave"), content: content, action: action, files: files, @@ -41,38 +41,38 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return { - - getRecycleBin: function() { + + getRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for content recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-         * var ids = [123,34533,2334,23434];
-         * contentResource.sort({ parentId: 1244, sortedIds: ids })
-         *    .then(function() {
-         *        $scope.complete = true;
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+          * var ids = [123,34533,2334,23434];
+          * contentResource.sort({ parentId: 1244, sortedIds: ids })
+          *    .then(function() {
+          *        $scope.complete = true;
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ sort: function (args) { if (!args) { throw "args cannot be null"; @@ -85,37 +85,37 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort content'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-         * contentResource.move({ parentId: 1244, id: 123 })
-         *    .then(function() {
-         *        alert("node was moved");
-         *    }, function(err){
-         *      alert("node didnt move:" + err.data.Message); 
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+          * contentResource.move({ parentId: 1244, id: 123 })
+          *    .then(function() {
+          *        alert("node was moved");
+          *    }, function(err){
+          *      alert("node didnt move:" + err.data.Message); 
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ move: function (args) { if (!args) { throw "args cannot be null"; @@ -128,38 +128,38 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
-         * contentResource.copy({ parentId: 1244, id: 123 })
-         *    .then(function() {
-         *        alert("node was copied");
-         *    }, function(err){
-         *      alert("node wasnt copy:" + err.data.Message); 
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
+          * contentResource.copy({ parentId: 1244, id: 123 })
+          *    .then(function() {
+          *        alert("node was copied");
+          *    }, function(err){
+          *      alert("node wasnt copy:" + err.data.Message); 
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ copy: function (args) { if (!args) { throw "args cannot be null"; @@ -172,271 +172,271 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), + args), + 'Failed to copy content'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
-         * contentResource.unPublish(1234)
-         *    .then(function() {
-         *        alert("node was unpulished");
-         *    }, function(err){
-         *      alert("node wasnt unpublished:" + err.data.Message); 
-         *    });
-         * 
- * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
+          * contentResource.unPublish(1234)
+          *    .then(function() {
+          *        alert("node was unpulished");
+          *    }, function(err){
+          *      alert("node wasnt unpublished:" + err.data.Message); 
+          *    });
+          * 
+ * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ unPublish: function (id) { if (!id) { throw "id cannot be null"; } - + return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + [{ id: id }])), + 'Failed to publish content with id ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
-         * contentResource.emptyRecycleBin()
-         *    .then(function() {
-         *        alert('its empty!');
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function() { + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
+          * contentResource.emptyRecycleBin()
+          *    .then(function() {
+          *        alert('its empty!');
+          *    });
+          * 
+ * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
-         * contentResource.deleteById(1234)
-         *    .then(function() {
-         *        alert('its gone!');
-         *    });
-         * 
- * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function(id) { + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
+          * contentResource.deleteById(1234)
+          *    .then(function() {
+          *        alert('its gone!');
+          *    });
+          * 
+ * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
-         * contentResource.getById(1234)
-         *    .then(function(content) {
-         *        var myDoc = content; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of content item to return - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id) { + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
+          * contentResource.getById(1234)
+          *    .then(function(content) {
+          *        var myDoc = content; 
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id id of content item to return + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); }, - + /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
-         * contentResource.getByIds( [1234,2526,28262])
-         *    .then(function(contentArray) {
-         *        var myDoc = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
+          * contentResource.getByIds( [1234,2526,28262])
+          *    .then(function(contentArray) {
+          *        var myDoc = contentArray; 
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ getByIds: function (ids) { - + var idQuery = ""; - _.each(ids, function(item) { + _.each(ids, function (item) { idQuery += "ids=" + item + "&"; }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids'); }, - + /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
-         * contentResource.getScaffold(1234, 'homepage')
-         *    .then(function(scaffold) {
-         *        var myDoc = scaffold;
-         *        myDoc.name = "My new document"; 
-         *
-         *        contentResource.publish(myDoc, true)
-         *            .then(function(content){
-         *                alert("Retrieved, updated and published again");
-         *            });
-         *    });
-         * 
- * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
+          * contentResource.getScaffold(1234, 'homepage')
+          *    .then(function(scaffold) {
+          *        var myDoc = scaffold;
+          *        myDoc.name = "My new document"; 
+          *
+          *        contentResource.publish(myDoc, true)
+          *            .then(function(content){
+          *                alert("Retrieved, updated and published again");
+          *            });
+          *    });
+          * 
+ * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ getScaffold: function (parentId, alias) { - + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
-         * contentResource.getNiceUrl(id)
-         *    .then(function(url) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ - getNiceUrl: function (id) { + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
+          * contentResource.getNiceUrl(id)
+          *    .then(function(url) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ + getNiceUrl: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl",[{id: id}])), - 'Failed to retrieve url for id:' + id); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), + 'Failed to retrieve url for id:' + id); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
-         * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-         *    .then(function(contentArray) {
-         *        var children = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
+          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+          *    .then(function(contentArray) {
+          *        var children = contentArray; 
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ getChildren: function (parentId, options) { var defaults = { @@ -444,10 +444,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { pageNumber: 0, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + orderBySystemField: true }; if (options === undefined) { - options = {}; + options = {}; } //overwrite the defaults if there are any specified angular.extend(defaults, options); @@ -461,52 +462,67 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { options.orderDirection = "Descending"; } + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { filter: options.filter } - ])), - 'Failed to retrieve children for content item ' + parentId); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ])), + 'Failed to retrieve children for content item ' + parentId); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
-         * contentResource.hasPermission('p',1234)
-         *    .then(function() {
-         *        alert('You are allowed to publish this item');
-         *    });
-         * 
- * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - checkPermission: function(permission, id) { + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
+          * contentResource.hasPermission('p',1234)
+          *    .then(function() {
+          *        alert('You are allowed to publish this item');
+          *    });
+          * 
+ * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + checkPermission: function (permission, id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission },{ nodeId: id }])), - 'Failed to check permission for item ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); }, getPermissions: function (nodeIds) { @@ -520,135 +536,135 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-         * contentResource.getById(1234)
-         *    .then(function(content) {
-         *          content.name = "I want a new name!";
-         *          contentResource.save(content, false)
-         *            .then(function(content){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+          * contentResource.getById(1234)
+          *    .then(function(content) {
+          *          content.name = "I want a new name!";
+          *          contentResource.save(content, false)
+          *            .then(function(content){
+          *                alert("Retrieved, updated and saved again");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ save: function (content, isNew, files) { return saveContentItem(content, "save" + (isNew ? "New" : ""), files); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-         * contentResource.getById(1234)
-         *    .then(function(content) {
-         *          content.name = "I want a new name, and be published!";
-         *          contentResource.publish(content, false)
-         *            .then(function(content){
-         *                alert("Retrieved, updated and published again");
-         *            });
-         *    });
-         * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+          * contentResource.getById(1234)
+          *    .then(function(content) {
+          *          content.name = "I want a new name, and be published!";
+          *          contentResource.publish(content, false)
+          *            .then(function(content){
+          *                alert("Retrieved, updated and published again");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ publish: function (content, isNew, files) { return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); }, - + /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
-         * contentResource.getById(1234)
-         *    .then(function(content) {
-         *          content.name = "I want a new name, and be published!";
-         *          contentResource.sendToPublish(content, false)
-         *            .then(function(content){
-         *                alert("Retrieved, updated and notication send off");
-         *            });
-         *    });
-         * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
+          * contentResource.getById(1234)
+          *    .then(function(content) {
+          *          content.name = "I want a new name, and be published!";
+          *          contentResource.sendToPublish(content, false)
+          *            .then(function(content){
+          *                alert("Retrieved, updated and notication send off");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ sendToPublish: function (content, isNew, files) { return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
-         * contentResource.publishById(1234)
-         *    .then(function(content) {
-         *        alert("published");
-         *    });
-         * 
- * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ - publishById: function(id){ + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
+          * contentResource.publishById(1234)
+          *    .then(function(content) {
+          *        alert("published");
+          *    });
+          * 
+ * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ + 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); - + $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/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 9a2310299f..4b56e55801 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -4,13 +4,13 @@ * @description Loads in data for media **/ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - + /** internal method process the saving of data and post processing the result */ function saveMediaItem(content, action, files) { return umbRequestHelper.postSaveContent({ restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), + "mediaApiBaseUrl", + "PostSave"), content: content, action: action, files: files, @@ -21,38 +21,38 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return { - + getRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-         * var ids = [123,34533,2334,23434];
-         * mediaResource.sort({ sortedIds: ids })
-         *    .then(function() {
-         *        $scope.complete = true;
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+          * var ids = [123,34533,2334,23434];
+          * mediaResource.sort({ sortedIds: ids })
+          *    .then(function() {
+          *        $scope.complete = true;
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ sort: function (args) { if (!args) { throw "args cannot be null"; @@ -65,37 +65,37 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-         * mediaResource.move({ parentId: 1244, id: 123 })
-         *    .then(function() {
-         *        alert("node was moved");
-         *    }, function(err){
-         *      alert("node didnt move:" + err.data.Message); 
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+          * mediaResource.move({ parentId: 1244, id: 123 })
+          *    .then(function() {
+          *        alert("node was moved");
+          *    }, function(err){
+          *      alert("node didnt move:" + err.data.Message); 
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ move: function (args) { if (!args) { throw "args cannot be null"; @@ -108,196 +108,196 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move media'); + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move media'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
-         * mediaResource.getById(1234)
-         *    .then(function(media) {
-         *        var myMedia = media; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
+          * mediaResource.getById(1234)
+          *    .then(function(media) {
+          *        var myMedia = media; 
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ getById: function (id) { - + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
-         * mediaResource.deleteById(1234)
-         *    .then(function() {
-         *        alert('its gone!');
-         *    });
-         * 
- * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function(id) { + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
+          * mediaResource.deleteById(1234)
+          *    .then(function() {
+          *        alert('its gone!');
+          *    });
+          * 
+ * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
-         * mediaResource.getByIds( [1234,2526,28262])
-         *    .then(function(mediaArray) {
-         *        var myDoc = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
+          * mediaResource.getByIds( [1234,2526,28262])
+          *    .then(function(mediaArray) {
+          *        var myDoc = contentArray; 
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ getByIds: function (ids) { - + var idQuery = ""; - _.each(ids, function(item) { + _.each(ids, function (item) { idQuery += "ids=" + item + "&"; }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
-         * mediaResource.getScaffold(1234, 'folder')
-         *    .then(function(scaffold) {
-         *        var myDoc = scaffold;
-         *        myDoc.name = "My new media item"; 
-         *
-         *        mediaResource.save(myDoc, true)
-         *            .then(function(media){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
+          * mediaResource.getScaffold(1234, 'folder')
+          *    .then(function(scaffold) {
+          *        var myDoc = scaffold;
+          *        myDoc.name = "My new media item"; 
+          *
+          *        mediaResource.save(myDoc, true)
+          *            .then(function(media){
+          *                alert("Retrieved, updated and saved again");
+          *            });
+          *    });
+          * 
+ * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ getScaffold: function (parentId, alias) { - + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); }, rootMedia: function () { - + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
-         * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-         *    .then(function(contentArray) {
-         *        var children = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
+          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+          *    .then(function(contentArray) {
+          *        var children = contentArray; 
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ getChildren: function (parentId, options) { var defaults = { @@ -305,7 +305,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { pageNumber: 0, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -322,150 +323,165 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { options.orderDirection = "Descending"; } + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { filter: options.filter } - ])), - 'Failed to retrieve children for media item ' + parentId); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ])), + 'Failed to retrieve children for media item ' + parentId); }, - + /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-         * mediaResource.getById(1234)
-         *    .then(function(media) {
-         *          media.name = "I want a new name!";
-         *          mediaResource.save(media, false)
-         *            .then(function(media){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+          * mediaResource.getById(1234)
+          *    .then(function(media) {
+          *          media.name = "I want a new name!";
+          *          mediaResource.save(media, false)
+          *            .then(function(media){
+          *                alert("Retrieved, updated and saved again");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ save: function (media, isNew, files) { return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
-         * mediaResource.addFolder("My gallery", 1234)
-         *    .then(function(folder) {
-         *        alert('New folder');
-         *    });
-         * 
- * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ - addFolder: function(name, parentId){ + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
+          * mediaResource.addFolder("My gallery", 1234)
+          *    .then(function(folder) {
+          *        alert('New folder');
+          *    });
+          * 
+ * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ + addFolder: function (name, parentId) { return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); }, - + /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * ##usage - *
-         * mediaResource.getChildFolders(1234)
-         *    .then(function(data) {
-         *        alert('folders');
-         *    });
-         * 
- * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ - getChildFolders: function(parentId){ - if(!parentId){ + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * ##usage + *
+          * mediaResource.getChildFolders(1234)
+          *    .then(function(data) {
+          *        alert('folders');
+          *    });
+          * 
+ * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function (parentId) { + if (!parentId) { parentId = -1; } - + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - [ - { id: parentId } - ])), - 'Failed to retrieve child folders for media item ' + parentId); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + [ + { id: parentId } + ])), + 'Failed to retrieve child folders for media item ' + parentId); }, - + /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
-         * mediaResource.emptyRecycleBin()
-         *    .then(function() {
-         *        alert('its empty!');
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function() { + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
+          * mediaResource.emptyRecycleBin()
+          *    .then(function() {
+          *        alert('its empty!');
+          *    });
+          * 
+ * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); } }; } 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 42db4f6366..2073307db9 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 @@ -4,25 +4,25 @@ * @description Loads in data for members **/ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { - + /** internal method process the saving of data and post processing the result */ function saveMember(content, action, files) { - + return umbRequestHelper.postSaveContent({ restApiUrl: umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "PostSave"), + "memberApiBaseUrl", + "PostSave"), content: content, action: action, - files: files, - dataFormatter: function(c, a) { + files: files, + dataFormatter: function (c, a) { return umbDataFormatter.formatMemberPostData(c, a); } }); } return { - + getPagedResults: function (memberTypeAlias, options) { if (memberTypeAlias === 'all-members') { @@ -34,7 +34,8 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { pageNumber: 1, filter: '', orderDirection: "Ascending", - orderBy: "LoginName" + orderBy: "LoginName", + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -51,179 +52,194 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { options.orderDirection = "Descending"; } + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + var params = [ - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { filter: options.filter } + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } ]; if (memberTypeAlias != null) { params.push({ memberTypeAlias: memberTypeAlias }); } return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetPagedResults", - params)), - 'Failed to retrieve member paged result'); + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetPagedResults", + params)), + 'Failed to retrieve member paged result'); }, - + getListNode: function (listName) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetListNodeDisplay", - [{ listName: listName }])), - 'Failed to retrieve data for member list ' + listName); + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetListNodeDisplay", + [{ listName: listName }])), + 'Failed to retrieve data for member list ' + listName); }, /** - * @ngdoc method - * @name umbraco.resources.memberResource#getByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Gets a member item with a given key - * - * ##usage - *
-         * memberResource.getByKey("0000-0000-000-00000-000")
-         *    .then(function(member) {
-         *        var mymember = member; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Guid} key key of member item to return - * @returns {Promise} resourcePromise object containing the member item. - * - */ + * @ngdoc method + * @name umbraco.resources.memberResource#getByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Gets a member item with a given key + * + * ##usage + *
+          * memberResource.getByKey("0000-0000-000-00000-000")
+          *    .then(function(member) {
+          *        var mymember = member; 
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Guid} key key of member item to return + * @returns {Promise} resourcePromise object containing the member item. + * + */ getByKey: function (key) { - + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetByKey", - [{ key: key }])), - 'Failed to retrieve data for member id ' + key); + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetByKey", + [{ key: key }])), + 'Failed to retrieve data for member id ' + key); }, /** - * @ngdoc method - * @name umbraco.resources.memberResource#deleteByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Deletes a member item with a given key - * - * ##usage - *
-         * memberResource.deleteByKey("0000-0000-000-00000-000")
-         *    .then(function() {
-         *        alert('its gone!');
-         *    });
-         * 
- * - * @param {Guid} key id of member item to delete - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.memberResource#deleteByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Deletes a member item with a given key + * + * ##usage + *
+          * memberResource.deleteByKey("0000-0000-000-00000-000")
+          *    .then(function() {
+          *        alert('its gone!');
+          *    });
+          * 
+ * + * @param {Guid} key id of member item to delete + * @returns {Promise} resourcePromise object. + * + */ deleteByKey: function (key) { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "DeleteByKey", - [{ key: key }])), - 'Failed to delete item ' + key); + $http.post( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "DeleteByKey", + [{ key: key }])), + 'Failed to delete item ' + key); }, /** - * @ngdoc method - * @name umbraco.resources.memberResource#getScaffold - * @methodOf umbraco.resources.memberResource - * - * @description - * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. - * - * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold - * - * The scaffold is used to build editors for member that has not yet been populated with data. - * - * ##usage - *
-         * memberResource.getScaffold('client')
-         *    .then(function(scaffold) {
-         *        var myDoc = scaffold;
-         *        myDoc.name = "My new member item"; 
-         *
-         *        memberResource.save(myDoc, true)
-         *            .then(function(member){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {String} alias membertype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the member scaffold. - * - */ + * @ngdoc method + * @name umbraco.resources.memberResource#getScaffold + * @methodOf umbraco.resources.memberResource + * + * @description + * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. + * + * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold + * + * The scaffold is used to build editors for member that has not yet been populated with data. + * + * ##usage + *
+          * memberResource.getScaffold('client')
+          *    .then(function(scaffold) {
+          *        var myDoc = scaffold;
+          *        myDoc.name = "My new member item"; 
+          *
+          *        memberResource.save(myDoc, true)
+          *            .then(function(member){
+          *                alert("Retrieved, updated and saved again");
+          *            });
+          *    });
+          * 
+ * + * @param {String} alias membertype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the member scaffold. + * + */ getScaffold: function (alias) { - + if (alias) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }])), - 'Failed to retrieve data for empty member item type ' + alias); + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }])), + 'Failed to retrieve data for empty member item type ' + alias); } else { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve data for empty member item type ' + alias); + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty")), + 'Failed to retrieve data for empty member item type ' + alias); } }, - + /** - * @ngdoc method - * @name umbraco.resources.memberResource#save - * @methodOf umbraco.resources.memberResource - * - * @description - * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation - * if the member needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-         * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
-         *    .then(function(member) {
-         *          member.name = "Bob";
-         *          memberResource.save(member, false)
-         *            .then(function(member){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {Object} media The member item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ + * @ngdoc method + * @name umbraco.resources.memberResource#save + * @methodOf umbraco.resources.memberResource + * + * @description + * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation + * if the member needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+          * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
+          *    .then(function(member) {
+          *          member.name = "Bob";
+          *          memberResource.save(member, false)
+          *            .then(function(member){
+          *                alert("Retrieved, updated and saved again");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} media The member item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ save: function (member, isNew, files) { return saveMember(member, "save" + (isNew ? "New" : ""), files); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index e211218ce1..0bdc3bb524 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -1,3 +1,47 @@ +/** + @ngdoc service + * @name umbraco.services.listViewHelper + * + * + * @description + * Service for performing operations against items in the list view UI. Used by the built-in internal listviews + * as well as custom listview. + * + * A custom listview is always used inside a wrapper listview, so there are a number of inherited values on its + * scope by default: + * + * **$scope.selection**: Array containing all items currently selected in the listview + * + * **$scope.items**: Array containing all items currently displayed in the listview + * + * **$scope.folders**: Array containing all folders in the current listview (only for media) + * + * **$scope.options**: configuration object containing information such as pagesize, permissions, order direction etc. + * + * **$scope.model.config.layouts**: array of available layouts to apply to the listview (grid, list or custom layout) + * + * ##Usage## + * To use, inject listViewHelper into custom listview controller, listviewhelper expects you + * to pass in the full collection of items in the listview in several of its methods + * this collection is inherited from the parent controller and is available on $scope.selection + * + *
+ *      angular.module("umbraco").controller("my.listVieweditor". function($scope, listViewHelper){
+ *
+ *          //current items in the listview
+ *          var items = $scope.items;
+ *
+ *          //current selection
+ *          var selection = $scope.selection;
+ *
+ *          //deselect an item , $scope.selection is inherited, item is picked from inherited $scope.items
+ *          listViewHelper.deselectItem(item, $scope.selection);
+ *
+ *          //test if all items are selected, $scope.items + $scope.selection are inherited
+ *          listViewhelper.isSelectedAll($scope.items, $scope.selection);
+ *      });
+ * 
+ */ (function () { 'use strict'; @@ -6,6 +50,19 @@ var firstSelectedIndex = 0; var localStorageKey = "umblistViewLayout"; + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#getLayout + * @methodOf umbraco.services.listViewHelper + * + * @description + * Method for internal use, based on the collection of layouts passed, the method selects either + * any previous layout from local storage, or picks the first allowed layout + * + * @param {Number} nodeId The id of the current node displayed in the content editor + * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts + */ + function getLayout(nodeId, availableLayouts) { var storedLayouts = []; @@ -28,6 +85,19 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#setLayout + * @methodOf umbraco.services.listViewHelper + * + * @description + * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage + * + * @param {Number} nodeID Id of the current node displayed in the content editor + * @param {Object} selectedLayout Layout selected as the layout to set as the current layout + * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts + */ + function setLayout(nodeId, selectedLayout, availableLayouts) { var activeLayout = {}; @@ -54,6 +124,18 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#saveLayoutInLocalStorage + * @methodOf umbraco.services.listViewHelper + * + * @description + * Stores a given layout as the current default selection in local storage + * + * @param {Number} nodeId Id of the current node displayed in the content editor + * @param {Object} selectedLayout Layout selected as the layout to set as the current layout + */ + function saveLayoutInLocalStorage(nodeId, selectedLayout) { var layoutFound = false; var storedLayouts = []; @@ -84,6 +166,17 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#getFirstAllowedLayout + * @methodOf umbraco.services.listViewHelper + * + * @description + * Returns currently selected layout, or alternatively the first layout in the available layouts collection + * + * @param {Array} layouts Array of all allowed layouts, available from $scope.model.config.layouts + */ + function getFirstAllowedLayout(layouts) { var firstAllowedLayout = {}; @@ -99,6 +192,23 @@ return firstAllowedLayout; } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#selectHandler + * @methodOf umbraco.services.listViewHelper + * + * @description + * Helper method for working with item selection via a checkbox, internally it uses selectItem and deselectItem. + * Working with this method, requires its triggered via a checkbox which can then pass in its triggered $event + * When the checkbox is clicked, this method will toggle selection of the associated item so it matches the state of the checkbox + * + * @param {Object} selectedItem Item being selected or deselected by the checkbox + * @param {Number} selectedIndex Index of item being selected/deselected, usually passed as $index + * @param {Array} items All items in the current listview, available as $scope.items + * @param {Array} selection All selected items in the current listview, available as $scope.selection + * @param {Event} $event Event triggered by the checkbox being checked to select / deselect an item + */ + function selectHandler(selectedItem, selectedIndex, items, selection, $event) { var start = 0; @@ -143,6 +253,18 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#selectItem + * @methodOf umbraco.services.listViewHelper + * + * @description + * Selects a given item to the listview selection array, requires you pass in the inherited $scope.selection collection + * + * @param {Object} item Item to select + * @param {Array} selection Listview selection, available as $scope.selection + */ + function selectItem(item, selection) { var isSelected = false; for (var i = 0; selection.length > i; i++) { @@ -157,6 +279,18 @@ } } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#deselectItem + * @methodOf umbraco.services.listViewHelper + * + * @description + * Deselects a given item from the listviews selection array, requires you pass in the inherited $scope.selection collection + * + * @param {Object} item Item to deselect + * @param {Array} selection Listview selection, available as $scope.selection + */ + function deselectItem(item, selection) { for (var i = 0; selection.length > i; i++) { var selectedItem = selection[i]; @@ -167,6 +301,20 @@ } } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#clearSelection + * @methodOf umbraco.services.listViewHelper + * + * @description + * Removes a given number of items and folders from the listviews selection array + * Folders can only be passed in if the listview is used in the media section which has a concept of folders. + * + * @param {Array} items Items to remove, can be null + * @param {Array} folders Folders to remove, can be null + * @param {Array} selection Listview selection, available as $scope.selection + */ + function clearSelection(items, folders, selection) { var i = 0; @@ -180,7 +328,7 @@ } } - if (angular.isArray(items)) { + if(angular.isArray(folders)) { for (i = 0; folders.length > i; i++) { var folder = folders[i]; folder.selected = false; @@ -188,6 +336,20 @@ } } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#selectAllItems + * @methodOf umbraco.services.listViewHelper + * + * @description + * Helper method for toggling the select state on all items in the active listview + * Can only be used from a checkbox as a checkbox $event is required to pass in. + * + * @param {Array} items Items to toggle selection on, should be $scope.items + * @param {Array} selection Listview selection, available as $scope.selection + * @param {$event} $event Event passed from the checkbox being toggled + */ + function selectAllItems(items, selection, $event) { var checkbox = $event.target; @@ -219,6 +381,20 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#isSelectedAll + * @methodOf umbraco.services.listViewHelper + * + * @description + * Method to determine if all items on the current page in the list has been selected + * Given the current items in the view, and the current selection, it will return true/false + * + * @param {Array} items Items to test if all are selected, should be $scope.items + * @param {Array} selection Listview selection, available as $scope.selection + * @returns {Boolean} boolean indicate if all items in the listview have been selected + */ + function isSelectedAll(items, selection) { var numberOfSelectedItem = 0; @@ -242,27 +418,51 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#setSortingDirection + * @methodOf umbraco.services.listViewHelper + * + * @description + * *Internal* method for changing sort order icon + * @param {String} col Column alias to order after + * @param {String} direction Order direction `asc` or `desc` + * @param {Object} options object passed from the parent listview available as $scope.options + */ + function setSortingDirection(col, direction, options) { return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction; } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#setSorting + * @methodOf umbraco.services.listViewHelper + * + * @description + * Method for setting the field on which the listview will order its items after. + * + * @param {String} field Field alias to order after + * @param {Boolean} allow Determines if the user is allowed to set this field, normally true + * @param {Object} options Options object passed from the parent listview available as $scope.options + */ + function setSorting(field, allow, options) { if (allow) { - options.orderBy = field; - - if (options.orderDirection === "desc") { - options.orderDirection = "asc"; - } else { + if (options.orderBy === field && options.orderDirection === 'asc') { options.orderDirection = "desc"; + } else { + options.orderDirection = "asc"; } + options.orderBy = field; } } - + //This takes in a dictionary of Ids with Permissions and determines // the intersect of all permissions to return an object representing the // listview button permissions function getButtonPermissions(unmergedPermissions, currentIdsWithPermissions) { - + if (currentIdsWithPermissions == null) { currentIdsWithPermissions = {}; } @@ -286,26 +486,28 @@ canCopy: _.contains(intersectPermissions, 'O'), //Magic Char = O canCreate: _.contains(intersectPermissions, 'C'), //Magic Char = C canDelete: _.contains(intersectPermissions, 'D'), //Magic Char = D - canMove: _.contains(intersectPermissions, 'M'), //Magic Char = M + canMove: _.contains(intersectPermissions, 'M'), //Magic Char = M canPublish: _.contains(intersectPermissions, 'U'), //Magic Char = U - canUnpublish: _.contains(intersectPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + canUnpublish: _.contains(intersectPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) }; } var service = { - getLayout: getLayout, - getFirstAllowedLayout: getFirstAllowedLayout, - setLayout: setLayout, - saveLayoutInLocalStorage: saveLayoutInLocalStorage, - selectHandler: selectHandler, - selectItem: selectItem, - deselectItem: deselectItem, - clearSelection: clearSelection, - selectAllItems: selectAllItems, - isSelectedAll: isSelectedAll, - setSortingDirection: setSortingDirection, - setSorting: setSorting, - getButtonPermissions: getButtonPermissions + + getLayout: getLayout, + getFirstAllowedLayout: getFirstAllowedLayout, + setLayout: setLayout, + saveLayoutInLocalStorage: saveLayoutInLocalStorage, + selectHandler: selectHandler, + selectItem: selectItem, + deselectItem: deselectItem, + clearSelection: clearSelection, + selectAllItems: selectAllItems, + isSelectedAll: isSelectedAll, + setSortingDirection: setSortingDirection, + setSorting: setSorting, + getButtonPermissions: getButtonPermissions + }; return service; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 3fb291619d..dffda24f1d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -211,7 +211,7 @@ angular.module('umbraco.services') return result; }); }, - + /** Logs the user out */ logout: function () { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 081a194b7e..6805783a86 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -66,7 +66,7 @@ font-weight: normal; } -.login-overlay .alert.alert-error { +.login-overlay .alert { display: inline-block; padding-right: 6px; padding-left: 6px; @@ -74,6 +74,10 @@ text-align: center; } +.login-overlay .switch-view { + margin-top: 10px; +} + @media (max-width: 565px) { // Remove padding on login-form on smaller devices .login-overlay .form { @@ -108,3 +112,9 @@ margin: auto; color: @grayLight; } + +.login-overlay .text-error, +.login-overlay .text-info +{ + font-weight:bold; +} diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 5d641fcb6c..69e6779a15 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -93,10 +93,10 @@ app.config(function ($routeProvider) { //ensure auth is *not* required so it will redirect to / resolve: canRoute(false) }) - .when('/logout', { + .when('/logout', { redirectTo: '/login/false', - resolve: doLogout() - }) + resolve: doLogout() + }) .when('/:section', { templateUrl: function (rp) { if (rp.section.toLowerCase() === "default" || rp.section.toLowerCase() === "umbraco" || rp.section === "") diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 325af02bbc..c38c55fcfb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,22 +1,56 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, $cookies, localizationService, userService, externalLoginInfo) { + function ($scope, $cookies, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource) { - /** - * @ngdoc function - * @name signin - * @methodOf MainController - * @function - * - * @description - * signs the user in - */ + var setFieldFocus = function(form, field) { + $timeout(function() { + $("form[name='" + form + "'] input[name='" + field + "']").focus(); + }); + } + function resetInputValidation() { + $scope.confirmPassword = ""; + $scope.password = ""; + $scope.login = ""; + if ($scope.loginForm) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + if ($scope.requestPasswordResetForm) { + $scope.requestPasswordResetForm.email.$setValidity("auth", true); + } + if ($scope.setPasswordForm) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + } + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + + $scope.showLogin = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "login"; + setFieldFocus("loginForm", "username"); + } + + $scope.showRequestPasswordReset = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "request-password-reset"; + $scope.showEmailResetConfirmation = false; + setFieldFocus("requestPasswordResetForm", "email"); + } + + $scope.showSetPassword = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "set-password"; + setFieldFocus("setPasswordForm", "password"); + } var d = new Date(); var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); var konamiMode = $cookies.konamiLogin; - //var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday"); if (konamiMode == "1") { $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; } else { @@ -29,6 +63,7 @@ $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; $scope.externalLoginProviders = externalLoginInfo.providers; $scope.externalLoginInfo = externalLoginInfo; + $scope.resetPasswordCodeInfo = resetPasswordCodeInfo; $scope.activateKonamiMode = function () { if ($cookies.konamiLogin == "1") { @@ -48,13 +83,12 @@ //if the login and password are not empty we need to automatically // validate them - this is because if there are validation errors on the server // then the user has to change both username & password to resubmit which isn't ideal, - // so if they're not empty , we'l just make sure to set them to valid. + // so if they're not empty, we'll just make sure to set them to valid. if (login && password && login.length > 0 && password.length > 0) { $scope.loginForm.username.$setValidity('auth', true); $scope.loginForm.password.$setValidity('auth', true); } - if ($scope.loginForm.$invalid) { return; } @@ -84,4 +118,88 @@ } }); }; + + $scope.requestPasswordResetSubmit = function (email) { + + $scope.errorMsg = ""; + $scope.showEmailResetConfirmation = false; + + if ($scope.requestPasswordResetForm.$invalid) { + return; + } + + authResource.performRequestPasswordReset(email) + .then(function () { + //remove the email entered + $scope.email = ""; + $scope.showEmailResetConfirmation = true; + }, function (reason) { + $scope.errorMsg = reason.errorMsg; + $scope.requestPasswordResetForm.email.$setValidity("auth", false); + }); + + $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () { + if ($scope.requestPasswordResetForm.email.$invalid) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + }); + }; + + $scope.setPasswordSubmit = function (password, confirmPassword) { + + $scope.showSetPasswordConfirmation = false; + + if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + + if ($scope.setPasswordForm.$invalid) { + return; + } + + authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode) + .then(function () { + $scope.showSetPasswordConfirmation = true; + $scope.resetComplete = true; + + //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again + resetPasswordCodeInfo.resetCodeModel = null; + + }, function (reason) { + if (reason.data && reason.data.Message) { + $scope.errorMsg = reason.data.Message; + } + else { + $scope.errorMsg = reason.errorMsg; + } + $scope.setPasswordForm.password.$setValidity("auth", false); + $scope.setPasswordForm.confirmPassword.$setValidity("auth", false); + }); + + $scope.setPasswordForm.password.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.password.$invalid) { + $scope.setPasswordForm.password.$setValidity('auth', true); + } + }); + $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.confirmPassword.$invalid) { + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + }); + } + + + //Now, show the correct panel: + + if ($scope.resetPasswordCodeInfo.resetCodeModel) { + $scope.showSetPassword(); + } + else if ($scope.resetPasswordCodeInfo.errors.length > 0) { + $scope.view = "password-reset-code-expired"; + } + else { + $scope.showLogin(); + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 3ca9bcda10..12f25f42a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -1,55 +1,135 @@ 
-
+

{{greeting}}

-

- Log in below. - Log in below -

- -
- -
- {{error}} + +
+ +

+ Log in below. + Log in below +

+ +
+ +
+ {{error}} +
+ +
+ +
+ + + +
+
+ +
+
+
Or
+
+
-
+ +
+ +
-
+
+ +
+ +
+
{{errorMsg}}
+
- + + - -
-
Or
-
-
-
-
- -
+
+

+ An email will be sent to the address specified with a link to reset your password +

-
- -
+ +
+ +
+ +
+
{{errorMsg}}
+
+ +
+
+ An email with password reset instructions will be sent to the specified address if it matched our records +
+
- + -
-
{{errorMsg}}
+ + +
+ +
+ +

+ Please provide a new password. +

+ +
+ +
+ +
+ +
+ +
+ +
+
{{errorMsg}}
+
+ +
+
+ Your new password has been set and you may now use it to log in. +
+
+ + + + +
+
+ +
+
+ {{error}}
- - + + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index fdd7dd23c9..fb5e992d81 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -1,76 +1,60 @@ -
- -
- - -
-
- -
- -
- - - - - -
-
- - -
-
- - -
- - -
- -
- - -
- -
- {{item[column.alias]}} -
- -
-
- -
- - - - There are no items show in the list. - - -
+
+
+ +
+
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ + +
+
+ {{item[column.alias]}} +
+
+
+
+ + + There are no items show in the list. + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index eb29e542e6..660bd38ce7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -1,74 +1,75 @@ -(function() { - "use strict"; +(function () { + "use strict"; - function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper) { + function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper) { - var vm = this; + var vm = this; - vm.nodeId = $scope.contentId; + vm.nodeId = $scope.contentId; //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); - vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; - vm.activeDrag = false; - vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; + vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; + vm.activeDrag = false; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; - vm.selectItem = selectItem; - vm.clickItem = clickItem; - vm.selectAll = selectAll; - vm.isSelectedAll = isSelectedAll; - vm.isSortDirection = isSortDirection; - vm.sort = sort; - vm.dragEnter = dragEnter; - vm.dragLeave = dragLeave; - vm.onFilesQueue = onFilesQueue; - vm.onUploadComplete = onUploadComplete; + vm.selectItem = selectItem; + vm.clickItem = clickItem; + vm.selectAll = selectAll; + vm.isSelectedAll = isSelectedAll; + vm.isSortDirection = isSortDirection; + vm.sort = sort; + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; - function selectAll($event) { - listViewHelper.selectAllItems($scope.items, $scope.selection, $event); - } + function selectAll($event) { + listViewHelper.selectAllItems($scope.items, $scope.selection, $event); + } - function isSelectedAll() { - return listViewHelper.isSelectedAll($scope.items, $scope.selection); - } + function isSelectedAll() { + return listViewHelper.isSelectedAll($scope.items, $scope.selection); + } - function selectItem(selectedItem, $index, $event) { - listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); - } + function selectItem(selectedItem, $index, $event) { + listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); + } - function clickItem(item) { - $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); - } + function clickItem(item) { + $location.path($scope.entityType + '/' +$scope.entityType + '/edit/' +item.id); + } - function isSortDirection(col, direction) { - return listViewHelper.setSortingDirection(col, direction, $scope.options); - } + function isSortDirection(col, direction) { + return listViewHelper.setSortingDirection(col, direction, $scope.options); + } - function sort(field, allow) { - if (allow) { - listViewHelper.setSorting(field, allow, $scope.options); - $scope.getContent($scope.contentId); + function sort(field, allow, isSystem) { + if (allow) { + $scope.options.orderBySystemField = isSystem; + listViewHelper.setSorting(field, allow, $scope.options); + $scope.getContent($scope.contentId); + } } - } - // Dropzone upload functions - function dragEnter(el, event) { - vm.activeDrag = true; - } + // Dropzone upload functions + function dragEnter(el, event) { + vm.activeDrag = true; + } - function dragLeave(el, event) { - vm.activeDrag = false; - } + function dragLeave(el, event) { + vm.activeDrag = false; + } - function onFilesQueue() { - vm.activeDrag = false; - } + function onFilesQueue() { + vm.activeDrag = false; + } - function onUploadComplete() { - $scope.getContent($scope.contentId); - } + function onUploadComplete() { + $scope.getContent($scope.contentId); + } - } + } - angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); +angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); -})(); +}) (); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 59ea03aa91..518c27d110 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -1,382 +1,393 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService) { - //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content - // that isn't created yet, if we continue this will use the parent id in the route params which isn't what - // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove - // the list view tab entirely when it's new. - if ($routeParams.create) { - $scope.isNew = true; - return; - } + //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content + // that isn't created yet, if we continue this will use the parent id in the route params which isn't what + // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove + // the list view tab entirely when it's new. + if ($routeParams.create) { + $scope.isNew = true; + return; + } - //Now we need to check if this is for media, members or content because that will depend on the resources we use - var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; + //Now we need to check if this is for media, members or content because that will depend on the resources we use + var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { - $scope.entityType = "member"; - contentResource = $injector.get('memberResource'); - getContentTypesCallback = $injector.get('memberTypeResource').getTypes; - getListResultsCallback = contentResource.getPagedResults; - deleteItemCallback = contentResource.deleteByKey; - getIdCallback = function(selected) { - var selectedKey = getItemKey(selected.id); - return selectedKey; - }; - createEditUrlCallback = function(item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; - }; - } - else { - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { - $scope.entityType = "media"; - contentResource = $injector.get('mediaResource'); - getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; - } - else { - $scope.entityType = "content"; - contentResource = $injector.get('contentResource'); - getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; - } - getListResultsCallback = contentResource.getChildren; - deleteItemCallback = contentResource.deleteById; - getIdCallback = function(selected) { - return selected.id; - }; - createEditUrlCallback = function(item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; - }; - } + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { + $scope.entityType = "member"; + contentResource = $injector.get('memberResource'); + getContentTypesCallback = $injector.get('memberTypeResource').getTypes; + getListResultsCallback = contentResource.getPagedResults; + deleteItemCallback = contentResource.deleteByKey; + getIdCallback = function (selected) { + var selectedKey = getItemKey(selected.id); + return selectedKey; + }; + createEditUrlCallback = function (item) { + return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; + }; + } + else { + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { + $scope.entityType = "media"; + contentResource = $injector.get('mediaResource'); + getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; + } + else { + $scope.entityType = "content"; + contentResource = $injector.get('contentResource'); + getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; + } + getListResultsCallback = contentResource.getChildren; + deleteItemCallback = contentResource.deleteById; + getIdCallback = function (selected) { + return selected.id; + }; + createEditUrlCallback = function (item) { + return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; + }; + } - $scope.pagination = []; - $scope.isNew = false; - $scope.actionInProgress = false; - $scope.selection = []; - $scope.folders = []; - $scope.listViewResultSet = { - totalPages: 0, - items: [] - }; - - $scope.currentNodePermissions = {} + $scope.pagination = []; + $scope.isNew = false; + $scope.actionInProgress = false; + $scope.selection = []; + $scope.folders = []; + $scope.listViewResultSet = { + totalPages: 0, + items: [] + }; - //Just ensure we do have an editorState - if (editorState.current) { - //Fetch current node allowed actions for the current user - //This is the current node & not each individual child node in the list - var currentUserPermissions = editorState.current.allowedActions; + $scope.currentNodePermissions = {} - //Create a nicer model rather than the funky & hard to remember permissions strings - $scope.currentNodePermissions = { - "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O - "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C - "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D - "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M - "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U - "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) - }; - } + //Just ensure we do have an editorState + if (editorState.current) { + //Fetch current node allowed actions for the current user + //This is the current node & not each individual child node in the list + var currentUserPermissions = editorState.current.allowedActions; - //when this is null, we don't check permissions - $scope.buttonPermissions = null; + //Create a nicer model rather than the funky & hard to remember permissions strings + $scope.currentNodePermissions = { + "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O + "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C + "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D + "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M + "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U + "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + }; + } - //When we are dealing with 'content', we need to deal with permissions on child nodes. - // Currently there is no real good way to - if ($scope.entityType === "content") { + //when this is null, we don't check permissions + $scope.buttonPermissions = null; - var idsWithPermissions = null; + //When we are dealing with 'content', we need to deal with permissions on child nodes. + // Currently there is no real good way to + if ($scope.entityType === "content") { - $scope.buttonPermissions = { - canCopy: true, - canCreate: true, - canDelete: true, - canMove: true, - canPublish: true, - canUnpublish: true - }; + var idsWithPermissions = null; - $scope.$watch(function() { - return $scope.selection.length; - }, function(newVal, oldVal) { + $scope.buttonPermissions = { + canCopy: true, + canCreate: true, + canDelete: true, + canMove: true, + canPublish: true, + canUnpublish: true + }; - if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) { - - //get all of the selected ids - var ids = _.map($scope.selection, function(i) { - return i.id.toString(); - }); + $scope.$watch(function () { + return $scope.selection.length; + }, function (newVal, oldVal) { - //remove the dictionary items that don't have matching ids - var filtered = {}; - _.each(idsWithPermissions, function (value, key, list) { - if (_.contains(ids, key)) { - filtered[key] = value; - } - }); - idsWithPermissions = filtered; + if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) { - //find all ids that we haven't looked up permissions for - var existingIds = _.keys(idsWithPermissions); - var missingLookup = _.map(_.difference(ids, existingIds), function (i) { - return Number(i); - }); + //get all of the selected ids + var ids = _.map($scope.selection, function (i) { + return i.id.toString(); + }); - if (missingLookup.length > 0) { - contentResource.getPermissions(missingLookup).then(function(p) { - $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions); - }); - } - else { - $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions); - } + //remove the dictionary items that don't have matching ids + var filtered = {}; + _.each(idsWithPermissions, function (value, key, list) { + if (_.contains(ids, key)) { + filtered[key] = value; + } + }); + idsWithPermissions = filtered; + + //find all ids that we haven't looked up permissions for + var existingIds = _.keys(idsWithPermissions); + var missingLookup = _.map(_.difference(ids, existingIds), function (i) { + return Number(i); + }); + + if (missingLookup.length > 0) { + contentResource.getPermissions(missingLookup).then(function (p) { + $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions); + }); } - }); + else { + $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions); + } + } + }); - } + } - $scope.options = { - displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, - pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, - pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, - filter: '', - orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), - orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", - includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ - { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, - { alias: 'updater', header: 'Last edited by', isSystem: 1 } - ], - layout: { - layouts: $scope.model.config.layouts, - activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) - }, - allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, - allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, - allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, - allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, - allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete - }; + $scope.options = { + displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, + pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, + pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, + filter: '', + orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), + orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", + orderBySystemField: true, + includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ + { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, + { alias: 'updater', header: 'Last edited by', isSystem: 1 } + ], + layout: { + layouts: $scope.model.config.layouts, + activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) + }, + orderBySystemField: true, + allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, + allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, + allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, + allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, + allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete + }; - //update all of the system includeProperties to enable sorting - _.each($scope.options.includeProperties, function(e, i) { + //update all of the system includeProperties to enable sorting + _.each($scope.options.includeProperties, function (e, i) { + + //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted + // to do that, we'd need to update the base query for content to include the content type alias column + // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? + if (e.alias != "contentTypeAlias") { + e.allowSorting = true; + } + + // Another special case for lasted edited data/update date for media, again this field isn't available on the base table so we can't sort by it + if (e.isSystem && $scope.entityType == "media") { + e.allowSorting = e.alias != 'updateDate'; + } + + // Another special case for members, only fields on the base table (cmsMember) can be used for sorting + if (e.isSystem && $scope.entityType == "member") { + e.allowSorting = e.alias == 'username' || e.alias == 'email'; + } if (e.isSystem) { + //localize the header + var key = getLocalizedKey(e.alias); + localizationService.localize(key).then(function (v) { + e.header = v; + }); + } + }); - //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted - // to do that, we'd need to update the base query for content to include the content type alias column - // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? - if (e.alias != "contentTypeAlias") { - e.allowSorting = true; - } + $scope.selectLayout = function (selectedLayout) { + $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); + }; - //localize the header - var key = getLocalizedKey(e.alias); - localizationService.localize(key).then(function (v) { - e.header = v; + function showNotificationsAndReset(err, reload, successMsg) { + + //check if response is ysod + if (err.status && err.status >= 500) { + + // Open ysod overlay + $scope.ysodOverlay = { + view: "ysod", + error: err, + show: true + }; + } + + $timeout(function () { + $scope.bulkStatus = ""; + $scope.actionInProgress = false; + }, 500); + + if (reload === true) { + $scope.reloadView($scope.contentId); + } + + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + else if (successMsg) { + notificationsService.success("Done", successMsg); + } + } + + $scope.next = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + $scope.goToPage = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + $scope.prev = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + + /*Loads the search results, based on parameters set in prev,next,sort and so on*/ + /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state + with simple values */ + + $scope.reloadView = function (id) { + + $scope.viewLoaded = false; + + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + + getListResultsCallback(id, $scope.options).then(function (data) { + + $scope.actionInProgress = false; + $scope.listViewResultSet = data; + + //update all values for display + if ($scope.listViewResultSet.items) { + _.each($scope.listViewResultSet.items, function (e, index) { + setPropertyValues(e); }); - } - }); + } - $scope.selectLayout = function(selectedLayout) { - $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); - }; + if ($scope.entityType === 'media') { - function showNotificationsAndReset(err, reload, successMsg) { - - //check if response is ysod - if(err.status && err.status >= 500) { - - // Open ysod overlay - $scope.ysodOverlay = { - view : "ysod", - error : err, - show : true - }; - } - - $timeout(function() { - $scope.bulkStatus = ""; - $scope.actionInProgress = false; - }, 500); - - if (reload === true) { - $scope.reloadView($scope.contentId); - } - - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - else if (successMsg) { - notificationsService.success("Done", successMsg); - } - } - - $scope.next = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.goToPage = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.prev = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - - /*Loads the search results, based on parameters set in prev,next,sort and so on*/ - /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state - with simple values */ - - $scope.reloadView = function(id) { - - $scope.viewLoaded = false; - - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - - getListResultsCallback(id, $scope.options).then(function(data) { - - $scope.actionInProgress = false; - $scope.listViewResultSet = data; - - //update all values for display - if ($scope.listViewResultSet.items) { - _.each($scope.listViewResultSet.items, function(e, index) { - setPropertyValues(e); - }); - } - - if ($scope.entityType === 'media') { - - mediaResource.getChildFolders($scope.contentId) - .then(function(folders) { - $scope.folders = folders; - $scope.viewLoaded = true; + mediaResource.getChildFolders($scope.contentId) + .then(function (folders) { + $scope.folders = folders; + $scope.viewLoaded = true; }); - } else { - $scope.viewLoaded = true; - } + } else { + $scope.viewLoaded = true; + } - //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example - // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last - // available page and then re-load again - if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { - $scope.options.pageNumber = $scope.listViewResultSet.totalPages; + //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example + // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last + // available page and then re-load again + if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber = $scope.listViewResultSet.totalPages; - //reload! - $scope.reloadView(id); - } + //reload! + $scope.reloadView(id); + } - }); - }; + }); + }; - var searchListView = _.debounce(function(){ - $scope.$apply(function() { + var searchListView = _.debounce(function () { + $scope.$apply(function () { + makeSearch(); + }); + }, 500); + + $scope.forceSearch = function (ev) { + //13: enter + switch (ev.keyCode) { + case 13: makeSearch(); - }); - }, 500); + break; + } + }; - $scope.forceSearch = function (ev) { - //13: enter - switch (ev.keyCode) { - case 13: - makeSearch(); - break; - } - }; + $scope.enterSearch = function () { + $scope.viewLoaded = false; + searchListView(); + }; - $scope.enterSearch = function() { - $scope.viewLoaded = false; - searchListView(); - }; + function makeSearch() { + if ($scope.options.filter !== null && $scope.options.filter !== undefined) { + $scope.options.pageNumber = 1; + //$scope.actionInProgress = true; + $scope.reloadView($scope.contentId); + } + } - function makeSearch() { - if ($scope.options.filter !== null && $scope.options.filter !== undefined) { - $scope.options.pageNumber = 1; - //$scope.actionInProgress = true; - $scope.reloadView($scope.contentId); - } - } + $scope.isAnythingSelected = function () { + if ($scope.selection.length === 0) { + return false; + } else { + return true; + } + }; - $scope.isAnythingSelected = function() { - if ($scope.selection.length === 0) { - return false; - } else { - return true; - } - }; - - $scope.selectedItemsCount = function() { + $scope.selectedItemsCount = function () { return $scope.selection.length; - }; + }; - $scope.clearSelection = function() { + $scope.clearSelection = function () { listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - }; + }; - $scope.getIcon = function(entry) { - return iconHelper.convertFromLegacyIcon(entry.icon); - }; + $scope.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; - function serial(selected, fn, getStatusMsg, index) { - return fn(selected, index).then(function (content) { - index++; - $scope.bulkStatus = getStatusMsg(index, selected.length); - return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; - }, function (err) { - var reload = index > 0; - showNotificationsAndReset(err, reload); - return err; - }); - } + function serial(selected, fn, getStatusMsg, index) { + return fn(selected, index).then(function (content) { + index++; + $scope.bulkStatus = getStatusMsg(index, selected.length); + return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; + }, function (err) { + var reload = index > 0; + showNotificationsAndReset(err, reload); + return err; + }); + } - function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { - var selected = $scope.selection; - if (selected.length === 0) - return; - if (confirmMsg && !confirm(confirmMsg)) - return; + function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { + var selected = $scope.selection; + if (selected.length === 0) + return; + if (confirmMsg && !confirm(confirmMsg)) + return; - $scope.actionInProgress = true; - $scope.bulkStatus = getStatusMsg(0, selected.length); + $scope.actionInProgress = true; + $scope.bulkStatus = getStatusMsg(0, selected.length); - serial(selected, fn, getStatusMsg, 0).then(function (result) { - // executes once the whole selection has been processed - // in case of an error (caught by serial), result will be the error - if (!(result.data && angular.isArray(result.data.notifications))) - showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); - }); - } + serial(selected, fn, getStatusMsg, 0).then(function (result) { + // executes once the whole selection has been processed + // in case of an error (caught by serial), result will be the error + if (!(result.data && angular.isArray(result.data.notifications))) + showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); + }); + } - $scope.delete = function () { - applySelected( - function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, - "Sure you want to delete?"); - }; + $scope.delete = function () { + applySelected( + function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, + function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, + "Sure you want to delete?"); + }; - $scope.publish = function () { - applySelected( - function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); - }; + $scope.publish = function () { + applySelected( + function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, + function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); + }; - $scope.unpublish = function() { - applySelected( - function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); - }; + $scope.unpublish = function () { + applySelected( + function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, + function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); + }; - $scope.move = function() { + $scope.move = function () { $scope.moveDialog = {}; $scope.moveDialog.title = "Move"; $scope.moveDialog.section = $scope.entityType; @@ -384,9 +395,9 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.moveDialog.view = "move"; $scope.moveDialog.show = true; - $scope.moveDialog.submit = function(model) { + $scope.moveDialog.submit = function (model) { - if(model.target) { + if (model.target) { performMove(model.target); } @@ -394,22 +405,22 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.moveDialog = null; }; - $scope.moveDialog.close = function(oldModel) { + $scope.moveDialog.close = function (oldModel) { $scope.moveDialog.show = false; $scope.moveDialog = null; }; - }; + }; - function performMove(target) { + function performMove(target) { applySelected( - function(selected, index) {return contentResource.move({parentId: target.id, id: getIdCallback(selected[index])}); }, - function(count, total) {return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function(total) {return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); - } + function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, + function (count, total) { return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); + } - $scope.copy = function() { + $scope.copy = function () { $scope.copyDialog = {}; $scope.copyDialog.title = "Copy"; $scope.copyDialog.section = $scope.entityType; @@ -417,8 +428,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.copyDialog.view = "copy"; $scope.copyDialog.show = true; - $scope.copyDialog.submit = function(model) { - if(model.target) { + $scope.copyDialog.submit = function (model) { + if (model.target) { performCopy(model.target, model.relateToOriginal); } @@ -426,142 +437,142 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.copyDialog = null; }; - $scope.copyDialog.close = function(oldModel) { + $scope.copyDialog.close = function (oldModel) { $scope.copyDialog.show = false; $scope.copyDialog = null; }; - }; + }; - function performCopy(target, relateToOriginal) { + function performCopy(target, relateToOriginal) { applySelected( - function(selected, index) {return contentResource.copy({parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal}); }, - function(count, total) {return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function(total) {return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); - } + function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, + function (count, total) { return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); + } - function getCustomPropertyValue(alias, properties) { - var value = ''; - var index = 0; - var foundAlias = false; - for (var i = 0; i < properties.length; i++) { - if (properties[i].alias == alias) { - foundAlias = true; - break; - } - index++; - } + function getCustomPropertyValue(alias, properties) { + var value = ''; + var index = 0; + var foundAlias = false; + for (var i = 0; i < properties.length; i++) { + if (properties[i].alias == alias) { + foundAlias = true; + break; + } + index++; + } - if (foundAlias) { - value = properties[index].value; - } + if (foundAlias) { + value = properties[index].value; + } - return value; - } + return value; + } - /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ - function setPropertyValues(result) { + /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ + function setPropertyValues(result) { - //set the edit url - result.editPath = createEditUrlCallback(result); + //set the edit url + result.editPath = createEditUrlCallback(result); - _.each($scope.options.includeProperties, function (e, i) { + _.each($scope.options.includeProperties, function (e, i) { - var alias = e.alias; + var alias = e.alias; - // First try to pull the value directly from the alias (e.g. updatedBy) - var value = result[alias]; + // First try to pull the value directly from the alias (e.g. updatedBy) + var value = result[alias]; - // If this returns an object, look for the name property of that (e.g. owner.name) - if (value === Object(value)) { - value = value['name']; - } + // If this returns an object, look for the name property of that (e.g. owner.name) + if (value === Object(value)) { + value = value['name']; + } - // If we've got nothing yet, look at a user defined property - if (typeof value === 'undefined') { - value = getCustomPropertyValue(alias, result.properties); - } + // If we've got nothing yet, look at a user defined property + if (typeof value === 'undefined') { + value = getCustomPropertyValue(alias, result.properties); + } - // If we have a date, format it - if (isDate(value)) { - value = value.substring(0, value.length - 3); - } + // If we have a date, format it + if (isDate(value)) { + value = value.substring(0, value.length - 3); + } - // set what we've got on the result - result[alias] = value; - }); + // set what we've got on the result + result[alias] = value; + }); - } + } - function isDate(val) { - if (angular.isString(val)) { - return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); - } - return false; - } + function isDate(val) { + if (angular.isString(val)) { + return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); + } + return false; + } - function initView() { - //default to root id if the id is undefined - var id = $routeParams.id; - if(id === undefined){ - id = -1; - } + function initView() { + //default to root id if the id is undefined + var id = $routeParams.id; + if (id === undefined) { + id = -1; + } - $scope.listViewAllowedTypes = getContentTypesCallback(id); + $scope.listViewAllowedTypes = getContentTypesCallback(id); - $scope.contentId = id; - $scope.isTrashed = id === "-20" || id === "-21"; + $scope.contentId = id; + $scope.isTrashed = id === "-20" || id === "-21"; - $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; - $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; + $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; + $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; - $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || - $scope.options.allowBulkUnpublish || - $scope.options.allowBulkCopy || - $scope.options.allowBulkMove || - $scope.options.allowBulkDelete; + $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || + $scope.options.allowBulkUnpublish || + $scope.options.allowBulkCopy || + $scope.options.allowBulkMove || + $scope.options.allowBulkDelete; - $scope.reloadView($scope.contentId); - } + $scope.reloadView($scope.contentId); + } - function getLocalizedKey(alias) { + function getLocalizedKey(alias) { - switch (alias) { - case "sortOrder": - return "general_sort"; - case "updateDate": - return "content_updateDate"; - case "updater": - return "content_updatedBy"; - case "createDate": - return "content_createDate"; - case "owner": - return "content_createBy"; - case "published": - return "content_isPublished"; - case "contentTypeAlias": - //TODO: Check for members - return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; - case "email": - return "general_email"; - case "username": - return "general_username"; - } - return alias; - } + switch (alias) { + case "sortOrder": + return "general_sort"; + case "updateDate": + return "content_updateDate"; + case "updater": + return "content_updatedBy"; + case "createDate": + return "content_createDate"; + case "owner": + return "content_createBy"; + case "published": + return "content_isPublished"; + case "contentTypeAlias": + //TODO: Check for members + return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; + case "email": + return "general_email"; + case "username": + return "general_username"; + } + return alias; + } - function getItemKey(itemId) { - for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { - var item = $scope.listViewResultSet.items[i]; - if (item.id === itemId) { - return item.key; - } - } - } + function getItemKey(itemId) { + for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { + var item = $scope.listViewResultSet.items[i]; + if (item.id === itemId) { + return item.key; + } + } + } - //GO! - initView(); + //GO! + initView(); } diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index e26259c357..e2e1e7dc10 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -40,4 +40,5 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index dcf9c4c7e4..200c30648c 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -103,6 +103,9 @@ false + + true + diff --git a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml index 8fb1487713..7b1f83115f 100644 --- a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml @@ -71,8 +71,8 @@ diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 96128ae94c..1360c09667 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -290,6 +290,7 @@ Enter your username Enter your password + Confirm your password Name the %0%... Enter a name... Label... @@ -297,6 +298,7 @@ Type to search... Type to filter... Type to add tags (press enter after each tag)... + Enter your email Allow at root @@ -660,6 +662,17 @@ To manage your website, simply open the Umbraco back office and start adding con Log in below Session timed out © 2001 - %0%
Umbraco.com

]]>
+ Forgotten password? + An email will be sent to the address specified with a link to reset your password + An email with password reset instructions will be sent to the specified address if it matched our records + Return to login form + Please provide a new password + Your Password has been updated + The link you have clicked on is invalid or has expired + Umbraco: Reset Password + + Your username to login to the Umbraco back-office is: %0%

Click here to reset your password or copy/paste this URL into your browser:

%1%

]]> +
Dashboard diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 1d1a2b05be..700c252cde 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -291,6 +291,7 @@ Enter your username Enter your password + Confirm your password Name the %0%... Enter a name... Label... @@ -298,6 +299,7 @@ Type to search... Type to filter... Type to add tags (press enter after each tag)... + Enter your email @@ -658,6 +660,17 @@ To manage your website, simply open the Umbraco back office and start adding con Log in below Session timed out © 2001 - %0%
Umbraco.com

]]>
+ Forgotten password? + An email will be sent to the address specified with a link to reset your password + An email with password reset instructions will be sent to the specified address if it matched our records + Return to login form + Please provide a new password + Your Password has been updated + The link you have clicked on is invalid or has expired + Umbraco: Reset Password + + Your username to login to the Umbraco back-office is: %0%

Click here to reset your password or copy/paste this URL into your browser:

%1%

]]> +
Dashboard diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 1b0ddfc4b3..368ef67b97 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -72,7 +72,7 @@ - + diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index e3a3005731..0b9b555287 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -4,26 +4,29 @@ using System.Net; using System.Net.Http; using System.Collections.Generic; using System.Threading.Tasks; +using System.Web; using System.Web.Http; +using System.Web.Mvc; using AutoMapper; using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Security; +using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; -using Umbraco.Core.Security; using Umbraco.Web.Security; +using Umbraco.Web.Security.Identity; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; -using Microsoft.AspNet.Identity.Owin; -using Umbraco.Core.Logging; using Umbraco.Core.Configuration; -using Umbraco.Web.Security.Identity; +using Umbraco.Core.Logging; namespace Umbraco.Web.Editors { - /// /// The API controller used for editing content /// @@ -97,7 +100,7 @@ namespace Umbraco.Web.Editors /// Checks if the current user's cookie is valid and if so returns OK or a 400 (BadRequest) ///
/// - [HttpGet] + [System.Web.Http.HttpGet] public bool IsAuthenticated() { var attempt = UmbracoContext.Security.AuthorizeRequest(); @@ -149,9 +152,7 @@ namespace Umbraco.Web.Editors [SetAngularAntiForgeryTokens] public async Task PostLogin(LoginModel loginModel) { - var http = this.TryGetHttpContext(); - if (http.Success == false) - throw new InvalidOperationException("This method requires that an HttpContext be active"); + var http = EnsureHttpContext(); var result = await SignInManager.PasswordSignInAsync( loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true); @@ -217,6 +218,91 @@ namespace Umbraco.Web.Editors } } + /// + /// Processes a password reset request. Looks for a match on the provided email address + /// and if found sends an email with a link to reset it + /// + /// + [SetAngularAntiForgeryTokens] + public async Task PostRequestPasswordReset(RequestPasswordResetModel model) + { + // If this feature is switched off in configuration the UI will be amended to not make the request to reset password available. + // So this is just a server-side secondary check. + if (UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset == false) + { + throw new HttpResponseException(HttpStatusCode.BadRequest); + } + var identityUser = await SignInManager.UserManager.FindByEmailAsync(model.Email); + if (identityUser != null) + { + var user = Services.UserService.GetByEmail(model.Email); + if (user != null && user.IsLockedOut == false) + { + var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); + var callbackUrl = ConstuctCallbackUrl(identityUser.Id, code); + + var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", + //Ensure the culture of the found user is used for the email! + UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService), + new[] {identityUser.UserName, callbackUrl}); + + await UserManager.SendEmailAsync(identityUser.Id, + Services.TextService.Localize("login/resetPasswordEmailCopySubject", + //Ensure the culture of the found user is used for the email! + UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService)), + message); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + private string ConstuctCallbackUrl(int userId, string code) + { + //get an mvc helper to get the url + var http = EnsureHttpContext(); + var urlHelper = new UrlHelper(http.Request.RequestContext); + + var action = urlHelper.Action("ValidatePasswordResetCode", "BackOffice", + new + { + area = GlobalSettings.UmbracoMvcArea, + u = userId, + r = code + }); + + //TODO: Virtual path? + + return string.Format("{0}://{1}{2}", + http.Request.Url.Scheme, + http.Request.Url.Host + (http.Request.Url.Port == 80 ? string.Empty : ":" + http.Request.Url.Port), + action); + } + + /// + /// Processes a set password request. Validates the request and sets a new password. + /// + /// + [SetAngularAntiForgeryTokens] + public async Task PostSetPassword(SetPasswordModel model) + { + var result = await UserManager.ResetPasswordAsync(model.UserId, model.ResetCode, model.Password); + if (result.Succeeded) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + return Request.CreateValidationErrorResponse( + result.Errors.Any() ? result.Errors.First() : "Set password failed"); + } + + private HttpContextBase EnsureHttpContext() + { + var attempt = this.TryGetHttpContext(); + if (attempt.Success == false) + throw new InvalidOperationException("This method requires that an HttpContext be active"); + return attempt.Result; + } /// /// Logs the current user out diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index f4742e3e9f..59ccac2de8 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -4,6 +4,8 @@ using System.Configuration; using System.Globalization; using System.IO; using System.Linq; +using System.Net; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -26,6 +28,7 @@ using Umbraco.Core.Manifest; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; +using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.PropertyEditors; @@ -34,6 +37,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi.Filters; using Umbraco.Web.WebServices; +using Umbraco.Core.Services; using Action = Umbraco.Web._Legacy.Actions.Action; using Constants = Umbraco.Core.Constants; @@ -54,6 +58,10 @@ namespace Umbraco.Web.Editors private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; + private const string TokenExternalSignInError = "ExternalSignInError"; + private const string TokenPasswordResetCode = "PasswordResetCode"; + private static readonly string[] TempDataTokenNames = { TokenExternalSignInError, TokenPasswordResetCode }; + protected BackOfficeSignInManager SignInManager { get { return _signInManager ?? (_signInManager = OwinContext.Get()); } @@ -364,6 +372,7 @@ namespace Umbraco.Web.Editors }, {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, + {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, } }, { @@ -433,7 +442,25 @@ namespace Umbraco.Web.Editors User.Identity.GetUserId()); } + [HttpGet] + public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) + { + var user = UserManager.FindById(userId); + if (user != null) + { + var result = await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", resetCode, UserManager, user); + if (result) + { + //Add a flag and redirect for it to be displayed + TempData[TokenPasswordResetCode] = new ValidatePasswordResetCodeModel {UserId = userId, ResetCode = resetCode}; + return RedirectToLocal(Url.Action("Default", "BackOffice")); + } + } + //Add error and redirect for it to be displayed + TempData[TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") }; + return RedirectToLocal(Url.Action("Default", "BackOffice")); + } [HttpGet] public async Task ExternalLinkLoginCallback() @@ -445,7 +472,7 @@ namespace Umbraco.Web.Editors if (loginInfo == null) { //Add error and redirect for it to be displayed - TempData["ExternalSignInError"] = new[] { "An error occurred, could not get external login info" }; + TempData[TokenExternalSignInError] = new[] { "An error occurred, could not get external login info" }; return RedirectToLocal(Url.Action("Default", "BackOffice")); } @@ -456,7 +483,7 @@ namespace Umbraco.Web.Editors } //Add errors and redirect for it to be displayed - TempData["ExternalSignInError"] = result.Errors; + TempData[TokenExternalSignInError] = result.Errors; return RedirectToLocal(Url.Action("Default", "BackOffice")); } @@ -468,22 +495,27 @@ namespace Umbraco.Web.Editors } /// - /// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info, otherwise - /// process the external login info. + /// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info, + /// otherwise process the external login info. /// - /// - private async Task RenderDefaultOrProcessExternalLoginAsync(Func defaultResponse, Func externalSignInResponse) + /// + private async Task RenderDefaultOrProcessExternalLoginAsync( + Func defaultResponse, + Func externalSignInResponse) { if (defaultResponse == null) throw new ArgumentNullException("defaultResponse"); if (externalSignInResponse == null) throw new ArgumentNullException("externalSignInResponse"); ViewBag.UmbracoPath = GlobalSettings.UmbracoMvcArea; - //check if there's errors in the TempData, assign to view bag and render the view - if (TempData["ExternalSignInError"] != null) - { - ViewBag.ExternalSignInError = TempData["ExternalSignInError"]; - return defaultResponse(); + //check if there is the TempData with the any token name specified, if so, assign to view bag and render the view + foreach (var tempDataTokenName in TempDataTokenNames) + { + if (TempData[tempDataTokenName] != null) + { + ViewData[tempDataTokenName] = TempData[tempDataTokenName]; + return defaultResponse(); + } } //First check if there's external login info, if there's not proceed as normal @@ -521,7 +553,7 @@ namespace Umbraco.Web.Editors { if (await AutoLinkAndSignInExternalAccount(loginInfo) == false) { - ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" }; + ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" }; } //Remove the cookie otherwise this message will keep appearing @@ -556,7 +588,7 @@ namespace Umbraco.Web.Editors //we are allowing auto-linking/creating of local accounts if (loginInfo.Email.IsNullOrWhiteSpace()) { - ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." }; + ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." }; } else { @@ -565,7 +597,7 @@ namespace Umbraco.Web.Editors var foundByEmail = Services.UserService.GetByEmail(loginInfo.Email); if (foundByEmail != null) { - ViewBag.ExternalSignInError = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider }; + ViewData[TokenExternalSignInError] = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider }; } else { @@ -573,7 +605,7 @@ namespace Umbraco.Web.Editors var userType = Services.UserService.GetUserTypeByAlias(defaultUserType); if (userType == null) { - ViewBag.ExternalSignInError = new[] { "Could not auto-link this account, the specified User Type does not exist: " + defaultUserType }; + ViewData[TokenExternalSignInError] = new[] { "Could not auto-link this account, the specified User Type does not exist: " + defaultUserType }; } else { @@ -601,21 +633,21 @@ namespace Umbraco.Web.Editors if (userCreationResult.Succeeded == false) { - ViewBag.ExternalSignInError = userCreationResult.Errors; + ViewData[TokenExternalSignInError] = userCreationResult.Errors; } else { var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login); if (linkResult.Succeeded == false) { - ViewBag.ExternalSignInError = linkResult.Errors; + ViewData[TokenExternalSignInError] = linkResult.Errors; //If this fails, we should really delete the user since it will be in an inconsistent state! var deleteResult = await UserManager.DeleteAsync(autoLinkUser); if (deleteResult.Succeeded == false) { //DOH! ... this isn't good, combine all errors to be shown - ViewBag.ExternalSignInError = linkResult.Errors.Concat(deleteResult.Errors); + ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors); } } else diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index aaf99a0519..d1604f6a8e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -47,13 +47,13 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] public class ContentController : ContentControllerBase - { + { /// /// Constructor /// public ContentController() - : this(UmbracoContext.Current) - { + : this(UmbracoContext.Current) + { } /// @@ -61,8 +61,8 @@ namespace Umbraco.Web.Editors /// /// public ContentController(UmbracoContext umbracoContext) - : base(umbracoContext) - { + : base(umbracoContext) + { } /// @@ -109,15 +109,15 @@ namespace Umbraco.Web.Editors [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetById(int id) { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) { HandleContentNotFound(id); } - + var content = Mapper.Map(foundContent); return content; - } + } [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetWithTreeDefinition(int id) @@ -155,7 +155,7 @@ namespace Umbraco.Web.Editors //remove this tab if it exists: umbContainerView var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] {containerTab}); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); return mapped; } @@ -168,7 +168,7 @@ namespace Umbraco.Web.Editors { var url = Umbraco.NiceUrl(id); var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); return response; } @@ -178,18 +178,21 @@ namespace Umbraco.Web.Editors /// [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren( - int id, - int pageNumber = 0, //TODO: This should be '1' as it's not the index - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { + int id, + int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { long totalChildren; IContent[] children; if (pageNumber > 0 && pageSize > 0) { - children = Services.ContentService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); + children = Services.ContentService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); } else { @@ -204,7 +207,7 @@ namespace Umbraco.Web.Editors var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); pagedResult.Items = children - .Select(Mapper.Map>); + .Select(Mapper.Map>); return pagedResult; } @@ -212,7 +215,7 @@ namespace Umbraco.Web.Editors [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] public bool GetHasPermission(string permissionToCheck, int nodeId) { - return HasPermission(permissionToCheck, nodeId); + return HasPermission(permissionToCheck, nodeId); } /// @@ -224,8 +227,8 @@ namespace Umbraco.Web.Editors public Dictionary GetPermissions(int[] nodeIds) { return Services.UserService - .GetPermissions(Security.CurrentUser, nodeIds) - .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); + .GetPermissions(Security.CurrentUser, nodeIds) + .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); } [HttpGet] @@ -239,7 +242,7 @@ namespace Umbraco.Web.Editors return false; } - + /// /// Saves content /// @@ -247,16 +250,16 @@ namespace Umbraco.Web.Editors [FileUploadCleanupFilter] [ContentPostValidate] public ContentItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) - { + [ModelBinder(typeof(ContentItemBinder))] + ContentItemSave contentItem) + { //If we've reached here it means: // * Our model has been bound // * and validated // * any file attachments have been saved to their temporary location for us to use // * we have a reference to the DTO object and the persisted object // * Permissions are valid - + MapPropertyValues(contentItem); //We need to manually check the validation results here because: @@ -274,7 +277,7 @@ namespace Umbraco.Web.Editors var forDisplay = Mapper.Map(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - + } //if the model state is not valid we cannot publish so change it to save @@ -291,7 +294,7 @@ namespace Umbraco.Web.Editors //initialize this to successful var publishStatus = Attempt.Succeed(); - var wasCancelled = false; + var wasCancelled = false; if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) { @@ -326,8 +329,8 @@ namespace Umbraco.Web.Editors if (wasCancelled == false) { display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - Services.TextService.Localize("speechBubbles/editContentSavedText")); + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editContentSavedText")); } else { @@ -339,8 +342,8 @@ namespace Umbraco.Web.Editors if (wasCancelled == false) { display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); } else { @@ -365,7 +368,7 @@ namespace Umbraco.Web.Editors return display; } - + /// /// Publishes a document with a given ID /// @@ -391,7 +394,7 @@ namespace Umbraco.Web.Editors { var notificationModel = new SimpleNotificationModel(); ShowMessageForPublishStatus(publishResult.Result, notificationModel); - return Request.CreateValidationErrorResponse(notificationModel); + return Request.CreateValidationErrorResponse(notificationModel); } //return ok @@ -439,7 +442,7 @@ namespace Umbraco.Web.Editors //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } + } } return Request.CreateResponse(HttpStatusCode.OK); @@ -456,7 +459,7 @@ namespace Umbraco.Web.Editors [HttpPost] [EnsureUserPermissionForContent(Constants.System.RecycleBinContent)] public HttpResponseMessage EmptyRecycleBin() - { + { Services.ContentService.EmptyRecycleBin(); return Request.CreateResponse(HttpStatusCode.OK); } @@ -515,7 +518,7 @@ namespace Umbraco.Web.Editors var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; + return response; } /// @@ -547,7 +550,7 @@ namespace Umbraco.Web.Editors if (foundContent == null) HandleContentNotFound(id); - + var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); var content = Mapper.Map(foundContent); @@ -558,7 +561,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); } else - { + { content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); return content; } @@ -596,8 +599,8 @@ namespace Umbraco.Web.Editors contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; //only set the template if it didn't change var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) - || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); if (templateChanged) { var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); @@ -640,8 +643,8 @@ namespace Umbraco.Web.Editors if (toMove.ContentType.AllowedAsRoot == false) { throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); } } else @@ -654,19 +657,19 @@ namespace Umbraco.Web.Editors //check if the item is allowed under this one if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) + .Any(x => x.Value == toMove.ContentType.Id) == false) { throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); } // Check on paths if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { + { throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); } } @@ -680,52 +683,52 @@ namespace Umbraco.Web.Editors case PublishStatusType.Success: case PublishStatusType.SuccessAlreadyPublished: display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), - Services.TextService.Localize("speechBubbles/editContentPublishedText")); + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + Services.TextService.Localize("speechBubbles/editContentPublishedText")); break; case PublishStatusType.FailedPathNotPublished: display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedByParent", - new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedByParent", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); break; case PublishStatusType.FailedCancelledByEvent: AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); - break; + break; case PublishStatusType.FailedAwaitingRelease: display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", - new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); break; case PublishStatusType.FailedHasExpired: display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedExpired", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - }).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedExpired", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + }).Trim()); break; case PublishStatusType.FailedIsTrashed: //TODO: We should add proper error messaging for this! break; case PublishStatusType.FailedContentInvalid: display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - string.Join(",", status.InvalidProperties.Select(x => x.Alias)) - }).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedInvalid", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Join(",", status.InvalidProperties.Select(x => x.Alias)) + }).Trim()); break; default: throw new IndexOutOfRangeException(); } } - + /// /// Performs a permissions check for the user to check if it has access to the node based on @@ -740,15 +743,15 @@ namespace Umbraco.Web.Editors /// Specifies the already resolved content item to check against /// internal static bool CheckPermissions( - IDictionary storage, - IUser user, - IUserService userService, - IContentService contentService, - int nodeId, - char[] permissionsToCheck = null, - IContent contentItem = null) + IDictionary storage, + IUser user, + IUserService userService, + IContentService contentService, + int nodeId, + char[] permissionsToCheck = null, + IContent contentItem = null) { - + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) { contentItem = contentService.GetById(nodeId); @@ -763,16 +766,16 @@ namespace Umbraco.Web.Editors } var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : (nodeId == Constants.System.RecycleBinContent) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinContent.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : user.HasPathAccess(contentItem); + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartContentId, + Constants.System.RecycleBinContent) + : (nodeId == Constants.System.RecycleBinContent) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinContent.ToInvariantString(), + user.StartContentId, + Constants.System.RecycleBinContent) + : user.HasPathAccess(contentItem); if (hasPathAccess == false) { diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index bf0526ff32..dbdd9bea40 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -51,7 +51,7 @@ namespace Umbraco.Web.Editors /// public MediaController() : this(UmbracoContext.Current) - { + { } /// @@ -155,7 +155,7 @@ namespace Umbraco.Web.Editors var folderTypes = Services.ContentTypeService.GetAllMediaTypes().ToArray().Where(x => x.Alias.EndsWith("Folder")).Select(x => x.Id); var children = (id < 0) ? Services.MediaService.GetRootMedia() : Services.MediaService.GetById(id).Children(); - return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); + return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); } /// @@ -179,13 +179,16 @@ namespace Umbraco.Web.Editors int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, string filter = "") { - int totalChildren; + long totalChildren; IMedia[] children; if (pageNumber > 0 && pageSize > 0) { - children = Services.MediaService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); + children = Services.MediaService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); } else { @@ -260,7 +263,7 @@ namespace Umbraco.Web.Editors var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; + return response; } /// @@ -306,7 +309,7 @@ namespace Umbraco.Web.Editors //return the updated model var display = Mapper.Map(contentItem.PersistedContent); - + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); @@ -333,8 +336,8 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); } } - - break; + + break; } return display; @@ -363,7 +366,7 @@ namespace Umbraco.Web.Editors Services.MediaService.EmptyRecycleBin(); return Request.CreateResponse(HttpStatusCode.OK); } - + /// /// Change the sort order for media /// @@ -382,7 +385,7 @@ namespace Umbraco.Web.Editors { return Request.CreateResponse(HttpStatusCode.OK); } - + var mediaService = base.ApplicationContext.Services.MediaService; var sortedMedia = new List(); try @@ -404,7 +407,7 @@ namespace Umbraco.Web.Editors } } - [EnsureUserPermissionForMedia("folder.ParentId")] + [EnsureUserPermissionForMedia("folder.ParentId")] public MediaItemDisplay PostAddFolder(EntityBasic folder) { var mediaService = ApplicationContext.Services.MediaService; @@ -435,7 +438,7 @@ namespace Umbraco.Web.Editors var provider = new MultipartFormDataStreamProvider(root); var result = await Request.Content.ReadAsMultipartAsync(provider); - + //must have a file if (result.FileData.Count == 0) { @@ -448,10 +451,10 @@ namespace Umbraco.Web.Editors { return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); } - + //ensure the user has access to this folder by parent id! if (CheckPermissions( - new Dictionary(), + new Dictionary(), Security.CurrentUser, Services.MediaService, parentId) == false) { @@ -462,65 +465,65 @@ namespace Umbraco.Web.Editors Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), SpeechBubbleIcon.Warning))); } - + var tempFiles = new PostedFiles(); var mediaService = ApplicationContext.Services.MediaService; //in case we pass a path with a folder in it, we will create it and upload media to it. - if (result.FormData.ContainsKey("path")) - { + if (result.FormData.ContainsKey("path")) + { - var folders = result.FormData["path"].Split('/'); + var folders = result.FormData["path"].Split('/'); - for (int i = 0; i < folders.Length - 1; i++) - { - var folderName = folders[i]; - IMedia folderMediaItem; + for (int i = 0; i < folders.Length - 1; i++) + { + var folderName = folders[i]; + IMedia folderMediaItem; - //if uploading directly to media root and not a subfolder - if (parentId == -1) - { - //look for matching folder - folderMediaItem = - mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - else - { - //get current parent - var mediaRoot = mediaService.GetById(parentId); + //if uploading directly to media root and not a subfolder + if (parentId == -1) + { + //look for matching folder + folderMediaItem = + mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + else + { + //get current parent + var mediaRoot = mediaService.GetById(parentId); - //if the media root is null, something went wrong, we'll abort - if (mediaRoot == null) - return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, - "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + - " returned null"); + //if the media root is null, something went wrong, we'll abort + if (mediaRoot == null) + return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, + "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + + " returned null"); - //look for matching folder - folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - //set the media root to the folder id so uploaded files will end there. - parentId = folderMediaItem.Id; - } - } + //look for matching folder + folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + //set the media root to the folder id so uploaded files will end there. + parentId = folderMediaItem.Id; + } + } - //get the files + //get the files foreach (var file in result.FileData) { var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); - var ext = fileName.Substring(fileName.LastIndexOf('.')+1).ToLower(); + var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) { @@ -582,7 +585,7 @@ namespace Umbraco.Web.Editors if (origin.Value == "blueimp") { return Request.CreateResponse(HttpStatusCode.OK, - tempFiles, + tempFiles, //Don't output the angular xsrf stuff, blue imp doesn't like that new JsonMediaTypeFormatter()); } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 377647d3ba..9d9093486f 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -73,15 +73,16 @@ namespace Umbraco.Web.Editors get { return Services.MemberService.GetMembershipScenario(); } } - public PagedResult GetPagedResults( + public PagedResult GetPagedResults( int pageNumber = 1, int pageSize = 100, string orderBy = "Name", Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, string filter = "", string memberTypeAlias = null) { - + if (pageNumber <= 0 || pageSize <= 0) { throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); @@ -90,7 +91,9 @@ namespace Umbraco.Web.Editors if (MembershipScenario == MembershipScenario.NativeUmbraco) { long totalRecords; - var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, memberTypeAlias, filter).ToArray(); + var members = Services.MemberService + .GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField + , memberTypeAlias, filter).ToArray(); if (totalRecords == 0) { return new PagedResult(0, 0, 0); @@ -131,7 +134,7 @@ namespace Umbraco.Web.Editors .Select(Mapper.Map); return pagedResult; } - + } /// @@ -179,23 +182,23 @@ namespace Umbraco.Web.Editors return Mapper.Map(foundMember); case MembershipScenario.CustomProviderWithUmbracoLink: - //TODO: Support editing custom properties for members with a custom membership provider here. + //TODO: Support editing custom properties for members with a custom membership provider here. - //foundMember = Services.MemberService.GetByKey(key); - //if (foundMember == null) - //{ - // HandleContentNotFound(key); - //} - //foundMembershipMember = Membership.GetUser(key, false); - //if (foundMembershipMember == null) - //{ - // HandleContentNotFound(key); - //} + //foundMember = Services.MemberService.GetByKey(key); + //if (foundMember == null) + //{ + // HandleContentNotFound(key); + //} + //foundMembershipMember = Membership.GetUser(key, false); + //if (foundMembershipMember == null) + //{ + // HandleContentNotFound(key); + //} - //display = Mapper.Map(foundMembershipMember); - ////map the name over - //display.Name = foundMember.Name; - //return display; + //display = Mapper.Map(foundMembershipMember); + ////map the name over + //display.Name = foundMember.Name; + //return display; case MembershipScenario.StandaloneCustomProvider: default: @@ -236,7 +239,7 @@ namespace Umbraco.Web.Editors emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); return Mapper.Map(emptyContent); case MembershipScenario.CustomProviderWithUmbracoLink: - //TODO: Support editing custom properties for members with a custom membership provider here. + //TODO: Support editing custom properties for members with a custom membership provider here. case MembershipScenario.StandaloneCustomProvider: default: @@ -292,7 +295,7 @@ namespace Umbraco.Web.Editors { throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); } - + //We're gonna look up the current roles now because the below code can cause // events to be raised and developers could be manually adding roles to members in // their handlers. If we don't look this up now there's a chance we'll just end up @@ -341,7 +344,7 @@ namespace Umbraco.Web.Editors //create/save the IMember Services.MemberService.Save(contentItem.PersistedContent); } - + //Now let's do the role provider stuff - now that we've saved the content item (that is important since // if we are changing the username, it must be persisted before looking up the member roles). if (rolesToRemove.Any()) @@ -521,7 +524,7 @@ namespace Umbraco.Web.Editors private void RefetchMemberData(MemberSave contentItem, LookupType lookup) { var currProps = contentItem.PersistedContent.Properties.ToArray(); - + switch (MembershipScenario) { case MembershipScenario.NativeUmbraco: @@ -529,11 +532,11 @@ namespace Umbraco.Web.Editors { case LookupType.ByKey: //Go and re-fetch the persisted item - contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); + contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); break; case LookupType.ByUserName: contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim()); - break; + break; } break; case MembershipScenario.CustomProviderWithUmbracoLink: @@ -541,14 +544,14 @@ namespace Umbraco.Web.Editors default: var membershipUser = _provider.GetUser(contentItem.Key, false); //Go and re-fetch the persisted item - contentItem.PersistedContent = Mapper.Map(membershipUser); + contentItem.PersistedContent = Mapper.Map(membershipUser); break; } UpdateName(contentItem); //re-assign the mapped values that are not part of the membership provider properties. - var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); + var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); foreach (var p in contentItem.PersistedContent.Properties) { var valueMapped = currProps.SingleOrDefault(x => x.Alias == p.Alias); @@ -559,7 +562,7 @@ namespace Umbraco.Web.Editors p.TagSupport.Enable = valueMapped.TagSupport.Enable; p.TagSupport.Tags = valueMapped.TagSupport.Tags; } - } + } } /// @@ -612,7 +615,7 @@ namespace Umbraco.Web.Editors contentItem.IsApproved, Guid.NewGuid(), //since it's the umbraco provider, the user key here doesn't make any difference out status); - + break; case MembershipScenario.CustomProviderWithUmbracoLink: //We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use @@ -635,7 +638,7 @@ namespace Umbraco.Web.Editors case MembershipScenario.StandaloneCustomProvider: // we don't have a member type to use so we will just create the basic membership user with the provider with no // link back to the umbraco data - + var newKey = Guid.NewGuid(); //TODO: We are not supporting q/a - passing in empty here membershipUser = _provider.CreateUser( @@ -645,9 +648,9 @@ namespace Umbraco.Web.Editors "TEMP", //some membership provider's require something here even if q/a is disabled! "TEMP", //some membership provider's require something here even if q/a is disabled! contentItem.IsApproved, - newKey, + newKey, out status); - + break; default: throw new ArgumentOutOfRangeException(); @@ -702,12 +705,12 @@ namespace Umbraco.Web.Editors break; case MembershipCreateStatus.InvalidProviderUserKey: ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property + //specify 'default' just so that it shows up as a notification - is not assigned to a property new ValidationResult("Invalid provider user key"), "default"); break; case MembershipCreateStatus.DuplicateProviderUserKey: ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property + //specify 'default' just so that it shows up as a notification - is not assigned to a property new ValidationResult("Duplicate provider user key"), "default"); break; case MembershipCreateStatus.ProviderError: diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs index 3f38ae35ec..71fc3be8f1 100644 --- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs @@ -9,9 +9,12 @@ using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Web.Editors; +using Umbraco.Web.Models; namespace Umbraco.Web { + using Umbraco.Core.Configuration; + /// /// HtmlHelper extensions for the back office /// @@ -43,6 +46,9 @@ namespace Umbraco.Web ""serverVarsJs"": """ + uri.GetUrlWithCacheBust("ServerVariables", "BackOffice") + @""", ""externalLoginsUrl"": """ + externalLoginsUrl + @""" }, + ""umbracoSettings"": { + ""allowPasswordReset"": " + (UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset ? "true" : "false") + @" + }, ""application"": { ""applicationPath"": """ + html.ViewContext.HttpContext.Request.ApplicationPath + @""", ""version"": """ + version + @""", @@ -56,12 +62,12 @@ namespace Umbraco.Web } /// - /// Used to render the script that will pass in the angular externalLoginInfo service on page load + /// Used to render the script that will pass in the angular "externalLoginInfo" service/value on page load /// /// /// /// - public static IHtmlString AngularExternalLoginInfoValuesScript(this HtmlHelper html, IEnumerable externalLoginErrors) + public static IHtmlString AngularValueExternalLoginInfoScript(this HtmlHelper html, IEnumerable externalLoginErrors) { var loginProviders = html.ViewContext.HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes() .Where(p => p.Properties.ContainsKey("UmbracoBackOffice")) @@ -93,5 +99,38 @@ namespace Umbraco.Web return html.Raw(sb.ToString()); } + + /// + /// Used to render the script that will pass in the angular "resetPasswordCodeInfo" service/value on page load + /// + /// + /// + /// + public static IHtmlString AngularValueResetPasswordCodeInfoScript(this HtmlHelper html, object val) + { + var sb = new StringBuilder(); + sb.AppendLine(); + sb.AppendLine(@"var errors = [];"); + + var errors = val as IEnumerable; + if (errors != null) + { + foreach (var error in errors) + { + sb.AppendFormat(@"errors.push(""{0}"");", error).AppendLine(); + } + } + + var resetCodeModel = val as ValidatePasswordResetCodeModel; + + + sb.AppendLine(@"app.value(""resetPasswordCodeInfo"", {"); + sb.AppendLine(@"errors: errors,"); + sb.Append(@"resetCodeModel: "); + sb.AppendLine(JsonConvert.SerializeObject(resetCodeModel)); + sb.AppendLine(@"});"); + + return html.Raw(sb.ToString()); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/RequestPasswordResetModel.cs b/src/Umbraco.Web/Models/RequestPasswordResetModel.cs new file mode 100644 index 0000000000..0ea173bfd6 --- /dev/null +++ b/src/Umbraco.Web/Models/RequestPasswordResetModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + + [DataContract(Name = "requestPasswordReset", Namespace = "")] + public class RequestPasswordResetModel + { + [Required] + [DataMember(Name = "email", IsRequired = true)] + public string Email { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/SetPasswordModel.cs b/src/Umbraco.Web/Models/SetPasswordModel.cs new file mode 100644 index 0000000000..02d0e4f901 --- /dev/null +++ b/src/Umbraco.Web/Models/SetPasswordModel.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + [DataContract(Name = "setPassword", Namespace = "")] + public class SetPasswordModel + { + [Required] + [DataMember(Name = "userId", IsRequired = true)] + public int UserId { get; set; } + + [Required] + [DataMember(Name = "password", IsRequired = true)] + public string Password { get; set; } + + [Required] + [DataMember(Name = "resetCode", IsRequired = true)] + public string ResetCode { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs new file mode 100644 index 0000000000..cba92eeff7 --- /dev/null +++ b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + [DataContract(Name = "validatePasswordReset", Namespace = "")] + public class ValidatePasswordResetCodeModel + { + [Required] + [DataMember(Name = "userId", IsRequired = true)] + public int UserId { get; set; } + + [Required] + [DataMember(Name = "resetCode", IsRequired = true)] + public string ResetCode { get; set; } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs index 2cebd665cd..e1b72f0ad4 100644 --- a/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs @@ -1,6 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -15,11 +16,14 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.CheckBoxListAlias, "Checkbox list", "checkboxlist", Icon="icon-bulleted-list", Group="lists")] public class CheckBoxListPropertyEditor : PropertyEditor { + private readonly ILocalizedTextService _textService; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public CheckBoxListPropertyEditor(ILogger logger) : base(logger) + public CheckBoxListPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger) { + _textService = textService; } /// @@ -31,7 +35,7 @@ namespace Umbraco.Web.PropertyEditors /// protected override PreValueEditor CreatePreValueEditor() { - return new ValueListPreValueEditor(); + return new ValueListPreValueEditor(_textService); } /// diff --git a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs index e7750c4c65..e9e721c108 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs @@ -6,13 +6,14 @@ using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { internal class ColorListPreValueEditor : ValueListPreValueEditor { - public ColorListPreValueEditor() + public ColorListPreValueEditor(ILocalizedTextService textService) : base(textService) { var field = Fields.First(); diff --git a/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs index 95d2c5f07e..3d2beb64fb 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs @@ -1,17 +1,21 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Constants.PropertyEditors.ColorPickerAlias, "Color Picker", "colorpicker", Icon="icon-colorpicker", Group="Pickers")] public class ColorPickerPropertyEditor : PropertyEditor { + private readonly ILocalizedTextService _textService; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public ColorPickerPropertyEditor(ILogger logger) : base(logger) + public ColorPickerPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger) { + _textService = textService; } /// @@ -23,7 +27,7 @@ namespace Umbraco.Web.PropertyEditors /// protected override PreValueEditor CreatePreValueEditor() { - return new ColorListPreValueEditor(); + return new ColorListPreValueEditor(_textService); } } diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs index e7cf1bb87b..2728f5c86b 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -21,7 +22,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The constructor will setup the property editor based on the attribute if one is found /// - public DropDownMultiplePropertyEditor(ILogger logger) : base(logger) + public DropDownMultiplePropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger, textService) { } diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs index 9540c4fdc5..f16d9872ec 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs @@ -3,6 +3,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -16,11 +17,14 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list")] public class DropDownMultipleWithKeysPropertyEditor : DropDownPropertyEditor { + private readonly ILocalizedTextService _textService; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public DropDownMultipleWithKeysPropertyEditor(ILogger logger) : base(logger) + public DropDownMultipleWithKeysPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger, textService) { + _textService = textService; } protected override PropertyValueEditor CreateValueEditor() @@ -30,7 +34,7 @@ namespace Umbraco.Web.PropertyEditors protected override PreValueEditor CreatePreValueEditor() { - return new DropDownMultiplePreValueEditor(); + return new DropDownMultiplePreValueEditor(_textService); } /// @@ -42,7 +46,7 @@ namespace Umbraco.Web.PropertyEditors /// internal class DropDownMultiplePreValueEditor : ValueListPreValueEditor { - public DropDownMultiplePreValueEditor() + public DropDownMultiplePreValueEditor(ILocalizedTextService textService) : base(textService) { //add the multiple field, we'll make it hidden so it is not seen in the pre-value editor Fields.Add(new PreValueField diff --git a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs index 3607a8b5fa..4981e88412 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; using umbraco; using ClientDependency.Core; +using Umbraco.Core.Services; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.PropertyEditors @@ -23,7 +24,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The constructor will setup the property editor based on the attribute if one is found /// - public DropDownPropertyEditor(ILogger logger) : base(logger) + public DropDownPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger, textService) { } diff --git a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs index b4138d170a..2192af8d4f 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs @@ -1,6 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -15,11 +16,14 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = "INT", Group = "lists", Icon = "icon-indent")] public class DropDownWithKeysPropertyEditor : PropertyEditor { + private readonly ILocalizedTextService _textService; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public DropDownWithKeysPropertyEditor(ILogger logger) : base(logger) + public DropDownWithKeysPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger) { + _textService = textService; } /// @@ -28,7 +32,7 @@ namespace Umbraco.Web.PropertyEditors /// protected override PreValueEditor CreatePreValueEditor() { - return new ValueListPreValueEditor(); + return new ValueListPreValueEditor(_textService); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 9b4e722b30..809716af62 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -24,14 +24,17 @@ namespace Umbraco.Web.PropertyEditors { private readonly MediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSettings; + private readonly ILocalizedTextService _textService; - public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings) + public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings, ILocalizedTextService textService) : base(logger) { if (mediaFileSystem == null) throw new ArgumentNullException("mediaFileSystem"); if (contentSettings == null) throw new ArgumentNullException("contentSettings"); + if (textService == null) throw new ArgumentNullException("textService"); _mediaFileSystem = mediaFileSystem; _contentSettings = contentSettings; + _textService = textService; MemberService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); } @@ -49,7 +52,7 @@ namespace Umbraco.Web.PropertyEditors protected override PreValueEditor CreatePreValueEditor() { - return new FileUploadPreValueEditor(); + return new FileUploadPreValueEditor(_textService); } /// @@ -172,8 +175,8 @@ namespace Umbraco.Web.PropertyEditors /// internal class FileUploadPreValueEditor : ValueListPreValueEditor { - public FileUploadPreValueEditor() - : base() + public FileUploadPreValueEditor(ILocalizedTextService textService) + : base(textService) { var field = Fields.First(); field.Description = "Enter a max width/height for each thumbnail"; diff --git a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs index 24c3a275fb..8ac6bff69e 100644 --- a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs @@ -1,6 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -18,7 +19,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The constructor will setup the property editor based on the attribute if one is found /// - public RadioButtonsPropertyEditor(ILogger logger) : base(logger) + public RadioButtonsPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger, textService) { } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs index 41c45efb98..e11a969bfe 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs @@ -22,9 +22,11 @@ namespace Umbraco.Web.PropertyEditors /// internal class ValueListPreValueEditor : PreValueEditor { + private readonly ILocalizedTextService _textService; - public ValueListPreValueEditor() + public ValueListPreValueEditor(ILocalizedTextService textService) { + _textService = textService; Fields.AddRange(CreatePreValueFields()); } @@ -46,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors //It's also important to note that by default the dropdown angular controller is expecting the // config options to come in with a property called 'items' Key = "items", - Name = ApplicationContext.Current.Services.TextService.Localize("editdatatype/addPrevalue"), + Name = _textService.Localize("editdatatype/addPrevalue"), // todo: inject View = "multivalues" } }; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 197175366e..578aab2755 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1201,6 +1201,7 @@ namespace Umbraco.Web /// Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc). internal static IEnumerable EnumerateAncestors(this IPublishedContent content, bool orSelf) { + if (content == null) throw new ArgumentNullException("content"); if (orSelf) yield return content; while ((content = content.Parent) != null) yield return content; @@ -1373,6 +1374,7 @@ namespace Umbraco.Web internal static IEnumerable EnumerateDescendants(this IPublishedContent content, bool orSelf) { + if (content == null) throw new ArgumentNullException("content"); if (orSelf) yield return content; foreach (var child in content.Children) @@ -1707,6 +1709,7 @@ namespace Umbraco.Web public static T Parent(this IPublishedContent content) where T : class, IPublishedContent { + if (content == null) throw new ArgumentNullException("content"); return content.Parent as T; } @@ -1724,9 +1727,10 @@ namespace Umbraco.Web /// This method exists for consistency, it is the same as calling content.Children as a property. /// public static IEnumerable Children(this IPublishedContent content) - { - return content.Children; - } + { + if (content == null) throw new ArgumentNullException("content"); + return content.Children; + } /// /// Gets the children of the content, filtered by a predicate. diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index 0557e56e57..380ae85401 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Scheduling // ensure we do not run if not main domain, but do NOT lock it if (_appContext.MainDom.IsMainDom == false) { - LogHelper.Debug("Does not run if not MainDom."); + LogHelper.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down } diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs index 24b706aee9..f40490c2d1 100644 --- a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Strategies.Migrations /// /// After upgrade we clear out the csrf tokens /// - public class ClearCsrfCookiesAfterUpgrade : MigrationStartupHander + public class ClearCsrfCookiesAfterUpgrade : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs index b3e1306429..e0e55e0405 100644 --- a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Strategies.Migrations /// /// * If current is less than or equal to 7.0.0 /// - public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : MigrationStartupHander + public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs index efffc8bfd0..92377d0a2f 100644 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs +++ b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Strategies.Migrations /// /// Creates the built in list view data types /// - public class EnsureDefaultListViewDataTypesCreated : MigrationStartupHander + public class EnsureDefaultListViewDataTypesCreated : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs b/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs similarity index 78% rename from src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs rename to src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs index a350911580..3b40bbdf5d 100644 --- a/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs +++ b/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Persistence.Migrations; @@ -7,8 +8,10 @@ namespace Umbraco.Web.Strategies.Migrations /// /// Base class that can be used to run code after the migration runner has executed /// - public abstract class MigrationStartupHander : ApplicationEventHandler + public abstract class MigrationStartupHandler : ApplicationEventHandler, IDisposable { + private bool _disposed; + /// /// Ensure this is run when not configured /// @@ -57,5 +60,23 @@ namespace Umbraco.Web.Strategies.Migrations /// Leaving empty will run for all migration products /// public virtual string[] TargetProductNames { get { return new string[] {}; } } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~MigrationStartupHandler() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + _disposed = true; + Unsubscribe(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs index 9a8b6a6044..d8e5ded98f 100644 --- a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs +++ b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Strategies.Migrations /// files during the migration since other parts of the migration might fail. So once the migration is complete, we'll then copy over the temp /// files that this migration created over top of the developer's files. We'll also create a backup of their files. /// - public sealed class OverwriteStylesheetFilesFromTempFiles : MigrationStartupHander + public sealed class OverwriteStylesheetFilesFromTempFiles : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs index 72f28c4628..66a412c1ff 100644 --- a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs +++ b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Strategies.Migrations /// This event ensures that upgrades from (configured) versions lower then 6.0.0 /// have their publish state updated after the database schema has been migrated. /// - public class PublishAfterUpgradeToVersionSixth : MigrationStartupHander + public class PublishAfterUpgradeToVersionSixth : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs index 2af3c402aa..63c8d5db16 100644 --- a/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Strategies.Migrations /// /// * If current is less than or equal to 7.0.0 /// - public class RebuildMediaXmlCacheAfterUpgrade : MigrationStartupHander + public class RebuildMediaXmlCacheAfterUpgrade : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e0400c5f4f..bd8a6b102d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -402,7 +402,10 @@ + + + @@ -698,7 +701,7 @@ - + @@ -1109,7 +1112,7 @@ ASPXCodeBehind - + diff --git a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs index 3deddde831..99e08a4597 100644 --- a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs @@ -14,14 +14,11 @@ namespace Umbraco.Web.WebApi.Filters //See: http://stackoverflow.com/questions/17755239/how-to-stop-chrome-from-caching-rest-response-from-webapi base.OnActionExecuted(actionExecutedContext); - - // happens if exception if (actionExecutedContext == null || actionExecutedContext.Response == null || actionExecutedContext.Response.Headers == null) { return; } - //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using // HttpContext.Current responses. I've changed this back to what it should be now since it works // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. @@ -37,8 +34,8 @@ namespace Umbraco.Web.WebApi.Filters if (actionExecutedContext.Response.Content != null) { actionExecutedContext.Response.Content.Headers.Expires = - //Mon, 01 Jan 1990 00:00:00 GMT - new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); + //Mon, 01 Jan 1990 00:00:00 GMT + new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); } } } diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index e4234587d5..3d3f505844 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -1399,7 +1399,7 @@ namespace umbraco //Trace out to profiling... doesn't actually profile, just for informational output. if (excludeProfiling == false) { - using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("{0}", message))) + using (ApplicationContext.Current.ProfilingLogger.DebugDuration(string.Format("{0}", message))) { } } diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 319ec304b3..d33683315c 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -20,7 +20,7 @@ using IMediaService = Umbraco.Core.Services.IMediaService; namespace UmbracoExamine { - + /// /// Custom indexer for members /// @@ -87,7 +87,7 @@ namespace UmbracoExamine if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) { - var field = new IndexField {Name = "_searchEmail"}; + var field = new IndexField { Name = "_searchEmail" }; var policy = IndexFieldPolicies.FirstOrDefault(x => x.Name == "_searchEmail"); if (policy != null) { @@ -97,7 +97,7 @@ namespace UmbracoExamine return new IndexCriteria( indexerData.StandardFields, - indexerData.UserFields.Concat(new[] {field}), + indexerData.UserFields.Concat(new[] { field }), indexerData.IncludeNodeTypes, indexerData.ExcludeNodeTypes, indexerData.ParentNodeId @@ -105,7 +105,7 @@ namespace UmbracoExamine } } - return indexerData; + return indexerData; } /// @@ -128,7 +128,7 @@ namespace UmbracoExamine //This only supports members if (SupportedTypes.Contains(type) == false) return; - + const int pageSize = 1000; var pageIndex = 0; @@ -142,7 +142,7 @@ namespace UmbracoExamine do { long total; - members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType).ToArray(); + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, null, nodeType).ToArray(); AddNodesToIndex(GetSerializedMembers(members), type); @@ -162,8 +162,8 @@ namespace UmbracoExamine pageIndex++; } while (members.Length == pageSize); - } - } + } + } private IEnumerable GetSerializedMembers(IEnumerable members) { @@ -207,7 +207,7 @@ namespace UmbracoExamine if (e.Fields.ContainsKey("_searchEmail") == false) e.Fields.Add("_searchEmail", e.Node.Attribute("email").Value.Replace(".", " ").Replace("@", " ")); } - + if (e.Fields.ContainsKey(IconFieldName) == false) e.Fields.Add(IconFieldName, (string)e.Node.Attribute("icon")); }