Merge branch 'v9/dev' into v9/task/package-refactor

# Conflicts:
#	src/Umbraco.Web.BackOffice/Controllers/PackageController.cs
#	src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs
This commit is contained in:
Shannon
2021-07-05 15:06:26 -06:00
141 changed files with 3993 additions and 1994 deletions

View File

@@ -6,7 +6,7 @@ body:
- type: input
id: "version"
attributes:
label: "Which Umbraco version are you using?"
label: "Which *exact* Umbraco version are you using? For example: 8.13.1 - don't just write v8"
description: "Use the help icon in the Umbraco backoffice to find the version you're using"
validations:
required: true

View File

@@ -0,0 +1,23 @@

namespace Umbraco.Core.Dashboards
{
public class ContentDashboardSettings
{
private const string DefaultContentDashboardPath = "cms";
/// <summary>
/// Gets a value indicating whether the content dashboard should be available to all users.
/// </summary>
/// <value>
/// <c>true</c> if the dashboard is visible for all user groups; otherwise, <c>false</c>
/// and the default access rules for that dashboard will be in use.
/// </value>
public bool AllowContentDashboardAccessToAllUsers { get; set; } = true;
/// <summary>
/// Gets the path to use when constructing the URL for retrieving data for the content dashboard.
/// </summary>
/// <value>The URL path.</value>
public string ContentDashboardPath { get; set; } = DefaultContentDashboardPath;
}
}

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Core
namespace Umbraco.Cms.Core
{
public static partial class Constants
{
@@ -30,7 +30,7 @@ namespace Umbraco.Cms.Core
public const string WhereNodeId = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeId";
public const string WhereNodeIdX = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeIdX";
public const string SourcesSelectUmbracoNodeJoin = "Umbraco.Web.PublishedCache.NuCache.DataSource.SourcesSelectUmbracoNodeJoin";
public const string ContentSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect";
public const string ContentSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect";
public const string ContentSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesCount";
public const string MediaSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesSelect";
public const string MediaSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesCount";

View File

@@ -188,12 +188,12 @@ namespace Umbraco.Cms.Core.Events
siteUri,
((IUser user, NotificationEmailSubjectParams subject) x)
=> _textService.Localize(
"notifications/mailSubject",
"notifications", "mailSubject",
x.user.GetUserCulture(_textService, _globalSettings),
new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }),
((IUser user, NotificationEmailBodyParams body, bool isHtml) x)
=> _textService.Localize(
x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody",
"notifications", x.isHtml ? "mailBodyHtml" : "mailBody",
x.user.GetUserCulture(_textService, _globalSettings),
new[]
{
@@ -205,7 +205,7 @@ namespace Umbraco.Cms.Core.Events
x.body.ItemId,
//format the summary depending on if it's variant or not
contentVariantGroup.Key == ContentVariation.Culture
? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary }))
? (x.isHtml ? _textService.Localize("notifications", "mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications","mailBodyVariantSummary", new []{ x.body.Summary }))
: x.body.Summary,
x.body.ItemUrl
}));

View File

@@ -31,10 +31,17 @@ namespace Umbraco.Extensions
{
// we have a value
// try to cast or convert it
var value = property.GetValue(culture, segment);
if (value is T valueAsT) return valueAsT;
var value = property.GetValue(culture, segment);
if (value is T valueAsT)
{
return valueAsT;
}
var valueConverted = value.TryConvertTo<T>();
if (valueConverted) return valueConverted.Result;
if (valueConverted)
{
return valueConverted.Result;
}
// cannot cast nor convert the value, nothing we can return but 'default'
// note: we don't want to fallback in that case - would make little sense
@@ -43,14 +50,23 @@ namespace Umbraco.Extensions
// we don't have a value, try fallback
if (publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue))
{
return fallbackValue;
}
// we don't have a value - neither direct nor fallback
// give a chance to the converter to return something (eg empty enumerable)
var noValue = property.GetValue(culture, segment);
if (noValue is T noValueAsT) return noValueAsT;
if (noValue is T noValueAsT)
{
return noValueAsT;
}
var noValueConverted = noValue.TryConvertTo<T>();
if (noValueConverted) return noValueConverted.Result;
if (noValueConverted)
{
return noValueConverted.Result;
}
// cannot cast noValue nor convert it, nothing we can return but 'default'
return default;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
@@ -54,7 +54,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks
/// <summary>
/// Gets the message for when the check has succeeded.
/// </summary>
public virtual string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck/checkSuccessMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath });
public virtual string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck", "checkSuccessMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath });
/// <summary>
/// Gets the message for when the check has failed.
@@ -62,10 +62,10 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks
public virtual string CheckErrorMessage =>
ValueComparisonType == ValueComparisonType.ShouldEqual
? LocalizedTextService.Localize(
"healthcheck/checkErrorMessageDifferentExpectedValue",
"healthcheck", "checkErrorMessageDifferentExpectedValue",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath })
: LocalizedTextService.Localize(
"healthcheck/checkErrorMessageUnexpectedValue",
"healthcheck", "checkErrorMessageUnexpectedValue",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath });
/// <inheritdoc/>

View File

@@ -78,7 +78,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration
/// </summary>
public override string CheckSuccessMessage =>
_textService.Localize(
"healthcheck/macroErrorModeCheckSuccessMessage",
"healthcheck","macroErrorModeCheckSuccessMessage",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value });
/// <summary>
@@ -86,7 +86,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration
/// </summary>
public override string CheckErrorMessage =>
_textService.Localize(
"healthcheck/macroErrorModeCheckErrorMessage",
"healthcheck","macroErrorModeCheckErrorMessage",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value });
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
@@ -50,11 +50,11 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration
/// <inheritdoc/>
public override string CheckSuccessMessage =>
LocalizedTextService.Localize("healthcheck/notificationEmailsCheckSuccessMessage",
LocalizedTextService.Localize("healthcheck","notificationEmailsCheckSuccessMessage",
new[] { CurrentValue ?? "&lt;null&gt;" });
/// <inheritdoc/>
public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck/notificationEmailsCheckErrorMessage", new[] { DefaultFromEmail });
public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck","notificationEmailsCheckErrorMessage", new[] { DefaultFromEmail });
/// <inheritdoc/>
public override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Configuration.NotificationEmailCheck;

View File

@@ -51,9 +51,9 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.LiveEnvironment
public override string CurrentValue => _hostingSettings.CurrentValue.Debug.ToString();
/// <inheritdoc/>
public override string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck/compilationDebugCheckSuccessMessage");
public override string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck","compilationDebugCheckSuccessMessage");
/// <inheritdoc/>
public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck/compilationDebugCheckErrorMessage");
public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck","compilationDebugCheckErrorMessage");
}
}

View File

@@ -95,12 +95,12 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security
}
message = success
? LocalizedTextService.Localize($"healthcheck/{_localizedTextPrefix}CheckHeaderFound")
: LocalizedTextService.Localize($"healthcheck/{_localizedTextPrefix}CheckHeaderNotFound");
? LocalizedTextService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderFound")
: LocalizedTextService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderNotFound");
}
catch (Exception ex)
{
message = LocalizedTextService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url.ToString(), ex.Message });
message = LocalizedTextService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url.ToString(), ex.Message });
}
return

View File

@@ -68,12 +68,12 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security
.ToArray();
success = headersFound.Any() == false;
message = success
? _textService.Localize("healthcheck/excessiveHeadersNotFound")
: _textService.Localize("healthcheck/excessiveHeadersFound", new[] { string.Join(", ", headersFound) });
? _textService.Localize("healthcheck","excessiveHeadersNotFound")
: _textService.Localize("healthcheck","excessiveHeadersFound", new[] { string.Join(", ", headersFound) });
}
catch (Exception ex)
{
message = _textService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url.ToString(), ex.Message });
message = _textService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url.ToString(), ex.Message });
}
return

View File

@@ -102,23 +102,23 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security
if (s_certificateDaysToExpiry <= 0)
{
result = StatusResultType.Error;
message = _textService.Localize("healthcheck/httpsCheckExpiredCertificate");
message = _textService.Localize("healthcheck","httpsCheckExpiredCertificate");
}
else if (s_certificateDaysToExpiry < numberOfDaysForExpiryWarning)
{
result = StatusResultType.Warning;
message = _textService.Localize("healthcheck/httpsCheckExpiringCertificate", new[] { s_certificateDaysToExpiry.ToString() });
message = _textService.Localize("healthcheck","httpsCheckExpiringCertificate", new[] { s_certificateDaysToExpiry.ToString() });
}
else
{
result = StatusResultType.Success;
message = _textService.Localize("healthcheck/httpsCheckValidCertificate");
message = _textService.Localize("healthcheck","httpsCheckValidCertificate");
}
}
else
{
result = StatusResultType.Error;
message = _textService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url, response.ReasonPhrase });
message = _textService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url, response.ReasonPhrase });
}
}
catch (Exception ex)
@@ -127,12 +127,12 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security
if (exception != null)
{
message = exception.Status == WebExceptionStatus.TrustFailure
? _textService.Localize("healthcheck/httpsCheckInvalidCertificate", new[] { exception.Message })
: _textService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url, exception.Message });
? _textService.Localize("healthcheck","httpsCheckInvalidCertificate", new[] { exception.Message })
: _textService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url, exception.Message });
}
else
{
message = _textService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url, ex.Message });
message = _textService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url, ex.Message });
}
result = StatusResultType.Error;
@@ -152,7 +152,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security
Uri uri = _hostingEnvironment.ApplicationMainUrl;
var success = uri.Scheme == "https";
return Task.FromResult(new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" }))
return Task.FromResult(new HealthCheckStatus(_textService.Localize("healthcheck","httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" }))
{
ResultType = success ? StatusResultType.Success : StatusResultType.Error,
ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps
@@ -168,13 +168,13 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security
StatusResultType resultType;
if (uri.Scheme != "https")
{
resultMessage = _textService.Localize("healthcheck/httpsCheckConfigurationRectifyNotPossible");
resultMessage = _textService.Localize("healthcheck","httpsCheckConfigurationRectifyNotPossible");
resultType = StatusResultType.Info;
}
else
{
resultMessage = _textService.Localize(
"healthcheck/httpsCheckConfigurationCheckResult",
"healthcheck","httpsCheckConfigurationCheckResult",
new[] { httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not" });
resultType = httpsSettingEnabled ? StatusResultType.Success : StatusResultType.Error;
}

View File

@@ -56,21 +56,21 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Services
string message;
if (smtpSettings == null)
{
message = _textService.Localize("healthcheck/smtpMailSettingsNotFound");
message = _textService.Localize("healthcheck", "smtpMailSettingsNotFound");
}
else
{
if (string.IsNullOrEmpty(smtpSettings.Host))
{
message = _textService.Localize("healthcheck/smtpMailSettingsHostNotConfigured");
message = _textService.Localize("healthcheck", "smtpMailSettingsHostNotConfigured");
}
else
{
success = CanMakeSmtpConnection(smtpSettings.Host, smtpSettings.Port);
message = success
? _textService.Localize("healthcheck/smtpMailSettingsConnectionSuccess")
? _textService.Localize("healthcheck", "smtpMailSettingsConnectionSuccess")
: _textService.Localize(
"healthcheck/smtpMailSettingsConnectionFail",
"healthcheck", "smtpMailSettingsConnectionFail",
new[] { smtpSettings.Host, smtpSettings.Port.ToString() });
}
}

View File

@@ -59,7 +59,7 @@ namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods
return;
}
var message = _textService.Localize("healthcheck/scheduledHealthCheckEmailBody", new[]
var message = _textService.Localize("healthcheck","scheduledHealthCheckEmailBody", new[]
{
DateTime.Now.ToShortDateString(),
DateTime.Now.ToShortTimeString(),
@@ -70,7 +70,7 @@ namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods
// you can identify the site that these results are for.
var host = _hostingEnvironment.ApplicationMainUrl?.ToString();
var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject", new[] { host });
var subject = _textService.Localize("healthcheck","scheduledHealthCheckEmailSubject", new[] { host });
var mailMessage = CreateMailMessage(subject, message);

View File

@@ -54,7 +54,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
// localize content app names
foreach (var app in apps)
{
var localizedAppName = _localizedTextService.Localize($"apps/{app.Alias}");
var localizedAppName = _localizedTextService.Localize("apps", app.Alias);
if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false)
{
app.Name = localizedAppName;

View File

@@ -150,7 +150,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
if(!isCultureVariant && !isSegmentVariant)
{
return _localizedTextService.Localize("general/default");
return _localizedTextService.Localize("general", "default");
}
var parts = new List<string>();

View File

@@ -72,7 +72,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1")
{
isLockedOutProperty.View = "readonlyvalue";
isLockedOutProperty.Value = _localizedTextService.Localize("general/no");
isLockedOutProperty.Value = _localizedTextService.Localize("general", "no");
}
if (_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser != null
@@ -108,14 +108,14 @@ namespace Umbraco.Cms.Core.Models.Mapping
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}id",
Label = _localizedTextService.Localize("general/id"),
Label = _localizedTextService.Localize("general","id"),
Value = new List<string> {member.Id.ToString(), member.Key.ToString()},
View = "idwithguid"
},
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype",
Label = _localizedTextService.Localize("content/membertype"),
Label = _localizedTextService.Localize("content","membertype"),
Value = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, member.ContentType.Name),
View = _propertyEditorCollection[Constants.PropertyEditors.Aliases.Label].GetValueEditor().View
},
@@ -123,7 +123,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email",
Label = _localizedTextService.Localize("general/email"),
Label = _localizedTextService.Localize("general","email"),
Value = member.Email,
View = "email",
Validation = {Mandatory = true}
@@ -131,8 +131,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password",
Label = _localizedTextService.Localize("password"),
Label = _localizedTextService.Localize(null,"password"),
Value = new Dictionary<string, object>
{
// TODO: why ignoreCase, what are we doing here?!
@@ -146,7 +145,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup",
Label = _localizedTextService.Localize("content/membergroup"),
Label = _localizedTextService.Localize("content","membergroup"),
Value = GetMemberGroupValue(member.Username),
View = "membergroups",
Config = new Dictionary<string, object> {{"IsRequired", true}}
@@ -222,7 +221,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
var prop = new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login",
Label = localizedText.Localize("login"),
Label = localizedText.Localize(null,"login"),
Value = member.Username
};

View File

@@ -35,7 +35,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
private void Map(ISection source, Section target, MapperContext context)
{
target.Alias = source.Alias;
target.Name = _textService.Localize("sections/" + source.Alias);
target.Name = _textService.Localize("sections", source.Alias);
}
// Umbraco.Code.MapAll

View File

@@ -59,7 +59,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
tabs.Add(new Tab<ContentPropertyDisplay>
{
Id = 0,
Label = LocalizedTextService.Localize("general/properties"),
Label = LocalizedTextService.Localize("general", "properties"),
Alias = "Generic properties",
Properties = genericproperties
});

View File

@@ -284,8 +284,8 @@ namespace Umbraco.Cms.Core.Models.Mapping
{
target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName);
target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator);
target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Document, "content/contentRoot", context);
target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Media, "media/mediaRoot", context);
target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService,_appCaches), UmbracoObjectTypes.Document, "content","contentRoot", context);
target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Media, "media","mediaRoot", context);
target.CreateDate = source.CreateDate;
target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString();
target.Email = source.Email;
@@ -300,8 +300,8 @@ namespace Umbraco.Cms.Core.Models.Mapping
target.Navigation = CreateUserEditorNavigation();
target.ParentId = -1;
target.Path = "-1," + source.Id;
target.StartContentIds = GetStartNodes(source.StartContentIds.ToArray(), UmbracoObjectTypes.Document, "content/contentRoot", context);
target.StartMediaIds = GetStartNodes(source.StartMediaIds.ToArray(), UmbracoObjectTypes.Media, "media/mediaRoot", context);
target.StartContentIds = GetStartNodes(source.StartContentIds.ToArray(), UmbracoObjectTypes.Document, "content","contentRoot", context);
target.StartMediaIds = GetStartNodes(source.StartMediaIds.ToArray(), UmbracoObjectTypes.Media, "media","mediaRoot", context);
target.UpdateDate = source.UpdateDate;
target.UserGroups = context.MapEnumerable<IReadOnlyUserGroup, UserGroupBasic>(source.Groups);
target.Username = source.Username;
@@ -358,12 +358,12 @@ namespace Umbraco.Cms.Core.Models.Mapping
if (sourceStartMediaId > 0)
target.MediaStartNode = context.Map<EntityBasic>(_entityService.Get(sourceStartMediaId.Value, UmbracoObjectTypes.Media));
else if (sourceStartMediaId == -1)
target.MediaStartNode = CreateRootNode(_textService.Localize("media/mediaRoot"));
target.MediaStartNode = CreateRootNode(_textService.Localize("media", "mediaRoot"));
if (sourceStartContentId > 0)
target.ContentStartNode = context.Map<EntityBasic>(_entityService.Get(sourceStartContentId.Value, UmbracoObjectTypes.Document));
else if (sourceStartContentId == -1)
target.ContentStartNode = CreateRootNode(_textService.Localize("content/contentRoot"));
target.ContentStartNode = CreateRootNode(_textService.Localize("content", "contentRoot"));
if (target.Icon.IsNullOrWhiteSpace())
target.Icon = Constants.Icons.UserGroup;
@@ -375,10 +375,10 @@ namespace Umbraco.Cms.Core.Models.Mapping
=> new Permission
{
Category = action.Category.IsNullOrWhiteSpace()
? _textService.Localize($"actionCategories/{Constants.Conventions.PermissionCategories.OtherCategory}")
: _textService.Localize($"actionCategories/{action.Category}"),
Name = _textService.Localize($"actions/{action.Alias}"),
Description = _textService.Localize($"actionDescriptions/{action.Alias}"),
? _textService.Localize("actionCategories",Constants.Conventions.PermissionCategories.OtherCategory)
: _textService.Localize("actionCategories", action.Category),
Name = _textService.Localize("actions", action.Alias),
Description = _textService.Localize("actionDescriptions", action.Alias),
Icon = action.Icon,
Checked = source.Permissions != null && source.Permissions.Contains(action.Letter.ToString(CultureInfo.InvariantCulture)),
PermissionCode = action.Letter.ToString(CultureInfo.InvariantCulture)
@@ -394,14 +394,14 @@ namespace Umbraco.Cms.Core.Models.Mapping
private static string MapContentTypeIcon(IEntitySlim entity)
=> entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null;
private IEnumerable<EntityBasic> GetStartNodes(int[] startNodeIds, UmbracoObjectTypes objectType, string localizedKey, MapperContext context)
private IEnumerable<EntityBasic> GetStartNodes(int[] startNodeIds, UmbracoObjectTypes objectType, string localizedArea,string localizedAlias, MapperContext context)
{
if (startNodeIds.Length <= 0)
return Enumerable.Empty<EntityBasic>();
var startNodes = new List<EntityBasic>();
if (startNodeIds.Contains(-1))
startNodes.Add(CreateRootNode(_textService.Localize(localizedKey)));
startNodes.Add(CreateRootNode(_textService.Localize(localizedArea, localizedAlias)));
var mediaItems = _entityService.GetAll(objectType, startNodeIds);
startNodes.AddRange(context.MapEnumerable<IEntitySlim, EntityBasic>(mediaItems));
@@ -417,7 +417,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
Active = true,
Alias = "details",
Icon = "icon-umb-users",
Name = _textService.Localize("general/user"),
Name = _textService.Localize("general","user"),
View = "views/users/views/user/details.html"
}
};

View File

@@ -32,12 +32,9 @@ namespace Umbraco.Cms.Core.Models.Trees
public MenuItem(string alias, ILocalizedTextService textService)
: this()
{
var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture);
values.TryGetValue($"visuallyHiddenTexts/{alias}_description", out var textDescription);
Alias = alias;
Name = textService.Localize($"actions/{Alias}");
TextDescription = textDescription;
Name = textService.Localize("actions", Alias);
TextDescription = textService.Localize("visuallyHiddenTexts", alias + "_description", Thread.CurrentThread.CurrentUICulture);
}
/// <summary>

View File

@@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
/// Determines if a property type's value should be compressed in memory
/// </summary>
/// <remarks>
///
///
/// </remarks>
public interface IPropertyCacheCompression
{

View File

@@ -5,9 +5,6 @@
/// </summary>
public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig
{
[ConfigurationField("notice", "You can NOT change the property editor", "obsoletemediapickernotice")]
public bool Notice { get; set; }
[ConfigurationField("multiPicker", "Pick multiple items", "boolean")]
public bool Multiple { get; set; }

View File

@@ -2,8 +2,10 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Cms.Core.PropertyEditors
namespace Umbraco.Core.PropertyEditors
{
/// <summary>

View File

@@ -48,7 +48,7 @@ namespace Umbraco.Extensions
if (content.Published == false)
{
result.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished")));
result.Add(UrlInfo.Message(textService.Localize("content", "itemNotPublished")));
return result;
}
@@ -145,7 +145,7 @@ namespace Umbraco.Extensions
// deal with exceptions
case "#ex":
result.Add(UrlInfo.Message(textService.Localize("content/getUrlException"), culture));
result.Add(UrlInfo.Message(textService.Localize("content", "getUrlException"), culture));
break;
// got a URL, deal with collisions, add URL
@@ -182,17 +182,17 @@ namespace Umbraco.Extensions
if (parent == null)
{
// oops, internal error
return UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture);
return UrlInfo.Message(textService.Localize("content", "parentNotPublishedAnomaly"), culture);
}
else if (!parent.Published)
{
// totally not published
return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] { parent.Name }), culture);
return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), culture);
}
else
{
// culture not published
return UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] {parent.Name}), culture);
return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] {parent.Name}), culture);
}
}
@@ -221,7 +221,7 @@ namespace Umbraco.Extensions
logger.LogDebug(logMsg, url, uri, culture);
}
var urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture);
var urlInfo = UrlInfo.Message(textService.Localize("content", "routeErrorCannotRoute"), culture);
return Attempt.Succeed(urlInfo);
}
@@ -243,7 +243,7 @@ namespace Umbraco.Extensions
l.Reverse();
var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")";
var urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture);
var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture);
return Attempt.Succeed(urlInfo);
}

