diff --git a/src/Umbraco.Infrastructure/Examine/UmbracoExamineExtensions.cs b/src/Umbraco.Infrastructure/Examine/UmbracoExamineExtensions.cs
index 7ba10019c7..daa39dfcf5 100644
--- a/src/Umbraco.Infrastructure/Examine/UmbracoExamineExtensions.cs
+++ b/src/Umbraco.Infrastructure/Examine/UmbracoExamineExtensions.cs
@@ -5,17 +5,17 @@ using Umbraco.Cms.Infrastructure.Examine;
namespace Umbraco.Extensions;
-public static class UmbracoExamineExtensions
+public static partial class UmbracoExamineExtensions
{
///
/// Matches a culture iso name suffix
///
///
/// myFieldName_en-us will match the "en-us"
+ /// myBlockListField.items[0].myFieldName_en-us will match the "en-us"
///
- internal static readonly Regex _cultureIsoCodeFieldNameMatchExpression = new(
- "^(?[_\\w]+)_(?[a-z]{2,3}(-[a-z0-9]{2,4})?)$",
- RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+ [GeneratedRegex(@"^(?.+)_(?[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();
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
diff --git a/src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs b/src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs
index 9f9e214316..f2c6236a2e 100644
--- a/src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs
+++ b/src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs
@@ -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;
diff --git a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs
index 6e82239dac..e232876001 100644
--- a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs
+++ b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs
@@ -131,4 +131,21 @@ public interface IPublishedContentQuery
/// The search results.
///
IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords);
+
+ ///
+ /// Executes the query and converts the results to .
+ ///
+ /// The query.
+ /// The amount of results to skip.
+ /// The amount of results to take/return.
+ /// The total amount of records.
+ /// The culture (defaults to a culture insensitive search).
+ ///
+ /// The search results.
+ ///
+ ///
+ /// While enumerating results, the ambient culture is changed to be the searched culture.
+ ///
+ IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords, string? culture)
+ => Search(query, skip, take, out totalRecords);
}
diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs
index d075e8b9d2..758115b9ca 100644
--- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs
+++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs
@@ -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);
}
///
@@ -316,6 +312,10 @@ public class PublishedContentQuery : IPublishedContentQuery
///
public IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords)
+ => Search(query, skip, take, out totalRecords, null);
+
+ ///
+ public IEnumerable 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);
}
///
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/PublishedContentQueryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/PublishedContentQueryTests.cs
index 84e4be9d60..08e4c20343 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/PublishedContentQueryTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/PublishedContentQueryTests.cs
@@ -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 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
- {
- [fieldNames[0]] = "Hello world, there are products here",
- [UmbracoExamineFieldNames.VariesByCultureFieldName] = "n"
- }));
- index.IndexItem(new ValueSet(
- "2",
- "content",
- new Dictionary
- {
- [fieldNames[1]] = "Hello world, there are products here",
- [UmbracoExamineFieldNames.VariesByCultureFieldName] = "y"
- }));
- index.IndexItem(new ValueSet(
- "3",
- "content",
- new Dictionary
- {
- [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
+ {
+ [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);