* Fixes #15136: Search includes fields from other cultures Regex was updated to support block list fields Unpublished nodes on the supplied culture are not filtered out * Making the code non-breaking * Fixed failing publish content query integration tests The tests were not setting the content as publish in the specifed culture causing the content items to be ignored --------- Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
This commit is contained in:
@@ -5,17 +5,17 @@ using Umbraco.Cms.Infrastructure.Examine;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class UmbracoExamineExtensions
|
||||
public static partial class UmbracoExamineExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Matches a culture iso name suffix
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// myFieldName_en-us will match the "en-us"
|
||||
/// myBlockListField.items[0].myFieldName_en-us will match the "en-us"
|
||||
/// </remarks>
|
||||
internal static readonly Regex _cultureIsoCodeFieldNameMatchExpression = new(
|
||||
"^(?<FieldName>[_\\w]+)_(?<CultureName>[a-z]{2,3}(-[a-z0-9]{2,4})?)$",
|
||||
RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
[GeneratedRegex(@"^(?<FieldName>.+)_(?<CultureName>[a-z]{2,3}(-[a-z0-9]{2,4})?)$", RegexOptions.ExplicitCapture)]
|
||||
internal static partial Regex CultureIsoCodeFieldNameMatchExpression();
|
||||
|
||||
// TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression
|
||||
|
||||
@@ -32,7 +32,7 @@ public static class UmbracoExamineExtensions
|
||||
var results = new List<string>();
|
||||
foreach (var field in allFields)
|
||||
{
|
||||
Match match = _cultureIsoCodeFieldNameMatchExpression.Match(field);
|
||||
Match match = CultureIsoCodeFieldNameMatchExpression().Match(field);
|
||||
if (match.Success && culture.InvariantEquals(match.Groups["CultureName"].Value))
|
||||
{
|
||||
results.Add(field);
|
||||
@@ -54,7 +54,7 @@ public static class UmbracoExamineExtensions
|
||||
|
||||
foreach (var field in allFields)
|
||||
{
|
||||
Match match = _cultureIsoCodeFieldNameMatchExpression.Match(field);
|
||||
Match match = CultureIsoCodeFieldNameMatchExpression().Match(field);
|
||||
if (match.Success && culture.InvariantEquals(match.Groups["CultureName"].Value))
|
||||
{
|
||||
yield return field; // matches this culture field
|
||||
|
||||
@@ -70,7 +70,7 @@ public class UmbracoFieldDefinitionCollection : FieldDefinitionCollection
|
||||
return false;
|
||||
}
|
||||
|
||||
Match match = UmbracoExamineExtensions._cultureIsoCodeFieldNameMatchExpression.Match(fieldName);
|
||||
Match match = UmbracoExamineExtensions.CultureIsoCodeFieldNameMatchExpression().Match(fieldName);
|
||||
if (match.Success)
|
||||
{
|
||||
var nonCultureFieldName = match.Groups["FieldName"].Value;
|
||||
|
||||
@@ -131,4 +131,21 @@ public interface IPublishedContentQuery
|
||||
/// The search results.
|
||||
/// </returns>
|
||||
IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the query and converts the results to <see cref="PublishedSearchResult" />.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="skip">The amount of results to skip.</param>
|
||||
/// <param name="take">The amount of results to take/return.</param>
|
||||
/// <param name="totalRecords">The total amount of records.</param>
|
||||
/// <param name="culture">The culture (defaults to a culture insensitive search).</param>
|
||||
/// <returns>
|
||||
/// The search results.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// While enumerating results, the ambient culture is changed to be the searched culture.
|
||||
/// </remarks>
|
||||
IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords, string? culture)
|
||||
=> Search(query, skip, take, out totalRecords);
|
||||
}
|
||||
|
||||
@@ -293,21 +293,17 @@ public class PublishedContentQuery : IPublishedContentQuery
|
||||
var fields =
|
||||
umbIndex.GetCultureAndInvariantFields(culture)
|
||||
.ToArray(); // Get all index fields suffixed with the culture name supplied
|
||||
ordering = query.ManagedQuery(term, fields);
|
||||
|
||||
// Filter out unpublished content for the specified culture if the content varies by culture
|
||||
// The published__{culture} field is not populated when the content is not published in that culture
|
||||
ordering = query
|
||||
.ManagedQuery(term, fields)
|
||||
.Not().Group(q => q
|
||||
.Field(UmbracoExamineFieldNames.VariesByCultureFieldName, "y")
|
||||
.Not().Field($"{UmbracoExamineFieldNames.PublishedFieldName}_{culture.ToLowerInvariant()}", "y"));
|
||||
}
|
||||
|
||||
// Filter selected fields because results are loaded from the published snapshot based on these
|
||||
IOrdering? queryExecutor = ordering.SelectFields(_returnedQueryFields);
|
||||
|
||||
|
||||
ISearchResults? results = skip == 0 && take == 0
|
||||
? queryExecutor.Execute()
|
||||
: queryExecutor.Execute(QueryOptions.SkipTake(skip, take));
|
||||
|
||||
totalRecords = results.TotalItemCount;
|
||||
|
||||
return new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content),
|
||||
_variationContextAccessor, culture);
|
||||
return Search(ordering, skip, take, out totalRecords, culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -316,6 +312,10 @@ public class PublishedContentQuery : IPublishedContentQuery
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords)
|
||||
=> Search(query, skip, take, out totalRecords, null);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishedSearchResult> Search(IQueryExecutor query, int skip, int take, out long totalRecords, string? culture)
|
||||
{
|
||||
if (skip < 0)
|
||||
{
|
||||
@@ -331,8 +331,8 @@ public class PublishedContentQuery : IPublishedContentQuery
|
||||
|
||||
if (query is IOrdering ordering)
|
||||
{
|
||||
// Filter selected fields because results are loaded from the published snapshot based on these
|
||||
query = ordering.SelectFields(_returnedQueryFields);
|
||||
// Filter selected fields because results are loaded from the published snapshot based on these
|
||||
query = ordering.SelectFields(_returnedQueryFields);
|
||||
}
|
||||
|
||||
ISearchResults? results = skip == 0 && take == 0
|
||||
@@ -341,7 +341,9 @@ public class PublishedContentQuery : IPublishedContentQuery
|
||||
|
||||
totalRecords = results.TotalItemCount;
|
||||
|
||||
return results.ToPublishedSearchResults(_publishedSnapshot);
|
||||
return culture.IsNullOrWhiteSpace()
|
||||
? results.ToPublishedSearchResults(_publishedSnapshot)
|
||||
: new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using Examine.Lucene;
|
||||
using Examine.Lucene.Directories;
|
||||
using Examine.Lucene.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
@@ -41,37 +42,27 @@ public class PublishedContentQueryTests : ExamineBaseTest
|
||||
public IEnumerable<string> GetFields() => _fieldNames;
|
||||
}
|
||||
|
||||
private TestIndex CreateTestIndex(Directory luceneDirectory, string[] fieldNames)
|
||||
private TestIndex CreateTestIndex(Directory luceneDirectory, (string Name, string Culture)[] fields)
|
||||
{
|
||||
var index = new TestIndex(LoggerFactory, "TestIndex", luceneDirectory, fieldNames);
|
||||
var index = new TestIndex(LoggerFactory, "TestIndex", luceneDirectory, fields.Select(f => f.Name).ToArray());
|
||||
|
||||
using (index.WithThreadingMode(IndexThreadingMode.Synchronous))
|
||||
{
|
||||
//populate with some test data
|
||||
index.IndexItem(new ValueSet(
|
||||
"1",
|
||||
"content",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
[fieldNames[0]] = "Hello world, there are products here",
|
||||
[UmbracoExamineFieldNames.VariesByCultureFieldName] = "n"
|
||||
}));
|
||||
index.IndexItem(new ValueSet(
|
||||
"2",
|
||||
"content",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
[fieldNames[1]] = "Hello world, there are products here",
|
||||
[UmbracoExamineFieldNames.VariesByCultureFieldName] = "y"
|
||||
}));
|
||||
index.IndexItem(new ValueSet(
|
||||
"3",
|
||||
"content",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
[fieldNames[2]] = "Hello world, there are products here",
|
||||
[UmbracoExamineFieldNames.VariesByCultureFieldName] = "y"
|
||||
}));
|
||||
// populate with some test data
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var (name, culture) = fields[i];
|
||||
|
||||
index.IndexItem(new ValueSet(
|
||||
$"{i + 1}",
|
||||
"content",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
[name] = "Hello world, there are products here",
|
||||
[UmbracoExamineFieldNames.VariesByCultureFieldName] = culture.IsNullOrEmpty() ? "n" : "y",
|
||||
[culture.IsNullOrEmpty() ? UmbracoExamineFieldNames.PublishedFieldName : $"{UmbracoExamineFieldNames.PublishedFieldName}_{culture}"] = "y"
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
@@ -102,8 +93,8 @@ public class PublishedContentQueryTests : ExamineBaseTest
|
||||
{
|
||||
using (var luceneDir = new RandomIdRAMDirectory())
|
||||
{
|
||||
var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" };
|
||||
using (var indexer = CreateTestIndex(luceneDir, fieldNames))
|
||||
var fields = new[] { (Name: "title", Culture: null), (Name: "title_en-us", Culture: "en-us"), (Name: "title_fr-fr", Culture: "fr-fr") };
|
||||
using (var indexer = CreateTestIndex(luceneDir, fields))
|
||||
{
|
||||
var pcq = CreatePublishedContentQuery(indexer);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user