View File

@@ -490,6 +490,11 @@ namespace Umbraco.Cms.Core.Services
/// </summary>
IContent Create(string name, int parentId, string documentTypeAlias, int userId = Constants.Security.SuperUserId);
/// <summary>
/// Creates a document
/// </summary>
IContent Create(string name, int parentId, IContentType contentType, int userId = Constants.Security.SuperUserId);
/// <summary>
/// Creates a document.
/// </summary>

View File

@@ -3,6 +3,10 @@ using System.Globalization;
namespace Umbraco.Cms.Core.Services
{
// TODO: This needs to be merged into one interface in v9, but better yet
// the Localize method should just the based on area + alias and we should remove
// the one with the 'key' (the concatenated area/alias) to ensure that we never use that again.
/// <summary>
/// The entry point to localize any key in the text storage source for a given culture
/// </summary>
@@ -15,11 +19,19 @@ namespace Umbraco.Cms.Core.Services
/// <summary>
/// Localize a key with variables
/// </summary>
/// <param name="key"></param>
/// <param name="area"></param>
/// <param name="alias"></param>
/// <param name="culture"></param>
/// <param name="tokens">This can be null</param>
/// <returns></returns>
string Localize(string key, CultureInfo culture, IDictionary<string, string> tokens = null);
string Localize(string area, string alias, CultureInfo culture, IDictionary<string, string> tokens = null);
/// <summary>
/// Returns all key/values in storage for the given culture
/// </summary>
/// <returns></returns>
IDictionary<string, IDictionary<string, string>> GetAllStoredValuesByAreaAndAlias(CultureInfo culture);
/// <summary>
/// Returns all key/values in storage for the given culture

View File

@@ -1,6 +1,7 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -15,88 +16,81 @@ namespace Umbraco.Extensions
/// </summary>
public static class LocalizedTextServiceExtensions
{
public static string Localize<T>(this ILocalizedTextService manager, string area, T key)
where T: System.Enum
{
var fullKey = string.Join("/", area, key);
return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture);
}
public static string Localize<T>(this ILocalizedTextService manager, string area, T key)
where T: System.Enum =>
manager.Localize(area, key.ToString(), Thread.CurrentThread.CurrentUICulture);
public static string Localize(this ILocalizedTextService manager, string area, string key)
{
var fullKey = string.Join("/", area, key);
return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture);
}
public static string Localize(this ILocalizedTextService manager, string area, string alias)
=> manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture);
/// <summary>
/// Localize using the current thread culture
/// </summary>
/// <param name="manager"></param>
/// <param name="key"></param>
/// <param name="area"></param>
/// <param name="alias"></param>
/// <param name="tokens"></param>
/// <returns></returns>
public static string Localize(this ILocalizedTextService manager, string key, string[] tokens)
{
return manager.Localize(key, Thread.CurrentThread.CurrentUICulture, tokens);
}
public static string Localize(this ILocalizedTextService manager, string area, string alias, string[] tokens)
=> manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, ConvertToDictionaryVars(tokens));
/// <summary>
/// Localize using the current thread culture
/// </summary>
/// <param name="manager"></param>
/// <param name="key"></param>
/// <param name="area"></param>
/// <param name="alias"></param>
/// <param name="tokens"></param>
/// <returns></returns>
public static string Localize(this ILocalizedTextService manager, string key, IDictionary<string, string> tokens = null)
{
return manager.Localize(key, Thread.CurrentThread.CurrentUICulture, tokens);
}
public static string Localize(this ILocalizedTextService manager, string area, string alias, IDictionary<string, string> tokens = null)
=> manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens);
/// <summary>
/// Localize a key without any variables
/// </summary>
/// <param name="manager"></param>
/// <param name="key"></param>
/// <param name="area"></param>
/// <param name="alias"></param>
/// <param name="culture"></param>
/// <param name="tokens"></param>
/// <returns></returns>
public static string Localize(this ILocalizedTextService manager, string key, CultureInfo culture, string[] tokens)
{
return manager.Localize(key, culture, ConvertToDictionaryVars(tokens));
}
public static string Localize(this ILocalizedTextService manager, string area, string alias, CultureInfo culture, string[] tokens)
=> manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens);
/// <summary>
/// Convert an array of strings to a dictionary of indices -> values
/// </summary>
/// <param name="variables"></param>
/// <returns></returns>
internal static IDictionary<string, string> ConvertToDictionaryVars(string[] variables)
{
if (variables == null) return null;
if (variables.Any() == false) return null;
/// <summary>
/// Convert an array of strings to a dictionary of indices -> values
/// </summary>
/// <param name="variables"></param>
/// <returns></returns>
internal static IDictionary<string, string> ConvertToDictionaryVars(string[] variables)
{
if (variables == null) return null;
if (variables.Any() == false) return null;
return variables.Select((s, i) => new { index = i.ToString(CultureInfo.InvariantCulture), value = s })
.ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value);
}
return variables.Select((s, i) => new { index = i.ToString(CultureInfo.InvariantCulture), value = s })
.ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value);
}
public static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, ICultureDictionary cultureDictionary, string text)
{
if (text == null)
return null;
public static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, ICultureDictionary cultureDictionary, string text)
{
if (text == null)
return null;
if (text.StartsWith("#") == false)
return text;
if (text.StartsWith("#") == false)
return text;
text = text.Substring(1);
var value = cultureDictionary[text];
if (value.IsNullOrWhiteSpace() == false)
{
return value;
}
text = text.Substring(1);
var value = cultureDictionary[text];
if (value.IsNullOrWhiteSpace() == false)
{
return value;
}
value = manager.Localize(text.Replace('_', '/'));
return value.StartsWith("[") ? text : value;
}
var areaAndKey = text.Split('_');
value = manager.Localize(areaAndKey[0], areaAndKey[1]);
return value.StartsWith("[") ? text : value;
}
}
}

View File

@@ -59,7 +59,7 @@ namespace Umbraco.Cms.Core.Trees
var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture);
values.TryGetValue($"visuallyHiddenTexts/{item.Alias}Description", out var textDescription);
var menuItem = new MenuItem(item, textService.Localize($"actions/{item.Alias}"))
var menuItem = new MenuItem(item, textService.Localize($"actions", item.Alias))
{
SeparatorBefore = hasSeparator,
OpensDialog = opensDialog,

View File

@@ -54,7 +54,7 @@ namespace Umbraco.Cms.Core.Trees
var label = $"[{tree.TreeAlias}]";
// try to look up a the localized tree header matching the tree alias
var localizedLabel = textService.Localize("treeHeaders/" + tree.TreeAlias);
var localizedLabel = textService.Localize("treeHeader", tree.TreeAlias);
// if the localizedLabel returns [alias] then return the title if it's defined
if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase))

View File

@@ -8,6 +8,7 @@ using System.Text;
using System.Text.RegularExpressions;
using Examine;
using Examine.Search;
using Examine.Search;
using Lucene.Net.QueryParsers.Classic;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
@@ -61,6 +62,15 @@ namespace Umbraco.Cms.Infrastructure.Examine
var indexName = Constants.UmbracoIndexes.InternalIndexName;
var fields = _treeSearcherFields.GetBackOfficeFields().ToList();
ISet<string> fieldsToLoad = new HashSet<string>(_treeSearcherFields.GetBackOfficeFieldsToLoad());
// TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string
// manipulation for things like start paths, member types, etc...
//if (Examine.ExamineExtensions.TryParseLuceneQuery(query))
//{
//}
//special GUID check since if a user searches on one specifically we need to escape it
if (Guid.TryParse(query, out var g))
{
@@ -75,6 +85,11 @@ namespace Umbraco.Cms.Infrastructure.Examine
indexName = Constants.UmbracoIndexes.MembersIndexName;
type = "member";
fields.AddRange(_treeSearcherFields.GetBackOfficeMembersFields());
foreach(var field in _treeSearcherFields.GetBackOfficeMembersFieldsToLoad())
{
fieldsToLoad.Add(field);
}
if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1")
{
sb.Append("+__NodeTypeAlias:");
@@ -85,6 +100,11 @@ namespace Umbraco.Cms.Infrastructure.Examine
case UmbracoEntityTypes.Media:
type = "media";
fields.AddRange(_treeSearcherFields.GetBackOfficeMediaFields());
foreach (var field in _treeSearcherFields.GetBackOfficeMediaFieldsToLoad())
{
fieldsToLoad.Add(field);
}
var allMediaStartNodes = currentUser != null
? currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches)
: Array.Empty<int>();
@@ -93,6 +113,10 @@ namespace Umbraco.Cms.Infrastructure.Examine
case UmbracoEntityTypes.Document:
type = "content";
fields.AddRange(_treeSearcherFields.GetBackOfficeDocumentFields());
foreach (var field in _treeSearcherFields.GetBackOfficeDocumentFieldsToLoad())
{
fieldsToLoad.Add(field);
}
var allContentStartNodes = currentUser != null
? currentUser.CalculateContentStartNodeIds(_entityService, _appCaches)
: Array.Empty<int>();
@@ -113,7 +137,9 @@ namespace Umbraco.Cms.Infrastructure.Examine
var result = index.Searcher
.CreateQuery()
.NativeQuery(sb.ToString())
.NativeQuery(sb.ToString())
.SelectFields(fieldsToLoad)
//only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested
.Execute(QueryOptions.SkipTake(Convert.ToInt32(pageSize * pageIndex), pageSize));
totalFound = result.TotalItemCount;

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex, IDisposable
{
private readonly ILogger<UmbracoContentIndex> _logger;
private readonly ISet<string> _idOnlyFieldSet = new HashSet<string> { "id" };
public UmbracoContentIndex(
ILoggerFactory loggerFactory,
string name,
@@ -133,7 +133,8 @@ namespace Umbraco.Cms.Infrastructure.Examine
var rawQuery = $"{UmbracoExamineFieldNames.IndexPathFieldName}:{descendantPath}";
var c = Searcher.CreateQuery();
var filtered = c.NativeQuery(rawQuery);
var results = filtered.Execute();
var selectedFields = filtered.SelectFields(_idOnlyFieldSet);
var results = selectedFields.Execute();
_logger.
LogDebug("DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount);

View File

@@ -88,7 +88,7 @@ namespace Umbraco.Cms.Core.Events
item.Entity.WriterId,
item.Entity.Id,
ObjectTypes.GetName(UmbracoObjectTypes.Document),
string.Format(_textService.Localize("recycleBin/contentTrashed"), item.Entity.Id, originalParentId)
string.Format(_textService.Localize("recycleBin","contentTrashed"), item.Entity.Id, originalParentId)
);
}
}
@@ -143,7 +143,7 @@ namespace Umbraco.Cms.Core.Events
item.Entity.CreatorId,
item.Entity.Id,
ObjectTypes.GetName(UmbracoObjectTypes.Media),
string.Format(_textService.Localize("recycleBin/mediaTrashed"), item.Entity.Id, originalParentId)
string.Format(_textService.Localize("recycleBin","mediaTrashed"), item.Entity.Id, originalParentId)
);
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Examine;
using Examine.Search;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Extensions;
@@ -18,6 +19,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
private readonly IIndex _index;
private static readonly string[] s_ignoreProperties = { "Description" };
private readonly ISet<string> _idOnlyFieldSet = new HashSet<string> { "id" };
public GenericIndexDiagnostics(IIndex index) => _index = index;
public int DocumentCount => -1; //unknown
@@ -31,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
try
{
var result = _index.Searcher.Search("test");
var result = _index.Searcher.CreateQuery().ManagedQuery("test").SelectFields(_idOnlyFieldSet).Execute(new QueryOptions(0, 1));
return Attempt<string>.Succeed(); //if we can search we'll assume it's healthy
}
catch (Exception e)

View File

@@ -8,20 +8,28 @@ namespace Umbraco.Cms.Infrastructure.Examine
public interface IUmbracoTreeSearcherFields
{
/// <summary>
/// Propagate list of searchable fields for all node types
/// The default index fields that are searched on in the back office search for umbraco content entities.
/// </summary>
IEnumerable<string> GetBackOfficeFields();
/// <summary>
/// Propagate list of searchable fields for Members
/// The additional index fields that are searched on in the back office for member entities.
/// </summary>
IEnumerable<string> GetBackOfficeMembersFields();
/// <summary>
/// Propagate list of searchable fields for Media
/// The additional index fields that are searched on in the back office for media entities.
/// </summary>
IEnumerable<string> GetBackOfficeMediaFields();
/// <summary>
/// Propagate list of searchable fields for Documents
/// The additional index fields that are searched on in the back office for document entities.
/// </summary>
IEnumerable<string> GetBackOfficeDocumentFields();
ISet<string> GetBackOfficeFieldsToLoad();
ISet<string> GetBackOfficeMembersFieldsToLoad();
ISet<string> GetBackOfficeDocumentFieldsToLoad();
ISet<string> GetBackOfficeMediaFieldsToLoad();
}
}

View File

@@ -21,5 +21,8 @@ namespace Umbraco.Cms.Infrastructure.Examine
public const string VariesByCultureFieldName = ExamineFieldNames.SpecialFieldPrefix + "VariesByCulture";
public const string NodeNameFieldName = "nodeName";
public const string ItemIdFieldName ="__NodeId";
public const string CategoryFieldName = "__IndexType";
public const string ItemTypeFieldName = "__NodeTypeAlias";
}
}

View File

@@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
namespace Umbraco.Extensions
{
public static class MediaPicker3ConfigurationExtensions
{
/// <summary>
/// Applies the configuration to ensure only valid crops are kept and have the correct width/height.
/// </summary>
/// <param name="configuration">The configuration.</param>
public static void ApplyConfiguration(this ImageCropperValue imageCropperValue, MediaPicker3Configuration configuration)
{
var crops = new List<ImageCropperValue.ImageCropperCrop>();
var configuredCrops = configuration?.Crops;
if (configuredCrops != null)
{
foreach (var configuredCrop in configuredCrops)
{
var crop = imageCropperValue.Crops?.FirstOrDefault(x => x.Alias == configuredCrop.Alias);
crops.Add(new ImageCropperValue.ImageCropperCrop
{
Alias = configuredCrop.Alias,
Width = configuredCrop.Width,
Height = configuredCrop.Height,
Coordinates = crop?.Coordinates
});
}
}
imageCropperValue.Crops = crops;
if (configuration?.EnableLocalFocalPoint == false)
{
imageCropperValue.FocalPoint = null;
}
}
}
}

View File

@@ -35,6 +35,29 @@ namespace Umbraco.Cms.Core
IEnumerable<IPublishedContent> Media(IEnumerable<Guid> ids);
IEnumerable<IPublishedContent> MediaAtRoot();
/// <summary>
/// Searches content.
/// </summary>
/// <param name="term">The term to search.</param>
/// <param name="skip">The amount of results to skip.</param>
/// <param name="take">The amount of results to take/return.</param>
/// <param name="totalRecords">The total amount of records.</param>
/// <param name="culture">The culture (defaults to a culture insensitive search).</param>
/// <param name="indexName">The name of the index to search (defaults to <see cref="Constants.UmbracoIndexes.ExternalIndexName" />).</param>
/// <param name="loadedFields">The fields to load in the results of the search (defaults to all fields loaded).</param>
/// <returns>
/// The search results.
/// </returns>
/// <remarks>
/// <para>
/// When the <paramref name="culture" /> is not specified or is *, all cultures are searched.
/// To search for only invariant documents and fields use null.
/// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents.
/// </para>
/// <para>While enumerating results, the ambient culture is changed to be the searched culture.</para>
/// </remarks>
IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName, ISet<string> loadedFields = null);
/// <summary>
/// Searches content.
/// </summary>

View File

