Prerequisites:

Sitecore 10.2 instance
Basic understanding of the following:

SXA search components and their settings
Search Scope
Predefined SXA search tokens in the search scope

Challenge:

We can provide multiple search filters in the search scope query for an SXA search results component. We can use the predefined SXA search tokens in the filter.

If a single search filter starts with
then the Operation applied to the filter is

+
must (AND operation)


not (NOT operation)

Nothing is set or default
should (OR operation)

The challenge with predefined SXA tokens like ItemsWithTheSameValueInField is that it always considers the operation “must”. As a result, even if we toggle the filter i.e. change the operation as shown below it does not affect the search result.

ItemsWithTheSameValueInField Demo

Same applies to SXA tokens like TaggedTheSameAsCurrentPage and TaggedWithAtLeastOneTagFromCurrentPage. Basically, the implementation of these two tokens is identical except for the filter type i.e. operation has different values. “must” operation is set in the case of TaggedTheSameAsCurrentPage, and the “should” operation is set in the case of TaggedWithAtLeastOneTagFromCurrentPage.

Solution:

We can implement new Search tokens inspired by the predefined token implementation with some modifications. We can supply the operation set by the content editor user in the search scope as in the following implementations.

Code commented with “setting the operation given in the scope” is the key, and also do check other comments for reference . 

CustomItemsWithTheSameValueInField – For a simple field type or field types where only a single ID is saved.

using Sitecore.ContentSearch.Utilities;
using Sitecore.Data.Fields;
using Sitecore.XA.Foundation.Search.Attributes;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
public class CustomItemsWithTheSameValueInField : ResolveSearchQueryTokensProcessor
{
protected string TokenPart { get; } = nameof(CustomItemsWithTheSameValueInField);

protected string Operation { get; set; } = “should”;

[SxaTokenKey]
protected override string TokenKey => FormattableString.Invariant(FormattableStringFactory.Create(“{0}|FieldName”, (object)this.TokenPart));

public override void Process(ResolveSearchQueryTokensEventArgs args)
{
if (args.ContextItem == null)
return;
for (int index = 0; index < args.Models.Count; ++index)
{
SearchStringModel model = args.Models[index];
if (model.Type.Equals(“sxa”) && this.ContainsToken(model))
{
string str = model.Value.Replace(this.TokenPart, string.Empty).TrimStart(‘|’);
Field field = args.ContextItem.Fields[str];
if (field != null)
{
this.Operation = model.Operation; //setting the operation given in the scope
args.Models.Insert(index, this.BuildModel(str, field.Value)); //pass the field value for filter
args.Models.Remove(model);
}
}
}
}

protected virtual SearchStringModel BuildModel(
string replace,
string fieldValue)
{
return new SearchStringModel(“custom”, FormattableString.Invariant(FormattableStringFactory.Create(“{0}|{1}”, (object)replace.ToLowerInvariant(), (object)fieldValue)))
{
Operation = this.Operation
};
}

protected override bool ContainsToken(SearchStringModel m) => Regex.Match(m.Value, FormattableString.Invariant(FormattableStringFactory.Create(“{0}\|[a-zA-Z ]*”, (object)this.TokenPart))).Success;
}
}

Following is a short demo where fields Team and Buddy are of a field type Single line text and Droplink respectively.

CustomItemsWithTheSameValueInField Demo

ItemsWithIDsInFieldAsCurrentPage – For a complex field types where a single ID or multiple IDs are selected.

using Sitecore.ContentSearch.Utilities;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.XA.Foundation.Search.Attributes;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using Sitecore.XA.Foundation.SitecoreExtensions.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
public class ItemsWithIDsInFieldAsCurrentPage : ResolveSearchQueryTokensProcessor
{
protected string TokenPart => nameof(ItemsWithIDsInFieldAsCurrentPage);

protected string Operation { get; set; } = “should”;

[SxaTokenKey]
protected override string TokenKey => FormattableString.Invariant(FormattableStringFactory.Create(“{0}|FieldName”, (object)this.TokenPart));

public override void Process(ResolveSearchQueryTokensEventArgs args)
{
if (args.ContextItem == null)
return;
for (int index = 0; index < args.Models.Count; ++index)
{
SearchStringModel model = args.Models[index];
if (model.Type.Is(“sxa”) && this.ContainsToken(model))
{
string str = model.Value.Replace(this.TokenPart, string.Empty).TrimStart(‘|’);
MultilistField field = (MultilistField)args.ContextItem.Fields[str];
if (field != null)
{
Item[] items = field.GetItems();
foreach (Item obj in ((IEnumerable<Item>)items).Reverse<Item>()) //loop through all the selected IDs
{
this.Operation = model.Operation; //setting the operation given in the scope
args.Models.Insert(index, this.BuildModel(str, Convert.ToString(obj.ID))); //pass the selected item ID for filter
}
if (items.Length >= 2)
index += items.Length – 1;
args.Models.Remove(model);
}
}
}
}

protected virtual SearchStringModel BuildModel(
string replace,
string fieldValue)
{
return new SearchStringModel(“custom”, FormattableString.Invariant(FormattableStringFactory.Create(“{0}|{1}”, (object)replace.ToLowerInvariant(), (object)fieldValue)))
{
Operation = this.Operation
};
}

protected override bool ContainsToken(SearchStringModel m) => Regex.Match(m.Value, FormattableString.Invariant(FormattableStringFactory.Create(“{0}\|[a-zA-Z ]*”, (object)this.TokenPart))).Success;
}
}

Following is a short demo where fields Services and Skills are of a field type Multilist.

ItemsWithIDsInFieldAsCurrentPage Demo

Patch the above classes as below.

<?xml version=”1.0″?>
<configuration xmlns:patch=”http://www.sitecore.net/xmlconfig/”>
<sitecore>
<pipelines>
<resolveSearchQueryTokens>
<processor type=”CustomSXA.Foundation.Search.SearchQueryToken.CustomItemsWithTheSameValueInField, CustomSXA.Foundation.Search” resolve=”true” />
<processor type=”CustomSXA.Foundation.Search.SearchQueryToken.ItemsWithIDsInFieldAsCurrentPage, CustomSXA.Foundation.Search” resolve=”true” />
</resolveSearchQueryTokens>
</pipelines>
</sitecore>
</configuration>

Refer to the list of the NuGet packages from here.

Check out the repository for the above implementation in the CustomSXA.Foundation.Search project.

Hope this helps.

Happy Sitecore Search Query Scoping