Developer forum

Forum » Dynamicweb 10 » Get itemtype content from contentindexbuilder

Get itemtype content from contentindexbuilder

Andreas Rundgren
Reply

Hi,
I have a itemtype which i use on my page in a 1 row. In that itemtype i have a category field which is a checkbox list, i want to create a facet on does categories but i cant seem to find them becuase contentindexbuilder does not build itemtype field values.

How can i get my category field to index in the contentindexbuilder?

When i ask chatgpt it says: 
When indexing pages with item-based content, the Content Index Builder only includes item fields that belong to the page’s item type — but only if the field data is stored directly on the page item, not paragraph-level items.
✅ In your screenshot, the KnowledgehubCategories field appears in the paragraph editor, not the page itself.
That means the field belongs to a paragraph item type, not a page item type.

The Content Index (by default) indexes pages, not paragraphs — so fields on paragraph-level items don’t automatically appear unless you explicitly extend the schema to include paragraph data.

Regards
Andreas


Replies

 
Nicolai Pedersen Dynamicweb Employee
Nicolai Pedersen
Reply

AI is correct. All item fields on the paragraphs of the page is merged into one big blob of things to search.

I just gave my codex the existing contentindexbuilder and asked it to make a new version that handles the item fields individually. Code below in a ContentItemIndexBuilder- it is not at all tested. You can add it as custom and use that to build an index.

This version does exactly what you asked:

  • Page document is the single place where page/row/paragraph item fields end up.

  • Repeated fields are merged (scalar → becomes multi-valued when repeated; arrays append + de-dupe).

  • Adds PageItemIds, PagePropertyItemIds, RowItemIds, ParagraphItemIds (as string[] values like ItemType:ItemId).

  • Keeps existing ParagraphHeaders[] and ParagraphTexts[].

  • Does not keep any “ParagraphContent merged blob” field.

using System;

using System.Collections;

using System.Collections.Generic;

using System.Data;

using System.Data.SqlClient;

using System.Diagnostics.CodeAnalysis;

using System.Globalization;

using System.Linq;

using System.Text;

using Dynamicweb.Content.Items;

using Dynamicweb.Content.Items.Editors;

using Dynamicweb.Content.Items.Metadata;

using Dynamicweb.Core;

using Dynamicweb.Core.Encoders;

using Dynamicweb.Core.Helpers;

using Dynamicweb.Data;

using Dynamicweb.Diagnostics.Tasks;

using Dynamicweb.Diagnostics.Tracking;

using Dynamicweb.Extensibility.AddIns;

using Dynamicweb.Extensibility.Editors;

using Dynamicweb.Indexing;

using Dynamicweb.Indexing.Schemas;

using Dynamicweb.Logging;

using Dynamicweb.Logging.LogEvents;


namespace Dynamicweb.Content

{

    /// <summary>

    /// ContentItemIndexBuilder implements an <seealso cref="IndexBuilderBase" /> that indexes Pages, paragraphs and items to the index.

    /// Stores item fields individually on the PAGE document and merges repeated fields.

    /// </summary>

    [AddInName("Content Item Index Builder")]

    public class ContentItemIndexBuilder : IndexBuilderBase

    {

        private const string AddInParameterGroupName = "Content Item Index Builder Settings";


        [AddInParameterGroup(AddInParameterGroupName), AddInParameter("AppsToHandle"), AddInLabel("Apps to handle"),

         AddInParameterEditor(typeof(TextParameterEditor), "infoText=Defaults to all; Explanation=Allows specification of ContentAppIndexProviders via a comma-separated list;")]

        public string AppsToHandle

        {

            get => GetString("AppsToHandle");

            set => SetValue("AppsToHandle", value);

        }


        [AddInParameterGroup(AddInParameterGroupName), AddInParameter("ExcludeItemsFromIndex"), AddInLabel("Exclude Items from Index"),

         AddInParameterEditor(typeof(YesNoParameterEditor), "Explanation=Excludes item content from the index;")]

        public bool ExcludeItemsFromIndex