@@ -177,7 +177,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
target.Name = source.Values.ContainsKey(UmbracoExamineFieldNames.NodeNameFieldName) ? source.Values[UmbracoExamineFieldNames.NodeNameFieldName] : "[no name]";
var culture = context.GetCulture();
var culture = context.GetCulture()?.ToLowerInvariant();
if(culture.IsNullOrWhiteSpace() == false)
{
target.Name = source.Values.ContainsKey($"nodeName_{culture}") ? source.Values[$"nodeName_{culture}"] : target.Name;

View File

@@ -1,17 +1,79 @@
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
namespace Umbraco.Cms.Core.Models
namespace Umbraco.Core.Models
{
/// <summary>
/// Model used in Razor Views for rendering
/// Represents a media item with local crops.
/// </summary>
public class MediaWithCrops
/// <seealso cref="PublishedContentWrapped" />
public class MediaWithCrops : PublishedContentWrapped
{
public IPublishedContent MediaItem { get; set; }
public ImageCropperValue LocalCrops { get; set; }
/// <summary>
/// Gets the content/media item.
/// </summary>
/// <value>
/// The content/media item.
/// </value>
public IPublishedContent Content => Unwrap();
/// <summary>
/// Gets the local crops.
/// </summary>
/// <value>
/// The local crops.
/// </value>
public ImageCropperValue LocalCrops { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MediaWithCrops" /> class.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="publishedValueFallback">The published value fallback.</param>
/// <param name="localCrops">The local crops.</param>
public MediaWithCrops(IPublishedContent content, IPublishedValueFallback publishedValueFallback, ImageCropperValue localCrops)
: base(content, publishedValueFallback)
{
LocalCrops = localCrops;
}
}
/// <summary>
/// Represents a media item with local crops.
/// </summary>
/// <typeparam name="T">The type of the media item.</typeparam>
/// <seealso cref="PublishedContentWrapped" />
public class MediaWithCrops<T> : MediaWithCrops
where T : IPublishedContent
{
/// <summary>
/// Gets the media item.
/// </summary>
/// <value>
/// The media item.
/// </value>
public new T Content { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MediaWithCrops{T}" /> class.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="publishedValueFallback">The published value fallback.</param>
/// <param name="localCrops">The local crops.</param>
public MediaWithCrops(T content,IPublishedValueFallback publishedValueFallback, ImageCropperValue localCrops)
: base(content, publishedValueFallback, localCrops)
{
Content = content;
}
/// <summary>
/// Performs an implicit conversion from <see cref="MediaWithCrops{T}" /> to <see cref="T" />.
/// </summary>
/// <param name="mediaWithCrops">The media with crops.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
public static implicit operator T(MediaWithCrops<T> mediaWithCrops) => mediaWithCrops.Content;
}
}

View File

@@ -14,6 +14,7 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Persistence
{
/// <summary>
/// Extends NPoco Database for Umbraco.
/// </summary>

View File

@@ -265,7 +265,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
|| (blockEditorData != null && validationLimit.Min.HasValue && blockEditorData.Layout.Count() < validationLimit.Min))
{
yield return new ValidationResult(
_textService.Localize("validation/entriesShort", new[]
_textService.Localize("validation", "entriesShort", new[]
{
validationLimit.Min.ToString(),
(validationLimit.Min - blockEditorData.Layout.Count()).ToString()
@@ -276,7 +276,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
if (blockEditorData != null && validationLimit.Max.HasValue && blockEditorData.Layout.Count() > validationLimit.Max)
{
yield return new ValidationResult(
_textService.Localize("validation/entriesExceed", new[]
_textService.Localize("validation", "entriesExceed", new[]
{
validationLimit.Max.ToString(),
(blockEditorData.Layout.Count() - validationLimit.Max).ToString()

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
var items = Fields.First(x => x.Key == "items");
// customize the items field
items.Name = textService.Localize("editdatatype/addPrevalue");
items.Name = textService.Localize("editdatatype", "addPrevalue");
items.Validators.Add(new ValueListUniqueValueValidator());
}

View File

@@ -1,7 +1,10 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
namespace Umbraco.Cms.Core.PropertyEditors
{
@@ -26,4 +29,35 @@ namespace Umbraco.Cms.Core.PropertyEditors
public int Height { get; set; }
}
}
internal static class ImageCropperConfigurationExtensions
{
/// <summary>
/// Applies the configuration to ensure only valid crops are kept and have the correct width/height.
/// </summary>
/// <param name="configuration">The configuration.</param>
public static void ApplyConfiguration(this ImageCropperValue imageCropperValue, ImageCropperConfiguration configuration)
{
var crops = new List<ImageCropperValue.ImageCropperCrop>();
var configuredCrops = configuration?.Crops;
if (configuredCrops != null)
{
foreach (var configuredCrop in configuredCrops)
{
var crop = imageCropperValue.Crops?.FirstOrDefault(x => x.Alias == configuredCrop.Alias);
crops.Add(new ImageCropperValue.ImageCropperCrop
{
Alias = configuredCrop.Alias,
Width = configuredCrop.Width,
Height = configuredCrop.Height,
Coordinates = crop?.Coordinates
});
}
}
imageCropperValue.Crops = crops;
}
}
}

View File

@@ -8,6 +8,11 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors
{
@@ -26,6 +31,9 @@ namespace Umbraco.Cms.Core.PropertyEditors
{
private readonly IIOHelper _ioHelper;
/// <summary>
/// Initializes a new instance of the <see cref="MediaPicker3PropertyEditor" /> class.
/// </summary>
public MediaPicker3PropertyEditor(
IDataValueEditorFactory dataValueEditorFactory,
IIOHelper ioHelper,
@@ -35,9 +43,11 @@ namespace Umbraco.Cms.Core.PropertyEditors
_ioHelper = ioHelper;
}
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() => new MediaPicker3ConfigurationEditor(_ioHelper);
/// <inheritdoc />
protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create<MediaPicker3PropertyValueEditor>(Attribute);
internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueReference
@@ -55,6 +65,13 @@ namespace Umbraco.Cms.Core.PropertyEditors
_jsonSerializer = jsonSerializer;
}
public override object ToEditor(IProperty property, string culture = null, string segment = null)
{
var value = property.GetValue(culture, segment);
return Deserialize(_jsonSerializer, value);
}
///<remarks>
/// Note: no FromEditor() and ToEditor() methods
/// We do not want to transform the way the data is stored in the DB and would like to keep a raw JSON string
@@ -62,19 +79,70 @@ namespace Umbraco.Cms.Core.PropertyEditors
public IEnumerable<UmbracoEntityReference> GetReferences(object value)
{
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
if (string.IsNullOrWhiteSpace(rawJson))
yield break;
var mediaWithCropsDtos = _jsonSerializer.Deserialize<MediaPickerWithCropsValueConverter.MediaWithCropsDto[]>(rawJson);
foreach (var mediaWithCropsDto in mediaWithCropsDtos)
foreach (var dto in Deserialize(_jsonSerializer, value))
{
yield return new UmbracoEntityReference(GuidUdi.Create(Constants.UdiEntityType.Media, mediaWithCropsDto.MediaKey));
yield return new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Media, dto.MediaKey));
}
}
internal static IEnumerable<MediaWithCropsDto> Deserialize(IJsonSerializer jsonSerializer,object value)
{
var rawJson = value is string str ? str : value?.ToString();
if (string.IsNullOrWhiteSpace(rawJson))
{
yield break;
}
if (!rawJson.DetectIsJson())
{
// Old comma seperated UDI format
foreach (var udiStr in rawJson.Split(Constants.CharArrays.Comma))
{
if (UdiParser.TryParse(udiStr, out GuidUdi udi))
{
yield return new MediaWithCropsDto
{
Key = Guid.NewGuid(),
MediaKey = udi.Guid,
Crops = Enumerable.Empty<ImageCropperValue.ImageCropperCrop>(),
FocalPoint = new ImageCropperValue.ImageCropperFocalPoint
{
Left = 0.5m,
Top = 0.5m
}
};
}
}
}
else
{
// New JSON format
foreach (var dto in jsonSerializer.Deserialize<IEnumerable<MediaWithCropsDto>>(rawJson))
{
yield return dto;
}
}
}
/// <summary>
/// Model/DTO that represents the JSON that the MediaPicker3 stores.
/// </summary>
[DataContract]
internal class MediaWithCropsDto
{
[DataMember(Name = "key")]
public Guid Key { get; set; }
[DataMember(Name = "mediaKey")]
public Guid MediaKey { get; set; }
[DataMember(Name = "crops")]
public IEnumerable<ImageCropperValue.ImageCropperCrop> Crops { get; set; }
[DataMember(Name = "focalPoint")]
public ImageCropperValue.ImageCropperFocalPoint FocalPoint { get; set; }
}
}
}
}

View File

@@ -158,6 +158,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
public override object ToEditor(IProperty property, string culture = null, string segment = null)
{
var val = property.GetValue(culture, segment);
var valEditors = new Dictionary<int, IDataValueEditor>();
var rows = _nestedContentValues.GetPropertyValues(val);
@@ -186,8 +187,15 @@ namespace Umbraco.Cms.Core.PropertyEditors
continue;
}
var tempConfig = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration;
var valEditor = propEditor.GetValueEditor(tempConfig);
var dataTypeId = prop.Value.PropertyType.DataTypeId;
if (!valEditors.TryGetValue(dataTypeId, out var valEditor))
{
var tempConfig = _dataTypeService.GetDataType(dataTypeId).Configuration;
valEditor = propEditor.GetValueEditor(tempConfig);
valEditors.Add(dataTypeId, valEditor);
}
var convValue = valEditor.ToEditor(tempProp);
// update the raw value since this is what will get serialized out

View File

@@ -50,7 +50,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
{
//we only store a single value for this editor so the 'member' or 'field'
// we'll associate this error with will simply be called 'value'
yield return new ValidationResult(_localizedTextService.Localize("errors/dissallowedMediaType"), new[] { "value" });
yield return new ValidationResult(_localizedTextService.Localize("errors", "dissallowedMediaType"), new[] { "value" });
}
}
}

View File

@@ -116,6 +116,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
settingsData = null;
}
// TODO: This should be optimized/cached, as calling Activator.CreateInstance is slow
var layoutType = typeof(BlockListItem<,>).MakeGenericType(contentData.GetType(), settingsData?.GetType() ?? typeof(IPublishedElement));
var layoutRef = (BlockListItem)Activator.CreateInstance(layoutType, contentGuidUdi, contentData, settingGuidUdi, settingsData);

View File

@@ -130,7 +130,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
/// Determines whether the value has a specified crop.
/// </summary>
public bool HasCrop(string alias)
=> Crops.Any(x => x.Alias == alias);
=> Crops != null && Crops.Any(x => x.Alias == alias);
/// <summary>
/// Determines whether the value has a source image.
@@ -138,46 +138,35 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
public bool HasImage()
=> !string.IsNullOrWhiteSpace(Src);
/// <summary>
/// Applies a configuration.
/// </summary>
/// <remarks>Ensures that all crops defined in the configuration exists in the value.</remarks>
public void ApplyConfiguration(ImageCropperConfiguration configuration)
public ImageCropperValue Merge(ImageCropperValue imageCropperValue)
{
// merge the crop values - the alias + width + height comes from
// configuration, but each crop can store its own coordinates
var configuredCrops = configuration?.Crops;
if (configuredCrops == null) return;
//Use Crops if it's not null, otherwise create a new list
var crops = Crops?.ToList() ?? new List<ImageCropperCrop>();
foreach (var configuredCrop in configuredCrops)
var incomingCrops = imageCropperValue?.Crops;
if (incomingCrops != null)
{
var crop = crops.FirstOrDefault(x => x.Alias == configuredCrop.Alias);
if (crop != null)
foreach (var incomingCrop in incomingCrops)
{
// found, apply the height & width
crop.Width = configuredCrop.Width;
crop.Height = configuredCrop.Height;
}
else
{
// not found, add
crops.Add(new ImageCropperCrop
var crop = crops.FirstOrDefault(x => x.Alias == incomingCrop.Alias);
if (crop == null)
{
Alias = configuredCrop.Alias,
Width = configuredCrop.Width,
Height = configuredCrop.Height
});
// Add incoming crop
crops.Add(incomingCrop);
}
else if (crop.Coordinates == null)
{
// Use incoming crop coordinates
crop.Coordinates = incomingCrop.Coordinates;
}
}
}
// assume we don't have to remove the crops in value, that
// are not part of configuration anymore?
Crops = crops;
return new ImageCropperValue()
{
Src = !string.IsNullOrWhiteSpace(Src) ? Src : imageCropperValue?.Src,
Crops = crops,
FocalPoint = FocalPoint ?? imageCropperValue?.FocalPoint
};
}
#region IEquatable

View File

@@ -1,13 +1,11 @@
using System;
using System.Collections;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Umbraco.Cms.Core.Models;
using System.Linq;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Core.Models;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
@@ -15,110 +13,85 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters
[DefaultPropertyValueConverter]
public class MediaPickerWithCropsValueConverter : PropertyValueConverterBase
{
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IPublishedUrlProvider _publishedUrlProvider;
private readonly IJsonSerializer _jsonSerializer;
public MediaPickerWithCropsValueConverter(
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IPublishedUrlProvider publishedUrlProvider)
IPublishedUrlProvider publishedUrlProvider,
IJsonSerializer jsonSerializer)
{
_publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
_publishedUrlProvider = publishedUrlProvider;
_jsonSerializer = jsonSerializer;
}
public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.Equals(Core.Constants.PropertyEditors.Aliases.MediaPicker3);
public override bool? IsValue(object value, PropertyValueLevel level)
{
var isValue = base.IsValue(value, level);
if (isValue != false && level == PropertyValueLevel.Source)
{
// Empty JSON array is not a value
isValue = value?.ToString() != "[]";
}
return isValue;
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> IsMultipleDataType(propertyType.DataType)
? typeof(IEnumerable<MediaWithCrops>)
: typeof(MediaWithCrops);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot;
/// <summary>
/// Enusre this property value convertor is for the New Media Picker with Crops aka MediaPicker 3
/// </summary>
public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.Equals(Core.Constants.PropertyEditors.Aliases.MediaPicker3);
/// <summary>
/// Check if the raw JSON value is not an empty array
/// </summary>
public override bool? IsValue(object value, PropertyValueLevel level) => value?.ToString() != "[]";
/// <summary>
/// What C# model type does the raw JSON return for Models & Views
/// </summary>
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
{
// Check do we want to return IPublishedContent collection still or a NEW model ?
var isMultiple = IsMultipleDataType(propertyType.DataType);
return isMultiple
? typeof(IEnumerable<MediaWithCrops>)
: typeof(MediaWithCrops);
}
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => source?.ToString();
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
var mediaItems = new List<MediaWithCrops>();
var isMultiple = IsMultipleDataType(propertyType.DataType);
if (inter == null)
if (string.IsNullOrEmpty(inter?.ToString()))
{
return isMultiple ? mediaItems: null;
// Short-circuit on empty value
return isMultiple ? Enumerable.Empty<MediaWithCrops>() : null;
}
var dtos = JsonConvert.DeserializeObject<IEnumerable<MediaWithCropsDto>>(inter.ToString());
var mediaItems = new List<MediaWithCrops>();
var dtos = MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor.Deserialize(_jsonSerializer, inter);
var configuration = propertyType.DataType.ConfigurationAs<MediaPicker3Configuration>();
foreach(var media in dtos)
foreach (var dto in dtos)
{
var item = _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(media.MediaKey);
if (item != null)
var mediaItem = _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(preview, dto.MediaKey);
if (mediaItem != null)
{
mediaItems.Add(new MediaWithCrops
var localCrops = new ImageCropperValue
{
MediaItem = item,
LocalCrops = new ImageCropperValue
{
Crops = media.Crops,
FocalPoint = media.FocalPoint,
Src = item.Url(_publishedUrlProvider)
}
});
Crops = dto.Crops,
FocalPoint = dto.FocalPoint,
Src = mediaItem.Url(_publishedUrlProvider)
};
localCrops.ApplyConfiguration(configuration);
// TODO: This should be optimized/cached, as calling Activator.CreateInstance is slow
var mediaWithCropsType = typeof(MediaWithCrops<>).MakeGenericType(mediaItem.GetType());
var mediaWithCrops = (MediaWithCrops)Activator.CreateInstance(mediaWithCropsType, mediaItem, localCrops);
mediaItems.Add(mediaWithCrops);
if (!isMultiple)
{
// Short-circuit on single item
break;
}
}
}
return isMultiple ? mediaItems : FirstOrDefault(mediaItems);
return isMultiple ? mediaItems : mediaItems.FirstOrDefault();
}
/// <summary>
/// Is the media picker configured to pick multiple media items
/// </summary>
/// <param name="dataType"></param>
/// <returns></returns>
private bool IsMultipleDataType(PublishedDataType dataType)
{
var config = dataType.ConfigurationAs<MediaPicker3Configuration>();
return config.Multiple;
}
private object FirstOrDefault(IList mediaItems)
{
return mediaItems.Count == 0 ? null : mediaItems[0];
}
/// <summary>
/// Model/DTO that represents the JSON that the MediaPicker3 stores
/// </summary>
[DataContract]
internal class MediaWithCropsDto
{
[DataMember(Name = "key")]
public Guid Key { get; set; }
[DataMember(Name = "mediaKey")]
public Guid MediaKey { get; set; }
[DataMember(Name = "crops")]
public IEnumerable<ImageCropperValue.ImageCropperCrop> Crops { get; set; }
[DataMember(Name = "focalPoint")]
public ImageCropperValue.ImageCropperFocalPoint FocalPoint { get; set; }
}
private bool IsMultipleDataType(PublishedDataType dataType) => dataType.ConfigurationAs<MediaPicker3Configuration>().Multiple;
}
}

View File

@@ -23,7 +23,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
var items = Fields.First(x => x.Key == "items");
// customize the items field
items.Name = textService.Localize("editdatatype/addPrevalue");
items.Name = textService.Localize("editdatatype", "addPrevalue");
items.Validators.Add(new ValueListUniqueValueValidator());
}

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.XPath;
using Examine;
using Examine.Search;
@@ -237,6 +236,10 @@ namespace Umbraco.Cms.Infrastructure
/// <inheritdoc />
public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords,
string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName)
=> Search(term, skip, take, out totalRecords, culture, indexName, null);
/// <inheritdoc />
public IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName, ISet<string> loadedFields = null)
{
if (skip < 0)
{
@@ -284,6 +287,10 @@ namespace Umbraco.Cms.Infrastructure
.ToArray(); // Get all index fields suffixed with the culture name supplied
queryExecutor = query.ManagedQuery(term, fields);
}
if (loadedFields != null && queryExecutor is IBooleanOperation booleanOperation)
{
queryExecutor = booleanOperation.SelectFields(loadedFields);
}
var results = skip == 0 && take == 0
? queryExecutor.Execute()

View File

@@ -1,31 +1,61 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Examine;
namespace Umbraco.Cms.Infrastructure.Search
{
public class UmbracoTreeSearcherFields : IUmbracoTreeSearcherFields
{
private IReadOnlyList<string> _backOfficeFields = new List<string> {"id", "__NodeId", "__Key"};
public IEnumerable<string> GetBackOfficeFields()
{
return _backOfficeFields;
}
private IReadOnlyList<string> _backOfficeMembersFields = new List<string> {"email", "loginName"};
public IEnumerable<string> GetBackOfficeMembersFields()
{
return _backOfficeMembersFields;
}
private IReadOnlyList<string> _backOfficeFields = new List<string> {"id", UmbracoExamineFieldNames.ItemIdFieldName, UmbracoExamineFieldNames.NodeKeyFieldName};
private readonly ISet<string> _backOfficeFieldsToLoad = new HashSet<string> { "id", UmbracoExamineFieldNames.ItemIdFieldName, UmbracoExamineFieldNames.NodeKeyFieldName, "nodeName", UmbracoExamineFieldNames.IconFieldName, UmbracoExamineFieldNames.CategoryFieldName, "parentID", UmbracoExamineFieldNames.ItemTypeFieldName };
private IReadOnlyList<string> _backOfficeMediaFields = new List<string> { UmbracoExamineFieldNames.UmbracoFileFieldName };
public IEnumerable<string> GetBackOfficeMediaFields()
private readonly ISet<string> _backOfficeMediaFieldsToLoad = new HashSet<string> { UmbracoExamineFieldNames.UmbracoFileFieldName };
private IReadOnlyList<string> _backOfficeMembersFields = new List<string> { "email", "loginName" };
private readonly ISet<string> _backOfficeMembersFieldsToLoad = new HashSet<string> { "email", "loginName" };
private readonly ISet<string> _backOfficeDocumentFieldsToLoad = new HashSet<string> { UmbracoExamineFieldNames.VariesByCultureFieldName };
private readonly ILocalizationService _localizationService;
public UmbracoTreeSearcherFields(ILocalizationService localizationService)
{
return _backOfficeMediaFields;
_localizationService = localizationService;
}
public IEnumerable<string> GetBackOfficeDocumentFields()
/// <inheritdoc />
public IEnumerable<string> GetBackOfficeFields() => _backOfficeFields;
/// <inheritdoc />
public IEnumerable<string> GetBackOfficeMembersFields() => _backOfficeMembersFields;
/// <inheritdoc />
public IEnumerable<string> GetBackOfficeMediaFields() => _backOfficeMediaFields;
/// <inheritdoc />
public IEnumerable<string> GetBackOfficeDocumentFields() => Enumerable.Empty<string>();
/// <inheritdoc />
public ISet<string> GetBackOfficeFieldsToLoad() => _backOfficeFieldsToLoad;
/// <inheritdoc />
public ISet<string> GetBackOfficeMembersFieldsToLoad() => _backOfficeMembersFieldsToLoad;
/// <inheritdoc />
public ISet<string> GetBackOfficeMediaFieldsToLoad() => _backOfficeMediaFieldsToLoad;
/// <inheritdoc />
public ISet<string> GetBackOfficeDocumentFieldsToLoad()
{
return Enumerable.Empty<string>();
var fields = _backOfficeDocumentFieldsToLoad;
// We need to load all nodeName_* fields but we won't know those up front so need to get
// all langs (this is cached)
foreach(var field in _localizationService.GetAllLanguages().Select(x => "nodeName_" + x.IsoCode.ToLowerInvariant()))
{
fields.Add(field);
}
return fields;
}
}
}

View File

@@ -17,43 +17,43 @@ namespace Umbraco.Cms.Core.Security
public override IdentityError DuplicateRoleName(string role) => new IdentityError
{
Code = nameof(DuplicateRoleName),
Description = _textService.Localize("validation/duplicateUserGroupName", new[] { role })
Description = _textService.Localize("validation", "duplicateUserGroupName", new[] { role })
};
public override IdentityError InvalidRoleName(string role) => new IdentityError
{
Code = nameof(InvalidRoleName),
Description = _textService.Localize("validation/invalidUserGroupName")
Description = _textService.Localize("validation", "invalidUserGroupName")
};
public override IdentityError LoginAlreadyAssociated() => new IdentityError
{
Code = nameof(LoginAlreadyAssociated),
Description = _textService.Localize("user/duplicateLogin")
Description = _textService.Localize("user", "duplicateLogin")
};
public override IdentityError UserAlreadyHasPassword() => new IdentityError
{
Code = nameof(UserAlreadyHasPassword),
Description = _textService.Localize("user/userHasPassword")
Description = _textService.Localize("user", "userHasPassword")
};
public override IdentityError UserAlreadyInRole(string role) => new IdentityError
{
Code = nameof(UserAlreadyInRole),
Description = _textService.Localize("user/userHasGroup", new[] { role })
Description = _textService.Localize("user", "userHasGroup", new[] { role })
};
public override IdentityError UserLockoutNotEnabled() => new IdentityError
{
Code = nameof(UserLockoutNotEnabled),
Description = _textService.Localize("user/userLockoutNotEnabled")
Description = _textService.Localize("user", "userLockoutNotEnabled")
};
public override IdentityError UserNotInRole(string role) => new IdentityError
{
Code = nameof(UserNotInRole),
Description = _textService.Localize("user/userNotInGroup", new[] { role })
Description = _textService.Localize("user", "userNotInGroup", new[] { role })
};
}
@@ -67,43 +67,43 @@ namespace Umbraco.Cms.Core.Security
public override IdentityError DuplicateRoleName(string role) => new IdentityError
{
Code = nameof(DuplicateRoleName),
Description = _textService.Localize("validation/duplicateMemberGroupName", new[] { role })
Description = _textService.Localize("validation", "duplicateMemberGroupName", new[] { role })
};
public override IdentityError InvalidRoleName(string role) => new IdentityError
{
Code = nameof(InvalidRoleName),
Description = _textService.Localize("validation/invalidMemberGroupName")
Description = _textService.Localize("validation", "invalidMemberGroupName")
};
public override IdentityError LoginAlreadyAssociated() => new IdentityError
{
Code = nameof(LoginAlreadyAssociated),
Description = _textService.Localize("member/duplicateMemberLogin")
Description = _textService.Localize("member", "duplicateMemberLogin")
};
public override IdentityError UserAlreadyHasPassword() => new IdentityError
{
Code = nameof(UserAlreadyHasPassword),
Description = _textService.Localize("member/memberHasPassword")
Description = _textService.Localize("member", "memberHasPassword")
};
public override IdentityError UserAlreadyInRole(string role) => new IdentityError
{
Code = nameof(UserAlreadyInRole),
Description = _textService.Localize("member/memberHasGroup", new[] { role })
Description = _textService.Localize("member", "memberHasGroup", new[] { role })
};
public override IdentityError UserLockoutNotEnabled() => new IdentityError
{
Code = nameof(UserLockoutNotEnabled),
Description = _textService.Localize("member/memberLockoutNotEnabled")
Description = _textService.Localize("member", "memberLockoutNotEnabled")
};
public override IdentityError UserNotInRole(string role) => new IdentityError
{
Code = nameof(UserNotInRole),
Description = _textService.Localize("member/memberNotInGroup", new[] { role })
Description = _textService.Localize("member", "memberNotInGroup", new[] { role })
};
}
}

View File

