Merge branch 'v8/contrib' into pr/5917

This commit is contained in:
emma burstow
2020-01-30 10:50:47 +00:00
1360 changed files with 60881 additions and 29173 deletions

View File

@@ -28,15 +28,5 @@ namespace Umbraco.Web.Actions
.WhereNotNull()
.ToList();
}
internal IReadOnlyList<IAction> FromEntityPermission(EntityPermission entityPermission)
{
var actions = this.ToArray(); // no worry: internally, it's already an array
return entityPermission.AssignedPermissions
.Where(x => x.Length == 1)
.SelectMany(x => actions.Where(y => y.Letter == x[0]))
.WhereNotNull()
.ToList();
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
@@ -51,7 +52,10 @@ namespace Umbraco.Web.Cache
foreach (var payload in payloads.Where(x => x.Id != default))
{
//By INT Id
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent>(payload.Id));
//By GUID Key
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent>(payload.Key));
_idkMap.ClearCache(payload.Id);
@@ -97,13 +101,7 @@ namespace Umbraco.Web.Cache
//if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache)
// ...
_publishedSnapshotService.Notify(payloads, out _, out var publishedChanged);
if (payloads.Any(x => x.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) || publishedChanged)
{
// when a public version changes
AppCaches.ClearPartialViewCache();
}
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
base.Refresh(payloads);
}
@@ -111,40 +109,54 @@ namespace Umbraco.Web.Cache
// these events should never trigger
// everything should be PAYLOAD/JSON
public override void RefreshAll()
{
throw new NotSupportedException();
}
public override void RefreshAll() => throw new NotSupportedException();
public override void Refresh(int id)
{
throw new NotSupportedException();
}
public override void Refresh(int id) => throw new NotSupportedException();
public override void Refresh(Guid id)
{
throw new NotSupportedException();
}
public override void Refresh(Guid id) => throw new NotSupportedException();
public override void Remove(int id)
{
throw new NotSupportedException();
}
public override void Remove(int id) => throw new NotSupportedException();
#endregion
#region Json
/// <summary>
/// Refreshes the publish snapshot service and if there are published changes ensures that partial view caches are refreshed too
/// </summary>
/// <param name="service"></param>
/// <param name="appCaches"></param>
/// <param name="payloads"></param>
internal static void NotifyPublishedSnapshotService(IPublishedSnapshotService service, AppCaches appCaches, JsonPayload[] payloads)
{
service.Notify(payloads, out _, out var publishedChanged);
if (payloads.Any(x => x.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) || publishedChanged)
{
// when a public version changes
appCaches.ClearPartialViewCache();
}
}
public class JsonPayload
{
[Obsolete("Use the constructor specifying a GUID instead, using this constructor will result in not refreshing all caches")]
public JsonPayload(int id, TreeChangeTypes changeTypes)
{
Id = id;
ChangeTypes = changeTypes;
}
public int Id { get; }
[JsonConstructor]
public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes)
{
Id = id;
Key = key;
ChangeTypes = changeTypes;
}
public int Id { get; }
public Guid? Key { get; }
public TreeChangeTypes ChangeTypes { get; }
}

View File

@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Web.PublishedCache;
@@ -86,11 +84,8 @@ namespace Umbraco.Web.Cache
// don't try to be clever - refresh all
MemberCacheRefresher.RefreshMemberTypes(AppCaches);
// we have to refresh models before we notify the published snapshot
// service of changes, else factories may try to rebuild models while
// we are using the database to load content into caches
_publishedModelFactory.WithSafeLiveFactory(() =>
// refresh the models and cache
_publishedModelFactory.WithSafeLiveFactoryReset(() =>
_publishedSnapshotService.Notify(payloads));
// now we can trigger the event

View File

@@ -62,11 +62,9 @@ namespace Umbraco.Web.Cache
TagsValueConverter.ClearCaches();
SliderValueConverter.ClearCaches();
// we have to refresh models before we notify the published snapshot
// service of changes, else factories may try to rebuild models while
// we are using the database to load content into caches
// refresh the models and cache
_publishedModelFactory.WithSafeLiveFactory(() =>
_publishedModelFactory.WithSafeLiveFactoryReset(() =>
_publishedSnapshotService.Notify(payloads));
base.Refresh(payloads);

View File

@@ -106,7 +106,7 @@ namespace Umbraco.Web.Cache
public static void RefreshAllContentCache(this DistributedCache dc)
{
var payloads = new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) };
var payloads = new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
// note: refresh all content cache does refresh content types too
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
@@ -117,7 +117,7 @@ namespace Umbraco.Web.Cache
if (changes.Length == 0) return;
var payloads = changes
.Select(x => new ContentCacheRefresher.JsonPayload(x.Item.Id, x.ChangeTypes));
.Select(x => new ContentCacheRefresher.JsonPayload(x.Item.Id, x.Item.Key, x.ChangeTypes));
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
}
@@ -157,7 +157,7 @@ namespace Umbraco.Web.Cache
public static void RefreshAllMediaCache(this DistributedCache dc)
{
var payloads = new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) };
var payloads = new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
// note: refresh all media cache does refresh content types too
dc.RefreshByPayload(MediaCacheRefresher.UniqueId, payloads);
@@ -168,7 +168,7 @@ namespace Umbraco.Web.Cache
if (changes.Length == 0) return;
var payloads = changes
.Select(x => new MediaCacheRefresher.JsonPayload(x.Item.Id, x.ChangeTypes));
.Select(x => new MediaCacheRefresher.JsonPayload(x.Item.Id, x.Item.Key, x.ChangeTypes));
dc.RefreshByPayload(MediaCacheRefresher.UniqueId, payloads);
}
@@ -266,13 +266,21 @@ namespace Umbraco.Web.Cache
public static void RefreshLanguageCache(this DistributedCache dc, ILanguage language)
{
if (language == null) return;
dc.Refresh(LanguageCacheRefresher.UniqueId, language.Id);
var payload = new LanguageCacheRefresher.JsonPayload(language.Id, language.IsoCode,
language.WasPropertyDirty(nameof(ILanguage.IsoCode))
? LanguageCacheRefresher.JsonPayload.LanguageChangeType.ChangeCulture
: LanguageCacheRefresher.JsonPayload.LanguageChangeType.Update);
dc.RefreshByPayload(LanguageCacheRefresher.UniqueId, new[] { payload });
}
public static void RemoveLanguageCache(this DistributedCache dc, ILanguage language)
{
if (language == null) return;
dc.Remove(LanguageCacheRefresher.UniqueId, language.Id);
var payload = new LanguageCacheRefresher.JsonPayload(language.Id, language.IsoCode, LanguageCacheRefresher.JsonPayload.LanguageChangeType.Remove);
dc.RefreshByPayload(LanguageCacheRefresher.UniqueId, new[] { payload });
}
#endregion

View File

@@ -47,25 +47,13 @@ namespace Umbraco.Web.Cache
// these events should never trigger
// everything should be PAYLOAD/JSON
public override void RefreshAll()
{
throw new NotSupportedException();
}
public override void RefreshAll() => throw new NotSupportedException();
public override void Refresh(int id)
{
throw new NotSupportedException();
}
public override void Refresh(int id) => throw new NotSupportedException();
public override void Refresh(Guid id)
{
throw new NotSupportedException();
}
public override void Refresh(Guid id) => throw new NotSupportedException();
public override void Remove(int id)
{
throw new NotSupportedException();
}
public override void Remove(int id) => throw new NotSupportedException();
#endregion

View File

@@ -5,16 +5,18 @@ using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Web.PublishedCache;
using static Umbraco.Web.Cache.LanguageCacheRefresher.JsonPayload;
namespace Umbraco.Web.Cache
{
public sealed class LanguageCacheRefresher : CacheRefresherBase<LanguageCacheRefresher>
public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase<LanguageCacheRefresher, LanguageCacheRefresher.JsonPayload>
//CacheRefresherBase<LanguageCacheRefresher>
{
public LanguageCacheRefresher(AppCaches appCaches, IPublishedSnapshotService publishedSnapshotService, IDomainService domainService)
public LanguageCacheRefresher(AppCaches appCaches, IPublishedSnapshotService publishedSnapshotService)
: base(appCaches)
{
_publishedSnapshotService = publishedSnapshotService;
_domainService = domainService;
}
#region Define
@@ -23,7 +25,6 @@ namespace Umbraco.Web.Cache
public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654");
private readonly IPublishedSnapshotService _publishedSnapshotService;
private readonly IDomainService _domainService;
public override Guid RefresherUniqueId => UniqueId;
@@ -33,41 +34,118 @@ namespace Umbraco.Web.Cache
#region Refresher
public override void Refresh(int id)
public override void Refresh(JsonPayload[] payloads)
{
if (payloads.Length == 0) return;
var clearDictionary = false;
var clearContent = false;
//clear all no matter what type of payload
ClearAllIsolatedCacheByEntityType<ILanguage>();
RefreshDomains(id);
base.Refresh(id);
foreach (var payload in payloads)
{
switch (payload.ChangeType)
{
case LanguageChangeType.Update:
clearDictionary = true;
break;
case LanguageChangeType.Remove:
case LanguageChangeType.ChangeCulture:
clearDictionary = true;
clearContent = true;
break;
}
}
if (clearDictionary)
{
ClearAllIsolatedCacheByEntityType<IDictionaryItem>();
}
//if this flag is set, we will tell the published snapshot service to refresh ALL content and evict ALL IContent items
if (clearContent)
{
//clear all domain caches
RefreshDomains();
ContentCacheRefresher.RefreshContentTypes(AppCaches); // we need to evict all IContent items
//now refresh all nucache
var clearContentPayload = new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, clearContentPayload);
}
// then trigger event
base.Refresh(payloads);
}
public override void Remove(int id)
{
ClearAllIsolatedCacheByEntityType<ILanguage>();
//if a language is removed, then all dictionary cache needs to be removed
ClearAllIsolatedCacheByEntityType<IDictionaryItem>();
RefreshDomains(id);
base.Remove(id);
}
// these events should never trigger
// everything should be PAYLOAD/JSON
public override void RefreshAll() => throw new NotSupportedException();
public override void Refresh(int id) => throw new NotSupportedException();
public override void Refresh(Guid id) => throw new NotSupportedException();
public override void Remove(int id) => throw new NotSupportedException();
#endregion
private void RefreshDomains(int langId)
/// <summary>
/// Clears all domain caches
/// </summary>
private void RefreshDomains()
{
var assignedDomains = _domainService.GetAll(true).Where(x => x.LanguageId.HasValue && x.LanguageId.Value == langId).ToList();
ClearAllIsolatedCacheByEntityType<IDomain>();
if (assignedDomains.Count > 0)
// note: must do what's above FIRST else the repositories still have the old cached
// content and when the PublishedCachesService is notified of changes it does not see
// the new content...
var payloads = new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) };
_publishedSnapshotService.Notify(payloads);
}
#region Json
public class JsonPayload
{
public JsonPayload(int id, string isoCode, LanguageChangeType changeType)
{
// TODO: this is duplicating the logic in DomainCacheRefresher BUT we cannot inject that into this because it it not registered explicitly in the container,
// and we cannot inject the CacheRefresherCollection since that would be a circular reference, so what is the best way to call directly in to the
// DomainCacheRefresher?
Id = id;
IsoCode = isoCode;
ChangeType = changeType;
}
ClearAllIsolatedCacheByEntityType<IDomain>();
// note: must do what's above FIRST else the repositories still have the old cached
// content and when the PublishedCachesService is notified of changes it does not see
// the new content...
// notify
_publishedSnapshotService.Notify(assignedDomains.Select(x => new DomainCacheRefresher.JsonPayload(x.Id, DomainChangeTypes.Remove)).ToArray());
public int Id { get; }
public string IsoCode { get; }
public LanguageChangeType ChangeType { get; }
public enum LanguageChangeType
{
/// <summary>
/// A new languages has been added
/// </summary>
Add = 0,
/// <summary>
/// A language has been deleted
/// </summary>
Remove = 1,
/// <summary>
/// A language has been updated - but it's culture remains the same
/// </summary>
Update = 2,
/// <summary>
/// A language has been updated - it's culture has changed
/// </summary>
ChangeCulture = 3
}
}
#endregion
}
}

View File

@@ -99,6 +99,7 @@ namespace Umbraco.Web.Cache
return new[]
{
CacheKeys.MacroContentCacheKey, // macro render cache
CacheKeys.MacroFromAliasCacheKey, // lookup macro by alias
};
}

View File

@@ -62,6 +62,7 @@ namespace Umbraco.Web.Cache
// it *was* done for each pathId but really that does not make sense
// only need to do it for the current media
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia>(payload.Id));
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia>(payload.Key));
// remove those that are in the branch
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove))
@@ -104,14 +105,15 @@ namespace Umbraco.Web.Cache
public class JsonPayload
{
public JsonPayload(int id, TreeChangeTypes changeTypes)
public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes)
{
Id = id;
Key = key;
ChangeTypes = changeTypes;
}
public int Id { get; }
public Guid? Key { get; }
public TreeChangeTypes ChangeTypes { get; }
}

View File

@@ -1,6 +1,7 @@
using System;
using Umbraco.Core.Cache;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Services;
namespace Umbraco.Web.Cache
@@ -8,11 +9,13 @@ namespace Umbraco.Web.Cache
public sealed class TemplateCacheRefresher : CacheRefresherBase<TemplateCacheRefresher>
{
private readonly IdkMap _idkMap;
private readonly IContentTypeCommonRepository _contentTypeCommonRepository;
public TemplateCacheRefresher(AppCaches appCaches, IdkMap idkMap)
public TemplateCacheRefresher(AppCaches appCaches, IdkMap idkMap, IContentTypeCommonRepository contentTypeCommonRepository)
: base(appCaches)
{
_idkMap = idkMap;
_contentTypeCommonRepository = contentTypeCommonRepository;
}
#region Define
@@ -45,6 +48,7 @@ namespace Umbraco.Web.Cache
// it has an associated template.
ClearAllIsolatedCacheByEntityType<IContent>();
ClearAllIsolatedCacheByEntityType<IContentType>();
_contentTypeCommonRepository.ClearCache();
base.Remove(id);
}

View File

@@ -1,12 +1,8 @@
using System;
using System.Threading;
using Umbraco.Core;
using Umbraco.Core.Compose;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Core.Sync;
@@ -58,8 +54,8 @@ namespace Umbraco.Web.Compose
// note: refresh all content & media caches does refresh content types too
var svc = Current.PublishedSnapshotService;
svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) });
svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _);
svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _);
svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _);
svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _);
},
//rebuild indexes if the server is not synced

View File