        {

            get => GetBoolean("ExcludeItemsFromIndex");

            set => SetValue("ExcludeItemsFromIndex", value);

        }


        [AddInParameterGroup(AddInParameterGroupName), AddInParameter("SkipPageItemRelationLists"), AddInLabel("Skip page Item relation lists"),

         AddInParameterEditor(typeof(YesNoParameterEditor), "")]

        public bool SkipPageItemRelationLists

        {

            get => GetBoolean("SkipPageItemRelationLists");

            set => SetValue("SkipPageItemRelationLists", value);

        }


        [AddInParameterGroup(AddInParameterGroupName), AddInParameter("SkipRowItemRelationLists"), AddInLabel("Skip row Item relation lists"),

         AddInParameterEditor(typeof(YesNoParameterEditor), "")]

        public bool SkipRowItemRelationLists

        {

            get => GetBoolean("SkipRowItemRelationLists");

            set => SetValue("SkipRowItemRelationLists", value);

        }


        [AddInParameterGroup(AddInParameterGroupName), AddInParameter("SkipParagraphItemRelationLists"), AddInLabel("Skip paragraph Item relation lists"),

         AddInParameterEditor(typeof(YesNoParameterEditor), "")]

        public bool SkipParagraphItemRelationLists

        {

            get => GetBoolean("SkipParagraphItemRelationLists");

            set => SetValue("SkipParagraphItemRelationLists", value);

        }


        [AddInParameterGroup(AddInParameterGroupName), AddInParameter("StripHtml"), AddInLabel("Remove html markup from text content"),

         AddInParameterEditor(typeof(YesNoParameterEditor), "")]

        public bool StripHtml

        {

            get => GetBoolean("StripHtml");

            set => SetValue("StripHtml", value);

        }


        private Dictionary<int, DataRow> Pages { get; set; } = [];

        private Dictionary<int, List<DataRow>> PageParagraphs { get; set; } = [];


        private readonly Dictionary<string, ItemField> ItemTypeFields = [];

        private readonly HashSet<string> ItemRelationListFields = [];

        private readonly Dictionary<string, Type> ItemArrayFields = [];

        private readonly HashSet<string> ItemExcludedFields = [];

        private readonly ValueConverter ValueConverter = new();


        private readonly Lazy<IEnumerable<ContentAppIndexProvider>> _allAppProviders = new(ContentAppIndexProvider.GetAll);


        private void InitializeItemFields()

        {

            ItemTypeFields.Clear();

            ItemRelationListFields.Clear();

            ItemArrayFields.Clear();

            ItemExcludedFields.Clear();


            var itemTypeEditor = typeof(ItemTypeEditor);

            var relationListTypeEditor = typeof(ItemRelationListEditor);


            var itemTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);


            foreach (var area in Services.Areas.GetAreas())

                if (!string.IsNullOrEmpty(area.ItemTypePageProperty))

                    itemTypes.Add(area.ItemTypePageProperty);


            foreach (var page in Services.Pages.GetPages())

                if (!string.IsNullOrEmpty(page.ItemType))

                    itemTypes.Add(page.ItemType);


            foreach (var paragraph in Services.Paragraphs.GetParagraphs())

                if (!string.IsNullOrEmpty(paragraph.ItemType))

                    itemTypes.Add(paragraph.ItemType);


            foreach (var row in Services.Grids.GetGridRows())

                if (!string.IsNullOrEmpty(row.ItemType))

                    itemTypes.Add(row.ItemType);


            foreach (string itemType in itemTypes)