@@ -13,91 +13,91 @@ namespace Umbraco.Cms.Core.Security
public override IdentityError ConcurrencyFailure() => new IdentityError
{
Code = nameof(ConcurrencyFailure),
Description = _textService.Localize("errors/concurrencyError")
Description = _textService.Localize("errors", "concurrencyError")
};
public override IdentityError DefaultError() => new IdentityError
{
Code = nameof(DefaultError),
Description = _textService.Localize("errors/defaultError")
Description = _textService.Localize("errors", "defaultError")
};
public override IdentityError DuplicateEmail(string email) => new IdentityError
{
Code = nameof(DuplicateEmail),
Description = _textService.Localize("validation/duplicateEmail", new[] { email })
Description = _textService.Localize("validation", "duplicateEmail", new[] { email })
};
public override IdentityError DuplicateUserName(string userName) => new IdentityError
{
Code = nameof(DuplicateUserName),
Description = _textService.Localize("validation/duplicateUsername", new[] { userName })
Description = _textService.Localize("validation", "duplicateUsername", new[] { userName })
};
public override IdentityError InvalidEmail(string email) => new IdentityError
{
Code = nameof(InvalidEmail),
Description = _textService.Localize("validation/invalidEmail")
Description = _textService.Localize("validation", "invalidEmail")
};
public override IdentityError InvalidToken() => new IdentityError
{
Code = nameof(InvalidToken),
Description = _textService.Localize("validation/invalidToken")
Description = _textService.Localize("validation", "invalidToken")
};
public override IdentityError InvalidUserName(string userName) => new IdentityError
{
Code = nameof(InvalidUserName),
Description = _textService.Localize("validation/invalidUsername")
Description = _textService.Localize("validation", "invalidUsername")
};
public override IdentityError PasswordMismatch() => new IdentityError
{
Code = nameof(PasswordMismatch),
Description = _textService.Localize("user/passwordMismatch")
Description = _textService.Localize("user", "passwordMismatch")
};
public override IdentityError PasswordRequiresDigit() => new IdentityError
{
Code = nameof(PasswordRequiresDigit),
Description = _textService.Localize("user/passwordRequiresDigit")
Description = _textService.Localize("user", "passwordRequiresDigit")
};
public override IdentityError PasswordRequiresLower() => new IdentityError
{
Code = nameof(PasswordRequiresLower),
Description = _textService.Localize("user/passwordRequiresLower")
Description = _textService.Localize("user", "passwordRequiresLower")
};
public override IdentityError PasswordRequiresNonAlphanumeric() => new IdentityError
{
Code = nameof(PasswordRequiresNonAlphanumeric),
Description = _textService.Localize("user/passwordRequiresNonAlphanumeric")
Description = _textService.Localize("user", "passwordRequiresNonAlphanumeric")
};
public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) => new IdentityError
{
Code = nameof(PasswordRequiresUniqueChars),
Description = _textService.Localize("user/passwordRequiresUniqueChars", new[] { uniqueChars.ToString() })
Description = _textService.Localize("user", "passwordRequiresUniqueChars", new[] { uniqueChars.ToString() })
};
public override IdentityError PasswordRequiresUpper() => new IdentityError
{
Code = nameof(PasswordRequiresUpper),
Description = _textService.Localize("user/passwordRequiresUpper")
Description = _textService.Localize("user", "passwordRequiresUpper")
};
public override IdentityError PasswordTooShort(int length) => new IdentityError
{
Code = nameof(PasswordTooShort),
Description = _textService.Localize("user/passwordTooShort", new[] { length.ToString() })
Description = _textService.Localize("user", "passwordTooShort", new[] { length.ToString() })
};
public override IdentityError RecoveryCodeRedemptionFailed() => new IdentityError
{
Code = nameof(RecoveryCodeRedemptionFailed),
Description = _textService.Localize("login/resetCodeExpired")
Description = _textService.Localize("login", "resetCodeExpired")
};
}
}

View File

@@ -194,11 +194,34 @@ namespace Umbraco.Cms.Core.Services.Implement
// TODO: what about culture?
var contentType = GetContentType(contentTypeAlias);
if (contentType == null)
throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias));
return Create(name, parentId, contentType, userId);
}
/// <summary>
/// Creates an <see cref="IContent"/> object of a specified content type.
/// </summary>
/// <remarks>This method simply returns a new, non-persisted, IContent without any identity. It
/// is intended as a shortcut to creating new content objects that does not invoke a save
/// operation against the database.
/// </remarks>
/// <param name="name">The name of the content object.</param>
/// <param name="parentId">The identifier of the parent, or -1.</param>
/// <param name="contentType">The content type of the content</param>
/// <param name="userId">The optional id of the user creating the content.</param>
/// <returns>The content object.</returns>
public IContent Create(string name, int parentId, IContentType contentType,
int userId = Constants.Security.SuperUserId)
{
if (contentType is null)
{
throw new ArgumentException("Content type must be specified", nameof(contentType));
}
var parent = parentId > 0 ? GetById(parentId) : null;
if (parentId > 0 && parent == null)
if (parentId > 0 && parent is null)
{
throw new ArgumentException("No content with that id.", nameof(parentId));
}
var content = new Content(name, parentId, contentType, userId);

View File

@@ -5,19 +5,18 @@ using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Extensions.Logging;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services.Implement
{
// TODO: Convert all of this over to Niels K's localization framework one day
public class LocalizedTextService : ILocalizedTextService
{
private readonly ILogger<LocalizedTextService> _logger;
private readonly Lazy<LocalizedTextServiceFileSources> _fileSources;
private readonly IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> _dictionarySource;
private readonly IDictionary<CultureInfo, Lazy<XDocument>> _xmlSource;
private IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> _dictionarySource => _dictionarySourceLazy.Value;
private IDictionary<CultureInfo, IDictionary<string, string>> _noAreaDictionarySource => _noAreaDictionarySourceLazy.Value;
private readonly Lazy<IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>> _dictionarySourceLazy;
private readonly Lazy<IDictionary<CultureInfo, IDictionary<string, string>>> _noAreaDictionarySourceLazy;
private readonly char[] _splitter = new[] { '/' };
/// <summary>
/// Initializes with a file sources instance
/// </summary>
@@ -25,12 +24,50 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <param name="logger"></param>
public LocalizedTextService(Lazy<LocalizedTextServiceFileSources> fileSources, ILogger<LocalizedTextService> logger)
{
if (logger == null) throw new ArgumentNullException("logger");
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger;
if (fileSources == null) throw new ArgumentNullException("fileSources");
if (fileSources == null) throw new ArgumentNullException(nameof(fileSources));
_dictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>>(() => FileSourcesToAreaDictionarySources(fileSources.Value));
_noAreaDictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, string>>>(() => FileSourcesToNoAreaDictionarySources(fileSources.Value));
_fileSources = fileSources;
}
private IDictionary<CultureInfo, IDictionary<string, string>> FileSourcesToNoAreaDictionarySources(LocalizedTextServiceFileSources fileSources)
{
var xmlSources = fileSources.GetXmlSources();
return XmlSourceToNoAreaDictionary(xmlSources);
}
private IDictionary<CultureInfo, IDictionary<string, string>> XmlSourceToNoAreaDictionary(IDictionary<CultureInfo, Lazy<XDocument>> xmlSources)
{
var cultureNoAreaDictionary = new Dictionary<CultureInfo, IDictionary<string, string>>();
foreach (var xmlSource in xmlSources)
{
var noAreaAliasValue = GetNoAreaStoredTranslations(xmlSources, xmlSource.Key);
cultureNoAreaDictionary.Add(xmlSource.Key, noAreaAliasValue);
}
return cultureNoAreaDictionary;
}
private IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> FileSourcesToAreaDictionarySources(LocalizedTextServiceFileSources fileSources)
{
var xmlSources = fileSources.GetXmlSources();
return XmlSourcesToAreaDictionary(xmlSources);
}
private IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> XmlSourcesToAreaDictionary(IDictionary<CultureInfo, Lazy<XDocument>> xmlSources)
{
var cultureDictionary = new Dictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>();
foreach (var xmlSource in xmlSources)
{
var areaAliaValue = GetAreaStoredTranslations(xmlSources, xmlSource.Key);
cultureDictionary.Add(xmlSource.Key, areaAliaValue);
}
return cultureDictionary;
}
/// <summary>
/// Initializes with an XML source
/// </summary>
@@ -38,12 +75,15 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <param name="logger"></param>
public LocalizedTextService(IDictionary<CultureInfo, Lazy<XDocument>> source, ILogger<LocalizedTextService> logger)
{
if (source == null) throw new ArgumentNullException("source");
if (logger == null) throw new ArgumentNullException("logger");
_xmlSource = source;
_logger = logger;
if (source == null) throw new ArgumentNullException(nameof(source));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_dictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>>(() => XmlSourcesToAreaDictionary(source));
_noAreaDictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, string>>>(() => XmlSourceToNoAreaDictionary(source));
}
/// <summary>
/// Initializes with a source of a dictionary of culture -> areas -> sub dictionary of keys/values
/// </summary>
@@ -51,37 +91,54 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <param name="logger"></param>
public LocalizedTextService(IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> source, ILogger<LocalizedTextService> logger)
{
_dictionarySource = source ?? throw new ArgumentNullException(nameof(source));
var dictionarySource = source ?? throw new ArgumentNullException(nameof(source));
_dictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>>>(() => dictionarySource);
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
var cultureNoAreaDictionary = new Dictionary<CultureInfo, IDictionary<string, string>>();
foreach (var cultureDictionary in dictionarySource)
{
var areaAliaValue = GetAreaStoredTranslations(source, cultureDictionary.Key);
var aliasValue = new Dictionary<string, string>();
foreach (var area in areaAliaValue)
{
foreach (var alias in area.Value)
{
if (!aliasValue.ContainsKey(alias.Key))
{
aliasValue.Add(alias.Key, alias.Value);
}
}
}
cultureNoAreaDictionary.Add(cultureDictionary.Key, aliasValue);
}
_noAreaDictionarySourceLazy = new Lazy<IDictionary<CultureInfo, IDictionary<string, string>>>(() => cultureNoAreaDictionary);
}
public string Localize(string key, CultureInfo culture, IDictionary<string, string> tokens = null)
{
if (culture == null) throw new ArgumentNullException(nameof(culture));
// TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
culture = ConvertToSupportedCultureWithRegionCode(culture);
//This is what the legacy ui service did
if (string.IsNullOrEmpty(key))
return string.Empty;
var keyParts = key.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
var keyParts = key.Split(_splitter, StringSplitOptions.RemoveEmptyEntries);
var area = keyParts.Length > 1 ? keyParts[0] : null;
var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0];
return Localize(area, alias, culture, tokens);
}
public string Localize(string area, string alias, CultureInfo culture, IDictionary<string, string> tokens = null)
{
if (culture == null) throw new ArgumentNullException(nameof(culture));
var xmlSource = _xmlSource ?? (_fileSources != null
? _fileSources.Value.GetXmlSources()
: null);
//This is what the legacy ui service did
if (string.IsNullOrEmpty(alias))
return string.Empty;
if (xmlSource != null)
{
return GetFromXmlSource(xmlSource, culture, area, alias, tokens);
}
else
{
return GetFromDictionarySource(culture, area, alias, tokens);
}
// TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
culture = ConvertToSupportedCultureWithRegionCode(culture);
return GetFromDictionarySource(culture, area, alias, tokens);
}
/// <summary>
@@ -89,76 +146,105 @@ namespace Umbraco.Cms.Core.Services.Implement
/// </summary>
public IDictionary<string, string> GetAllStoredValues(CultureInfo culture)
{
if (culture == null) throw new ArgumentNullException("culture");
if (culture == null) throw new ArgumentNullException(nameof(culture));
// TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
culture = ConvertToSupportedCultureWithRegionCode(culture);
var result = new Dictionary<string, string>();
var xmlSource = _xmlSource ?? (_fileSources != null
? _fileSources.Value.GetXmlSources()
: null);
if (xmlSource != null)
if (_dictionarySource.ContainsKey(culture) == false)
{
if (xmlSource.ContainsKey(culture) == false)
_logger.LogWarning("The culture specified {Culture} was not found in any configured sources for this service", culture);
return new Dictionary<string, string>(0);
}
IDictionary<string, string> result = new Dictionary<string, string>();
//convert all areas + keys to a single key with a '/'
foreach (var area in _dictionarySource[culture])
{
foreach (var key in area.Value)
{
_logger.LogWarning("The culture specified {Culture} was not found in any configured sources for this service", culture);
return result;
}
// convert all areas + keys to a single key with a '/'
result = GetStoredTranslations(xmlSource, culture);
// merge with the English file in case there's keys in there that don't exist in the local file
var englishCulture = CultureInfo.GetCultureInfo("en-US");
if (culture.Equals(englishCulture) == false)
{
var englishResults = GetStoredTranslations(xmlSource, englishCulture);
foreach (var englishResult in englishResults.Where(englishResult => result.ContainsKey(englishResult.Key) == false))
var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key);
//i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case.
if (result.ContainsKey(dictionaryKey) == false)
{
result.Add(englishResult.Key, englishResult.Value);
result.Add(dictionaryKey, key.Value);
}
}
}
else
{
if (_dictionarySource.ContainsKey(culture) == false)
{
_logger.LogWarning("The culture specified {Culture} was not found in any configured sources for this service", culture);
return result;
}
// convert all areas + keys to a single key with a '/'
foreach (var area in _dictionarySource[culture])
{
foreach (var key in area.Value)
{
var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key);
// i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case.
if (result.ContainsKey(dictionaryKey) == false)
{
result.Add(dictionaryKey, key.Value);
}
}
}
}
return result;
}
private Dictionary<string, string> GetStoredTranslations(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)
private Dictionary<string, IDictionary<string, string>> GetAreaStoredTranslations(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)
{
var result = new Dictionary<string, string>();
var overallResult = new Dictionary<string, IDictionary<string, string>>(StringComparer.InvariantCulture);
var areas = xmlSource[cult].Value.XPathSelectElements("//area");
foreach (var area in areas)
{
var result = new Dictionary<string, string>(StringComparer.InvariantCulture);
var keys = area.XPathSelectElements("./key");
foreach (var key in keys)
{
var dictionaryKey = string.Format("{0}/{1}", (string)area.Attribute("alias"),
(string)key.Attribute("alias"));
var dictionaryKey =
(string)key.Attribute("alias");
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
if (result.ContainsKey(dictionaryKey) == false)
result.Add(dictionaryKey, key.Value);
}
overallResult.Add(area.Attribute("alias").Value, result);
}
//Merge English Dictionary
var englishCulture = new CultureInfo("en-US");
if (!cult.Equals(englishCulture))
{
var enUS = xmlSource[englishCulture].Value.XPathSelectElements("//area");
foreach (var area in enUS)
{
IDictionary<string, string> result = new Dictionary<string, string>(StringComparer.InvariantCulture);
if (overallResult.ContainsKey(area.Attribute("alias").Value))
{
result = overallResult[area.Attribute("alias").Value];
}
var keys = area.XPathSelectElements("./key");
foreach (var key in keys)
{
var dictionaryKey =
(string)key.Attribute("alias");
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
if (result.ContainsKey(dictionaryKey) == false)
result.Add(dictionaryKey, key.Value);
}
if (!overallResult.ContainsKey(area.Attribute("alias").Value))
{
overallResult.Add(area.Attribute("alias").Value, result);
}
}
}
return overallResult;
}
private Dictionary<string, string> GetNoAreaStoredTranslations(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)
{
var result = new Dictionary<string, string>(StringComparer.InvariantCulture);
var keys = xmlSource[cult].Value.XPathSelectElements("//key");
foreach (var key in keys)
{
var dictionaryKey =
(string)key.Attribute("alias");
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
if (result.ContainsKey(dictionaryKey) == false)
result.Add(dictionaryKey, key.Value);
}
//Merge English Dictionary
var englishCulture = new CultureInfo("en-US");
if (!cult.Equals(englishCulture))
{
var keysEn = xmlSource[englishCulture].Value.XPathSelectElements("//key");
foreach (var key in keys)
{
var dictionaryKey =
(string)key.Attribute("alias");
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
if (result.ContainsKey(dictionaryKey) == false)
result.Add(dictionaryKey, key.Value);
@@ -166,6 +252,25 @@ namespace Umbraco.Cms.Core.Services.Implement
}
return result;
}
private Dictionary<string, IDictionary<string, string>> GetAreaStoredTranslations(IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> dictionarySource, CultureInfo cult)
{
var overallResult = new Dictionary<string, IDictionary<string, string>>(StringComparer.InvariantCulture);
var areaDict = dictionarySource[cult];
foreach (var area in areaDict)
{
var result = new Dictionary<string, string>(StringComparer.InvariantCulture);
var keys = area.Value.Keys;
foreach (var key in keys)
{
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
if (result.ContainsKey(key) == false)
result.Add(key, area.Value[key]);
}
overallResult.Add(area.Key, result);
}
return overallResult;
}
/// <summary>
/// Returns a list of all currently supported cultures
@@ -173,11 +278,7 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <returns></returns>
public IEnumerable<CultureInfo> GetSupportedCultures()
{
var xmlSource = _xmlSource ?? (_fileSources != null
? _fileSources.Value.GetXmlSources()
: null);
return xmlSource != null ? xmlSource.Keys : _dictionarySource.Keys;
return _dictionarySource.Keys;
}
/// <summary>
@@ -213,27 +314,25 @@ namespace Umbraco.Cms.Core.Services.Implement
return "[" + key + "]";
}
var cultureSource = _dictionarySource[culture];
string found;
if (area.IsNullOrWhiteSpace())
string found = null;
if (string.IsNullOrWhiteSpace(area))
{
found = cultureSource
.SelectMany(x => x.Value)
.Where(keyvals => keyvals.Key.InvariantEquals(key))
.Select(x => x.Value)
.FirstOrDefault();
_noAreaDictionarySource[culture].TryGetValue(key, out found);
}
else
{
found = cultureSource
.Where(areas => areas.Key.InvariantEquals(area))
.SelectMany(a => a.Value)
.Where(keyvals => keyvals.Key.InvariantEquals(key))
.Select(x => x.Value)
.FirstOrDefault();
if (_dictionarySource[culture].TryGetValue(area, out var areaDictionary))
{
areaDictionary.TryGetValue(key, out found);
}
if (found == null)
{
_noAreaDictionarySource[culture].TryGetValue(key, out found);
}
}
if (found != null)
{
return ParseTokens(found, tokens);
@@ -242,44 +341,6 @@ namespace Umbraco.Cms.Core.Services.Implement
//NOTE: Based on how legacy works, the default text does not contain the area, just the key
return "[" + key + "]";
}
private string GetFromXmlSource(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo culture, string area, string key, IDictionary<string, string> tokens)
{
if (xmlSource.ContainsKey(culture) == false)
{
_logger.LogWarning("The culture specified {Culture} was not found in any configured sources for this service", culture);
return "[" + key + "]";
}
var found = FindTranslation(xmlSource, culture, area, key);
if (found != null)
{
return ParseTokens(found.Value, tokens);
}
// Fall back to English by default if we can't find the key
found = FindTranslation(xmlSource, new CultureInfo("en-US"), area, key);
if (found != null)
return ParseTokens(found.Value, tokens);
// If it can't be found in either file, fall back to the default, showing just the key in square brackets
// NOTE: Based on how legacy works, the default text does not contain the area, just the key
return "[" + key + "]";
}
private XElement FindTranslation(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo culture, string area, string key)
{
var cultureSource = xmlSource[culture].Value;
var xpath = area.IsNullOrWhiteSpace()
? string.Format("//key [@alias = '{0}']", key)
: string.Format("//area [@alias = '{0}']/key [@alias = '{1}']", area, key);
var found = cultureSource.XPathSelectElement(xpath);
return found;
}
/// <summary>
/// Parses the tokens in the value
/// </summary>
@@ -303,11 +364,26 @@ namespace Umbraco.Cms.Core.Services.Implement
foreach (var token in tokens)
{
value = value.Replace(string.Format("{0}{1}{0}", "%", token.Key), token.Value);
value = value.Replace(string.Concat("%", token.Key, "%"), token.Value);
}
return value;
}
public IDictionary<string, IDictionary<string, string>> GetAllStoredValuesByAreaAndAlias(CultureInfo culture)
{
if (culture == null) throw new ArgumentNullException("culture");
// TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
culture = ConvertToSupportedCultureWithRegionCode(culture);
if (_dictionarySource.ContainsKey(culture) == false)
{
_logger.LogWarning("The culture specified {Culture} was not found in any configured sources for this service", culture);
return new Dictionary<string, IDictionary<string, string>>(0);
}
return _dictionarySource[culture];
}
}
}

View File

@@ -299,7 +299,7 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name),
GetQuotedTableName(index.TableName), columns);
}
public override string GetSpecialDbType(SpecialDbTypes dbTypes)
{
if (dbTypes == SpecialDbTypes.NVARCHARMAX) // SqlCE does not have nvarchar(max) for now

View File

@@ -16,8 +16,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
// read properties count
var pcount = PrimitiveSerializer.Int32.ReadFrom(stream);
var dict = new Dictionary<string, PropertyData[]>(pcount,StringComparer.InvariantCultureIgnoreCase);
// read each property
for (var i = 0; i < pcount; i++)
{
@@ -34,7 +34,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
for (var j = 0; j < vcount; j++)
{
var pdata = new PropertyData();
pdatas[j] =pdata;
pdatas[j] = pdata;
// everything that can be null is read/written as object
// even though - culture and segment should never be null here, as 'null' represents

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq.Expressions;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.Options;
@@ -9,7 +9,6 @@ using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Infrastructure.Persistence.Querying;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
using Umbraco.Cms.Persistence.SqlCe;
namespace Umbraco.Tests.Benchmarks
{
@@ -19,7 +18,7 @@ namespace Umbraco.Tests.Benchmarks
protected Lazy<ISqlContext> MockSqlContext()
{
var sqlContext = Mock.Of<ISqlContext>();
var syntax = new SqlCeSyntaxProvider(Options.Create(new GlobalSettings()));
var syntax = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(syntax);
return new Lazy<ISqlContext>(() => sqlContext);
}
@@ -36,7 +35,7 @@ namespace Umbraco.Tests.Benchmarks
_mapperCollection = mapperCollection.Object;
}
private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider(Options.Create(new GlobalSettings()));
private readonly ISqlSyntaxProvider _syntaxProvider = new SqlServerSyntaxProvider(Options.Create(new GlobalSettings()));
private readonly CachedExpression _cachedExpression;
private readonly IMapperCollection _mapperCollection;

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Umbraco.Tests.Benchmarks")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Umbraco.Tests.Benchmarks")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3a33adc9-c6c0-4db1-a613-a9af0210df3d")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,4 +1,4 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using Microsoft.Extensions.Options;
@@ -6,7 +6,7 @@ using NPoco;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Persistence.SqlCe;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
using Umbraco.Extensions;
namespace Umbraco.Tests.Benchmarks
@@ -36,7 +36,7 @@ namespace Umbraco.Tests.Benchmarks
var mappers = new NPoco.MapperCollection( );
var factory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init());
SqlContext = new SqlContext(new SqlCeSyntaxProvider(Options.Create(new GlobalSettings())), DatabaseType.SQLCe, factory);
SqlContext = new SqlContext(new SqlServerSyntaxProvider(Options.Create(new GlobalSettings())), DatabaseType.SQLCe, factory);
SqlTemplates = new SqlTemplates(SqlContext);
}

View File

