Compare commits

...

68 Commits

Author SHA1 Message Date
Shannon efd6bd82bd Revert "V8: Don't show multiple open menus (take two) (#5609)"
This reverts commit 9ce996cbba.

(cherry picked from commit 1d49c8626e)
2019-07-24 12:02:41 +02:00
Sebastiaan Janssen 17527c99eb Makes checks a little more robust
(cherry picked from commit ca75a25802)
2019-07-24 11:37:29 +02:00
Steve Megson 361557dc39 Handle CSV values in TagsPropertyEditor
(cherry picked from commit a97604d6c4)
2019-07-24 11:37:17 +02:00
Kenn Jacobsen 342b2087e9 Make sure save options are up to date with the content state
(cherry picked from commit 8a43e3a87e)
2019-07-24 10:59:01 +02:00
Steve Megson dc371c1979 Include CheckBoxList in ValueListPreValueMigrator 2019-07-23 12:48:48 +02:00
Shannon 2faa4defd8 Merge remote-tracking branch 'origin/v7/7.15' into v8/8.1
# Conflicts:
#	src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
2019-07-23 19:03:14 +10:00
Warren Buckley bb8356d7db Merge pull request #5924 from umbraco/v/bugfix/5035-slot-swap2
Fixes race condition in PublishedSnapshotService and more...
2019-07-23 09:18:44 +01:00
Shannon Deminick 686f6752f3 Merge pull request #5951 from umbraco/v7/hotfix/5921-preview-failing
https://github.com/umbraco/Umbraco-CMS/issues/5921 - AB#1794 - Remove…
2019-07-23 16:58:13 +10:00
Warren Buckley 66ff4d191a Merge pull request #5946 from umbraco/v8/bugfix/5886-fixes-Children
Fixes issue with Children(culture) method
2019-07-22 14:54:49 +01:00
Shannon 21218b0332 Merge remote-tracking branch 'origin/v7/7.15' into v8/8.1
# Conflicts:
#	src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
#	src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js
#	src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js
2019-07-22 13:59:14 +10:00
Shannon Deminick 23ccdeb2cf Merge pull request #5868 from umbraco/v7/hotfix/5835-AB#1693-add-null-checks-when-using-dataTypeId
Added null checks when *.dataTypeId is used
2019-07-22 13:44:05 +10:00
Shannon 6148ce4e52 oops no es6 support in 7.x 2019-07-22 13:41:26 +10:00
Shannon 06850ed25c Merge branch 'v7/7.15' into v7/hotfix/5835-AB#1693-add-null-checks-when-using-dataTypeId 2019-07-22 13:40:35 +10:00
Shannon 0320a56cb5 Fixes up dataTypeId var declarations 2019-07-22 13:27:51 +10:00
Warren Buckley b9e44f381b Merge pull request #5923 from umbraco/v8/hotfix/5902-migration-should-not-warn-about-build-in-renamed-data-types
V8: Migration should not warn about build in data types that is handled correct but renamed
2019-07-19 10:56:38 +01:00
Bjarke Berg cd27bb210f https://github.com/umbraco/Umbraco-CMS/issues/5921 - AB#1794 - Remove trashed nodes from sql when building xml for previews 2019-07-19 11:25:33 +02:00
Bjarke Berg 67f680a675 Revert "https://github.com/umbraco/Umbraco-CMS/issues/5921 - AB#1794 - Remove trashed nodes from sql when building xml for previews"
This reverts commit 47c3e3a7
2019-07-19 11:24:01 +02:00
Bjarke Berg 47c3e3a79f https://github.com/umbraco/Umbraco-CMS/issues/5921 - AB#1794 - Remove trashed nodes from sql when building xml for previews 2019-07-19 11:20:31 +02:00
Shannon 02f49a0039 Cherry picks fix for 5894 and makes adjustments 2019-07-19 16:00:42 +10:00
Shannon b07df63422 Adds some better notes to the api docs 2019-07-19 15:49:39 +10:00
Shannon 487e45b45b updates note 2019-07-19 15:26:31 +10:00
Shannon 929c84822a Fixes the Children ext method and adds unit tests 2019-07-19 15:18:00 +10:00
Warren Buckley 993f11cd2c Merge pull request #5898 from umbraco/v8/hotfix/5846-AB#1695-Fix-for-migration-of-corrupt-data
V8: Give better exception if a json exception happens doing migration
2019-07-18 13:36:35 +01:00
Warren Buckley 6544b5c272 Update src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs
Co-Authored-By: Bjarke Berg <mail@bergmania.dk>
2019-07-18 13:03:14 +01:00
Shannon 817a458334 Merge remote-tracking branch 'origin/v7/7.15' into v8/8.1
# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Web.UI/packages.config
2019-07-18 16:22:38 +10:00
Shannon f3bfc1ffb2 Puts back UmbracoAntiForgeryAdditionalDataProvider for backwards compat reasons but it is not used 2019-07-18 16:18:11 +10:00
Shannon 3603090421 Fixes race condition in PublishedSnapshotService and more... 2019-07-17 22:51:14 +10:00
Bjarke Berg 79159b684d https://github.com/umbraco/Umbraco-CMS/issues/5902
- Fixed issue with migration of Umbraco.Date if it was never saved in v7, the default format was added the time-part.
- Fixed issue with warnings of migrations of build in types, that was renamed between v7 and v8. These are handled correct.
- Added constants for some of the legacy property editor aliases.
2019-07-17 14:30:02 +02:00
Shannon 26d4d056be bumps version 2019-07-17 22:28:06 +10:00
Shannon 6ad28f1103 Merge remote-tracking branch 'origin/v7/7.15' into v8/8.1
# Conflicts:
#	build/NuSpecs/tools/Web.config.install.xdt
#	src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs
#	src/Umbraco.Core/Services/EntityService.cs
#	src/Umbraco.Core/Services/IEntityService.cs
#	src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs
#	src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js
#	src/Umbraco.Web/Controllers/UmbLoginController.cs
#	src/Umbraco.Web/Controllers/UmbLoginStatusController.cs
#	src/Umbraco.Web/Controllers/UmbProfileController.cs
#	src/Umbraco.Web/Controllers/UmbRegisterController.cs
#	src/Umbraco.Web/HtmlHelperRenderExtensions.cs
#	src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs
#	src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs
#	src/Umbraco.Web/UmbracoHelper.cs
#	src/Umbraco.Web/WebBootManager.cs
2019-07-17 21:58:33 +10:00
Shannon 89cd655df2 Merge remote-tracking branch 'origin/v7/bugfix/build-without-vs2017' into v7/7.15 2019-07-17 21:48:21 +10:00
Shannon 14c4c4815d bumps version 2019-07-17 21:35:43 +10:00
Shannon bfb69a34ef re-adds back in the serialization overloads for the custom exception, re-adds detailed error messages, adds more documentation. Adds unit tests. 2019-07-17 21:15:18 +10:00
Shannon b675f6252c removes files (should have been part of cherry pick?), fixes logic 2019-07-16 23:07:31 +10:00
Shannon d52420183e Cherry picks ValidateUmbracoFormRouteStringAttribute implementation and fixes up some logic 2019-07-16 23:03:26 +10:00
Shannon 0cbe0b4796 Cherry picks ValidateUmbracoFormRouteStringAttribute implementation 2019-07-16 22:34:34 +10:00
Steve Megson 0feb053e51 Fixes #5852 - TablesForScheduledPublishing 2019-07-16 13:39:09 +02:00
Bjarke Berg 09c8ae4e7e Fixed null ref exception in related links migrations 2019-07-16 13:36:16 +02:00
skttl 2aaca865e7 changes innerjoin to left join, to allow folders in sql query 2019-07-16 07:26:26 +02:00
Shannon 77dda5a70f Fixes issue during upgrade where umbraco can't clear cdf log files 2019-07-15 14:34:32 +02:00
Bjarke Berg f6a8d487cc AB#1695 - https://github.com/umbraco/Umbraco-CMS/issues/5846
Give better exception, if a json exception happens doing migration of grid properties in ConvertTinyMceAndGridMediaUrlsToLocalLink
2019-07-15 13:06:47 +02:00
Bjarke Berg 59bf24c461 Added null checks when *.dataTypeId is used 2019-07-12 11:22:31 +02:00
Bjarke Berg 2748c3635d Merge pull request #5863 from umbraco/v7/bugfix/5822-entityservice-overloads
Fixes Change to Entity Service GetByKey isn't breaking in 7.15 #5822
2019-07-12 09:29:45 +02:00
Shannon 2252db0d55 Fixes 5822 by reinstating optional method overloads of loadBaseType 2019-07-12 13:40:21 +10:00
Claus df896af476 adding build targets package. 2019-07-10 12:58:15 +02:00
Claus 78735ff349 Merge pull request #5798 from kjac/v8-fix-save-member
V8: Can't save members without resetting their password
2019-07-08 09:29:37 +02:00
Claus 4b605b2580 Merge pull request #5799 from kjac/v7-fix-save-member
V7: Can't save members without resetting their password
2019-07-08 09:28:46 +02:00
Kenn Jacobsen 80d7f1b2c9 Make it possible to save a member without resetting the password 2019-07-07 11:36:26 +02:00
Kenn Jacobsen 1af43498d9 Make it possible to save a member without resetting the password 2019-07-07 11:16:02 +02:00
Sebastiaan Janssen 5ada85df29 Provide the correct assembly redirects 2019-07-05 12:11:03 +02:00
Shannon 015ad64e30 Updates the exception thrown during upgrade to provide some meaningful feedback to the user. 2019-07-05 14:36:33 +10:00
Bjarke Berg 6dbb988903 Refactor to limit to only v7.14.0 - V7.15.* 2019-07-04 11:44:05 +02:00
Bjarke Berg 926acb910e Forces the initial migration state of V7 sites that are allowed to be migrated into v7.14 state. 2019-07-04 11:44:05 +02:00
Sebastiaan Janssen 57e3187e3a For some reason this file was not part of the project any more :-( 2019-07-03 22:10:01 +02:00
Bjarke Berg 988d51c4c8 Merge pull request #5767 from umbraco/v8/bugfix/5671-fix-for-different-version-number-in-modelbuilder-models-in-live-mode
V8: Bugfix for Modelsbuilder models with different versions error
2019-07-03 13:43:00 +02:00
Bjarke Berg 4733256ca1 Merge pull request #5772 from umbraco/v8/bugfix/fix-n1-issue
Fixes N+1 Issues caused by the new bypass start nodes changes
2019-07-03 13:15:55 +02:00
Bjarke Berg 4133a1fe76 Refactored the ModelBindingExceptionFilter to also handle InvalidCastException, and also ensure the models are the same type.
Otherwise a infinite loop was introduced, when requesting a wrong model in the view.
Also refactored the why we do the retry from Html to Http headers
2019-07-03 13:14:35 +02:00
Shannon 37bf6fe938 Automatically refresh the page if we encounter a ModelBindingException 2019-07-03 19:15:54 +10:00
Shannon Deminick 31716e574c Update src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
good catch!

Co-Authored-By: Bjarke Berg <mail@bergmania.dk>
2019-07-03 18:34:11 +10:00
Shannon 126c4cbd46 ensures media is rebuild and adds more notes 2019-07-03 18:25:19 +10:00
Shannon 7cc91f71c2 Fixes pure live mode when changing doc types 2019-07-03 18:11:00 +10:00
Shannon 7c52b9602c Reduce some allocations and in advertent SQL calls along with inadvertent enumeration of the entire cache, adds some notes 2019-07-03 17:43:30 +10:00
Bjarke Berg c5f1cc15fd Refactored dataTypeId when a guid into dataTypeKey 2019-07-03 08:33:30 +02:00
Shannon d089518681 Fixes SQL generation to populate the nodedto object of the datatypedto 2019-07-03 15:40:18 +10:00
Shannon cd6ef35bf9 comments out the fix during investigation - adds some notes 2019-07-03 15:17:51 +10:00
Shannon 47cdc79fcb Merge branch 'v8/dev' into v8/bugfix/5671-fix-for-different-version-number-in-modelbuilder-models-in-live-mode 2019-07-03 13:50:38 +10:00
Shannon f7382255c2 Fixes N+1 Issues caused by the new bypass start nodes changes 2019-07-03 13:16:40 +10:00
Bjarke Berg 96d5bdd7b2 https://github.com/umbraco/Umbraco-CMS/issues/5671 - If modelsbuilder is in live mode, we need to clear cache for all document types when at least one is cleared. This is due to modelsbuilder updating all the models to different versions. 2019-07-02 15:26:54 +02:00
85 changed files with 963 additions and 542 deletions
+2 -2
View File
@@ -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.1.1")]
[assembly: AssemblyInformationalVersion("8.1.1")]
+25 -22
View File
@@ -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>
+2 -1
View File
@@ -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>
+6 -1
View File
@@ -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
{
@@ -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;
@@ -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
/*
@@ -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();
}
}
}
}
@@ -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>();
}
+8
View File
@@ -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;
@@ -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;
}
}
}
+14 -6
View File
@@ -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();
}
+3 -3
View File
@@ -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
{
+5
View File
@@ -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
}
}
+2
View File
@@ -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.
@@ -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);
+1 -1
View File
@@ -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" />
@@ -268,6 +267,7 @@
<Compile Include="StringNewlineExtensions.cs" />
<Compile Include="Strings\StylesheetHelperTests.cs" />
<Compile Include="Strings\StringValidationTests.cs" />
<Compile Include="Web\Mvc\ValidateUmbracoFormRouteStringAttributeTests.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()));
}
}
}
@@ -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);
@@ -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;
@@ -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;
@@ -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) {
@@ -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);
});
@@ -1121,7 +1121,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) {
@@ -1159,7 +1159,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();
@@ -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: []
};
@@ -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];
@@ -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">
@@ -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);
}
@@ -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
@@ -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>
@@ -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;
}
@@ -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) {
@@ -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>
@@ -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,
@@ -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) {
@@ -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 = {
+2 -2
View File
@@ -345,9 +345,9 @@
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>8100</DevelopmentServerPort>
<DevelopmentServerPort>8110</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:8100</IISUrl>
<IISUrl>http://localhost:8110</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -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)
+12 -12
View File
@@ -104,10 +104,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 +116,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);
}
@@ -424,7 +424,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 +433,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 +482,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 +507,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 +543,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 +558,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 +642,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 +661,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 +704,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)
{
@@ -224,13 +224,6 @@ namespace Umbraco.Web
_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;
}
@@ -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,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
{
@@ -98,6 +98,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
public IEnumerable<ContentNodeKit> GetTypeContentSources(IScope scope, IEnumerable<int> ids)
{
if (!ids.Any()) return Enumerable.Empty<ContentNodeKit>();
var sql = ContentSourcesSelect(scope)
.Where<NodeDto>(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed)
.WhereIn<ContentDto>(x => x.ContentTypeId, ids)
@@ -169,6 +171,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
public IEnumerable<ContentNodeKit> GetTypeMediaSources(IScope scope, IEnumerable<int> ids)
{
if (!ids.Any()) return Enumerable.Empty<ContentNodeKit>();
var sql = MediaSourcesSelect(scope)
.Where<NodeDto>(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed)
.WhereIn<ContentDto>(x => x.ContentTypeId, ids)
@@ -15,7 +15,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// register the NuCache published snapshot service
// must register default options, required in the service ctor
composition.Register(factory => new PublishedSnapshotService.Options());
composition.Register(factory => new PublishedSnapshotServiceOptions());
composition.SetPublishedSnapshotService<PublishedSnapshotService>();
// add the NuCache health check (hidden from type finder)
@@ -30,7 +30,8 @@ using File = System.IO.File;
namespace Umbraco.Web.PublishedCache.NuCache
{
class PublishedSnapshotService : PublishedSnapshotServiceBase
internal class PublishedSnapshotService : PublishedSnapshotServiceBase
{
private readonly ServiceContext _serviceContext;
private readonly IPublishedContentTypeFactory _publishedContentTypeFactory;
@@ -42,6 +43,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
private readonly IMemberRepository _memberRepository;
private readonly IGlobalSettings _globalSettings;
private readonly IEntityXmlSerializer _entitySerializer;
private readonly IPublishedModelFactory _publishedModelFactory;
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
@@ -55,7 +57,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
private BPlusTree<int, ContentNodeKit> _localContentDb;
private BPlusTree<int, ContentNodeKit> _localMediaDb;
private readonly bool _localDbExists;
private bool _localDbExists;
// define constant - determines whether to use cache when previewing
// to store eg routes, property converted values, anything - caching
@@ -67,13 +69,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
//private static int _singletonCheck;
public PublishedSnapshotService(Options options, IMainDom mainDom, IRuntimeState runtime,
public PublishedSnapshotService(PublishedSnapshotServiceOptions options, IMainDom mainDom, IRuntimeState runtime,
ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap,
IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, ILogger logger, IScopeProvider scopeProvider,
IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository,
IDefaultCultureAccessor defaultCultureAccessor,
IDataSource dataSource, IGlobalSettings globalSettings,
IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory,
IEntityXmlSerializer entitySerializer,
IPublishedModelFactory publishedModelFactory,
UrlSegmentProviderCollection urlSegmentProviders)
: base(publishedSnapshotAccessor, variationContextAccessor)
{
@@ -95,6 +98,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// we need an Xml serializer here so that the member cache can support XPath,
// for members this is done by navigating the serialized-to-xml member
_entitySerializer = entitySerializer;
_publishedModelFactory = publishedModelFactory;
// we always want to handle repository events, configured or not
// assuming no repository event will trigger before the whole db is ready
@@ -112,30 +116,36 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (options.IgnoreLocalDb == false)
{
var registered = mainDom.Register(
null,
() =>
{
//"install" phase of MainDom
//this is inside of a lock in MainDom so this is guaranteed to run if MainDom was acquired and guaranteed
//to not run if MainDom wasn't acquired.
//If MainDom was not acquired, then _localContentDb and _localMediaDb will remain null which means this appdomain
//will load in published content via the DB and in that case this appdomain will probably not exist long enough to
//serve more than a page of content.
var path = GetLocalFilesPath();
var localContentDbPath = Path.Combine(path, "NuCache.Content.db");
var localMediaDbPath = Path.Combine(path, "NuCache.Media.db");
_localDbExists = File.Exists(localContentDbPath) && File.Exists(localMediaDbPath);
// if both local databases exist then GetTree will open them, else new databases will be created
_localContentDb = BTree.GetTree(localContentDbPath, _localDbExists);
_localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists);
},
() =>
{
//"release" phase of MainDom
lock (_storesLock)
{
_contentStore.ReleaseLocalDb();
_contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned
_localContentDb = null;
_mediaStore.ReleaseLocalDb();
_mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned
_localMediaDb = null;
}
});
if (registered)
{
var path = GetLocalFilesPath();
var localContentDbPath = Path.Combine(path, "NuCache.Content.db");
var localMediaDbPath = Path.Combine(path, "NuCache.Media.db");
_localDbExists = System.IO.File.Exists(localContentDbPath) && System.IO.File.Exists(localMediaDbPath);
// if both local databases exist then GetTree will open them, else new databases will be created
_localContentDb = BTree.GetTree(localContentDbPath, _localDbExists);
_localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists);
}
// stores are created with a db so they can write to it, but they do not read from it,
// stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to
// figure out whether it can read the databases or it should populate them from sql
@@ -248,19 +258,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
base.Dispose();
}
public class Options
{
// disabled: prevents the published snapshot from updating and exposing changes
// or even creating a new published snapshot to see changes, uses old cache = bad
//
//// indicates that the snapshot cache should reuse the application request cache
//// otherwise a new cache object would be created for the snapshot specifically,
//// which is the default - web boot manager uses this to optimize facades
//public bool PublishedSnapshotCacheIsApplicationRequestCache;
public bool IgnoreLocalDb;
}
#endregion
#region Local files
@@ -708,6 +705,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
/// <inheritdoc />
public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged)
{
// no cache, trash everything
@@ -800,6 +798,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
/// <inheritdoc />
public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads)
{
// no cache, nothing we can do
@@ -812,33 +811,49 @@ namespace Umbraco.Web.PublishedCache.NuCache
Notify<IContentType>(_contentStore, payloads, RefreshContentTypesLocked);
Notify<IMediaType>(_mediaStore, payloads, RefreshMediaTypesLocked);
if (_publishedModelFactory.IsLiveFactory())
{
//In the case of Pure Live - we actually need to refresh all of the content and the media
//see https://github.com/umbraco/Umbraco-CMS/issues/5671
//The underlying issue is that in Pure Live the ILivePublishedModelFactory will re-compile all of the classes/models
//into a new DLL for the application which includes both content types and media types.
//Since the models in the cache are based on these actual classes, all of the objects in the cache need to be updated
//to use the newest version of the class.
using (_contentStore.GetScopedWriteLock(_scopeProvider))
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
{
NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out var draftChanged, out var publishedChanged);
NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out var anythingChanged);
}
}
((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync();
}
private void Notify<T>(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action<IEnumerable<int>, IEnumerable<int>, IEnumerable<int>, IEnumerable<int>> action)
private void Notify<T>(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action<List<int>, List<int>, List<int>, List<int>> action)
where T : IContentTypeComposition
{
if (payloads.Length == 0) return; //nothing to do
var nameOfT = typeof(T).Name;
var removedIds = new List<int>();
var refreshedIds = new List<int>();
var otherIds = new List<int>();
var newIds = new List<int>();
List<int> removedIds = null, refreshedIds = null, otherIds = null, newIds = null;
foreach (var payload in payloads)
{
if (payload.ItemType != nameOfT) continue;
if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Remove))
removedIds.Add(payload.Id);
AddToList(ref removedIds, payload.Id);
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain))
refreshedIds.Add(payload.Id);
AddToList(ref refreshedIds, payload.Id);
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshOther))
otherIds.Add(payload.Id);
AddToList(ref otherIds, payload.Id);
else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Create))
newIds.Add(payload.Id);
AddToList(ref newIds, payload.Id);
}
if (removedIds.Count == 0 && refreshedIds.Count == 0 && otherIds.Count == 0 && newIds.Count == 0) return;
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) return;
using (store.GetScopedWriteLock(_scopeProvider))
{
@@ -925,15 +940,19 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
//Methods used to prevent allocations of lists
private void AddToList(ref List<int> list, int val) => GetOrCreateList(ref list).Add(val);
private List<int> GetOrCreateList(ref List<int> list) => list ?? (list = new List<int>());
#endregion
#region Content Types
private IEnumerable<IPublishedContentType> CreateContentTypes(PublishedItemType itemType, int[] ids)
private IReadOnlyCollection<IPublishedContentType> CreateContentTypes(PublishedItemType itemType, int[] ids)
{
// XxxTypeService.GetAll(empty) returns everything!
if (ids.Length == 0)
return Enumerable.Empty<IPublishedContentType>();
return Array.Empty<IPublishedContentType>();
IEnumerable<IContentTypeComposition> contentTypes;
switch (itemType)
@@ -953,7 +972,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// some may be missing - not checking here
return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x));
return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x)).ToList();
}
private IPublishedContentType CreateContentType(PublishedItemType itemType, int id)
@@ -977,48 +996,64 @@ namespace Umbraco.Web.PublishedCache.NuCache
return contentType == null ? null : _publishedContentTypeFactory.CreateContentType(contentType);
}
private void RefreshContentTypesLocked(IEnumerable<int> removedIds, IEnumerable<int> refreshedIds, IEnumerable<int> otherIds, IEnumerable<int> newIds)
private void RefreshContentTypesLocked(List<int> removedIds, List<int> refreshedIds, List<int> otherIds, List<int> newIds)
{
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty())
return;
// locks:
// content (and content types) are read-locked while reading content
// contentStore is wlocked (so readable, only no new views)
// and it can be wlocked by 1 thread only at a time
var refreshedIdsA = refreshedIds.ToArray();
using (var scope = _scopeProvider.CreateScope())
{
scope.ReadLock(Constants.Locks.ContentTypes);
var typesA = CreateContentTypes(PublishedItemType.Content, refreshedIdsA).ToArray();
var kits = _dataSource.GetTypeContentSources(scope, refreshedIdsA);
var typesA = refreshedIds.IsCollectionEmpty()
? Array.Empty<IPublishedContentType>()
: CreateContentTypes(PublishedItemType.Content, refreshedIds.ToArray()).ToArray();
var kits = refreshedIds.IsCollectionEmpty()
? Array.Empty<ContentNodeKit>()
: _dataSource.GetTypeContentSources(scope, refreshedIds).ToArray();
_contentStore.UpdateContentTypes(removedIds, typesA, kits);
_contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()).ToArray());
_contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()).ToArray());
if (!otherIds.IsCollectionEmpty())
_contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()));
if (!newIds.IsCollectionEmpty())
_contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()));
scope.Complete();
}
}
private void RefreshMediaTypesLocked(IEnumerable<int> removedIds, IEnumerable<int> refreshedIds, IEnumerable<int> otherIds, IEnumerable<int> newIds)
private void RefreshMediaTypesLocked(List<int> removedIds, List<int> refreshedIds, List<int> otherIds, List<int> newIds)
{
if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty())
return;
// locks:
// media (and content types) are read-locked while reading media
// mediaStore is wlocked (so readable, only no new views)
// and it can be wlocked by 1 thread only at a time
var refreshedIdsA = refreshedIds.ToArray();
using (var scope = _scopeProvider.CreateScope())
{
scope.ReadLock(Constants.Locks.MediaTypes);
var typesA = CreateContentTypes(PublishedItemType.Media, refreshedIdsA).ToArray();
var kits = _dataSource.GetTypeMediaSources(scope, refreshedIdsA);
var typesA = refreshedIds == null
? Array.Empty<IPublishedContentType>()
: CreateContentTypes(PublishedItemType.Media, refreshedIds.ToArray()).ToArray();
var kits = refreshedIds == null
? Array.Empty<ContentNodeKit>()
: _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray();
_mediaStore.UpdateContentTypes(removedIds, typesA, kits);
_mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
_mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
if (!otherIds.IsCollectionEmpty())
_mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
if (!newIds.IsCollectionEmpty())
_mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
scope.Complete();
}
}
@@ -0,0 +1,28 @@
namespace Umbraco.Web.PublishedCache.NuCache
{
/// <summary>
/// Options class for configuring the <see cref="IPublishedSnapshotService"/>
/// </summary>
public class PublishedSnapshotServiceOptions
{
// disabled: prevents the published snapshot from updating and exposing changes
// or even creating a new published snapshot to see changes, uses old cache = bad
//
//// indicates that the snapshot cache should reuse the application request cache
//// otherwise a new cache object would be created for the snapshot specifically,
//// which is the default - web boot manager uses this to optimize facades
//public bool PublishedSnapshotCacheIsApplicationRequestCache;
/// <summary>
/// If true this disables the persisted local cache files for content and media
/// </summary>
/// <remarks>
/// By default this is false which means umbraco will use locally persisted cache files for reading in all published content and media on application startup.
/// The reason for this is to improve startup times because the alternative to populating the published content and media on application startup is to read
/// these values from the database. In scenarios where sites are relatively small (below a few thousand nodes) reading the content/media from the database to populate
/// the in memory cache isn't that slow and is only marginally slower than reading from the locally persisted cache files.
/// </remarks>
public bool IgnoreLocalDb { get; set; }
}
}
@@ -36,8 +36,6 @@ namespace Umbraco.Web.Runtime
// ensure WebAPI is initialized, after everything
GlobalConfiguration.Configuration.EnsureInitialized();
AntiForgeryConfig.AdditionalDataProvider = new UmbracoAntiForgeryAdditionalDataProvider(AntiForgeryConfig.AdditionalDataProvider);
}
public void Terminate()
+1 -1
View File
@@ -64,7 +64,7 @@ namespace Umbraco.Web.Scheduling
}
catch (Exception ex)
{
_logger.Error<KeepAlive>(ex, "Failed (at '{UmbracoAppUrl}').", umbracoAppUrl);
_logger.Error<KeepAlive>(ex, "Keep alive failed (at '{UmbracoAppUrl}').", umbracoAppUrl);
}
}
@@ -4,13 +4,12 @@ using Umbraco.Core;
using System.Web.Helpers;
using System.Web;
using Newtonsoft.Json;
using Umbraco.Web.Composing;
using System.ComponentModel;
namespace Umbraco.Web.Security
{
/// <summary>
/// A custom <see cref="IAntiForgeryAdditionalDataProvider"/> to create a unique antiforgery token/validator per form created with BeginUmbracoForm
/// </summary>
[Obsolete("This is no longer used and will be removed from the codebase in future versions")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider
{
private readonly IAntiForgeryAdditionalDataProvider _defaultProvider;
@@ -338,7 +338,7 @@ namespace Umbraco.Web.Trees
//Here we need to figure out if the node is a container and if so check if the user has a custom start node, then check if that start node is a child
// of this container node. If that is true, the HasChildren must be true so that the tree node still renders even though this current node is a container/list view.
if (isContainer && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false)
{
{
var startNodes = Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes);
//if any of these start nodes' parent is current, then we need to render children normally so we need to switch some logic and tell
// the UI that this node does have children and that it isn't a container
@@ -396,7 +396,7 @@ namespace Umbraco.Web.Trees
}
var menu = new MenuItemCollection();
// only add empty recycle bin if the current user is allowed to delete by default
// only add empty recycle bin if the current user is allowed to delete by default
if (deleteAllowed)
{
menu.Items.Add(new MenuItem("emptyRecycleBin", Services.TextService)
@@ -538,8 +538,8 @@ namespace Umbraco.Web.Trees
{
if (_ignoreUserStartNodes.HasValue) return _ignoreUserStartNodes.Value;
var dataTypeId = queryStrings.GetValue<Guid?>(TreeQueryStringParameters.DataTypeId);
_ignoreUserStartNodes = dataTypeId.HasValue ? Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value) : false;
var dataTypeKey = queryStrings.GetValue<Guid?>(TreeQueryStringParameters.DataTypeKey);
_ignoreUserStartNodes = dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
return _ignoreUserStartNodes.Value;
}
@@ -8,7 +8,7 @@
public const string Use = "use";
public const string Application = "application";
public const string StartNodeId = "startNodeId";
public const string DataTypeId = "dataTypeId";
public const string DataTypeKey = "dataTypeKey";
//public const string OnNodeClick = "OnNodeClick";
//public const string RenderParent = "RenderParent";
}
+6 -1
View File
@@ -137,6 +137,7 @@
<Compile Include="Dashboards\HealthCheckDashboard.cs" />
<Compile Include="Dashboards\MediaDashboard.cs" />
<Compile Include="Dashboards\MembersDashboard.cs" />
<Compile Include="Dashboards\ProfilerDashboard.cs" />
<Compile Include="Dashboards\PublishedStatusDashboard.cs" />
<Compile Include="Dashboards\RedirectUrlDashboard.cs" />
<Compile Include="Dashboards\SettingsDashboards.cs" />
@@ -218,8 +219,12 @@
<Compile Include="Models\Mapping\MapperContextExtensions.cs" />
<Compile Include="Models\PublishedContent\HybridVariationContextAccessor.cs" />
<Compile Include="Models\TemplateQuery\QueryConditionExtensions.cs" />
<Compile Include="Mvc\HttpUmbracoFormRouteStringException.cs" />
<Compile Include="Mvc\ModelBindingExceptionFilter.cs" />
<Compile Include="Mvc\SurfaceControllerTypeCollectionBuilder.cs" />
<Compile Include="Mvc\ValidateUmbracoFormRouteStringAttribute.cs" />
<Compile Include="Profiling\WebProfilingController.cs" />
<Compile Include="PublishedCache\NuCache\PublishedSnapshotServiceOptions.cs" />
<Compile Include="PublishedCache\NuCache\Snap\GenObj.cs" />
<Compile Include="PublishedCache\NuCache\Snap\GenRef.cs" />
<Compile Include="PublishedCache\NuCache\Snap\LinkedNode.cs" />
@@ -228,7 +233,6 @@
<Compile Include="Routing\IPublishedRouter.cs" />
<Compile Include="Routing\MediaUrlProviderCollection.cs" />
<Compile Include="Routing\MediaUrlProviderCollectionBuilder.cs" />
<Compile Include="Security\UmbracoAntiForgeryAdditionalDataProvider.cs" />
<Compile Include="Services\DashboardService.cs" />
<Compile Include="Services\IDashboardService.cs" />
<Compile Include="Models\Link.cs" />
@@ -338,6 +342,7 @@
<Compile Include="Editors\TourController.cs" />
<Compile Include="HealthCheck\Checks\Security\XssProtectionCheck.cs" />
<Compile Include="HealthCheck\Checks\Security\HstsCheck.cs" />
<Compile Include="Security\UmbracoAntiForgeryAdditionalDataProvider.cs" />
<Compile Include="Editors\UserEditorAuthorizationHelper.cs" />
<Compile Include="Editors\Filters\UserGroupAuthorizationAttribute.cs" />
<Compile Include="Editors\Filters\UserGroupEditorAuthorizationHelper.cs" />
+1 -1
View File
@@ -820,7 +820,7 @@ namespace Umbraco.Web
{
decryptedString = ufprt.DecryptWithMachineKey();
}
catch (FormatException)
catch (Exception ex) when (ex is FormatException || ex is ArgumentException)
{
Current.Logger.Warn(typeof(UmbracoHelper), "A value was detected in the ufprt parameter but Umbraco could not decrypt the string");
parts = null;