            {

                var metadata = ItemManager.Metadata.GetItemType(itemType);

                if (metadata is null)

                    continue;


                var fields = ItemManager.Metadata.GetItemFields(metadata);

                if (fields is null)

                    continue;


                foreach (ItemField fieldMetadata in fields)

                {

                    var fieldKey = GetItemFieldKey(itemType, fieldMetadata.SystemName);

                    if (fieldMetadata.ExcludeFromSearch)

                    {

                        ItemExcludedFields.Add(fieldKey);

                        continue;

                    }


                    if (fieldMetadata.IsEditor(relationListTypeEditor))

                        ItemRelationListFields.Add(fieldKey);

                    else if (fieldMetadata.IsEditor(itemTypeEditor, true))

                        ItemTypeFields.Add(fieldKey, fieldMetadata);

                    else if (fieldMetadata.UnderlyingType.IsGenericType &&

                             fieldMetadata.UnderlyingType.GetGenericTypeDefinition() == typeof(IEnumerable<>))

                        ItemArrayFields.Add(fieldKey, fieldMetadata.UnderlyingType);

                }

            }

        }


        private static string GetItemFieldKey(string itemType, string fieldName) => $"[{itemType}].[{fieldName}]";


        public override void Build(IIndexWriter writer, Tracker tracker)

        {

            ArgumentNullException.ThrowIfNull(writer);

            ArgumentNullException.ThrowIfNull(tracker);


            if (TaskManager.Context is null)

                throw new InvalidOperationException("TaskManager.Context is null");


            writer.Open(false);

            using var conn = new SqlConnection(TaskManager.Context["Database.ConnectionString"].ToString());

            conn.Open();


            TrackSettings(tracker);

            LoadPages(conn);

            LoadParagraphs(conn);

            InitializeItemFields();

            BuildContent(writer, tracker);

        }


        private void TrackSettings(Tracker tracker)

        {

            if (tracker is null) return;


            tracker.LogInformation("AppsToHandle: '{0}'", AppsToHandle);

            tracker.LogInformation("ExcludeItemsFromIndex: '{0}'", ExcludeItemsFromIndex);

            tracker.LogInformation("SkipPageItemRelationLists: '{0}'", SkipPageItemRelationLists);

            tracker.LogInformation("SkipRowItemRelationLists: '{0}'", SkipRowItemRelationLists);

            tracker.LogInformation("SkipParagraphItemRelationLists: '{0}'", SkipParagraphItemRelationLists);

            tracker.LogInformation("StripHtml: '{0}'", StripHtml);

        }


        private void LoadPages(SqlConnection connection)

        {

            var pages = new Dictionary<int, DataRow>();

            var cb = CommandBuilder.Create(

                "SELECT [Page].* FROM Page INNER JOIN Area on [Page].PageAreaID = Area.AreaID WHERE Area.AreaActive = 1 AND Area.AreaDeleted = 0 AND Page.PageDeleted = 0 AND Page.PageHidden = 0");


            using (var ds = Database.CreateDataSet(cb, connection))

            {

                foreach (DataRow row in ds.Tables[0].Rows)

                {

                    int pageId = (int)row["PageID"];

                    pages.Add(pageId, row);

                }

            }


            Pages = pages;

        }


        private void LoadParagraphs(SqlConnection connection)

        {

            var pageParagraphs = new Dictionary<int, List<DataRow>>();

            var cb = CommandBuilder.Create(

                @"SELECT p.ParagraphID, p.ParagraphPageID, p.ParagraphModuleSystemName, p.ParagraphHeader, p.ParagraphText, p.ParagraphModuleSettings, p.ParagraphItemType, p.ParagraphItemId 

                  FROM Paragraph p

                  LEFT JOIN GridRow rows ON p.ParagraphGridRowId = rows.GridRowId 

                  WHERE 

                      p.ParagraphShowParagraph = 1 

                      AND p.ParagraphDeleted = 0 

                      AND (p.ParagraphGridRowId = 0 OR (

                          rows.GridRowActive = 1 

                          AND rows.GridRowDeleted = 0

                   AND (rows.GridRowValidFrom IS NULL OR rows.GridRowValidFrom <= {0})

                   AND (rows.GridRowValidTo IS NULL OR rows.GridRowValidTo > {0}))) 

                      AND (( {0} BETWEEN p.[ParagraphValidFrom] AND p.[ParagraphValidTo] ) OR ( p.[ParagraphValidTo] IS NULL ))",

                DateTime.Now);


            using (var ds = Database.CreateDataSet(cb, connection))

            {

                foreach (DataRow row in ds.Tables[0].Rows)

                {

                    int pageId = (int)row["ParagraphPageID"];

                    if (!pageParagraphs.TryGetValue(pageId, out var paragraphs))

                    {

                        paragraphs = [];

                        pageParagraphs.Add(pageId, paragraphs);

                    }


                    paragraphs.Add(row);

                }

            }


            PageParagraphs = pageParagraphs;

        }