@@ -1,21 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<OutputType>Exe</OutputType>
<LangVersion>8</LangVersion>
<OutputType>Exe</OutputType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="CtorInvokeBenchmarks.cs.bak" />
<None Remove="ReflectionUtilities-Unused.cs.bak" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.Persistence.SqlCe\Umbraco.Persistence.SqlCe.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet">
@@ -27,10 +25,7 @@
<PackageReference Include="Moq">
<Version>4.16.1</Version>
</PackageReference>
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.2.231403">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
</ItemGroup>
</ItemGroup>
</Project>

View File

@@ -260,8 +260,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Mapping
}
Assert.AreEqual(contentType.CompositionPropertyGroups.Count(), invariantContent.Tabs.Count() - 1);
Assert.IsTrue(invariantContent.Tabs.Any(x => x.Label == _localizedTextService.Localize("general/properties")));
Assert.AreEqual(2, invariantContent.Tabs.Where(x => x.Label == _localizedTextService.Localize("general/properties")).SelectMany(x => x.Properties.Where(p => p.Alias.StartsWith("_umb_") == false)).Count());
Assert.IsTrue(invariantContent.Tabs.Any(x => x.Label == _localizedTextService.Localize("general","properties")));
Assert.AreEqual(2, invariantContent.Tabs.Where(x => x.Label == _localizedTextService.Localize("general","properties")).SelectMany(x => x.Properties.Where(p => p.Alias.StartsWith("_umb_") == false)).Count());
}
private void AssertBasics(ContentItemDisplay result, IContent content)

View File

@@ -1,5 +1,7 @@
using System;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NUnit.Framework;
@@ -10,7 +12,9 @@ using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Tests.Integration.TestServerTest;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Routing
{
@@ -44,6 +48,24 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Routing
// Assert
Assert.AreEqual(HttpStatusCode.NoContent, response.StatusCode);
}
[Test]
public async Task Plugin_Controller_Routes_By_Area()
{
// Create URL manually, because PrepareSurfaceController URl will prepare whatever the controller is routed as
Type controllerType = typeof(TestPluginController);
var pluginAttribute = CustomAttributeExtensions.GetCustomAttribute<PluginControllerAttribute>(controllerType, false);
var controllerName = ControllerExtensions.GetControllerName(controllerType);
string url = $"/umbraco/{pluginAttribute?.AreaName}/{controllerName}";
PrepareUrl(url);
HttpResponseMessage response = await Client.GetAsync(url);
string body = await response.Content.ReadAsStringAsync();
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
// Test controllers must be non-nested, else we need to jump through some hoops with custom
@@ -61,4 +83,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Routing
public IActionResult News() => NoContent();
}
[PluginController("TestArea")]
public class TestPluginController : SurfaceController
{
public TestPluginController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider) : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
}
public IActionResult Index() => Ok();
}
}

View File

@@ -117,7 +117,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors
// editor wants ApplicationContext.Current.Services.TextService
// (that should be fixed with proper injection)
var textService = new Mock<ILocalizedTextService>();
textService.Setup(x => x.Localize(It.IsAny<string>(), It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>())).Returns("blah");
textService.Setup(x => x.Localize(It.IsAny<string>(), It.IsAny<string>(),It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>())).Returns("blah");
//// var appContext = new ApplicationContext(
//// new DatabaseContext(TestObjects.GetIDatabaseFactoryMock(), logger, Mock.Of<IRuntimeState>(), Mock.Of<IMigrationEntryService>()),

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Services
private void MockObjects(out PropertyValidationService validationService, out IDataType dt)
{
var textService = new Mock<ILocalizedTextService>();
textService.Setup(x => x.Localize(It.IsAny<string>(), Thread.CurrentThread.CurrentCulture, null)).Returns("Localized text");
textService.Setup(x => x.Localize(It.IsAny<string>(),It.IsAny<string>(), Thread.CurrentThread.CurrentCulture, null)).Returns("Localized text");
var dataTypeService = new Mock<IDataTypeService>();
IDataType dataType = Mock.Of<IDataType>(

View File

@@ -9,6 +9,7 @@ using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Extensions;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions
{

View File

@@ -54,48 +54,60 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common
Assert.AreEqual(cropperValue, obj);
}
//// [TestCase(CropperJson1, CropperJson1, true)]
//// [TestCase(CropperJson1, CropperJson2, false)]
//// public void CanConvertImageCropperPropertyEditor(string val1, string val2, bool expected)
//// {
//// try
//// {
//// var container = TestHelper.GetRegister();
//// var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of<IProfilingLogger>(), ComponentTests.MockRuntimeState(RuntimeLevel.Run), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache);
////
//// composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
////
//// Current.Factory = composition.CreateFactory();
////
//// var logger = Mock.Of<ILogger>();
//// var scheme = Mock.Of<IMediaPathScheme>();
//// var shortStringHelper = Mock.Of<IShortStringHelper>();
////
//// var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>(), scheme, logger, shortStringHelper);
////
//// var dataTypeService = new TestObjects.TestDataTypeService(
//// new DataType(new ImageCropperPropertyEditor(Mock.Of<ILogger>(), mediaFileSystem, Mock.Of<IContentSettings>(), Mock.Of<IDataTypeService>(), Mock.Of<ILocalizationService>(), TestHelper.IOHelper, TestHelper.ShortStringHelper, Mock.Of<ILocalizedTextService>())) { Id = 1 });
////
//// var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);
////
//// var converter = new ImageCropperValueConverter();
//// var result = converter.ConvertSourceToIntermediate(null, factory.CreatePropertyType("test", 1), val1, false); // does not use type for conversion
////
//// var resultShouldMatch = val2.DeserializeImageCropperValue();
//// if (expected)
//// {
//// Assert.AreEqual(resultShouldMatch, result);
//// }
//// else
//// {
//// Assert.AreNotEqual(resultShouldMatch, result);
//// }
//// }
//// finally
//// {
//// Current.Reset();
//// }
//// }
// [TestCase(CropperJson1, CropperJson1, true)]
// [TestCase(CropperJson1, CropperJson2, false)]
// public void CanConvertImageCropperPropertyEditor(string val1, string val2, bool expected)
// {
// try
// {
// var container = RegisterFactory.Create();
// var composition = new Composition(container, new TypeLoader(), Mock.Of<IProfilingLogger>(), ComponentTests.MockRuntimeState(RuntimeLevel.Run));
//
// composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
//
// Current.Factory = composition.CreateFactory();
//
// var logger = Mock.Of<ILogger>();
// var scheme = Mock.Of<IMediaPathScheme>();
// var config = Mock.Of<IContentSection>();
//
// var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>(), config, scheme, logger);
//
// var imageCropperConfiguration = new ImageCropperConfiguration()
// {
// Crops = new[]
// {
// new ImageCropperConfiguration.Crop()
// {
// Alias = "thumb",
// Width = 100,
// Height = 100
// }
// }
// };
// var dataTypeService = new TestObjects.TestDataTypeService(
// new DataType(new ImageCropperPropertyEditor(Mock.Of<ILogger>(), mediaFileSystem, Mock.Of<IContentSection>(), Mock.Of<IDataTypeService>())) { Id = 1, Configuration = imageCropperConfiguration });
//
// var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);
//
// var converter = new ImageCropperValueConverter();
// var result = converter.ConvertSourceToIntermediate(null, factory.CreatePropertyType("test", 1), val1, false); // does not use type for conversion
//
// var resultShouldMatch = val2.DeserializeImageCropperValue();
// if (expected)
// {
// Assert.AreEqual(resultShouldMatch, result);
// }
// else
// {
// Assert.AreNotEqual(resultShouldMatch, result);
// }
// }
// finally
// {
// Current.Reset();
// }
// }
[Test]
public void GetCropUrl_CropAliasTest()

View File

@@ -142,7 +142,7 @@ namespace Umbraco.Tests.PublishedContent
var globalSettings = new GlobalSettings();
var nuCacheSettings = new NuCacheSettings();
// at last, create the complete NuCache snapshot service!
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
_snapshotService = new PublishedSnapshotService(

View File

@@ -1,20 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.ActionResults
{
public class UmbracoNotificationSuccessResponse : OkObjectResult
{
public UmbracoNotificationSuccessResponse(string successMessage) : base(null)
{
var notificationModel = new SimpleNotificationModel
{
Message = successMessage
};
notificationModel.AddSuccessNotification(successMessage, string.Empty);
Value = notificationModel;
}
}
}

View File

@@ -209,7 +209,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
else
{
AddModelErrors(result);
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return new ValidationErrorResult(ModelState);
}
}
@@ -383,12 +383,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var code = await _userManager.GeneratePasswordResetTokenAsync(identityUser);
var callbackUrl = ConstructCallbackUrl(identityUser.Id, code);
var message = _textService.Localize("login/resetPasswordEmailCopyFormat",
var message = _textService.Localize("login","resetPasswordEmailCopyFormat",
// Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(identityUser.Culture, _textService, _globalSettings),
new[] { identityUser.UserName, callbackUrl });
var subject = _textService.Localize("login/resetPasswordEmailCopySubject",
var subject = _textService.Localize("login","resetPasswordEmailCopySubject",
// Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(identityUser.Culture, _textService, _globalSettings));
@@ -445,11 +445,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return BadRequest("Invalid code");
}
var subject = _textService.Localize("login/mfaSecurityCodeSubject",
var subject = _textService.Localize("login","mfaSecurityCodeSubject",
// Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(user.Culture, _textService, _globalSettings));
var message = _textService.Localize("login/mfaSecurityCodeMessage",
var message = _textService.Localize("login","mfaSecurityCodeMessage",
// Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(user.Culture, _textService, _globalSettings),
new[] { code });
@@ -474,7 +474,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
if (ModelState.IsValid == false)
{
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return new ValidationErrorResult(ModelState);
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();

View File

@@ -335,7 +335,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
//Add error and redirect for it to be displayed
TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { _textService.Localize("login/resetCodeExpired") };
TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { _textService.Localize("login","resetCodeExpired") };
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
@@ -431,7 +431,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
// Sign in the user with this external login provider (which auto links, etc...)
var result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false);
var errors = new List<string>();
if (result == Microsoft.AspNetCore.Identity.SignInResult.Success)

View File

@@ -1,4 +1,10 @@
using Umbraco.Cms.Web.BackOffice.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Web.BackOffice.ActionResults;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
@@ -11,5 +17,55 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[AppendCurrentEventMessages]
public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController
{
/// <summary>
/// returns a 200 OK response with a notification message
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
protected OkObjectResult Ok(string message)
{
var notificationModel = new SimpleNotificationModel
{
Message = message
};
notificationModel.AddSuccessNotification(message, string.Empty);
return new OkObjectResult(notificationModel);
}
/// <summary>
/// Overridden to ensure that the error message is an error notification message
/// </summary>
/// <param name="errorMessage"></param>
/// <returns></returns>
protected override ActionResult ValidationProblem(string errorMessage)
=> ValidationProblem(errorMessage, string.Empty);
/// <summary>
/// Creates a notofication validation problem with a header and message
/// </summary>
/// <param name="errorHeader"></param>
/// <param name="errorMessage"></param>
/// <returns></returns>
protected ActionResult ValidationProblem(string errorHeader, string errorMessage)
{
var notificationModel = new SimpleNotificationModel
{
Message = errorMessage
};
notificationModel.AddErrorNotification(errorHeader, errorMessage);
return new ValidationErrorResult(notificationModel);
}
/// <summary>
/// Overridden to ensure that all queued notifications are sent to the back office
/// </summary>
/// <returns></returns>
[NonAction]
public override ActionResult ValidationProblem()
// returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
=> new ValidationErrorResult(new SimpleNotificationModel());
}
}

View File

@@ -86,9 +86,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
view.Content = display.Content;
var result = _fileService.CreatePartialView(view, display.Snippet, currentUser.Id);
if (result.Success)
{
return Ok();
}
else
return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message);
{
return ValidationProblem(result.Exception.Message);
}
case Constants.Trees.PartialViewMacros:
var viewMacro = new PartialView(PartialViewType.PartialViewMacro, display.VirtualPath);
@@ -97,7 +101,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (resultMacro.Success)
return Ok();
else
return ValidationErrorResult.CreateNotificationValidationErrorResult(resultMacro.Exception.Message);
return ValidationProblem(resultMacro.Exception.Message);
case Constants.Trees.Scripts:
var script = new Script(display.VirtualPath);
@@ -123,7 +127,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (string.IsNullOrWhiteSpace(parentId)) throw new ArgumentException("Value cannot be null or whitespace.", "parentId");
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
if (name.ContainsAny(Path.GetInvalidPathChars())) {
return ValidationErrorResult.CreateNotificationValidationErrorResult(_localizedTextService.Localize("codefile/createFolderIllegalChars"));
return ValidationProblem(_localizedTextService.Localize("codefile", "createFolderIllegalChars"));
}
// if the parentId is root (-1) then we just need an empty string as we are
@@ -418,8 +422,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
display.AddErrorNotification(
_localizedTextService.Localize("speechBubbles/partialViewErrorHeader"),
_localizedTextService.Localize("speechBubbles/partialViewErrorText"));
_localizedTextService.Localize("speechBubbles", "partialViewErrorHeader"),
_localizedTextService.Localize("speechBubbles", "partialViewErrorText"));
break;
case Constants.Trees.PartialViewMacros:
@@ -433,8 +437,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
display.AddErrorNotification(
_localizedTextService.Localize("speechBubbles/partialViewErrorHeader"),
_localizedTextService.Localize("speechBubbles/partialViewErrorText"));
_localizedTextService.Localize("speechBubbles", "partialViewErrorHeader"),
_localizedTextService.Localize("speechBubbles", "partialViewErrorText"));
break;
case Constants.Trees.Scripts:

View File

@@ -275,7 +275,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
new ContentVariantDisplay
{
CreateDate = DateTime.Now,
Name = _localizedTextService.Localize("general/recycleBin")
Name = _localizedTextService.Localize("general","recycleBin")
}
},
ContentApps = apps
@@ -417,16 +417,65 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
var emptyContent = _contentService.Create("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
var mapped = MapToDisplay(emptyContent);
return CleanContentItemDisplay(mapped);
}
private ContentItemDisplay CleanContentItemDisplay(ContentItemDisplay display)
{
// translate the content type name if applicable
mapped.ContentTypeName = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, mapped.ContentTypeName);
display.ContentTypeName = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, display.ContentTypeName);
// if your user type doesn't have access to the Settings section it would not get this property mapped
if (mapped.DocumentType != null)
mapped.DocumentType.Name = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, mapped.DocumentType.Name);
if (display.DocumentType != null)
display.DocumentType.Name = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, display.DocumentType.Name);
//remove the listview app if it exists
mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList();
display.ContentApps = display.ContentApps.Where(x => x.Alias != "umbListView").ToList();
return mapped;
return display;
}
/// <summary>
/// Gets an empty <see cref="ContentItemDisplay"/> for each content type in the IEnumerable, all with the same parent ID
/// </summary>
/// <remarks>Will attempt to re-use the same permissions for every content as long as the path and user are the same</remarks>
/// <param name="contentTypes"></param>
/// <param name="parentId"></param>
/// <returns></returns>
private IEnumerable<ContentItemDisplay> GetEmpties(IEnumerable<IContentType> contentTypes, int parentId)
{
var result = new List<ContentItemDisplay>();
var backOfficeSecurity = _backofficeSecurityAccessor.BackOfficeSecurity;
var userId = backOfficeSecurity.GetUserId().ResultOr(0);
var currentUser = backOfficeSecurity.CurrentUser;
// We know that if the ID is less than 0 the parent is null.
// Since this is called with parent ID it's safe to assume that the parent is the same for all the content types.
var parent = parentId > 0 ? _contentService.GetById(parentId) : null;
// Since the parent is the same and the path used to get permissions is based on the parent we only have to do it once
var path = parent == null ? "-1" : parent.Path;
var permissions = new Dictionary<string, EntityPermissionSet>
{
[path] = _userService.GetPermissionsForPath(currentUser, path)
};
foreach (var contentType in contentTypes)
{
var emptyContent = _contentService.Create("", parentId, contentType, userId);
var mapped = MapToDisplay(emptyContent, context =>
{
// Since the permissions depend on current user and path, we add both of these to context as well,
// that way we can compare the path and current user when mapping, if they're the same just take permissions
// and skip getting them again, in theory they should always be the same, but better safe than sorry.,
context.Items["Parent"] = parent;
context.Items["CurrentUser"] = currentUser;
context.Items["Permissions"] = permissions;
});
result.Add(CleanContentItemDisplay(mapped));
}
return result;
}
/// <summary>
@@ -437,22 +486,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[OutgoingEditorModelEvent]
public ActionResult<IDictionary<Guid, ContentItemDisplay>> GetEmptyByKeys([FromQuery] Guid[] contentTypeKeys, [FromQuery] int parentId)
{
var result = new Dictionary<Guid, ContentItemDisplay>();
using var scope = _scopeProvider.CreateScope(autoComplete: true);
var contentTypes = _contentTypeService.GetAll(contentTypeKeys).ToList();
foreach (var contentType in contentTypes)
{
if (contentType is null)
{
return NotFound();
}
result.Add(contentType.Key, GetEmptyInner(contentType, parentId));
}
return result;
return GetEmpties(contentTypes, parentId).ToDictionary(x => x.ContentTypeKey);
}
[OutgoingEditorModelEvent]
@@ -607,7 +643,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (!EnsureUniqueName(name, content, nameof(name)))
{
return new ValidationErrorResult(ModelState.ToErrorDictionary());
return ValidationProblem(ModelState);
}
var blueprint = _contentService.CreateContentFromBlueprint(content, name, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
@@ -616,8 +652,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var notificationModel = new SimpleNotificationModel();
notificationModel.AddSuccessNotification(
_localizedTextService.Localize("blueprints/createdBlueprintHeading"),
_localizedTextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name })
_localizedTextService.Localize("blueprints", "createdBlueprintHeading"),
_localizedTextService.Localize("blueprints", "createdBlueprintMessage", new[] { content.Name })
);
return notificationModel;
@@ -628,7 +664,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var existing = _contentService.GetBlueprintsForContentTypes(content.ContentTypeId);
if (existing.Any(x => x.Name == name && x.Id != content.Id))
{
ModelState.AddModelError(modelName, _localizedTextService.Localize("blueprints/duplicateBlueprintMessage"));
ModelState.AddModelError(modelName, _localizedTextService.Localize("blueprints", "duplicateBlueprintMessage"));
return false;
}
@@ -714,8 +750,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
// ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
// add the model state to the outgoing object and throw a validation message
var forDisplay = mapToDisplay(contentItem.PersistedContent);
forDisplay.Errors = ModelState.ToErrorDictionary();
return new ValidationErrorResult(forDisplay);
return ValidationProblem(forDisplay, ModelState);
}
// if there's only one variant and the model state is not valid we cannot publish so change it to save
@@ -795,15 +830,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var variantName = GetVariantName(culture, segment);
AddSuccessNotification(notifications, culture, segment,
_localizedTextService.Localize("speechBubbles/editContentSendToPublish"),
_localizedTextService.Localize("speechBubbles/editVariantSendToPublishText", new[] { variantName }));
_localizedTextService.Localize("speechBubbles", "editContentSendToPublish"),
_localizedTextService.Localize("speechBubbles", "editVariantSendToPublishText", new[] { variantName }));
}
}
else if (ModelState.IsValid)
{
globalNotifications.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/editContentSendToPublish"),
_localizedTextService.Localize("speechBubbles/editContentSendToPublishText"));
_localizedTextService.Localize("speechBubbles", "editContentSendToPublish"),
_localizedTextService.Localize("speechBubbles", "editContentSendToPublishText"));
}
}
break;
@@ -820,8 +855,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (!await ValidatePublishBranchPermissionsAsync(contentItem))
{
globalNotifications.AddErrorNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize("publish/invalidPublishBranchPermissions"));
_localizedTextService.Localize(null,"publish"),
_localizedTextService.Localize("publish", "invalidPublishBranchPermissions"));
wasCancelled = false;
break;
}
@@ -836,8 +871,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (!await ValidatePublishBranchPermissionsAsync(contentItem))
{
globalNotifications.AddErrorNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize("publish/invalidPublishBranchPermissions"));
_localizedTextService.Localize(null,"publish"),
_localizedTextService.Localize("publish", "invalidPublishBranchPermissions"));
wasCancelled = false;
break;
}
@@ -866,8 +901,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//lastly, if it is not valid, add the model state to the outgoing object and throw a 400
if (!ModelState.IsValid)
{
display.Errors = ModelState.ToErrorDictionary();
return new ValidationErrorResult(display);
return ValidationProblem(display, ModelState);
}
if (wasCancelled)
@@ -878,7 +912,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//If the item is new and the operation was cancelled, we need to return a different
// status code so the UI can handle it since it won't be able to redirect since there
// is no Id to redirect to!
return new ValidationErrorResult(display);
return ValidationProblem(display);
}
}
@@ -933,7 +967,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//if there's more than 1 variant, then we need to add the culture specific error
//messages based on the variants in error so that the messages show in the publish/save dialog
if (variants.Count > 1)
AddVariantValidationError(variant.Culture, variant.Segment, "publish/contentPublishedFailedByMissingName");
AddVariantValidationError(variant.Culture, variant.Segment, "publish","contentPublishedFailedByMissingName");
else
return false; //It's invariant and is missing critical data, it cannot be saved
}
@@ -970,15 +1004,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// <param name="variantCount"></param>
/// <param name="notifications"></param>
/// <param name="globalNotifications"></param>
/// <param name="invariantSavedLocalizationKey"></param>
/// <param name="variantSavedLocalizationKey"></param>
/// <param name="invariantSavedLocalizationAlias"></param>
/// <param name="variantSavedLocalizationAlias"></param>
/// <param name="wasCancelled"></param>
/// <remarks>
/// Method is used for normal Saving and Scheduled Publishing
/// </remarks>
private void SaveAndNotify(ContentItemSave contentItem, Func<IContent, OperationResult> saveMethod, int variantCount,
Dictionary<string, SimpleNotificationModel> notifications, SimpleNotificationModel globalNotifications,
string invariantSavedLocalizationKey, string variantSavedLocalizationKey, string cultureForInvariantErrors,
string invariantSavedLocalizationAlias, string variantSavedLocalizationAlias, string cultureForInvariantErrors,
out bool wasCancelled)
{
var saveResult = saveMethod(contentItem.PersistedContent);
@@ -998,15 +1032,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var variantName = GetVariantName(culture, segment);
AddSuccessNotification(notifications, culture, segment,
_localizedTextService.Localize("speechBubbles/editContentSavedHeader"),
_localizedTextService.Localize(variantSavedLocalizationKey, new[] { variantName }));
_localizedTextService.Localize("speechBubbles", "editContentSavedHeader"),
_localizedTextService.Localize(null,variantSavedLocalizationAlias, new[] { variantName }));
}
}
else if (ModelState.IsValid)
{
globalNotifications.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/editContentSavedHeader"),
_localizedTextService.Localize(invariantSavedLocalizationKey));
_localizedTextService.Localize("speechBubbles", "editContentSavedHeader"),
_localizedTextService.Localize("speechBubbles",invariantSavedLocalizationAlias));
}
}
}
@@ -1133,7 +1167,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
//can't continue, a mandatory variant is not published and not scheduled for publishing
// TODO: Add segment
AddVariantValidationError(culture, null, "speechBubbles/scheduleErrReleaseDate2");
AddVariantValidationError(culture, null, "speechBubbles", "scheduleErrReleaseDate2");
isValid = false;
continue;
}
@@ -1141,7 +1175,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
//can't continue, a mandatory variant is not published and it's scheduled for publishing after a non-mandatory
// TODO: Add segment
AddVariantValidationError(culture, null, "speechBubbles/scheduleErrReleaseDate3");
AddVariantValidationError(culture, null, "speechBubbles", "scheduleErrReleaseDate3");
isValid = false;
continue;
}
@@ -1155,7 +1189,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//1) release date cannot be less than now
if (variant.ReleaseDate.HasValue && variant.ReleaseDate < DateTime.Now)
{
AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles/scheduleErrReleaseDate1");
AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles", "scheduleErrReleaseDate1");
isValid = false;
continue;
}
@@ -1163,7 +1197,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//2) expire date cannot be less than now
if (variant.ExpireDate.HasValue && variant.ExpireDate < DateTime.Now)
{
AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles/scheduleErrExpireDate1");
AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles", "scheduleErrExpireDate1");
isValid = false;
continue;
}
@@ -1171,7 +1205,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//3) expire date cannot be less than release date
if (variant.ExpireDate.HasValue && variant.ReleaseDate.HasValue && variant.ExpireDate <= variant.ReleaseDate)
{
AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles/scheduleErrExpireDate2");
AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles", "scheduleErrExpireDate2");
isValid = false;
continue;
}
@@ -1399,19 +1433,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (r.publishing && !r.isValid)
{
//flagged for publishing but the mandatory culture is invalid
AddVariantValidationError(r.model.Culture, r.model.Segment, "publish/contentPublishedFailedReqCultureValidationError");
AddVariantValidationError(r.model.Culture, r.model.Segment, "publish", "contentPublishedFailedReqCultureValidationError");
canPublish = false;
}
else if (r.publishing && r.isValid && firstInvalidMandatoryCulture != null)
{
//in this case this culture also cannot be published because another mandatory culture is invalid
AddVariantValidationError(r.model.Culture, r.model.Segment, "publish/contentPublishedFailedReqCultureValidationError", firstInvalidMandatoryCulture);
AddVariantValidationError(r.model.Culture, r.model.Segment, "publish", "contentPublishedFailedReqCultureValidationError", firstInvalidMandatoryCulture);
canPublish = false;
}
else if (!r.publishing)
{
//cannot continue publishing since a required culture that is not currently being published isn't published
AddVariantValidationError(r.model.Culture, r.model.Segment, "speechBubbles/contentReqCulturePublishError");
AddVariantValidationError(r.model.Culture, r.model.Segment, "speechBubbles", "contentReqCulturePublishError");
canPublish = false;
}
}
@@ -1436,7 +1470,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var valid = persistentContent.PublishCulture(CultureImpact.Explicit(variant.Culture, defaultCulture.InvariantEquals(variant.Culture)));
if (!valid)
{
AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles/contentCultureValidationError");
AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles", "contentCultureValidationError");
return false;
}
}
@@ -1453,12 +1487,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// <param name="cultureToken">
/// The culture used in the localization message, null by default which means <see cref="culture"/> will be used.
/// </param>
private void AddVariantValidationError(string culture, string segment, string localizationKey, string cultureToken = null)
private void AddVariantValidationError(string culture, string segment, string localizationArea,string localizationAlias, string cultureToken = null)
{
var cultureToUse = cultureToken ?? culture;
var variantName = GetVariantName(cultureToUse, segment);
var errMsg = _localizedTextService.Localize(localizationKey, new[] { variantName });
var errMsg = _localizedTextService.Localize(localizationArea, localizationAlias, new[] { variantName });
ModelState.AddVariantValidationError(culture, segment, errMsg);
}
@@ -1508,7 +1542,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
var notificationModel = new SimpleNotificationModel();
AddMessageForPublishStatus(new[] { publishResult }, notificationModel);
return new ValidationErrorResult(notificationModel);
return ValidationProblem(notificationModel);
}
return Ok();
@@ -1558,9 +1592,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var moveResult = _contentService.MoveToRecycleBin(foundContent, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
if (moveResult.Success == false)
{
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return new ValidationErrorResult(new SimpleNotificationModel());
return ValidationProblem();
}
}
else
@@ -1568,9 +1600,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var deleteResult = _contentService.Delete(foundContent, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
if (deleteResult.Success == false)
{
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return new ValidationErrorResult(new SimpleNotificationModel());
return ValidationProblem();
}
}
@@ -1591,7 +1621,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
_contentService.EmptyRecycleBin(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(Constants.Security.SuperUserId));
return new UmbracoNotificationSuccessResponse(_localizedTextService.Localize("defaultdialogs/recycleBinIsEmpty"));
return Ok(_localizedTextService.Localize("defaultdialogs", "recycleBinIsEmpty"));
}
/// <summary>
@@ -1628,7 +1658,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
_logger.LogWarning("Content sorting failed, this was probably caused by an event being cancelled");
// TODO: Now you can cancel sorting, does the event messages bubble up automatically?
return new ValidationErrorResult("Content sorting failed, this was probably caused by an event being cancelled");
return ValidationProblem("Content sorting failed, this was probably caused by an event being cancelled");
}
return Ok();
@@ -1727,13 +1757,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (!unpublishResult.Success)
{
AddCancelMessage(content);
return new ValidationErrorResult(content);
return ValidationProblem(content);
}
else
{
content.AddSuccessNotification(
_localizedTextService.Localize("content/unpublish"),
_localizedTextService.Localize("speechBubbles/contentUnpublished"));
_localizedTextService.Localize("content", "unpublish"),
_localizedTextService.Localize("speechBubbles", "contentUnpublished"));
return content;
}
}
@@ -1758,8 +1788,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (results.Any(x => x.Value.Result == PublishResultType.SuccessUnpublishMandatoryCulture))
{
content.AddSuccessNotification(
_localizedTextService.Localize("content/unpublish"),
_localizedTextService.Localize("speechBubbles/contentMandatoryCultureUnpublished"));
_localizedTextService.Localize("content", "unpublish"),
_localizedTextService.Localize("speechBubbles", "contentMandatoryCultureUnpublished"));
return content;
}
@@ -1767,8 +1797,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
foreach (var r in results)
{
content.AddSuccessNotification(
_localizedTextService.Localize("content/unpublish"),
_localizedTextService.Localize("speechBubbles/contentCultureUnpublished", new[] { _allLangs.Value[r.Key].CultureName }));
_localizedTextService.Localize("conten", "unpublish"),
_localizedTextService.Localize("speechBubbles", "contentCultureUnpublished", new[] { _allLangs.Value[r.Key].CultureName }));
}
return content;
@@ -1799,7 +1829,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
catch (UriFormatException)
{
return new ValidationErrorResult(_localizedTextService.Localize("assignDomain/invalidDomain"));
return ValidationProblem(_localizedTextService.Localize("assignDomain", "invalidDomain"));
}
}
@@ -1949,7 +1979,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
foreach (var (culture, segment) in variantErrors)
{
AddVariantValidationError(culture, segment, "speechBubbles/contentCultureValidationError");
AddVariantValidationError(culture, segment, "speechBubbles", "contentCultureValidationError");
}
}
}
@@ -2069,8 +2099,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//cannot move if the content item is not allowed at the root
if (toMove.ContentType.AllowedAsRoot == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult(
_localizedTextService.Localize("moveOrCopy/notAllowedAtRoot"));
return ValidationProblem(
_localizedTextService.Localize("moveOrCopy", "notAllowedAtRoot"));
}
}
else
@@ -2086,15 +2116,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (parentContentType.AllowedContentTypes.Select(x => x.Id).ToArray()
.Any(x => x.Value == toMove.ContentType.Id) == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult(
_localizedTextService.Localize("moveOrCopy/notAllowedByContentType"));
return ValidationProblem(
_localizedTextService.Localize("moveOrCopy", "notAllowedByContentType"));
}
// Check on paths
if ($",{parent.Path},".IndexOf($",{toMove.Id},", StringComparison.Ordinal) > -1)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult(
_localizedTextService.Localize("moveOrCopy/notAllowedByPath"));
return ValidationProblem(
_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"));
}
}
@@ -2163,16 +2193,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
//either invariant single publish, or bulk publish where all statuses are already published
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/editContentPublishedHeader"),
_localizedTextService.Localize("speechBubbles/editContentPublishedText"));
_localizedTextService.Localize("speechBubbles", "editContentPublishedHeader"),
_localizedTextService.Localize("speechBubbles", "editContentPublishedText"));
}
else
{
foreach (var c in successfulCultures)
{
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/editContentPublishedHeader"),
_localizedTextService.Localize("speechBubbles/editVariantPublishedText", new[] { _allLangs.Value[c].CultureName }));
_localizedTextService.Localize("speechBubbles", "editContentPublishedHeader"),
_localizedTextService.Localize("speechBubbles", "editVariantPublishedText", new[] { _allLangs.Value[c].CultureName }));
}
}
}
@@ -2188,20 +2218,20 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (successfulCultures == null)
{
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/editContentPublishedHeader"),
_localizedTextService.Localize("speechBubbles", "editContentPublishedHeader"),
totalStatusCount > 1
? _localizedTextService.Localize("speechBubbles/editMultiContentPublishedText", new[] { itemCount.ToInvariantString() })
: _localizedTextService.Localize("speechBubbles/editContentPublishedText"));
? _localizedTextService.Localize("speechBubbles", "editMultiContentPublishedText", new[] { itemCount.ToInvariantString() })
: _localizedTextService.Localize("speechBubbles", "editContentPublishedText"));
}
else
{
foreach (var c in successfulCultures)
{
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/editContentPublishedHeader"),
_localizedTextService.Localize("speechBubbles", "editContentPublishedHeader"),
totalStatusCount > 1
? _localizedTextService.Localize("speechBubbles/editMultiVariantPublishedText", new[] { itemCount.ToInvariantString(), _allLangs.Value[c].CultureName })
: _localizedTextService.Localize("speechBubbles/editVariantPublishedText", new[] { _allLangs.Value[c].CultureName }));
? _localizedTextService.Localize("speechBubbles", "editMultiVariantPublishedText", new[] { itemCount.ToInvariantString(), _allLangs.Value[c].CultureName })
: _localizedTextService.Localize("speechBubbles", "editVariantPublishedText", new[] { _allLangs.Value[c].CultureName }));
}
}
}
@@ -2211,8 +2241,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
display.AddWarningNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize("publish/contentPublishedFailedByParent",
_localizedTextService.Localize(null,"publish"),
_localizedTextService.Localize("publish", "contentPublishedFailedByParent",
new[] { names }).Trim());
}
break;
@@ -2220,7 +2250,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
AddCancelMessage(display, message: "publish/contentPublishedFailedByEvent", messageParams: new[] { names });
AddCancelMessage(display, "publish","contentPublishedFailedByEvent", messageParams: new[] { names });
}
break;
case PublishResultType.FailedPublishAwaitingRelease:
@@ -2228,8 +2258,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
display.AddWarningNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize("publish/contentPublishedFailedAwaitingRelease",
_localizedTextService.Localize(null,"publish"),
_localizedTextService.Localize("publish", "contentPublishedFailedAwaitingRelease",
new[] { names }).Trim());
}
break;
@@ -2238,8 +2268,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
display.AddWarningNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize("publish/contentPublishedFailedExpired",
_localizedTextService.Localize(null,"publish"),
_localizedTextService.Localize("publish", "contentPublishedFailedExpired",
new[] { names }).Trim());
}
break;
@@ -2248,8 +2278,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//TODO: This doesn't take into account variations with the successfulCultures param
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
display.AddWarningNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize("publish/contentPublishedFailedIsTrashed",
_localizedTextService.Localize(null,"publish"),
_localizedTextService.Localize("publish", "contentPublishedFailedIsTrashed",
new[] { names }).Trim());
}
break;
@@ -2259,8 +2289,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
display.AddWarningNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize("publish/contentPublishedFailedInvalid",
_localizedTextService.Localize(null,"publish"),
_localizedTextService.Localize("publish", "contentPublishedFailedInvalid",
new[] { names }).Trim());
}
else
@@ -2269,8 +2299,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
var names = string.Join(", ", status.Select(x => $"'{(x.Content.ContentType.VariesByCulture() ? x.Content.GetCultureName(c) : x.Content.Name)}'"));
display.AddWarningNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize("publish/contentPublishedFailedInvalid",
_localizedTextService.Localize(null,"publish"),
_localizedTextService.Localize("publish", "contentPublishedFailedInvalid",
new[] { names }).Trim());
}
}
@@ -2278,7 +2308,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
break;
case PublishResultType.FailedPublishMandatoryCultureMissing:
display.AddWarningNotification(
_localizedTextService.Localize("publish"),
_localizedTextService.Localize(null,"publish"),
"publish/contentPublishedFailedByCulture");
break;
default:
@@ -2292,12 +2322,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
private ContentItemDisplay MapToDisplay(IContent content)
{
var display = _umbracoMapper.Map<ContentItemDisplay>(content, context =>
private ContentItemDisplay MapToDisplay(IContent content) =>
MapToDisplay(content, context =>
{
context.Items["CurrentUser"] = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
});
/// <summary>
/// Used to map an <see cref="IContent"/> instance to a <see cref="ContentItemDisplay"/> and ensuring AllowPreview is set correctly.
/// Also allows you to pass in an action for the mapper context where you can pass additional information on to the mapper.
/// </summary>
/// <param name="content"></param>
/// <param name="contextOptions"></param>
/// <returns></returns>
private ContentItemDisplay MapToDisplay(IContent content, Action<MapperContext> contextOptions)
{
var display = _umbracoMapper.Map<ContentItemDisplay>(content, contextOptions);
display.AllowPreview = display.AllowPreview && content.Trashed == false && content.ContentType.IsElement == false;
return display;
}
@@ -2403,8 +2443,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (rollbackResult.Success)
return Ok();
var notificationModel = new SimpleNotificationModel();
switch (rollbackResult.Result)
{
case OperationResultType.Failed:
@@ -2412,22 +2450,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case OperationResultType.FailedExceptionThrown:
case OperationResultType.NoOperation:
default:
notificationModel.AddErrorNotification(
_localizedTextService.Localize("speechBubbles/operationFailedHeader"),
null); // TODO: There is no specific failed to save error message AFAIK
break;
return ValidationProblem(_localizedTextService.Localize("speechBubbles", "operationFailedHeader"));
case OperationResultType.FailedCancelledByEvent:
notificationModel.AddErrorNotification(
_localizedTextService.Localize("speechBubbles/operationCancelledHeader"),
_localizedTextService.Localize("speechBubbles/operationCancelledText"));
break;
return ValidationProblem(
_localizedTextService.Localize("speechBubbles", "operationCancelledHeader"),
_localizedTextService.Localize("speechBubbles", "operationCancelledText"));
}
return new ValidationErrorResult(notificationModel);
}
}
}

