Language Fallback Module: Endless Fallback Problem Fix

Alex Shyba`s Language Fallback is one of the most popular modules for multi-language websites. Thanks to this module it is possible to automatically fallback to another language version of the item if there is no version found in the requested language. The module is proven to work in many Sitecore solutions.

However, there is one serious issue which may cause a lot of problems even in production environments – there is no protection against circular fallbacks. For example there is no protection for language to fallback to itself (A->A->…), or languages to have circular fallback to each other (A->B->A->…), or even more complex cases with more than 3 languages involved (A->B->C->A->…). This scenarios might bring your servers to the ground with nothing else except application pool crash and some strange error in the server error logs.

On the bright side there is a quick fix that can resolve the problem.

First we need to download the Module Source from the Sitecore Marketplace.

After that we need to open the Sitecore.LanguageFallbackItemProvider solution. The fix will take place in LanguageFallbackItemProvider class. Currently the method recursively calls itself until the item is found in the target language or until there is no fallback language defined. You can check the code below.

namespace Sitecore.Data.Managers
{
  using Globalization;

  using Items;

  /// <summary>
  ///
  /// </summary>
  public class LanguageFallbackItemProvider : ItemProvider
  {
    /// <summary>
    ///
    /// </summary>
    /// <param name="itemId"></param>
    /// <param name="language"></param>
    /// <param name="version"></param>
    /// <param name="database"></param>
    /// <returns></returns>
    protected override Item GetItem(ID itemId, Language language, Version version, Database database)
    {
      var item = base.GetItem(itemId, language, version, database);

      if (item == null)
      {
        return item;
      }

      if (item.Versions.GetVersionNumbers().Length > 0)
      {
        return item;
      }

      // there is no version in this language.
      Language fallbackLanguage = LanguageFallbackManager.GetFallbackLanguage(language, database, itemId);
      if (fallbackLanguage == null)
      {
        return item;
      }

      Item fallback = GetItem(itemId, fallbackLanguage, Version.Latest, database);

      var stubData = new ItemData(fallback.InnerData.Definition, item.Language, item.Version, fallback.InnerData.Fields);
      var stub = new StubItem(itemId, stubData, database) { OriginalLanguage = item.Language };
      stub.RuntimeSettings.SaveAll = true;

      return stub;
    }
  }
}

The idea behind the fix is to create a new method which will check if the fallback language equals the current language or if not – to keep a collection of languages which were already tested for fallback value and if the languages start to repeat – to return the item (or null). The fixed code below.



namespace Sitecore.Data.Managers
{
    using Globalization;

    using Items;

    using System.Collections.Generic;

    /// <summary>
    ///
    /// </summary>
    public class LanguageFallbackItemProvider : ItemProvider
    {
        /// <summary>
        ///
        /// </summary>
        /// <param name="itemId"></param>
        /// <param name="language"></param>
        /// <param name="version"></param>
        /// <param name="database"></param>
        /// <returns></returns>
        protected override Item GetItem(ID itemId, Language language, Version version, Database database)
        {
            return GetItem(itemId, language, version, database, new List<Language> { language });

        }

        private Item GetItem(ID itemId, Language language, Version version, Database database,
                             List<Language> fallbackedLanguages)
        {

            var item = base.GetItem(itemId, language, version, database);

            if (item == null)
            {
                return item;
            }

            if (item.Versions.GetVersionNumbers().Length > 0)
            {
                return item;
            }

            Language fallbackLanguage = LanguageFallbackManager.GetFallbackLanguage(language, database, itemId);

            // If the fallback language is null, or the fallback language matches the current language (A-A Reference), or if the language was already tested for fallback value (A-B-... Reference) - Return the item (null).
            if (fallbackLanguage == null || fallbackLanguage == language || fallbackedLanguages.Contains(fallbackLanguage))
            {
                return item;
            }

            //Add the fallback language to the collection
            fallbackedLanguages.Add(fallbackLanguage);

            Item fallback = GetItem(itemId, fallbackLanguage, Version.Latest, database, fallbackedLanguages);

            var stubData = new ItemData(fallback.InnerData.Definition, item.Language, item.Version,
                                        fallback.InnerData.Fields);
            var stub = new StubItem(itemId, stubData, database) { OriginalLanguage = item.Language };
            stub.RuntimeSettings.SaveAll = true;

            return stub;
        }

    }
}

And that is it ! Now you can sleep well knowing that even if someone messes up with the language fallbacks the server won`t go down !

Happy Fallbacking ! 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s