        private void BuildContent(IIndexWriter writer, Tracker tracker)

        {

            tracker.Status.TotalCount = Pages.Count;


            var fieldsIdsForFacets = new Dictionary<string, FieldDefinition>(StringComparer.OrdinalIgnoreCase)

            {

                { ContentIndexSchemaExtender.PageItemIdStringFieldDefinition.Source, ContentIndexSchemaExtender.PageItemIdStringFieldDefinition },

                { ContentIndexSchemaExtender.PageIdStringFieldDifenition.Source, ContentIndexSchemaExtender.PageIdStringFieldDifenition },

                { ContentIndexSchemaExtender.PageParentIdStringFieldDefinition.Source, ContentIndexSchemaExtender.PageParentIdStringFieldDefinition },

                { ContentIndexSchemaExtender.PageMasterPageIdStringFieldDefinition.Source, ContentIndexSchemaExtender.PageMasterPageIdStringFieldDefinition },

                { ContentIndexSchemaExtender.PageAreaIdStringFieldDefinition.Source, ContentIndexSchemaExtender.PageAreaIdStringFieldDefinition }

            };


            foreach (var pageRow in Pages.Values)

            {

                var document = new IndexDocument();

                int pageId = 0;


                if (pageRow is not null)

                {

                    foreach (DataColumn col in pageRow.Table.Columns)

                    {

                        var value = pageRow[col];

                        if (col.ColumnName.Equals("PageID", StringComparison.OrdinalIgnoreCase))

                            pageId = (int)value;


                        if (IsNullOrDbNull(value))

                            continue;


                        if (fieldsIdsForFacets.TryGetValue(col.ColumnName, out FieldDefinition? facetField))

                            AddOrMerge(document, facetField.SystemName, value);


                        // Keep original raw columns as-is (but merge-safe anyway)

                        AddOrMerge(document, col.ColumnName, value);

                    }


                    if (!ExcludeItemsFromIndex)

                    {

                        HandlePageItems(pageRow, document, tracker); // now merges

                        HandleRowItems(pageId, document);            // now adds RowItemIds + Row_* fields

                    }


                    HandlePagePath(pageId, document, pageRow);

                    HandleParagraphs(pageId, document, writer, tracker); // now adds ParagraphItemIds + Paragraph_* fields + header/text arrays

                }


                WriteDocument(document, pageId, writer, tracker);

            }

        }


        private void HandlePagePath(int pageId, IndexDocument document, DataRow pageRow)

        {

            if (pageRow == null) return;


            var pagePath = new List<int> { pageId };


            if (pageRow["PageParentPageId"] is int pageParentId)

            {

                while (Pages.TryGetValue(pageParentId, out var parentRow))

                {

                    pagePath.Insert(0, pageParentId);

                    pageParentId = Converter.ToInt32(parentRow["PageParentPageId"]);

                }

            }


            AddOrMerge(document, "PagePath", pagePath.ToArray());

        }


        private void WriteDocument(IndexDocument document, int pageId, IIndexWriter writer, Tracker tracker)

        {

            foreach (var extender in Extenders)

                extender.ExtendDocument(document);


            writer.AddDocument(document);

            tracker.Status.Meta["CurrentPageID"] = Converter.ToString(pageId);

            tracker.IncrementCounter();

        }


        private void HandlePageItems(DataRow pageRow, IndexDocument document, Tracker tracker)

