Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 294452a6aa | |||
| fca74ad8d7 | |||
| 7bc7dba86d | |||
| ca75a25802 | |||
| ece31733a9 | |||
| 28a8027179 | |||
| a97604d6c4 | |||
| 308f929f7b | |||
| c8d7df1515 | |||
| 2c795662d2 | |||
| 8a43e3a87e | |||
| 210e43fcb0 | |||
| beb8c7ac7c | |||
| 2f8979bbc3 | |||
| a8ed7f2c17 | |||
| 1c6bf55e5d | |||
| c246044ac7 | |||
| 5126521d37 | |||
| 27223738c6 | |||
| 2faa4defd8 | |||
| bb8356d7db | |||
| 686f6752f3 | |||
| 66ff4d191a | |||
| 21218b0332 | |||
| 23ccdeb2cf | |||
| 6148ce4e52 | |||
| 06850ed25c | |||
| 0320a56cb5 | |||
| 968463912a | |||
| b9e44f381b | |||
| cd27bb210f | |||
| 67f680a675 | |||
| 47c3e3a79f | |||
| 02f49a0039 | |||
| 05c6b856f9 | |||
| b07df63422 | |||
| 487e45b45b | |||
| 929c84822a | |||
| 993f11cd2c | |||
| 6544b5c272 | |||
| 817a458334 | |||
| f3bfc1ffb2 | |||
| 3603090421 | |||
| 6fe3089207 | |||
| 79159b684d | |||
| 26d4d056be | |||
| 6ad28f1103 | |||
| 89cd655df2 | |||
| 14c4c4815d | |||
| bfb69a34ef | |||
| b675f6252c | |||
| d52420183e | |||
| 0cbe0b4796 | |||
| 06b136fdf6 | |||
| c6246b5404 | |||
| 3062f40ef5 | |||
| dcd0d38419 | |||
| 0feb053e51 | |||
| c01a8f8f13 | |||
| 09c8ae4e7e | |||
| da71a94c39 | |||
| f6413af3f4 | |||
| 2aaca865e7 | |||
| 77dda5a70f | |||
| 440e649d6d | |||
| f6a8d487cc | |||
| 47dcd37d1d | |||
| d60fc63e78 | |||
| b0c4042e87 | |||
| da525513a3 | |||
| 2ac5e61e44 | |||
| 101a8b6c78 | |||
| f7a10e3a15 | |||
| 4e0dd728a8 | |||
| 320ccf8105 | |||
| 62f924a757 | |||
| c47a08b553 | |||
| de9241bcf5 | |||
| 7c03fe3c1f | |||
| 82eb241119 | |||
| 1073e6257c | |||
| bc3e4847fe | |||
| 1a7251909c | |||
| 61a18297d9 | |||
| bdd413f9ee | |||
| fd8ca35e63 | |||
| fdd9aee773 | |||
| 59bf24c461 | |||
| 2748c3635d | |||
| 3c2a92d3b6 | |||
| cf49e6160a | |||
| 2252db0d55 | |||
| ec93030e21 | |||
| 1d49c8626e | |||
| 89bb94aa1a | |||
| 03a4b75176 | |||
| 943b54dcf7 | |||
| 39bd18ec1a | |||
| df896af476 | |||
| e93ddff46b | |||
| 3144b51c91 | |||
| e92f68d040 | |||
| 469054e9b3 | |||
| d17022d87d | |||
| 6865629090 | |||
| a4749f201d | |||
| ad90264ef1 | |||
| 9ba7e7742a | |||
| 6473f600c7 | |||
| 78735ff349 | |||
| 4b605b2580 | |||
| 80d7f1b2c9 | |||
| 1af43498d9 | |||
| 6329dfa629 | |||
| 5ada85df29 | |||
| 015ad64e30 | |||
| 2cd0173534 | |||
| 6dbb988903 | |||
| 926acb910e | |||
| a0bcfeddcc | |||
| c4b5483d8c | |||
| 57e3187e3a | |||
| 988d51c4c8 | |||
| 4733256ca1 | |||
| 4133a1fe76 | |||
| 37bf6fe938 | |||
| 31716e574c | |||
| 126c4cbd46 | |||
| 7cc91f71c2 | |||
| 7c52b9602c | |||
| c5f1cc15fd | |||
| d089518681 | |||
| cd6ef35bf9 | |||
| 47cdc79fcb | |||
| f7382255c2 | |||
| 96d5bdd7b2 | |||
| 7557c584d8 |
@@ -12,7 +12,7 @@ thoroughly. Then, proceed by filling out the rest of the details in the issue
|
||||
template below. The more details you can give us, the easier it will be for us
|
||||
to determine the cause of a problem.
|
||||
|
||||
See: https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md
|
||||
See: https://github.com/umbraco/Umbraco-CMS/blob/v8/dev/.github/CONTRIBUTING.md
|
||||
|
||||
-->
|
||||
|
||||
|
||||
+2
-2
@@ -18,5 +18,5 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("8.0.0")]
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("8.1.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.1.0")]
|
||||
[assembly: AssemblyFileVersion("8.2.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.2.0")]
|
||||
|
||||
@@ -67,31 +67,34 @@ namespace Umbraco.Core
|
||||
: new NamedSemaphoreReleaser(_semaphore2);
|
||||
}
|
||||
|
||||
public Task<IDisposable> LockAsync()
|
||||
{
|
||||
var wait = _semaphore != null
|
||||
? _semaphore.WaitAsync()
|
||||
: _semaphore2.WaitOneAsync();
|
||||
//NOTE: We don't use the "Async" part of this lock at all
|
||||
//TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again
|
||||
|
||||
return wait.IsCompleted
|
||||
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
: wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()),
|
||||
this, CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
}
|
||||
//public Task<IDisposable> LockAsync()
|
||||
//{
|
||||
// var wait = _semaphore != null
|
||||
// ? _semaphore.WaitAsync()
|
||||
// : _semaphore2.WaitOneAsync();
|
||||
|
||||
public Task<IDisposable> LockAsync(int millisecondsTimeout)
|
||||
{
|
||||
var wait = _semaphore != null
|
||||
? _semaphore.WaitAsync(millisecondsTimeout)
|
||||
: _semaphore2.WaitOneAsync(millisecondsTimeout);
|
||||
// return wait.IsCompleted
|
||||
// ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
// : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()),
|
||||
// this, CancellationToken.None,
|
||||
// TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
//}
|
||||
|
||||
return wait.IsCompleted
|
||||
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
: wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
|
||||
this, CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
}
|
||||
//public Task<IDisposable> LockAsync(int millisecondsTimeout)
|
||||
//{
|
||||
// var wait = _semaphore != null
|
||||
// ? _semaphore.WaitAsync(millisecondsTimeout)
|
||||
// : _semaphore2.WaitOneAsync(millisecondsTimeout);
|
||||
|
||||
// return wait.IsCompleted
|
||||
// ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
|
||||
// : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()),
|
||||
// this, CancellationToken.None,
|
||||
// TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
//}
|
||||
|
||||
public IDisposable Lock()
|
||||
{
|
||||
|
||||
@@ -14,6 +14,23 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public const string InternalGenericPropertiesPrefix = "_umb_";
|
||||
|
||||
public static class Legacy
|
||||
{
|
||||
public static class Aliases
|
||||
{
|
||||
public const string Textbox = "Umbraco.Textbox";
|
||||
public const string Date = "Umbraco.Date";
|
||||
public const string ContentPicker2 = "Umbraco.ContentPicker2";
|
||||
public const string MediaPicker2 = "Umbraco.MediaPicker2";
|
||||
public const string MemberPicker2 = "Umbraco.MemberPicker2";
|
||||
public const string MultiNodeTreePicker2 = "Umbraco.MultiNodeTreePicker2";
|
||||
public const string TextboxMultiple = "Umbraco.TextboxMultiple";
|
||||
public const string RelatedLinks2 = "Umbraco.RelatedLinks2";
|
||||
public const string RelatedLinks = "Umbraco.RelatedLinks";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines Umbraco built-in property editor aliases.
|
||||
/// </summary>
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Umbraco.Core
|
||||
///</summary>
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
internal static bool IsCollectionEmpty<T>(this IReadOnlyCollection<T> list) => list == null || list.Count == 0;
|
||||
|
||||
internal static bool HasDuplicates<T>(this IEnumerable<T> items, bool includeNull)
|
||||
{
|
||||
var hs = new HashSet<T>();
|
||||
@@ -112,7 +114,6 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if all items in the other collection exist in this collection
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web.Hosting;
|
||||
@@ -113,7 +114,7 @@ namespace Umbraco.Core
|
||||
|
||||
lock (_locko)
|
||||
{
|
||||
_logger.Debug<MainDom>("Signaled {Signaled} ({SignalSource})", _signaled ? "(again)" : string.Empty, source);
|
||||
_logger.Debug<MainDom>("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source);
|
||||
if (_signaled) return;
|
||||
if (_isMainDom == false) return; // probably not needed
|
||||
_signaled = true;
|
||||
@@ -171,6 +172,7 @@ namespace Umbraco.Core
|
||||
// if more than 1 instance reach that point, one will get the lock
|
||||
// and the other one will timeout, which is accepted
|
||||
|
||||
//TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset?
|
||||
_asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds);
|
||||
_isMainDom = true;
|
||||
|
||||
@@ -181,6 +183,9 @@ namespace Umbraco.Core
|
||||
// which is accepted
|
||||
|
||||
_signal.Reset();
|
||||
|
||||
//WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
|
||||
|
||||
_signal.WaitOneAsync()
|
||||
.ContinueWith(_ => OnSignal("signal"));
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}."
|
||||
+ $" Please upgrade first to at least {minVersion}.");
|
||||
|
||||
// Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes,
|
||||
// and we don't want users to workaround my putting in version 7.14.0 them self.
|
||||
if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16))
|
||||
return GetInitState(minVersion);
|
||||
|
||||
// initial state is eg "{init-7.14.0}"
|
||||
return GetInitState(currentVersion);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var sqlDataTypes = Sql()
|
||||
.Select<DataTypeDto>()
|
||||
.From<DataTypeDto>()
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == "Umbraco.RelatedLinks"
|
||||
|| x.EditorAlias == "Umbraco.RelatedLinks2");
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks
|
||||
|| x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2);
|
||||
|
||||
var dataTypes = Database.Fetch<DataTypeDto>(sqlDataTypes);
|
||||
var dataTypeIds = dataTypes.Select(x => x.NodeId).ToList();
|
||||
@@ -50,10 +50,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var properties = Database.Fetch<PropertyDataDto>(sqlPropertyData);
|
||||
|
||||
// Create a Multi URL Picker datatype for the converted RelatedLinks data
|
||||
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var value = property.Value.ToString();
|
||||
var value = property.Value?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
continue;
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
Name = relatedLink.Caption,
|
||||
Target = relatedLink.NewWindow ? "_blank" : null,
|
||||
Udi = udi,
|
||||
// Should only have a URL if it's an external link otherwise it wil be a UDI
|
||||
// Should only have a URL if it's an external link otherwise it wil be a UDI
|
||||
Url = relatedLink.IsInternal == false ? relatedLink.Link : null
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
Database.Update(property);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -18,6 +19,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private static readonly ISet<string> LegacyAliases = new HashSet<string>()
|
||||
{
|
||||
Constants.PropertyEditors.Legacy.Aliases.Date,
|
||||
Constants.PropertyEditors.Legacy.Aliases.Textbox,
|
||||
Constants.PropertyEditors.Legacy.Aliases.ContentPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MediaPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MemberPicker2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2,
|
||||
Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple,
|
||||
Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2,
|
||||
};
|
||||
|
||||
public DataTypeMigration(IMigrationContext context, PreValueMigratorCollection preValueMigrators, PropertyEditorCollection propertyEditors, ILogger logger)
|
||||
: base(context)
|
||||
{
|
||||
@@ -70,16 +83,23 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var newAlias = migrator.GetNewAlias(dataType.EditorAlias);
|
||||
if (newAlias == null)
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {EditorAlias}."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, dataType.EditorAlias);
|
||||
if (!LegacyAliases.Contains(dataType.EditorAlias))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>(
|
||||
"Skipping validation of configuration for data type {NodeId} : {EditorAlias}."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, dataType.EditorAlias);
|
||||
}
|
||||
}
|
||||
else if (!_propertyEditors.TryGet(newAlias, out var propertyEditor))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})"
|
||||
+ " because no property editor with that alias was found."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, newAlias, dataType.EditorAlias);
|
||||
if (!LegacyAliases.Contains(newAlias))
|
||||
{
|
||||
_logger.Warn<DataTypeMigration>("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})"
|
||||
+ " because no property editor with that alias was found."
|
||||
+ " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.",
|
||||
dataType.NodeId, newAlias, dataType.EditorAlias);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
class ContentPickerPreValueMigrator : DefaultPreValueMigrator
|
||||
{
|
||||
public override bool CanMigrate(string editorAlias)
|
||||
=> editorAlias == "Umbraco.ContentPicker2";
|
||||
=> editorAlias == Constants.PropertyEditors.Legacy.Aliases.ContentPicker2;
|
||||
|
||||
public override string GetNewAlias(string editorAlias)
|
||||
=> null;
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
class DropDownFlexiblePreValueMigrator : IPreValueMigrator
|
||||
{
|
||||
public bool CanMigrate(string editorAlias)
|
||||
=> editorAlias == "Umbraco.DropDown.Flexible";
|
||||
|
||||
public virtual string GetNewAlias(string editorAlias)
|
||||
=> null;
|
||||
|
||||
public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary<string, PreValueDto> preValues)
|
||||
{
|
||||
var config = new DropDownFlexibleConfiguration();
|
||||
foreach (var preValue in preValues.Values)
|
||||
{
|
||||
if (preValue.Alias == "multiple")
|
||||
{
|
||||
config.Multiple = (preValue.Value == "1");
|
||||
}
|
||||
else
|
||||
{
|
||||
config.Items.Add(new ValueListConfiguration.ValueListItem { Id = preValue.Id, Value = preValue.Value });
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,15 +6,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
{
|
||||
private readonly string[] _editors =
|
||||
{
|
||||
"Umbraco.MediaPicker2",
|
||||
"Umbraco.MediaPicker"
|
||||
Constants.PropertyEditors.Legacy.Aliases.MediaPicker2,
|
||||
Constants.PropertyEditors.Aliases.MediaPicker
|
||||
};
|
||||
|
||||
public override bool CanMigrate(string editorAlias)
|
||||
=> _editors.Contains(editorAlias);
|
||||
|
||||
public override string GetNewAlias(string editorAlias)
|
||||
=> "Umbraco.MediaPicker";
|
||||
=> Constants.PropertyEditors.Aliases.MediaPicker;
|
||||
|
||||
// you wish - but MediaPickerConfiguration lives in Umbraco.Web
|
||||
/*
|
||||
|
||||
@@ -19,6 +19,7 @@ public class PreValueMigratorComposer : ICoreComposer
|
||||
.Append<NestedContentPreValueMigrator>()
|
||||
.Append<DecimalPreValueMigrator>()
|
||||
.Append<ListViewPreValueMigrator>()
|
||||
.Append<DropDownFlexiblePreValueMigrator>()
|
||||
.Append<ValueListPreValueMigrator>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
|
||||
private readonly string[] _editors =
|
||||
{
|
||||
"Umbraco.RadioButtonList",
|
||||
"Umbraco.CheckBoxList",
|
||||
"Umbraco.DropDown",
|
||||
"Umbraco.DropdownlistPublishingKeys",
|
||||
"Umbraco.DropDownMultiple",
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
var dataTypes = GetDataTypes("Umbraco.Date");
|
||||
var dataTypes = GetDataTypes(Constants.PropertyEditors.Legacy.Aliases.Date);
|
||||
|
||||
foreach (var dataType in dataTypes)
|
||||
{
|
||||
@@ -25,6 +25,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
config = (DateTimeConfiguration) new CustomDateTimeConfigurationEditor().FromDatabase(
|
||||
dataType.Configuration);
|
||||
|
||||
// If the Umbraco.Date type is the default from V7 and it has never been updated, then the
|
||||
// configuration is empty, and the format stuff is handled by in JS by moment.js. - We can't do that
|
||||
// after the migration, so we force the format to the default from V7.
|
||||
if (string.IsNullOrEmpty(dataType.Configuration))
|
||||
{
|
||||
config.Format = "YYYY-MM-DD";
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.ContentPicker + "2", Constants.PropertyEditors.Aliases.ContentPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MediaPicker + "2", Constants.PropertyEditors.Aliases.MediaPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MemberPicker + "2", Constants.PropertyEditors.Aliases.MemberPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Aliases.MultiNodeTreePicker + "2", Constants.PropertyEditors.Aliases.MultiNodeTreePicker);
|
||||
RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false);
|
||||
RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.ContentPicker2, Constants.PropertyEditors.Aliases.ContentPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, Constants.PropertyEditors.Aliases.MediaPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MemberPicker2, Constants.PropertyEditors.Aliases.MemberPicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2, Constants.PropertyEditors.Aliases.MultiNodeTreePicker);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple, Constants.PropertyEditors.Aliases.TextArea, false);
|
||||
RenameDataType(Constants.PropertyEditors.Legacy.Aliases.Textbox, Constants.PropertyEditors.Aliases.TextBox, false);
|
||||
}
|
||||
|
||||
private void RenameDataType(string fromAlias, string toAlias, bool checkCollision = true)
|
||||
@@ -30,7 +30,20 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
.Where<DataTypeDto>(x => x.EditorAlias == toAlias));
|
||||
|
||||
if (oldCount > 0)
|
||||
throw new InvalidOperationException($"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used.");
|
||||
{
|
||||
// If we throw it means that the upgrade will exit and cannot continue.
|
||||
// This will occur if a v7 site has the old "Obsolete" property editors that are already named with the `toAlias` name.
|
||||
// TODO: We should have an additional upgrade step when going from 7 -> 8 like we did with 6 -> 7 that shows a compatibility report,
|
||||
// this would include this check and then we can provide users with information on what they should do (i.e. before upgrading to v8 they will
|
||||
// need to migrate these old obsolete editors to non-obsolete editors)
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used." +
|
||||
$"This is generally because when upgrading from a v7 to v8 site, the v7 site contains Data Types that reference old and already Obsolete " +
|
||||
$"Property Editors. Before upgrading to v8, any Data Types using property editors that are named with the prefix '(Obsolete)' must be migrated " +
|
||||
$"to the non-obsolete v7 property editors of the same type.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Database.Execute(Sql()
|
||||
|
||||
@@ -14,11 +14,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
public override void Migrate()
|
||||
{
|
||||
//Get anything currently scheduled
|
||||
var scheduleSql = new Sql()
|
||||
.Select("nodeId", "releaseDate", "expireDate")
|
||||
var releaseSql = new Sql()
|
||||
.Select("nodeId", "releaseDate")
|
||||
.From("umbracoDocument")
|
||||
.Where("releaseDate IS NOT NULL OR expireDate IS NOT NULL");
|
||||
var schedules = Database.Dictionary<int, (DateTime? releaseDate, DateTime? expireDate)> (scheduleSql);
|
||||
.Where("releaseDate IS NOT NULL");
|
||||
var releases = Database.Dictionary<int, DateTime> (releaseSql);
|
||||
|
||||
var expireSql = new Sql()
|
||||
.Select("nodeId", "expireDate")
|
||||
.From("umbracoDocument")
|
||||
.Where("expireDate IS NOT NULL");
|
||||
var expires = Database.Dictionary<int, DateTime>(expireSql);
|
||||
|
||||
|
||||
//drop old cols
|
||||
Delete.Column("releaseDate").FromTable("umbracoDocument").Do();
|
||||
@@ -27,20 +34,25 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
Create.Table<ContentScheduleDto>(true).Do();
|
||||
|
||||
//migrate the schedule
|
||||
foreach(var s in schedules)
|
||||
foreach(var s in releases)
|
||||
{
|
||||
var date = s.Value.releaseDate;
|
||||
var date = s.Value;
|
||||
var action = ContentScheduleAction.Release.ToString();
|
||||
if (!date.HasValue)
|
||||
{
|
||||
date = s.Value.expireDate;
|
||||
action = ContentScheduleAction.Expire.ToString();
|
||||
}
|
||||
|
||||
Insert.IntoTable(ContentScheduleDto.TableName)
|
||||
.Row(new { nodeId = s.Key, date = date.Value, action = action })
|
||||
.Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action })
|
||||
.Do();
|
||||
}
|
||||
|
||||
foreach (var s in expires)
|
||||
{
|
||||
var date = s.Value;
|
||||
var action = ContentScheduleAction.Expire.ToString();
|
||||
|
||||
Insert.IntoTable(ContentScheduleDto.TableName)
|
||||
.Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action })
|
||||
.Do();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-8
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
@@ -36,6 +37,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
|
||||
var properties = Database.Fetch<PropertyDataDto>(sqlPropertyData);
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var value = property.TextValue;
|
||||
@@ -43,19 +45,34 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
|
||||
if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid)
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject<JObject>(value);
|
||||
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
|
||||
|
||||
foreach (var control in allControls.SelectMany(c => c))
|
||||
try
|
||||
{
|
||||
var controlValue = control["value"];
|
||||
if (controlValue.Type == JTokenType.String)
|
||||
var obj = JsonConvert.DeserializeObject<JObject>(value);
|
||||
var allControls = obj.SelectTokens("$.sections..rows..areas..controls");
|
||||
|
||||
foreach (var control in allControls.SelectMany(c => c))
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
var controlValue = control["value"];
|
||||
if (controlValue.Type == JTokenType.String)
|
||||
{
|
||||
control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value<string>());
|
||||
}
|
||||
}
|
||||
|
||||
property.TextValue = JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
exceptions.Add(new InvalidOperationException(
|
||||
"Cannot deserialize the value as json. This can be because the property editor " +
|
||||
"type is changed from another type into a grid. Old versions of the value in this " +
|
||||
"property can have the structure from the old property editor type. This needs to be " +
|
||||
"changed manually before updating the database.\n" +
|
||||
$"Property info: Id = {property.Id}, LanguageId = {property.LanguageId}, VersionId = {property.VersionId}, Value = {property.Value}"
|
||||
, e));
|
||||
continue;
|
||||
}
|
||||
|
||||
property.TextValue = JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -65,6 +82,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0
|
||||
Database.Update(property);
|
||||
}
|
||||
|
||||
|
||||
if (exceptions.Any())
|
||||
{
|
||||
throw new AggregateException("One or more errors related to unexpected data in grid values occurred.", exceptions);
|
||||
}
|
||||
|
||||
Context.AddPostMigration<RebuildPublishedSnapshot>();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Umbraco.Core.Models
|
||||
private string _alias;
|
||||
private string _description;
|
||||
private int _dataTypeId;
|
||||
private Guid _dataTypeKey;
|
||||
private Lazy<int> _propertyGroupId;
|
||||
private string _propertyEditorAlias;
|
||||
private ValueStorageType _valueStorageType;
|
||||
@@ -113,7 +114,7 @@ namespace Umbraco.Core.Models
|
||||
/// Gets of sets the alias of the property type.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Alias
|
||||
public virtual string Alias
|
||||
{
|
||||
get => _alias;
|
||||
set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, nameof(Alias));
|
||||
@@ -139,6 +140,13 @@ namespace Umbraco.Core.Models
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, nameof(DataTypeId));
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public Guid DataTypeKey
|
||||
{
|
||||
get => _dataTypeKey;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _dataTypeKey, nameof(DataTypeKey));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias of the property editor for this property type.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
/// <summary>
|
||||
/// Refreshes the factory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This will typically re-compiled models/classes into a new DLL that are used to populate the cache.</para>
|
||||
/// <para>This is called prior to refreshing the cache.</para>
|
||||
/// </remarks>
|
||||
void Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
propertyType.Alias = typeDto.Alias;
|
||||
propertyType.DataTypeId = typeDto.DataTypeId;
|
||||
propertyType.DataTypeKey = typeDto.DataTypeDto.NodeDto.UniqueId;
|
||||
propertyType.Description = typeDto.Description;
|
||||
propertyType.Id = typeDto.Id;
|
||||
propertyType.Key = typeDto.UniqueId;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Scoping;
|
||||
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
@@ -188,10 +189,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var groupDtos = Database.Fetch<PropertyTypeGroupDto>(sql1);
|
||||
|
||||
var sql2 = Sql()
|
||||
.Select<PropertyTypeDto>(r => r.Select(x => x.DataTypeDto))
|
||||
.Select<PropertyTypeDto>(r => r.Select(x => x.DataTypeDto, r1 => r1.Select(x => x.NodeDto)))
|
||||
.AndSelect<MemberPropertyTypeDto>()
|
||||
.From<PropertyTypeDto>()
|
||||
.InnerJoin<DataTypeDto>().On<PropertyTypeDto, DataTypeDto>((pt, dt) => pt.DataTypeId == dt.NodeId)
|
||||
.InnerJoin<NodeDto>().On<DataTypeDto, NodeDto>((dt, n) => dt.NodeId == n.NodeId)
|
||||
.InnerJoin<ContentTypeDto>().On<PropertyTypeDto, ContentTypeDto>((pt, ct) => pt.ContentTypeId == ct.NodeId)
|
||||
.LeftJoin<PropertyTypeGroupDto>().On<PropertyTypeDto, PropertyTypeGroupDto>((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id)
|
||||
.LeftJoin<MemberPropertyTypeDto>().On<PropertyTypeDto, MemberPropertyTypeDto>((pt, mpt) => pt.Id == mpt.PropertyTypeId)
|
||||
@@ -290,6 +292,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
Description = dto.Description,
|
||||
DataTypeId = dto.DataTypeId,
|
||||
DataTypeKey = dto.DataTypeDto.NodeDto.UniqueId,
|
||||
Id = dto.Id,
|
||||
Key = dto.UniqueId,
|
||||
Mandatory = dto.Mandatory,
|
||||
|
||||
@@ -1013,8 +1013,9 @@ AND umbracoNode.id <> @id",
|
||||
if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
var sql = Sql()
|
||||
.SelectAll()
|
||||
.Select<DataTypeDto>(dt => dt.Select(x => x.NodeDto))
|
||||
.From<DataTypeDto>()
|
||||
.InnerJoin<NodeDto>().On<DataTypeDto, NodeDto>((dt, n) => dt.NodeId == n.NodeId)
|
||||
.Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias })
|
||||
.OrderBy<DataTypeDto>(typeDto => typeDto.NodeId);
|
||||
var datatype = Database.FirstOrDefault<DataTypeDto>(sql);
|
||||
@@ -1022,6 +1023,7 @@ AND umbracoNode.id <> @id",
|
||||
if (datatype != null)
|
||||
{
|
||||
propertyType.DataTypeId = datatype.NodeId;
|
||||
propertyType.DataTypeKey = datatype.NodeDto.UniqueId;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -225,6 +225,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
//this reset's its current data type reference which will be re-assigned based on the property editor assigned on the next line
|
||||
propertyType.DataTypeId = 0;
|
||||
propertyType.DataTypeKey = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,18 +103,26 @@ namespace Umbraco.Core
|
||||
/// Gets the children of the content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="culture">The specific culture to get the url children for. If null is used the current culture is used (Default is null).</param>
|
||||
/// <param name="culture">
|
||||
/// The specific culture to get the url children for. Default is null which will use the current culture in <see cref="VariationContext"/>
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// <para>Gets children that are available for the specified culture.</para>
|
||||
/// <para>Children are sorted by their sortOrder.</para>
|
||||
/// <para>The '*' culture and supported and returns everything.</para>
|
||||
/// <para>
|
||||
/// For culture,
|
||||
/// if null is used the current culture is used.
|
||||
/// If an empty string is used only invariant children are returned.
|
||||
/// If "*" is used all children are returned.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If a variant culture is specified or there is a current culture in the <see cref="VariationContext"/> then the Children returned
|
||||
/// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture.
|
||||
/// However, if an empty string is specified only invariant children are returned.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, string culture = null)
|
||||
{
|
||||
// invariant has invariant value (whatever the requested culture)
|
||||
if (!content.ContentType.VariesByCulture() && culture != "*")
|
||||
culture = "";
|
||||
|
||||
// handle context culture for variant
|
||||
if (culture == null)
|
||||
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
|
||||
|
||||
@@ -9,7 +9,14 @@ namespace Umbraco.Core
|
||||
public static class PublishedModelFactoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes an action with a safe live factory/
|
||||
/// Returns true if the current <see cref="IPublishedModelFactory"/> is an implementation of <see cref="ILivePublishedModelFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Executes an action with a safe live factory
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If the factory is a live factory, ensures it is refreshed and locked while executing the action.</para>
|
||||
@@ -20,6 +27,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
lock (liveFactory.SyncRoot)
|
||||
{
|
||||
//Call refresh on the live factory to re-compile the models
|
||||
liveFactory.Refresh();
|
||||
action();
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Umbraco.Core.Runtime
|
||||
// there should be none, really - this is here "just in case"
|
||||
Compose(composition);
|
||||
|
||||
// acquire the main domain
|
||||
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
|
||||
AcquireMainDom(mainDom);
|
||||
|
||||
// determine our runtime level
|
||||
@@ -218,13 +218,13 @@ namespace Umbraco.Core.Runtime
|
||||
IOHelper.SetRootDirectory(path);
|
||||
}
|
||||
|
||||
private void AcquireMainDom(MainDom mainDom)
|
||||
private bool AcquireMainDom(MainDom mainDom)
|
||||
{
|
||||
using (var timer = ProfilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired."))
|
||||
{
|
||||
try
|
||||
{
|
||||
mainDom.Acquire();
|
||||
return mainDom.Acquire();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -97,6 +97,11 @@ namespace Umbraco.Core
|
||||
/// <param name="request"></param>
|
||||
internal void EnsureApplicationUrl(HttpRequestBase request = null)
|
||||
{
|
||||
//Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that
|
||||
// it changes the URL to `localhost:80` which actually doesn't work for pinging itself, it only works internally in Azure. The ironic part
|
||||
// about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626
|
||||
|
||||
|
||||
// see U4-10626 - in some cases we want to reset the application url
|
||||
// (this is a simplified version of what was in 7.x)
|
||||
// note: should this be optional? is it expensive?
|
||||
|
||||
@@ -6,9 +6,25 @@ namespace Umbraco.Core.Services.Changes
|
||||
public enum ContentTypeChangeTypes : byte
|
||||
{
|
||||
None = 0,
|
||||
Create = 1, // item type has been created, no impact
|
||||
RefreshMain = 2, // changed, impacts content (adding property or composition does NOT)
|
||||
RefreshOther = 4, // changed, other changes
|
||||
Remove = 8 // item type has been removed
|
||||
|
||||
/// <summary>
|
||||
/// Item type has been created, no impact
|
||||
/// </summary>
|
||||
Create = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Content type changes impact only the Content type being saved
|
||||
/// </summary>
|
||||
RefreshMain = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Content type changes impacts the content type being saved and others used that are composed of it
|
||||
/// </summary>
|
||||
RefreshOther = 4, // changed, other change
|
||||
|
||||
/// <summary>
|
||||
/// Content type was removed
|
||||
/// </summary>
|
||||
Remove = 8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +241,7 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DefaultPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RenamingPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RichTextPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DropDownFlexiblePreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
|
||||
<Compile Include="Models\Entities\EntityExtensions.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorBase.cs" />
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Umbraco.Core
|
||||
handle,
|
||||
(state, timedOut) =>
|
||||
{
|
||||
//TODO: We aren't checking if this is timed out
|
||||
|
||||
tcs.SetResult(null);
|
||||
|
||||
// we take a lock here to make sure the outer method has completed setting the local variable callbackHandle.
|
||||
|
||||
@@ -48,6 +48,31 @@ namespace Umbraco.Examine
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all index fields that are culture specific (suffixed) or invariant
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<string> GetCultureAndInvariantFields(this IUmbracoIndex index, string culture)
|
||||
{
|
||||
var allFields = index.GetFields();
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var field in allFields)
|
||||
{
|
||||
var match = CultureIsoCodeFieldNameMatchExpression.Match(field);
|
||||
if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value))
|
||||
{
|
||||
yield return field; //matches this culture field
|
||||
}
|
||||
else if (!match.Success)
|
||||
{
|
||||
yield return field; //matches no culture field (invariant)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryParseLuceneQuery(string query)
|
||||
{
|
||||
// TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll
|
||||
@@ -77,7 +102,7 @@ namespace Umbraco.Examine
|
||||
/// <remarks>
|
||||
/// This is not thread safe, use with care
|
||||
/// </remarks>
|
||||
internal static void UnlockLuceneIndexes(this IExamineManager examineManager, ILogger logger)
|
||||
internal static void ConfigureLuceneIndexes(this IExamineManager examineManager, ILogger logger, bool disableExamineIndexing)
|
||||
{
|
||||
foreach (var luceneIndexer in examineManager.Indexes.OfType<LuceneIndex>())
|
||||
{
|
||||
@@ -86,6 +111,8 @@ namespace Umbraco.Examine
|
||||
//that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems.
|
||||
luceneIndexer.WaitForIndexQueueOnShutdown = false;
|
||||
|
||||
if (disableExamineIndexing) continue; //exit if not enabled, we don't need to unlock them if we're not maindom
|
||||
|
||||
//we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that
|
||||
//the indexes are not operational unless MainDom is true
|
||||
var dir = luceneIndexer.GetLuceneDirectory();
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
_source = new TestDataSource(kits);
|
||||
|
||||
// at last, create the complete NuCache snapshot service!
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
_snapshotService = new PublishedSnapshotService(options,
|
||||
null,
|
||||
runtime,
|
||||
@@ -151,117 +151,139 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Mock.Get(factory).Setup(x => x.GetInstance(typeof(IVariationContextAccessor))).Returns(_variationAccesor);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> GetNestedVariantKits()
|
||||
{
|
||||
var paths = new Dictionary<int, string> { { -1, "-1" } };
|
||||
|
||||
//1x variant (root)
|
||||
yield return CreateVariantKit(1, -1, 1, paths);
|
||||
|
||||
//1x invariant under root
|
||||
yield return CreateInvariantKit(4, 1, 1, paths);
|
||||
|
||||
//1x variant under root
|
||||
yield return CreateVariantKit(7, 1, 4, paths);
|
||||
|
||||
//2x mixed under invariant
|
||||
yield return CreateVariantKit(10, 4, 1, paths);
|
||||
yield return CreateInvariantKit(11, 4, 2, paths);
|
||||
|
||||
//2x mixed under variant
|
||||
yield return CreateVariantKit(12, 7, 1, paths);
|
||||
yield return CreateInvariantKit(13, 7, 2, paths);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> GetInvariantKits()
|
||||
{
|
||||
var paths = new Dictionary<int, string> { { -1, "-1" } };
|
||||
|
||||
ContentNodeKit CreateKit(int id, int parentId, int sortOrder)
|
||||
yield return CreateInvariantKit(1, -1, 1, paths);
|
||||
yield return CreateInvariantKit(2, -1, 2, paths);
|
||||
yield return CreateInvariantKit(3, -1, 3, paths);
|
||||
|
||||
yield return CreateInvariantKit(4, 1, 1, paths);
|
||||
yield return CreateInvariantKit(5, 1, 2, paths);
|
||||
yield return CreateInvariantKit(6, 1, 3, paths);
|
||||
|
||||
yield return CreateInvariantKit(7, 2, 3, paths);
|
||||
yield return CreateInvariantKit(8, 2, 2, paths);
|
||||
yield return CreateInvariantKit(9, 2, 1, paths);
|
||||
|
||||
yield return CreateInvariantKit(10, 3, 1, paths);
|
||||
|
||||
yield return CreateInvariantKit(11, 4, 1, paths);
|
||||
yield return CreateInvariantKit(12, 4, 2, paths);
|
||||
}
|
||||
|
||||
private ContentNodeKit CreateInvariantKit(int id, int parentId, int sortOrder, Dictionary<int, string> paths)
|
||||
{
|
||||
if (!paths.TryGetValue(parentId, out var parentPath))
|
||||
throw new Exception("Unknown parent.");
|
||||
|
||||
var path = paths[id] = parentPath + "," + id;
|
||||
var level = path.Count(x => x == ',');
|
||||
var now = DateTime.Now;
|
||||
|
||||
return new ContentNodeKit
|
||||
{
|
||||
if (!paths.TryGetValue(parentId, out var parentPath))
|
||||
throw new Exception("Unknown parent.");
|
||||
|
||||
var path = paths[id] = parentPath + "," + id;
|
||||
var level = path.Count(x => x == ',');
|
||||
var now = DateTime.Now;
|
||||
|
||||
return new ContentNodeKit
|
||||
ContentTypeId = _contentTypeInvariant.Id,
|
||||
Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
|
||||
DraftData = null,
|
||||
PublishedData = new ContentData
|
||||
{
|
||||
ContentTypeId = _contentTypeInvariant.Id,
|
||||
Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
|
||||
DraftData = null,
|
||||
PublishedData = new ContentData
|
||||
{
|
||||
Name = "N" + id,
|
||||
Published = true,
|
||||
TemplateId = 0,
|
||||
VersionId = 1,
|
||||
VersionDate = now,
|
||||
WriterId = 0,
|
||||
Properties = new Dictionary<string, PropertyData[]>(),
|
||||
CultureInfos = new Dictionary<string, CultureVariation>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
yield return CreateKit(1, -1, 1);
|
||||
yield return CreateKit(2, -1, 2);
|
||||
yield return CreateKit(3, -1, 3);
|
||||
|
||||
yield return CreateKit(4, 1, 1);
|
||||
yield return CreateKit(5, 1, 2);
|
||||
yield return CreateKit(6, 1, 3);
|
||||
|
||||
yield return CreateKit(7, 2, 3);
|
||||
yield return CreateKit(8, 2, 2);
|
||||
yield return CreateKit(9, 2, 1);
|
||||
|
||||
yield return CreateKit(10, 3, 1);
|
||||
|
||||
yield return CreateKit(11, 4, 1);
|
||||
yield return CreateKit(12, 4, 2);
|
||||
Name = "N" + id,
|
||||
Published = true,
|
||||
TemplateId = 0,
|
||||
VersionId = 1,
|
||||
VersionDate = now,
|
||||
WriterId = 0,
|
||||
Properties = new Dictionary<string, PropertyData[]>(),
|
||||
CultureInfos = new Dictionary<string, CultureVariation>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> GetVariantKits()
|
||||
{
|
||||
var paths = new Dictionary<int, string> { { -1, "-1" } };
|
||||
|
||||
Dictionary<string, CultureVariation> GetCultureInfos(int id, DateTime now)
|
||||
yield return CreateVariantKit(1, -1, 1, paths);
|
||||
yield return CreateVariantKit(2, -1, 2, paths);
|
||||
yield return CreateVariantKit(3, -1, 3, paths);
|
||||
|
||||
yield return CreateVariantKit(4, 1, 1, paths);
|
||||
yield return CreateVariantKit(5, 1, 2, paths);
|
||||
yield return CreateVariantKit(6, 1, 3, paths);
|
||||
|
||||
yield return CreateVariantKit(7, 2, 3, paths);
|
||||
yield return CreateVariantKit(8, 2, 2, paths);
|
||||
yield return CreateVariantKit(9, 2, 1, paths);
|
||||
|
||||
yield return CreateVariantKit(10, 3, 1, paths);
|
||||
|
||||
yield return CreateVariantKit(11, 4, 1, paths);
|
||||
yield return CreateVariantKit(12, 4, 2, paths);
|
||||
}
|
||||
|
||||
private static Dictionary<string, CultureVariation> GetCultureInfos(int id, DateTime now)
|
||||
{
|
||||
var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
|
||||
var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 };
|
||||
|
||||
var infos = new Dictionary<string, CultureVariation>();
|
||||
if (en.Contains(id))
|
||||
infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false };
|
||||
if (fr.Contains(id))
|
||||
infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false };
|
||||
return infos;
|
||||
}
|
||||
|
||||
private ContentNodeKit CreateVariantKit(int id, int parentId, int sortOrder, Dictionary<int, string> paths)
|
||||
{
|
||||
if (!paths.TryGetValue(parentId, out var parentPath))
|
||||
throw new Exception("Unknown parent.");
|
||||
|
||||
var path = paths[id] = parentPath + "," + id;
|
||||
var level = path.Count(x => x == ',');
|
||||
var now = DateTime.Now;
|
||||
|
||||
return new ContentNodeKit
|
||||
{
|
||||
var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
|
||||
var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 };
|
||||
|
||||
var infos = new Dictionary<string, CultureVariation>();
|
||||
if (en.Contains(id))
|
||||
infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false };
|
||||
if (fr.Contains(id))
|
||||
infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false };
|
||||
return infos;
|
||||
}
|
||||
|
||||
ContentNodeKit CreateKit(int id, int parentId, int sortOrder)
|
||||
{
|
||||
if (!paths.TryGetValue(parentId, out var parentPath))
|
||||
throw new Exception("Unknown parent.");
|
||||
|
||||
var path = paths[id] = parentPath + "," + id;
|
||||
var level = path.Count(x => x == ',');
|
||||
var now = DateTime.Now;
|
||||
|
||||
return new ContentNodeKit
|
||||
ContentTypeId = _contentTypeVariant.Id,
|
||||
Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
|
||||
DraftData = null,
|
||||
PublishedData = new ContentData
|
||||
{
|
||||
ContentTypeId = _contentTypeVariant.Id,
|
||||
Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0),
|
||||
DraftData = null,
|
||||
PublishedData = new ContentData
|
||||
{
|
||||
Name = "N" + id,
|
||||
Published = true,
|
||||
TemplateId = 0,
|
||||
VersionId = 1,
|
||||
VersionDate = now,
|
||||
WriterId = 0,
|
||||
Properties = new Dictionary<string, PropertyData[]>(),
|
||||
CultureInfos = GetCultureInfos(id, now)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
yield return CreateKit(1, -1, 1);
|
||||
yield return CreateKit(2, -1, 2);
|
||||
yield return CreateKit(3, -1, 3);
|
||||
|
||||
yield return CreateKit(4, 1, 1);
|
||||
yield return CreateKit(5, 1, 2);
|
||||
yield return CreateKit(6, 1, 3);
|
||||
|
||||
yield return CreateKit(7, 2, 3);
|
||||
yield return CreateKit(8, 2, 2);
|
||||
yield return CreateKit(9, 2, 1);
|
||||
|
||||
yield return CreateKit(10, 3, 1);
|
||||
|
||||
yield return CreateKit(11, 4, 1);
|
||||
yield return CreateKit(12, 4, 2);
|
||||
Name = "N" + id,
|
||||
Published = true,
|
||||
TemplateId = 0,
|
||||
VersionId = 1,
|
||||
VersionDate = now,
|
||||
WriterId = 0,
|
||||
Properties = new Dictionary<string, PropertyData[]>(),
|
||||
CultureInfos = GetCultureInfos(id, now)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> GetVariantWithDraftKits()
|
||||
@@ -660,6 +682,96 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NestedVariationChildrenTest()
|
||||
{
|
||||
var mixedKits = GetNestedVariantKits();
|
||||
Init(mixedKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
|
||||
//TEST with en-us variation context
|
||||
|
||||
_variationAccesor.VariationContext = new VariationContext("en-US");
|
||||
|
||||
var documents = snapshot.Content.GetAtRoot().ToArray();
|
||||
AssertDocuments(documents, "N1-en-US");
|
||||
|
||||
documents = snapshot.Content.GetById(1).Children().ToArray();
|
||||
AssertDocuments(documents, "N4", "N7-en-US");
|
||||
|
||||
//Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants
|
||||
documents = snapshot.Content.GetById(4).Children().ToArray();
|
||||
AssertDocuments(documents, "N10-en-US", "N11");
|
||||
|
||||
//Get the variant and list children, there's a variation context so it should return invariant AND en-us variants
|
||||
documents = snapshot.Content.GetById(7).Children().ToArray();
|
||||
AssertDocuments(documents, "N12-en-US", "N13");
|
||||
|
||||
//TEST with fr-fr variation context
|
||||
|
||||
_variationAccesor.VariationContext = new VariationContext("fr-FR");
|
||||
|
||||
documents = snapshot.Content.GetAtRoot().ToArray();
|
||||
AssertDocuments(documents, "N1-fr-FR");
|
||||
|
||||
documents = snapshot.Content.GetById(1).Children().ToArray();
|
||||
AssertDocuments(documents, "N4", "N7-fr-FR");
|
||||
|
||||
//Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants
|
||||
documents = snapshot.Content.GetById(4).Children().ToArray();
|
||||
AssertDocuments(documents, "N10-fr-FR", "N11");
|
||||
|
||||
//Get the variant and list children, there's a variation context so it should return invariant AND en-us variants
|
||||
documents = snapshot.Content.GetById(7).Children().ToArray();
|
||||
AssertDocuments(documents, "N12-fr-FR", "N13");
|
||||
|
||||
//TEST specific cultures
|
||||
|
||||
documents = snapshot.Content.GetAtRoot("fr-FR").ToArray();
|
||||
AssertDocuments(documents, "N1-fr-FR");
|
||||
|
||||
documents = snapshot.Content.GetById(1).Children("fr-FR").ToArray();
|
||||
AssertDocuments(documents, "N4", "N7-fr-FR"); //NOTE: Returns invariant, this is expected
|
||||
documents = snapshot.Content.GetById(1).Children("").ToArray();
|
||||
AssertDocuments(documents, "N4"); //Only returns invariant since that is what was requested
|
||||
|
||||
documents = snapshot.Content.GetById(4).Children("fr-FR").ToArray();
|
||||
AssertDocuments(documents, "N10-fr-FR", "N11"); //NOTE: Returns invariant, this is expected
|
||||
documents = snapshot.Content.GetById(4).Children("").ToArray();
|
||||
AssertDocuments(documents, "N11"); //Only returns invariant since that is what was requested
|
||||
|
||||
documents = snapshot.Content.GetById(7).Children("fr-FR").ToArray();
|
||||
AssertDocuments(documents, "N12-fr-FR", "N13"); //NOTE: Returns invariant, this is expected
|
||||
documents = snapshot.Content.GetById(7).Children("").ToArray();
|
||||
AssertDocuments(documents, "N13"); //Only returns invariant since that is what was requested
|
||||
|
||||
//TEST without variation context
|
||||
// This will actually convert the culture to "" which will be invariant since that's all it will know how to do
|
||||
// This will return a NULL name for culture specific entities because there is no variation context
|
||||
|
||||
_variationAccesor.VariationContext = null;
|
||||
|
||||
documents = snapshot.Content.GetAtRoot().ToArray();
|
||||
//will return nothing because there's only variant at root
|
||||
Assert.AreEqual(0, documents.Length);
|
||||
//so we'll continue to getting the known variant, do not fully assert this because the Name will NULL
|
||||
documents = snapshot.Content.GetAtRoot("fr-FR").ToArray();
|
||||
Assert.AreEqual(1, documents.Length);
|
||||
|
||||
documents = snapshot.Content.GetById(1).Children().ToArray();
|
||||
AssertDocuments(documents, "N4");
|
||||
|
||||
//Get the invariant and list children
|
||||
documents = snapshot.Content.GetById(4).Children().ToArray();
|
||||
AssertDocuments(documents, "N11");
|
||||
|
||||
//Get the variant and list children
|
||||
documents = snapshot.Content.GetById(7).Children().ToArray();
|
||||
AssertDocuments(documents, "N13");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void VariantChildrenTest()
|
||||
{
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
_variationAccesor = new TestVariationContextAccessor();
|
||||
|
||||
// at last, create the complete NuCache snapshot service!
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
_snapshotService = new PublishedSnapshotService(options,
|
||||
null,
|
||||
runtime,
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Umbraco.Tests.Scoping
|
||||
|
||||
protected override IPublishedSnapshotService CreatePublishedSnapshotService()
|
||||
{
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
|
||||
var runtimeStateMock = new Mock<IRuntimeState>();
|
||||
runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run);
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Web;
|
||||
using System.Web.Helpers;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Tests.Security
|
||||
{
|
||||
[TestFixture]
|
||||
public class UmbracoAntiForgeryAdditionalDataProviderTests
|
||||
{
|
||||
[Test]
|
||||
public void Test_Wrapped_Non_BeginUmbracoForm()
|
||||
{
|
||||
var wrapped = Mock.Of<IAntiForgeryAdditionalDataProvider>(x => x.GetAdditionalData(It.IsAny<HttpContextBase>()) == "custom");
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var data = provider.GetAdditionalData(httpContextFactory.HttpContext);
|
||||
|
||||
Assert.IsTrue(data.DetectIsJson());
|
||||
var json = JsonConvert.DeserializeObject<UmbracoAntiForgeryAdditionalDataProvider.AdditionalData>(data);
|
||||
Assert.AreEqual(null, json.Ufprt);
|
||||
Assert.IsTrue(json.Stamp != default);
|
||||
Assert.AreEqual("custom", json.WrappedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Null_Wrapped_Non_BeginUmbracoForm()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var data = provider.GetAdditionalData(httpContextFactory.HttpContext);
|
||||
|
||||
Assert.IsTrue(data.DetectIsJson());
|
||||
var json = JsonConvert.DeserializeObject<UmbracoAntiForgeryAdditionalDataProvider.AdditionalData>(data);
|
||||
Assert.AreEqual(null, json.Ufprt);
|
||||
Assert.IsTrue(json.Stamp != default);
|
||||
Assert.AreEqual("default", json.WrappedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Non_Json()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "hello");
|
||||
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Invalid_Json()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '0'}");
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': ''}");
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'hello': 'world'}");
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_No_Request_Ufprt()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
//there is a ufprt in the additional data, but not in the request
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': 'ASBVDFDFDFDF'}");
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_No_AdditionalData_Ufprt()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
|
||||
requestMock.SetupGet(x => x["ufprt"]).Returns("ABCDEFG");
|
||||
|
||||
//there is a ufprt in the additional data, but not in the request
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}");
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_No_AdditionalData_Or_Request_Ufprt()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
|
||||
//there is a ufprt in the additional data, but not in the request
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}");
|
||||
Assert.IsTrue(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Request_And_AdditionalData_Ufprt()
|
||||
{
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(null);
|
||||
|
||||
var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
|
||||
requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey());
|
||||
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsTrue(isValid);
|
||||
|
||||
routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_Wrapped_Request_And_AdditionalData_Ufprt()
|
||||
{
|
||||
var wrapped = Mock.Of<IAntiForgeryAdditionalDataProvider>(x => x.ValidateAdditionalData(It.IsAny<HttpContextBase>(), "custom") == true);
|
||||
var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped);
|
||||
|
||||
var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
|
||||
var httpContextFactory = new FakeHttpContextFactory("/hello/world");
|
||||
var requestMock = Mock.Get(httpContextFactory.HttpContext.Request);
|
||||
requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey());
|
||||
|
||||
var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsFalse(isValid);
|
||||
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'custom', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsTrue(isValid);
|
||||
|
||||
routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco";
|
||||
isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}");
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
protected override IPublishedSnapshotService CreatePublishedSnapshotService()
|
||||
{
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
|
||||
var runtimeStateMock = new Mock<IRuntimeState>();
|
||||
runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Lucene.Net.Store;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Tests.TestHelpers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Used for tests with Lucene so that each RAM directory is unique
|
||||
/// </summary>
|
||||
public class RandomIdRAMDirectory : RAMDirectory
|
||||
{
|
||||
private readonly string _lockId = Guid.NewGuid().ToString();
|
||||
public override string GetLockId()
|
||||
{
|
||||
return _lockId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,6 @@
|
||||
<Compile Include="Routing\MediaUrlProviderTests.cs" />
|
||||
<Compile Include="Runtimes\StandaloneTests.cs" />
|
||||
<Compile Include="Routing\GetContentUrlsTests.cs" />
|
||||
<Compile Include="Security\UmbracoAntiForgeryAdditionalDataProviderTests.cs" />
|
||||
<Compile Include="Services\AmbiguousEventTests.cs" />
|
||||
<Compile Include="Services\ContentServiceEventTests.cs" />
|
||||
<Compile Include="Services\ContentServicePublishBranchTests.cs" />
|
||||
@@ -158,6 +157,7 @@
|
||||
<Compile Include="Services\MemberGroupServiceTests.cs" />
|
||||
<Compile Include="Services\MediaTypeServiceTests.cs" />
|
||||
<Compile Include="Services\PropertyValidationServiceTests.cs" />
|
||||
<Compile Include="TestHelpers\RandomIdRamDirectory.cs" />
|
||||
<Compile Include="Testing\Objects\TestDataSource.cs" />
|
||||
<Compile Include="Published\PublishedSnapshotTestObjects.cs" />
|
||||
<Compile Include="Published\ModelTypeTests.cs" />
|
||||
@@ -268,6 +268,8 @@
|
||||
<Compile Include="StringNewlineExtensions.cs" />
|
||||
<Compile Include="Strings\StylesheetHelperTests.cs" />
|
||||
<Compile Include="Strings\StringValidationTests.cs" />
|
||||
<Compile Include="Web\Mvc\ValidateUmbracoFormRouteStringAttributeTests.cs" />
|
||||
<Compile Include="Web\PublishedContentQueryTests.cs" />
|
||||
<Compile Include="Web\UmbracoHelperTests.cs" />
|
||||
<Compile Include="Membership\MembershipProviderBaseTests.cs" />
|
||||
<Compile Include="Membership\UmbracoServiceMembershipProviderTests.cs" />
|
||||
|
||||
@@ -4,6 +4,7 @@ using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.Web.Mvc
|
||||
{
|
||||
|
||||
[TestFixture]
|
||||
public class HtmlHelperExtensionMethodsTests
|
||||
{
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Tests.Web.Mvc
|
||||
{
|
||||
[TestFixture]
|
||||
public class ValidateUmbracoFormRouteStringAttributeTests
|
||||
{
|
||||
[Test]
|
||||
public void Validate_Route_String()
|
||||
{
|
||||
var attribute = new ValidateUmbracoFormRouteStringAttribute();
|
||||
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(null, null, null, null));
|
||||
|
||||
const string ControllerName = "Test";
|
||||
const string ControllerAction = "Index";
|
||||
const string Area = "MyArea";
|
||||
var validUfprt = UrlHelperRenderExtensions.CreateEncryptedRouteString(ControllerName, ControllerAction, Area);
|
||||
|
||||
var invalidUfprt = validUfprt + "z";
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(invalidUfprt, null, null, null));
|
||||
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, "doesntMatch"));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, null));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, ControllerName, "doesntMatch", Area));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, ControllerName, null, Area));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, "doesntMatch", ControllerAction, Area));
|
||||
Assert.Throws<HttpUmbracoFormRouteStringException>(() => attribute.ValidateRouteString(validUfprt, null, ControllerAction, Area));
|
||||
|
||||
Assert.DoesNotThrow(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, Area));
|
||||
Assert.DoesNotThrow(() => attribute.ValidateRouteString(validUfprt, ControllerName.ToLowerInvariant(), ControllerAction.ToLowerInvariant(), Area.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Examine;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Lucene.Net.Store;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Examine;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
|
||||
namespace Umbraco.Tests.Web
|
||||
{
|
||||
[TestFixture]
|
||||
public class PublishedContentQueryTests
|
||||
{
|
||||
|
||||
private class TestIndex : LuceneIndex, IUmbracoIndex
|
||||
{
|
||||
private readonly string[] _fieldNames;
|
||||
|
||||
public TestIndex(string name, Directory luceneDirectory, string[] fieldNames)
|
||||
: base(name, luceneDirectory, null, null, null, null)
|
||||
{
|
||||
_fieldNames = fieldNames;
|
||||
}
|
||||
public bool EnableDefaultEventHandler => throw new NotImplementedException();
|
||||
public bool PublishedValuesOnly => throw new NotImplementedException();
|
||||
public IEnumerable<string> GetFields() => _fieldNames;
|
||||
}
|
||||
|
||||
private TestIndex CreateTestIndex(Directory luceneDirectory, string[] fieldNames)
|
||||
{
|
||||
var indexer = new TestIndex("TestIndex", luceneDirectory, fieldNames);
|
||||
|
||||
using (indexer.ProcessNonAsync())
|
||||
{
|
||||
//populate with some test data
|
||||
indexer.IndexItem(new ValueSet("1", "content", new Dictionary<string, object>
|
||||
{
|
||||
[fieldNames[0]] = "Hello world, there are products here",
|
||||
[UmbracoContentIndex.VariesByCultureFieldName] = "n"
|
||||
}));
|
||||
indexer.IndexItem(new ValueSet("2", "content", new Dictionary<string, object>
|
||||
{
|
||||
[fieldNames[1]] = "Hello world, there are products here",
|
||||
[UmbracoContentIndex.VariesByCultureFieldName] = "y"
|
||||
}));
|
||||
indexer.IndexItem(new ValueSet("3", "content", new Dictionary<string, object>
|
||||
{
|
||||
[fieldNames[2]] = "Hello world, there are products here",
|
||||
[UmbracoContentIndex.VariesByCultureFieldName] = "y"
|
||||
}));
|
||||
}
|
||||
|
||||
return indexer;
|
||||
}
|
||||
|
||||
private PublishedContentQuery CreatePublishedContentQuery(IIndex indexer)
|
||||
{
|
||||
var examineManager = new Mock<IExamineManager>();
|
||||
IIndex outarg = indexer;
|
||||
examineManager.Setup(x => x.TryGetIndex("TestIndex", out outarg)).Returns(true);
|
||||
|
||||
var contentCache = new Mock<IPublishedContentCache>();
|
||||
contentCache.Setup(x => x.GetById(It.IsAny<int>())).Returns((int intId) => Mock.Of<IPublishedContent>(x => x.Id == intId));
|
||||
var snapshot = Mock.Of<IPublishedSnapshot>(x => x.Content == contentCache.Object);
|
||||
var variationContext = new VariationContext();
|
||||
var variationContextAccessor = Mock.Of<IVariationContextAccessor>(x => x.VariationContext == variationContext);
|
||||
|
||||
return new PublishedContentQuery(snapshot, variationContextAccessor, examineManager.Object);
|
||||
}
|
||||
|
||||
[TestCase("fr-fr", ExpectedResult = "1, 3", TestName = "Search Culture: fr-fr. Must return both fr-fr and invariant results")]
|
||||
[TestCase("en-us", ExpectedResult = "1, 2", TestName = "Search Culture: en-us. Must return both en-us and invariant results")]
|
||||
[TestCase("*", ExpectedResult = "1, 2, 3", TestName = "Search Culture: *. Must return all cultures and all invariant results")]
|
||||
[TestCase(null, ExpectedResult = "1", TestName = "Search Culture: null. Must return only invariant results")]
|
||||
public string Search(string culture)
|
||||
{
|
||||
using (var luceneDir = new RandomIdRAMDirectory())
|
||||
{
|
||||
var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" };
|
||||
using (var indexer = CreateTestIndex(luceneDir, fieldNames))
|
||||
{
|
||||
var pcq = CreatePublishedContentQuery(indexer);
|
||||
|
||||
var results = pcq.Search("Products", culture, "TestIndex");
|
||||
|
||||
var ids = results.Select(x => x.Content.Id).ToArray();
|
||||
|
||||
return string.Join(", ", ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Examine.LuceneEngine;
|
||||
using Lucene.Net.Analysis;
|
||||
using Lucene.Net.Analysis.Standard;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
@@ -13,18 +16,19 @@ using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.Web
|
||||
{
|
||||
|
||||
[TestFixture]
|
||||
public class UmbracoHelperTests
|
||||
{
|
||||
{
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
Current.Reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ------- Int32 conversion tests
|
||||
[Test]
|
||||
public static void Converting_Boxed_34_To_An_Int_Returns_34()
|
||||
|
||||
+266
-74
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,7 @@
|
||||
"npm": "^6.4.1",
|
||||
"signalr": "2.4.0",
|
||||
"spectrum-colorpicker": "1.8.0",
|
||||
"tinymce": "4.9.2",
|
||||
"tinymce": "^5.0.12",
|
||||
"typeahead.js": "0.11.1",
|
||||
"underscore": "1.9.1"
|
||||
},
|
||||
|
||||
@@ -94,6 +94,10 @@
|
||||
content.apps[0].active = true;
|
||||
$scope.appChanged(content.apps[0]);
|
||||
}
|
||||
// otherwise make sure the save options are up to date with the current content state
|
||||
else {
|
||||
createButtons($scope.content);
|
||||
}
|
||||
|
||||
editorState.set(content);
|
||||
|
||||
|
||||
+6
-12
@@ -101,14 +101,14 @@ angular.module('umbraco.directives')
|
||||
var eventBindings = [];
|
||||
|
||||
function oneTimeClick(event) {
|
||||
// ignore clicks on button groups toggles (i.e. the save and publish button)
|
||||
var parents = $(event.target).closest("[data-element='button-group-toggle']");
|
||||
if (parents.length > 0) {
|
||||
return;
|
||||
}
|
||||
var el = event.target.nodeName;
|
||||
|
||||
//ignore link and button clicks
|
||||
var els = ["INPUT", "A", "BUTTON"];
|
||||
if (els.indexOf(el) >= 0) { return; }
|
||||
|
||||
// ignore clicks on new overlay
|
||||
parents = $(event.target).parents(".umb-overlay,.umb-tour");
|
||||
var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour");
|
||||
if (parents.length > 0) {
|
||||
return;
|
||||
}
|
||||
@@ -131,12 +131,6 @@ angular.module('umbraco.directives')
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore clicks on dialog actions
|
||||
var actions = $(event.target).parents(".umb-action");
|
||||
if (actions.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
//ignore clicks inside this element
|
||||
if ($(element).has($(event.target)).length > 0) {
|
||||
return;
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ angular.module("umbraco.directives")
|
||||
uniqueId: '=',
|
||||
value: '=',
|
||||
configuration: "=", //this is the RTE configuration
|
||||
datatypeId: '@',
|
||||
datatypeKey: '@',
|
||||
ignoreUserStartNodes: '@'
|
||||
},
|
||||
templateUrl: 'views/components/grid/grid-rte.html',
|
||||
@@ -43,7 +43,7 @@ angular.module("umbraco.directives")
|
||||
scope.config = {
|
||||
ignoreUserStartNodes: scope.ignoreUserStartNodes === "true"
|
||||
}
|
||||
scope.dataTypeId = scope.datatypeId; //Yes - this casing is rediculous, but it's because the var starts with `data` so it can't be `data-type-id` :/
|
||||
scope.dataTypeKey = scope.datatypeKey; //Yes - this casing is rediculous, but it's because the var starts with `data` so it can't be `data-type-id` :/
|
||||
|
||||
//stores a reference to the editor
|
||||
var tinyMceEditor = null;
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) {
|
||||
searchFromName: "@",
|
||||
showSearch: "@",
|
||||
section: "@",
|
||||
datatypeId: "@",
|
||||
datatypeKey: "@",
|
||||
hideSearchCallback: "=",
|
||||
searchCallback: "="
|
||||
},
|
||||
@@ -63,8 +63,8 @@ function treeSearchBox(localizationService, searchService, $q) {
|
||||
}
|
||||
|
||||
//append dataTypeId value if there is one
|
||||
if (scope.datatypeId) {
|
||||
searchArgs["dataTypeId"] = scope.datatypeId;
|
||||
if (scope.datatypeKey) {
|
||||
searchArgs["dataTypeKey"] = scope.datatypeKey;
|
||||
}
|
||||
|
||||
searcher(searchArgs).then(function (data) {
|
||||
|
||||
+82
-120
@@ -4,7 +4,7 @@
|
||||
@restrict A
|
||||
|
||||
@description
|
||||
Use this directive make an element sticky and follow the page when scrolling.
|
||||
Use this directive make an element sticky and follow the page when scrolling. `umb-sticky-bar--active` class is applied when the element is stuck
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
@@ -12,140 +12,102 @@ Use this directive make an element sticky and follow the page when scrolling.
|
||||
|
||||
<div
|
||||
class="my-sticky-bar"
|
||||
umb-sticky-bar
|
||||
scrollable-container=".container">
|
||||
umb-sticky-bar>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<h3>CSS example</h3>
|
||||
<pre>
|
||||
.my-sticky-bar {
|
||||
padding: 15px 0;
|
||||
background: #000000;
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.my-sticky-bar.-umb-sticky-bar {
|
||||
top: 100px;
|
||||
}
|
||||
</pre>
|
||||
|
||||
@param {string} scrollableContainer Set the class (".element") or the id ("#element") of the scrollable container element.
|
||||
**/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function StickyBarDirective($rootScope) {
|
||||
function StickyBarDirective() {
|
||||
|
||||
/**
|
||||
On initial load, the intersector fires if the grid editor is in the viewport
|
||||
This flag is used to suppress the setClass behaviour on the initial load
|
||||
**/
|
||||
var initial = true;
|
||||
|
||||
/**
|
||||
Toggle `umb-sticky-bar--active` class on the sticky-bar element
|
||||
**/
|
||||
function setClass(addClass, current) {
|
||||
if (!initial) {
|
||||
current.classList.toggle('umb-sticky-bar--active', addClass);
|
||||
} else {
|
||||
initial = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts two elements in the umbStickyBar parent element
|
||||
These are used by the IntersectionObserve to calculate scroll position
|
||||
**/
|
||||
function addSentinels(current) {
|
||||
['-top', '-bottom'].forEach(s => {
|
||||
const sentinel = document.createElement('div');
|
||||
sentinel.classList.add('umb-sticky-sentinel', s);
|
||||
current.parentElement.appendChild(sentinel);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Calls into setClass when the footer sentinel enters/exits the bottom of the container
|
||||
Container is the parent element of the umbStickyBar element
|
||||
**/
|
||||
function observeFooter(current, container) {
|
||||
const observer = new IntersectionObserver((records, observer) => {
|
||||
let [target, rootBounds, intersected] = [records[0].boundingClientRect, records[0].rootBounds, records[0].intersectionRatio === 1];
|
||||
|
||||
if (target.bottom > rootBounds.top && intersected) {
|
||||
setClass(true, current);
|
||||
}
|
||||
if (target.top < rootBounds.top && target.bottom < rootBounds.bottom) {
|
||||
setClass(false, current);
|
||||
}
|
||||
}, {
|
||||
threshold: [1],
|
||||
root: container
|
||||
});
|
||||
|
||||
observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-bottom'));
|
||||
}
|
||||
|
||||
/**
|
||||
Calls into setClass when the header sentinel enters/exits the top of the container
|
||||
Container is the parent element of the umbStickyBar element
|
||||
**/
|
||||
function observeHeader(current, container) {
|
||||
const observer = new IntersectionObserver((records, observer) => {
|
||||
let [target, rootBounds] = [records[0].boundingClientRect, records[0].rootBounds];
|
||||
|
||||
if (target.bottom < rootBounds.top) {
|
||||
setClass(true, current);
|
||||
}
|
||||
|
||||
if (target.bottom >= rootBounds.top && target.bottom < rootBounds.bottom) {
|
||||
setClass(false, current);
|
||||
}
|
||||
}, {
|
||||
threshold: [0],
|
||||
root: container
|
||||
});
|
||||
|
||||
observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-top'));
|
||||
}
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
var bar = $(el);
|
||||
var scrollableContainer = null;
|
||||
var clonedBar = null;
|
||||
var cloneIsMade = false;
|
||||
let current = el[0];
|
||||
let container = current.closest('[data-element="editor-container"]');
|
||||
|
||||
function activate() {
|
||||
|
||||
if (bar.parents(".umb-property").length > 1) {
|
||||
bar.addClass("nested");
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.scrollableContainer) {
|
||||
scrollableContainer = bar.closest(attr.scrollableContainer);
|
||||
} else {
|
||||
scrollableContainer = $(window);
|
||||
}
|
||||
|
||||
scrollableContainer.on('scroll.umbStickyBar', determineVisibility).trigger("scroll");
|
||||
$(window).on('resize.umbStickyBar', determineVisibility);
|
||||
|
||||
scope.$on('$destroy', function () {
|
||||
scrollableContainer.off('.umbStickyBar');
|
||||
$(window).off('.umbStickyBar');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function determineVisibility() {
|
||||
|
||||
var barTop = bar[0].offsetTop;
|
||||
var scrollTop = scrollableContainer.scrollTop();
|
||||
|
||||
if (scrollTop > barTop) {
|
||||
|
||||
if (!cloneIsMade) {
|
||||
|
||||
createClone();
|
||||
|
||||
clonedBar.css({
|
||||
'visibility': 'visible'
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
calculateSize();
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (cloneIsMade) {
|
||||
|
||||
//remove cloned element (switched places with original on creation)
|
||||
bar.remove();
|
||||
bar = clonedBar;
|
||||
clonedBar = null;
|
||||
|
||||
bar.removeClass('-umb-sticky-bar');
|
||||
bar.css({
|
||||
position: 'relative',
|
||||
'width': 'auto',
|
||||
'height': 'auto',
|
||||
'z-index': 'auto',
|
||||
'visibility': 'visible'
|
||||
});
|
||||
|
||||
cloneIsMade = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function calculateSize() {
|
||||
var width = bar.innerWidth();
|
||||
clonedBar.css({
|
||||
width: width + 10 // + 10 (5*2) because we need to add border to avoid seeing the shadow beneath. Look at the CSS.
|
||||
});
|
||||
}
|
||||
|
||||
function createClone() {
|
||||
//switch place with cloned element, to keep binding intact
|
||||
clonedBar = bar;
|
||||
bar = clonedBar.clone();
|
||||
clonedBar.after(bar);
|
||||
clonedBar.addClass('-umb-sticky-bar');
|
||||
clonedBar.css({
|
||||
'position': 'fixed',
|
||||
// if you change this z-index value, make sure the sticky editor sub headers do not
|
||||
// clash with umb-dropdown (e.g. the content actions dropdown in content list view)
|
||||
'z-index': 99,
|
||||
'visibility': 'hidden'
|
||||
});
|
||||
|
||||
cloneIsMade = true;
|
||||
calculateSize();
|
||||
|
||||
}
|
||||
|
||||
activate();
|
||||
addSentinels(current);
|
||||
|
||||
observeHeader(current, container);
|
||||
observeFooter(current, container);
|
||||
}
|
||||
|
||||
var directive = {
|
||||
|
||||
@@ -172,7 +172,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
umbRequestHelper.getApiUrl(
|
||||
"entityApiBaseUrl",
|
||||
"GetUrlAndAnchors",
|
||||
{ id: id })),
|
||||
[{ id: id }])),
|
||||
'Failed to retrieve url and anchors data for id ' + id);
|
||||
},
|
||||
|
||||
@@ -327,8 +327,8 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
{ type: type },
|
||||
{ culture: culture}
|
||||
];
|
||||
if (options && options.dataTypeId) {
|
||||
args.push({ dataTypeId: options.dataTypeId });
|
||||
if (options && options.dataTypeKey) {
|
||||
args.push({ dataTypeKey: options.dataTypeKey });
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
@@ -356,8 +356,8 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
getChildren: function (id, type, options) {
|
||||
|
||||
var args = [{ id: id }, { type: type }];
|
||||
if (options && options.dataTypeId) {
|
||||
args.push({ dataTypeId: options.dataTypeId });
|
||||
if (options && options.dataTypeKey) {
|
||||
args.push({ dataTypeKey: options.dataTypeKey });
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
@@ -404,7 +404,8 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
pageNumber: 100,
|
||||
filter: '',
|
||||
orderDirection: "Ascending",
|
||||
orderBy: "SortOrder"
|
||||
orderBy: "SortOrder",
|
||||
dataTypeKey: null
|
||||
};
|
||||
if (options === undefined) {
|
||||
options = {};
|
||||
@@ -421,6 +422,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
options.orderDirection = "Descending";
|
||||
}
|
||||
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
@@ -434,7 +436,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
orderBy: options.orderBy,
|
||||
orderDirection: options.orderDirection,
|
||||
filter: encodeURIComponent(options.filter),
|
||||
dataTypeId: options.dataTypeId
|
||||
dataTypeKey: options.dataTypeKey
|
||||
}
|
||||
)),
|
||||
'Failed to retrieve child data for id ' + parentId);
|
||||
@@ -476,7 +478,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
filter: '',
|
||||
orderDirection: "Ascending",
|
||||
orderBy: "SortOrder",
|
||||
dataTypeId: null
|
||||
dataTypeKey: null
|
||||
};
|
||||
if (options === undefined) {
|
||||
options = {};
|
||||
@@ -506,7 +508,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
orderBy: options.orderBy,
|
||||
orderDirection: options.orderDirection,
|
||||
filter: encodeURIComponent(options.filter),
|
||||
dataTypeId: options.dataTypeId
|
||||
dataTypeKey: options.dataTypeKey
|
||||
}
|
||||
)),
|
||||
'Failed to retrieve child data for id ' + parentId);
|
||||
@@ -535,15 +537,15 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the entity array.
|
||||
*
|
||||
*/
|
||||
search: function (query, type, searchFrom, canceler, dataTypeId) {
|
||||
search: function (query, type, searchFrom, canceler, dataTypeKey) {
|
||||
|
||||
var args = [{ query: query }, { type: type }];
|
||||
if (searchFrom) {
|
||||
args.push({ searchFrom: searchFrom });
|
||||
}
|
||||
|
||||
if (dataTypeId) {
|
||||
args.push({ dataTypeId: dataTypeId });
|
||||
if (dataTypeKey) {
|
||||
args.push({ dataTypeKey: dataTypeKey });
|
||||
}
|
||||
|
||||
var httpConfig = {};
|
||||
|
||||
@@ -67,7 +67,7 @@ angular.module('umbraco.services')
|
||||
throw "args.term is required";
|
||||
}
|
||||
|
||||
return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) {
|
||||
return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) {
|
||||
_.each(data, function (item) {
|
||||
searchResultFormatter.configureContentResult(item);
|
||||
});
|
||||
@@ -92,7 +92,7 @@ angular.module('umbraco.services')
|
||||
throw "args.term is required";
|
||||
}
|
||||
|
||||
return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) {
|
||||
return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) {
|
||||
_.each(data, function (item) {
|
||||
searchResultFormatter.configureMediaResult(item);
|
||||
});
|
||||
|
||||
@@ -195,7 +195,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
//distraction-free = Theme: inlite, inline: true
|
||||
switch (args.mode) {
|
||||
case "classic":
|
||||
modeTheme = "modern";
|
||||
//modeTheme = "modern";
|
||||
modeTheme = "silver";
|
||||
modeInline = false;
|
||||
break;
|
||||
|
||||
@@ -206,7 +207,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
|
||||
default:
|
||||
//Will default to 'classic'
|
||||
modeTheme = "modern";
|
||||
//modeTheme = "modern";
|
||||
modeTheme = "silver";
|
||||
modeInline = false;
|
||||
break;
|
||||
}
|
||||
@@ -336,10 +338,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
* @param {Object} editor the TinyMCE editor instance
|
||||
*/
|
||||
createInsertEmbeddedMedia: function (editor, callback) {
|
||||
editor.addButton('umbembeddialog', {
|
||||
editor.ui.registry.addButton('umbembeddialog', {
|
||||
icon: 'custom icon-tv',
|
||||
tooltip: 'Embed',
|
||||
onclick: function () {
|
||||
onAction: function (buttonApi) {
|
||||
if (callback) {
|
||||
angularHelper.safeApply($rootScope, function() {
|
||||
callback();
|
||||
@@ -356,10 +358,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
|
||||
createAceCodeEditor: function(editor, callback){
|
||||
|
||||
editor.addButton("ace", {
|
||||
editor.ui.registry.addButton("ace", {
|
||||
icon: "code",
|
||||
tooltip: "View Source Code",
|
||||
onclick: function(){
|
||||
onAction: function(buttonApi){
|
||||
if (callback) {
|
||||
angularHelper.safeApply($rootScope, function() {
|
||||
callback();
|
||||
@@ -381,11 +383,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
* @param {Object} editor the TinyMCE editor instance
|
||||
*/
|
||||
createMediaPicker: function (editor, callback) {
|
||||
editor.addButton('umbmediapicker', {
|
||||
editor.ui.registry.addButton('umbmediapicker', {
|
||||
icon: 'custom icon-picture',
|
||||
tooltip: 'Media Picker',
|
||||
stateSelector: 'img',
|
||||
onclick: function () {
|
||||
onAction: function (buttonApi) {
|
||||
|
||||
|
||||
var selectedElm = editor.selection.getNode(),
|
||||
@@ -428,7 +430,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
id: '__mcenew',
|
||||
'data-udi': img.udi
|
||||
};
|
||||
|
||||
|
||||
editor.selection.setContent(editor.dom.createHTML('img', data));
|
||||
|
||||
$timeout(function () {
|
||||
@@ -447,9 +449,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
}
|
||||
}
|
||||
editor.dom.setAttrib(imgElm, 'id', null);
|
||||
|
||||
|
||||
editor.fire('Change');
|
||||
|
||||
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
@@ -515,13 +517,13 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
}
|
||||
|
||||
/** Adds the button instance */
|
||||
editor.addButton('umbmacro', {
|
||||
editor.ui.registry.addButton('umbmacro', {
|
||||
icon: 'custom icon-settings-alt',
|
||||
tooltip: 'Insert macro',
|
||||
onPostRender: function () {
|
||||
|
||||
let ctrl = this;
|
||||
|
||||
|
||||
/**
|
||||
* Check if the macro is currently selected and toggle the menu button
|
||||
*/
|
||||
@@ -543,7 +545,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
},
|
||||
|
||||
/** The insert macro button click event handler */
|
||||
onclick: function () {
|
||||
onAction: function (buttonApi) {
|
||||
|
||||
var dialogData = {
|
||||
//flag for use in rte so we only show macros flagged for the editor
|
||||
@@ -865,29 +867,29 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
});
|
||||
}
|
||||
|
||||
editor.addButton('link', {
|
||||
editor.ui.registry.addButton('link', {
|
||||
icon: 'link',
|
||||
tooltip: 'Insert/edit link',
|
||||
shortcut: 'Ctrl+K',
|
||||
onclick: createLinkList(showDialog),
|
||||
onAction: createLinkList(showDialog),
|
||||
stateSelector: 'a[href]'
|
||||
});
|
||||
|
||||
editor.addButton('unlink', {
|
||||
icon: 'unlink',
|
||||
tooltip: 'Remove link',
|
||||
cmd: 'unlink',
|
||||
stateSelector: 'a[href]'
|
||||
});
|
||||
// editor.ui.registry.addButton('unlink', {
|
||||
// icon: 'unlink',
|
||||
// tooltip: 'Remove link',
|
||||
// cmd: 'unlink',
|
||||
// stateSelector: 'a[href]'
|
||||
// });
|
||||
|
||||
editor.addShortcut('Ctrl+K', '', createLinkList(showDialog));
|
||||
this.showDialog = showDialog;
|
||||
|
||||
editor.addMenuItem('link', {
|
||||
editor.ui.registry.addMenuItem('link', {
|
||||
icon: 'link',
|
||||
text: 'Insert link',
|
||||
shortcut: 'Ctrl+K',
|
||||
onclick: createLinkList(showDialog),
|
||||
onAction: createLinkList(showDialog),
|
||||
stateSelector: 'a[href]',
|
||||
context: 'insert',
|
||||
prependToContext: true
|
||||
@@ -1121,7 +1123,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
entityResource.getAnchors(args.model.value).then(function (anchorValues) {
|
||||
var linkPicker = {
|
||||
currentTarget: currentTarget,
|
||||
dataTypeId: args.model.dataTypeId,
|
||||
dataTypeKey: args.model.dataTypeKey,
|
||||
ignoreUserStartNodes: args.model.config.ignoreUserStartNodes,
|
||||
anchors: anchorValues,
|
||||
submit: function (model) {
|
||||
@@ -1134,7 +1136,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
};
|
||||
editorService.linkPicker(linkPicker);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
//Create the insert media plugin
|
||||
@@ -1159,7 +1161,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
disableFolderSelect: true,
|
||||
startNodeId: startNodeId,
|
||||
startNodeIsVirtual: startNodeIsVirtual,
|
||||
dataTypeId: args.model.dataTypeId,
|
||||
dataTypeKey: args.model.dataTypeKey,
|
||||
submit: function (model) {
|
||||
self.insertMediaInEditor(args.editor, model.selection[0]);
|
||||
editorService.close();
|
||||
|
||||
+23
-6
@@ -32,15 +32,32 @@
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.umb-editor-sub-header.-umb-sticky-bar {
|
||||
box-shadow: 0 6px 3px -3px rgba(0,0,0,.16);
|
||||
[umb-sticky-bar] {
|
||||
transition: box-shadow 240ms;
|
||||
margin-top: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
top: calc(@appHeaderHeight + @editorHeaderHeight);
|
||||
position:sticky;
|
||||
z-index: 99;
|
||||
|
||||
.umb-editor--infinityMode & {
|
||||
top: calc(@editorHeaderHeight);
|
||||
&.umb-sticky-bar--active {
|
||||
box-shadow: 0 6px 3px -3px rgba(0,0,0,.16);
|
||||
}
|
||||
}
|
||||
|
||||
.umb-sticky-sentinel {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
|
||||
&.-top {
|
||||
top:0px;
|
||||
height:1px;
|
||||
}
|
||||
|
||||
&.-bottom {
|
||||
bottom:50px;
|
||||
height:10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// --------------------------------------------------
|
||||
.umb-property-editor {
|
||||
width: 100%;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.umb-property-editor-tiny {
|
||||
@@ -165,8 +166,6 @@
|
||||
.sp-replacer {
|
||||
display: inline-flex;
|
||||
margin-right: 18px;
|
||||
border: solid 1px @gray-8;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
label {
|
||||
|
||||
+5
-5
@@ -20,14 +20,14 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
$scope.model.title = value;
|
||||
});
|
||||
}
|
||||
$scope.customTreeParams = dialogOptions.dataTypeId ? "dataTypeId=" + dialogOptions.dataTypeId : "";
|
||||
$scope.customTreeParams = dialogOptions.dataTypeKey ? "dataTypeKey=" + dialogOptions.dataTypeKey : "";
|
||||
$scope.dialogTreeApi = {};
|
||||
$scope.model.target = {};
|
||||
$scope.searchInfo = {
|
||||
searchFromId: null,
|
||||
searchFromName: null,
|
||||
showSearch: false,
|
||||
dataTypeId: dialogOptions.dataTypeId,
|
||||
dataTypeKey: dialogOptions.dataTypeKey,
|
||||
results: [],
|
||||
selectedSearchResults: []
|
||||
};
|
||||
@@ -59,10 +59,10 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
if (dialogOptions.currentTarget) {
|
||||
// clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target
|
||||
$scope.model.target = angular.copy(dialogOptions.currentTarget);
|
||||
//if we have a node ID, we fetch the current node to build the form data
|
||||
// if we have a node ID, we fetch the current node to build the form data
|
||||
if ($scope.model.target.id || $scope.model.target.udi) {
|
||||
|
||||
//will be either a udi or an int
|
||||
// will be either a udi or an int
|
||||
var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
|
||||
|
||||
if ($scope.model.target.udi) {
|
||||
@@ -175,7 +175,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
|
||||
var mediaPicker = {
|
||||
startNodeId: startNodeId,
|
||||
startNodeIsVirtual: startNodeIsVirtual,
|
||||
dataTypeId: dialogOptions.dataTypeId,
|
||||
dataTypeKey: dialogOptions.dataTypeKey,
|
||||
submit: function (model) {
|
||||
var media = model.selection[0];
|
||||
|
||||
|
||||
+16
-16
@@ -25,7 +25,7 @@
|
||||
ng-model="model.target.url"
|
||||
ng-disabled="model.target.id || model.target.udi" />
|
||||
</umb-control-group>
|
||||
|
||||
|
||||
<umb-control-group label="@defaultdialogs_anchorLinkPicker">
|
||||
<input type="text"
|
||||
list="anchors"
|
||||
@@ -33,13 +33,13 @@
|
||||
placeholder="@placeholders_anchor"
|
||||
class="umb-property-editor umb-textstring"
|
||||
ng-model="model.target.anchor" />
|
||||
|
||||
|
||||
<datalist id="anchors">
|
||||
<option value="{{a}}" ng-repeat="a in anchorValues"></option>
|
||||
</datalist>
|
||||
</umb-control-group>
|
||||
</div>
|
||||
|
||||
|
||||
<umb-control-group label="@defaultdialogs_nodeNameLinkPicker">
|
||||
<input type="text"
|
||||
localize="placeholder"
|
||||
@@ -47,7 +47,7 @@
|
||||
class="umb-property-editor umb-textstring"
|
||||
ng-model="model.target.name" />
|
||||
</umb-control-group>
|
||||
|
||||
|
||||
<umb-control-group ng-if="showTarget" label="@content_target">
|
||||
<umb-checkbox
|
||||
model="vm.openInNewWindow"
|
||||
@@ -58,28 +58,28 @@
|
||||
|
||||
<div class="umb-control-group">
|
||||
<h5><localize key="defaultdialogs_linkToPage">Link to page</localize></h5>
|
||||
|
||||
|
||||
<div ng-hide="miniListView">
|
||||
<umb-tree-search-box
|
||||
<umb-tree-search-box
|
||||
hide-search-callback="hideSearch"
|
||||
search-callback="onSearchResults"
|
||||
search-from-id="{{searchInfo.searchFromId}}"
|
||||
search-from-name="{{searchInfo.searchFromName}}"
|
||||
datatype-id="{{searchInfo.dataTypeId}}"
|
||||
datatype-key="{{searchInfo.dataTypeKey}}"
|
||||
show-search="{{searchInfo.showSearch}}"
|
||||
section="{{section}}">
|
||||
</umb-tree-search-box>
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
<umb-tree-search-results
|
||||
|
||||
<umb-tree-search-results
|
||||
ng-if="searchInfo.showSearch"
|
||||
results="searchInfo.results"
|
||||
select-result-callback="selectResult">
|
||||
</umb-tree-search-results>
|
||||
|
||||
|
||||
<div ng-hide="searchInfo.showSearch">
|
||||
<umb-tree
|
||||
<umb-tree
|
||||
section="content"
|
||||
hideheader="true"
|
||||
hideoptions="true"
|
||||
@@ -92,17 +92,17 @@
|
||||
</umb-tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<umb-mini-list-view
|
||||
|
||||
<umb-mini-list-view
|
||||
ng-if="miniListView"
|
||||
node="miniListView"
|
||||
entity-type="Document"
|
||||
on-select="selectListViewNode(node)"
|
||||
on-close="closeMiniListView()">
|
||||
</umb-mini-list-view>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="umb-control-group">
|
||||
<h5><localize key="defaultdialogs_linkToMedia">Link to media</localize></h5>
|
||||
<a href ng-click="switchToMediaPicker()" class="btn">
|
||||
|
||||
+8
-3
@@ -51,13 +51,17 @@ angular.module("umbraco")
|
||||
$scope.acceptedMediatypes = types;
|
||||
});
|
||||
|
||||
var dataTypeKey = null;
|
||||
if($scope.model && $scope.model.dataTypeKey) {
|
||||
dataTypeKey = $scope.model.dataTypeKey;
|
||||
}
|
||||
$scope.searchOptions = {
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
filter: '',
|
||||
dataTypeId: $scope.model.dataTypeId
|
||||
dataTypeKey: dataTypeKey
|
||||
};
|
||||
|
||||
//preload selected item
|
||||
@@ -160,7 +164,7 @@ angular.module("umbraco")
|
||||
}
|
||||
|
||||
if (folder.id > 0) {
|
||||
entityResource.getAncestors(folder.id, "media", null, { dataTypeId: $scope.model.dataTypeId })
|
||||
entityResource.getAncestors(folder.id, "media", null, { dataTypeKey: dataTypeKey })
|
||||
.then(function (anc) {
|
||||
$scope.path = _.filter(anc,
|
||||
function (f) {
|
||||
@@ -311,6 +315,7 @@ angular.module("umbraco")
|
||||
if ($scope.searchOptions.filter) {
|
||||
searchMedia();
|
||||
} else {
|
||||
|
||||
// reset pagination
|
||||
$scope.searchOptions = {
|
||||
pageNumber: 1,
|
||||
@@ -318,7 +323,7 @@ angular.module("umbraco")
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
filter: '',
|
||||
dataTypeId: $scope.model.dataTypeId
|
||||
dataTypeKey: dataTypeKey
|
||||
};
|
||||
getChildren($scope.currentFolder.id);
|
||||
}
|
||||
|
||||
+4
-4
@@ -28,12 +28,12 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
|
||||
vm.treeAlias = $scope.model.treeAlias;
|
||||
vm.multiPicker = $scope.model.multiPicker;
|
||||
vm.hideHeader = (typeof $scope.model.hideHeader) === "boolean" ? $scope.model.hideHeader : true;
|
||||
vm.dataTypeId = $scope.model.dataTypeId;
|
||||
vm.dataTypeKey = $scope.model.dataTypeKey;
|
||||
vm.searchInfo = {
|
||||
searchFromId: $scope.model.startNodeId,
|
||||
searchFromName: null,
|
||||
showSearch: false,
|
||||
dataTypeId: vm.dataTypeId,
|
||||
dataTypeKey: vm.dataTypeKey,
|
||||
results: [],
|
||||
selectedSearchResults: []
|
||||
}
|
||||
@@ -192,8 +192,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
|
||||
if (vm.selectedLanguage && vm.selectedLanguage.id) {
|
||||
queryParams["culture"] = vm.selectedLanguage.culture;
|
||||
}
|
||||
if (vm.dataTypeId) {
|
||||
queryParams["dataTypeId"] = vm.dataTypeId;
|
||||
if (vm.dataTypeKey) {
|
||||
queryParams["dataTypeKey"] = vm.dataTypeKey;
|
||||
}
|
||||
|
||||
var queryString = $.param(queryParams); //create the query string from the params object
|
||||
|
||||
+11
-11
@@ -27,7 +27,7 @@
|
||||
<a ng-click="vm.selectLanguage(language)" ng-repeat="language in vm.languages" href="">{{language.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="umb-control-group">
|
||||
<umb-tree-search-box
|
||||
ng-if="vm.enableSearh"
|
||||
@@ -36,22 +36,22 @@
|
||||
search-from-id="{{vm.searchInfo.searchFromId}}"
|
||||
search-from-name="{{vm.searchInfo.searchFromName}}"
|
||||
show-search="{{vm.searchInfo.showSearch}}"
|
||||
datatype-id="{{vm.searchInfo.dataTypeId}}"
|
||||
datatype-key="{{vm.searchInfo.dataTypeKey}}"
|
||||
section="{{vm.section}}">
|
||||
</umb-tree-search-box>
|
||||
</div>
|
||||
|
||||
|
||||
<umb-tree-search-results ng-if="vm.searchInfo.showSearch"
|
||||
results="vm.searchInfo.results"
|
||||
select-result-callback="vm.selectResult">
|
||||
</umb-tree-search-results>
|
||||
|
||||
|
||||
<umb-empty-state ng-if="!vm.hasItems && vm.emptyStateMessage" position="center">
|
||||
{{ vm.emptyStateMessage }}
|
||||
</umb-empty-state>
|
||||
|
||||
|
||||
<div ng-if="vm.treeReady" ng-hide="vm.searchInfo.showSearch" ng-animate="'tree-fade-out'">
|
||||
<umb-tree
|
||||
<umb-tree
|
||||
section="{{vm.section}}"
|
||||
treealias="{{vm.treeAlias}}"
|
||||
hideheader="{{vm.hideHeader}}"
|
||||
@@ -65,10 +65,10 @@
|
||||
api="vm.dialogTreeApi">
|
||||
</umb-tree>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<umb-mini-list-view
|
||||
|
||||
<umb-mini-list-view
|
||||
ng-if="vm.miniListView"
|
||||
node="vm.miniListView"
|
||||
entity-type="{{vm.entityType}}"
|
||||
@@ -93,7 +93,7 @@
|
||||
shortcut="esc"
|
||||
action="vm.close()">
|
||||
</umb-button>
|
||||
|
||||
|
||||
<umb-button
|
||||
ng-if="vm.multiPicker"
|
||||
type="button"
|
||||
@@ -101,7 +101,7 @@
|
||||
label-key="general_submit"
|
||||
action="vm.submit(model)">
|
||||
</umb-button>
|
||||
|
||||
|
||||
</umb-editor-footer-content-right>
|
||||
|
||||
</umb-editor-footer>
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
<div
|
||||
class="umb-editor-sub-header umb-editor-sub-header--{{appearance}}"
|
||||
umb-sticky-bar
|
||||
scrollable-container=".umb-editor-container"
|
||||
ng-transclude>
|
||||
</div>
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
vm.numberOfErrors = 0;
|
||||
vm.commonLogMessages = [];
|
||||
vm.commonLogMessagesCount = 10;
|
||||
vm.dateRangeLabel = "";
|
||||
|
||||
// ChartJS Options - for count/overview of log distribution
|
||||
vm.logTypeLabels = ["Info", "Debug", "Warning", "Error", "Critical"];
|
||||
vm.logTypeLabels = ["Debug", "Info", "Warning", "Error", "Fatal"];
|
||||
vm.logTypeData = [0, 0, 0, 0, 0];
|
||||
vm.logTypeColors = [ '#dcdcdc', '#97bbcd', '#46bfbd', '#fdb45c', '#f7464a'];
|
||||
vm.logTypeColors = ['#eaddd5', '#2bc37c', '#3544b1', '#ff9412', '#d42054'];
|
||||
vm.chartOptions = {
|
||||
legend: {
|
||||
display: true,
|
||||
@@ -23,35 +24,42 @@
|
||||
};
|
||||
|
||||
let querystring = $location.search();
|
||||
if(querystring.startDate){
|
||||
if (querystring.startDate) {
|
||||
vm.startDate = querystring.startDate;
|
||||
}else{
|
||||
vm.dateRangeLabel = getDateRangeLabel("Selected Time Period");
|
||||
} else {
|
||||
vm.startDate = new Date(Date.now());
|
||||
vm.startDate.setDate(vm.startDate.getDate()-1);
|
||||
vm.startDate.setDate(vm.startDate.getDate() - 1);
|
||||
vm.startDate = vm.startDate.toIsoDateString();
|
||||
vm.dateRangeLabel = getDateRangeLabel("Today");
|
||||
}
|
||||
|
||||
if(querystring.endDate){
|
||||
if (querystring.endDate) {
|
||||
vm.endDate = querystring.endDate;
|
||||
}else{
|
||||
|
||||
if (querystring.endDate === querystring.startDate) {
|
||||
vm.dateRangeLabel = getDateRangeLabel("Selected Date");
|
||||
}
|
||||
} else {
|
||||
vm.endDate = new Date(Date.now()).toIsoDateString();
|
||||
}
|
||||
vm.period = [vm.startDate, vm.endDate];
|
||||
|
||||
vm.period = [vm.startDate, vm.endDate];
|
||||
|
||||
//functions
|
||||
vm.searchLogQuery = searchLogQuery;
|
||||
vm.findMessageTemplate = findMessageTemplate;
|
||||
|
||||
vm.searchErrors = searchErrors;
|
||||
|
||||
function preFlightCheck(){
|
||||
vm.loading = true;
|
||||
//Do our pre-flight check (to see if we can view logs)
|
||||
//IE the log file is NOT too big such as 1GB & crash the site
|
||||
logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function(result){
|
||||
logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function (result) {
|
||||
vm.loading = false;
|
||||
vm.canLoadLogs = result;
|
||||
|
||||
if(result){
|
||||
if (result) {
|
||||
//Can view logs - so initalise
|
||||
init();
|
||||
}
|
||||
@@ -62,39 +70,39 @@
|
||||
function init() {
|
||||
|
||||
vm.loading = true;
|
||||
|
||||
|
||||
var savedSearches = logViewerResource.getSavedSearches().then(function (data) {
|
||||
vm.searches = data;
|
||||
},
|
||||
// fallback to some defaults if error from API response
|
||||
function () {
|
||||
vm.searches = [
|
||||
{
|
||||
"name": "Find all logs where the Level is NOT Verbose and NOT Debug",
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
vm.searches = data;
|
||||
},
|
||||
// fallback to some defaults if error from API response
|
||||
function () {
|
||||
vm.searches = [
|
||||
{
|
||||
"name": "Find all logs where the Level is NOT Verbose and NOT Debug",
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration'",
|
||||
"query": "Has(Duration)"
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration'",
|
||||
"query": "Has(Duration)"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
|
||||
"query": "Has(Duration) and Duration > 1000"
|
||||
{
|
||||
"name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
|
||||
"query": "Has(Duration) and Duration > 1000"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that are from the namespace 'Umbraco.Core'",
|
||||
"query": "StartsWith(SourceContext, 'Umbraco.Core')"
|
||||
{
|
||||
"name": "Find all logs that are from the namespace 'Umbraco.Core'",
|
||||
"query": "StartsWith(SourceContext, 'Umbraco.Core')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that use a specific log message template",
|
||||
"query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
|
||||
{
|
||||
"name": "Find all logs that use a specific log message template",
|
||||
"query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'"
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
var numOfErrors = logViewerResource.getNumberOfErrors(vm.startDate, vm.endDate).then(function (data) {
|
||||
vm.numberOfErrors = data;
|
||||
@@ -102,41 +110,56 @@
|
||||
|
||||
var logCounts = logViewerResource.getLogLevelCounts(vm.startDate, vm.endDate).then(function (data) {
|
||||
vm.logTypeData = [];
|
||||
vm.logTypeData.push(data.Information);
|
||||
vm.logTypeData.push(data.Debug);
|
||||
vm.logTypeData.push(data.Warning);
|
||||
vm.logTypeData.push(data.Error);
|
||||
vm.logTypeData.push(data.Fatal);
|
||||
|
||||
for (let [key, value] of Object.entries(data)) {
|
||||
const index = vm.logTypeLabels.findIndex(x => key.startsWith(x));
|
||||
if (index > -1) {
|
||||
vm.logTypeData[index] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function(data){
|
||||
var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function (data) {
|
||||
vm.commonLogMessages = data;
|
||||
});
|
||||
|
||||
//Set loading indicatior to false when these 3 queries complete
|
||||
$q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function(data) {
|
||||
//Set loading indicator to false when these 3 queries complete
|
||||
$q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function () {
|
||||
vm.loading = false;
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
navigationService.syncTree({ tree: "logViewer", path: "-1" });
|
||||
navigationService.syncTree({
|
||||
tree: "logViewer",
|
||||
path: "-1"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function searchLogQuery(logQuery){
|
||||
$location.path("/settings/logViewer/search").search({lq: logQuery, startDate: vm.startDate, endDate: vm.endDate});
|
||||
function searchLogQuery(logQuery) {
|
||||
$location.path("/settings/logViewer/search").search({
|
||||
lq: logQuery,
|
||||
startDate: vm.startDate,
|
||||
endDate: vm.endDate
|
||||
});
|
||||
}
|
||||
|
||||
function findMessageTemplate(template){
|
||||
function findMessageTemplate(template) {
|
||||
var logQuery = "@MessageTemplate='" + template.MessageTemplate + "'";
|
||||
searchLogQuery(logQuery);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getDateRangeLabel(suffix) {
|
||||
return "Log Overview for " + suffix;
|
||||
}
|
||||
|
||||
function searchErrors(){
|
||||
var logQuery = "@Level='Fatal' or @Level='Error' or Has(@Exception)";
|
||||
searchLogQuery(logQuery);
|
||||
}
|
||||
|
||||
preFlightCheck();
|
||||
|
||||
|
||||
/////////////////////
|
||||
|
||||
vm.config = {
|
||||
@@ -147,20 +170,21 @@
|
||||
maxDate: "today",
|
||||
conjunction: " to "
|
||||
};
|
||||
|
||||
vm.dateRangeChange = function(selectedDates, dateStr, instance) {
|
||||
|
||||
if(selectedDates.length > 0){
|
||||
vm.startDate = selectedDates[0].toIsoDateString();
|
||||
vm.endDate = selectedDates[selectedDates.length-1].toIsoDateString(); // Take the last date as end
|
||||
|
||||
if(vm.startDate === vm.endDate){
|
||||
vm.period = [vm.startDate];
|
||||
}else{
|
||||
vm.period = [vm.startDate, vm.endDate];
|
||||
}
|
||||
|
||||
preFlightCheck();
|
||||
vm.dateRangeChange = function (selectedDates, dateStr, instance) {
|
||||
|
||||
if (selectedDates.length > 0) {
|
||||
|
||||
// Update view by re-requesting route with updated querystring.
|
||||
// By doing this we make sure the URL matches the selected time period, aiding sharing the link.
|
||||
// Also resolves a minor layout issue where the " to " conjunction between the selected dates
|
||||
// is collapsed to a comma.
|
||||
const startDate = selectedDates[0].toIsoDateString();
|
||||
const endDate = selectedDates[selectedDates.length - 1].toIsoDateString(); // Take the last date as end
|
||||
$location.path("/settings/logViewer/overview").search({
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<umb-editor-view footer="false">
|
||||
|
||||
<umb-editor-header
|
||||
name="'Log Overview for Today'"
|
||||
name="vm.dateRangeLabel"
|
||||
name-locked="true"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
@@ -74,17 +74,16 @@
|
||||
<umb-box>
|
||||
<umb-box-header title="Time Period"></umb-box-header>
|
||||
|
||||
<umb-flatpickr
|
||||
class="datepicker"
|
||||
ng-model="vm.period"
|
||||
options="vm.config"
|
||||
on-close="vm.dateRangeChange(selectedDates, dateStr, instance)">
|
||||
<umb-flatpickr class="datepicker"
|
||||
ng-model="vm.period"
|
||||
options="vm.config"
|
||||
on-close="vm.dateRangeChange(selectedDates, dateStr, instance)">
|
||||
</umb-flatpickr>
|
||||
</umb-box>
|
||||
|
||||
<div ng-show=" vm.canLoadLogs">
|
||||
<!-- No of Errors -->
|
||||
<umb-box ng-click="vm.searchLogQuery('Has(@Exception)')" style="cursor:pointer;">
|
||||
<umb-box ng-click="vm.searchErrors()" style="cursor:pointer;">
|
||||
<umb-box-header title="Number of Errors"></umb-box-header>
|
||||
<umb-box-content class="block-form" style="font-size: 40px; font-weight:900; text-align:center; color:#fe6561;">
|
||||
{{ vm.numberOfErrors }}
|
||||
|
||||
@@ -11,26 +11,28 @@
|
||||
vm.showBackButton = true;
|
||||
vm.page = {};
|
||||
|
||||
// this array is also used to map the logTypeColor param onto the log items
|
||||
// in setLogTypeColors()
|
||||
vm.logLevels = [
|
||||
{
|
||||
name: 'Verbose',
|
||||
logTypeColor: 'gray'
|
||||
logTypeColor: ''
|
||||
},
|
||||
{
|
||||
name: 'Debug',
|
||||
logTypeColor: 'secondary'
|
||||
logTypeColor: 'gray'
|
||||
},
|
||||
{
|
||||
name: 'Information',
|
||||
logTypeColor: 'primary'
|
||||
logTypeColor: 'success'
|
||||
},
|
||||
{
|
||||
name: 'Warning',
|
||||
logTypeColor: 'warning'
|
||||
logTypeColor: 'primary'
|
||||
},
|
||||
{
|
||||
name: 'Error',
|
||||
logTypeColor: 'danger'
|
||||
logTypeColor: 'warning'
|
||||
},
|
||||
{
|
||||
name: 'Fatal',
|
||||
@@ -118,7 +120,7 @@
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)",
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
},
|
||||
{
|
||||
@@ -173,25 +175,8 @@
|
||||
}
|
||||
|
||||
function setLogTypeColor(logItems) {
|
||||
angular.forEach(logItems, function (log) {
|
||||
switch (log.Level) {
|
||||
case "Information":
|
||||
log.logTypeColor = "primary";
|
||||
break;
|
||||
case "Debug":
|
||||
log.logTypeColor = "secondary";
|
||||
break;
|
||||
case "Warning":
|
||||
log.logTypeColor = "warning";
|
||||
break;
|
||||
case "Fatal":
|
||||
case "Error":
|
||||
log.logTypeColor = "danger";
|
||||
break;
|
||||
default:
|
||||
log.logTypeColor = "gray";
|
||||
}
|
||||
});
|
||||
logItems.forEach(logItem =>
|
||||
logItem.logTypeColor = vm.logLevels.find(x => x.name === logItem.Level).logTypeColor);
|
||||
}
|
||||
|
||||
function getFilterName(array) {
|
||||
|
||||
@@ -139,7 +139,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
|
||||
|
||||
//anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here
|
||||
var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' });
|
||||
if (!passwordProp.value.reset) {
|
||||
if (passwordProp && passwordProp.value && !passwordProp.value.reset) {
|
||||
//so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered
|
||||
passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword;
|
||||
}
|
||||
|
||||
+3
-3
@@ -86,7 +86,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
showOpenButton: false,
|
||||
showEditButton: false,
|
||||
showPathOnHover: false,
|
||||
dataTypeId: null,
|
||||
dataTypeKey: null,
|
||||
maxNumber: 1,
|
||||
minNumber: 0,
|
||||
startNode: {
|
||||
@@ -140,7 +140,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
entityType: entityType,
|
||||
filterCssClass: "not-allowed not-published",
|
||||
startNodeId: null,
|
||||
dataTypeId: $scope.model.dataTypeId,
|
||||
dataTypeKey: $scope.model.dataTypeKey,
|
||||
currentNode: editorState ? editorState.current : null,
|
||||
callback: function (data) {
|
||||
if (angular.isArray(data)) {
|
||||
@@ -162,7 +162,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
// pre-value config on to the dialog options
|
||||
angular.extend(dialogOptions, $scope.model.config);
|
||||
|
||||
dialogOptions.dataTypeId = $scope.model.dataTypeId;
|
||||
dialogOptions.dataTypeKey = $scope.model.dataTypeKey;
|
||||
|
||||
// if we can't pick more than one item, explicitly disable multiPicker in the dialog options
|
||||
if ($scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) === 1) {
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ angular.module("umbraco")
|
||||
showDetails: true,
|
||||
disableFolderSelect: true,
|
||||
onlyImages: true,
|
||||
dataTypeId: $scope.model.dataTypeId,
|
||||
dataTypeKey: $scope.model.dataTypeKey,
|
||||
submit: function(model) {
|
||||
var selectedImage = model.selection[0];
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
configuration="model.config.rte"
|
||||
value="control.value"
|
||||
unique-id="control.$uniqueId"
|
||||
datatype-id="{{model.dataTypeId}}"
|
||||
datatype-key="{{model.dataTypeKey}}"
|
||||
ignore-user-start-nodes="{{model.config.ignoreUserStartNodes}}">
|
||||
</grid-rte>
|
||||
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
|
||||
var mediaPicker = {
|
||||
startNodeId: $scope.model.config.startNodeId,
|
||||
startNodeIsVirtual: $scope.model.config.startNodeIsVirtual,
|
||||
dataTypeId: $scope.model.dataTypeId,
|
||||
dataTypeKey: $scope.model.dataTypeKey,
|
||||
multiPicker: multiPicker,
|
||||
onlyImages: onlyImages,
|
||||
disableFolderSelect: disableFolderSelect,
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en
|
||||
|
||||
var linkPicker = {
|
||||
currentTarget: target,
|
||||
dataTypeId: $scope.model.dataTypeId,
|
||||
dataTypeKey: $scope.model.dataTypeKey,
|
||||
ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes,
|
||||
submit: function (model) {
|
||||
if (model.target.url || model.target.anchor) {
|
||||
|
||||
+2
@@ -19,6 +19,7 @@
|
||||
$scope.hasError = false;
|
||||
|
||||
$scope.internal = function ($event) {
|
||||
|
||||
$scope.currentEditLink = null;
|
||||
|
||||
var contentPicker = {
|
||||
@@ -41,6 +42,7 @@
|
||||
};
|
||||
|
||||
$scope.selectInternal = function ($event, link) {
|
||||
|
||||
$scope.currentEditLink = link;
|
||||
|
||||
var contentPicker = {
|
||||
|
||||
@@ -48,18 +48,19 @@ angular.module("umbraco")
|
||||
$q.all(promises).then(function (result) {
|
||||
|
||||
var standardConfig = result[promises.length - 1];
|
||||
|
||||
|
||||
if (height !== null) {
|
||||
standardConfig.plugins.splice(standardConfig.plugins.indexOf("autoresize"), 1);
|
||||
}
|
||||
|
||||
|
||||
//create a baseline Config to extend upon
|
||||
var baseLineConfigObj = {
|
||||
maxImageSize: editorConfig.maxImageSize,
|
||||
width: width,
|
||||
height: height
|
||||
maxImageSize: editorConfig.maxImageSize
|
||||
//width: width,
|
||||
//height: height
|
||||
};
|
||||
|
||||
|
||||
angular.extend(baseLineConfigObj, standardConfig);
|
||||
|
||||
baseLineConfigObj.setup = function (editor) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div ng-controller="Umbraco.PropertyEditors.RTEController" class="umb-property-editor umb-rte">
|
||||
<umb-load-indicator ng-if="isLoading"></umb-load-indicator>
|
||||
|
||||
<div ng-style="{ visibility : isLoading ? 'hidden' : 'visible', width :containerWidth, height: containerHeight, overflow: containerOverflow}"
|
||||
<div ng-style="{ visibility : isLoading ? 'hidden' : 'visible', overflow: containerOverflow}"
|
||||
id="{{textAreaHtmlId}}" class="umb-rte-editor"></div>
|
||||
</div>
|
||||
|
||||
@@ -345,9 +345,9 @@
|
||||
<WebProjectProperties>
|
||||
<UseIIS>False</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>8100</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>8200</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:8100</IISUrl>
|
||||
<IISUrl>http://localhost:8200</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"query": "Not(@Level='Verbose') and Not(@Level='Debug')"
|
||||
},
|
||||
{
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)",
|
||||
"name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)",
|
||||
"query": "Has(@Exception)"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -26,12 +26,13 @@
|
||||
<add key="serilog:write-to:File.restrictedToMinimumLevel" value="Debug" />
|
||||
<add key="serilog:write-to:File.retainedFileCountLimit" value="32" />--> <!-- Number of log files to keep (or remove value to keep all files) -->
|
||||
<!--<add key="serilog:write-to:File.rollingInterval" value="Day" />--> <!-- Create a new log file every Minute/Hour/Day/Month/Year/infinite -->
|
||||
<!--<add key="serilog:write-to:File.outputTemplate" value="{Timestamp:yyyy-MM-dd HH:mm:ss,fff} [P{ProcessId}/D{AppDomainId}/T{ThreadId}] {Log4NetLevel} {SourceContext} - {Message:lj}{NewLine}{Exception}" /> -->
|
||||
|
||||
<!-- Filters all above sink's to use this expression -->
|
||||
<!-- Common use case is to include SourceType starting with your own namespace -->
|
||||
<!--
|
||||
<add key="serilog:using:FilterExpressions" value="Serilog.Filters.Expressions" />
|
||||
<add key="serilog:filter:ByIncluding.expression" value="StartsWith(SourceContext, 'Umbraco.Core')" />
|
||||
<add key="serilog:filter:ByIncludingOnly.expression" value="StartsWith(SourceContext, 'Umbraco.Core')" />
|
||||
-->
|
||||
|
||||
</appSettings>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -96,6 +97,7 @@ namespace Umbraco.Web.Cache
|
||||
base.Refresh(payloads);
|
||||
}
|
||||
|
||||
|
||||
public override void RefreshAll()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Umbraco.Web.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[ValidateUmbracoFormRouteString]
|
||||
public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Umbraco.Web.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[ValidateUmbracoFormRouteString]
|
||||
public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Umbraco.Web.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[ValidateUmbracoFormRouteString]
|
||||
public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model)
|
||||
{
|
||||
var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Umbraco.Web.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[ValidateUmbracoFormRouteString]
|
||||
public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace Umbraco.Web.Editors
|
||||
//id is passed in eventually we'll probably want to support GUID + Udi too
|
||||
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)),
|
||||
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)),
|
||||
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetUrlAndAnchors", "id", typeof(int), typeof(Guid), typeof(Udi)),
|
||||
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)),
|
||||
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[]))));
|
||||
}
|
||||
@@ -104,10 +105,10 @@ namespace Umbraco.Web.Editors
|
||||
/// <param name="searchFrom">
|
||||
/// A starting point for the search, generally a node id, but for members this is a member type alias
|
||||
/// </param>
|
||||
/// <param name="dataTypeId">If set used to look up whether user and group start node permissions will be ignored.</param>
|
||||
/// <param name="dataTypeKey">If set used to look up whether user and group start node permissions will be ignored.</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public IEnumerable<EntityBasic> Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeId = null)
|
||||
public IEnumerable<EntityBasic> Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeKey = null)
|
||||
{
|
||||
// NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that
|
||||
|
||||
@@ -116,7 +117,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
//TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type
|
||||
|
||||
var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value);
|
||||
var ignoreUserStartNodes = dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
|
||||
return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes);
|
||||
}
|
||||
|
||||
@@ -288,7 +289,16 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public UrlAndAnchors GetUrlAndAnchors([FromUri]int id, [FromUri]string culture = "*")
|
||||
public UrlAndAnchors GetUrlAndAnchors(Udi id, string culture = "*")
|
||||
{
|
||||
var intId = Services.EntityService.GetId(id);
|
||||
if (!intId.Success)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
return GetUrlAndAnchors(intId.Result, culture);
|
||||
}
|
||||
[HttpGet]
|
||||
public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*")
|
||||
{
|
||||
var url = UmbracoContext.UrlProvider.GetUrl(id);
|
||||
var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id, culture);
|
||||
@@ -424,7 +434,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
#endregion
|
||||
|
||||
public IEnumerable<EntityBasic> GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeId = null)
|
||||
public IEnumerable<EntityBasic> GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeKey = null)
|
||||
{
|
||||
var objectType = ConvertToObjectType(type);
|
||||
if (objectType.HasValue)
|
||||
@@ -433,7 +443,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var startNodes = GetStartNodes(type);
|
||||
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId);
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
|
||||
|
||||
// root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes
|
||||
if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
|
||||
@@ -482,7 +492,7 @@ namespace Umbraco.Web.Editors
|
||||
string orderBy = "SortOrder",
|
||||
Direction orderDirection = Direction.Ascending,
|
||||
string filter = "",
|
||||
Guid? dataTypeId = null)
|
||||
Guid? dataTypeKey = null)
|
||||
{
|
||||
if (int.TryParse(id, out var intId))
|
||||
{
|
||||
@@ -507,7 +517,7 @@ namespace Umbraco.Web.Editors
|
||||
//the EntityService can search paged members from the root
|
||||
|
||||
intId = -1;
|
||||
return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeId);
|
||||
return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeKey);
|
||||
}
|
||||
|
||||
//the EntityService cannot search members of a certain type, this is currently not supported and would require
|
||||
@@ -543,7 +553,7 @@ namespace Umbraco.Web.Editors
|
||||
string orderBy = "SortOrder",
|
||||
Direction orderDirection = Direction.Ascending,
|
||||
string filter = "",
|
||||
Guid? dataTypeId = null)
|
||||
Guid? dataTypeKey = null)
|
||||
{
|
||||
if (pageNumber <= 0)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
@@ -558,7 +568,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var startNodes = GetStartNodes(type);
|
||||
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId);
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
|
||||
|
||||
// root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes
|
||||
if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
|
||||
@@ -642,7 +652,7 @@ namespace Umbraco.Web.Editors
|
||||
string orderBy = "SortOrder",
|
||||
Direction orderDirection = Direction.Ascending,
|
||||
string filter = "",
|
||||
Guid? dataTypeId = null)
|
||||
Guid? dataTypeKey = null)
|
||||
{
|
||||
if (pageNumber <= 0)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
@@ -661,7 +671,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
int[] aids = GetStartNodes(type);
|
||||
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId);
|
||||
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
|
||||
entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes
|
||||
? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords,
|
||||
SqlContext.Query<IUmbracoEntity>().Where(x => x.Name.Contains(filter)),
|
||||
@@ -704,7 +714,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value);
|
||||
private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
|
||||
|
||||
public IEnumerable<EntityBasic> GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings)
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace Umbraco.Web.Editors
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if the index has been rebuilt
|
||||
@@ -250,7 +250,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
private void Indexer_IndexOperationComplete(object sender, EventArgs e)
|
||||
{
|
||||
var indexer = (LuceneIndex)sender;
|
||||
var indexer = (IIndex)sender;
|
||||
|
||||
_logger.Debug<ExamineManagementController>("Logging operation completed for index {IndexName}", indexer.Name);
|
||||
|
||||
@@ -259,7 +259,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
_logger
|
||||
.Info<ExamineManagementController
|
||||
>($"Rebuilding index '{indexer.Name}' done, {indexer.CommitCount} items committed (can differ from the number of items in the index)");
|
||||
>($"Rebuilding index '{indexer.Name}' done.");
|
||||
|
||||
var cacheKey = "temp_indexing_op_" + indexer.Name;
|
||||
_runtimeCache.Clear(cacheKey);
|
||||
|
||||
@@ -15,11 +15,11 @@ namespace Umbraco.Web.Editors
|
||||
[PluginController("UmbracoApi")]
|
||||
public class LogViewerController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private ILogViewer _logViewer;
|
||||
private readonly ILogViewer _logViewer;
|
||||
|
||||
public LogViewerController(ILogViewer logViewer)
|
||||
{
|
||||
_logViewer = logViewer;
|
||||
_logViewer = logViewer ?? throw new ArgumentNullException(nameof(logViewer));
|
||||
}
|
||||
|
||||
private bool CanViewLogs(LogTimePeriod logTimePeriod)
|
||||
@@ -91,8 +91,6 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var direction = orderDirection == "Descending" ? Direction.Descending : Direction.Ascending;
|
||||
|
||||
|
||||
|
||||
return _logViewer.GetLogs(logTimePeriod, filterExpression: filterExpression, pageNumber: pageNumber, orderDirection: direction, logLevels: logLevels);
|
||||
}
|
||||
|
||||
|
||||
@@ -223,14 +223,6 @@ namespace Umbraco.Web
|
||||
_method = method;
|
||||
_controllerName = controllerName;
|
||||
_encryptedString = UrlHelperRenderExtensions.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals);
|
||||
|
||||
//For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used.
|
||||
//In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against
|
||||
//the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms,
|
||||
//that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique
|
||||
//per user.
|
||||
_viewContext.HttpContext.Items["ufprt"] = _encryptedString;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,10 +39,14 @@ namespace Umbraco.Web
|
||||
/// <param name="culture">Optional culture.</param>
|
||||
/// <param name="indexName">Optional index name.</param>
|
||||
/// <remarks>
|
||||
/// <para>When the <paramref name="culture"/> is not specified, all cultures are searched.</para>
|
||||
/// <para>
|
||||
/// When the <paramref name="culture"/> is not specified or is *, all cultures are searched.
|
||||
/// To search for only invariant documents and fields use null.
|
||||
/// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents.
|
||||
/// </para>
|
||||
/// <para>While enumerating results, the ambient culture is changed to be the searched culture.</para>
|
||||
/// </remarks>
|
||||
IEnumerable<PublishedSearchResult> Search(string term, string culture = null, string indexName = null);
|
||||
IEnumerable<PublishedSearchResult> Search(string term, string culture = "*", string indexName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Searches content.
|
||||
@@ -54,10 +58,14 @@ namespace Umbraco.Web
|
||||
/// <param name="culture">Optional culture.</param>
|
||||
/// <param name="indexName">Optional index name.</param>
|
||||
/// <remarks>
|
||||
/// <para>When the <paramref name="culture"/> is not specified, all cultures are searched.</para>
|
||||
/// <para>
|
||||
/// When the <paramref name="culture"/> is not specified or is *, all cultures are searched.
|
||||
/// To search for only invariant documents and fields use null.
|
||||
/// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents.
|
||||
/// </para>
|
||||
/// <para>While enumerating results, the ambient culture is changed to be the searched culture.</para>
|
||||
/// </remarks>
|
||||
IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null);
|
||||
IEnumerable<PublishedSearchResult> Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the query and converts the results to PublishedSearchResult.
|
||||
|
||||
@@ -106,8 +106,10 @@ namespace Umbraco.Web.JavaScript
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fullPath = currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder);
|
||||
{
|
||||
var fullPath = XmlFileMapper.FileMapDefaultFolder.StartsWith("~/")
|
||||
? currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder)
|
||||
: XmlFileMapper.FileMapDefaultFolder;
|
||||
if (fullPath != null)
|
||||
{
|
||||
cdfTempDirectories.Add(fullPath);
|
||||
@@ -122,13 +124,12 @@ namespace Umbraco.Web.JavaScript
|
||||
var success = true;
|
||||
foreach (var directory in cdfTempDirectories)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(directory);
|
||||
if (directoryInfo.Exists == false)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
directoryInfo.Delete(true);
|
||||
if (!Directory.Exists(directory))
|
||||
continue;
|
||||
|
||||
Directory.Delete(directory, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -22,8 +22,9 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[Required]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember(Name = "dataTypeId", IsRequired = false)]
|
||||
public Guid? DataTypeId { get; set; }
|
||||
[DataMember(Name = "dataTypeKey", IsRequired = false)]
|
||||
[ReadOnly(true)]
|
||||
public Guid DataTypeKey { get; set; }
|
||||
|
||||
[DataMember(Name = "value")]
|
||||
public object Value { get; set; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
@@ -43,6 +44,10 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[Required]
|
||||
public int DataTypeId { get; set; }
|
||||
|
||||
[DataMember(Name = "dataTypeKey")]
|
||||
[ReadOnly(true)]
|
||||
public Guid DataTypeKey { get; set; }
|
||||
|
||||
//SD: Is this really needed ?
|
||||
[DataMember(Name = "groupId")]
|
||||
public int GroupId { get; set; }
|
||||
|
||||
@@ -50,13 +50,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
dest.Alias = property.Alias;
|
||||
dest.PropertyEditor = editor;
|
||||
dest.Editor = editor.Alias;
|
||||
|
||||
//fixme: although this might get cached, if a content item has 100 properties of different data types, then this means this is going to be 100 extra DB queries :( :( :(
|
||||
// - ideally, we'd just have the DataTypeKey alongside the DataTypeId which is loaded in the single sql statement which should be relatively easy.
|
||||
var dataTypeKey = _entityService.GetKey(property.PropertyType.DataTypeId, UmbracoObjectTypes.DataType);
|
||||
if (!dataTypeKey.Success)
|
||||
throw new InvalidOperationException("Can't get the unique key from the id: " + property.PropertyType.DataTypeId);
|
||||
dest.DataTypeId = dataTypeKey.Result;
|
||||
dest.DataTypeKey = property.PropertyType.DataTypeKey;
|
||||
|
||||
// if there's a set of property aliases specified, we will check if the current property's value should be mapped.
|
||||
// if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value.
|
||||
|
||||
@@ -219,6 +219,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
target.Name = source.Label;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Mandatory = source.Validation.Mandatory;
|
||||
target.ValidationRegExp = source.Validation.Pattern;
|
||||
target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing;
|
||||
@@ -334,6 +335,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Alias = source.Alias;
|
||||
target.AllowCultureVariant = source.AllowCultureVariant;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Description = source.Description;
|
||||
target.GroupId = source.GroupId;
|
||||
target.Id = source.Id;
|
||||
@@ -349,6 +351,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Alias = source.Alias;
|
||||
target.AllowCultureVariant = source.AllowCultureVariant;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Description = source.Description;
|
||||
target.GroupId = source.GroupId;
|
||||
target.Id = source.Id;
|
||||
|
||||
@@ -231,6 +231,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
GroupId = groupId,
|
||||
Inherited = inherited,
|
||||
DataTypeId = p.DataTypeId,
|
||||
DataTypeKey = p.DataTypeKey,
|
||||
SortOrder = p.SortOrder,
|
||||
ContentTypeId = contentType.Id,
|
||||
ContentTypeName = contentType.Name,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Web;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception that occurs when an Umbraco form route string is invalid
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Web.HttpException" />
|
||||
[Serializable]
|
||||
public sealed class HttpUmbracoFormRouteStringException : HttpException
|
||||
{
|
||||
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> 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 holds the contextual information about the source or destination.</param>
|
||||
private HttpUmbracoFormRouteStringException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message displayed to the client when the exception is thrown.</param>
|
||||
public HttpUmbracoFormRouteStringException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpUmbracoFormRouteStringException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message displayed to the client when the exception is thrown.</param>
|
||||
/// <param name="innerException">The <see cref="P:System.Exception.InnerException" />, if any, that threw the current exception.</param>
|
||||
public HttpUmbracoFormRouteStringException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{ }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception filter checking if we get a <see cref="ModelBindingException" /> or <see cref="InvalidCastException" /> with the same model. in which case it returns a redirect to the same page after 1 sec.
|
||||
/// </summary>
|
||||
internal class ModelBindingExceptionFilter : FilterAttribute, IExceptionFilter
|
||||
{
|
||||
private static readonly Regex GetPublishedModelsTypesRegex = new Regex("Umbraco.Web.PublishedModels.(\\w+)", RegexOptions.Compiled);
|
||||
|
||||
public void OnException(ExceptionContext filterContext)
|
||||
{
|
||||
if (!filterContext.ExceptionHandled
|
||||
&& ((filterContext.Exception is ModelBindingException || filterContext.Exception is InvalidCastException)
|
||||
&& IsMessageAboutTheSameModelType(filterContext.Exception.Message)))
|
||||
{
|
||||
filterContext.HttpContext.Response.Headers.Add(HttpResponseHeader.RetryAfter.ToString(), "1");
|
||||
filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.RawUrl, false);
|
||||
|
||||
filterContext.ExceptionHandled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the message is about two models with the same name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Message could be something like:
|
||||
/// <para>
|
||||
/// InvalidCastException:
|
||||
/// [A]Umbraco.Web.PublishedModels.Home cannot be cast to [B]Umbraco.Web.PublishedModels.Home. Type A originates from 'App_Web_all.generated.cs.8f9494c4.rtdigm_z, Version=0.0.0.3, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rtdigm_z.dll'. Type B originates from 'App_Web_all.generated.cs.8f9494c4.rbyqlplu, Version=0.0.0.5, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rbyqlplu.dll'.
|
||||
///</para>
|
||||
/// <para>
|
||||
/// ModelBindingException:
|
||||
/// Cannot bind source content type Umbraco.Web.PublishedModels.Home to model type Umbraco.Web.PublishedModels.Home. Both view and content models are PureLive, with different versions. The application is in an unstable state and is going to be restarted. The application is restarting now.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private bool IsMessageAboutTheSameModelType(string exceptionMessage)
|
||||
{
|
||||
var matches = GetPublishedModelsTypesRegex.Matches(exceptionMessage);
|
||||
|
||||
if (matches.Count >= 2)
|
||||
{
|
||||
return string.Equals(matches[0].Value, matches[1].Value, StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,12 @@ using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents the default front-end rendering controller.
|
||||
/// </summary>
|
||||
[PreRenderViewActionFilter]
|
||||
[ModelBindingExceptionFilter]
|
||||
public class RenderMvcController : UmbracoController, IRenderMvcController
|
||||
{
|
||||
private PublishedRequest _publishedRequest;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to check that the request contains a valid Umbraco form request string.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Web.Mvc.FilterAttribute" />
|
||||
/// <seealso cref="System.Web.Mvc.IAuthorizationFilter" />
|
||||
/// <remarks>
|
||||
/// Applying this attribute/filter to a <see cref="SurfaceController"/> or SurfaceController Action will ensure that the Action can only be executed
|
||||
/// when it is routed to from within Umbraco, typically when rendering a form with BegingUmbracoForm. It will mean that the natural MVC route for this Action
|
||||
/// will fail with a <see cref="HttpUmbracoFormRouteStringException"/>.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ValidateUmbracoFormRouteStringAttribute : FilterAttribute, IAuthorizationFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when authorization is required.
|
||||
/// </summary>
|
||||
/// <param name="filterContext">The filter context.</param>
|
||||
/// <exception cref="ArgumentNullException">filterContext</exception>
|
||||
/// <exception cref="Umbraco.Web.Mvc.HttpUmbracoFormRouteStringException">The required request field \"ufprt\" is not present.
|
||||
/// or
|
||||
/// The Umbraco form request route string could not be decrypted.
|
||||
/// or
|
||||
/// The provided Umbraco form request route string was meant for a different controller and action.</exception>
|
||||
public void OnAuthorization(AuthorizationContext filterContext)
|
||||
{
|
||||
if (filterContext == null)
|
||||
throw new ArgumentNullException(nameof(filterContext));
|
||||
|
||||
var ufprt = filterContext.HttpContext.Request["ufprt"];
|
||||
ValidateRouteString(ufprt, filterContext.ActionDescriptor?.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor?.ActionName, filterContext.RouteData?.DataTokens["area"]?.ToString());
|
||||
}
|
||||
|
||||
public void ValidateRouteString(string ufprt, string currentController, string currentAction, string currentArea)
|
||||
{
|
||||
if (ufprt.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new HttpUmbracoFormRouteStringException("The required request field \"ufprt\" is not present.");
|
||||
}
|
||||
|
||||
if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprt, out var additionalDataParts))
|
||||
{
|
||||
throw new HttpUmbracoFormRouteStringException("The Umbraco form request route string could not be decrypted.");
|
||||
}
|
||||
|
||||
if (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller].InvariantEquals(currentController) ||
|
||||
!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action].InvariantEquals(currentAction) ||
|
||||
(!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].IsNullOrWhiteSpace() && !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].InvariantEquals(currentArea)))
|
||||
{
|
||||
throw new HttpUmbracoFormRouteStringException("The provided Umbraco form request route string was meant for a different controller and action.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-11
@@ -12,7 +12,7 @@ namespace Umbraco.Web.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.2.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
@@ -50,15 +50,5 @@ namespace Umbraco.Web.Properties {
|
||||
return ((string)(this["test"]));
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.WebServiceUrl)]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("https://our.umbraco.com/umbraco/webservices/api/repository.asmx")]
|
||||
public string umbraco_org_umbraco_our_Repository {
|
||||
get {
|
||||
return ((string)(this["umbraco_org_umbraco_our_Repository"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,5 @@
|
||||
<Setting Name="test" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">Something</Value>
|
||||
</Setting>
|
||||
<Setting Name="umbraco_org_umbraco_our_Repository" Type="(Web Service URL)" Scope="Application">
|
||||
<Value Profile="(Default)">https://our.umbraco.com/umbraco/webservices/api/repository.asmx</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -38,9 +39,24 @@ namespace Umbraco.Web.PropertyEditors
|
||||
/// <inheritdoc />
|
||||
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
|
||||
{
|
||||
return editorValue.Value is JArray json
|
||||
? json.Select(x => x.Value<string>())
|
||||
: null;
|
||||
var value = editorValue?.Value?.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (editorValue.Value is JArray json)
|
||||
{
|
||||
return json.Select(x => x.Value<string>());
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value) == false)
|
||||
{
|
||||
return value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CSharpTest.Net.Collections;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Scoping;
|
||||
@@ -275,6 +276,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
public void UpdateContentTypes(IEnumerable<IPublishedContentType> types)
|
||||
{
|
||||
//nothing to do if this is empty, no need to lock/allocate/iterate/etc...
|
||||
if (!types.Any()) return;
|
||||
|
||||
var lockInfo = new WriteLockInfo();
|
||||
try
|
||||
{
|
||||
@@ -330,13 +334,16 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateContentTypes(IEnumerable<int> removedIds, IEnumerable<IPublishedContentType> refreshedTypes, IEnumerable<ContentNodeKit> kits)
|
||||
public void UpdateContentTypes(IReadOnlyCollection<int> removedIds, IReadOnlyCollection<IPublishedContentType> refreshedTypes, IReadOnlyCollection<ContentNodeKit> kits)
|
||||
{
|
||||
var removedIdsA = removedIds?.ToArray() ?? Array.Empty<int>();
|
||||
var refreshedTypesA = refreshedTypes?.ToArray() ?? Array.Empty<IPublishedContentType>();
|
||||
var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToArray();
|
||||
var removedIdsA = removedIds ?? Array.Empty<int>();
|
||||
var refreshedTypesA = refreshedTypes ?? Array.Empty<IPublishedContentType>();
|
||||
var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToList();
|
||||
kits = kits ?? Array.Empty<ContentNodeKit>();
|
||||
|
||||
if (kits.Count == 0 && refreshedIdsA.Count == 0 && removedIdsA.Count == 0)
|
||||
return; //exit - there is nothing to do here
|
||||
|
||||
var lockInfo = new WriteLockInfo();
|
||||
try
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user