@@ -204,14 +204,15 @@ namespace Umbraco.Web.Compose
private void SendNotification(IUser sender, IEnumerable<IContent> entities, IAction action, Uri siteUri)
{
if (sender == null) throw new ArgumentNullException(nameof(sender));
if (siteUri == null) throw new ArgumentNullException(nameof(siteUri));
if (siteUri == null)
{
_logger.Warn(typeof(Notifier), "Notifications can not be sent, no site url is set (might be during boot process?)");
return;
}
//group by the content type variation since the emails will be different
foreach(var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations))
{
if (contentVariantGroup.Key == ContentVariation.CultureAndSegment || contentVariantGroup.Key == ContentVariation.Segment)
throw new NotSupportedException("Segments are not yet supported in Umbraco");
_notificationService.SendNotifications(
sender,
contentVariantGroup,

View File

@@ -182,6 +182,8 @@ namespace Umbraco.Web.Composing
public static DataEditorCollection DataEditors => CoreCurrent.DataEditors;
public static DataValueReferenceFactoryCollection DataValueReferenceFactories => CoreCurrent.DataValueReferenceFactories;
public static PropertyEditorCollection PropertyEditors => CoreCurrent.PropertyEditors;
public static ParameterEditorCollection ParameterEditors => CoreCurrent.ParameterEditors;

View File

@@ -14,6 +14,7 @@ namespace Umbraco.Web.ContentApps
private ContentApp _contentApp;
private ContentApp _mediaApp;
private ContentApp _memberApp;
public ContentApp GetContentAppFor(object o, IEnumerable<IReadOnlyUserGroup> userGroups)
{
@@ -45,6 +46,16 @@ namespace Umbraco.Web.ContentApps
case IMedia _:
return null;
case IMember _:
return _memberApp ?? (_memberApp = new ContentApp
{
Alias = "umbContent",
Name = "Content",
Icon = Constants.Icons.Content,
View = "views/member/apps/content/content.html",
Weight = Weight
});
default:
throw new NotSupportedException($"Object type {o.GetType()} is not supported here.");
}

View File

@@ -13,6 +13,7 @@ namespace Umbraco.Web.ContentApps
private ContentApp _contentApp;
private ContentApp _mediaApp;
private ContentApp _memberApp;
public ContentApp GetContentAppFor(object o, IEnumerable<IReadOnlyUserGroup> userGroups)
{
@@ -37,6 +38,15 @@ namespace Umbraco.Web.ContentApps
View = "views/media/apps/info/info.html",
Weight = Weight
});
case IMember _:
return _memberApp ?? (_memberApp = new ContentApp
{
Alias = "umbInfo",
Name = "Info",
Icon = "icon-info",
View = "views/member/apps/info/info.html",
Weight = Weight
});
default:
throw new NotSupportedException($"Object type {o.GetType()} is not supported here.");

View File

@@ -45,6 +45,8 @@ namespace Umbraco.Web.ContentApps
entityType = "media";
dtdId = Core.Constants.DataTypes.DefaultMediaListView;
break;
case IMember member:
return null;
default:
throw new NotSupportedException($"Object type {o.GetType()} is not supported here.");
}
@@ -100,6 +102,15 @@ namespace Umbraco.Web.ContentApps
if (configTabName != null && String.IsNullOrWhiteSpace(configTabName.ToString()) == false)
contentApp.Name = configTabName.ToString();
}
//Override Icon if icon is provided
if (listViewConfig.ContainsKey("icon"))
{
var configIcon = listViewConfig["icon"];
if (configIcon != null && String.IsNullOrWhiteSpace(configIcon.ToString()) == false)
contentApp.Icon = configIcon.ToString();
}
// if the list view is configured to show umbContent first, update the list view content app weight accordingly
if(listViewConfig.ContainsKey("showContentFirst") &&
listViewConfig["showContentFirst"]?.ToString().TryConvertTo<bool>().Result == true)

View File

@@ -122,7 +122,20 @@ namespace Umbraco.Web.Dictionary
//ensure it's stored/retrieved from request cache
//NOTE: This is no longer necessary since these are cached at the runtime level, but we can leave it here for now.
return _requestCache.GetCacheItem<ILanguage>(typeof (DefaultCultureDictionary).Name + "Culture" + Culture.Name,
() => _localizationService.GetLanguageByIsoCode(Culture.Name));
() => {
// find a language that matches the current culture or any of its parent cultures
var culture = Culture;
while(culture != CultureInfo.InvariantCulture)
{
var language = _localizationService.GetLanguageByIsoCode(culture.Name);
if(language != null)
{
return language;
}
culture = culture.Parent;
}
return null;
});
}
}
}

View File

@@ -145,6 +145,7 @@ namespace Umbraco.Web.Editors
/// </summary>
/// <returns></returns>
[HttpGet]
[StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult> AuthorizeUpgrade()
{
return await RenderDefaultOrProcessExternalLoginAsync(
@@ -189,7 +190,7 @@ namespace Umbraco.Web.Editors
.ToDictionary(pv => pv.Key, pv =>
pv.ToDictionary(pve => pve.valueAlias, pve => pve.value));
return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.Indented };
return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.None };
}
/// <summary>
@@ -236,7 +237,7 @@ namespace Umbraco.Web.Editors
GetAssetList,
new TimeSpan(0, 2, 0));
return new JsonNetResult { Data = result, Formatting = Formatting.Indented };
return new JsonNetResult { Data = result, Formatting = Formatting.None };
}
[UmbracoAuthorize(Order = 0)]
@@ -244,7 +245,7 @@ namespace Umbraco.Web.Editors
public JsonNetResult GetGridConfig()
{
var gridConfig = Current.Configs.Grids();
return new JsonNetResult { Data = gridConfig.EditorsConfig.Editors, Formatting = Formatting.Indented };
return new JsonNetResult { Data = gridConfig.EditorsConfig.Editors, Formatting = Formatting.None };
}

View File

@@ -309,7 +309,11 @@ namespace Umbraco.Web.Editors
{
"webProfilingBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<WebProfilingController>(
controller => controller.GetStatus())
}
},
{
"tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TinyMceController>(
controller => controller.UploadImage())
},
}
},
{

View File

@@ -49,18 +49,15 @@ namespace Umbraco.Web.Editors.Binders
{
foreach (var variant in model.Variants)
{
if (variant.Culture.IsNullOrWhiteSpace())
{
//map the property dto collection (no culture is passed to the mapping context so it will be invariant)
variant.PropertyCollectionDto = Current.Mapper.Map<ContentPropertyCollectionDto>(model.PersistedContent);
}
else
{
//map the property dto collection with the culture of the current variant
variant.PropertyCollectionDto = Current.Mapper.Map<ContentPropertyCollectionDto>(
model.PersistedContent,
context => context.SetCulture(variant.Culture));
}
//map the property dto collection with the culture of the current variant
variant.PropertyCollectionDto = Current.Mapper.Map<ContentPropertyCollectionDto>(
model.PersistedContent,
context =>
{
// either of these may be null and that is ok, if it's invariant they will be null which is what is expected
context.SetCulture(variant.Culture);
context.SetSegment(variant.Segment);
});
//now map all of the saved values to the dto
_modelBinderHelper.MapPropertyValuesFromSaved(variant, variant.PropertyCollectionDto);
@@ -87,6 +84,5 @@ namespace Umbraco.Web.Editors.Binders
model.ParentId,
contentType);
}
}
}

View File

@@ -303,7 +303,7 @@ namespace Umbraco.Web.Editors
}
/// <summary>
/// Gets the content json for the content id
/// Gets the content json for the content guid
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
@@ -323,7 +323,7 @@ namespace Umbraco.Web.Editors
}
/// <summary>
/// Gets the content json for the content id
/// Gets the content json for the content udi
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
@@ -341,7 +341,7 @@ namespace Umbraco.Web.Editors
}
/// <summary>
/// Gets an empty content item for the
/// Gets an empty content item for the document type.
/// </summary>
/// <param name="contentTypeAlias"></param>
/// <param name="parentId"></param>
@@ -1679,9 +1679,9 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(response);
}
var permission = Services.UserService.GetPermissions(Security.CurrentUser, node.Path);
var assignedPermissions = Services.UserService.GetAssignedPermissions(Security.CurrentUser, node.Id);
if (permission.AssignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) == false)
if (assignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) == false)
{
var response = Request.CreateResponse(HttpStatusCode.BadRequest);
response.Content = new StringContent("You do not have permission to assign domains on that node.");
@@ -1836,8 +1836,13 @@ namespace Umbraco.Web.Editors
/// <param name="contentSave"></param>
private void MapValuesForPersistence(ContentItemSave contentSave)
{
// inline method to determine if a property type varies
bool Varies(Property property) => property.PropertyType.VariesByCulture();
// inline method to determine the culture and segment to persist the property
(string culture, string segment) PropertyCultureAndSegment(Property property, ContentVariantSave variant)
{
var culture = property.PropertyType.VariesByCulture() ? variant.Culture : null;
var segment = property.PropertyType.VariesBySegment() ? variant.Segment : null;
return (culture, segment);
}
var variantIndex = 0;
@@ -1876,8 +1881,18 @@ namespace Umbraco.Web.Editors
MapPropertyValuesForPersistence<IContent, ContentItemSave>(
contentSave,
propertyCollection,
(save, property) => Varies(property) ? property.GetValue(variant.Culture) : property.GetValue(), //get prop val
(save, property, v) => { if (Varies(property)) property.SetValue(v, variant.Culture); else property.SetValue(v); }, //set prop val
(save, property) =>
{
// Get property value
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
return property.GetValue(culture, segment);
},
(save, property, v) =>
{
// Set property value
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
property.SetValue(v, culture, segment);
},
variant.Culture);
variantIndex++;
@@ -1926,10 +1941,8 @@ namespace Umbraco.Web.Editors
}
if (model.ParentId < 0)
{
//cannot move if the content item is not allowed at the root unless there are
//none allowed at root (in which case all should be allowed at root)
var contentTypeService = Services.ContentTypeService;
if (toMove.ContentType.AllowedAsRoot == false && contentTypeService.GetAll().Any(ct => ct.AllowedAsRoot))
//cannot move if the content item is not allowed at the root
if (toMove.ContentType.AllowedAsRoot == false)
{
throw new HttpResponseException(
Request.CreateNotificationValidationErrorResponse(
@@ -2297,7 +2310,7 @@ namespace Umbraco.Web.Editors
}
var entry = Services.PublicAccessService.GetEntryForContent(content);
if (entry == null)
if (entry == null || entry.ProtectedNodeId != content.Id)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
@@ -2379,7 +2392,7 @@ namespace Umbraco.Web.Editors
var entry = Services.PublicAccessService.GetEntryForContent(content);
if (entry == null)
if (entry == null || entry.ProtectedNodeId != content.Id)
{
entry = new PublicAccessEntry(content, loginPage, errorPage, new List<PublicAccessRule>());

View File

@@ -17,9 +17,11 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Packaging;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
@@ -46,6 +48,7 @@ namespace Umbraco.Web.Editors
{
private readonly IEntityXmlSerializer _serializer;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IScopeProvider _scopeProvider;
public ContentTypeController(IEntityXmlSerializer serializer,
ICultureDictionaryFactory cultureDictionaryFactory,
@@ -53,11 +56,13 @@ namespace Umbraco.Web.Editors
IUmbracoContextAccessor umbracoContextAccessor,
ISqlContext sqlContext, PropertyEditorCollection propertyEditors,
ServiceContext services, AppCaches appCaches,
IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper,
IScopeProvider scopeProvider)
: base(cultureDictionaryFactory, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper)
{
_serializer = serializer;
_propertyEditors = propertyEditors;
_scopeProvider = scopeProvider;
}
public int GetCount()
@@ -65,6 +70,13 @@ namespace Umbraco.Web.Editors
return Services.ContentTypeService.Count();
}
[HttpGet]
[UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)]
public bool HasContentNodes(int id)
{
return Services.ContentTypeService.HasContentNodes(id);
}
public DocumentTypeDisplay GetById(int id)
{
var ct = Services.ContentTypeService.Get(id);
@@ -132,7 +144,7 @@ namespace Umbraco.Web.Editors
[HttpPost]
public HttpResponseMessage GetAvailableCompositeContentTypes(GetAvailableCompositionsFilter filter)
{
var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType, filter.FilterContentTypes, filter.FilterPropertyTypes)
var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType, filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement)
.Select(x => new
{
contentType = x.Item1,
@@ -409,11 +421,7 @@ namespace Umbraco.Web.Editors
IEnumerable<IContentType> types;
if (contentId == Constants.System.Root)
{
var allContentTypes = Services.ContentTypeService.GetAll().ToList();
bool AllowedAsRoot(IContentType x) => x.AllowedAsRoot;
types = allContentTypes.Any(AllowedAsRoot)
? allContentTypes.Where(AllowedAsRoot).ToList()
: allContentTypes;
types = Services.ContentTypeService.GetAll().Where(x => x.AllowedAsRoot).ToList();
}
else
{
@@ -424,11 +432,11 @@ namespace Umbraco.Web.Editors
}
var contentType = Services.ContentTypeBaseServices.GetContentTypeOf(contentItem);
var ids = contentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray();
var ids = contentType.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value).ToArray();
if (ids.Any() == false) return Enumerable.Empty<ContentTypeBasic>();
types = Services.ContentTypeService.GetAll(ids).ToList();
types = Services.ContentTypeService.GetAll(ids).OrderBy(c => ids.IndexOf(c.Id)).ToList();
}
var basics = types.Where(type => type.IsElement == false).Select(Mapper.Map<IContentType, ContentTypeBasic>).ToList();
@@ -451,7 +459,7 @@ namespace Umbraco.Web.Editors
}
}
return basics;
return basics.OrderBy(c => contentId == Constants.System.Root ? c.Name : string.Empty);
}
/// <summary>
@@ -520,7 +528,7 @@ namespace Umbraco.Web.Editors
}
var dataInstaller = new PackageDataInstallation(Logger, Services.FileService, Services.MacroService, Services.LocalizationService,
Services.DataTypeService, Services.EntityService, Services.ContentTypeService, Services.ContentService, _propertyEditors);
Services.DataTypeService, Services.EntityService, Services.ContentTypeService, Services.ContentService, _propertyEditors, _scopeProvider);
var xd = new XmlDocument {XmlResolver = null};
xd.Load(filePath);

View File

@@ -53,11 +53,13 @@ namespace Umbraco.Web.Editors
/// be looked up via the db, they need to be passed in.
/// </param>
/// <param name="contentTypeId"></param>
/// <param name="isElement">Wether the composite content types should be applicable for an element type</param>
/// <returns></returns>
protected IEnumerable<Tuple<EntityBasic, bool>> PerformGetAvailableCompositeContentTypes(int contentTypeId,
UmbracoObjectTypes type,
string[] filterContentTypes,
string[] filterPropertyTypes)
string[] filterPropertyTypes,
bool isElement)
{
IContentTypeComposition source = null;
@@ -98,7 +100,7 @@ namespace Umbraco.Web.Editors
throw new ArgumentOutOfRangeException("The entity type was not a content type");
}
var availableCompositions = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes);
var availableCompositions = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes, isElement);

View File

@@ -140,7 +140,7 @@ namespace Umbraco.Web.Editors
break;
case "OUR":
urlPrefix = "https://our.umbraco.org/";
urlPrefix = "https://our.umbraco.com/";
break;
case "COM":

View File