        {

            // Track item IDs/types on the document

            var pageItemIds = new List<string>();

            var pagePropertyItemIds = new List<string>();


            // PropertyItemID case

            if (Converter.ToInt32(pageRow["PagePropertyItemId"]) > 0)

            {

                var area = Services.Areas.GetArea(Converter.ToInt32(pageRow["PageAreaID"]));

                if (area is not null && !string.IsNullOrEmpty(area.ItemTypePageProperty))

                {

                    var itemType = area.ItemTypePageProperty;

                    var itemId = Converter.ToString(pageRow["PagePropertyItemId"]);

                    pagePropertyItemIds.Add($"{itemType}:{itemId}");


                    _ = HandlePageItem(

                        pageId: Converter.ToInt32(pageRow["PageID"]),

                        itemType: itemType,

                        itemId: itemId,

                        document: document,

                        isPropertyItem: true,

                        tracker: tracker);

                }

            }


            // Normal page item

            if (!string.IsNullOrEmpty(Converter.ToString(pageRow["PageItemType"])))

            {

                var itemType = Converter.ToString(pageRow["PageItemType"]);

                var itemId = Converter.ToString(pageRow["PageItemID"]);

                pageItemIds.Add($"{itemType}:{itemId}");


                _ = HandlePageItem(

                    pageId: Converter.ToInt32(pageRow["PageID"]),

                    itemType: itemType,

                    itemId: itemId,

                    document: document,

                    isPropertyItem: false,

                    tracker: tracker);

            }


            if (pageItemIds.Count > 0)

                AddOrMerge(document, "PageItemIds", pageItemIds.ToArray());

            if (pagePropertyItemIds.Count > 0)

                AddOrMerge(document, "PagePropertyItemIds", pagePropertyItemIds.ToArray());

        }


        private string HandlePageItem(int pageId, string itemType, string itemId, IndexDocument document, bool isPropertyItem, Tracker tracker)

        {

            Item? item = null;

            try

            {

                if (isPropertyItem)

                {

                    item = ItemManager.Storage.GetById(itemType, itemId);

                }

                else if (pageId > 0)

                {

                    item = Services.Pages.GetPage(pageId)?.Item;

                }

            }

            catch (SqlException ex)

            {

                tracker.LogException($"Error processing page item fields (Page ID: {pageId}, Page item ID: {itemId}, Page item type: {itemType}). Exception logged", ex);

            }


            if (item is null)

                return string.Empty;


            if (isPropertyItem)

                AddOrMerge(document, "PagePropertyItemType", itemType);


            // Merge fields into page document

            foreach (string name in item.Names)

            {

                var value = item[name];

                if (IsNullOrDbNull(value))

                    continue;


                var key = isPropertyItem

                    ? string.Format(CultureInfo.InvariantCulture, "Property_{0}_{1}", itemType, name)

                    : string.Format(CultureInfo.InvariantCulture, "{0}_{1}", itemType, name);


                var itemFieldKey = GetItemFieldKey(itemType, name);


                if (ItemArrayFields.TryGetValue(itemFieldKey, out Type? typeOfCollection))

                {

                    if (TryConvertCollectionValue(value, typeOfCollection, out var arrValue) && arrValue is not null)

                        AddOrMerge(document, key, arrValue);

                    else

                        AddOrMerge(document, key, value);

                }

                else

                {

                    AddOrMerge(document, key, value);

                }

            }


            // Keep return value for compatibility (not used as merged blob here)

            return GetItemContent(item, SkipPageItemRelationLists);

        }


        private void HandleParagraphs(int pageId, IndexDocument document, IIndexWriter writer, Tracker tracker)