View File

@@ -190,15 +190,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// Adds a cancelled message to the display
/// </summary>
/// <param name="display"></param>
/// <param name="header"></param>
/// <param name="message"></param>
/// <param name="localizeHeader"></param>
/// <param name="localizeMessage"></param>
/// <param name="headerParams"></param>
/// <param name="messageArea"></param>
/// <param name="messageAlias"></param>
/// <param name="messageParams"></param>
protected void AddCancelMessage(INotificationModel display, string header = "speechBubbles/operationCancelledHeader", string message = "speechBubbles/operationCancelledText", bool localizeHeader = true,
bool localizeMessage = true,
string[] headerParams = null,
protected void AddCancelMessage(
INotificationModel display,
string messageArea = "speechBubbles",
string messageAlias ="operationCancelledText",
string[] messageParams = null)
{
// if there's already a default event message, don't add our default one
@@ -209,8 +207,29 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
display.AddWarningNotification(
localizeHeader ? LocalizedTextService.Localize(header, headerParams) : header,
localizeMessage ? LocalizedTextService.Localize(message, messageParams) : message);
LocalizedTextService.Localize("speechBubbles", "operationCancelledHeader"),
LocalizedTextService.Localize(messageArea, messageAlias, messageParams));
}
/// <summary>
/// Adds a cancelled message to the display
/// </summary>
/// <param name="display"></param>
/// <param name="header"></param>
/// <param name="message"></param>
/// <param name="headerArea"></param>
/// <param name="headerAlias"></param>
/// <param name="headerParams"></param>
protected void AddCancelMessage(INotificationModel display, string message)
{
// if there's already a default event message, don't add our default one
IEventMessagesFactory messages = EventMessages;
if (messages != null && messages.GetOrDefault().GetAll().Any(x => x.IsDefaultEventMessage))
{
return;
}
display.AddWarningNotification(LocalizedTextService.Localize("speechBubbles", "operationCancelledHeader"), message);
}
}
}

View File

@@ -297,7 +297,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (result.Success)
return Ok(result.Result); //return the id
else
return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message);
return ValidationProblem(result.Exception.Message);
}
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
@@ -308,7 +308,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (result.Success)
return Ok(result.Result); //return the id
else
return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message);
return ValidationProblem(result.Exception.Message);
}
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
@@ -364,7 +364,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/contentTypeSavedHeader"),
_localizedTextService.Localize("speechBubbles","contentTypeSavedHeader"),
string.Empty);
return display;
@@ -612,8 +612,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
else
{
model.Notifications.Add(new BackOfficeNotification(
_localizedTextService.Localize("speechBubbles/operationFailedHeader"),
_localizedTextService.Localize("media/disallowedFileType"),
_localizedTextService.Localize("speechBubbles","operationFailedHeader"),
_localizedTextService.Localize("media","disallowedFileType"),
NotificationStyle.Warning));
}
}

View File

@@ -13,9 +13,7 @@ using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Extensions;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
@@ -27,7 +25,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// </summary>
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[PrefixlessBodyModelValidator]
public abstract class ContentTypeControllerBase<TContentType> : UmbracoAuthorizedJsonController
public abstract class ContentTypeControllerBase<TContentType> : BackOfficeNotificationsController
where TContentType : class, IContentTypeComposition
{
private readonly EditorValidatorCollection _editorValidatorCollection;
@@ -274,7 +272,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var exists = allAliases.InvariantContains(contentTypeSave.Alias);
if (exists && (ctId == 0 || !ct.Alias.InvariantEquals(contentTypeSave.Alias)))
{
ModelState.AddModelError("Alias", LocalizedTextService.Localize("editcontenttype/aliasAlreadyExists"));
ModelState.AddModelError("Alias", LocalizedTextService.Localize("editcontenttype", "aliasAlreadyExists"));
}
// execute the external validators
@@ -283,7 +281,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
{
var err = CreateModelStateValidationEror<TContentTypeSave, TContentTypeDisplay>(ctId, contentTypeSave, ct);
return new ValidationErrorResult(err);
return ValidationProblem(err);
}
//filter out empty properties
@@ -305,11 +303,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
catch (Exception ex)
{
var responseEx = CreateInvalidCompositionResponseException<TContentTypeDisplay, TContentTypeSave, TPropertyType>(ex, contentTypeSave, ct, ctId);
if (responseEx != null) return new ValidationErrorResult(responseEx);
if (responseEx != null) return ValidationProblem(responseEx);
}
var exResult = CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType, TContentTypeDisplay>(contentTypeSave, ct);
if (exResult != null) return new ValidationErrorResult(exResult);
if (exResult != null) return ValidationProblem(exResult);
saveContentType(ct);
@@ -348,11 +346,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (responseEx is null)
throw ex;
return new ValidationErrorResult(responseEx);
return ValidationProblem(responseEx);
}
var exResult = CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType, TContentTypeDisplay>(contentTypeSave, newCt);
if (exResult != null) return new ValidationErrorResult(exResult);
if (exResult != null) return ValidationProblem(exResult);
//set id to null to ensure its handled as a new type
contentTypeSave.Id = null;
@@ -417,13 +415,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case MoveOperationStatusType.FailedParentNotFound:
return NotFound();
case MoveOperationStatusType.FailedCancelledByEvent:
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return new ValidationErrorResult(new SimpleNotificationModel());
return ValidationProblem();
case MoveOperationStatusType.FailedNotAllowedByPath:
var notificationModel = new SimpleNotificationModel();
notificationModel.AddErrorNotification(LocalizedTextService.Localize("moveOrCopy/notAllowedByPath"), "");
return new ValidationErrorResult(notificationModel);
return ValidationProblem(LocalizedTextService.Localize("moveOrCopy", "notAllowedByPath"));
default:
throw new ArgumentOutOfRangeException();
}
@@ -458,13 +452,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case MoveOperationStatusType.FailedParentNotFound:
return NotFound();
case MoveOperationStatusType.FailedCancelledByEvent:
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return new ValidationErrorResult(new SimpleNotificationModel());
return ValidationProblem();
case MoveOperationStatusType.FailedNotAllowedByPath:
var notificationModel = new SimpleNotificationModel();
notificationModel.AddErrorNotification(LocalizedTextService.Localize("moveOrCopy/notAllowedByPath"), "");
return new ValidationErrorResult(notificationModel);
return ValidationProblem(LocalizedTextService.Localize("moveOrCopy", "notAllowedByPath"));
default:
throw new ArgumentOutOfRangeException();
}

