Published content does not get indexed for language variants when using Language Fallback Module

Just when I thought I have Alex Shyba's Language Fallback module all figured out, I ran into a bug related to indexing fallback versions of items.

Bug (Short Version):
Fallback language variants of items do not get crawled by the default SitecoreItemCrawler. 

Bug Explained (Long Version)
The reason is that the default crawler relies on field values stored in the database in order to index them. The Fallback Provider in the fallback module "spoofs" fallback language version of  the field values by returning the field values of the master language version of the item, and as such does not store these values in database. The result is that you only have the master language field values available for crawling.

But thanks to the Sitecore community, had already been resolved here.
However, this resolution assumes that you are working with SEARCH CONTRIB / ADVANCED DATABASE CRAWLER module.

I wasn't looking to introduce more uncertainty to my solution by adding more of untested third party code, especially anything that deals with the "Language Fallback Module" and this close to production launch. So the only option I was left with was to intercept the item before it gets indexed and update the fallback field values. So, here is the custom crawler I ended up writing that did the trick (Note: most of the code is from SitecoreItemCrawler):


    protected override void DoAdd(IProviderUpdateContext context, SitecoreIndexableItem indexable)
        {
            Assert.ArgumentNotNull((object)context, "context");
            Assert.ArgumentNotNull((object)indexable, "indexable");
            this.Index.Locator.GetInstance<IEvent>().RaiseEvent("indexing:adding", (object)context.Index.Name, (object)indexable.UniqueId, (object)indexable.AbsolutePath);
            if (this.IsExcludedFromIndex(indexable, false))
                return;
            foreach (Sitecore.Globalization.Language language in indexable.Item.Languages)
            {
                Item item;
                using (new SitecoreCachesDisabler())
                    item = indexable.Item.Database.GetItem(indexable.Item.ID, language, Sitecore.Data.Version.Latest);

               
                if (item == null)
                {
                    CrawlingLog.Log.Warn(string.Format("SitecoreItemCrawler : AddItem : Could not build document data {0} - Latest version could not be found. Skipping.", (object)indexable.Item.Uri), (Exception)null);
                }
                else
                {
                    item.Fields.ReadAll();
                    Item[] versions;
                    using (new SitecoreCachesDisabler())
                        versions = item.Versions.GetVersions(false);

                    foreach (Item versionItem in versions)
                    {
                        SitecoreIndexableItem sitecoreIndexableItem = (SitecoreIndexableItem)versionItem;
                        //update indexabled item for missing field values for fallback language variants
                        //this must execute before value for latest version is assigned
                        if (sitecoreIndexableItem.Item != null
                               && sitecoreIndexableItem.Item.Language.Name != string.Empty
                               && sitecoreIndexableItem.Item.Language != LanguageManager.DefaultLanguage
                               && !sitecoreIndexableItem.Item.IsOfSupportedTemplateCustom())
                            sitecoreIndexableItem = UpdateFallbackFieldValues(versionItem);

                        IIndexableBuiltinFields indexableBuiltinFields = (IIndexableBuiltinFields)sitecoreIndexableItem;
                        indexableBuiltinFields.IsLatestVersion = indexableBuiltinFields.Version == item.Version.Number;
                        sitecoreIndexableItem.IndexFieldStorageValueFormatter = context.Index.Configuration.Inde
                        this.Operations.Add((IIndexable)sitecoreIndexableItem, context, this.index.Configuration);
                    }
                }
            }
            this.Index.Locator.GetInstance<IEvent>().RaiseEvent("indexing:added", (object)context.Index.Name, (object)indexable.UniqueId, (object)indexable.AbsolutePath);
        }

        protected override void DoUpdate(IProviderUpdateContext context, SitecoreIndexableItem indexable)
        {
            Assert.ArgumentNotNull((object)context, "context");
            Assert.ArgumentNotNull((object)indexable, "indexable");
            if (this.IndexUpdateNeedDelete(indexable))
            {
                this.Index.Locator.GetInstance<IEvent>().RaiseEvent("indexing:deleteitem", (object)this.index.Name, (object)indexable.UniqueId, (object)indexable.AbsolutePath);
                this.Operations.Delete((IIndexable)indexable, context);
            }
            else
            {
                this.Index.Locator.GetInstance<IEvent>().RaiseEvent("indexing:updatingitem", (object)this.index.Name, (object)indexable.UniqueId, (object)indexable.AbsolutePath);
                if (!this.IsExcludedFromIndex(indexable, true))
                {
                    foreach (Sitecore.Globalization.Language language in indexable.Item.Languages)
                    {
                        Item indexableItem;
                        using (new SitecoreCachesDisabler())
                            indexableItem = indexable.Item.Database.GetItem(indexable.Item.ID, language, Sitecore.Data.Version.Latest);
                        if (indexableItem == null)
                        {
                            CrawlingLog.Log.Warn(string.Format("SitecoreItemCrawler : Update : Latest version not found for item {0}. Skipping.", (object)indexable.Item.Uri), (Exception)null);
                        }
                        else
                        {
                            Item[] versions;
                            using (new SitecoreCachesDisabler())
                                versions = indexableItem.Versions.GetVersions(false);
                            foreach (Item versionItem in versions)
                            {
                                SitecoreIndexableItem versionIndexable = this.PrepareIndexableVersion(versionItem, context);
                                this.Operations.Update((IIndexable)versionIndexable, context, context.Index.Configuration);
                                this.UpdateClones(context, versionIndexable);
                            }
                        }
                    }
                    this.Index.Locator.GetInstance<IEvent>().RaiseEvent("indexing:updateditem", (object)this.index.Name, (object)indexable.UniqueId, (object)indexable.AbsolutePath);
                }
                if (!this.DocumentOptions.ProcessDependencies)
                    return;
                this.Index.Locator.GetInstance<IEvent>().RaiseEvent("indexing:updatedependents", (object)this.index.Name, (object)indexable.UniqueId, (object)indexable.AbsolutePath);
                this.UpdateDependents(context, indexable);
            }
        }

        internal SitecoreIndexableItem PrepareIndexableVersion(Item item, IProviderUpdateContext context)
        {
            SitecoreIndexableItem sitecoreIndexableItem = (SitecoreIndexableItem)item;

           //update indexable item for missing field values for fallback language variants
           //this must execute before vale for latest version is assigned
            if (sitecoreIndexableItem.Item != null
                   && sitecoreIndexableItem.Item.Language.Name != string.Empty
                   && sitecoreIndexableItem.Item.Language != LanguageManager.DefaultLanguage
                   && !sitecoreIndexableItem.Item.IsOfSupportedTemplateCustom())
                sitecoreIndexableItem = UpdateFallbackFieldValues(item);
            
((IIndexableBuiltinFields)sitecoreIndexableItem).IsLatestVersion = item.Versions.IsLatestVersion(); sitecoreIndexableItem.IndexFieldStorageValueFormatter = context.Index.Configuration.IndexFieldStorageValueFormatter;  
            
            return sitecoreIndexableItem;
        }

        private void UpdateClones(IProviderUpdateContext context, SitecoreIndexableItem versionIndexable)
        {
            IEnumerable<Item> clones;
            using (new SitecoreCachesDisabler())
                clones = versionIndexable.Item.GetClones(false);
            foreach (Item obj in clones)
            {
                SitecoreIndexableItem sitecoreIndexableItem = this.PrepareIndexableVersion(obj, context);
                if (!this.IsExcludedFromIndex((SitecoreIndexableItem)obj, false))
                    this.Operations.Update((IIndexable)sitecoreIndexableItem, context, context.Index.Configuration);
            }
        }
        