        {

            var paragraphHeaders = new List<string>();

            var paragraphTexts = new List<string>();


            var paragraphItemIds = new List<string>();


            if (!PageParagraphs.TryGetValue(pageId, out var paragraphs))

                return;


            Exception? firstException = null;

            var errorMessages = new List<string>();


            foreach (DataRow paragraphRow in paragraphs)

            {

                // App providers may emit separate docs (kept as-is)

                string sysName = Converter.ToString(paragraphRow["ParagraphModuleSystemName"]);

                if (!string.IsNullOrEmpty(sysName) && (string.IsNullOrEmpty(AppsToHandle) || AppsToHandle.Split([',', ';']).Contains(sysName)))

                {

                    var paragraphAppIndexProvider = _allAppProviders.Value.FirstOrDefault(appIndexProvider => appIndexProvider.CanHandle(sysName));

                    if (paragraphAppIndexProvider is not null)

                    {

                        IEnumerable<IndexDocument> indexDocs = paragraphAppIndexProvider.GetDocuments(pageId, document, paragraphRow).ToList();

                        tracker.Status.TotalCount = tracker.Status.TotalCount + indexDocs.Count();

                        foreach (var doc in indexDocs)

                            WriteDocument(doc, pageId, writer, tracker);

                    }

                }


                // Header/text arrays (kept)

                var paragraphHeader = Converter.ToString(paragraphRow["ParagraphHeader"]);

                if (!string.IsNullOrEmpty(paragraphHeader))

                    paragraphHeaders.Add(paragraphHeader);


                var paragraphText = Converter.ToString(paragraphRow["ParagraphText"]);

                if (!string.IsNullOrEmpty(paragraphText))

                {

                    paragraphText = HtmlEncoder.HtmlDecode(paragraphText);

                    if (StripHtml)

                        paragraphText = StringHelper.StripHtml(paragraphText);


                    paragraphTexts.Add(paragraphText);

                }


                // Paragraph item fields individually (NO merged ParagraphContent blob)

                if (!ExcludeItemsFromIndex)

                {

                    string itemType = Converter.ToString(paragraphRow["ParagraphItemType"]);

                    if (!string.IsNullOrEmpty(itemType))

                    {

                        int paragraphId = Converter.ToInt32(paragraphRow["ParagraphID"]);


                        try

                        {

                            var paragraph = Services.Paragraphs.GetParagraph(paragraphId)

                                ?? throw new InvalidOperationException($"Paragraph with ID {paragraphId} not found");


                            var item = paragraph.Item;

                            if (item is not null)

                            {

                                var itemId = Converter.ToString(paragraphRow["ParagraphItemID"]);

                                if (!string.IsNullOrEmpty(itemId) && itemId != "0")

                                    paragraphItemIds.Add($"{itemType}:{itemId}");


                                AddItemFieldsToDocument(document, item, prefix: "Paragraph_", skipRelationLists: SkipParagraphItemRelationLists);

                            }

                        }

                        catch (Exception ex) when (ex is InvalidOperationException || ex is SqlException)

                        {

                            firstException ??= ex;

                            errorMessages.Add($"Error processing paragraph (Paragraph ID: '{paragraphRow["ParagraphID"]}', Paragraph item type: '{paragraphRow["ParagraphItemType"]}', Paragraph item ID: '{paragraphRow["ParagraphItemID"]}')");

                        }

                    }

                }

            }


            if (firstException is not null)

            {

                LogEventManager.Current.LogErrors(LogCategory.Health, "IndexBuild", $"Error processing paragraph on page {pageId}", errorMessages, firstException);

                tracker.LogException($"Error processing paragraphs on page {pageId}", firstException);

            }


            AddOrMerge(document, "ParagraphHeaders", paragraphHeaders.ToArray());

            AddOrMerge(document, "ParagraphTexts", paragraphTexts.ToArray());


            if (paragraphItemIds.Count > 0)

                AddOrMerge(document, "ParagraphItemIds", paragraphItemIds.ToArray());

        }


        private void HandleRowItems(int pageId, IndexDocument document)

        {

            var rowItemIds = new List<string>();


            foreach (var row in Services.Grids.GetGridRowsByPageId(pageId))

            {

                if (string.IsNullOrEmpty(row.ItemType) || string.IsNullOrEmpty(row.ItemId))

                    continue;


                var item = ItemManager.Storage.GetById(row.ItemType, row.ItemId);

                if (item is null)

                    continue;


                rowItemIds.Add($"{row.ItemType}:{row.ItemId}");

                AddItemFieldsToDocument(document, item, prefix: "Row_", skipRelationLists: SkipRowItemRelationLists);

            }


            if (rowItemIds.Count > 0)

                AddOrMerge(document, "RowItemIds", rowItemIds.ToArray());

        }


