diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md
index b3e34ef55d..8c2bfffd87 100644
--- a/.github/CONTRIBUTING_DETAILED.md
+++ b/.github/CONTRIBUTING_DETAILED.md
@@ -19,7 +19,7 @@ When contributing code to Umbraco there's plenty of things you'll want to know,
* [What branch should I target for my contributions?](#what-branch-should-i-target-for-my-contributions)
* [Building Umbraco from source code](#building-umbraco-from-source-code)
* [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
-
+
## How Can I Contribute?
### Reporting Bugs
@@ -52,7 +52,7 @@ Provide more context by answering these questions:
Include details about your configuration and environment:
- * **Which version of Umbraco are you using?**
+ * **Which version of Umbraco are you using?**
* **What is the environment you're using Umbraco in?** Is this a problem on your local machine or on a server. Tell us about your configuration: Windows version, IIS/IISExpress, database type, etc.
* **Which packages do you have installed?**
@@ -80,7 +80,7 @@ The most successful pull requests usually look a like this:
* Unit tests, while optional are awesome, thank you!
* New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated
-Again, these are guidelines, not strict requirements.
+Again, these are guidelines, not strict requirements.
## Making changes after the PR was opened
@@ -90,7 +90,7 @@ If you make the corrections we ask for in the same branch and push them to your
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
-That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
+That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
## What should I know before I get started?
@@ -125,6 +125,12 @@ We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-fl
### Building Umbraco from source code
+In order to build the Umbraco source code locally, first make sure you have the following installed.
+
+ * Visual Studio 2017 v15.3+
+ * Node v10+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v10)
+ * npm v6.4.1+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v6.4.1)
+
The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 (version 15.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs
index 4cad6e9f15..54367ed588 100644
--- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs
+++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs
@@ -70,10 +70,7 @@ namespace Umbraco.Core.Collections
/// The number of elements contained in the .
///
/// 2
- public int Count
- {
- get { return GetThreadSafeClone().Count; }
- }
+ public int Count => GetThreadSafeClone().Count;
///
/// Gets a value indicating whether the is read-only.
@@ -81,10 +78,7 @@ namespace Umbraco.Core.Collections
///
/// true if the is read-only; otherwise, false.
///
- public bool IsReadOnly
- {
- get { return false; }
- }
+ public bool IsReadOnly => false;
///
/// Adds an item to the .
diff --git a/src/Umbraco.Core/Constants-Indexes.cs b/src/Umbraco.Core/Constants-Indexes.cs
new file mode 100644
index 0000000000..c73a170b62
--- /dev/null
+++ b/src/Umbraco.Core/Constants-Indexes.cs
@@ -0,0 +1,19 @@
+using System;
+using System.ComponentModel;
+
+namespace Umbraco.Core
+{
+ public static partial class Constants
+ {
+ public static class UmbracoIndexes
+ {
+ public const string InternalIndexName = InternalIndexPath + "Index";
+ public const string ExternalIndexName = ExternalIndexPath + "Index";
+ public const string MembersIndexName = MembersIndexPath + "Index";
+
+ public const string InternalIndexPath = "Internal";
+ public const string ExternalIndexPath = "External";
+ public const string MembersIndexPath = "Members";
+ }
+ }
+}
diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs
index c455fadad7..4fa013c095 100644
--- a/src/Umbraco.Core/EnumerableExtensions.cs
+++ b/src/Umbraco.Core/EnumerableExtensions.cs
@@ -1,11 +1,6 @@
using System;
-using System.Collections;
using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Text;
-using Umbraco.Core.Logging;
namespace Umbraco.Core
{
@@ -14,6 +9,18 @@ namespace Umbraco.Core
///
public static class EnumerableExtensions
{
+ ///
+ /// Wraps this object instance into an IEnumerable{T} consisting of a single item.
+ ///
+ /// Type of the object.
+ /// The instance that will be wrapped.
+ /// An IEnumerable{T} consisting of a single item.
+ public static IEnumerable Yield(this T item)
+ {
+ // see EnumeratorBenchmarks - this is faster, and allocates less, than returning an array
+ yield return item;
+ }
+
public static IEnumerable> InGroupsOf(this IEnumerable source, int groupSize)
{
if (source == null)
diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Core/Models/ContentScheduleCollection.cs
index 46813bdb45..4c06f8927d 100644
--- a/src/Umbraco.Core/Models/ContentScheduleCollection.cs
+++ b/src/Umbraco.Core/Models/ContentScheduleCollection.cs
@@ -169,6 +169,7 @@ namespace Umbraco.Core.Models
/// Gets the schedule for a culture
///
///
+ ///
///
public IEnumerable GetSchedule(string culture, ContentScheduleAction? action = null)
{
diff --git a/src/Umbraco.Core/Models/GridValue.cs b/src/Umbraco.Core/Models/GridValue.cs
index 717fcd2f88..237385f3f4 100644
--- a/src/Umbraco.Core/Models/GridValue.cs
+++ b/src/Umbraco.Core/Models/GridValue.cs
@@ -5,6 +5,8 @@ using Newtonsoft.Json.Linq;
namespace Umbraco.Core.Models
{
+ //TODO: Make a property value converter for this!
+
///
/// A model representing the value saved for the grid
///
@@ -19,7 +21,7 @@ namespace Umbraco.Core.Models
public class GridSection
{
[JsonProperty("grid")]
- public string Grid { get; set; }
+ public string Grid { get; set; } //fixme: what is this?
[JsonProperty("rows")]
public IEnumerable Rows { get; set; }
@@ -46,7 +48,7 @@ namespace Umbraco.Core.Models
public class GridArea
{
[JsonProperty("grid")]
- public string Grid { get; set; }
+ public string Grid { get; set; } //fixme: what is this?
[JsonProperty("controls")]
public IEnumerable Controls { get; set; }
diff --git a/src/Umbraco.Core/Models/PagedResult.cs b/src/Umbraco.Core/Models/PagedResult.cs
index 653712d9f8..ef4d4efdfd 100644
--- a/src/Umbraco.Core/Models/PagedResult.cs
+++ b/src/Umbraco.Core/Models/PagedResult.cs
@@ -19,7 +19,7 @@ namespace Umbraco.Core.Models
if (pageSize > 0)
{
- TotalPages = (long)Math.Ceiling(totalItems / (Decimal)pageSize);
+ TotalPages = (long)Math.Ceiling(totalItems / (decimal)pageSize);
}
else
{
diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs
index e93dc56e35..df2d9f9ddc 100644
--- a/src/Umbraco.Core/Models/PublicAccessEntry.cs
+++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs
@@ -21,6 +21,10 @@ namespace Umbraco.Core.Models
public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, IEnumerable ruleCollection)
{
+ if (protectedNode == null) throw new ArgumentNullException(nameof(protectedNode));
+ if (loginNode == null) throw new ArgumentNullException(nameof(loginNode));
+ if (noAccessNode == null) throw new ArgumentNullException(nameof(noAccessNode));
+
LoginNodeId = loginNode.Id;
NoAccessNodeId = noAccessNode.Id;
_protectedNodeId = protectedNode.Id;
diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs
index f847950033..ba4d8cf590 100644
--- a/src/Umbraco.Core/Models/UserExtensions.cs
+++ b/src/Umbraco.Core/Models/UserExtensions.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
+using System.Security.Cryptography;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Composing;
@@ -55,8 +56,11 @@ namespace Umbraco.Core.Models
///
internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache)
{
- //check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none"
- if (user.Avatar == "none")
+ // If FIPS is required, never check the Gravatar service as it only supports MD5 hashing.
+ // Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception
+ // and the website will not run.
+ // Also, check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none"
+ if (user.Avatar == "none" || CryptoConfig.AllowOnlyFipsAlgorithms)
{
return new string[0];
}
diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs
index 44e5968a9f..318a826b25 100644
--- a/src/Umbraco.Core/ObjectExtensions.cs
+++ b/src/Umbraco.Core/ObjectExtensions.cs
@@ -724,8 +724,8 @@ namespace Umbraco.Core
{
return typeConverter;
}
-
- TypeConverter converter = TypeDescriptor.GetConverter(target);
+
+ var converter = TypeDescriptor.GetConverter(target);
if (converter.CanConvertFrom(source))
{
return DestinationTypeConverterCache[key] = converter;
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
index 64489bb059..84c76dbb53 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
@@ -211,12 +211,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//.Where(x => Equals(x, default(TId)) == false)
.ToArray();
- if (ids.Length > 2000)
+ // can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities,
+ // the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
+ const int maxParams = 2000;
+ if (ids.Length <= maxParams)
{
- throw new InvalidOperationException("Cannot perform a query with more than 2000 parameters");
+ return CachePolicy.GetAll(ids, PerformGetAll);
}
- return CachePolicy.GetAll(ids, PerformGetAll);
+ var entities = new List();
+ foreach (var groupOfIds in ids.InGroupsOf(maxParams))
+ {
+ entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
+ }
+ return entities;
}
///
diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
index 2d0b34a849..f3fc4f669b 100644
--- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
@@ -153,6 +153,9 @@ namespace Umbraco.Core.PropertyEditors
set => _defaultConfiguration = value;
}
+ ///
+ public virtual IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory();
+
///
/// Creates a value editor instance.
///
diff --git a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs
new file mode 100644
index 0000000000..413f31d79e
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.PropertyEditors
+{
+ ///
+ /// Provides a default implementation for , returning a single field to index containing the property value.
+ ///
+ public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory
+ {
+ ///
+ public IEnumerable>> GetIndexValues(Property property, string culture, string segment, bool published)
+ {
+ yield return new KeyValuePair>(
+ property.Alias,
+ property.GetValue(culture, segment, published).Yield());
+ }
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataEditor.cs
index 8137101826..f109620ad9 100644
--- a/src/Umbraco.Core/PropertyEditors/IDataEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/IDataEditor.cs
@@ -65,5 +65,10 @@ namespace Umbraco.Core.PropertyEditors
/// Is expected to throw if the editor does not support being configured, e.g. for most parameter editors.
///
IConfigurationEditor GetConfigurationEditor();
+
+ ///
+ /// Gets the index value factory for the editor.
+ ///
+ IPropertyIndexValueFactory PropertyIndexValueFactory { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs
new file mode 100644
index 0000000000..fd4e272f08
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.PropertyEditors
+{
+ ///
+ /// Represents a property index value factory.
+ ///
+ public interface IPropertyIndexValueFactory
+ {
+ ///
+ /// Gets the index values for a property.
+ ///
+ ///
+ /// Returns key-value pairs, where keys are indexed field names. By default, that would be the property alias,
+ /// and there would be only one pair, but some implementations (see for instance the grid one) may return more than
+ /// one pair, with different indexed field names.
+ /// And then, values are an enumerable of objects, because each indexed field can in turn have multiple
+ /// values. By default, there would be only one object: the property value. But some implementations may return
+ /// more than one value for a given field.
+ ///
+ IEnumerable>> GetIndexValues(Property property, string culture, string segment, bool published);
+ }
+}
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index 22138a5e8c..7915bbe24b 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -176,10 +176,8 @@ namespace Umbraco.Core.Services
/// The page number.
/// The page size.
/// Total number of documents.
- /// A field to order by.
- /// The ordering direction.
- /// A flag indicating whether the ordering field is a system field.
/// Query filter.
+ /// Ordering infos.
IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
IQuery filter = null, Ordering ordering = null);
diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs
index 461b1d3c1e..03a371204c 100644
--- a/src/Umbraco.Core/StringExtensions.cs
+++ b/src/Umbraco.Core/StringExtensions.cs
@@ -540,7 +540,7 @@ namespace Umbraco.Core
public static string StripHtml(this string text)
{
const string pattern = @"<(.|\n)*?>";
- return Regex.Replace(text, pattern, string.Empty);
+ return Regex.Replace(text, pattern, string.Empty, RegexOptions.Compiled);
}
///
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 06d410e39f..98aad7ec3c 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -321,6 +321,7 @@
+
@@ -453,6 +454,7 @@
+
@@ -461,6 +463,7 @@
+
diff --git a/src/Umbraco.Examine/BaseValueSetBuilder.cs b/src/Umbraco.Examine/BaseValueSetBuilder.cs
new file mode 100644
index 0000000000..22d379d148
--- /dev/null
+++ b/src/Umbraco.Examine/BaseValueSetBuilder.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+using System.Linq;
+using Examine;
+using Umbraco.Core;
+using Umbraco.Core.Models;
+using Umbraco.Core.PropertyEditors;
+
+namespace Umbraco.Examine
+{
+
+ ///
+ public abstract class BaseValueSetBuilder : IValueSetBuilder
+ where TContent : IContentBase
+ {
+ protected bool PublishedValuesOnly { get; }
+ private readonly PropertyEditorCollection _propertyEditors;
+
+ protected BaseValueSetBuilder(PropertyEditorCollection propertyEditors, bool publishedValuesOnly)
+ {
+ PublishedValuesOnly = publishedValuesOnly;
+ _propertyEditors = propertyEditors ?? throw new System.ArgumentNullException(nameof(propertyEditors));
+ }
+
+ ///
+ public abstract IEnumerable GetValueSets(params TContent[] content);
+
+ protected void AddPropertyValue(Property property, string culture, string segment, IDictionary> values)
+ {
+ var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias];
+ if (editor == null) return;
+
+ var indexVals = editor.PropertyIndexValueFactory.GetIndexValues(property, culture, segment, PublishedValuesOnly);
+ foreach (var keyVal in indexVals)
+ {
+ if (keyVal.Key.IsNullOrWhiteSpace()) continue;
+
+ var cultureSuffix = culture == null ? string.Empty : "_" + culture;
+
+ foreach (var val in keyVal.Value)
+ {
+ switch (val)
+ {
+ //only add the value if its not null or empty (we'll check for string explicitly here too)
+ case null:
+ continue;
+ case string strVal:
+ {
+ if (strVal.IsNullOrWhiteSpace()) return;
+ var key = $"{keyVal.Key}{cultureSuffix}";
+ if (values.TryGetValue(key, out var v))
+ values[key] = new List