View File

@@ -190,7 +190,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
// so that is why it is being used here.
ModelState.AddModelError("value", result.Errors.ToErrorMessage());
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
//They've successfully set their password, we can now update their user account to be approved
@@ -233,7 +233,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
// even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword
var result = new ModelWithNotifications<string>(passwordChangeResult.Result.ResetPassword);
result.AddSuccessNotification(_localizedTextService.Localize("user/password"), _localizedTextService.Localize("user/passwordChanged"));
result.AddSuccessNotification(_localizedTextService.Localize("user","password"), _localizedTextService.Localize("user","passwordChanged"));
return result;
}
@@ -242,7 +242,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
}
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
// TODO: Why is this necessary? This inherits from UmbracoAuthorizedApiController

View File

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration;
@@ -20,6 +21,7 @@ using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Filters;
using Umbraco.Core.Dashboards;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
@@ -39,7 +41,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
private readonly IDashboardService _dashboardService;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IShortStringHelper _shortStringHelper;
private readonly IOptions<ContentDashboardSettings> _dashboardSettings;
/// <summary>
/// Initializes a new instance of the <see cref="DashboardController"/> with all its dependencies.
/// </summary>
@@ -49,7 +51,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
ILogger<DashboardController> logger,
IDashboardService dashboardService,
IUmbracoVersion umbracoVersion,
IShortStringHelper shortStringHelper)
IShortStringHelper shortStringHelper,
IOptions<ContentDashboardSettings> dashboardSettings)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
@@ -58,6 +61,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
_dashboardService = dashboardService;
_umbracoVersion = umbracoVersion;
_shortStringHelper = shortStringHelper;
_dashboardSettings = dashboardSettings;
}
//we have just one instance of HttpClient shared for the entire application
@@ -65,7 +69,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side
[ValidateAngularAntiForgeryToken]
public async Task<JObject> GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/")
public async Task<JObject> GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.com/")
{
var user = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
var allowedSections = string.Join(",", user.AllowedSections);
@@ -73,7 +77,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild();
var isAdmin = user.IsAdmin();
var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}&admin={4}", section, allowedSections, language, version, isAdmin);
var url = string.Format("{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}",
baseUrl,
_dashboardSettings.Value.ContentDashboardPath,
section,
allowedSections,
language,
version,
isAdmin);
var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section;
var content = _appCaches.RuntimeCache.GetCacheItem<JObject>(key);

View File

@@ -16,9 +16,7 @@ using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Extensions;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
@@ -267,7 +265,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (result.Success)
return Ok(result.Result); //return the id
else
return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message);
return ValidationProblem(result.Exception.Message);
}
/// <summary>
@@ -302,12 +300,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
catch (DuplicateNameException ex)
{
ModelState.AddModelError("Name", ex.Message);
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
// map back to display model, and return
var display = _umbracoMapper.Map<IDataType, DataTypeDisplay>(dataType.PersistedDataType);
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/dataTypeSaved"), "");
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles", "dataTypeSaved"), "");
return display;
}
@@ -335,13 +333,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case MoveOperationStatusType.FailedParentNotFound:
return NotFound();
case MoveOperationStatusType.FailedCancelledByEvent:
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return new ValidationErrorResult(new SimpleNotificationModel());
return ValidationProblem();
case MoveOperationStatusType.FailedNotAllowedByPath:
var notificationModel = new SimpleNotificationModel();
notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedByPath"), "");
return new ValidationErrorResult(notificationModel);
notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"), "");
return ValidationProblem(notificationModel);
default:
throw new ArgumentOutOfRangeException();
}
@@ -355,7 +351,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (result.Success)
return Ok(result.Result);
else
return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message);
return ValidationProblem(result.Exception.Message);
}
/// <summary>

View File

@@ -13,8 +13,6 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Extensions;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
@@ -101,15 +99,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
public ActionResult<int> Create(int parentId, string key)
{
if (string.IsNullOrEmpty(key))
return ValidationErrorResult.CreateNotificationValidationErrorResult("Key can not be empty."); // TODO: translate
return ValidationProblem("Key can not be empty."); // TODO: translate
if (_localizationService.DictionaryItemExists(key))
{
var message = _localizedTextService.Localize(
"dictionaryItem/changeKeyError",
"dictionaryItem","changeKeyError",
_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserCulture(_localizedTextService, _globalSettings),
new Dictionary<string, string> { { "0", key } });
return ValidationErrorResult.CreateNotificationValidationErrorResult(message);
return ValidationProblem(message);
}
try
@@ -130,7 +128,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
catch (Exception ex)
{
_logger.LogError(ex, "Error creating dictionary with {Name} under {ParentId}", key, parentId);
return ValidationErrorResult.CreateNotificationValidationErrorResult("Error creating dictionary item");
return ValidationProblem("Error creating dictionary item");
}
}
@@ -207,7 +205,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
_localizationService.GetDictionaryItemById(int.Parse(dictionary.Id.ToString()));
if (dictionaryItem == null)
return ValidationErrorResult.CreateNotificationValidationErrorResult("Dictionary item does not exist");
return ValidationProblem("Dictionary item does not exist");
var userCulture = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserCulture(_localizedTextService, _globalSettings);
@@ -220,11 +218,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
var message = _localizedTextService.Localize(
"dictionaryItem/changeKeyError",
"dictionaryItem","changeKeyError",
userCulture,
new Dictionary<string, string> { { "0", dictionary.Name } });
ModelState.AddModelError("Name", message);
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
dictionaryItem.ItemKey = dictionary.Name;
@@ -243,7 +241,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var model = _umbracoMapper.Map<IDictionaryItem, DictionaryDisplay>(dictionaryItem);
model.Notifications.Add(new BackOfficeNotification(
_localizedTextService.Localize("speechBubbles/dictionaryItemSaved", userCulture), string.Empty,
_localizedTextService.Localize("speechBubbles","dictionaryItemSaved", userCulture), string.Empty,
NotificationStyle.Success));
return model;
@@ -251,7 +249,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
catch (Exception ex)
{
_logger.LogError(ex, "Error saving dictionary with {Name} under {ParentId}", dictionary.Name, dictionary.ParentId);
return ValidationErrorResult.CreateNotificationValidationErrorResult("Something went wrong saving dictionary");
return ValidationProblem("Something went wrong saving dictionary");
}
}

View File

@@ -53,8 +53,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(x => !x.Name.IsNullOrWhiteSpace())
.Select(x => new CultureInfo(x.Name)) // important!
.OrderBy(x => x.DisplayName)
.ToDictionary(x => x.Name, x => x.DisplayName);
.OrderBy(x => x.EnglishName)
.ToDictionary(x => x.Name, x => x.EnglishName);
}
/// <summary>
@@ -97,7 +97,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (language.IsDefault)
{
var message = $"Language '{language.IsoCode}' is currently set to 'default' and can not be deleted.";
return ValidationErrorResult.CreateNotificationValidationErrorResult(message);
return ValidationProblem(message);
}
// service is happy deleting a language that's fallback for another language,
@@ -116,7 +116,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
public ActionResult<Language> SaveLanguage(Language language)
{
if (!ModelState.IsValid)
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
// this is prone to race conditions but the service will not let us proceed anyways
var existingByCulture = _localizationService.GetLanguageByIsoCode(language.IsoCode);
@@ -132,7 +132,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
//someone is trying to create a language that already exist
ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists");
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
var existingById = language.Id != default ? _localizationService.GetLanguageById(language.Id) : null;
@@ -149,7 +149,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
catch (CultureNotFoundException)
{
ModelState.AddModelError("IsoCode", "No Culture found with name " + language.IsoCode);
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
// create it (creating a new language cannot create a fallback cycle)
@@ -172,7 +172,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (existingById.IsDefault && !language.IsDefault)
{
ModelState.AddModelError("IsDefault", "Cannot un-default the default language.");
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
existingById.IsDefault = language.IsDefault;
@@ -187,12 +187,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (!languages.ContainsKey(existingById.FallbackLanguageId.Value))
{
ModelState.AddModelError("FallbackLanguage", "The selected fall back language does not exist.");
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
if (CreatesCycle(existingById, languages))
{
ModelState.AddModelError("FallbackLanguage", $"The selected fall back language {languages[existingById.FallbackLanguageId.Value].IsoCode} would create a circular path.");
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
}

View File

@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Constants = Umbraco.Cms.Core.Constants;
@@ -18,7 +17,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
public class LogViewerController : UmbracoAuthorizedJsonController
public class LogViewerController : BackOfficeNotificationsController
{
private readonly ILogViewer _logViewer;
@@ -51,7 +50,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//We will need to stop the request if trying to do this on a 1GB file
if (CanViewLogs(logTimePeriod) == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Unable to view logs, due to size");
return ValidationProblem("Unable to view logs, due to size");
}
return _logViewer.GetNumberOfErrors(logTimePeriod);
@@ -64,7 +63,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//We will need to stop the request if trying to do this on a 1GB file
if (CanViewLogs(logTimePeriod) == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Unable to view logs, due to size");
return ValidationProblem("Unable to view logs, due to size");
}
return _logViewer.GetLogLevelCounts(logTimePeriod);
@@ -77,7 +76,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//We will need to stop the request if trying to do this on a 1GB file
if (CanViewLogs(logTimePeriod) == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Unable to view logs, due to size");
return ValidationProblem("Unable to view logs, due to size");
}
return new ActionResult<IEnumerable<LogTemplate>>(_logViewer.GetMessageTemplates(logTimePeriod));
@@ -91,7 +90,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//We will need to stop the request if trying to do this on a 1GB file
if (CanViewLogs(logTimePeriod) == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Unable to view logs, due to size");
return ValidationProblem("Unable to view logs, due to size");
}
var direction = orderDirection == "Descending" ? Direction.Descending : Direction.Ascending;

View File

@@ -15,7 +15,6 @@ using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
@@ -74,19 +73,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
if (string.IsNullOrWhiteSpace(name))
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Name can not be empty");
return ValidationProblem("Name can not be empty");
}
var alias = name.ToSafeAlias(_shortStringHelper);
if (_macroService.GetByAlias(alias) != null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Macro with this alias already exists");
return ValidationProblem("Macro with this alias already exists");
}
if (name == null || name.Length > 255)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Name cannnot be more than 255 characters in length.");
return ValidationProblem("Name cannnot be more than 255 characters in length.");
}
try
@@ -106,7 +105,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
const string errorMessage = "Error creating macro";
_logger.LogError(exception, errorMessage);
return ValidationErrorResult.CreateNotificationValidationErrorResult(errorMessage);
return ValidationProblem(errorMessage);
}
}
@@ -117,7 +116,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
return ValidationProblem($"Macro with id {id} does not exist");
}
var macroDisplay = MapToDisplay(macro);
@@ -132,7 +131,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
return ValidationProblem($"Macro with id {id} does not exist");
}
var macroDisplay = MapToDisplay(macro);
@@ -145,12 +144,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
var guidUdi = id as GuidUdi;
if (guidUdi == null)
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
return ValidationProblem($"Macro with id {id} does not exist");
var macro = _macroService.GetById(guidUdi.Guid);
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
return ValidationProblem($"Macro with id {id} does not exist");
}
var macroDisplay = MapToDisplay(macro);
@@ -165,7 +164,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist");
return ValidationProblem($"Macro with id {id} does not exist");
}
_macroService.Delete(macro);
@@ -178,19 +177,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
if (macroDisplay == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("No macro data found in request");
return ValidationProblem("No macro data found in request");
}
if (macroDisplay.Name == null || macroDisplay.Name.Length > 255)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Name cannnot be more than 255 characters in length.");
return ValidationProblem("Name cannnot be more than 255 characters in length.");
}
var macro = _macroService.GetById(int.Parse(macroDisplay.Id.ToString()));
if (macro == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {macroDisplay.Id} does not exist");
return ValidationProblem($"Macro with id {macroDisplay.Id} does not exist");
}
if (macroDisplay.Alias != macro.Alias)
@@ -199,7 +198,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (macroByAlias != null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Macro with this alias already exists");
return ValidationProblem("Macro with this alias already exists");
}
}
@@ -227,7 +226,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
const string errorMessage = "Error creating macro";
_logger.LogError(exception, errorMessage);
return ValidationErrorResult.CreateNotificationValidationErrorResult(errorMessage);
return ValidationProblem(errorMessage);
}
}

View File

@@ -157,7 +157,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
Id = Constants.System.RecycleBinMedia,
Alias = "recycleBin",
ParentId = -1,
Name = _localizedTextService.Localize("general/recycleBin"),
Name = _localizedTextService.Localize("general", "recycleBin"),
ContentTypeAlias = "recycleBin",
CreateDate = DateTime.Now,
IsContainer = true,
@@ -452,9 +452,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var moveResult = _mediaService.MoveToRecycleBin(foundMedia, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(Constants.Security.SuperUserId));
if (moveResult == false)
{
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return new ValidationErrorResult(new SimpleNotificationModel());
return ValidationProblem();
}
}
else
@@ -462,9 +460,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var deleteResult = _mediaService.Delete(foundMedia, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(Constants.Security.SuperUserId));
if (deleteResult == false)
{
//returning an object of INotificationModel will ensure that any pending
// notification messages are added to the response.
return new ValidationErrorResult(new SimpleNotificationModel());
return ValidationProblem();
}
}
@@ -500,11 +496,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (sourceParentID == destinationParentID)
{
return new ValidationErrorResult(new SimpleNotificationModel(new BackOfficeNotification("",_localizedTextService.Localize("media/moveToSameFolderFailed"),NotificationStyle.Error)));
return ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification("",_localizedTextService.Localize("media", "moveToSameFolderFailed"),NotificationStyle.Error)));
}
if (moveResult == false)
{
return new ValidationErrorResult(new SimpleNotificationModel());
return ValidationProblem();
}
else
{
@@ -563,9 +559,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
// add the model state to the outgoing object and throw validation response
var forDisplay = _umbracoMapper.Map<MediaItemDisplay>(contentItem.PersistedContent);
forDisplay.Errors = ModelState.ToErrorDictionary();
return new ValidationErrorResult(forDisplay);
MediaItemDisplay forDisplay = _umbracoMapper.Map<MediaItemDisplay>(contentItem.PersistedContent);
return ValidationProblem(forDisplay, ModelState);
}
}
@@ -578,8 +573,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//lastly, if it is not valid, add the model state to the outgoing object and throw a 403
if (!ModelState.IsValid)
{
display.Errors = ModelState.ToErrorDictionary();
return new ValidationErrorResult(display, StatusCodes.Status403Forbidden);
return ValidationProblem(display, ModelState, StatusCodes.Status403Forbidden);
}
//put the correct msgs in
@@ -590,8 +584,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (saveStatus.Success)
{
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/editMediaSaved"),
_localizedTextService.Localize("speechBubbles/editMediaSavedText"));
_localizedTextService.Localize("speechBubbles", "editMediaSaved"),
_localizedTextService.Localize("speechBubbles", "editMediaSavedText"));
}
else
{
@@ -602,7 +596,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
// is no Id to redirect to!
if (saveStatus.Result.Result == OperationResultType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action))
{
return new ValidationErrorResult(display);
return ValidationProblem(display);
}
}
@@ -622,7 +616,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
_mediaService.EmptyRecycleBin(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(Constants.Security.SuperUserId));
return new UmbracoNotificationSuccessResponse(_localizedTextService.Localize("defaultdialogs/recycleBinIsEmpty"));
return Ok(_localizedTextService.Localize("defaultdialogs", "recycleBinIsEmpty"));
}
/// <summary>
@@ -661,7 +655,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (_mediaService.Sort(sortedMedia) == false)
{
_logger.LogWarning("Media sorting failed, this was probably caused by an event being cancelled");
return new ValidationErrorResult("Media sorting failed, this was probably caused by an event being cancelled");
return ValidationProblem("Media sorting failed, this was probably caused by an event being cancelled");
}
return Ok();
}
@@ -837,15 +831,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var saveResult = _mediaService.Save(f, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id);
if (saveResult == false)
{
AddCancelMessage(tempFiles,
message: _localizedTextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName);
AddCancelMessage(tempFiles, _localizedTextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName);
}
}
else
{
tempFiles.Notifications.Add(new BackOfficeNotification(
_localizedTextService.Localize("speechBubbles/operationFailedHeader"),
_localizedTextService.Localize("media/disallowedFileType"),
_localizedTextService.Localize("speechBubbles", "operationFailedHeader"),
_localizedTextService.Localize("media", "disallowedFileType"),
NotificationStyle.Warning));
}
}
@@ -919,7 +912,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
else
{
return new ValidationErrorResult("The request was not formatted correctly, the parentId is not an integer, Guid or UDI");
return ValidationProblem("The request was not formatted correctly, the parentId is not an integer, Guid or UDI");
}
}
@@ -931,10 +924,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var authorizationResult = await _authorizationService.AuthorizeAsync(User, new MediaPermissionsResource(_mediaService.GetById(intParentId)), requirement);
if (!authorizationResult.Succeeded)
{
return new ValidationErrorResult(
return ValidationProblem(
new SimpleNotificationModel(new BackOfficeNotification(
_localizedTextService.Localize("speechBubbles/operationFailedHeader"),
_localizedTextService.Localize("speechBubbles/invalidUserPermissionsText"),
_localizedTextService.Localize("speechBubbles", "operationFailedHeader"),
_localizedTextService.Localize("speechBubbles", "invalidUserPermissionsText"),
NotificationStyle.Warning)),
StatusCodes.Status403Forbidden);
}
@@ -969,8 +962,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (toMove.ContentType.AllowedAsRoot == false && mediaTypeService.GetAll().Any(ct => ct.AllowedAsRoot))
{
var notificationModel = new SimpleNotificationModel();
notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedAtRoot"), "");
return new ValidationErrorResult(notificationModel);
notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedAtRoot"), "");
return ValidationProblem(notificationModel);
}
}
else
@@ -987,16 +980,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
.Any(x => x.Value == toMove.ContentType.Id) == false)
{
var notificationModel = new SimpleNotificationModel();
notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedByContentType"), "");
return new ValidationErrorResult(notificationModel);
notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByContentType"), "");
return ValidationProblem(notificationModel);
}
// Check on paths
if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1)
{
var notificationModel = new SimpleNotificationModel();
notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedByPath"), "");
return new ValidationErrorResult(notificationModel);
notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"), "");
return ValidationProblem(notificationModel);
}
}

View File

@@ -266,7 +266,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (result.Success)
return Ok(result.Result); //return the id
else
return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message);
return ValidationProblem(result.Exception.Message);
}
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
@@ -277,7 +277,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (result.Success)
return Ok(result.Result); //return the id
else
return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message);
return ValidationProblem(result.Exception.Message);
}
[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
@@ -297,7 +297,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/mediaTypeSavedHeader"),
_localizedTextService.Localize("speechBubbles","mediaTypeSavedHeader"),
string.Empty);
return display;

View File

@@ -26,11 +26,8 @@ using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Implement;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.BackOffice.Extensions;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.BackOffice.ModelBinders;
using Umbraco.Cms.Web.BackOffice.Security;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.Filters;
@@ -260,8 +257,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
{
MemberDisplay forDisplay = _umbracoMapper.Map<MemberDisplay>(contentItem.PersistedContent);
forDisplay.Errors = ModelState.ToErrorDictionary();
return new ValidationErrorResult(forDisplay);
return ValidationProblem(forDisplay, ModelState);
}
// Create a scope here which will wrap all child data operations in a single transaction.
@@ -300,8 +296,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
// lastly, if it is not valid, add the model state to the outgoing object and throw a 403
if (!ModelState.IsValid)
{
display.Errors = ModelState.ToErrorDictionary();
return new ValidationErrorResult(display, StatusCodes.Status403Forbidden);
return ValidationProblem(display, ModelState, StatusCodes.Status403Forbidden);
}
// put the correct messages in
@@ -310,8 +305,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case ContentSaveAction.Save:
case ContentSaveAction.SaveNew:
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/editMemberSaved"),
_localizedTextService.Localize("speechBubbles/editMemberSaved"));
_localizedTextService.Localize("speechBubbles","editMemberSaved"),
_localizedTextService.Localize("speechBubbles","editMemberSaved"));
break;
}
@@ -373,7 +368,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (created.Succeeded == false)
{
return new ValidationErrorResult(created.Errors.ToErrorMessage());
return ValidationProblem(created.Errors.ToErrorMessage());
}
// now re-look up the member, which will now exist
@@ -460,7 +455,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
MemberIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id.ToString());
if (identityMember == null)
{
return new ValidationErrorResult("Member was not found");
return ValidationProblem("Member was not found");
}
// Handle unlocking with the member manager (takes care of other nuances)
@@ -469,7 +464,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
IdentityResult unlockResult = await _memberManager.SetLockoutEndDateAsync(identityMember, DateTimeOffset.Now.AddMinutes(-1));
if (unlockResult.Succeeded == false)
{
return new ValidationErrorResult(
return ValidationProblem(
$"Could not unlock for member {contentItem.Id} - error {unlockResult.Errors.ToErrorMessage()}");
}
needsResync = true;
@@ -478,7 +473,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
// NOTE: This should not ever happen unless someone is mucking around with the request data.
// An admin cannot simply lock a user, they get locked out by password attempts, but an admin can unlock them
return new ValidationErrorResult("An admin cannot lock a member");
return ValidationProblem("An admin cannot lock a member");
}
// If we're changing the password...
@@ -488,13 +483,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
IdentityResult validatePassword = await _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword);
if (validatePassword.Succeeded == false)
{
return new ValidationErrorResult(validatePassword.Errors.ToErrorMessage());
return ValidationProblem(validatePassword.Errors.ToErrorMessage());
}
Attempt<int> intId = identityMember.Id.TryConvertTo<int>();
if (intId.Success == false)
{
return new ValidationErrorResult("Member ID was not valid");
return ValidationProblem("Member ID was not valid");
}
var changingPasswordModel = new ChangingPasswordModel
@@ -513,7 +508,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError?.ErrorMessage ?? string.Empty);
}
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
needsResync = true;
@@ -622,7 +617,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
IdentityResult identityResult = await _memberManager.RemoveFromRolesAsync(identityMember, rolesToRemove);
if (!identityResult.Succeeded)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult(identityResult.Errors.ToErrorMessage());
return ValidationProblem(identityResult.Errors.ToErrorMessage());
}
hasChanges = true;
}
@@ -635,7 +630,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
IdentityResult identityResult = await _memberManager.AddToRolesAsync(identityMember, toAdd);
if (!identityResult.Succeeded)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult(identityResult.Errors.ToErrorMessage());
return ValidationProblem(identityResult.Errors.ToErrorMessage());
}
hasChanges = true;
}