        private void AddItemFieldsToDocument(IndexDocument document, Item item, string prefix, bool skipRelationLists)

        {

            foreach (var name in item.Names)

            {

                var raw = item[name];

                if (IsNullOrDbNull(raw))

                    continue;


                var fieldKey = GetItemFieldKey(item.SystemName, name);


                // Respect ExcludeFromSearch and your derived exclusions

                if (ItemExcludedFields.Contains(fieldKey))

                    continue;


                // If relation list fields should be skipped, skip them here (we don't flatten relation list items into fields)

                if (skipRelationLists && ItemRelationListFields.Contains(fieldKey))

                    continue;


                var indexFieldName = $"{prefix}{item.SystemName}_{name}";


                if (ItemArrayFields.TryGetValue(fieldKey, out var collectionType))

                {

                    if (TryConvertCollectionValue(raw, collectionType, out var converted) && converted is not null)

                        AddOrMerge(document, indexFieldName, converted);

                    else

                        AddOrMerge(document, indexFieldName, raw);

                }

                else

                {

                    AddOrMerge(document, indexFieldName, raw);

                }

            }

        }


        private static bool IsNullOrDbNull(object? v) => v is null || Convert.IsDBNull(v);


        private static IEnumerable<object> ToObjectEnumerable(object value)

        {

            // Treat string as scalar, not IEnumerable<char>

            if (value is string) return new object[] { value };


            if (value is IEnumerable enumerable)

            {

                var list = new List<object>();

                foreach (var x in enumerable)

                {

                    if (!IsNullOrDbNull(x)) list.Add(x!);

                }

                return list;

            }


            return new object[] { value };

        }


        private static object? MergeValues(object? existing, object incoming)

        {

            if (IsNullOrDbNull(incoming))

                return existing;


            if (existing is null)

                return incoming;


            var left = ToObjectEnumerable(existing);

            var right = ToObjectEnumerable(incoming);


            var merged = left.Concat(right)

                .Where(x => x is not null && !IsNullOrDbNull(x))

                .Distinct(new LooseEqualityComparer())

                .ToList();


            if (merged.Count == 0)

                return null;


            // If the value repeats, keep multi-valued.

            if (merged.Count == 1)

                return merged[0];


            // Try to keep typed arrays where it’s unambiguous; otherwise object[]

            var elementType = merged.Select(x => x.GetType()).Distinct().SingleOrDefault();


            if (elementType == typeof(string))

                return merged.Cast<string>().ToArray();


            if (elementType == typeof(int))

                return merged.Cast<int>().ToArray();


            return merged.ToArray();

        }


        private sealed class LooseEqualityComparer : IEqualityComparer<object>

        {

            public new bool Equals(object? x, object? y)

            {

                if (ReferenceEquals(x, y)) return true;

                if (x is null || y is null) return false;


                if (x is string xs && y is string ys)

                    return string.Equals(xs, ys, StringComparison.OrdinalIgnoreCase);


                return x.Equals(y);

            }


            public int GetHashCode(object obj)

            {

                if (obj is string s)

                    return StringComparer.OrdinalIgnoreCase.GetHashCode(s);


                return obj.GetHashCode();

            }

        }


        private static void AddOrMerge(IndexDocument doc, string field, object value)

        {

            if (IsNullOrDbNull(value))

                return;


            if (!doc.ContainsKey(field))

            {

                doc.Add(field, value);

                return;

            }


            var existing = doc[field];

            var merged = MergeValues(existing, value);

            if (merged is not null)

                doc[field] = merged;

        }


        private bool TryConvertCollectionValue(object? value, Type collectionType, [NotNullWhen(true)] out object? result)