@@ -20,6 +20,7 @@ using Umbraco.Web.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using System.Web.Http.Controllers;
namespace Umbraco.Web.Editors
{
@@ -34,6 +35,7 @@ namespace Umbraco.Web.Editors
[PluginController("UmbracoApi")]
[UmbracoTreeAuthorize(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)]
[EnableOverrideAuthorization]
[DataTypeControllerConfiguration]
public class DataTypeController : BackOfficeNotificationsController
{
private readonly PropertyEditorCollection _propertyEditors;
@@ -44,6 +46,19 @@ namespace Umbraco.Web.Editors
_propertyEditors = propertyEditors;
}
/// <summary>
/// Configures this controller with a custom action selector
/// </summary>
private class DataTypeControllerConfigurationAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector(
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi))
));
}
}
/// <summary>
/// Gets data type by name
/// </summary>
@@ -70,6 +85,40 @@ namespace Umbraco.Web.Editors
return Mapper.Map<IDataType, DataTypeDisplay>(dataType);
}
/// <summary>
/// Gets the datatype json for the datatype guid
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public DataTypeDisplay GetById(Guid id)
{
var dataType = Services.DataTypeService.GetDataType(id);
if (dataType == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Mapper.Map<IDataType, DataTypeDisplay>(dataType);
}
/// <summary>
/// Gets the datatype json for the datatype udi
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public DataTypeDisplay GetById(Udi id)
{
var guidUdi = id as GuidUdi;
if (guidUdi == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
var dataType = Services.DataTypeService.GetDataType(guidUdi.Guid);
if (dataType == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Mapper.Map<IDataType, DataTypeDisplay>(dataType);
}
/// <summary>
/// Deletes a data type with a given ID
/// </summary>
@@ -283,6 +332,60 @@ namespace Umbraco.Web.Editors
: Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
}
/// <summary>
/// Returns the references (usages) for the data type
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public DataTypeReferences GetReferences(int id)
{
var result = new DataTypeReferences();
var usages = Services.DataTypeService.GetReferences(id);
foreach(var groupOfEntityType in usages.GroupBy(x => x.Key.EntityType))
{
//get all the GUIDs for the content types to find
var guidsAndPropertyAliases = groupOfEntityType.ToDictionary(i => ((GuidUdi)i.Key).Guid, i => i.Value);
if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.DocumentType))
result.DocumentTypes = GetContentTypeUsages(Services.ContentTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases);
else if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.MediaType))
result.MediaTypes = GetContentTypeUsages(Services.MediaTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases);
else if (groupOfEntityType.Key == ObjectTypes.GetUdiType(UmbracoObjectTypes.MemberType))
result.MemberTypes = GetContentTypeUsages(Services.MemberTypeService.GetAll(guidsAndPropertyAliases.Keys), guidsAndPropertyAliases);
}
return result;
}
/// <summary>
/// Maps the found content types and usages to the resulting model
/// </summary>
/// <param name="cts"></param>
/// <param name="usages"></param>
/// <returns></returns>
private IEnumerable<DataTypeReferences.ContentTypeReferences> GetContentTypeUsages(
IEnumerable<IContentTypeBase> cts,
IReadOnlyDictionary<Guid, IEnumerable<string>> usages)
{
return cts.Select(x => new DataTypeReferences.ContentTypeReferences
{
Id = x.Id,
Key = x.Key,
Alias = x.Alias,
Icon = x.Icon,
Name = x.Name,
Udi = new GuidUdi(ObjectTypes.GetUdiType(UmbracoObjectTypes.DocumentType), x.Key),
//only select matching properties
Properties = x.PropertyTypes.Where(p => usages[x.Key].InvariantContains(p.Alias))
.Select(p => new DataTypeReferences.ContentTypeReferences.PropertyTypeReferences
{
Alias = p.Alias,
Name = p.Name
})
});
}
#region ReadOnly actions to return basic data - allow access for: content ,media, members, settings, developer
/// <summary>
/// Gets the content json for all data types

View File

@@ -6,7 +6,7 @@ using Umbraco.Core.Composing;
namespace Umbraco.Web.Editors
{
internal class EditorValidatorCollection : BuilderCollectionBase<IEditorValidator>
public class EditorValidatorCollection : BuilderCollectionBase<IEditorValidator>
{
public EditorValidatorCollection(IEnumerable<IEditorValidator> items)
: base(items)

View File

@@ -2,7 +2,7 @@
namespace Umbraco.Web.Editors
{
internal class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase<EditorValidatorCollectionBuilder, EditorValidatorCollection, IEditorValidator>
public class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase<EditorValidatorCollectionBuilder, EditorValidatorCollection, IEditorValidator>
{
protected override EditorValidatorCollectionBuilder This => this;
}

View File

@@ -206,22 +206,54 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
}
/// <summary>
/// Gets the url of an entity
/// </summary>
/// <param name="udi">UDI of the entity to fetch URL for</param>
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>The URL or path to the item</returns>
public HttpResponseMessage GetUrl(Udi udi, string culture = "*")
{
var intId = Services.EntityService.GetId(udi);
if (!intId.Success)
throw new HttpResponseException(HttpStatusCode.NotFound);
UmbracoEntityTypes entityType;
switch(udi.EntityType)
{
case Constants.UdiEntityType.Document:
entityType = UmbracoEntityTypes.Document;
break;
case Constants.UdiEntityType.Media:
entityType = UmbracoEntityTypes.Media;
break;
case Constants.UdiEntityType.Member:
entityType = UmbracoEntityTypes.Member;
break;
default:
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return GetUrl(intId.Result, entityType, culture);
}
/// <summary>
/// Gets the url of an entity
/// </summary>
/// <param name="id">Int id of the entity to fetch URL for</param>
/// <param name="type">The type of entity such as Document, Media, Member</param>
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>The URL or path to the item</returns>
/// <remarks>
/// We are not restricting this with security because there is no sensitive data
/// </remarks>
public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type)
public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type, string culture = null)
{
culture = culture ?? ClientCulture();
var returnUrl = string.Empty;
if (type == UmbracoEntityTypes.Document)
{
var foundUrl = UmbracoContext.Url(id);
var foundUrl = UmbracoContext.Url(id, culture);
if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#")
{
returnUrl = foundUrl;
@@ -300,7 +332,9 @@ namespace Umbraco.Web.Editors
[HttpGet]
public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*")
{
var url = UmbracoContext.UrlProvider.GetUrl(id);
culture = culture ?? ClientCulture();
var url = UmbracoContext.UrlProvider.GetUrl(id, culture: culture);
var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id, culture);
return new UrlAndAnchors(url, anchorValues);
}
@@ -659,6 +693,9 @@ namespace Umbraco.Web.Editors
if (pageSize <= 0)
throw new HttpResponseException(HttpStatusCode.NotFound);
// re-normalize since NULL can be passed in
filter = filter ?? string.Empty;
var objectType = ConvertToObjectType(type);
if (objectType.HasValue)
{

View File

@@ -177,8 +177,12 @@ namespace Umbraco.Web.Editors
var indexName = index.Name;
if (!(index is IIndexDiagnostics indexDiag))
indexDiag = new GenericIndexDiagnostics(index);
{
if (index is LuceneIndex luceneIndex)
indexDiag = new LuceneIndexDiagnostics(luceneIndex, Logger);
else
indexDiag = new GenericIndexDiagnostics(index);
}
var isHealth = indexDiag.IsHealthy();
var properties = new Dictionary<string, object>

View File

@@ -5,10 +5,10 @@ using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Editors.Filters
@@ -43,8 +43,11 @@ namespace Umbraco.Web.Editors.Filters
where TModelSave: IContentSave<TPersisted>
where TModelWithProperties : IContentProperties<ContentPropertyBasic>
{
protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
private readonly ILocalizedTextService _textService;
protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor)
{
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
}
/// <summary>
@@ -122,6 +125,18 @@ namespace Umbraco.Web.Editors.Filters
{
var properties = modelWithProperties.Properties.ToDictionary(x => x.Alias, x => x);
// Retrieve default messages used for required and regex validatation. We'll replace these
// if set with custom ones if they've been provided for a given property.
var requiredDefaultMessages = new[]
{
_textService.Localize("validation", "invalidNull"),
_textService.Localize("validation", "invalidEmpty")
};
var formatDefaultMessages = new[]
{
_textService.Localize("validation", "invalidPattern"),
};
foreach (var p in dto.Properties)
{
var editor = p.PropertyEditor;
@@ -141,7 +156,7 @@ namespace Umbraco.Web.Editors.Filters
var postedValue = postedProp.Value;
ValidatePropertyValue(model, modelWithProperties, editor, p, postedValue, modelState);
ValidatePropertyValue(model, modelWithProperties, editor, p, postedValue, modelState, requiredDefaultMessages, formatDefaultMessages);
}
@@ -157,22 +172,34 @@ namespace Umbraco.Web.Editors.Filters
/// <param name="property"></param>
/// <param name="postedValue"></param>
/// <param name="modelState"></param>
/// <param name="requiredDefaultMessages"></param>
/// <param name="formatDefaultMessages"></param>
protected virtual void ValidatePropertyValue(
TModelSave model,
TModelWithProperties modelWithProperties,
IDataEditor editor,
ContentPropertyDto property,
object postedValue,
ModelStateDictionary modelState)
ModelStateDictionary modelState,
string[] requiredDefaultMessages,
string[] formatDefaultMessages)
{
// validate
var valueEditor = editor.GetValueEditor(property.DataType.Configuration);
foreach (var r in valueEditor.Validate(postedValue, property.IsRequired, property.ValidationRegExp))
{
// If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate().
if (property.IsRequired && !string.IsNullOrWhiteSpace(property.IsRequiredMessage) && requiredDefaultMessages.Contains(r.ErrorMessage, StringComparer.OrdinalIgnoreCase))
{
r.ErrorMessage = property.IsRequiredMessage;
}
if (!string.IsNullOrWhiteSpace(property.ValidationRegExp) && !string.IsNullOrWhiteSpace(property.ValidationRegExpMessage) && formatDefaultMessages.Contains(r.ErrorMessage, StringComparer.OrdinalIgnoreCase))
{
r.ErrorMessage = property.ValidationRegExpMessage;
}
modelState.AddPropertyError(r, property.Alias, property.Culture);
}
}
}
}

View File

@@ -1,8 +1,6 @@
using System.Web.Http.ModelBinding;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Editors.Filters
@@ -12,7 +10,7 @@ namespace Umbraco.Web.Editors.Filters
/// </summary>
internal class ContentSaveModelValidator : ContentModelValidator<IContent, ContentItemSave, ContentVariantSave>
{
public ContentSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
public ContentSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor, textService)
{
}
}

View File

@@ -23,28 +23,30 @@ namespace Umbraco.Web.Editors.Filters
/// </summary>
internal sealed class ContentSaveValidationAttribute : ActionFilterAttribute
{
public ContentSaveValidationAttribute(): this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService)
private readonly ILogger _logger;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ILocalizedTextService _textService;
private readonly IContentService _contentService;
private readonly IUserService _userService;
private readonly IEntityService _entityService;
public ContentSaveValidationAttribute(): this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService)
{ }
public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IContentService contentService, IUserService userService, IEntityService entityService)
public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IContentService contentService, IUserService userService, IEntityService entityService)
{
_logger = logger;
_umbracoContextAccessor = umbracoContextAccessor;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
}
private readonly ILogger _logger;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IContentService _contentService;
private readonly IUserService _userService;
private readonly IEntityService _entityService;
public override void OnActionExecuting(HttpActionContext actionContext)
{
var model = (ContentItemSave)actionContext.ActionArguments["contentItem"];
var contentItemValidator = new ContentSaveModelValidator(_logger, _umbracoContextAccessor);
var contentItemValidator = new ContentSaveModelValidator(_logger, _umbracoContextAccessor, _textService);
if (!ValidateAtLeastOneVariantIsBeingSaved(model, actionContext)) return;
if (!contentItemValidator.ValidateExistingContent(model, actionContext)) return;

View File

@@ -1,7 +1,6 @@
using System.Linq;
using System;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Umbraco.Core;
@@ -21,25 +20,27 @@ namespace Umbraco.Web.Editors.Filters
{
private readonly ILogger _logger;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ILocalizedTextService _textService;
private readonly IMediaService _mediaService;
private readonly IEntityService _entityService;
public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.MediaService, Current.Services.EntityService)
public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MediaService, Current.Services.EntityService)
{
}
public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IMediaService mediaService, IEntityService entityService)
public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMediaService mediaService, IEntityService entityService)
{
_logger = logger;
_umbracoContextAccessor = umbracoContextAccessor;
_mediaService = mediaService;
_entityService = entityService;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
_mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService));
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var model = (MediaItemSave)actionContext.ActionArguments["contentItem"];
var contentItemValidator = new MediaSaveModelValidator(_logger, _umbracoContextAccessor);
var contentItemValidator = new MediaSaveModelValidator(_logger, _umbracoContextAccessor, _textService);
if (ValidateUserAccess(model, actionContext))
{

View File

@@ -1,5 +1,6 @@
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Editors.Filters
@@ -9,8 +10,8 @@ namespace Umbraco.Web.Editors.Filters
/// </summary>
internal class MediaSaveModelValidator : ContentModelValidator<IMedia, MediaItemSave, IContentProperties<ContentPropertyBasic>>
{
public MediaSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
public MediaSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor, textService)
{
}
}
}
}

View File

@@ -21,10 +21,10 @@ namespace Umbraco.Web.Editors.Filters
{
private readonly IMemberTypeService _memberTypeService;
public MemberSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IMemberTypeService memberTypeService)
: base(logger, umbracoContextAccessor)
public MemberSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMemberTypeService memberTypeService)
: base(logger, umbracoContextAccessor, textService)
{
_memberTypeService = memberTypeService;
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
}
/// <summary>

View File

@@ -1,4 +1,5 @@
using System.Web.Http.Controllers;
using System;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Umbraco.Core.Logging;
using Umbraco.Core.Services;
@@ -14,23 +15,25 @@ namespace Umbraco.Web.Editors.Filters
{
private readonly ILogger _logger;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ILocalizedTextService _textService;
private readonly IMemberTypeService _memberTypeService;
public MemberSaveValidationAttribute()
: this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.MemberTypeService)
: this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MemberTypeService)
{ }
public MemberSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IMemberTypeService memberTypeService)
public MemberSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMemberTypeService memberTypeService)
{
_logger = logger;
_umbracoContextAccessor = umbracoContextAccessor;
_memberTypeService = memberTypeService;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_textService = textService ?? throw new ArgumentNullException(nameof(textService));
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var model = (MemberSave)actionContext.ActionArguments["contentItem"];
var contentItemValidator = new MemberSaveModelValidator(_logger, _umbracoContextAccessor, _memberTypeService);
var contentItemValidator = new MemberSaveModelValidator(_logger, _umbracoContextAccessor, _textService, _memberTypeService);
//now do each validation step
if (contentItemValidator.ValidateExistingContent(model, actionContext))
if (contentItemValidator.ValidateProperties(model, model, actionContext))

View File

@@ -50,6 +50,14 @@ namespace Umbraco.Web.Editors.Filters
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
return;
}
if (persisted.Alias != userGroupSave.Alias && persisted.IsSystemUserGroup())
{
var message = $"User group with alias: {persisted.Alias} cannot be changed";
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, message);
return;
}
//map the model to the persisted instance
Mapper.Map(userGroupSave, persisted);
break;

View File