View File

@@ -135,7 +135,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
MemberGroupDisplay display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/memberGroupSavedHeader"),
_localizedTextService.Localize("speechBubbles","memberGroupSavedHeader"),
string.Empty);
return display;

View File

@@ -246,7 +246,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var display =_umbracoMapper.Map<MemberTypeDisplay>(savedCt.Value);
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles/memberTypeSavedHeader"),
_localizedTextService.Localize("speechBubbles","memberTypeSavedHeader"),
string.Empty);
return display;

View File

@@ -88,14 +88,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
public ActionResult<PackageDefinition> PostSavePackage(PackageDefinition model)
{
if (ModelState.IsValid == false)
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
//save it
if (!_packagingService.SaveCreatedPackage(model))
return ValidationErrorResult.CreateNotificationValidationErrorResult(
{
return ValidationProblem(
model.Id == default
? $"A package with the name {model.Name} already exists"
: $"The package with id {model.Id} was not found");
}
_packagingService.ExportCreatedPackage(model);
@@ -153,7 +155,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return NotFound();
if (!System.IO.File.Exists(package.PackagePath))
return ValidationErrorResult.CreateNotificationValidationErrorResult("No file found for path " + package.PackagePath);
return ValidationProblem("No file found for path " + package.PackagePath);
var fileName = Path.GetFileName(package.PackagePath);
@@ -161,7 +163,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var cd = new System.Net.Mime.ContentDisposition
{
FileName = WebUtility.UrlEncode(fileName),
FileName = WebUtility.UrlEncode(fileName),
Inline = false // false = prompt the user for downloading; true = browser to try to show the file inline
};
Response.Headers.Add("Content-Disposition", cd.ToString());

View File

@@ -154,7 +154,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
catch (Exception ex)
{
_logger.LogError(ex, "Error creating relation type with {Name}", relationType.Name);
return ValidationErrorResult.CreateNotificationValidationErrorResult("Error creating relation type.");
return ValidationProblem("Error creating relation type.");
}
}
@@ -169,7 +169,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (relationTypePersisted == null)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Relation type does not exist");
return ValidationProblem("Relation type does not exist");
}
_umbracoMapper.Map(relationType, relationTypePersisted);
@@ -185,7 +185,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
catch (Exception ex)
{
_logger.LogError(ex, "Error saving relation type with {Id}", relationType.Id);
return ValidationErrorResult.CreateNotificationValidationErrorResult("Something went wrong when saving the relation type");
return ValidationProblem("Something went wrong when saving the relation type");
}
}

View File

@@ -47,28 +47,28 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
private IEnumerable<OperatorTerm> Terms => new List<OperatorTerm>
{
new OperatorTerm(_localizedTextService.Localize("template/is"), Operator.Equals, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template/isNot"), Operator.NotEquals, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template/before"), Operator.LessThan, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template/beforeIncDate"), Operator.LessThanEqualTo, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template/after"), Operator.GreaterThan, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template/afterIncDate"), Operator.GreaterThanEqualTo, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template/equals"), Operator.Equals, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template/doesNotEqual"), Operator.NotEquals, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template/contains"), Operator.Contains, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template/doesNotContain"), Operator.NotContains, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template/greaterThan"), Operator.GreaterThan, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template/greaterThanEqual"), Operator.GreaterThanEqualTo, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template/lessThan"), Operator.LessThan, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template/lessThanEqual"), Operator.LessThanEqualTo, new [] {"int"})
new OperatorTerm(_localizedTextService.Localize("template","is"), Operator.Equals, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template","isNot"), Operator.NotEquals, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template","before"), Operator.LessThan, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template","beforeIncDate"), Operator.LessThanEqualTo, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template","after"), Operator.GreaterThan, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template","afterIncDate"), Operator.GreaterThanEqualTo, new [] {"datetime"}),
new OperatorTerm(_localizedTextService.Localize("template","equals"), Operator.Equals, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","doesNotEqual"), Operator.NotEquals, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","contains"), Operator.Contains, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template","doesNotContain"), Operator.NotContains, new [] {"string"}),
new OperatorTerm(_localizedTextService.Localize("template","greaterThan"), Operator.GreaterThan, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","greaterThanEqual"), Operator.GreaterThanEqualTo, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","lessThan"), Operator.LessThan, new [] {"int"}),
new OperatorTerm(_localizedTextService.Localize("template","lessThanEqual"), Operator.LessThanEqualTo, new [] {"int"})
};
private IEnumerable<PropertyModel> Properties => new List<PropertyModel>
{
new PropertyModel { Name = _localizedTextService.Localize("template/id"), Alias = "Id", Type = "int" },
new PropertyModel { Name = _localizedTextService.Localize("template/name"), Alias = "Name", Type = "string" },
new PropertyModel { Name = _localizedTextService.Localize("template/createdDate"), Alias = "CreateDate", Type = "datetime" },
new PropertyModel { Name = _localizedTextService.Localize("template/lastUpdatedDate"), Alias = "UpdateDate", Type = "datetime" }
new PropertyModel { Name = _localizedTextService.Localize("template","id"), Alias = "Id", Type = "int" },
new PropertyModel { Name = _localizedTextService.Localize("template","name"), Alias = "Name", Type = "string" },
new PropertyModel { Name = _localizedTextService.Localize("template","createdDate"), Alias = "CreateDate", Type = "datetime" },
new PropertyModel { Name = _localizedTextService.Localize("template","lastUpdatedDate"), Alias = "UpdateDate", Type = "datetime" }
};
public QueryResultModel PostTemplateQuery(QueryModel model)
@@ -232,10 +232,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
public IEnumerable<ContentTypeModel> GetContentTypes()
{
var contentTypes = _contentTypeService.GetAll()
.Select(x => new ContentTypeModel { Alias = x.Alias, Name = _localizedTextService.Localize("template/contentOfType", tokens: new string[] { x.Name }) })
.Select(x => new ContentTypeModel { Alias = x.Alias, Name = _localizedTextService.Localize("template", "contentOfType", tokens: new string[] { x.Name }) })
.OrderBy(x => x.Name).ToList();
contentTypes.Insert(0, new ContentTypeModel { Alias = string.Empty, Name = _localizedTextService.Localize("template/allContent") });
contentTypes.Insert(0, new ContentTypeModel { Alias = string.Empty, Name = _localizedTextService.Localize("template", "allContent") });
return contentTypes;
}

View File

@@ -1,10 +1,17 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Filters;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
@@ -25,6 +32,91 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[MiddlewareFilter(typeof(UnhandledExceptionLoggerFilter))]
public abstract class UmbracoAuthorizedApiController : UmbracoApiController
{
/// <summary>
/// Returns a validation problem result for the <see cref="IErrorModel"/> and the <see cref="ModelStateDictionary"/>
/// </summary>
/// <param name="model"></param>
/// <param name="modelStateDictionary"></param>
/// <param name="statusCode"></param>
/// <returns></returns>
protected virtual ActionResult ValidationProblem(IErrorModel model, ModelStateDictionary modelStateDictionary, int statusCode = StatusCodes.Status400BadRequest)
{
model.Errors = modelStateDictionary.ToErrorDictionary();
return ValidationProblem(model, statusCode);
}
/// <summary>
/// Overridden to return Umbraco compatible errors
/// </summary>
/// <param name="modelStateDictionary"></param>
/// <returns></returns>
[NonAction]
public override ActionResult ValidationProblem(ModelStateDictionary modelStateDictionary)
{
return new ValidationErrorResult(new SimpleValidationModel(modelStateDictionary.ToErrorDictionary()));
//ValidationProblemDetails problemDetails = GetValidationProblemDetails(modelStateDictionary: modelStateDictionary);
//return new ValidationErrorResult(problemDetails);
}
// creates validation problem details instance.
// borrowed from netcore: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ControllerBase.cs#L1970
protected ValidationProblemDetails GetValidationProblemDetails(
string detail = null,
string instance = null,
int? statusCode = null,
string title = null,
string type = null,
[ActionResultObjectValue] ModelStateDictionary modelStateDictionary = null)
{
modelStateDictionary ??= ModelState;
ValidationProblemDetails validationProblem;
if (ProblemDetailsFactory == null)
{
// ProblemDetailsFactory may be null in unit testing scenarios. Improvise to make this more testable.
validationProblem = new ValidationProblemDetails(modelStateDictionary)
{
Detail = detail,
Instance = instance,
Status = statusCode,
Title = title,
Type = type,
};
}
else
{
validationProblem = ProblemDetailsFactory?.CreateValidationProblemDetails(
HttpContext,
modelStateDictionary,
statusCode: statusCode,
title: title,
type: type,
detail: detail,
instance: instance);
}
return validationProblem;
}
/// <summary>
/// Returns an Umbraco compatible validation problem for the given error message
/// </summary>
/// <param name="errorMessage"></param>
/// <returns></returns>
protected virtual ActionResult ValidationProblem(string errorMessage)
{
ValidationProblemDetails problemDetails = GetValidationProblemDetails(errorMessage);
return new ValidationErrorResult(problemDetails);
}
/// <summary>
/// Returns an Umbraco compatible validation problem for the object result
/// </summary>
/// <param name="value"></param>
/// <param name="statusCode"></param>
/// <returns></returns>
protected virtual ActionResult ValidationProblem(object value, int statusCode = StatusCodes.Status400BadRequest)
=> new ValidationErrorResult(value, statusCode);
}
}

View File

@@ -10,7 +10,6 @@ using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.BackOffice.ActionResults;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
@@ -22,7 +21,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)]
[PrefixlessBodyModelValidator]
public class UserGroupsController : UmbracoAuthorizedJsonController
public class UserGroupsController : BackOfficeNotificationsController
{
private readonly IUserService _userService;
private readonly IContentService _contentService;
@@ -118,7 +117,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var display = _umbracoMapper.Map<UserGroupDisplay>(userGroupSave.PersistedUserGroup);
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/operationSavedHeader"), _localizedTextService.Localize("speechBubbles/editUserGroupSaved"));
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","operationSavedHeader"), _localizedTextService.Localize("speechBubbles","editUserGroupSaved"));
return display;
}
@@ -202,10 +201,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
_userService.DeleteUserGroup(userGroup);
}
if (userGroups.Length > 1)
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/deleteUserGroupsSuccess", new[] {userGroups.Length.ToString()}));
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/deleteUserGroupSuccess", new[] {userGroups[0].Name}));
{
return Ok(_localizedTextService.Localize("speechBubbles","deleteUserGroupsSuccess", new[] {userGroups.Length.ToString()}));
}
return Ok(_localizedTextService.Localize("speechBubbles","deleteUserGroupSuccess", new[] {userGroups[0].Name}));
}
}
}

View File

@@ -51,7 +51,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)]
[PrefixlessBodyModelValidator]
[IsCurrentUserModelFilter]
public class UsersController : UmbracoAuthorizedJsonController
public class UsersController : BackOfficeNotificationsController
{
private readonly MediaFileManager _mediaFileManager;
private readonly ContentSettings _contentSettings;
@@ -128,7 +128,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
var urls = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator);
if (urls == null)
return new ValidationErrorResult("Could not access Gravatar endpoint");
return ValidationProblem("Could not access Gravatar endpoint");
return urls;
}
@@ -345,7 +345,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
{
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
if (_securitySettings.UsernameIsEmail)
@@ -362,7 +362,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
{
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
//Perform authorization here to see if the current user can actually save this user with the info being requested
@@ -380,7 +380,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var created = await _userManager.CreateAsync(identityUser);
if (created.Succeeded == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult(created.Errors.ToErrorMessage());
return ValidationProblem(created.Errors.ToErrorMessage());
}
string resetPassword;
@@ -389,7 +389,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var result = await _userManager.AddPasswordAsync(identityUser, password);
if (result.Succeeded == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult(created.Errors.ToErrorMessage());
return ValidationProblem(created.Errors.ToErrorMessage());
}
resetPassword = password;
@@ -446,19 +446,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
{
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
if (!_emailSender.CanSendRequiredEmail())
{
return new ValidationErrorResult("No Email server is configured");
return ValidationProblem("No Email server is configured");
}
//Perform authorization here to see if the current user can actually save this user with the info being requested
var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups);
if (canSaveUser == false)
{
return new ValidationErrorResult(canSaveUser.Result, StatusCodes.Status401Unauthorized);
return ValidationProblem(canSaveUser.Result, StatusCodes.Status401Unauthorized);
}
if (user == null)
@@ -471,7 +471,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var created = await _userManager.CreateAsync(identityUser);
if (created.Succeeded == false)
{
return ValidationErrorResult.CreateNotificationValidationErrorResult(created.Errors.ToErrorMessage());
return ValidationProblem(created.Errors.ToErrorMessage());
}
//now re-look the user back up
@@ -491,7 +491,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//send the email
await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message);
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/resendInviteHeader"), _localizedTextService.Localize("speechBubbles/resendInviteSuccess", new[] { user.Name }));
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","resendInviteHeader"), _localizedTextService.Localize("speechBubbles","resendInviteSuccess", new[] { user.Name }));
return display;
}
@@ -513,7 +513,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
ModelState.AddModelError(
_securitySettings.UsernameIsEmail ? "Email" : "Username",
"A user with the username already exists");
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
return new ActionResult<IUser>(user);
@@ -543,10 +543,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var applicationUri = _hostingEnvironment.ApplicationMainUrl;
var inviteUri = new Uri(applicationUri, action);
var emailSubject = _localizedTextService.Localize("user/inviteEmailCopySubject",
var emailSubject = _localizedTextService.Localize("user","inviteEmailCopySubject",
//Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings));
var emailBody = _localizedTextService.Localize("user/inviteEmailCopyFormat",
var emailBody = _localizedTextService.Localize("user","inviteEmailCopyFormat",
//Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings),
new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail });
@@ -568,7 +568,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
{
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
var intId = userSave.Id.TryConvertTo<int>();
@@ -631,7 +631,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
if (hasErrors)
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
//merge the save data onto the user
var user = _umbracoMapper.Map(userSave, found);
@@ -645,11 +645,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var userHasChangedOwnLanguage =
user.Id == currentUser.Id && currentUser.Language != user.Language;
var textToLocalise = userHasChangedOwnLanguage ? "speechBubbles/operationSavedHeaderReloadUser" : "speechBubbles/operationSavedHeader";
var textToLocalise = userHasChangedOwnLanguage ? "operationSavedHeaderReloadUser" : "operationSavedHeader";
var culture = userHasChangedOwnLanguage
? CultureInfo.GetCultureInfo(user.Language)
: Thread.CurrentThread.CurrentUICulture;
display.AddSuccessNotification(_localizedTextService.Localize(textToLocalise, culture), _localizedTextService.Localize("speechBubbles/editUserSaved", culture));
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles", textToLocalise, culture), _localizedTextService.Localize("speechBubbles","editUserSaved", culture));
return display;
}
@@ -664,7 +664,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
{
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
Attempt<int> intId = changingPasswordModel.Id.TryConvertTo<int>();
@@ -684,12 +684,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
// if it's the current user, the current user cannot reset their own password without providing their old password
if (currentUser.Username == found.Username && string.IsNullOrEmpty(changingPasswordModel.OldPassword))
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("Password reset is not allowed without providing old password");
return ValidationProblem("Password reset is not allowed without providing old password");
}
if (!currentUser.IsAdmin() && found.IsAdmin())
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("The current user cannot change the password for the specified user");
return ValidationProblem("The current user cannot change the password for the specified user");
}
Attempt<PasswordChangedModel> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _userManager);
@@ -697,7 +697,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (passwordChangeResult.Success)
{
var result = new ModelWithNotifications<string>(passwordChangeResult.Result.ResetPassword);
result.AddSuccessNotification(_localizedTextService.Localize("general/success"), _localizedTextService.Localize("user/passwordChangedGeneric"));
result.AddSuccessNotification(_localizedTextService.Localize("general","success"), _localizedTextService.Localize("user","passwordChangedGeneric"));
return result;
}
@@ -706,7 +706,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
}
return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary()));
return ValidationProblem(ModelState);
}
@@ -720,7 +720,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var tryGetCurrentUserId = _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId();
if (tryGetCurrentUserId && userIds.Contains(tryGetCurrentUserId.Result))
{
return ValidationErrorResult.CreateNotificationValidationErrorResult("The current user cannot disable itself");
return ValidationProblem("The current user cannot disable itself");
}
var users = _userService.GetUsersById(userIds).ToArray();
@@ -733,12 +733,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (users.Length > 1)
{
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/disableUsersSuccess", new[] {userIds.Length.ToString()}));
return Ok(_localizedTextService.Localize("speechBubbles","disableUsersSuccess", new[] {userIds.Length.ToString()}));
}
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/disableUserSuccess", new[] { users[0].Name }));
return Ok(_localizedTextService.Localize("speechBubbles","disableUserSuccess", new[] { users[0].Name }));
}
/// <summary>
@@ -757,12 +755,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (users.Length > 1)
{
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/enableUsersSuccess", new[] { userIds.Length.ToString() }));
return Ok(
_localizedTextService.Localize("speechBubbles","enableUsersSuccess", new[] { userIds.Length.ToString() }));
}
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/enableUserSuccess", new[] { users[0].Name }));
return Ok(
_localizedTextService.Localize("speechBubbles","enableUserSuccess", new[] { users[0].Name }));
}
/// <summary>
@@ -787,19 +785,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var unlockResult = await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now.AddMinutes(-1));
if (unlockResult.Succeeded == false)
{
return new ValidationErrorResult(
return ValidationProblem(
$"Could not unlock for user {u} - error {unlockResult.Errors.ToErrorMessage()}");
}
if (userIds.Length == 1)
{
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/unlockUserSuccess", new[] {user.Name}));
return Ok(
_localizedTextService.Localize("speechBubbles","unlockUserSuccess", new[] {user.Name}));
}
}
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/unlockUsersSuccess", new[] {(userIds.Length - notFound.Count).ToString()}));
return Ok(
_localizedTextService.Localize("speechBubbles","unlockUsersSuccess", new[] {(userIds.Length - notFound.Count).ToString()}));
}
[Authorize(Policy = AuthorizationPolicies.AdminUserEditsRequireAdmin)]
@@ -816,8 +814,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
}
_userService.Save(users);
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/setUserGroupOnUsersSuccess"));
return Ok(
_localizedTextService.Localize("speechBubbles","setUserGroupOnUsersSuccess"));
}
/// <summary>
@@ -847,8 +845,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var userName = user.Name;
_userService.Delete(user, true);
return new UmbracoNotificationSuccessResponse(
_localizedTextService.Localize("speechBubbles/deleteUserSuccess", new[] { userName }));
return Ok(
_localizedTextService.Localize("speechBubbles","deleteUserSuccess", new[] { userName }));
}
public class PagedUserResult : PagedResult<UserBasic>

View File

@@ -6,18 +6,11 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Umbraco.Cms.Web.BackOffice.PropertyEditors.Validation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.Extensions
namespace Umbraco.Extensions
{
public static class ModelStateExtensions
{
/// <summary>
/// Checks if there are any model errors on any fields containing the prefix
/// </summary>
/// <param name="state"></param>
/// <param name="prefix"></param>
/// <returns></returns>
public static bool IsValid(this ModelStateDictionary state, string prefix) =>
state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any());
/// <summary>
/// Adds the <see cref="ValidationResult"/> to the model state with the appropriate keys for property errors
@@ -171,42 +164,5 @@ namespace Umbraco.Cms.Web.BackOffice.Extensions
}
}
public static IDictionary<string, object> ToErrorDictionary(this ModelStateDictionary modelState)
{
var modelStateError = new Dictionary<string, object>();
foreach (KeyValuePair<string, ModelStateEntry> keyModelStatePair in modelState)
{
var key = keyModelStatePair.Key;
ModelErrorCollection errors = keyModelStatePair.Value.Errors;
if (errors != null && errors.Count > 0)
{
modelStateError.Add(key, errors.Select(error => error.ErrorMessage));
}
}
return modelStateError;
}
/// <summary>
/// Serializes the ModelState to JSON for JavaScript to interrogate the errors
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
public static JsonResult ToJsonErrors(this ModelStateDictionary state) =>
new JsonResult(new
{
success = state.IsValid.ToString().ToLower(),
failureType = "ValidationError",
validationErrors = from e in state
where e.Value.Errors.Count > 0
select new
{
name = e.Key,
errors = e.Value.Errors.Select(x => x.ErrorMessage)
.Concat(
e.Value.Errors.Where(x => x.Exception != null).Select(x => x.Exception.Message))
}
});
}
}

Some files were not shown because too many files have changed in this diff Show More