        {

            result = null;


            if (!TryConvertCollectionValue(value, collectionType, [typeof(int), typeof(string)], out var convertedCollection))

                return false;


            if (collectionType.IsAssignableFrom(convertedCollection.GetType()))

            {

                result = convertedCollection;

                return true;

            }


            try

            {

                result = ValueConverter.Convert(convertedCollection, collectionType);

                return result is not null;

            }

            catch

            {

                return false;

            }

        }


        private bool TryConvertCollectionValue(object? value, Type collectionType, List<Type> valueTypesToTry, [NotNullWhen(true)] out object? result)

        {

            try

            {

                result = ValueConverter.Convert(value, collectionType);

                return result is not null;

            }

            catch

            {

                // Ignore and retry with alternative element type

            }


            var isGenericCollectionType = collectionType.IsGenericType && !collectionType.IsGenericTypeDefinition;

            if (!isGenericCollectionType)

            {

                result = null;

                return false;

            }


            if (valueTypesToTry.Count == 0)

            {

                result = null;

                return false;

            }


            var valueType = collectionType.GetGenericArguments().FirstOrDefault();

            if (valueType is not null)

                valueTypesToTry.Remove(valueType);

            else

            {

                result = null;

                return false;

            }


            var typeDefinition = collectionType.GetGenericTypeDefinition();

            collectionType = typeDefinition.MakeGenericType(valueTypesToTry.First());


            return TryConvertCollectionValue(value, collectionType, valueTypesToTry, out result);

        }


        private string GetItemContent(Item item, bool skipItemRelationLists)

        {

            var output = new StringBuilder();

            GetItemContent(output, item, skipItemRelationLists, 0);

            var content = HtmlEncoder.HtmlDecode(output.ToString());

            if (StripHtml)

                content = StringHelper.StripHtml(content);


            return content;

        }


        private void GetItemContent(StringBuilder stringBuilder, Item item, bool skipItemRelationLists, int depth)

        {

            if (depth > 2)

                return;


            foreach (string name in item.Names)

            {

                string value = Converter.ToString(item[name]);

                if (string.IsNullOrEmpty(value))

                    continue;


                var fieldKey = GetItemFieldKey(item.SystemName, name);


                if (!skipItemRelationLists && ItemRelationListFields.Contains(fieldKey))

                {

                    GetItemRelationListContent(stringBuilder, Converter.ToInt32(value), depth + 1);

                }

                else if (ItemTypeFields.TryGetValue(fieldKey, out var fieldMetaData))

                {

                    var editor = fieldMetaData.CreateEditorInstance(false) as ItemTypeEditor;

                    if (!string.IsNullOrEmpty(editor?.ItemType))

                    {

                        var fieldItem = ItemManager.Storage.GetById(editor.ItemType, value);

                        if (fieldItem is not null)

                            GetItemContent(stringBuilder, fieldItem, skipItemRelationLists, depth);

                    }

                }

                else if (!ItemExcludedFields.Contains(fieldKey))

                {

                    stringBuilder.Append(value);

                    stringBuilder.Append(' ');

                }

            }

        }


        private void GetItemRelationListContent(StringBuilder stringBuilder, int itemListId, int depth)

        {

            var itemList = ItemList.GetItemListById(itemListId);

            if (itemList is not null)

            {

                foreach (Item item in itemList.Relations.Cast<Item>())

                    GetItemContent(stringBuilder, item, false, depth);

            }

        }


        public override IEnumerable<FieldDefinitionBase> GetFields()

        {

            var extender = new ContentIndexSchemaExtender();

            return extender.GetFields();

        }


        public override IEnumerable<string> SupportedActions => new[] { "Full" };


        public override IDictionary<string, object> DefaultSettings => new Dictionary<string, object>()

        {

            { "ExcludeItemsFromIndex", false },

            { "SkipPageItemRelationLists", false },

            { "SkipRowItemRelationLists", false },

            { "SkipParagraphItemRelationLists", false },

            { "StripHtml", false },

            { "AppsToHandle", string.Empty }

        };

    }
}
 
Andreas Rundgren
Reply

We changed it so we have on a page field instead for it.

 

You must be logged in to post in the forum