//this is where the magic happens
private SitecoreIndexableItem UpdateFallbackFieldValues(Item item) {
            //my default item is in DefaultLanguage since all my languages fallback to DefaultLanguage
            //you can use the fallback extension to fetch the fallback language version
            var defaultItem = item.Database.GetItem(item.ID, LanguageManager.DefaultLanguage);
            var itemDefinition = new ItemDefinition(item.ID, item.Name, item.TemplateID, item.BranchId);
            var fieldList = new FieldList();
            foreach (Field field in item.Fields)
            {
                if (string.IsNullOrEmpty(field.Value)
                    && !string.IsNullOrEmpty(defaultItem.Fields[field.ID].Value)
                    && !field.Name.StartsWith("_"))
                {
                    fieldList.Add(field.ID, defaultItem.Fields[field.ID].Value);
                }
                else
                {
                    fieldList.Add(field.ID, field.Value);
                }
            }
            var itemData = new ItemData(itemDefinition, item.Language, item.Version, fieldList);
            var updatedItem = new Item(item.ID, itemData, item.Database);

            return new SitecoreIndexableItem(updatedItem);
        }

And the config element:
    <locations hint="list:AddCrawler">
        <crawler type="Namespace.CustomCrawler, Assemblyname">
            <Database>web</Database>
            <Root>/sitecore/content/site</Root>
        </crawler>
    </locations>

Comments

Popular posts from this blog

RESOLVED: Solr Exceptions - Document contains at least one immense term in field

First look at Sitecore XM Cloud: Part 4 - Creating a new Site

Is Rendered Item Valid XHtml Document Could not find schema information warnings during publish item Sitecore 7.2