@@ -13,14 +13,23 @@ namespace Umbraco.Web.Editors
{
var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree);
if (_httpClient == null)
_httpClient = new HttpClient();
try
{
//fetch dashboard json and parse to JObject
var json = await _httpClient.GetStringAsync(url);
var result = JsonConvert.DeserializeObject<List<HelpPage>>(json);
if (result != null)
return result;
if (_httpClient == null)
_httpClient = new HttpClient();
//fetch dashboard json and parse to JObject
var json = await _httpClient.GetStringAsync(url);
var result = JsonConvert.DeserializeObject<List<HelpPage>>(json);
if (result != null)
return result;
}
catch (HttpRequestException rex)
{
Logger.Info(GetType(), $"Check your network connection, exception: {rex.Message}");
}
return new List<HelpPage>();
}

View File

@@ -14,9 +14,7 @@ namespace Umbraco.Web.Editors
// initialized with all IEditorValidator instances
//
// validation is used exclusively in ContentTypeControllerBase
// the whole thing is internal at the moment, never released
// and, there are no IEditorValidator implementation in Core
// so... this all mechanism is basically useless
// currently the only implementations are for Models Builder.
/// <summary>
/// Provides a general object validator.

View File

@@ -60,8 +60,27 @@ namespace Umbraco.Web.Editors
//redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file
var response = Request.CreateResponse(HttpStatusCode.Found);
var imageLastModified = _mediaFileSystem.GetLastModified(imagePath);
response.Headers.Location = new Uri($"{imagePath}?rnd={imageLastModified:yyyyMMddHHmmss}&upscale=false&width={width}&animationprocessmode=first&mode=max", UriKind.Relative);
DateTimeOffset? imageLastModified = null;
try
{
imageLastModified = _mediaFileSystem.GetLastModified(imagePath);
}
catch (Exception)
{
// if we get an exception here it's probably because the image path being requested is an image that doesn't exist
// in the local media file system. This can happen if someone is storing an absolute path to an image online, which
// is perfectly legal but in that case the media file system isn't going to resolve it.
// so ignore and we won't set a last modified date.
}
// TODO: When we abstract imaging for netcore, we are actually just going to be abstracting a URI builder for images, this
// is one of those places where this can be used.
var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : string.Empty;
response.Headers.Location = new Uri($"{imagePath}?upscale=false&width={width}&animationprocessmode=first&mode=max{rnd}", UriKind.RelativeOrAbsolute);
return response;
}

View File

@@ -99,24 +99,28 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
// this is prone to race conditions but the service will not let us proceed anyways
var existing = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode);
var existingByCulture = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode);
// the localization service might return the generic language even when queried for specific ones (e.g. "da" when queried for "da-DK")
// - we need to handle that explicitly
if (existing?.IsoCode != language.IsoCode)
if (existingByCulture?.IsoCode != language.IsoCode)
{
existing = null;
existingByCulture = null;
}
if (existing != null && language.Id != existing.Id)
if (existingByCulture != null && language.Id != existingByCulture.Id)
{
//someone is trying to create a language that already exist
ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists");
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
}
if (existing == null)
var existingById = language.Id != default ? Services.LocalizationService.GetLanguageById(language.Id) : null;
if (existingById == null)
{
//Creating a new lang...
CultureInfo culture;
try
{
@@ -141,38 +145,39 @@ namespace Umbraco.Web.Editors
return Mapper.Map<Language>(newLang);
}
existing.IsMandatory = language.IsMandatory;
existingById.IsMandatory = language.IsMandatory;
// note that the service will prevent the default language from being "un-defaulted"
// but does not hurt to test here - though the UI should prevent it too
if (existing.IsDefault && !language.IsDefault)
if (existingById.IsDefault && !language.IsDefault)
{
ModelState.AddModelError("IsDefault", "Cannot un-default the default language.");
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
}
existing.IsDefault = language.IsDefault;
existing.FallbackLanguageId = language.FallbackLanguageId;
existingById.IsDefault = language.IsDefault;
existingById.FallbackLanguageId = language.FallbackLanguageId;
existingById.IsoCode = language.IsoCode;
// modifying an existing language can create a fallback, verify
// note that the service will check again, dealing with race conditions
if (existing.FallbackLanguageId.HasValue)
if (existingById.FallbackLanguageId.HasValue)
{
var languages = Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.Id, x => x);
if (!languages.ContainsKey(existing.FallbackLanguageId.Value))
if (!languages.ContainsKey(existingById.FallbackLanguageId.Value))
{
ModelState.AddModelError("FallbackLanguage", "The selected fall back language does not exist.");
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
}
if (CreatesCycle(existing, languages))
if (CreatesCycle(existingById, languages))
{
ModelState.AddModelError("FallbackLanguage", $"The selected fall back language {languages[existing.FallbackLanguageId.Value].IsoCode} would create a circular path.");
ModelState.AddModelError("FallbackLanguage", $"The selected fall back language {languages[existingById.FallbackLanguageId.Value].IsoCode} would create a circular path.");
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
}
}
Services.LocalizationService.Save(existing);
return Mapper.Map<Language>(existing);
Services.LocalizationService.Save(existingById);
return Mapper.Map<Language>(existingById);
}
// see LocalizationService

View File

@@ -130,5 +130,11 @@ namespace Umbraco.Web.Editors
{
return _logViewer.DeleteSavedSearch(item.Name, item.Query);
}
[HttpGet]
public string GetLogLevel()
{
return _logViewer.GetLogLevel();
}
}
}

View File

@@ -135,14 +135,19 @@ namespace Umbraco.Web.Editors
// must have an active variation context!
_variationContextAccessor.VariationContext = new VariationContext(culture);
var result = Request.CreateResponse();
//need to create a specific content result formatted as HTML since this controller has been configured
//with only json formatters.
result.Content = new StringContent(
_componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(),
Encoding.UTF8,
"text/html");
return result;
using (UmbracoContext.ForcedPreview(true))
{
var result = Request.CreateResponse();
//need to create a specific content result formatted as HTML since this controller has been configured
//with only json formatters.
result.Content = new StringContent(
_componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(),
Encoding.UTF8,
"text/html");
return result;
}
}
[HttpPost]

View File

@@ -19,6 +19,7 @@ using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
using System.Web.Http.Controllers;
namespace Umbraco.Web.Editors
{
@@ -28,6 +29,7 @@ namespace Umbraco.Web.Editors
/// </summary>
[PluginController("UmbracoApi")]
[UmbracoTreeAuthorize(Constants.Trees.Macros)]
[MacrosControllerConfiguration]
public class MacrosController : BackOfficeNotificationsController
{
private readonly IMacroService _macroService;
@@ -38,6 +40,19 @@ namespace Umbraco.Web.Editors
_macroService = Services.MacroService;
}
/// <summary>
/// Configures this controller with a custom action selector
/// </summary>
private class MacrosControllerConfigurationAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector(
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi))
));
}
}
/// <summary>
/// Creates a new macro
/// </summary>
@@ -62,6 +77,11 @@ namespace Umbraco.Web.Editors
return this.ReturnErrorResponse("Macro with this alias already exists");
}
if (name == null || name.Length > 255)
{
return this.ReturnErrorResponse("Name cannnot be more than 255 characters in length.");
}
try
{
var macro = new Macro
@@ -92,39 +112,43 @@ namespace Umbraco.Web.Editors
return this.ReturnErrorResponse($"Macro with id {id} does not exist");
}
var macroDisplay = new MacroDisplay
{
Alias = macro.Alias,
Id = macro.Id,
Key = macro.Key,
Name = macro.Name,
CacheByPage = macro.CacheByPage,
CacheByUser = macro.CacheByMember,
CachePeriod = macro.CacheDuration,
View = macro.MacroSource,
RenderInEditor = !macro.DontRender,
UseInEditor = macro.UseInEditor,
Path = $"-1,{macro.Id}"
};
var parameters = new List<MacroParameterDisplay>();
foreach (var param in macro.Properties.Values.OrderBy(x => x.SortOrder))
{
parameters.Add(new MacroParameterDisplay
{
Editor = param.EditorAlias,
Key = param.Alias,
Label = param.Name,
Id = param.Id
});
}
macroDisplay.Parameters = parameters;
var macroDisplay = MapToDisplay(macro);
return this.Request.CreateResponse(HttpStatusCode.OK, macroDisplay);
}
[HttpGet]
public HttpResponseMessage GetById(Guid id)
{
var macro = _macroService.GetById(id);
if (macro == null)
{
return this.ReturnErrorResponse($"Macro with id {id} does not exist");
}
var macroDisplay = MapToDisplay(macro);
return this.Request.CreateResponse(HttpStatusCode.OK, macroDisplay);
}
[HttpGet]
public HttpResponseMessage GetById(Udi id)
{
var guidUdi = id as GuidUdi;
if (guidUdi == null)
this.ReturnErrorResponse($"Macro with id {id} does not exist");
var macro = _macroService.GetById(guidUdi.Guid);
if (macro == null)
{
return this.ReturnErrorResponse($"Macro with id {id} does not exist");
}
var macroDisplay = MapToDisplay(macro);
return this.Request.CreateResponse(HttpStatusCode.OK, macroDisplay);
}
[HttpPost]
public HttpResponseMessage DeleteById(int id)
@@ -149,6 +173,11 @@ namespace Umbraco.Web.Editors
return this.ReturnErrorResponse($"No macro data found in request");
}
if (macroDisplay.Name == null || macroDisplay.Name.Length > 255)
{
return this.ReturnErrorResponse("Name cannnot be more than 255 characters in length.");
}
var macro = _macroService.GetById(int.Parse(macroDisplay.Id.ToString()));
if (macro == null)
@@ -219,6 +248,39 @@ namespace Umbraco.Web.Editors
return this.Request.CreateResponse(HttpStatusCode.OK, Current.ParameterEditors);
}
/// <summary>
/// Gets the available parameter editors grouped by their group.
/// </summary>
/// <returns>
/// The <see cref="HttpResponseMessage"/>.
/// </returns>
public HttpResponseMessage GetGroupedParameterEditors()
{
var parameterEditors = Current.ParameterEditors.ToArray();
var grouped = parameterEditors
.GroupBy(x => x.Group.IsNullOrWhiteSpace() ? "" : x.Group.ToLower())
.OrderBy(x => x.Key)
.ToDictionary(group => group.Key, group => group.OrderBy(d => d.Name).AsEnumerable());
return this.Request.CreateResponse(HttpStatusCode.OK, grouped);
}
/// <summary>
/// Get parameter editor by alias.
/// </summary>
/// <returns>
/// The <see cref="HttpResponseMessage"/>.
/// </returns>
public HttpResponseMessage GetParameterEditorByAlias(string alias)
{
var parameterEditors = Current.ParameterEditors.ToArray();
var parameterEditor = parameterEditors.FirstOrDefault(x => x.Alias.InvariantEquals(alias));
return this.Request.CreateResponse(HttpStatusCode.OK, parameterEditor);
}
/// <summary>
/// Returns a error response and optionally logs it
/// </summary>
@@ -280,7 +342,6 @@ namespace Umbraco.Web.Editors
/// Finds partial view files in app plugin folders.
/// </summary>
/// <returns>
/// The <see cref="IEnumerable"/>.
/// </returns>
private IEnumerable<string> FindPartialViewFilesInPluginFolders()
{
@@ -342,5 +403,29 @@ namespace Umbraco.Web.Editors
return files;
}
/// <summary>
/// Used to map an <see cref="IMacro"/> instance to a <see cref="MacroDisplay"/>
/// </summary>
/// <param name="macro"></param>
/// <returns></returns>
private MacroDisplay MapToDisplay(IMacro macro)
{
var display = Mapper.Map<MacroDisplay>(macro);
var parameters = macro.Properties.Values
.OrderBy(x => x.SortOrder)
.Select(x => new MacroParameterDisplay()
{
Editor = x.EditorAlias,
Key = x.Alias,
Label = x.Name,
Id = x.Id
});
display.Parameters = parameters;
return display;
}
}
}

View File

@@ -36,6 +36,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Web.ContentApps;
using Umbraco.Web.Editors.Binders;
using Umbraco.Web.Editors.Filters;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Web.Editors
{
@@ -815,7 +816,7 @@ namespace Umbraco.Web.Editors
}
else
{
throw new EntityNotFoundException(parentId, "The passed id doesn't exist");
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, "The passed id doesn't exist"));
}
}
else
@@ -943,5 +944,31 @@ namespace Umbraco.Web.Editors
return hasPathAccess;
}
public PagedResult<EntityBasic> GetPagedReferences(int id, string entityType, int pageNumber = 1, int pageSize = 100)
{
if (pageNumber <= 0 || pageSize <= 0)
{
throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero");
}
var objectType = ObjectTypes.GetUmbracoObjectType(entityType);
var udiType = ObjectTypes.GetUdiType(objectType);
var relations = Services.RelationService.GetPagedParentEntitiesByChildId(id, pageNumber - 1, pageSize, out var totalRecords, objectType);
return new PagedResult<EntityBasic>(totalRecords, pageNumber, pageSize)
{
Items = relations.Cast<ContentEntitySlim>().Select(rel => new EntityBasic
{
Id = rel.Id,
Key = rel.Key,
Udi = Udi.Create(udiType, rel.Key),
Icon = rel.ContentTypeIcon,
Name = rel.Name,
Alias = rel.ContentTypeAlias
})
};
}
}
}

View File

@@ -19,6 +19,7 @@ using Umbraco.Core.Dictionary;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Web.Composing;
using IMediaType = Umbraco.Core.Models.IMediaType;
namespace Umbraco.Web.Editors
{
@@ -109,7 +110,7 @@ namespace Umbraco.Web.Editors
[HttpPost]
public HttpResponseMessage GetAvailableCompositeMediaTypes(GetAvailableCompositionsFilter filter)
{
var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.MediaType, filter.FilterContentTypes, filter.FilterPropertyTypes)
var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.MediaType, filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement)
.Select(x => new
{
contentType = x.Item1,
@@ -135,12 +136,18 @@ namespace Umbraco.Web.Editors
}
public MediaTypeDisplay GetEmpty(int parentId)
{
var ct = new MediaType(parentId)
IMediaType mt;
if (parentId != Constants.System.Root)
{
Icon = Constants.Icons.MediaImage
};
var parent = Services.MediaTypeService.Get(parentId);
mt = parent != null ? new MediaType(parent, string.Empty) : new MediaType(parentId);
}
else
mt = new MediaType(parentId);
var dto = Mapper.Map<IMediaType, MediaTypeDisplay>(ct);
mt.Icon = Constants.Icons.MediaImage;
var dto = Mapper.Map<IMediaType, MediaTypeDisplay>(mt);
return dto;
}
@@ -233,11 +240,11 @@ namespace Umbraco.Web.Editors
}
var contentType = Services.MediaTypeService.Get(contentItem.ContentTypeId);
var ids = contentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray();
var ids = contentType.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value).ToArray();
if (ids.Any() == false) return Enumerable.Empty<ContentTypeBasic>();
types = Services.MediaTypeService.GetAll(ids).ToList();
types = Services.MediaTypeService.GetAll(ids).OrderBy(c => ids.IndexOf(c.Id)).ToList();
}
var basics = types.Select(Mapper.Map<IMediaType, ContentTypeBasic>).ToList();
@@ -248,7 +255,7 @@ namespace Umbraco.Web.Editors
basic.Description = TranslateItem(basic.Description);
}
return basics.OrderBy(x => x.Name);
return basics.OrderBy(c => contentId == Constants.System.Root ? c.Name : string.Empty);
}
/// <summary>

View File

