Merge branch temp8 into temp8-di

This commit is contained in:
Stephan
2018-07-18 10:46:42 +02:00
74 changed files with 2791 additions and 1733 deletions
+1 -1
View File
@@ -20,7 +20,7 @@
the latter would pick anything below 3.0.0 and that includes prereleases such as 3.0.0-alpha, and we do
not want this to happen as the alpha of the next major is, really, the next major already.
-->
<dependency id="AutoMapper" version="[7.0.0,7.999999)" />
<dependency id="AutoMapper" version="[7.0.1,7.999999)" />
<dependency id="ClientDependency" version="[1.9.6,1.999999)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0,1.999999)" />
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
@@ -7,7 +7,7 @@ namespace Umbraco.Core.Composing.CompositionRoots
{
public void Compose(IServiceRegistry container)
{
container.Register<IdentityProfile>();
container.Register<IdentityMapperProfile>();
}
}
}
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class ContentElement : UmbracoConfigurationElement, IContentSection
{
private const string DefaultPreviewBadge = @"<a id=""umbracoPreviewBadge"" style=""position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;"" href=""{0}/endPreview.aspx?redir={1}""><span style=""display:none;"">In Preview Mode - click to end</span></a>";
private const string DefaultPreviewBadge = @"<a id=""umbracoPreviewBadge"" style=""z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;"" href=""#"" OnClick=""javascript:window.top.location.href = '{0}/endPreview.aspx?redir={1}'""><span style=""display:none;"">In Preview Mode - click to end</span></a>";
[ConfigurationProperty("imaging")]
internal ContentImagingElement Imaging => (ContentImagingElement) this["imaging"];
@@ -61,8 +61,28 @@ namespace Umbraco.Core.Models
{
get
{
var groups = ContentTypeComposition.SelectMany(x => x.CompositionPropertyGroups).Union(PropertyGroups);
return groups;
// we need to "acquire" composition groups and properties here, ie get our own clones,
// so that we can change their variation according to this content type variations.
//
// it would be nice to cache the resulting enumerable, but alas we cannot, otherwise
// any change to compositions are ignored and that breaks many things - and tracking
// changes to refresh the cache would be expensive.
void AcquireProperty(PropertyType propertyType)
{
propertyType.Variations = propertyType.Variations & Variations;
propertyType.ResetDirtyProperties(false);
}
return ContentTypeComposition.SelectMany(x => x.CompositionPropertyGroups)
.Select(group =>
{
group = (PropertyGroup) group.DeepClone();
foreach (var property in group.PropertyTypes)
AcquireProperty(property);
return group;
})
.Union(PropertyGroups);
}
}
@@ -74,8 +94,23 @@ namespace Umbraco.Core.Models
{
get
{
var propertyTypes = ContentTypeComposition.SelectMany(x => x.CompositionPropertyTypes).Union(PropertyTypes);
return propertyTypes;
// we need to "acquire" composition properties here, ie get our own clones,
// so that we can change their variation according to this content type variations.
//
// see note in CompositionPropertyGroups for comments on caching the resulting enumerable
PropertyType AcquireProperty(PropertyType propertyType)
{
propertyType = (PropertyType) propertyType.DeepClone();
propertyType.Variations = propertyType.Variations & Variations;
propertyType.ResetDirtyProperties(false);
return propertyType;
}
return ContentTypeComposition
.SelectMany(x => x.CompositionPropertyTypes)
.Select(AcquireProperty)
.Union(PropertyTypes);
}
}
@@ -11,6 +11,7 @@ namespace Umbraco.Core.Models
/// be available or not, and published or not, individually. Varying by segment
/// is a property-level thing.</para>
/// </remarks>
[Flags]
public enum ContentVariation : byte
{
/// <summary>
@@ -33,6 +33,7 @@ namespace Umbraco.Core.Models.Identity
private string[] _allowedSections;
private int[] _startMediaIds;
private int[] _startContentIds;
private DateTime? _lastPasswordChangeDateUtc;
/// <summary>
/// Used to construct a new instance without an identity
@@ -136,6 +137,15 @@ namespace Umbraco.Core.Models.Identity
set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, Ps.Value.UserNameSelector);
}
/// <summary>
/// LastPasswordChangeDateUtc so we can track changes to it
/// </summary>
public override DateTime? LastPasswordChangeDateUtc
{
get { return _lastPasswordChangeDateUtc; }
set { _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDateUtc, Ps.Value.LastPasswordChangeDateUtcSelector); }
}
/// <summary>
/// Override LastLoginDateUtc so we can track changes to it
/// </summary>
@@ -419,6 +429,7 @@ namespace Umbraco.Core.Models.Identity
public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo<BackOfficeIdentityUser, string>(x => x.Email);
public readonly PropertyInfo UserNameSelector = ExpressionHelper.GetPropertyInfo<BackOfficeIdentityUser, string>(x => x.UserName);
public readonly PropertyInfo LastLoginDateUtcSelector = ExpressionHelper.GetPropertyInfo<BackOfficeIdentityUser, DateTime?>(x => x.LastLoginDateUtc);
public readonly PropertyInfo LastPasswordChangeDateUtcSelector = ExpressionHelper.GetPropertyInfo<BackOfficeIdentityUser, DateTime?>(x => x.LastPasswordChangeDateUtc);
public readonly PropertyInfo EmailConfirmedSelector = ExpressionHelper.GetPropertyInfo<BackOfficeIdentityUser, bool>(x => x.EmailConfirmed);
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<BackOfficeIdentityUser, string>(x => x.Name);
public readonly PropertyInfo AccessFailedCountSelector = ExpressionHelper.GetPropertyInfo<BackOfficeIdentityUser, int>(x => x.AccessFailedCount);
@@ -439,5 +450,6 @@ namespace Umbraco.Core.Models.Identity
groups => groups.GetHashCode());
}
}
}
@@ -8,9 +8,9 @@ using Umbraco.Core.Services;
namespace Umbraco.Core.Models.Identity
{
public class IdentityProfile : Profile
public class IdentityMapperProfile : Profile
{
public IdentityProfile(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings)
public IdentityMapperProfile(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings)
{
CreateMap<IUser, BackOfficeIdentityUser>()
.BeforeMap((src, dest) =>
@@ -19,6 +19,7 @@ namespace Umbraco.Core.Models.Identity
})
.ConstructUsing(src => new BackOfficeIdentityUser(src.Id, src.Groups))
.ForMember(dest => dest.LastLoginDateUtc, opt => opt.MapFrom(src => src.LastLoginDate.ToUniversalTime()))
.ForMember(user => user.LastPasswordChangeDateUtc, expression => expression.MapFrom(user => user.LastPasswordChangeDate.ToUniversalTime()))
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email))
.ForMember(dest => dest.EmailConfirmed, opt => opt.MapFrom(src => src.EmailConfirmedDate.HasValue))
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
@@ -18,10 +18,8 @@ namespace Umbraco.Core.Models.Identity
where TRole : IdentityUserRole<string>
where TClaim : IdentityUserClaim<TKey>
{
/// <summary>
/// Constructor
///
/// Initializes a new instance of the <see cref="IdentityUser{TKey, TLogin, TRole, TClaim}"/> class.
/// </summary>
public IdentityUser()
{
@@ -75,6 +73,11 @@ namespace Umbraco.Core.Models.Identity
/// </summary>
public virtual DateTime? LockoutEndDateUtc { get; set; }
/// <summary>
/// DateTime in UTC when the password was last changed.
/// </summary>
public virtual DateTime? LastPasswordChangeDateUtc { get; set; }
/// <summary>
/// Is lockout enabled for this user
/// </summary>
@@ -77,7 +77,7 @@ namespace Umbraco.Core.Models.PublishedContent
/// <summary>
/// Gets the published content type containing the property type.
/// </summary>
/// </summary>
public PublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor
/// <summary>
@@ -193,6 +193,20 @@ namespace Umbraco.Core.Models.PublishedContent
_modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this);
}
/// <summary>
/// Determines whether a source value is an actual value, or not a value.
/// </summary>
/// <remarks>Used by property.HasValue and, for instance, in fallback scenarios.</remarks>
public bool IsValue(object value)
{
// if we have a converter, use the converter,
// otherwise use the old magic null & string comparisons
return _converter == null
? value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false)
: _converter.IsValue(value);
}
/// <summary>
/// Gets the property cache level.
/// </summary>
@@ -272,7 +272,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (orderBySystemField == false || orderBy.InvariantEquals(dbfield) == false)
{
// get alias, if aliased
var matches = VersionableRepositoryBaseAliasRegex.For(SqlContext.SqlSyntax).Matches(sql.SQL);
var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL);
var match = matches.Cast<Match>().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(dbfield));
if (match != null) dbfield = match.Groups[2].Value;
@@ -304,7 +304,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// note: ContentTypeAlias is not properly managed because it's not part of the query to begin with!
// get alias, if aliased
var matches = VersionableRepositoryBaseAliasRegex.For(SqlContext.SqlSyntax).Matches(sql.SQL);
var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL);
var match = matches.Cast<Match>().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(dbfield));
if (match != null) dbfield = match.Groups[2].Value;
@@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
internal static class VersionableRepositoryBaseAliasRegex
{
private static readonly Dictionary<Type, Regex> Regexes = new Dictionary<Type, Regex>();
public static Regex For(ISqlSyntaxProvider sqlSyntax)
{
var type = sqlSyntax.GetType();
Regex aliasRegex;
if (Regexes.TryGetValue(type, out aliasRegex))
return aliasRegex;
var col = Regex.Escape(sqlSyntax.GetQuotedColumnName("column")).Replace("column", @"\w+");
var fld = Regex.Escape(sqlSyntax.GetQuotedTableName("table") + ".").Replace("table", @"\w+") + col;
aliasRegex = new Regex("(" + fld + @")\s+AS\s+(" + col + ")", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase);
Regexes[type] = aliasRegex;
return aliasRegex;
}
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -19,17 +20,6 @@ namespace Umbraco.Core.Persistence.SqlSyntax
string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType);
string GetConcat(params string[] args);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType);
string GetQuotedTableName(string tableName);
string GetQuotedColumnName(string columnName);
string GetQuotedName(string name);
@@ -70,6 +60,14 @@ namespace Umbraco.Core.Persistence.SqlSyntax
string FormatColumnRename(string tableName, string oldName, string newName);
string FormatTableRename(string oldName, string newName);
/// <summary>
/// Gets a regex matching aliased fields.
/// </summary>
/// <remarks>
/// <para>Matches "(table.column) AS (alias)" where table, column and alias are properly escaped.</para>
/// </remarks>
Regex AliasRegex { get; }
Sql<ISqlContext> SelectTop(Sql<ISqlContext> sql, int top);
bool SupportsClustered();
@@ -70,66 +70,6 @@ namespace Umbraco.Core.Persistence.SqlSyntax
}
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnStartsWithComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return $"{column} LIKE '{value}%'";
default:
throw new ArgumentOutOfRangeException(nameof(columnType));
}
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnEndsWithComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return $"{column} LIKE '%{value}'";
default:
throw new ArgumentOutOfRangeException(nameof(columnType));
}
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnContainsComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return $"{column} LIKE '%{value}%'";
default:
throw new ArgumentOutOfRangeException(nameof(columnType));
}
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnContainsComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return $"{column} LIKE '{value}'";
default:
throw new ArgumentOutOfRangeException(nameof(columnType));
}
}
/// <summary>
/// This uses a the DbTypeMap created and custom mapping to resolve the SqlDbType
/// </summary>
@@ -49,21 +49,6 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return indexType;
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnEqualComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for = comparison with NText columns but allows this syntax
return string.Format("{0} LIKE '{1}'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetConcat(params string[] args)
{
return "(" + string.Join("+", args) + ")";
@@ -4,6 +4,7 @@ using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -40,8 +41,17 @@ namespace Umbraco.Core.Persistence.SqlSyntax
DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale);
InitColumnTypeMap();
// ReSharper disable VirtualMemberCallInConstructor
// ok to call virtual GetQuotedXxxName here - they don't depend on any state
var col = Regex.Escape(GetQuotedColumnName("column")).Replace("column", @"\w+");
var fld = Regex.Escape(GetQuotedTableName("table") + ".").Replace("table", @"\w+") + col;
// ReSharper restore VirtualMemberCallInConstructor
AliasRegex = new Regex("(" + fld + @")\s+AS\s+(" + col + ")", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase);
}
public Regex AliasRegex { get; }
public string GetWildcardPlaceholder()
{
return "%";
@@ -137,41 +147,6 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return $"upper({column}) LIKE upper(@{paramIndex})";
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return $"upper({column}) = '{value.ToUpper()}'";
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return $"upper({column}) LIKE '{value.ToUpper()}%'";
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return $"upper({column}) LIKE '%{value.ToUpper()}'";
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return $"upper({column}) LIKE '%{value.ToUpper()}%'";
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return $"upper({column}) LIKE '{value.ToUpper()}'";
}
public virtual string GetConcat(params string[] args)
{
return "concat(" + string.Join(",", args) + ")";
@@ -17,6 +17,11 @@ namespace Umbraco.Core.PropertyEditors
/// <returns>A value indicating whether the converter supports a property type.</returns>
bool IsConverter(PublishedPropertyType propertyType);
/// <summary>
/// Determines whether a source value is an actual value, or not a value.
/// </summary>
bool IsValue(object value);
/// <summary>
/// Gets the type of values returned by the converter.
/// </summary>
@@ -9,33 +9,24 @@ namespace Umbraco.Core.PropertyEditors
public abstract class PropertyValueConverterBase : IPropertyValueConverter
{
public virtual bool IsConverter(PublishedPropertyType propertyType)
{
return false;
}
=> false;
public bool IsValue(object value)
=> value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false);
public virtual Type GetPropertyValueType(PublishedPropertyType propertyType)
{
return typeof (object);
}
=> typeof (object);
public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType)
{
return PropertyCacheLevel.Snapshot;
}
=> PropertyCacheLevel.Snapshot;
public virtual object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview)
{
return source;
}
=> source;
public virtual object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
return inter;
}
=> inter;
public virtual object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
return inter?.ToString() ?? string.Empty;
}
=> inter?.ToString() ?? string.Empty;
}
}
@@ -434,6 +434,7 @@ namespace Umbraco.Core.Security
/// </remarks>
protected override async Task<IdentityResult> UpdatePassword(IUserPasswordStore<T, int> passwordStore, T user, string newPassword)
{
user.LastPasswordChangeDateUtc = DateTime.UtcNow;
var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher<BackOfficeIdentityUser, int>;
if (userAwarePasswordHasher == null)
return await base.UpdatePassword(passwordStore, user, newPassword);
@@ -484,15 +485,22 @@ namespace Umbraco.Core.Security
#endregion
public override Task<IdentityResult> SetLockoutEndDateAsync(int userId, DateTimeOffset lockoutEnd)
public override async Task<IdentityResult> SetLockoutEndDateAsync(int userId, DateTimeOffset lockoutEnd)
{
var result = base.SetLockoutEndDateAsync(userId, lockoutEnd);
var result = await base.SetLockoutEndDateAsync(userId, lockoutEnd);
// The way we unlock is by setting the lockoutEnd date to the current datetime
if (result.Result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow)
if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow)
{
RaiseAccountLockedEvent(userId);
}
else
{
RaiseAccountUnlockedEvent(userId);
//Resets the login attempt fails back to 0 when unlock is clicked
await ResetAccessFailedCountAsync(userId);
}
return result;
}
@@ -517,13 +525,36 @@ namespace Umbraco.Core.Security
public override Task<IdentityResult> AccessFailedAsync(int userId)
/// <summary>
/// Overides the microsoft ASP.NET user managment method
/// </summary>
/// <param name="userId"></param>
/// <returns>
/// returns a Async Task<IdentityResult>
/// </returns>
/// <remarks>
/// Doesnt set fail attempts back to 0
/// </remarks>
public override async Task<IdentityResult> AccessFailedAsync(int userId)
{
var result = base.AccessFailedAsync(userId);
var lockoutStore = (IUserLockoutStore<BackOfficeIdentityUser, int>)Store;
var user = await FindByIdAsync(userId);
if (user == null)
throw new InvalidOperationException("No user found by user id " + userId);
var count = await lockoutStore.IncrementAccessFailedCountAsync(user);
if (count >= MaxFailedAccessAttemptsBeforeLockout)
{
await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(DefaultAccountLockoutTimeSpan));
//NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0
//here we are persisting the value for the back office
}
var result = await UpdateAsync(user);
//Slightly confusing: this will return a Success if we successfully update the AccessFailed count
if (result.Result.Succeeded)
if (result.Succeeded)
RaiseLoginFailedEvent(userId);
return result;
@@ -637,6 +637,13 @@ namespace Umbraco.Core.Security
anythingChanged = true;
user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime();
}
if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc")
|| (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|| identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value)
{
anythingChanged = true;
user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime();
}
if (identityUser.IsPropertyDirty("EmailConfirmed")
|| (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default(DateTime) && identityUser.EmailConfirmed == false)
|| ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default(DateTime)) && identityUser.EmailConfirmed))
@@ -1841,7 +1841,7 @@ namespace Umbraco.Core.Services.Implement
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(copy, TreeChangeTypes.RefreshBranch).ToEventArgs());
foreach (var x in copies)
scope.Events.Dispatch(Copied, this, new CopyEventArgs<IContent>(x.Item1, x.Item2, false, x.Item2.ParentId, relateToOriginal));
Audit(AuditType.Copy, "Copy Content performed by user", content.WriterId, content.Id);
Audit(AuditType.Copy, "Copy Content performed by user", userId, content.Id);
scope.Complete();
}
@@ -332,16 +332,23 @@ namespace Umbraco.Core.Services.Implement
var evtMsgs = EventMessagesFactory.Get();
//NOTE: This isn't pretty but we need to maintain backwards compatibility so we cannot change
//fixme: This isn't pretty because we we're required to maintain backwards compatibility so we could not change
// the event args here. The other option is to create a different event with different event
// args specifically for this method... which also isn't pretty. So for now, we'll use this
// dictionary approach to store 'additional data' in.
// args specifically for this method... which also isn't pretty. So fix this in v8!
var additionalData = new Dictionary<string, object>
{
{ "CreateTemplateForContentType", true },
{ "ContentTypeAlias", contentTypeAlias },
};
// check that the template hasn't been created on disk before creating the content type
// if it exists, set the new template content to the existing file content
string content = GetViewContent(contentTypeAlias);
if (content.IsNullOrWhiteSpace() == false)
{
template.Content = content;
}
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<ITemplate>(template, true, evtMsgs, additionalData);
@@ -368,6 +375,15 @@ namespace Umbraco.Core.Services.Implement
{
Content = content
};
// check that the template hasn't been created on disk before creating the content type
// if it exists, set the new template content to the existing file content
string existingContent = GetViewContent(template.Alias);
if (existingContent.IsNullOrWhiteSpace() == false)
{
template.Content = content;
}
if (masterTemplate != null)
{
template.SetMasterTemplate(masterTemplate);
@@ -659,10 +675,26 @@ namespace Umbraco.Core.Services.Implement
return _templateRepository.GetFileSize(filepath);
}
}
private string GetViewContent(string fileName)
{
if (fileName.IsNullOrWhiteSpace())
throw new ArgumentNullException(nameof(fileName));
#endregion
if (!fileName.EndsWith(".cshtml"))
fileName = string.Concat(fileName, ".cshtml");
#region Partial Views
var fs = _templateRepository.GetFileContentStream(fileName);
if (fs == null) return string.Empty;
using (var view = new StreamReader(fs))
{
return view.ReadToEnd().Trim();
}
}
#endregion
#region Partial Views
public IEnumerable<string> GetPartialViewSnippetNames(params string[] filterNames)
{
+1 -2
View File
@@ -357,6 +357,7 @@
<Compile Include="Models\IAuditEntry.cs" />
<Compile Include="Models\IAuditItem.cs" />
<Compile Include="Models\IConsent.cs" />
<Compile Include="Models\Identity\IdentityMapperProfile.cs" />
<Compile Include="Models\Membership\MemberExportModel.cs" />
<Compile Include="Models\Membership\MemberExportProperty.cs" />
<Compile Include="Models\PathValidationExtensions.cs" />
@@ -665,7 +666,6 @@
<Compile Include="Models\IDataType.cs" />
<Compile Include="Models\IDeepCloneable.cs" />
<Compile Include="Models\Identity\BackOfficeIdentityUser.cs" />
<Compile Include="Models\Identity\IdentityProfile.cs" />
<Compile Include="Models\Identity\IdentityUser.cs" />
<Compile Include="Models\Identity\IdentityUserClaim.cs" />
<Compile Include="Models\Identity\IdentityUserLogin.cs" />
@@ -1208,7 +1208,6 @@
<Compile Include="Persistence\Repositories\Implement\UserGroupRepository.cs" />
<Compile Include="Persistence\Repositories\Implement\UserRepository.cs" />
<Compile Include="Persistence\Repositories\Implement\ContentRepositoryBase.cs" />
<Compile Include="Persistence\Repositories\Implement\VersionableRepositoryBaseAliasRegex.cs" />
<Compile Include="Persistence\SqlContext.cs" />
<Compile Include="Persistence\SqlSyntax\ColumnInfo.cs" />
<Compile Include="Persistence\SqlSyntax\DbTypes.cs" />
@@ -16,28 +16,28 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
[Test]
public void EmailAddress()
{
Assert.IsTrue(SettingsSection.Content.NotificationEmailAddress == "robot@umbraco.dk");
Assert.AreEqual(SettingsSection.Content.NotificationEmailAddress, "robot@umbraco.dk");
}
[Test]
public virtual void DisableHtmlEmail()
{
Assert.IsTrue(SettingsSection.Content.DisableHtmlEmail == true);
Assert.IsTrue(SettingsSection.Content.DisableHtmlEmail);
}
[Test]
public virtual void Can_Set_Multiple()
{
Assert.IsTrue(SettingsSection.Content.Error404Collection.Count() == 3);
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(0).Culture == "default");
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(0).ContentId == 1047);
Assert.AreEqual(SettingsSection.Content.Error404Collection.Count(), 3);
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).Culture, "default");
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).ContentId, 1047);
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentId);
Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentKey);
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(1).Culture == "en-US");
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath == "$site/error [@name = 'error']");
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).Culture, "en-US");
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath, "$site/error [@name = 'error']");
Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentId);
Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentKey);
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(2).Culture == "en-UK");
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey == new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"));
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).Culture, "en-UK");
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey, new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"));
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentKey);
Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentId);
}
@@ -45,7 +45,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
[Test]
public void ScriptFolderPath()
{
Assert.IsTrue(SettingsSection.Content.ScriptFolderPath == "/scripts");
Assert.AreEqual(SettingsSection.Content.ScriptFolderPath, "/scripts");
}
[Test]
public void ScriptFileTypes()
@@ -55,7 +55,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
[Test]
public void DisableScriptEditor()
{
Assert.IsTrue(SettingsSection.Content.ScriptEditorDisable == false);
Assert.AreEqual(SettingsSection.Content.ScriptEditorDisable, false);
}
[Test]
@@ -71,80 +71,79 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
[Test]
public virtual void ImageAutoFillProperties()
{
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.Count() == 2);
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias == "umbracoFile");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias == "umbracoWidth");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias == "umbracoHeight");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias == "umbracoBytes");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias == "umbracoExtension");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias == "umbracoFile2");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias == "umbracoWidth2");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias == "umbracoHeight2");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias == "umbracoBytes2");
Assert.IsTrue(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias == "umbracoExtension2");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.Count(), 2);
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias, "umbracoFile");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias, "umbracoWidth");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias, "umbracoHeight");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias, "umbracoBytes");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias, "umbracoExtension");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias, "umbracoFile2");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias, "umbracoWidth2");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias, "umbracoHeight2");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias, "umbracoBytes2");
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias, "umbracoExtension2");
}
[Test]
public void UploadAllowDirectories()
{
Assert.IsTrue(SettingsSection.Content.UploadAllowDirectories == true);
Assert.IsTrue(SettingsSection.Content.UploadAllowDirectories);
}
[Test]
public void DefaultDocumentTypeProperty()
{
Assert.IsTrue(SettingsSection.Content.DefaultDocumentTypeProperty == "Textstring");
Assert.AreEqual(SettingsSection.Content.DefaultDocumentTypeProperty, "Textstring");
}
[Test]
public void GlobalPreviewStorageEnabled()
{
Assert.IsTrue(SettingsSection.Content.GlobalPreviewStorageEnabled == false);
Assert.IsFalse(SettingsSection.Content.GlobalPreviewStorageEnabled);
}
[Test]
public void CloneXmlContent()
{
Assert.IsTrue(SettingsSection.Content.CloneXmlContent == true);
Assert.IsTrue(SettingsSection.Content.CloneXmlContent);
}
[Test]
public void EnsureUniqueNaming()
{
Assert.IsTrue(SettingsSection.Content.EnsureUniqueNaming == true);
Assert.IsTrue(SettingsSection.Content.EnsureUniqueNaming);
}
[Test]
public void ForceSafeAliases()
{
Assert.IsTrue(SettingsSection.Content.ForceSafeAliases == true);
Assert.IsTrue(SettingsSection.Content.ForceSafeAliases);
}
[Test]
public void XmlCacheEnabled()
{
Assert.IsTrue(SettingsSection.Content.XmlCacheEnabled == true);
Assert.IsTrue(SettingsSection.Content.XmlCacheEnabled);
}
[Test]
public void ContinouslyUpdateXmlDiskCache()
{
Assert.IsTrue(SettingsSection.Content.ContinouslyUpdateXmlDiskCache == true);
Assert.IsTrue(SettingsSection.Content.ContinouslyUpdateXmlDiskCache);
}
[Test]
public virtual void XmlContentCheckForDiskChanges()
{
Assert.IsTrue(SettingsSection.Content.XmlContentCheckForDiskChanges == true);
Assert.IsTrue(SettingsSection.Content.XmlContentCheckForDiskChanges);
}
[Test]
public void EnableSplashWhileLoading()
{
Assert.IsTrue(SettingsSection.Content.EnableSplashWhileLoading == false);
Assert.IsFalse(SettingsSection.Content.EnableSplashWhileLoading);
}
[Test]
public void PropertyContextHelpOption()
{
Assert.IsTrue(SettingsSection.Content.PropertyContextHelpOption == "text");
Assert.AreEqual(SettingsSection.Content.PropertyContextHelpOption, "text");
}
[Test]
public void PreviewBadge()
{
Assert.IsTrue(SettingsSection.Content.PreviewBadge == @"<a id=""umbracoPreviewBadge"" style=""position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;"" href=""{0}/endPreview.aspx?redir={1}""><span style=""display:none;"">In Preview Mode - click to end</span></a>");
Assert.AreEqual(SettingsSection.Content.PreviewBadge, @"<a id=""umbracoPreviewBadge"" style=""z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;"" href=""#"" OnClick=""javascript:window.top.location.href = '{0}/endPreview.aspx?redir={1}'""><span style=""display:none;"">In Preview Mode - click to end</span></a>");
}
[Test]
public void ResolveUrlsFromTextString()
@@ -154,7 +153,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
[Test]
public void MacroErrors()
{
Assert.IsTrue(SettingsSection.Content.MacroErrorBehaviour == MacroErrorBehaviour.Inline);
Assert.AreEqual(SettingsSection.Content.MacroErrorBehaviour, MacroErrorBehaviour.Inline);
}
[Test]
@@ -75,7 +75,10 @@
<ForceSafeAliases>true</ForceSafeAliases>
<PreviewBadge><![CDATA[<a id="umbracoPreviewBadge" style="position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;" href="{0}/endPreview.aspx?redir={1}"><span style="display:none;">In Preview Mode - click to end</span></a>]]></PreviewBadge>
<PreviewBadge>
<![CDATA[
<a id="umbracoPreviewBadge" style="z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;" href="#" OnClick="javascript:window.top.location.href = '{0}/endPreview.aspx?redir={1}'"><span style="display:none;">In Preview Mode - click to end</span></a>
]]></PreviewBadge>
<!-- How Umbraco should handle errors during macro execution. Can be one of the following values:
- inline - show an inline error within the macro but allow the page to continue rendering. Historial Umbraco behaviour.
+18 -242
View File
@@ -25,7 +25,6 @@ namespace Umbraco.Tests.FrontEnd
{
Current.Reset();
}
[Test]
public static void Truncate_Simple()
{
@@ -165,7 +164,7 @@ namespace Umbraco.Tests.FrontEnd
var result = helper.StripHtml(SampleWithBoldAndAnchorElements, tags).ToString();
Assert.AreEqual("Hello world, this is some text <a href='blah'>with a link</a>", result);
Assert.AreEqual(SampleWithAnchorElement, result);
}
[Test]
@@ -180,236 +179,6 @@ namespace Umbraco.Tests.FrontEnd
Assert.AreEqual("Hello world, is some text with a link", result);
}
// ------- Int32 conversion tests
[Test]
public static void Converting_boxed_34_to_an_int_returns_34()
{
// Arrange
const int sample = 34;
// Act
bool success = UmbracoHelper.ConvertIdObjectToInt(
sample,
out int result
);
// Assert
Assert.IsTrue(success);
Assert.That(result, Is.EqualTo(34));
}
[Test]
public static void Converting_string_54_to_an_int_returns_54()
{
// Arrange
const string sample = "54";
// Act
bool success = UmbracoHelper.ConvertIdObjectToInt(
sample,
out int result
);
// Assert
Assert.IsTrue(success);
Assert.That(result, Is.EqualTo(54));
}
[Test]
public static void Converting_hello_to_an_int_returns_false()
{
// Arrange
const string sample = "Hello";
// Act
bool success = UmbracoHelper.ConvertIdObjectToInt(
sample,
out int result
);
// Assert
Assert.IsFalse(success);
Assert.That(result, Is.EqualTo(0));
}
[Test]
public static void Converting_unsupported_object_to_an_int_returns_false()
{
// Arrange
var clearlyWillNotConvertToInt = new StringBuilder(0);
// Act
bool success = UmbracoHelper.ConvertIdObjectToInt(
clearlyWillNotConvertToInt,
out int result
);
// Assert
Assert.IsFalse(success);
Assert.That(result, Is.EqualTo(0));
}
// ------- GUID conversion tests
[Test]
public static void Converting_boxed_guid_to_a_guid_returns_original_guid_value()
{
// Arrange
Guid sample = Guid.NewGuid();
// Act
bool success = UmbracoHelper.ConvertIdObjectToGuid(
sample,
out Guid result
);
// Assert
Assert.IsTrue(success);
Assert.That(result, Is.EqualTo(sample));
}
[Test]
public static void Converting_string_guid_to_a_guid_returns_original_guid_value()
{
// Arrange
Guid sample = Guid.NewGuid();
// Act
bool success = UmbracoHelper.ConvertIdObjectToGuid(
sample.ToString(),
out Guid result
);
// Assert
Assert.IsTrue(success);
Assert.That(result, Is.EqualTo(sample));
}
[Test]
public static void Converting_hello_to_a_guid_returns_false()
{
// Arrange
const string sample = "Hello";
// Act
bool success = UmbracoHelper.ConvertIdObjectToGuid(
sample,
out Guid result
);
// Assert
Assert.IsFalse(success);
Assert.That(result, Is.EqualTo(new Guid("00000000-0000-0000-0000-000000000000")));
}
[Test]
public static void Converting_unsupported_object_to_a_guid_returns_false()
{
// Arrange
var clearlyWillNotConvertToGuid = new StringBuilder(0);
// Act
bool success = UmbracoHelper.ConvertIdObjectToGuid(
clearlyWillNotConvertToGuid,
out Guid result
);
// Assert
Assert.IsFalse(success);
Assert.That(result, Is.EqualTo(new Guid("00000000-0000-0000-0000-000000000000")));
}
// ------- UDI Conversion Tests
/// <remarks>
/// This requires PluginManager.Current to be initialised before
/// running.
/// </remarks>
[Test]
public static void Converting_boxed_udi_to_a_udi_returns_original_udi_value()
{
// Arrange
Udi.ResetUdiTypes();
Udi sample = new GuidUdi(Constants.UdiEntityType.AnyGuid, Guid.NewGuid());
// Act
bool success = UmbracoHelper.ConvertIdObjectToUdi(
sample,
out Udi result
);
// Assert
Assert.IsTrue(success);
Assert.That(result, Is.EqualTo(sample));
}
/// <remarks>
/// This requires PluginManager.Current to be initialised before
/// running.
/// </remarks>
[Test]
public void Converting_string_udi_to_a_udi_returns_original_udi_value()
{
// Arrange
SetUpDependencyContainer();
Udi.ResetUdiTypes();
Udi sample = new GuidUdi(Constants.UdiEntityType.AnyGuid, Guid.NewGuid());
// Act
bool success = UmbracoHelper.ConvertIdObjectToUdi(
sample.ToString(),
out Udi result
);
// Assert
Assert.IsTrue(success, "Conversion of UDI failed.");
Assert.That(result, Is.EqualTo(sample));
}
/// <remarks>
/// This requires PluginManager.Current to be initialised before
/// running.
/// </remarks>
[Test]
public void Converting_hello_to_a_udi_returns_false()
{
// Arrange
SetUpDependencyContainer();
Udi.ResetUdiTypes();
const string sample = "Hello";
// Act
bool success = UmbracoHelper.ConvertIdObjectToUdi(
sample,
out Udi result
);
// Assert
Assert.IsFalse(success);
Assert.That(result, Is.Null);
}
/// <remarks>
/// This requires PluginManager.Current to be initialised before
/// running.
/// </remarks>
[Test]
public static void Converting_unsupported_object_to_a_udi_returns_false()
{
// Arrange
Udi.ResetUdiTypes();
var clearlyWillNotConvertToGuid = new StringBuilder(0);
// Act
bool success = UmbracoHelper.ConvertIdObjectToUdi(
clearlyWillNotConvertToGuid,
out Udi result
);
// Assert
Assert.IsFalse(success);
Assert.That(result, Is.Null);
}
// ------- Int32 conversion tests
[Test]
public static void Converting_Boxed_34_To_An_Int_Returns_34()
@@ -549,13 +318,14 @@ namespace Umbraco.Tests.FrontEnd
}
// ------- UDI Conversion Tests
/// <remarks>
/// This requires PluginManager.Current to be initialised before running.
/// </remarks>
[Test]
public void Converting_Boxed_Udi_To_A_Udi_Returns_Original_Udi_Value()
public static void Converting_Boxed_Udi_To_A_Udi_Returns_Original_Udi_Value()
{
// Arrange
SetUpDependencyContainer();
Udi.ResetUdiTypes();
Udi sample = new GuidUdi(Constants.UdiEntityType.AnyGuid, Guid.NewGuid());
// Act
@@ -569,13 +339,15 @@ namespace Umbraco.Tests.FrontEnd
Assert.That(result, Is.EqualTo(sample));
}
/// <remarks>
/// This requires PluginManager.Current to be initialised before running.
/// </remarks>
[Test]
public void Converting_String_Udi_To_A_Udi_Returns_Original_Udi_Value()
{
// Arrange
SetUpDependencyContainer();
Udi.ResetUdiTypes();
Udi sample = new GuidUdi(Constants.UdiEntityType.AnyGuid, Guid.NewGuid());
// Act
@@ -585,22 +357,24 @@ namespace Umbraco.Tests.FrontEnd
);
// Assert
Assert.IsTrue(success);
Assert.IsTrue(success, "Conversion of UDI failed.");
Assert.That(result, Is.EqualTo(sample));
}
/// <remarks>
/// This requires PluginManager.Current to be initialised before running.
/// </remarks>
[Test]
public void Converting_Hello_To_A_Udi_Returns_False()
{
// Arrange
SetUpDependencyContainer();
Udi.ResetUdiTypes();
const string SAMPLE = "Hello";
const string sample = "Hello";
// Act
bool success = UmbracoHelper.ConvertIdObjectToUdi(
SAMPLE,
sample,
out Udi result
);
@@ -609,11 +383,13 @@ namespace Umbraco.Tests.FrontEnd
Assert.That(result, Is.Null);
}
/// <remarks>
/// This requires PluginManager.Current to be initialised before running.
/// </remarks>
[Test]
public void Converting_Unsupported_Object_To_A_Udi_Returns_False()
public static void Converting_Unsupported_Object_To_A_Udi_Returns_False()
{
// Arrange
SetUpDependencyContainer();
Udi.ResetUdiTypes();
var clearlyWillNotConvertToGuid = new StringBuilder(0);
@@ -667,9 +667,9 @@ namespace Umbraco.Tests.Persistence.Repositories
}
[Test]
public void RegexAliasTest()
public void AliasRegexTest()
{
var regex = VersionableRepositoryBaseAliasRegex.For(new SqlServerSyntaxProvider(new Lazy<IScopeProvider>(() => null)));
var regex = new SqlServerSyntaxProvider(new Lazy<IScopeProvider>(() => null)).AliasRegex;
Assert.AreEqual(@"(\[\w+]\.\[\w+])\s+AS\s+(\[\w+])", regex.ToString());
const string sql = "SELECT [table].[column1] AS [alias1], [table].[column2] AS [alias2] FROM [table];";
var matches = regex.Matches(sql);
@@ -47,6 +47,9 @@ namespace Umbraco.Tests.Published
private class SimpleConverter1 : IPropertyValueConverter
{
public bool IsValue(object value)
=> value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false);
public bool IsConverter(PublishedPropertyType propertyType)
=> propertyType.EditorAlias.InvariantEquals("Umbraco.Void");
@@ -117,6 +120,9 @@ namespace Umbraco.Tests.Published
_cacheLevel = cacheLevel;
}
public bool IsValue(object value)
=> value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false);
public bool IsConverter(PublishedPropertyType propertyType)
=> propertyType.EditorAlias.InvariantEquals("Umbraco.Void");
@@ -210,6 +210,9 @@ namespace Umbraco.Tests.Published
public int SourceConverts { get; private set; }
public int InterConverts { get; private set; }
public bool IsValue(object value)
=> value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false);
public bool IsConverter(PublishedPropertyType propertyType)
=> propertyType.EditorAlias.InvariantEquals("Umbraco.Void");
@@ -235,4 +238,4 @@ namespace Umbraco.Tests.Published
=> ((int) inter).ToString();
}
}
}
}
@@ -112,7 +112,9 @@ namespace Umbraco.Tests.PublishedContent
<testRecursive><![CDATA[]]></testRecursive>
</Home>
<CustomDocument id=""1177"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1177"" isDoc="""" />
<CustomDocument id=""1178"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 2"" urlName=""custom-sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178"" isDoc="""" />
<CustomDocument id=""1178"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 2"" urlName=""custom-sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178"" isDoc="""">
<CustomDocument id=""1179"" parentID=""1178"" level=""4"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub sub 1"" urlName=""custom-sub-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178,1179"" isDoc="""" />
</CustomDocument>
<Home id=""1176"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-20T18:08:08"" updateDate=""2012-07-20T19:10:52"" nodeName=""Sub 3"" urlName=""sub-3"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1176"" isDoc="""" key=""CDB83BBC-A83B-4BA6-93B8-AADEF67D3C09"">
<content><![CDATA[]]></content>
<umbracoNaviHide>1</umbracoNaviHide>
@@ -322,7 +324,7 @@ namespace Umbraco.Tests.PublishedContent
{
var doc = GetNode(1046);
var expected = new[] {1046, 1173, 1174, 1177, 1178, 1176, 1175, 4444, 1172};
var expected = new[] { 1046, 1173, 1174, 1177, 1178, 1179, 1176, 1175, 4444, 1172 };
var exindex = 0;
// must respect the XPath descendants-or-self axis!
@@ -559,6 +561,93 @@ namespace Umbraco.Tests.PublishedContent
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1046 }));
}
[Test]
public void IsAncestor()
{
// Structure:
// - Root : 1046 (no parent)
// -- Home: 1173 (parent 1046)
// -- Custom Doc: 1178 (parent 1173)
// --- Custom Doc2: 1179 (parent: 1178)
// - Custom Doc3: 1172 (no parent)
var home = GetNode(1173);
var root = GetNode(1046);
var customDoc = GetNode(1178);
var customDoc2 = GetNode(1179);
var customDoc3 = GetNode(1172);
Assert.IsFalse(root.IsAncestor(customDoc3));
Assert.IsTrue(root.IsAncestor(customDoc2));
Assert.IsTrue(root.IsAncestor(customDoc));
Assert.IsTrue(root.IsAncestor(home));
Assert.IsFalse(root.IsAncestor(root));
Assert.IsFalse(home.IsAncestor(customDoc3));
Assert.IsTrue(home.IsAncestor(customDoc2));
Assert.IsTrue(home.IsAncestor(customDoc));
Assert.IsFalse(home.IsAncestor(home));
Assert.IsFalse(home.IsAncestor(root));
Assert.IsFalse(customDoc.IsAncestor(customDoc3));
Assert.IsTrue(customDoc.IsAncestor(customDoc2));
Assert.IsFalse(customDoc.IsAncestor(customDoc));
Assert.IsFalse(customDoc.IsAncestor(home));
Assert.IsFalse(customDoc.IsAncestor(root));
Assert.IsFalse(customDoc2.IsAncestor(customDoc3));
Assert.IsFalse(customDoc2.IsAncestor(customDoc2));
Assert.IsFalse(customDoc2.IsAncestor(customDoc));
Assert.IsFalse(customDoc2.IsAncestor(home));
Assert.IsFalse(customDoc2.IsAncestor(root));
Assert.IsFalse(customDoc3.IsAncestor(customDoc3));
}
[Test]
public void IsAncestorOrSelf()
{
// Structure:
// - Root : 1046 (no parent)
// -- Home: 1173 (parent 1046)
// -- Custom Doc: 1178 (parent 1173)
// --- Custom Doc2: 1179 (parent: 1178)
// - Custom Doc3: 1172 (no parent)
var home = GetNode(1173);
var root = GetNode(1046);
var customDoc = GetNode(1178);
var customDoc2 = GetNode(1179);
var customDoc3 = GetNode(1172);
Assert.IsFalse(root.IsAncestorOrSelf(customDoc3));
Assert.IsTrue(root.IsAncestorOrSelf(customDoc2));
Assert.IsTrue(root.IsAncestorOrSelf(customDoc));
Assert.IsTrue(root.IsAncestorOrSelf(home));
Assert.IsTrue(root.IsAncestorOrSelf(root));
Assert.IsFalse(home.IsAncestorOrSelf(customDoc3));
Assert.IsTrue(home.IsAncestorOrSelf(customDoc2));
Assert.IsTrue(home.IsAncestorOrSelf(customDoc));
Assert.IsTrue(home.IsAncestorOrSelf(home));
Assert.IsFalse(home.IsAncestorOrSelf(root));
Assert.IsFalse(customDoc.IsAncestorOrSelf(customDoc3));
Assert.IsTrue(customDoc.IsAncestorOrSelf(customDoc2));
Assert.IsTrue(customDoc.IsAncestorOrSelf(customDoc));
Assert.IsFalse(customDoc.IsAncestorOrSelf(home));
Assert.IsFalse(customDoc.IsAncestorOrSelf(root));
Assert.IsFalse(customDoc2.IsAncestorOrSelf(customDoc3));
Assert.IsTrue(customDoc2.IsAncestorOrSelf(customDoc2));
Assert.IsFalse(customDoc2.IsAncestorOrSelf(customDoc));
Assert.IsFalse(customDoc2.IsAncestorOrSelf(home));
Assert.IsFalse(customDoc2.IsAncestorOrSelf(root));
Assert.IsTrue(customDoc3.IsAncestorOrSelf(customDoc3));
}
[Test]
public void Descendants_Or_Self()
{
@@ -568,7 +657,7 @@ namespace Umbraco.Tests.PublishedContent
Assert.IsNotNull(result);
Assert.AreEqual(8, result.Length);
Assert.AreEqual(9, result.Count());
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 }));
}
@@ -581,10 +670,96 @@ namespace Umbraco.Tests.PublishedContent
Assert.IsNotNull(result);
Assert.AreEqual(7, result.Length);
Assert.AreEqual(8, result.Count());
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 }));
}
[Test]
public void IsDescendant()
{
// Structure:
// - Root : 1046 (no parent)
// -- Home: 1173 (parent 1046)
// -- Custom Doc: 1178 (parent 1173)
// --- Custom Doc2: 1179 (parent: 1178)
// - Custom Doc3: 1172 (no parent)
var home = GetNode(1173);
var root = GetNode(1046);
var customDoc = GetNode(1178);
var customDoc2 = GetNode(1179);
var customDoc3 = GetNode(1172);
Assert.IsFalse(root.IsDescendant(root));
Assert.IsFalse(root.IsDescendant(home));
Assert.IsFalse(root.IsDescendant(customDoc));
Assert.IsFalse(root.IsDescendant(customDoc2));
Assert.IsFalse(root.IsDescendant(customDoc3));
Assert.IsTrue(home.IsDescendant(root));
Assert.IsFalse(home.IsDescendant(home));
Assert.IsFalse(home.IsDescendant(customDoc));
Assert.IsFalse(home.IsDescendant(customDoc2));
Assert.IsFalse(home.IsDescendant(customDoc3));
Assert.IsTrue(customDoc.IsDescendant(root));
Assert.IsTrue(customDoc.IsDescendant(home));
Assert.IsFalse(customDoc.IsDescendant(customDoc));
Assert.IsFalse(customDoc.IsDescendant(customDoc2));
Assert.IsFalse(customDoc.IsDescendant(customDoc3));
Assert.IsTrue(customDoc2.IsDescendant(root));
Assert.IsTrue(customDoc2.IsDescendant(home));
Assert.IsTrue(customDoc2.IsDescendant(customDoc));
Assert.IsFalse(customDoc2.IsDescendant(customDoc2));
Assert.IsFalse(customDoc2.IsDescendant(customDoc3));
Assert.IsFalse(customDoc3.IsDescendant(customDoc3));
}
[Test]
public void IsDescendantOrSelf()
{
// Structure:
// - Root : 1046 (no parent)
// -- Home: 1173 (parent 1046)
// -- Custom Doc: 1178 (parent 1173)
// --- Custom Doc2: 1179 (parent: 1178)
// - Custom Doc3: 1172 (no parent)
var home = GetNode(1173);
var root = GetNode(1046);
var customDoc = GetNode(1178);
var customDoc2 = GetNode(1179);
var customDoc3 = GetNode(1172);
Assert.IsTrue(root.IsDescendantOrSelf(root));
Assert.IsFalse(root.IsDescendantOrSelf(home));
Assert.IsFalse(root.IsDescendantOrSelf(customDoc));
Assert.IsFalse(root.IsDescendantOrSelf(customDoc2));
Assert.IsFalse(root.IsDescendantOrSelf(customDoc3));
Assert.IsTrue(home.IsDescendantOrSelf(root));
Assert.IsTrue(home.IsDescendantOrSelf(home));
Assert.IsFalse(home.IsDescendantOrSelf(customDoc));
Assert.IsFalse(home.IsDescendantOrSelf(customDoc2));
Assert.IsFalse(home.IsDescendantOrSelf(customDoc3));
Assert.IsTrue(customDoc.IsDescendantOrSelf(root));
Assert.IsTrue(customDoc.IsDescendantOrSelf(home));
Assert.IsTrue(customDoc.IsDescendantOrSelf(customDoc));
Assert.IsFalse(customDoc.IsDescendantOrSelf(customDoc2));
Assert.IsFalse(customDoc.IsDescendantOrSelf(customDoc3));
Assert.IsTrue(customDoc2.IsDescendantOrSelf(root));
Assert.IsTrue(customDoc2.IsDescendantOrSelf(home));
Assert.IsTrue(customDoc2.IsDescendantOrSelf(customDoc));
Assert.IsTrue(customDoc2.IsDescendantOrSelf(customDoc2));
Assert.IsFalse(customDoc2.IsDescendantOrSelf(customDoc3));
Assert.IsTrue(customDoc3.IsDescendantOrSelf(customDoc3));
}
[Test]
public void Up()
{
@@ -1732,6 +1732,39 @@ namespace Umbraco.Tests.Services
Assert.IsNull(mediaType2.Description);
}
[Test]
public void Variations_In_Compositions()
{
var service = ServiceContext.ContentTypeService;
var typeA = MockedContentTypes.CreateSimpleContentType("a", "A");
typeA.Variations = ContentVariation.Culture; // make it variant
typeA.PropertyTypes.First(x => x.Alias.InvariantEquals("title")).Variations = ContentVariation.Culture; // with a variant property
service.Save(typeA);
var typeB = MockedContentTypes.CreateSimpleContentType("b", "B", typeA, true);
typeB.Variations = ContentVariation.Nothing; // make it invariant
service.Save(typeB);
var typeC = MockedContentTypes.CreateSimpleContentType("c", "C", typeA, true);
typeC.Variations = ContentVariation.Culture; // make it variant
service.Save(typeC);
// property is variant on A
var test = service.Get(typeA.Id);
Assert.AreEqual(ContentVariation.Culture, test.CompositionPropertyTypes.First(x => x.Alias.InvariantEquals("title")).Variations);
Assert.AreEqual(ContentVariation.Culture, test.CompositionPropertyGroups.First().PropertyTypes.First(x => x.Alias.InvariantEquals("title")).Variations);
// but not on B
test = service.Get(typeB.Id);
Assert.AreEqual(ContentVariation.Nothing, test.CompositionPropertyTypes.First(x => x.Alias.InvariantEquals("title")).Variations);
Assert.AreEqual(ContentVariation.Nothing, test.CompositionPropertyGroups.First().PropertyTypes.First(x => x.Alias.InvariantEquals("title")).Variations);
// but on C
test = service.Get(typeC.Id);
Assert.AreEqual(ContentVariation.Culture, test.CompositionPropertyTypes.First(x => x.Alias.InvariantEquals("title")).Variations);
Assert.AreEqual(ContentVariation.Culture, test.CompositionPropertyGroups.First().PropertyTypes.First(x => x.Alias.InvariantEquals("title")).Variations);
}
private ContentType CreateComponent()
{
var component = new ContentType(-1)
+17 -2
View File
@@ -39,6 +39,7 @@ using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web.Composing.CompositionRoots;
using Umbraco.Web._Legacy.Actions;
using Current = Umbraco.Core.Composing.Current;
using Umbraco.Web.Routing;
namespace Umbraco.Tests.Testing
{
@@ -137,6 +138,7 @@ namespace Umbraco.Tests.Testing
ComposeApplication(Options.WithApplication);
// etc
ComposeWeb();
ComposeWtf();
// not sure really
@@ -173,12 +175,24 @@ namespace Umbraco.Tests.Testing
Container.RegisterSingleton(f => new ProfilingLogger(f.GetInstance<ILogger>(), f.GetInstance<IProfiler>()));
}
protected virtual void ComposeWtf()
protected virtual void ComposeWeb()
{
//TODO: Should we 'just' register the WebRuntimeComponent?
// imported from TestWithSettingsBase
// which was inherited by TestWithApplicationBase so pretty much used everywhere
Umbraco.Web.Composing.Current.UmbracoContextAccessor = new TestUmbracoContextAccessor();
// web
Container.Register(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor);
Container.RegisterSingleton<PublishedRouter>();
Container.RegisterCollectionBuilder<ContentFinderCollectionBuilder>();
Container.Register<IContentLastChanceFinder, TestLastChanceFinder>();
Container.Register<IVariationContextAccessor, TestVariationContextAccessor>();
}
protected virtual void ComposeWtf()
{
// what else?
var runtimeStateMock = new Mock<IRuntimeState>();
runtimeStateMock.Setup(x => x.Level).Returns(RuntimeLevel.Run);
@@ -271,7 +285,8 @@ namespace Umbraco.Tests.Testing
Container.RegisterSingleton(factory => globalSettings);
Container.RegisterSingleton(factory => umbracoSettings.Content);
Container.RegisterSingleton(factory => umbracoSettings.Templates);
Container.RegisterSingleton(factory => umbracoSettings.WebRouting);
// fixme - The whole MediaFileSystem coupling thing seems broken.
Container.Register<IFileSystem, MediaFileSystem>((factory, fileSystem) => new MediaFileSystem(fileSystem, factory.GetInstance<IContentSection>(), factory.GetInstance<IMediaPathScheme>(), factory.GetInstance<ILogger>()));
Container.RegisterConstructorDependency((factory, parameterInfo) => factory.GetInstance<FileSystems>().MediaFileSystem);
File diff suppressed because it is too large Load Diff
@@ -531,6 +531,13 @@ input.umb-group-builder__group-title-input {
overflow: hidden;
}
.editor-validation-pattern{
border: 1px solid @gray-7;
margin: 10px 0 0;
padding: 6px;
max-height: 32px;
}
.umb-dropdown {
width: 100%;
}
@@ -3,10 +3,24 @@
// --------------------------------------------------
.umb-property-editor {
min-width:66.6%;
&-pull {
float:left;
width:66.6%;
}
&-push {
float:right;
}
}
.umb-property-editor-tiny {
width: 60px;
&.umb-editor-push {
width:30%;
min-width:0;
}
}
.umb-property-editor-small {
@@ -28,6 +42,27 @@
width: 99%;
}
// displays property inline with preceeding
.umb-property {
&--pull {
float:left;
width:60%;
}
&--push {
float:right;
width:35%;
}
&--pull, &--push {
.umb-editor {
min-width:0;
width:100%;
}
}
}
//
// Content picker
// --------------------------------------------------
+25 -6
View File
@@ -147,13 +147,32 @@ app.config(function ($routeProvider) {
resolve: canRoute(true)
})
.when('/:section/:tree/:method?', {
templateUrl: function (rp) {
//This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method.
template: "<div ng-include='templateUrl'></div>",
//This controller will execute for this route, then we replace the template dynamnically based on the current tree.
controller: function ($scope, $route, $routeParams, treeService) {
//if there is no method registered for this then show the dashboard
if (!rp.method)
return "views/common/dashboard.html";
return ('views/' + rp.tree + '/' + rp.method + '.html');
if (!$routeParams.method) {
$scope.templateUrl = "views/common/dashboard.html";
}
// Here we need to figure out if this route is for a package tree and if so then we need
// to change it's convention view path to:
// /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html
// otherwise if it is a core tree we use the core paths:
// views/{treetype}/{method}.html
var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree);
if (packageTreeFolder) {
$scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath +
"/" + packageTreeFolder +
"/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html");
}
else {
$scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html');
}
},
reloadOnSearch: false,
resolve: canRoute(true)
@@ -1,155 +1,170 @@
//used for the media picker dialog
angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
var dialogOptions = $scope.model;
function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) {
var dialogOptions = $scope.model;
var searchText = "Search...";
localizationService.localize("general_search").then(function (value) {
searchText = value + "...";
});
var anchorPattern = /<a id=\\"(.*?)\\">/gi;
if(!$scope.model.title) {
localizationService.localize("defaultdialogs_selectLink").then(function(value){
$scope.model.title = value;
});
}
var searchText = "Search...";
localizationService.localize("general_search").then(function (value) {
searchText = value + "...";
});
$scope.dialogTreeApi = {};
$scope.model.target = {};
$scope.searchInfo = {
searchFromId: null,
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: []
};
if (!$scope.model.title) {
$scope.model.title = localizationService.localize("defaultdialogs_selectLink");
}
$scope.showTarget = $scope.model.hideTarget !== true;
$scope.dialogTreeApi = {};
$scope.model.target = {};
$scope.searchInfo = {
searchFromId: null,
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: []
};
if (dialogOptions.currentTarget) {
$scope.model.target = dialogOptions.currentTarget;
$scope.showTarget = $scope.model.hideTarget !== true;
//if we have a node ID, we fetch the current node to build the form data
if ($scope.model.target.id || $scope.model.target.udi) {
if (dialogOptions.currentTarget) {
$scope.model.target = dialogOptions.currentTarget;
//if we have a node ID, we fetch the current node to build the form data
if ($scope.model.target.id || $scope.model.target.udi) {
//will be either a udi or an int
var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
if (!$scope.model.target.path) {
entityResource.getPath(id, "Document").then(function (path) {
$scope.model.target.path = path;
//now sync the tree to this path
$scope.dialogTreeApi.syncTree({ path: $scope.model.target.path, tree: "content" });
});
}
$scope.model.target.path = path;
//now sync the tree to this path
$scope.dialogTreeApi.syncTree({
path: $scope.model.target.path,
tree: "content"
});
});
}
contentResource.getNiceUrl(id).then(function (url) {
$scope.model.target.url = url;
});
}
}
// if a link exists, get the properties to build the anchor name list
contentResource.getById(id).then(function (resp) {
$scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
$scope.model.target.url = resp.urls[0];
});
} else if ($scope.model.target.url.length) {
// a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
$scope.model.target.url = $scope.model.target.url.substring(0, $scope.model.target.url.search(/(#|\?)/));
}
} else if (dialogOptions.anchors) {
$scope.anchorValues = dialogOptions.anchors;
}
function nodeSelectHandler(args) {
function nodeSelectHandler(args) {
if (args && args.event) {
args.event.preventDefault();
args.event.stopPropagation();
}
if(args && args.event) {
args.event.preventDefault();
args.event.stopPropagation();
}
eventsService.emit("dialogs.linkPicker.select", args);
eventsService.emit("dialogs.linkPicker.select", args);
if ($scope.currentNode) {
//un-select if there's a current one selected
$scope.currentNode.selected = false;
}
if ($scope.currentNode) {
//un-select if there's a current one selected
$scope.currentNode.selected = false;
}
$scope.currentNode = args.node;
$scope.currentNode.selected = true;
$scope.model.target.id = args.node.id;
$scope.model.target.udi = args.node.udi;
$scope.model.target.name = args.node.name;
$scope.currentNode = args.node;
$scope.currentNode.selected = true;
$scope.model.target.id = args.node.id;
$scope.model.target.udi = args.node.udi;
$scope.model.target.name = args.node.name;
if (args.node.id < 0) {
$scope.model.target.url = "/";
} else {
contentResource.getById(args.node.id).then(function (resp) {
$scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
$scope.model.target.url = resp.urls[0];
});
}
if (args.node.id < 0) {
$scope.model.target.url = "/";
}
else {
contentResource.getNiceUrl(args.node.id).then(function (url) {
$scope.model.target.url = url;
});
}
if (!angular.isUndefined($scope.model.target.isMedia)) {
delete $scope.model.target.isMedia;
}
}
if (!angular.isUndefined($scope.model.target.isMedia)) {
delete $scope.model.target.isMedia;
}
}
function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
}
function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
}
$scope.switchToMediaPicker = function () {
userService.getCurrentUser().then(function (userData) {
$scope.mediaPickerOverlay = {
view: "mediapicker",
startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
startNodeIsVirtual: userData.startMediaIds.length !== 1,
show: true,
submit: function (model) {
var media = model.selectedImages[0];
$scope.switchToMediaPicker = function () {
userService.getCurrentUser().then(function (userData) {
$scope.mediaPickerOverlay = {
view: "mediapicker",
startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
startNodeIsVirtual: userData.startMediaIds.length !== 1,
show: true,
submit: function(model) {
var media = model.selectedImages[0];
$scope.model.target.id = media.id;
$scope.model.target.udi = media.udi;
$scope.model.target.isMedia = true;
$scope.model.target.name = media.name;
$scope.model.target.url = mediaHelper.resolveFile(media);
$scope.model.target.id = media.id;
$scope.model.target.udi = media.udi;
$scope.model.target.isMedia = true;
$scope.model.target.name = media.name;
$scope.model.target.url = mediaHelper.resolveFile(media);
debugger;
$scope.mediaPickerOverlay.show = false;
$scope.mediaPickerOverlay = null;
}
};
});
};
$scope.mediaPickerOverlay.show = false;
$scope.mediaPickerOverlay = null;
}
};
});
};
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
$scope.searchInfo.searchFromId = null;
$scope.searchInfo.searchFromName = null;
$scope.searchInfo.results = [];
}
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
$scope.searchInfo.searchFromId = null;
$scope.searchInfo.searchFromName = null;
$scope.searchInfo.results = [];
}
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, {event: evt, node: result});
};
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, {
event: evt,
node: result
});
};
//callback when there are search results
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
};
$scope.onTreeInit = function () {
$scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
$scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
}
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
nodeSelectHandler({}, { node: node });
};
$scope.closeMiniListView = function () {
$scope.miniListView = undefined;
};
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
nodeSelectHandler({}, {
node: node
});
};
function openMiniListView(node) {
$scope.miniListView = node;
}
$scope.closeMiniListView = function () {
$scope.miniListView = undefined;
};
});
function openMiniListView(node) {
$scope.miniListView = node;
}
});
@@ -1,26 +1,41 @@
<div ng-controller="Umbraco.Overlays.LinkPickerController">
<umb-control-group label="@defaultdialogs_urlLinkPicker">
<umb-control-group label="@defaultdialogs_urlLinkPicker" class="umb-property--pull">
<input type="text"
localize="placeholder"
placeholder="@general_url"
class="umb-property-editor umb-textstring"
ng-model="model.target.url"
ng-disabled="model.target.id"
focus-when="{{true}} "/>
localize="placeholder"
placeholder="@general_url"
class="umb-property-editor umb-textstring"
ng-model="model.target.url"
ng-disabled="model.target.id" />
</umb-control-group>
<umb-control-group label="@defaultdialogs_anchorLinkPicker" class="umb-property--push">
<input type="text"
list="anchors"
localize="placeholder"
placeholder="@placeholders_anchor"
class="umb-editor umb-textstring"
ng-model="model.target.anchor" />
<datalist id="anchors">
<option value="{{a}}" ng-repeat="a in anchorValues"></option>
</datalist>
</umb-control-group>
<umb-control-group label="@defaultdialogs_nodeNameLinkPicker">
<input type="text"
localize="placeholder"
placeholder="@placeholders_entername"
class="umb-property-editor umb-textstring"
ng-model="model.target.name" />
localize="placeholder"
placeholder="@placeholders_entername"
class="umb-property-editor umb-textstring"
ng-model="model.target.name" />
</umb-control-group>
<umb-control-group ng-if="showTarget" label="@content_target">
<label class="checkbox no-indent">
<input type="checkbox" ng-model="model.target.target" ng-true-value="_blank" ng-false-value="" /> <localize key="defaultdialogs_openInNewWindow">Opens the linked document in a new window or tab</localize>
<input type="checkbox"
ng-model="model.target.target"
ng-true-value="_blank"
ng-false-value="" /> <localize key="defaultdialogs_openInNewWindow">Opens the linked document in a new window or tab</localize>
</label>
</umb-control-group>
@@ -30,43 +45,39 @@
</h5>
<div ng-hide="miniListView">
<umb-tree-search-box
hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="{{section}}">
<umb-tree-search-box hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="{{section}}">
</umb-tree-search-box>
<br/>
<br />
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
<umb-tree-search-results ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
<div ng-hide="searchInfo.showSearch">
<umb-tree
section="content"
hideheader="true"
hideoptions="true"
api="dialogTreeApi"
on-init="onTreeInit()"
enablelistviewexpand="true"
isdialog="true"
enablecheckboxes="true">
<umb-tree section="content"
hideheader="true"
hideoptions="true"
api="dialogTreeApi"
on-init="onTreeInit()"
enablelistviewexpand="true"
isdialog="true"
enablecheckboxes="true">
</umb-tree>
</div>
</div>
<umb-mini-list-view
ng-if="miniListView"
node="miniListView"
entity-type="Document"
on-select="selectListViewNode(node)"
on-close="closeMiniListView()">
<umb-mini-list-view ng-if="miniListView"
node="miniListView"
entity-type="Document"
on-select="selectListViewNode(node)"
on-close="closeMiniListView()">
</umb-mini-list-view>
</div>
@@ -80,11 +91,10 @@
</a>
</div>
<umb-overlay
ng-if="mediaPickerOverlay.show"
model="mediaPickerOverlay"
view="mediaPickerOverlay.view"
position="right">
<umb-overlay ng-if="mediaPickerOverlay.show"
model="mediaPickerOverlay"
view="mediaPickerOverlay.view"
position="right">
</umb-overlay>
</div>
@@ -20,9 +20,10 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
}
$scope.treeModel = {
hideHeader: false
}
}
$scope.toggle = toggleHandler;
userService.getCurrentUser().then(function (userData) {
$scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
$scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
});
var node = dialogOptions.currentNode;
@@ -57,7 +58,27 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
}
}
function toggleHandler(type){
// If the relateToOriginal toggle is clicked
if(type === "relate"){
if($scope.relateToOriginal){
$scope.relateToOriginal = false;
return;
}
$scope.relateToOriginal = true;
}
// If the recurvise toggle is clicked
if(type === "recursive"){
if($scope.recursive){
$scope.recursive = false;
return;
}
$scope.recursive = true;
}
}
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
@@ -69,13 +69,13 @@
<umb-pane>
<umb-control-group localize="label" label="@defaultdialogs_relateToOriginalLabel">
<input type="checkbox" ng-model="$parent.$parent.relateToOriginal"/>
<umb-toggle checked="$parent.$parent.relateToOriginal" on-click="$parent.$parent.toggle('relate')"></umb-toggle>
</umb-control-group>
</umb-pane>
<umb-pane>
<umb-control-group localize="label" label="@defaultdialogs_includeDescendants">
<input type="checkbox" ng-model="$parent.$parent.recursive" />
<umb-control-group localize="label" label="@defaultdialogs_includeDescendants">
<umb-toggle checked="$parent.$parent.recursive" on-click="$parent.$parent.toggle('recursive')"></umb-toggle>
</umb-control-group>
</umb-pane>
@@ -88,7 +88,10 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
// sortable options
$scope.sortableOptions = {
axis: "y",
containment: "parent",
distance: 10,
opacity: 0.7,
tolerance: "pointer",
scroll: true,
zIndex: 6000
@@ -1,7 +1,7 @@
(function() {
"use strict";
function GridRichTextEditorController($scope, tinyMceService, macroService) {
function GridRichTextEditorController($scope, tinyMceService, macroService, editorState) {
var vm = this;
@@ -11,9 +11,11 @@
vm.openEmbed = openEmbed;
function openLinkPicker(editor, currentTarget, anchorElement) {
vm.linkPickerOverlay = {
view: "linkpicker",
currentTarget: currentTarget,
anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)),
show: true,
submit: function(model) {
tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
@@ -1,6 +1,6 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.RTEController",
function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService) {
function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) {
$scope.isLoading = true;
@@ -273,11 +273,12 @@ angular.module("umbraco")
syncContent(editor);
});
tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) {
$scope.linkPickerOverlay = {
view: "linkpicker",
currentTarget: currentTarget,
anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)),
show: true,
submit: function(model) {
tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
@@ -5,15 +5,12 @@ angular.module("umbraco")
var $typeahead;
$scope.isLoading = true;
$scope.tagToAdd = "";
assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js", $scope).then(function () {
$scope.isLoading = false;
//load current value
if ($scope.model.value) {
$scope.tagToAdd = "";
function setModelValue(val) {
$scope.model.value = val || $scope.model.value;
if ($scope.model.value) {
if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
//it is csv
if (!$scope.model.value) {
@@ -21,7 +18,14 @@ angular.module("umbraco")
}
else {
if($scope.model.value.length > 0) {
$scope.model.value = $scope.model.value.split(",");
// split the csv string, and remove any duplicate values
var tempArray = $scope.model.value.split(',').map(function(v) {
return v.trim();
});
$scope.model.value = tempArray.filter(function(v, i, self) {
return self.indexOf(v) === i;
});
}
}
}
@@ -29,6 +33,14 @@ angular.module("umbraco")
else {
$scope.model.value = [];
}
}
assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js", $scope).then(function () {
$scope.isLoading = false;
//load current value
setModelValue();
// Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected)
$scope.validateMandatory = function () {
@@ -85,17 +97,7 @@ angular.module("umbraco")
//vice versa
$scope.model.onValueChanged = function (newVal, oldVal) {
//update the display val again if it has changed from the server
$scope.model.value = newVal;
if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
//it is csv
if (!$scope.model.value) {
$scope.model.value = [];
}
else {
$scope.model.value = $scope.model.value.split(",");
}
}
setModelValue(newVal);
};
//configure the tags data source
@@ -8,7 +8,7 @@
<input type="hidden" name="tagCount" ng-model="model.value.length" val-property-validator="validateMandatory" />
<span ng-repeat="tag in model.value" ng-click="$parent.removeTag(tag)" class="label label-primary tag">
<span ng-repeat="tag in model.value track by $index" ng-click="$parent.removeTag(tag)" class="label label-primary tag">
<span ng-bind-html="tag"></span>
<i class="icon icon-delete"></i>
</span>
@@ -67,7 +67,7 @@
});
vm.save = function () {
vm.save = function (suppressNotification) {
vm.page.saveButtonState = "busy";
vm.template.content = vm.editor.getValue();
@@ -83,11 +83,13 @@
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {
if (!suppressNotification) {
localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function(data){
var header = data[0];
var message = data[1];
notificationsService.success(header, message);
});
}
vm.page.saveButtonState = "success";
@@ -173,6 +175,21 @@
vm.page.loading = false;
vm.template = template;
// if this is a new template, bind to the blur event on the name
if ($routeParams.create) {
$timeout(function() {
var nameField = angular.element(document.querySelector('[data-element="editor-name-field"]'));
if (nameField) {
nameField.bind('blur', function(event) {
if (event.target.value) {
vm.save(true);
}
});
}
});
}
//sync state
editorState.set(vm.template);
navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) {
@@ -329,8 +329,10 @@
vm.unlockUserButtonState = "busy";
usersResource.unlockUsers([vm.user.id]).then(function (data) {
vm.user.userState = 0;
vm.user.failedPasswordAttempts = 0;
setUserDisplayState();
vm.unlockUserButtonState = "success";
}, function (error) {
vm.unlockUserButtonState = "error";
});
@@ -344,6 +344,7 @@
<area alias="defaultdialogs">
<key alias="nodeNameLinkPicker">Link title</key>
<key alias="urlLinkPicker">Link</key>
<key alias="anchorLinkPicker">Anchor / querystring</key>
<key alias="anchorInsert">Name</key>
<key alias="assignDomain">Manage hostnames</key>
<key alias="closeThisWindow">Close this window</key>
@@ -462,6 +463,7 @@
<key alias="email">Enter your email...</key>
<key alias="enterMessage">Enter a message...</key>
<key alias="usernameHint">Your username is usually your email</key>
<key alias="anchor">#value or ?key=value</key>
</area>
<area alias="editcontenttype">
<key alias="allowAtRoot" version="7.2">Allow at root</key>
@@ -352,6 +352,7 @@
<area alias="defaultdialogs">
<key alias="nodeNameLinkPicker">Link title</key>
<key alias="urlLinkPicker">Link</key>
<key alias="anchorLinkPicker">Anchor / querystring</key>
<key alias="anchorInsert">Name</key>
<key alias="closeThisWindow">Close this window</key>
<key alias="confirmdelete">Are you sure you want to delete</key>
@@ -470,6 +471,7 @@
<key alias="email">Enter your email...</key>
<key alias="enterMessage">Enter a message...</key>
<key alias="usernameHint">Your username is usually your email</key>
<key alias="anchor">#value or ?key=value</key>
</area>
<area alias="editcontenttype">
<key alias="allowAtRoot" version="7.2">Allow at root</key>
File diff suppressed because it is too large Load Diff
@@ -39,14 +39,14 @@
<h2>Easy start with Umbraco.tv</h2>
<p>We have created a bunch of 'how-to' videos, to get you easily started with Umbraco. Learn how to build projects in just a couple of minutes. Easiest CMS in the world.</p>
<a href="http://umbraco.tv?ref=tvFromInstaller" target="_blank">Umbraco.tv &rarr;</a>
<a href="https://umbraco.tv?ref=tvFromInstaller" target="_blank">Umbraco.tv &rarr;</a>
</div>
<div class="col">
<h2>Be a part of the community</h2>
<p>The Umbraco community is the best of its kind, be sure to visit, and if you have any questions, we're sure that you can get your answers from the community.</p>
<a href="http://our.umbraco.org?ref=ourFromInstaller" target="_blank">our.Umbraco &rarr;</a>
<a href="https://our.umbraco.com/?ref=ourFromInstaller" target="_blank">our.Umbraco &rarr;</a>
</div>
</div>
@@ -40,7 +40,9 @@
<!-- The html injected into a (x)html page if Umbraco is running in preview mode -->
<PreviewBadge>
<![CDATA[<a id="umbracoPreviewBadge" style="position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;z-index: 9999999;" href="#" OnClick="javascript:window.top.location.href = '{0}/endPreview.aspx?redir={1}'"><span style="display:none;">In Preview Mode - click to end</span></a>]]></PreviewBadge>
<![CDATA[
<a id="umbracoPreviewBadge" style="z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;" href="#" OnClick="javascript:window.top.location.href = '{0}/endPreview.aspx?redir={1}'"><span style="display:none;">In Preview Mode - click to end</span></a>
]]></PreviewBadge>
<!-- How Umbraco should handle errors during macro execution. Can be one of the following values:
- inline - show an inline error within the macro but allow the page to continue rendering. Historial Umbraco behaviour.
@@ -75,7 +75,8 @@
<!-- The html injected into a (x)html page if Umbraco is running in preview mode -->
<PreviewBadge>
<![CDATA[<a id="umbracoPreviewBadge" style="position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;" href="#" OnClick="javascript:window.top.location.href = '{0}/endPreview.aspx?redir={1}'"><span style="display:none;">In Preview Mode - click to end</span></a>
<![CDATA[
<a id="umbracoPreviewBadge" style="z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;" href="#" OnClick="javascript:window.top.location.href = '{0}/endPreview.aspx?redir={1}'"><span style="display:none;">In Preview Mode - click to end</span></a>
]]></PreviewBadge>
@@ -1,5 +1,8 @@
using LightInject;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Models.Mapping;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Composing.CompositionRoots
{
@@ -7,6 +10,7 @@ namespace Umbraco.Web.Composing.CompositionRoots
{
public void Compose(IServiceRegistry container)
{
//register the profiles
container.Register<AuditMapperProfile>();
container.Register<CodeFileMapperProfile>();
container.Register<ContentMapperProfile>();
@@ -25,6 +29,16 @@ namespace Umbraco.Web.Composing.CompositionRoots
container.Register<TemplateMapperProfile>();
container.Register<UserMapperProfile>();
container.Register<LanguageMapperProfile>();
//register any resolvers, etc.. that the profiles use
container.Register<ContentUrlResolver>();
container.Register<ContentTreeNodeUrlResolver<IContent, ContentTreeController>>();
container.Register<TabsAndPropertiesResolver<IContent, ContentItemDisplay>>();
container.Register<TabsAndPropertiesResolver<IMedia, MediaItemDisplay>>();
container.Register<ContentTreeNodeUrlResolver<IMedia, MediaTreeController>>();
container.Register<MemberTabsAndPropertiesResolver>();
container.Register<MemberTreeNodeUrlResolver>();
container.Register<MemberBasicPropertiesResolver>();
}
}
}
+41 -2
View File
@@ -61,7 +61,9 @@ namespace Umbraco.Web.Editors
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector(
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi))));
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)),
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi))
));
}
}
@@ -273,6 +275,25 @@ namespace Umbraco.Web.Editors
[OutgoingEditorModelEvent]
[EnsureUserPermissionForContent("id")]
public ContentItemDisplay GetById(int id, string culture = null)
{
var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
if (foundContent == null)
{
HandleContentNotFound(id);
return null;//irrelevant since the above throws
}
var content = MapToDisplay(foundContent, culture);
return content;
}
/// <summary>
/// Gets the content json for the content id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[OutgoingEditorModelEvent]
[EnsureUserPermissionForContent("id")]
public ContentItemDisplay GetById(Guid id, string culture = null)
{
var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
if (foundContent == null)
@@ -285,6 +306,24 @@ namespace Umbraco.Web.Editors
return content;
}
/// <summary>
/// Gets the content json for the content id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[OutgoingEditorModelEvent]
[EnsureUserPermissionForContent("id")]
public ContentItemDisplay GetById(Udi id, string culture = null)
{
var guidUdi = id as GuidUdi;
if (guidUdi != null)
{
return GetById(guidUdi.Guid, culture);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
/// <summary>
/// Gets an empty content item for the
/// </summary>
@@ -1283,7 +1322,7 @@ namespace Umbraco.Web.Editors
culture = Services.LocalizationService.GetDefaultLanguageIsoCode();
}
var display = ContextMapper.Map<IContent, ContentItemDisplay>(content, UmbracoContext,
var display = ContextMapper.Map<IContent, ContentItemDisplay>(content,
new Dictionary<string, object> { { ContextMapper.CultureKey, culture } });
return display;
@@ -21,11 +21,12 @@ namespace Umbraco.Web.Editors
/// An API controller used for dealing with member types
/// </summary>
[PluginController("UmbracoApi")]
[UmbracoTreeAuthorize(Constants.Trees.MemberTypes)]
[UmbracoTreeAuthorize(new string[] { Constants.Trees.MemberTypes, Constants.Trees.Members})]
public class MemberTypeController : ContentTypeControllerBase<IMemberType>
{
private readonly MembershipProvider _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
[UmbracoTreeAuthorize(Constants.Trees.MemberTypes)]
public MemberTypeDisplay GetById(int id)
{
var ct = Services.MemberTypeService.Get(id);
@@ -45,6 +46,7 @@ namespace Umbraco.Web.Editors
/// <returns></returns>
[HttpDelete]
[HttpPost]
[UmbracoTreeAuthorize(Constants.Trees.MemberTypes)]
public HttpResponseMessage DeleteById(int id)
{
var foundType = Services.MemberTypeService.Get(id);
@@ -71,6 +73,8 @@ namespace Umbraco.Web.Editors
/// be looked up via the db, they need to be passed in.
/// </param>
/// <returns></returns>
[UmbracoTreeAuthorize(Constants.Trees.MemberTypes)]
public HttpResponseMessage GetAvailableCompositeMemberTypes(int contentTypeId,
[FromUri]string[] filterContentTypes,
[FromUri]string[] filterPropertyTypes)
@@ -84,6 +88,7 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(result);
}
[UmbracoTreeAuthorize(Constants.Trees.MemberTypes)]
public MemberTypeDisplay GetEmpty()
{
var ct = new MemberType(-1);
@@ -107,6 +112,7 @@ namespace Umbraco.Web.Editors
return Enumerable.Empty<ContentTypeBasic>();
}
[UmbracoTreeAuthorize(Constants.Trees.MemberTypes)]
public MemberTypeDisplay PostSave(MemberTypeSave contentTypeSave)
{
//get the persisted member type
@@ -1,15 +1,13 @@
using System.Collections.Generic;
using AutoMapper;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using AutoMapper;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
@@ -14,18 +14,25 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentMapperProfile : Profile
{
public ContentMapperProfile(IUserService userService, ILocalizedTextService textService, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, ILocalizationService localizationService, ILogger logger)
public ContentMapperProfile(
ContentUrlResolver contentUrlResolver,
ContentTreeNodeUrlResolver<IContent, ContentTreeController> contentTreeNodeUrlResolver,
TabsAndPropertiesResolver<IContent, ContentItemDisplay> tabsAndPropertiesResolver,
IUserService userService,
ILocalizedTextService textService,
IContentService contentService,
IContentTypeService contentTypeService,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
ILogger logger)
{
// create, capture, cache
var contentOwnerResolver = new OwnerResolver<IContent>(userService);
var creatorResolver = new CreatorResolver(userService);
var actionButtonsResolver = new ActionButtonsResolver(userService, contentService);
var tabsAndPropertiesResolver = new TabsAndPropertiesResolver<IContent, ContentItemDisplay>(textService);
var childOfListViewResolver = new ContentChildOfListViewResolver(contentService, contentTypeService);
var contentTypeBasicResolver = new ContentTypeBasicResolver<IContent, ContentItemDisplay>();
var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver<IContent, ContentTreeController>();
var defaultTemplateResolver = new DefaultTemplateResolver();
var contentUrlResolver = new ContentUrlResolver(textService, contentService, logger);
var variantResolver = new ContentItemDisplayVariationResolver(localizationService);
//FROM IContent TO ContentItemDisplay
@@ -12,9 +12,16 @@ namespace Umbraco.Web.Models.Mapping
where TSource : IContentBase
where TController : ContentTreeControllerBase
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
public ContentTreeNodeUrlResolver(IUmbracoContextAccessor umbracoContextAccessor)
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
}
public string Resolve(TSource source, object destination, string destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext(throwIfMissing: false);
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
if (umbracoContext == null) return null;
var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext);
@@ -10,24 +10,36 @@ namespace Umbraco.Web.Models.Mapping
{
internal class ContentUrlResolver : IValueResolver<IContent, ContentItemDisplay, UrlInfo[]>
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly PublishedRouter _publishedRouter;
private readonly ILocalizationService _localizationService;
private readonly ILocalizedTextService _textService;
private readonly IContentService _contentService;
private readonly ILogger _logger;
public ContentUrlResolver(ILocalizedTextService textService, IContentService contentService, ILogger logger)
public ContentUrlResolver(
IUmbracoContextAccessor umbracoContextAccessor,
PublishedRouter publishedRouter,
ILocalizationService localizationService,
ILocalizedTextService textService,
IContentService contentService,
ILogger logger)
{
_textService = textService;
_contentService = contentService;
_logger = logger;
_umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
_publishedRouter = publishedRouter ?? throw new System.ArgumentNullException(nameof(publishedRouter));
_localizationService = localizationService ?? throw new System.ArgumentNullException(nameof(localizationService));
_textService = textService ?? throw new System.ArgumentNullException(nameof(textService));
_contentService = contentService ?? throw new System.ArgumentNullException(nameof(contentService));
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
}
public UrlInfo[] Resolve(IContent source, ContentItemDisplay destination, UrlInfo[] destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext(throwIfMissing: false);
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
var urls = umbracoContext == null
? new[] { UrlInfo.Message("Cannot generate urls without a current Umbraco Context") }
: source.GetContentUrls(umbracoContext.UrlProvider, _textService, _contentService, _logger).ToArray();
: source.GetContentUrls(_publishedRouter, umbracoContext, _localizationService, _textService, _contentService, _logger).ToArray();
return urls;
}
@@ -10,42 +10,9 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal static class ContextMapper
{
public const string UmbracoContextKey = "ContextMapper.UmbracoContext";
//public const string UmbracoContextKey = "ContextMapper.UmbracoContext";
public const string CultureKey = "ContextMapper.Culture";
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext)
=> Mapper.Map<TSource, TDestination>(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext);
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext, IDictionary<string, object> contextVals)
=> Mapper.Map<TSource, TDestination>(obj, opt =>
{
//set the umb ctx
opt.Items[UmbracoContextKey] = umbracoContext;
//set other supplied context vals
if (contextVals != null)
{
foreach (var contextVal in contextVals)
{
opt.Items[contextVal.Key] = contextVal.Value;
}
}
});
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext, object contextVals)
=> Mapper.Map<TSource, TDestination>(obj, opt =>
{
//set the umb ctx
opt.Items[UmbracoContextKey] = umbracoContext;
//set other supplied context vals
if (contextVals != null)
{
foreach (var contextVal in contextVals.ToDictionary())
{
opt.Items[contextVal.Key] = contextVal.Value;
}
}
});
public static TDestination Map<TSource, TDestination>(TSource obj, IDictionary<string, object> contextVals)
=> Mapper.Map<TSource, TDestination>(obj, opt =>
{
@@ -86,27 +53,6 @@ namespace Umbraco.Web.Models.Mapping
return null;
}
/// <summary>
/// Returns the <see cref="UmbracoContext"/> in the mapping context if one is found
/// </summary>
/// <param name="resolutionContext"></param>
/// <param name="throwIfMissing"></param>
/// <returns></returns>
public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true)
{
if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext)
return umbracoContext;
// better fail fast
if (throwIfMissing)
throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext.");
// fixme - not a good idea at all
// because this falls back to magic singletons
// so really we should remove this line, but then some tests+app breaks ;(
return Umbraco.Web.Composing.Current.UmbracoContext;
}
}
}
@@ -14,13 +14,19 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class MediaMapperProfile : Profile
{
public MediaMapperProfile(IUserService userService, ILocalizedTextService textService, IDataTypeService dataTypeService, IMediaService mediaService, IMediaTypeService mediaTypeService, ILogger logger)
public MediaMapperProfile(
TabsAndPropertiesResolver<IMedia, MediaItemDisplay> tabsAndPropertiesResolver,
ContentTreeNodeUrlResolver<IMedia, MediaTreeController> contentTreeNodeUrlResolver,
IUserService userService,
ILocalizedTextService textService,
IDataTypeService dataTypeService,
IMediaService mediaService,
IMediaTypeService mediaTypeService,
ILogger logger)
{
// create, capture, cache
var mediaOwnerResolver = new OwnerResolver<IMedia>(userService);
var tabsAndPropertiesResolver = new TabsAndPropertiesResolver<IMedia, MediaItemDisplay>(textService);
var childOfListViewResolver = new MediaChildOfListViewResolver(mediaService, mediaTypeService);
var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver<IMedia, MediaTreeController>();
var mediaTypeBasicResolver = new ContentTypeBasicResolver<IMedia, MediaItemDisplay>();
//FROM IMedia TO MediaItemDisplay
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Umbraco.Core.Models;
@@ -11,9 +12,17 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class MemberBasicPropertiesResolver : IValueResolver<IMember, MemberBasic, IEnumerable<ContentPropertyBasic>>
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
public MemberBasicPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor)
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
}
public IEnumerable<ContentPropertyBasic> Resolve(IMember source, MemberBasic destination, IEnumerable<ContentPropertyBasic> destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext();
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
if (umbracoContext == null) throw new InvalidOperationException("Cannot resolve value without an UmbracoContext available");
var result = Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyBasic>>(
// Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties.
@@ -39,4 +48,4 @@ namespace Umbraco.Web.Models.Mapping
return result;
}
}
}
}
@@ -14,16 +14,19 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class MemberMapperProfile : Profile
{
public MemberMapperProfile(IUserService userService, ILocalizedTextService textService, IMemberTypeService memberTypeService, IMemberService memberService)
public MemberMapperProfile(
MemberTabsAndPropertiesResolver tabsAndPropertiesResolver,
MemberTreeNodeUrlResolver memberTreeNodeUrlResolver,
MemberBasicPropertiesResolver memberBasicPropertiesResolver,
IUserService userService,
IMemberTypeService memberTypeService,
IMemberService memberService)
{
// create, capture, cache
var memberOwnerResolver = new OwnerResolver<IMember>(userService);
var tabsAndPropertiesResolver = new MemberTabsAndPropertiesResolver(textService, memberService, userService);
var memberProfiderFieldMappingResolver = new MemberProviderFieldResolver();
var membershipScenarioMappingResolver = new MembershipScenarioResolver(memberTypeService);
var memberDtoPropertiesResolver = new MemberDtoPropertiesResolver();
var memberTreeNodeUrlResolver = new MemberTreeNodeUrlResolver();
var memberBasicPropertiesResolver = new MemberBasicPropertiesResolver();
//FROM MembershipUser TO MediaItemDisplay - used when using a non-umbraco membership provider
CreateMap<MembershipUser, MemberDisplay>().ConvertUsing<MembershipUserTypeConverter>();
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Security;
using AutoMapper;
@@ -22,24 +23,27 @@ namespace Umbraco.Web.Models.Mapping
/// </remarks>
internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver<IMember, MemberDisplay>
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ILocalizedTextService _localizedTextService;
private readonly IMemberService _memberService;
private readonly IUserService _userService;
public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService)
public MemberTabsAndPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService)
: base(localizedTextService)
{
_localizedTextService = localizedTextService;
_memberService = memberService;
_userService = userService;
_umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
_localizedTextService = localizedTextService ?? throw new System.ArgumentNullException(nameof(localizedTextService));
_memberService = memberService ?? throw new System.ArgumentNullException(nameof(memberService));
_userService = userService ?? throw new System.ArgumentNullException(nameof(userService));
}
public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable<string> ignoreProperties, IMemberService memberService, IUserService userService)
public MemberTabsAndPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IEnumerable<string> ignoreProperties, IMemberService memberService, IUserService userService)
: base(localizedTextService, ignoreProperties)
{
_localizedTextService = localizedTextService;
_memberService = memberService;
_userService = userService;
_umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
_localizedTextService = localizedTextService ?? throw new System.ArgumentNullException(nameof(localizedTextService));
_memberService = memberService ?? throw new System.ArgumentNullException(nameof(memberService));
_userService = userService ?? throw new System.ArgumentNullException(nameof(userService));
}
/// <inheritdoc />
@@ -80,7 +84,7 @@ namespace Umbraco.Web.Models.Mapping
}
}
var umbracoContext = context.GetUmbracoContext();
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
if (umbracoContext != null
&& umbracoContext.Security.CurrentUser != null
&& umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
@@ -166,24 +170,25 @@ namespace Umbraco.Web.Models.Mapping
/// <summary>
/// Overridden to assign the IsSensitive property values
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="properties"></param>
/// <param name="context"></param>
/// <returns></returns>
protected override List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties, ResolutionContext context)
protected override List<ContentPropertyDisplay> MapProperties(IContentBase content, List<Property> properties, ResolutionContext context)
{
var result = base.MapProperties(umbracoContext, content, properties, context);
var result = base.MapProperties(content, properties, context);
var member = (IMember)content;
var memberType = member.ContentType;
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
//now update the IsSensitive value
foreach (var prop in result)
{
//check if this property is flagged as sensitive
var isSensitiveProperty = memberType.IsSensitiveProperty(prop.Alias);
//check permissions for viewing sensitive data
if (isSensitiveProperty && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false)
if (isSensitiveProperty && (umbracoContext == null || umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false))
{
//mark this property as sensitive
prop.IsSensitive = true;
@@ -11,9 +11,16 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class MemberTreeNodeUrlResolver : IValueResolver<IMember, MemberDisplay, string>
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
public MemberTreeNodeUrlResolver(IUmbracoContextAccessor umbracoContextAccessor)
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
}
public string Resolve(IMember source, MemberDisplay destination, string destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext(throwIfMissing: false);
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
if (umbracoContext == null) return null;
var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext);
@@ -13,9 +13,9 @@ namespace Umbraco.Web.Models.Mapping
public MemberDisplay Convert(MembershipUser source, MemberDisplay destination, ResolutionContext context)
{
//first convert to IMember
var member = Mapper.Map<MembershipUser, IMember>(source);
var member = Mapper.Map<IMember>(source);
//then convert to MemberDisplay
return ContextMapper.Map<IMember, MemberDisplay>(member, context.GetUmbracoContext());
return Mapper.Map<MemberDisplay>(member);
}
}
}
}
@@ -164,7 +164,6 @@ namespace Umbraco.Web.Models.Mapping
/// <summary>
/// Maps properties on to the generic properties tab
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="tabs"></param>
/// <param name="context"></param>
@@ -172,14 +171,14 @@ namespace Umbraco.Web.Models.Mapping
/// The generic properties tab is responsible for
/// setting up the properties such as Created date, updated date, template selected, etc...
/// </remarks>
protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List<Tab<ContentPropertyDisplay>> tabs, ResolutionContext context)
protected virtual void MapGenericProperties(IContentBase content, List<Tab<ContentPropertyDisplay>> tabs, ResolutionContext context)
{
// add the generic properties tab, for properties that don't belong to a tab
// get the properties, map and translate them, then add the tab
var noGroupProperties = content.GetNonGroupedProperties()
.Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored
.ToList();
var genericproperties = MapProperties(umbracoContext, content, noGroupProperties, context);
var genericproperties = MapProperties(content, noGroupProperties, context);
tabs.Add(new Tab<ContentPropertyDisplay>
{
@@ -225,12 +224,11 @@ namespace Umbraco.Web.Models.Mapping
/// <summary>
/// Maps a list of <see cref="Property"/> to a list of <see cref="ContentPropertyDisplay"/>
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="properties"></param>
/// <param name="context"></param>
/// <returns></returns>
protected virtual List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties, ResolutionContext context)
protected virtual List<ContentPropertyDisplay> MapProperties(IContentBase content, List<Property> properties, ResolutionContext context)
{
//we need to map this way to pass the context through, I don't like it but we'll see what AutoMapper says: https://github.com/AutoMapper/AutoMapper/issues/2588
var result = context.Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyDisplay>>(
@@ -252,15 +250,16 @@ namespace Umbraco.Web.Models.Mapping
{
public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService)
: base(localizedTextService)
{ }
{
}
public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable<string> ignoreProperties)
: base(localizedTextService, ignoreProperties)
{ }
{
}
public virtual IEnumerable<Tab<ContentPropertyDisplay>> Resolve(TSource source, TDestination destination, IEnumerable<Tab<ContentPropertyDisplay>> destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext(throwIfMissing: false); // fixme
var tabs = new List<Tab<ContentPropertyDisplay>>();
// add the tabs, for properties that belong to a tab
@@ -285,7 +284,7 @@ namespace Umbraco.Web.Models.Mapping
continue;
//map the properties
var mappedProperties = MapProperties(umbracoContext, source, properties, context);
var mappedProperties = MapProperties(source, properties, context);
// add the tab
// we need to pick an identifier... there is no "right" way...
@@ -303,7 +302,7 @@ namespace Umbraco.Web.Models.Mapping
});
}
MapGenericProperties(umbracoContext, source, tabs, context);
MapGenericProperties(source, tabs, context);
// activate the first tab, if any
if (tabs.Count > 0)
@@ -90,11 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
public override bool HasValue(string culture = null, string segment = null)
{
ContextualizeVariation(ref culture, ref segment);
var sourceValue = GetSourceValue(culture, segment);
return sourceValue != null &&
(!(sourceValue is string) || string.IsNullOrWhiteSpace((string) sourceValue) == false);
return PropertyType.IsValue(GetSourceValue(culture, segment));
}
// used to cache the CacheValues of this property
@@ -385,7 +385,7 @@ namespace Umbraco.Web
public static bool IsDescendant(this IPublishedContent content, IPublishedContent other)
{
return content.Ancestors().Any(x => x.Id == other.Id);
return other.Level < content.Level && content.Path.InvariantStartsWith(other.Path);
}
public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
@@ -400,7 +400,7 @@ namespace Umbraco.Web
public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other)
{
return content.AncestorsOrSelf().Any(x => x.Id == other.Id);
return content.Path.InvariantStartsWith(other.Path);
}
public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
@@ -415,8 +415,8 @@ namespace Umbraco.Web
public static bool IsAncestor(this IPublishedContent content, IPublishedContent other)
{
// avoid using Descendants(), that's expensive
return other.Ancestors().Any(x => x.Id == content.Id);
// avoid using Descendants(), or Ancestors(), they're expensive
return content.Level < other.Level && other.Path.InvariantStartsWith(content.Path);
}
public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
@@ -431,8 +431,8 @@ namespace Umbraco.Web
public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other)
{
// avoid using DescendantsOrSelf(), that's expensive
return other.AncestorsOrSelf().Any(x => x.Id == content.Id);
// avoid using DescendantsOrSelf() or AncestorsOrSelf(), they're expensive
return other.Path.InvariantStartsWith(content.Path);
}
public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
@@ -78,6 +78,11 @@ namespace Umbraco.Web.Routing
_publishedRouter.PrepareRequest(this);
}
/// <summary>
/// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request.
/// </summary>
public bool IgnorePublishedContentCollisions { get; set; }
#region Events
/// <summary>
@@ -18,10 +18,18 @@ namespace Umbraco.Web.Routing
/// <para>Use when displaying Urls. If errors occur when generating the Urls, they will show in the list.</para>
/// <para>Contains all the Urls that we can figure out (based upon domains, etc).</para>
/// </remarks>
public static IEnumerable<UrlInfo> GetContentUrls(this IContent content, UrlProvider urlProvider, ILocalizedTextService textService, IContentService contentService, ILogger logger)
public static IEnumerable<UrlInfo> GetContentUrls(this IContent content,
PublishedRouter publishedRouter,
UmbracoContext umbracoContext,
ILocalizationService localizationService,
ILocalizedTextService textService,
IContentService contentService,
ILogger logger)
{
if (content == null) throw new ArgumentNullException(nameof(content));
if (urlProvider == null) throw new ArgumentNullException(nameof(urlProvider));
if (publishedRouter == null) throw new ArgumentNullException(nameof(publishedRouter));
if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
if (localizationService == null) throw new ArgumentNullException(nameof(localizationService));
if (textService == null) throw new ArgumentNullException(nameof(textService));
if (contentService == null) throw new ArgumentNullException(nameof(contentService));
if (logger == null) throw new ArgumentNullException(nameof(logger));
@@ -33,12 +41,7 @@ namespace Umbraco.Web.Routing
urls.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished")));
return urls;
}
// fixme inject
// fixme PublishedRouter is stateless and should be a singleton!
var localizationService = Core.Composing.Current.Services.LocalizationService;
var publishedRouter = Core.Composing.Current.Container.GetInstance<PublishedRouter>();
// build a list of urls, for the back-office
// which will contain
// - the 'main' urls, which is what .Url would return, for each culture
@@ -61,7 +64,7 @@ namespace Umbraco.Web.Routing
string url;
try
{
url = urlProvider.GetUrl(content.Id, culture);
url = umbracoContext.UrlProvider.GetUrl(content.Id, culture);
}
catch (Exception e)
{
@@ -83,7 +86,7 @@ namespace Umbraco.Web.Routing
// got a url, deal with collisions, add url
default:
if (!DetectCollision(content, url, urls, culture, publishedRouter, textService)) // detect collisions, etc
if (!DetectCollision(content, url, urls, culture, umbracoContext, publishedRouter, textService)) // detect collisions, etc
urls.Add(UrlInfo.Url(url, culture));
break;
}
@@ -137,13 +140,13 @@ namespace Umbraco.Web.Routing
urls.Add(UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] { parent.Name }), culture));
}
private static bool DetectCollision(IContent content, string url, List<UrlInfo> urls, string culture, PublishedRouter publishedRouter, ILocalizedTextService textService)
private static bool DetectCollision(IContent content, string url, List<UrlInfo> urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService)
{
// test for collisions on the 'main' url
var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute);
if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(UmbracoContext.Current.CleanedUmbracoUrl);
if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl);
uri = UriUtility.UriToUmbraco(uri);
var pcr = publishedRouter.CreateRequest(UmbracoContext.Current, uri);
var pcr = publishedRouter.CreateRequest(umbracoContext, uri);
publishedRouter.TryRouteRequest(pcr);
if (pcr.HasPublishedContent == false)
@@ -152,6 +155,9 @@ namespace Umbraco.Web.Routing
return true;
}
if (pcr.IgnorePublishedContentCollisions)
return false;
if (pcr.PublishedContent.Id != content.Id)
{
var o = pcr.PublishedContent;
@@ -194,7 +194,7 @@ namespace Umbraco.Web.Runtime
composition.Container.RegisterAuto(typeof(UmbracoViewPage<>));
// register published router
composition.Container.Register<PublishedRouter>();
composition.Container.RegisterSingleton<PublishedRouter>();
composition.Container.Register(_ => UmbracoConfig.For.UmbracoSettings().WebRouting);
// register preview SignalR hub
-2
View File
@@ -587,7 +587,6 @@ namespace Umbraco.Web
return ContentQuery.ContentAtRoot();
}
/// <remarks>Had to change to internal for testing.</remarks>
internal static bool ConvertIdObjectToInt(object id, out int intId)
{
switch (id)
@@ -605,7 +604,6 @@ namespace Umbraco.Web
}
}
/// <remarks>Had to change to internal for testing.</remarks>
internal static bool ConvertIdObjectToGuid(object id, out Guid guidId)
{
switch (id)
@@ -6,6 +6,8 @@ using Umbraco.Core.Exceptions;
using Umbraco.Web.Composing;
using Umbraco.Web.Editors;
using Umbraco.Web._Legacy.Actions;
using Umbraco.Core;
using Umbraco.Core.Models;
namespace Umbraco.Web.WebApi.Filters
{
@@ -68,7 +70,25 @@ namespace Umbraco.Web.WebApi.Filters
if (parts.Length == 1)
{
nodeId = (int)actionContext.ActionArguments[parts[0]];
var argument = actionContext.ActionArguments[parts[0]].ToString();
// if the argument is an int, it will parse and can be assigned to nodeId
// if might be a udi, so check that next
// otherwise treat it as a guid - unlikely we ever get here
if (int.TryParse(argument, out int parsedId))
{
nodeId = parsedId;
}
else if (Udi.TryParse(argument, true, out Udi udi))
{
//fixme: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
nodeId = Current.Services.EntityService.GetId(udi).Result;
}
else
{
Guid.TryParse(argument, out Guid key);
//fixme: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
nodeId = Current.Services.EntityService.GetId(key, UmbracoObjectTypes.Document).Result;
}
}
else
{
@@ -89,7 +109,8 @@ namespace Umbraco.Web.WebApi.Filters
if (ContentController.CheckPermissions(
actionContext.Request.Properties,
UmbracoContext.Current.Security.CurrentUser,
//fixme: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
Current.UmbracoContext.Security.CurrentUser,
Current.Services.UserService,
Current.Services.ContentService,
Current.Services.EntityService,