@@ -89,7 +89,7 @@ namespace Umbraco.Web.Editors
[FromUri]string[] filterContentTypes,
[FromUri]string[] filterPropertyTypes)
{
var result = PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MemberType, filterContentTypes, filterPropertyTypes)
var result = PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MemberType, filterContentTypes, filterPropertyTypes, false)
.Select(x => new
{
contentType = x.Item1,

View File

@@ -3,12 +3,14 @@ using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
@@ -45,14 +47,28 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var relations = Services.RelationService.GetByRelationTypeId(relationType.Id);
var display = Mapper.Map<IRelationType, RelationTypeDisplay>(relationType);
display.Relations = Mapper.MapEnumerable<IRelation, RelationDisplay>(relations);
return display;
}
public PagedResult<RelationDisplay> GetPagedResults(int id, int pageNumber = 1, int pageSize = 100)
{
if (pageNumber <= 0 || pageSize <= 0)
{
throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero");
}
// Ordering do we need to pass through?
var relations = Services.RelationService.GetPagedByRelationTypeId(id, pageNumber -1, pageSize, out long totalRecords);
return new PagedResult<RelationDisplay>(totalRecords, pageNumber, pageSize)
{
Items = relations.Select(x => Mapper.Map<RelationDisplay>(x))
};
}
/// <summary>
/// Gets a list of object types which can be associated via relations.
/// </summary>
@@ -84,11 +100,7 @@ namespace Umbraco.Web.Editors
/// <returns>A <see cref="HttpResponseMessage"/> containing the persisted relation type's ID.</returns>
public HttpResponseMessage PostCreate(RelationTypeSave relationType)
{
var relationTypePersisted = new RelationType(relationType.ChildObjectType, relationType.ParentObjectType, relationType.Name.ToSafeAlias(true))
{
Name = relationType.Name,
IsBidirectional = relationType.IsBidirectional
};
var relationTypePersisted = new RelationType(relationType.Name, relationType.Name.ToSafeAlias(true), relationType.IsBidirectional, relationType.ChildObjectType, relationType.ParentObjectType);
try
{

View File

@@ -0,0 +1,110 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Editors
{
[PluginController("UmbracoApi")]
[UmbracoApplicationAuthorize(
Constants.Applications.Content,
Constants.Applications.Media,
Constants.Applications.Members)]
public class TinyMceController : UmbracoAuthorizedApiController
{
private IMediaService _mediaService;
private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
public TinyMceController(IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider)
{
_mediaService = mediaService;
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
}
[HttpPost]
public async Task<HttpResponseMessage> UploadImage()
{
if (Request.Content.IsMimeMultipartContent() == false)
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
// Create an unique folder path to help with concurrent users to avoid filename clash
var imageTempPath = IOHelper.MapPath(SystemDirectories.TempImageUploads + "/" + Guid.NewGuid().ToString());
// Temp folderpath (Files come in as bodypart & will need to move/saved into imgTempPath
var folderPath = IOHelper.MapPath(SystemDirectories.TempFileUploads);
// Ensure image temp path exists
if(Directory.Exists(imageTempPath) == false)
{
Directory.CreateDirectory(imageTempPath);
}
// File uploaded will be saved as bodypart into TEMP folder
var provider = new MultipartFormDataStreamProvider(folderPath);
var result = await Request.Content.ReadAsMultipartAsync(provider);
// Must have a file
if (result.FileData.Count == 0)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
// Should only have one file
if (result.FileData.Count > 1)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Only one file can be uploaded at a time");
}
// Really we should only have one file per request to this endpoint
var file = result.FileData[0];
var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd();
var safeFileName = fileName.ToSafeFileName();
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
if (Current.Configs.Settings().Content.IsFileAllowedForUpload(ext) == false || Current.Configs.Settings().Content.ImageFileTypes.Contains(ext) == false)
{
// Throw some error - to say can't upload this IMG type
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "This is not an image filetype extension that is approved");
}
//var mediaItemName = fileName.ToFriendlyName();
var currentFile = file.LocalFileName;
var newFilePath = imageTempPath + IOHelper.DirSepChar + safeFileName;
var relativeNewFilePath = IOHelper.GetRelativePath(newFilePath);
try
{
// Move the file from bodypart to a real filename
// This is what we return from this API so RTE updates img src path
// Until we fully persist & save the media item when persisting
// If we find <img data-temp-img /> data attribute
File.Move(currentFile, newFilePath);
}
catch (Exception ex)
{
// IOException, PathTooLong, DirectoryNotFound, UnathorizedAccess
Logger.Error<TinyMceController>(ex, "Error when trying to move {CurrentFilePath} to {NewFilePath}", currentFile, newFilePath);
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, $"Error when trying to move {currentFile} to {newFilePath}", ex);
}
return Request.CreateResponse(HttpStatusCode.OK, new { tmpLocation = relativeNewFilePath });
}
}
}

View File

@@ -51,29 +51,35 @@ namespace Umbraco.Web.Editors
}
//collect all tour files in packages
foreach (var plugin in Directory.EnumerateDirectories(IOHelper.MapPath(SystemDirectories.AppPlugins)))
var appPlugins = IOHelper.MapPath(SystemDirectories.AppPlugins);
if (Directory.Exists(appPlugins))
{
var pluginName = Path.GetFileName(plugin.TrimEnd('\\'));
var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)).ToList();
//If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely
var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null);
if (isPluginFiltered) continue;
//combine matched package filters with filters not specific to a package
var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList();
foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice"))
foreach (var plugin in Directory.EnumerateDirectories(appPlugins))
{
foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours"))
var pluginName = Path.GetFileName(plugin.TrimEnd('\\'));
var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName))
.ToList();
//If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely
var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null);
if (isPluginFiltered) continue;
//combine matched package filters with filters not specific to a package
var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList();
foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice"))
{
foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json"))
foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours"))
{
TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName);
foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json"))
{
TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName);
}
}
}
}
}
//Get all allowed sections for the current user
var allowedSections = user.AllowedSections.ToList();
@@ -104,6 +110,39 @@ namespace Umbraco.Web.Editors
return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
}
/// <summary>
/// Gets a tours for a specific doctype
/// </summary>
/// <param name="doctypeAlias">The documenttype alias</param>
/// <returns>A <see cref="BackOfficeTour"/></returns>
public IEnumerable<BackOfficeTour> GetToursForDoctype(string doctypeAlias)
{
var tourFiles = this.GetTours();
var doctypeAliasWithCompositions = new List<string>
{
doctypeAlias
};
var contentType = this.Services.ContentTypeService.Get(doctypeAlias);
if (contentType != null)
{
doctypeAliasWithCompositions.AddRange(contentType.CompositionAliases());
}
return tourFiles.SelectMany(x => x.Tours)
.Where(x =>
{
if (string.IsNullOrEmpty(x.ContentType))
{
return false;
}
var contentTypes = x.ContentType.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim());
return contentTypes.Intersect(doctypeAliasWithCompositions).Any();
});
}
private void TryParseTourFile(string tourFile,
ICollection<BackOfficeTourFile> result,
List<BackOfficeTourFilter> filters,

View File

@@ -154,8 +154,8 @@ namespace Umbraco.Web.Editors
public HttpResponseMessage PostDeleteUserGroups([FromUri] int[] userGroupIds)
{
var userGroups = Services.UserService.GetAllUserGroups(userGroupIds)
//never delete the admin group or translators group
.Where(x => x.Alias != Constants.Security.AdminGroupAlias && x.Alias != Constants.Security.TranslatorGroupAlias)
//never delete the admin group, sensitive data or translators group
.Where(x => !x.IsSystemUserGroup())
.ToArray();
foreach (var userGroup in userGroups)
{

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
@@ -25,6 +26,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Editors.Filters;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
@@ -105,7 +107,7 @@ namespace Umbraco.Web.Editors
if (Current.Configs.Settings().Content.DisallowedUploadFiles.Contains(ext) == false)
{
//generate a path of known data, we don't want this path to be guessable
user.Avatar = "UserAvatars/" + (user.Id + safeFileName).ToSHA1() + "." + ext;
user.Avatar = "UserAvatars/" + (user.Id + safeFileName).GenerateHash<SHA1>() + "." + ext;
using (var fs = System.IO.File.OpenRead(file.LocalFileName))
{
@@ -543,34 +545,12 @@ namespace Umbraco.Web.Editors
hasErrors = true;
}
// if the found user has his email for username, we want to keep this synced when changing the email.
// if the found user has their email for username, we want to keep this synced when changing the email.
// we have already cross-checked above that the email isn't colliding with anything, so we can safely assign it here.
if (Current.Configs.Settings().Security.UsernameIsEmail && found.Username == found.Email && userSave.Username != userSave.Email)
{
userSave.Username = userSave.Email;
}
if (userSave.ChangePassword != null)
{
var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext);
//this will change the password and raise appropriate events
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager);
if (passwordChangeResult.Success)
{
//need to re-get the user
found = Services.UserService.GetUserById(intId.Result);
}
else
{
hasErrors = true;
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
{
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
}
}
}
}
if (hasErrors)
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
@@ -586,6 +566,51 @@ namespace Umbraco.Web.Editors
return display;
}
/// <summary>
///
/// </summary>
/// <param name="changingPasswordModel"></param>
/// <returns></returns>
public async Task<ModelWithNotifications<string>> PostChangePassword(ChangingPasswordModel changingPasswordModel)
{
changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel));
if (ModelState.IsValid == false)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
}
var intId = changingPasswordModel.Id.TryConvertTo<int>();
if (intId.Success == false)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var found = Services.UserService.GetUserById(intId.Result);
if (found == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext);
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, changingPasswordModel, UserManager);
if (passwordChangeResult.Success)
{
var result = new ModelWithNotifications<string>(passwordChangeResult.Result.ResetPassword);
result.AddSuccessNotification(Services.TextService.Localize("general/success"), Services.TextService.Localize("user/passwordChangedGeneric"));
return result;
}
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
{
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
}
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
}
/// <summary>
/// Disables the users with the given user ids
/// </summary>

View File

@@ -2,32 +2,95 @@
using System.Collections.Generic;
using System.Linq;
using Examine;
using Umbraco.Core;
using Examine.LuceneEngine.Providers;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Examine;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Web
{
/// <summary>
/// Extension methods for Examine
/// Extension methods for Examine.
/// </summary>
public static class ExamineExtensions
{
/// <summary>
/// Creates an <see cref="IEnumerable{PublishedSearchResult}" /> containing all content from the <paramref name="cache" />.
/// </summary>
/// <param name="results">The search results.</param>
/// <param name="cache">The cache to fetch the content from.</param>
/// <returns>
/// An <see cref="IEnumerable{PublishedSearchResult}" /> containing all content.
/// </returns>
/// <exception cref="ArgumentNullException">cache</exception>
/// <remarks>
/// Search results are skipped if it can't be fetched from the <paramref name="cache" /> by its integer id.
/// </remarks>
public static IEnumerable<PublishedSearchResult> ToPublishedSearchResults(this IEnumerable<ISearchResult> results, IPublishedCache cache)
{
var list = new List<PublishedSearchResult>();
if (cache == null) throw new ArgumentNullException(nameof(cache));
foreach (var result in results.OrderByDescending(x => x.Score))
var publishedSearchResults = new List<PublishedSearchResult>();
foreach (var result in results)
{
if (!int.TryParse(result.Id, out var intId)) continue; //invalid
var content = cache.GetById(intId);
if (content == null) continue; // skip if this doesn't exist in the cache
list.Add(new PublishedSearchResult(content, result.Score));
if (int.TryParse(result.Id, out var contentId) &&
cache.GetById(contentId) is IPublishedContent content)
{
publishedSearchResults.Add(new PublishedSearchResult(content, result.Score));
}
}
return list;
return publishedSearchResults;
}
/// <summary>
/// Creates an <see cref="IEnumerable{PublishedSearchResult}" /> containing all content, media or members from the <paramref name="snapshot" />.
/// </summary>
/// <param name="results">The search results.</param>
/// <param name="snapshot">The snapshot.</param>
/// <returns>
/// An <see cref="IEnumerable{PublishedSearchResult}" /> containing all content, media or members.
/// </returns>
/// <exception cref="ArgumentNullException">snapshot</exception>
/// <remarks>
/// Search results are skipped if it can't be fetched from the respective cache by its integer id.
/// </remarks>
public static IEnumerable<PublishedSearchResult> ToPublishedSearchResults(this IEnumerable<ISearchResult> results, IPublishedSnapshot snapshot)
{
if (snapshot == null) throw new ArgumentNullException(nameof(snapshot));
var publishedSearchResults = new List<PublishedSearchResult>();
foreach (var result in results)
{
if (int.TryParse(result.Id, out var contentId) &&
result.Values.TryGetValue(LuceneIndex.CategoryFieldName, out var indexType))
{
IPublishedContent content;
switch (indexType)
{
case IndexTypes.Content:
content = snapshot.Content.GetById(contentId);
break;
case IndexTypes.Media:
content = snapshot.Media.GetById(contentId);
break;
case IndexTypes.Member:
content = snapshot.Members.GetById(contentId);
break;
default:
continue;
}
if (content != null)
{
publishedSearchResults.Add(new PublishedSearchResult(content, result.Score));
}
}
}
return publishedSearchResults;
}
}
}

View File

@@ -1,13 +1,10 @@
using System;
using System.Web.Mvc.Html;
using System.Web.Mvc;
using System.IO;
using Umbraco.Core.Exceptions;
using System.Web.Mvc.Html;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web
{
public static class GridTemplateExtensions
{
public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedProperty property, string framework = "bootstrap3")
@@ -26,18 +23,20 @@ namespace Umbraco.Web
public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias)
{
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias));
if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias));
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias));
return html.GetGridHtml(contentItem, propertyAlias, "bootstrap3");
}
public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias, string framework)
{
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias));
if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias));
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias));
var view = "Grid/" + framework;
var prop = contentItem.GetProperty(propertyAlias);
if (prop == null) throw new NullReferenceException("No property type found with alias " + propertyAlias);
if (prop == null) throw new InvalidOperationException("No property type found with alias " + propertyAlias);
var model = prop.GetValue();
var asString = model as string;
@@ -54,23 +53,28 @@ namespace Umbraco.Web
var view = "Grid/" + framework;
return html.Partial(view, property.GetValue());
}
public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html)
{
return GetGridHtml(contentItem, html, "bodyText", "bootstrap3");
}
public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html, string propertyAlias)
{
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias));
if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias));
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias));
return GetGridHtml(contentItem, html, propertyAlias, "bootstrap3");
}
public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html, string propertyAlias, string framework)
{
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias));
if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias));
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias));
var view = "Grid/" + framework;
var prop = contentItem.GetProperty(propertyAlias);
if (prop == null) throw new NullReferenceException("No property type found with alias " + propertyAlias);
if (prop == null) throw new InvalidOperationException("No property type found with alias " + propertyAlias);
var model = prop.GetValue();
var asString = model as string;
@@ -78,12 +82,5 @@ namespace Umbraco.Web
return html.Partial(view, model);
}
private class FakeView : IView
{
public void Render(ViewContext viewContext, TextWriter writer)
{
}
}
}
}

View File

@@ -78,7 +78,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
var response = request.GetResponse();
// Check first for header
success = DoHttpHeadersContainHeader(response);
success = HasMatchingHeader(response.Headers.AllKeys);
// If not found, and available, check for meta-tag
if (success == false && _metaTagOptionAvailable)
@@ -113,9 +113,9 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
};
}
private bool DoHttpHeadersContainHeader(WebResponse response)
private bool HasMatchingHeader(IEnumerable<string> headerKeys)
{
return response.Headers.AllKeys.Contains(_header);
return headerKeys.Contains(_header, StringComparer.InvariantCultureIgnoreCase);
}
private bool DoMetaTagsContainKeyForHeader(WebResponse response)
@@ -127,7 +127,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
{
var html = reader.ReadToEnd();
var metaTags = ParseMetaTags(html);
return metaTags.ContainsKey(_header);
return HasMatchingHeader(metaTags.Keys);
}
}
}
@@ -186,24 +186,22 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
}
var removeHeaderElement = customHeadersElement.Elements("remove")
.SingleOrDefault(x => x.Attribute("name") != null &&
x.Attribute("name")?.Value == _value);
.SingleOrDefault(x => x.Attribute("name")?.Value.Equals(_value, StringComparison.InvariantCultureIgnoreCase) == true);
if (removeHeaderElement == null)
{
removeHeaderElement = new XElement("remove");
removeHeaderElement.Add(new XAttribute("name", _header));
customHeadersElement.Add(removeHeaderElement);
customHeadersElement.Add(
new XElement("remove",
new XAttribute("name", _header)));
}
var addHeaderElement = customHeadersElement.Elements("add")
.SingleOrDefault(x => x.Attribute("name") != null &&
x.Attribute("name").Value == _header);
.SingleOrDefault(x => x.Attribute("name")?.Value.Equals(_header, StringComparison.InvariantCultureIgnoreCase) == true);
if (addHeaderElement == null)
{
addHeaderElement = new XElement("add");
addHeaderElement.Add(new XAttribute("name", _header));
addHeaderElement.Add(new XAttribute("value", _value));
customHeadersElement.Add(addHeaderElement);
customHeadersElement.Add(
new XElement("add",
new XAttribute("name", _header),
new XAttribute("value", _value)));
}
doc.Save(configFile);

View File

@@ -13,6 +13,7 @@ using Umbraco.Web.Models;
namespace Umbraco.Web
{
using Core.Configuration;
using Umbraco.Web.JavaScript;
/// <summary>
/// HtmlHelper extensions for the back office
@@ -118,6 +119,21 @@ namespace Umbraco.Web
sb.AppendLine(JsonConvert.SerializeObject(resetCodeModel));
sb.AppendLine(@"});");
return html.Raw(sb.ToString());
}
public static IHtmlString AngularValueTinyMceAssets(this HtmlHelper html)
{
var ctx = new HttpContextWrapper(HttpContext.Current);
var files = JsInitialization.OptimizeTinyMceScriptFiles(ctx);
var sb = new StringBuilder();
sb.AppendLine(@"app.value(""tinyMceAssets"",");
sb.AppendLine(JsonConvert.SerializeObject(files));
sb.AppendLine(@");");
return html.Raw(sb.ToString());
}
}

View File

@@ -8,11 +8,7 @@ using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.Security;
using Current = Umbraco.Web.Composing.Current;
@@ -68,7 +64,8 @@ namespace Umbraco.Web
var htmlBadge =
String.Format(Current.Configs.Settings().Content.PreviewBadge,
IOHelper.ResolveUrl(SystemDirectories.Umbraco),
Current.UmbracoContext.HttpContext.Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Path));
Current.UmbracoContext.HttpContext.Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Path),
Current.UmbracoContext.PublishedRequest.PublishedContent.Id);
return new MvcHtmlString(htmlBadge);
}
return new MvcHtmlString("");
@@ -169,8 +166,9 @@ namespace Umbraco.Web
/// <returns></returns>
public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName, Type surfaceType)
{
if (actionName == null) throw new ArgumentNullException(nameof(actionName));
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName));
if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType));
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName));
var routeVals = new RouteValueDictionary(new {area = ""});
@@ -221,7 +219,7 @@ namespace Umbraco.Web
{
_viewContext = viewContext;
_method = method;
_controllerName = controllerName;
_controllerName = controllerName;
_encryptedString = UrlHelperRenderExtensions.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals);
}
@@ -230,7 +228,7 @@ namespace Umbraco.Web
private readonly FormMethod _method;
private bool _disposed;
private readonly string _encryptedString;
private readonly string _controllerName;
private readonly string _controllerName;
protected override void Dispose(bool disposing)
{
@@ -243,12 +241,12 @@ namespace Umbraco.Web
|| _controllerName == "UmbProfile"
|| _controllerName == "UmbLoginStatus"
|| _controllerName == "UmbLogin")
{
{
_viewContext.Writer.Write(AntiForgery.GetHtml().ToString());
}
}
//write out the hidden surface form routes
_viewContext.Writer.Write("<input name='ufprt' type='hidden' value='" + _encryptedString + "' />");
_viewContext.Writer.Write("<input name=\"ufprt\" type=\"hidden\" value=\"" + _encryptedString + "\" />");
base.Dispose(disposing);
}
@@ -355,8 +353,10 @@ namespace Umbraco.Web
IDictionary<string, object> htmlAttributes,
FormMethod method)
{
if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName));
if (action == null) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action));
if (controllerName == null) throw new ArgumentNullException(nameof(controllerName));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName));
return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method);
}
@@ -374,8 +374,10 @@ namespace Umbraco.Web
object additionalRouteVals,
IDictionary<string, object> htmlAttributes)
{
if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName));
if (action == null) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action));
if (controllerName == null) throw new ArgumentNullException(nameof(controllerName));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName));
return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes);
}
@@ -575,7 +577,9 @@ namespace Umbraco.Web
IDictionary<string, object> htmlAttributes,
FormMethod method)
{
if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action));
if (action == null) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action));
if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType));
var area = "";
@@ -687,8 +691,10 @@ namespace Umbraco.Web
IDictionary<string, object> htmlAttributes,
FormMethod method)
{
if (string.IsNullOrEmpty(action)) throw new ArgumentNullOrEmptyException(nameof(action));
if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName));
if (action == null) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(action)) throw new ArgumentException("Value can't be empty.", nameof(action));
if (controllerName == null) throw new ArgumentNullException(nameof(controllerName));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName));
var formAction = Current.UmbracoContext.OriginalRequestUrl.PathAndQuery;
return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals);

View File

@@ -19,10 +19,26 @@ namespace Umbraco.Web
/// Replaces text line breaks with HTML line breaks
/// </summary>
/// <param name="text">The text.</param>
/// <returns>The text with text line breaks replaced with HTML line breaks (<br/>)</returns>
/// <returns>The text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).
[Obsolete("This method doesn't HTML encode the text. Use ReplaceLineBreaks instead.")]
public HtmlString ReplaceLineBreaksForHtml(string text)
{
return new HtmlString(text.Replace("\r\n", @"<br />").Replace("\n", @"<br />").Replace("\r", @"<br />"));
return new HtmlString(text.Replace("\r\n", @"<br />").Replace("\n", @"<br />").Replace("\r", @"<br />"));
}
/// <summary>
/// HTML encodes the text and replaces text line breaks with HTML line breaks.
/// </summary>
/// <param name="text">The text.</param>
/// <returns>The HTML encoded text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).</returns>
public IHtmlString ReplaceLineBreaks(string text)
{
var value = HttpUtility.HtmlEncode(text)?
.Replace("\r\n", "<br />")
.Replace("\r", "<br />")
.Replace("\n", "<br />");
return new HtmlString(value);
}
public HtmlString StripHtmlTags(string html, params string[] tags)

View File

@@ -3,7 +3,6 @@ using System.Linq;
using System.Linq.Expressions;
using System.Web.Http.Routing;
using Umbraco.Core;
using Umbraco.Core.Exceptions;
using Umbraco.Web.Composing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
@@ -53,7 +52,8 @@ namespace Umbraco.Web
/// <returns></returns>
public static string GetUmbracoApiService(this UrlHelper url, string actionName, Type apiControllerType, object id = null)
{
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName));
if (actionName == null) throw new ArgumentNullException(nameof(actionName));
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName));
if (apiControllerType == null) throw new ArgumentNullException(nameof(apiControllerType));
var area = "";
@@ -95,8 +95,10 @@ namespace Umbraco.Web
/// <returns></returns>
public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, string area, object id = null)
{
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName));
if (actionName == null) throw new ArgumentNullException(nameof(actionName));
if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName));
if (controllerName == null) throw new ArgumentNullException(nameof(controllerName));
if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName));
string routeName;
if (area.IsNullOrWhiteSpace())

View File

@@ -35,52 +35,63 @@ namespace Umbraco.Web
/// <summary>
/// Searches content.
/// </summary>
/// <param name="term">Term to search.</param>
/// <param name="culture">Optional culture.</param>
/// <param name="indexName">Optional index name.</param>
/// <param name="term">The term to search.</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>
/// <returns>
/// The search results.
/// </returns>
/// <remarks>
/// <para>
/// When the <paramref name="culture"/> is not specified or is *, all cultures are searched.
/// 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, string culture = "*", string indexName = null);
IEnumerable<PublishedSearchResult> Search(string term, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName);
/// <summary>
/// Searches content.
/// </summary>
/// <param name="term">Term to search.</param>
/// <param name="skip">Numbers of items to skip.</param>
/// <param name="take">Numbers of items to return.</param>
/// <param name="totalRecords">Total number of matching items.</param>
/// <param name="culture">Optional culture.</param>
/// <param name="indexName">Optional index name.</param>
/// <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>
/// <returns>
/// The search results.
/// </returns>
/// <remarks>
/// <para>
/// When the <paramref name="culture"/> is not specified or is *, all cultures are searched.
/// 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 = null);
IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName);
/// <summary>
/// Executes the query and converts the results to PublishedSearchResult.
/// Executes the query and converts the results to <see cref="PublishedSearchResult" />.
/// </summary>
/// <remarks>
/// <para>While enumerating results, the ambient culture is changed to be the searched culture.</para>
/// </remarks>
/// <param name="query">The query.</param>
/// <returns>
/// The search results.
/// </returns>
IEnumerable<PublishedSearchResult> Search(IQueryExecutor query);
/// <summary>
/// Executes the query and converts the results to PublishedSearchResult.
/// Executes the query and converts the results to <see cref="PublishedSearchResult" />.
/// </summary>
/// <remarks>
/// <para>While enumerating results, the ambient culture is changed to be the searched culture.</para>
/// </remarks>
/// <param name="query">The query.</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>
/// <returns>
/// The search results.
/// </returns>
IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords);
}
}

View File

@@ -6,6 +6,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
using Umbraco.Web.JavaScript;
using Umbraco.Web.Mvc;
using Umbraco.Web.Security;
namespace Umbraco.Web.Install.Controllers
@@ -35,6 +36,7 @@ namespace Umbraco.Web.Install.Controllers
}
[HttpGet]
[StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)]
public ActionResult Index()
{
if (_runtime.Level == RuntimeLevel.Run)

View File

@@ -45,8 +45,8 @@ namespace Umbraco.Web.Install
/// <summary>
/// This will test the directories for write access
/// </summary>
/// <param name="directories"></param>
/// <param name="errorReport"></param>
/// <param name="dirs"></param>
/// <param name="errors"></param>
/// <param name="writeCausesRestart">
/// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause
/// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as

View File

@@ -1,44 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;
namespace Umbraco.Web.Install
{
/// <summary>
/// Used for steps to be able to return a json structure back to the UI
/// Used for steps to be able to return a JSON structure back to the UI.
/// </summary>
internal class InstallException : Exception
/// <seealso cref="System.Exception" />
[Serializable]
public class InstallException : Exception
{
private readonly string _message;
/// <summary>
/// Gets the view.
/// </summary>
/// <value>
/// The view.
/// </value>
public string View { get; private set; }
/// <summary>
/// Gets the view model.
/// </summary>
/// <value>
/// The view model.
/// </value>
/// <remarks>
/// This object is not included when serializing.
/// </remarks>
public object ViewModel { get; private set; }
public override string Message
{
get { return _message; }
}
/// <summary>
/// Initializes a new instance of the <see cref="InstallException" /> class.
/// </summary>
public InstallException()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="InstallException" /> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public InstallException(string message)
: this(message, "error", null)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="InstallException" /> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="viewModel">The view model.</param>
public InstallException(string message, object viewModel)
: this(message, "error", viewModel)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="InstallException" /> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="view">The view.</param>
/// <param name="viewModel">The view model.</param>
public InstallException(string message, string view, object viewModel)
: base(message)
{
_message = message;
View = view;
ViewModel = viewModel;
}
public InstallException(string message, object viewModel)
/// <summary>
/// Initializes a new instance of the <see cref="InstallException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public InstallException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="InstallException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected InstallException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
_message = message;
View = "error";
ViewModel = viewModel;
View = info.GetString(nameof(View));
}
public InstallException(string message)
/// <summary>
/// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">info</exception>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
_message = message;
View = "error";
ViewModel = null;
if (info == null)
{
throw new ArgumentNullException(nameof(info));
}
info.AddValue(nameof(View), View);
base.GetObjectData(info, context);
}
}
}

View File

@@ -39,7 +39,8 @@ namespace Umbraco.Web.Install.InstallSteps
var fileName = IOHelper.MapPath($"{SystemDirectories.Root}/web.config");
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
var systemWeb = xml.Root.DescendantsAndSelf("system.web").Single();
// we only want to get the element that is under the root, (there may be more under <location> tags we don't want them)
var systemWeb = xml.Root.Element("system.web");
// Update appSetting if it exists, or else create a new appSetting for the given key and value
var machineKey = systemWeb.Descendants("machineKey").FirstOrDefault();

View File

@@ -131,6 +131,17 @@ namespace Umbraco.Web.JavaScript
return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString());
}
internal static IEnumerable<string> GetTinyMceInitialization()
{
var resources = JsonConvert.DeserializeObject<JArray>(Resources.TinyMceInitialize);
return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString());
}
internal static IEnumerable<string> OptimizeTinyMceScriptFiles(HttpContextBase httpContext)
{
return OptimizeScriptFiles(httpContext, GetTinyMceInitialization());
}
/// <summary>
/// Parses the JsResources.Main and replaces the replacement tokens accordingly.
/// </summary>

View File

@@ -13,6 +13,7 @@
'lib/angular-route/angular-route.js',
'lib/angular-cookies/angular-cookies.js',
'lib/angular-aria/angular-aria.min.js',
'lib/angular-touch/angular-touch.js',
'lib/angular-sanitize/angular-sanitize.js',
'lib/angular-animate/angular-animate.js',

View File

@@ -146,5 +146,17 @@ namespace Umbraco.Web.JavaScript {
return ResourceManager.GetString("ServerVariables", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to [
/// &apos;../lib/tinymce/tinymce.min.js&apos;,
///]
///.
/// </summary>
internal static string TinyMceInitialize {
get {
return ResourceManager.GetString("TinyMceInitialize", resourceCulture);
}
}
}
}

View File

@@ -130,4 +130,7 @@
<data name="ServerVariables" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>servervariables.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
</root>
<data name="TinyMceInitialize" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>TinyMceInitialize.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
</root>

View File

@@ -0,0 +1,17 @@
[
'lib/tinymce/tinymce.min.js',
'lib/tinymce/plugins/paste/plugin.min.js',
'lib/tinymce/plugins/anchor/plugin.min.js',
'lib/tinymce/plugins/charmap/plugin.min.js',
'lib/tinymce/plugins/table/plugin.min.js',
'lib/tinymce/plugins/lists/plugin.min.js',
'lib/tinymce/plugins/advlist/plugin.min.js',
'lib/tinymce/plugins/hr/plugin.min.js',
'lib/tinymce/plugins/autolink/plugin.min.js',
'lib/tinymce/plugins/directionality/plugin.min.js',
'lib/tinymce/plugins/tabfocus/plugin.min.js',
'lib/tinymce/plugins/searchreplace/plugin.min.js',
'lib/tinymce/plugins/fullscreen/plugin.min.js',
'lib/tinymce/plugins/noneditable/plugin.min.js'
]

View File

@@ -186,7 +186,7 @@ namespace Umbraco.Web.Macros
foreach (var prop in model.Properties)
{
var key = prop.Key.ToLowerInvariant();
prop.Value = macroParams.ContainsKey(key)
prop.Value = macroParams != null && macroParams.ContainsKey(key)
? macroParams[key]?.ToString() ?? string.Empty
: string.Empty;
}
@@ -197,7 +197,8 @@ namespace Umbraco.Web.Macros
public MacroContent Render(string macroAlias, IPublishedContent content, IDictionary<string, object> macroParams)
{
var m = _macroService.GetByAlias(macroAlias);
var m = _appCaches.RuntimeCache.GetCacheItem(CacheKeys.MacroFromAliasCacheKey + macroAlias, () => _macroService.GetByAlias(macroAlias));
if (m == null)
throw new InvalidOperationException("No macro found by alias " + macroAlias);

View File

@@ -25,7 +25,7 @@ namespace Umbraco.Web.Media.EmbedProviders
var imageHeight = GetXmlProperty(xmlDocument, "/oembed/height");
var imageTitle = GetXmlProperty(xmlDocument, "/oembed/title");
return string.Format("<img src=\"{0}\" width\"{1}\" height=\"{2}\" alt=\"{3}\" />", imageUrl, imageWidth, imageHeight, HttpUtility.HtmlEncode(imageTitle));
return string.Format("<img src=\"{0}\" width=\"{1}\" height=\"{2}\" alt=\"{3}\" />", imageUrl, imageWidth, imageHeight, HttpUtility.HtmlEncode(imageTitle));
}
}
}

View File

@@ -1,42 +1,47 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Web.Media.Exif
{
/// <summary>
/// The exception that is thrown when the format of the JPEG/Exif file
/// could not be understood.
/// The exception that is thrown when the format of the JPEG/EXIF file could not be understood.
/// </summary>
internal class NotValidExifFileException : Exception
/// <seealso cref="System.Exception" />
[Serializable]
public class NotValidExifFileException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="NotValidExifFileException" /> class.
/// </summary>
public NotValidExifFileException()
: base("Not a valid JPEG/Exif file.")
{
;
}
: base("Not a valid JPEG/EXIF file.")
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidExifFileException" /> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public NotValidExifFileException(string message)
: base(message)
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidExifFileException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public NotValidExifFileException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidExifFileException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected NotValidExifFileException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
/// <summary>
/// The exception that is thrown when an invalid enum type is given to an
/// ExifEnumProperty.
/// </summary>
internal class UnknownEnumTypeException : Exception
{
public UnknownEnumTypeException()
: base("Unknown enum type.")
{
;
}
public UnknownEnumTypeException(string message)
: base(message)
{
;
}
}
}

View File

@@ -64,7 +64,7 @@ namespace Umbraco.Web.Media.Exif
return new ExifInterOperability(tagid, 3, 1, ExifBitConverter.GetBytes((ushort)((object)mValue), BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder));
}
else
throw new UnknownEnumTypeException();
throw new InvalidOperationException($"An invalid enum type ({basetype.FullName}) was provided for type {type.FullName}");
}
}
}

View File

@@ -1,99 +1,171 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Web.Media.Exif
{
/// <summary>
/// The exception that is thrown when the format of the image file
/// could not be understood.
/// </summary>
internal class NotValidImageFileException : Exception
{
public NotValidImageFileException()
: base("Not a valid image file.")
{
;
}
public NotValidImageFileException(string message)
: base(message)
{
;
}
}
/// <summary>
/// The exception that is thrown when the format of the JPEG file
/// could not be understood.
/// The exception that is thrown when the format of the JPEG file could not be understood.
/// </summary>
internal class NotValidJPEGFileException : Exception
/// <seealso cref="System.Exception" />
[Serializable]
public class NotValidJPEGFileException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="NotValidJPEGFileException" /> class.
/// </summary>
public NotValidJPEGFileException()
: base("Not a valid JPEG file.")
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidJPEGFileException" /> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public NotValidJPEGFileException(string message)
: base(message)
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidJPEGFileException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public NotValidJPEGFileException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidJPEGFileException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected NotValidJPEGFileException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
/// <summary>
/// The exception that is thrown when the format of the TIFF file
/// could not be understood.
/// The exception that is thrown when the format of the TIFF file could not be understood.
/// </summary>
internal class NotValidTIFFileException : Exception
/// <seealso cref="System.Exception" />
[Serializable]
public class NotValidTIFFileException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="NotValidTIFFileException" /> class.
/// </summary>
public NotValidTIFFileException()
: base("Not a valid TIFF file.")
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidTIFFileException" /> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public NotValidTIFFileException(string message)
: base(message)
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidTIFFileException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public NotValidTIFFileException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidTIFFileException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected NotValidTIFFileException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
/// <summary>
/// The exception that is thrown when the format of the TIFF header
/// could not be understood.
/// The exception that is thrown when the format of the TIFF header could not be understood.
/// </summary>
/// <seealso cref="System.Exception" />
[Serializable]
internal class NotValidTIFFHeader : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="NotValidTIFFHeader" /> class.
/// </summary>
public NotValidTIFFHeader()
: base("Not a valid TIFF header.")
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidTIFFHeader" /> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public NotValidTIFFHeader(string message)
: base(message)
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidTIFFHeader" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public NotValidTIFFHeader(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="NotValidTIFFHeader" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected NotValidTIFFHeader(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
/// <summary>
/// The exception that is thrown when the length of a section exceeds 64 kB.
/// </summary>
internal class SectionExceeds64KBException : Exception
/// <seealso cref="System.Exception" />
[Serializable]
public class SectionExceeds64KBException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="SectionExceeds64KBException" /> class.
/// </summary>
public SectionExceeds64KBException()
: base("Section length exceeds 64 kB.")
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="SectionExceeds64KBException" /> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public SectionExceeds64KBException(string message)
: base(message)
{
;
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="SectionExceeds64KBException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public SectionExceeds64KBException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="SectionExceeds64KBException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected SectionExceeds64KBException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Web.Media.TypeDetector
{
document = XDocument.Load(fileStream);
}
catch (System.Exception ex)
catch (System.Exception)
{
return false;
}

View File

@@ -73,7 +73,7 @@ namespace Umbraco.Web
}
/// <summary>
/// Returns a list of cultures that have property validation errors errors
/// Returns a list of cultures that have property validation errors
/// </summary>
/// <param name="modelState"></param>
/// <param name="localizationService"></param>

View File

@@ -16,20 +16,32 @@ namespace Umbraco.Web.Models
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
[DataMember(Name = "group")]
public string Group { get; set; }
[DataMember(Name = "groupOrder")]
public int GroupOrder { get; set; }
[DataMember(Name = "hidden")]
public bool Hidden { get; set; }
[DataMember(Name = "allowDisable")]
public bool AllowDisable { get; set; }
[DataMember(Name = "requiredSections")]
public List<string> RequiredSections { get; set; }
[DataMember(Name = "steps")]
public BackOfficeTourStep[] Steps { get; set; }
[DataMember(Name = "culture")]
public string Culture { get; set; }
[DataMember(Name = "contentType")]
public string ContentType { get; set; }
}
}

View File

@@ -49,5 +49,11 @@ namespace Umbraco.Web.Models
/// </summary>
[DataMember(Name = "generatedPassword")]
public string GeneratedPassword { get; set; }
/// <summary>
/// The id of the user - required to allow changing password without the entire UserSave model
/// </summary>
[DataMember(Name = "id")]
public int Id { get; set; }
}
}

View File

@@ -53,11 +53,21 @@ namespace Umbraco.Web.Models.ContentEditing
[ReadOnly(true)]
public string Culture { get; set; }
/// <summary>
/// The segment of the property
/// </summary>
/// <remarks>
/// The segment value of a property can always be null but can only have a non-null value
/// when the property can be varied by segment.
/// </remarks>
[DataMember(Name = "segment")]
[ReadOnly(true)]
public string Segment { get; set; }
/// <summary>
/// Used internally during model mapping
/// </summary>
[IgnoreDataMember]
internal IDataEditor PropertyEditor { get; set; }
}
}

View File

@@ -11,10 +11,17 @@ namespace Umbraco.Web.Models.ContentEditing
internal class ContentPropertyDto : ContentPropertyBasic
{
public IDataType DataType { get; set; }
public string Label { get; set; }
public string Description { get; set; }
public bool IsRequired { get; set; }
public string IsRequiredMessage { get; set; }
public string ValidationRegExp { get; set; }
public string ValidationRegExpMessage { get; set; }
}
}

View File

@@ -29,6 +29,12 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "culture")]
public string Culture { get; set; }
/// <summary>
/// The segment of this variant, if this is invariant than this is null or empty
/// </summary>
[DataMember(Name = "segment")]
public string Segment { get; set; }
/// <summary>
/// Indicates if the variant should be updated
/// </summary>

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models.ContentEditing
{
[DataContract(Name = "dataTypeReferences", Namespace = "")]
public class DataTypeReferences
{
[DataMember(Name = "documentTypes")]
public IEnumerable<ContentTypeReferences> DocumentTypes { get; set; } = Enumerable.Empty<ContentTypeReferences>();
[DataMember(Name = "mediaTypes")]
public IEnumerable<ContentTypeReferences> MediaTypes { get; set; } = Enumerable.Empty<ContentTypeReferences>();
[DataMember(Name = "memberTypes")]
public IEnumerable<ContentTypeReferences> MemberTypes { get; set; } = Enumerable.Empty<ContentTypeReferences>();
[DataContract(Name = "contentType", Namespace = "")]
public class ContentTypeReferences : EntityBasic
{
[DataMember(Name = "properties")]
public object Properties { get; set; }
[DataContract(Name = "property", Namespace = "")]
public class PropertyTypeReferences
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
}
}
}
}

View File

@@ -16,5 +16,10 @@
/// along with any content types that have matching property types that are included in the filtered content types
/// </summary>
public string[] FilterContentTypes { get; set; }
/// <summary>
/// Wether the content type is currently marked as an element type
/// </summary>
public bool IsElement { get; set; }
}
}

View File

@@ -5,6 +5,7 @@ namespace Umbraco.Web.Models.ContentEditing
[DataContract(Name = "contentType", Namespace = "")]
public class MediaTypeDisplay : ContentTypeCompositionDisplay<PropertyTypeDisplay>
{
[DataMember(Name = "isSystemMediaType")]
public bool IsSystemMediaType { get; set; }
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.Models.ContentEditing
@@ -15,6 +16,7 @@ namespace Umbraco.Web.Models.ContentEditing
public MemberDisplay()
{
MemberProviderFieldMapping = new Dictionary<string, string>();
ContentApps = new List<ContentApp>();
}
[DataMember(Name = "username")]
@@ -34,5 +36,7 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "fieldConfig")]
public IDictionary<string, string> MemberProviderFieldMapping { get; set; }
[DataMember(Name = "apps")]
public IEnumerable<ContentApp> ContentApps { get; set; }
}
}

View File

@@ -11,7 +11,13 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "mandatory")]
public bool Mandatory { get; set; }
[DataMember(Name = "mandatoryMessage")]
public string MandatoryMessage { get; set; }
[DataMember(Name = "pattern")]
public string Pattern { get; set; }
[DataMember(Name = "patternMessage")]
public string PatternMessage { get; set; }
}
}

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Web.Models.ContentEditing
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember(Name = "parentObjectType", IsRequired = true)]
public Guid ParentObjectType { get; set; }
public Guid? ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Parent's object type name.
@@ -38,7 +38,7 @@ namespace Umbraco.Web.Models.ContentEditing
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember(Name = "childObjectType", IsRequired = true)]
public Guid ChildObjectType { get; set; }
public Guid? ChildObjectType { get; set; }
/// <summary>
/// Gets or sets the Child's object type name.
@@ -47,13 +47,6 @@ namespace Umbraco.Web.Models.ContentEditing
[ReadOnly(true)]
public string ChildObjectTypeName { get; set; }
/// <summary>
/// Gets or sets the relations associated with this relation type.
/// </summary>
[DataMember(Name = "relations")]
[ReadOnly(true)]
public IEnumerable<RelationDisplay> Relations { get; set; }
/// <summary>
/// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
/// </summary>

View File

@@ -16,12 +16,12 @@ namespace Umbraco.Web.Models.ContentEditing
/// Gets or sets the parent object type ID.
/// </summary>
[DataMember(Name = "parentObjectType", IsRequired = false)]
public Guid ParentObjectType { get; set; }
public Guid? ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the child object type ID.
/// </summary>
[DataMember(Name = "childObjectType", IsRequired = false)]
public Guid ChildObjectType { get; set; }
public Guid? ChildObjectType { get; set; }
}
}

View File

@@ -33,5 +33,11 @@ namespace Umbraco.Web.Models.ContentEditing
/// </summary>
[DataMember(Name = "userCount")]
public int UserCount { get; set; }
/// <summary>
/// Is the user group a system group e.g. "Administrators", "Sensitive data" or "Translators"
/// </summary>
[DataMember(Name = "isSystemUserGroup")]
public bool IsSystemUserGroup { get; set; }
}
}

View File

@@ -70,8 +70,13 @@ namespace Umbraco.Web.Models.Mapping
dest.Culture = culture;
// Get the segment, which is always allowed to be null even if the propertyType *can* be varied by segment.
// There is therefore no need to perform the null check like with culture above.
var segment = !property.PropertyType.VariesBySegment() ? null : context.GetSegment();
dest.Segment = segment;
// if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return.
dest.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture);
dest.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture, segment);
}
}
}

View File

@@ -42,7 +42,9 @@ namespace Umbraco.Web.Models.Mapping
//add the validation information
dest.Validation.Mandatory = originalProp.PropertyType.Mandatory;
dest.Validation.MandatoryMessage = originalProp.PropertyType.MandatoryMessage;
dest.Validation.Pattern = originalProp.PropertyType.ValidationRegExp;
dest.Validation.PatternMessage = originalProp.PropertyType.ValidationRegExpMessage;
if (dest.PropertyEditor == null)
{

View File

@@ -21,7 +21,9 @@ namespace Umbraco.Web.Models.Mapping
base.Map(property, dest, context);
dest.IsRequired = property.PropertyType.Mandatory;
dest.IsRequiredMessage = property.PropertyType.MandatoryMessage;
dest.ValidationRegExp = property.PropertyType.ValidationRegExp;
dest.ValidationRegExpMessage = property.PropertyType.ValidationRegExpMessage;
dest.Description = property.PropertyType.Description;
dest.Label = property.PropertyType.Name;
dest.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId);

View File

@@ -8,6 +8,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Core.Services;
using Umbraco.Core.Exceptions;
namespace Umbraco.Web.Models.Mapping
{
@@ -144,6 +145,7 @@ namespace Umbraco.Web.Models.Mapping
//default listview
target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media";
target.IsSystemMediaType = source.IsSystemMediaType();
if (string.IsNullOrEmpty(source.Name)) return;
@@ -214,16 +216,18 @@ namespace Umbraco.Web.Models.Mapping
}
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate
// Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType
// Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations
private static void Map(PropertyTypeBasic source, PropertyType target, MapperContext context)
{
target.Name = source.Label;
target.DataTypeId = source.DataTypeId;
target.DataTypeKey = source.DataTypeKey;
target.Mandatory = source.Validation.Mandatory;
target.MandatoryMessage = source.Validation.MandatoryMessage;
target.ValidationRegExp = source.Validation.Pattern;
target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing;
target.ValidationRegExpMessage = source.Validation.PatternMessage;
target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant);
if (source.Id > 0)
target.Id = source.Id;
@@ -364,7 +368,7 @@ namespace Umbraco.Web.Models.Mapping
target.Validation = source.Validation;
}
// Umbraco.Code.MapAll -CreatorId -Level -SortOrder
// Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
// Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType)
private static void MapSaveToTypeBase<TSource, TSourcePropertyType>(TSource source, IContentTypeComposition target, MapperContext context)
@@ -394,9 +398,7 @@ namespace Umbraco.Web.Models.Mapping
if (!(target is IMemberType))
{
target.Variations = ContentVariation.Nothing;
if (source.AllowCultureVariant)
target.Variations |= ContentVariation.Culture;
target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant);
}
// handle property groups and property types
@@ -487,7 +489,7 @@ namespace Umbraco.Web.Models.Mapping
target.Udi = MapContentTypeUdi(source);
target.UpdateDate = source.UpdateDate;
target.AllowedContentTypes = source.AllowedContentTypes.Select(x => x.Id.Value);
target.AllowedContentTypes = source.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value);
target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias);
target.LockedCompositeContentTypes = MapLockedCompositions(source);
}
@@ -577,7 +579,7 @@ namespace Umbraco.Web.Models.Mapping
udiType = Constants.UdiEntityType.DocumentType;
break;
default:
throw new Exception("panic");
throw new PanicException($"Source is of type {source.GetType()} which isn't supported here");
}
return Udi.Create(udiType, source.Key);

View File

@@ -21,52 +21,117 @@ namespace Umbraco.Web.Models.Mapping
public IEnumerable<ContentVariantDisplay> Map(IContent source, MapperContext context)
{
var result = new List<ContentVariantDisplay>();
if (!source.ContentType.VariesByCulture())
var variesByCulture = source.ContentType.VariesByCulture();
var variesBySegment = source.ContentType.VariesBySegment();
IList<ContentVariantDisplay> variants = new List<ContentVariantDisplay>();
if (!variesByCulture && !variesBySegment)
{
//this is invariant so just map the IContent instance to ContentVariationDisplay
result.Add(context.Map<ContentVariantDisplay>(source));
// this is invariant so just map the IContent instance to ContentVariationDisplay
var variantDisplay = context.Map<ContentVariantDisplay>(source);
variants.Add(variantDisplay);
}
else if (variesByCulture && !variesBySegment)
{
var languages = GetLanguages(context);
variants = languages
.Select(language => CreateVariantDisplay(context, source, language, null))
.ToList();
}
else if (variesBySegment && !variesByCulture)
{
// Segment only
var segments = GetSegments(source);
variants = segments
.Select(segment => CreateVariantDisplay(context, source, null, segment))
.ToList();
}
else
{
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
if (allLanguages.Count == 0) return Enumerable.Empty<ContentVariantDisplay>(); //this should never happen
// Culture and segment
var languages = GetLanguages(context).ToList();
var segments = GetSegments(source).ToList();
var langs = context.MapEnumerable<ILanguage, Language>(allLanguages).ToList();
//create a variant for each language, then we'll populate the values
var variants = langs.Select(x =>
if (languages.Count == 0 || segments.Count == 0)
{
//We need to set the culture in the mapping context since this is needed to ensure that the correct property values
//are resolved during the mapping
context.SetCulture(x.IsoCode);
return context.Map<ContentVariantDisplay>(source);
}).ToList();
for (int i = 0; i < langs.Count; i++)
{
var x = langs[i];
var variant = variants[i];
variant.Language = x;
variant.Name = source.GetCultureName(x.IsoCode);
// This should not happen
throw new InvalidOperationException("No languages or segments available");
}
//Put the default language first in the list & then sort rest by a-z
var defaultLang = variants.SingleOrDefault(x => x.Language.IsDefault);
variants = languages
.SelectMany(language => segments
.Select(segment => CreateVariantDisplay(context, source, language, segment)))
.ToList();
}
//Remove the default language from the list for now
variants.Remove(defaultLang);
//Sort the remaining languages a-z
variants = variants.OrderBy(x => x.Language.Name).ToList();
//Insert the default language as the first item
variants.Insert(0, defaultLang);
return SortVariants(variants);
}
private IList<ContentVariantDisplay> SortVariants(IList<ContentVariantDisplay> variants)
{
if (variants == null || variants.Count <= 1)
{
return variants;
}
return result;
// Default variant first, then order by language, segment.
return variants
.OrderBy(v => IsDefaultLanguage(v) ? 0 : 1)
.ThenBy(v => IsDefaultSegment(v) ? 0 : 1)
.ThenBy(v => v?.Language?.Name)
.ThenBy(v => v.Segment)
.ToList();
}
private static bool IsDefaultSegment(ContentVariantDisplay variant)
{
return variant.Segment == null;
}
private static bool IsDefaultLanguage(ContentVariantDisplay variant)
{
return variant.Language == null || variant.Language.IsDefault;
}
private IEnumerable<Language> GetLanguages(MapperContext context)
{
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
if (allLanguages.Count == 0)
{
// This should never happen
return Enumerable.Empty<Language>();
}
else
{
return context.MapEnumerable<ILanguage, Language>(allLanguages).ToList();
}
}
/// <summary>
/// Returns all segments assigned to the content
/// </summary>
/// <param name="content"></param>
/// <returns>
/// Returns all segments assigned to the content including 'null' values
/// </returns>
private IEnumerable<string> GetSegments(IContent content)
{
return content.Properties.SelectMany(p => p.Values.Select(v => v.Segment)).Distinct();
}
private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, Language language, string segment)
{
context.SetCulture(language?.IsoCode);
context.SetSegment(segment);
var variantDisplay = context.Map<ContentVariantDisplay>(content);
variantDisplay.Segment = segment;
variantDisplay.Language = language;
variantDisplay.Name = content.GetCultureName(language?.IsoCode);
return variantDisplay;
}
}
}

View File

@@ -42,14 +42,20 @@ namespace Umbraco.Web.Models.Mapping
target.Trashed = source.Trashed;
target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key);
if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace())
target.Icon = Constants.Icons.Member;
if (source is IContentEntitySlim contentSlim)
{
source.AdditionalData["ContentTypeAlias"] = contentSlim.ContentTypeAlias;
}
if (source is IDocumentEntitySlim documentSlim)
{
source.AdditionalData["IsPublished"] = documentSlim.Published;
}
if (source is IMediaEntitySlim mediaSlim)
{
source.AdditionalData["MediaPath"] = mediaSlim.MediaPath;
}
// NOTE: we're mapping the objects in AdditionalData by object reference here.
// it works fine for now, but it's something to keep in mind in the future
@@ -171,6 +177,14 @@ namespace Umbraco.Web.Models.Mapping
target.Name = source.Values.ContainsKey("nodeName") ? source.Values["nodeName"] : "[no name]";
if (source.Values.TryGetValue(UmbracoExamineIndex.UmbracoFileFieldName, out var umbracoFile))
{
if (umbracoFile != null)
{
target.Name = $"{target.Name} ({umbracoFile})";
}
}
if (source.Values.ContainsKey(UmbracoExamineIndex.NodeKeyFieldName))
{
if (Guid.TryParse(source.Values[UmbracoExamineIndex.NodeKeyFieldName], out var key))
@@ -217,7 +231,18 @@ namespace Umbraco.Web.Models.Mapping
}
private static string MapContentTypeIcon(IEntitySlim entity)
=> entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null;
{
switch (entity)
{
case IMemberEntitySlim memberEntity:
return memberEntity.ContentTypeIcon.IfNullOrWhiteSpace(Constants.Icons.Member);
case IContentEntitySlim contentEntity:
// NOTE: this case covers both content and media entities
return contentEntity.ContentTypeIcon;
}
return null;
}
private static string MapName(IEntitySlim source, MapperContext context)
{

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Logging;
@@ -23,6 +24,7 @@ namespace Umbraco.Web.Models.Mapping
public void DefineMaps(UmbracoMapper mapper)
{
mapper.Define<IMacro, EntityBasic>((source, context) => new EntityBasic(), Map);
mapper.Define<IMacro, MacroDisplay>((source, context) => new MacroDisplay(), Map);
mapper.Define<IMacro, IEnumerable<MacroParameter>>((source, context) => context.MapEnumerable<IMacroProperty, MacroParameter>(source.Properties.Values));
mapper.Define<IMacroProperty, MacroParameter>((source, context) => new MacroParameter(), Map);
}
@@ -40,6 +42,23 @@ namespace Umbraco.Web.Models.Mapping
target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key);
}
private void Map(IMacro source, MacroDisplay target, MapperContext context)
{
target.Alias = source.Alias;
target.Icon = Constants.Icons.Macro;
target.Id = source.Id;
target.Key = source.Key;
target.Name = source.Name;
target.ParentId = -1;
target.Path = "-1," + source.Id;
target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key);
target.CacheByPage = source.CacheByPage;
target.CacheByUser = source.CacheByMember;
target.CachePeriod = source.CacheDuration;
target.UseInEditor = source.UseInEditor;
target.RenderInEditor = !source.DontRender;
target.View = source.MacroSource;
}
// Umbraco.Code.MapAll -Value
private void Map(IMacroProperty source, MacroParameter target, MapperContext context)
{

View File

@@ -8,6 +8,7 @@ namespace Umbraco.Web.Models.Mapping
internal static class MapperContextExtensions
{
private const string CultureKey = "Map.Culture";
private const string SegmentKey = "Map.Segment";
private const string IncludedPropertiesKey = "Map.IncludedProperties";
/// <summary>
@@ -18,6 +19,14 @@ namespace Umbraco.Web.Models.Mapping
return context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null;
}
/// <summary>
/// Gets the context segment.
/// </summary>
public static string GetSegment(this MapperContext context)
{
return context.HasItems && context.Items.TryGetValue(SegmentKey, out var obj) && obj is string s ? s : null;
}
/// <summary>
/// Sets a context culture.
/// </summary>
@@ -26,6 +35,14 @@ namespace Umbraco.Web.Models.Mapping
context.Items[CultureKey] = culture;
}
/// <summary>
/// Sets a context segment.
/// </summary>
public static void SetSegment(this MapperContext context, string segment)
{
context.Items[SegmentKey] = segment;
}
/// <summary>
/// Get included properties.
/// </summary>
@@ -42,4 +59,4 @@ namespace Umbraco.Web.Models.Mapping
context.Items[IncludedPropertiesKey] = properties;
}
}
}
}

View File

@@ -76,6 +76,7 @@ namespace Umbraco.Web.Models.Mapping
// Umbraco.Code.MapAll -Trashed -IsContainer -VariesByCulture
private void Map(IMember source, MemberDisplay target, MapperContext context)
{
target.ContentApps = _commonMapper.GetContentApps(source);
target.ContentTypeId = source.ContentType.Id;
target.ContentTypeAlias = source.ContentType.Alias;
target.ContentTypeName = source.ContentType.Name;
@@ -144,7 +145,7 @@ namespace Umbraco.Web.Models.Mapping
target.Id = source.Id;
target.Key = source.Key;
target.Name = source.Name;
target.Path = "-1" + source.Id;
target.Path = $"-1,{source.Id}";
target.Udi = Udi.Create(Constants.UdiEntityType.MemberGroup, source.Key);
}

View File

@@ -223,7 +223,13 @@ namespace Umbraco.Web.Models.Mapping
Alias = p.Alias,
Description = p.Description,
Editor = p.PropertyEditorAlias,
Validation = new PropertyTypeValidation {Mandatory = p.Mandatory, Pattern = p.ValidationRegExp},
Validation = new PropertyTypeValidation
{
Mandatory = p.Mandatory,
MandatoryMessage = p.MandatoryMessage,
Pattern = p.ValidationRegExp,
PatternMessage = p.ValidationRegExpMessage,
},
Label = p.Name,
View = propertyEditor.GetValueEditor().View,
Config = config,

View File

@@ -1,12 +1,23 @@
using Umbraco.Core;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
internal class RelationMapDefinition : IMapDefinition
{
private readonly IEntityService _entityService;
private readonly IRelationService _relationService;
public RelationMapDefinition(IEntityService entityService, IRelationService relationService)
{
_entityService = entityService;
_relationService = relationService;
}
public void DefineMaps(UmbracoMapper mapper)
{
mapper.Define<IRelationType, RelationTypeDisplay>((source, context) => new RelationTypeDisplay(), Map);
@@ -15,8 +26,8 @@ namespace Umbraco.Web.Models.Mapping
}
// Umbraco.Code.MapAll -Icon -Trashed -AdditionalData
// Umbraco.Code.MapAll -Relations -ParentId -Notifications
private static void Map(IRelationType source, RelationTypeDisplay target, MapperContext context)
// Umbraco.Code.MapAll -ParentId -Notifications
private void Map(IRelationType source, RelationTypeDisplay target, MapperContext context)
{
target.ChildObjectType = source.ChildObjectType;
target.Id = source.Id;
@@ -28,18 +39,32 @@ namespace Umbraco.Web.Models.Mapping
target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key);
target.Path = "-1," + source.Id;
// Set the "friendly" names for the parent and child object types
target.ParentObjectTypeName = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType).GetFriendlyName();
target.ChildObjectTypeName = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType).GetFriendlyName();
// Set the "friendly" and entity names for the parent and child object types
if (source.ParentObjectType.HasValue)
{
var objType = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType.Value);
target.ParentObjectTypeName = objType.GetFriendlyName();
}
if (source.ChildObjectType.HasValue)
{
var objType = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value);
target.ChildObjectTypeName = objType.GetFriendlyName();
}
}
// Umbraco.Code.MapAll -ParentName -ChildName
private static void Map(IRelation source, RelationDisplay target, MapperContext context)
private void Map(IRelation source, RelationDisplay target, MapperContext context)
{
target.ChildId = source.ChildId;
target.Comment = source.Comment;
target.CreateDate = source.CreateDate;
target.ParentId = source.ParentId;
var entities = _relationService.GetEntitiesFromRelation(source);
target.ParentName = entities.Item1.Name;
target.ChildName = entities.Item2.Name;
}
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate

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