You are here

LanguageHierarchyConfigFactoryOverride.php in Language Hierarchy 8

Same filename and directory in other branches
  1. 2.x src/Config/LanguageHierarchyConfigFactoryOverride.php

File

src/Config/LanguageHierarchyConfigFactoryOverride.php
View source
<?php

namespace Drupal\language_hierarchy\Config;

use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Config\LanguageConfigFactoryOverride;

/**
 * Provides language overrides for the configuration factory, with fallbacks.
 */
class LanguageHierarchyConfigFactoryOverride extends LanguageConfigFactoryOverride {

  /**
   * The chain of language codes to fallback through, with most specific first.
   *
   * @var string[]|null
   */
  protected $fallbackChain;

  /**
   * {@inheritdoc}
   */
  public function loadOverrides($names) {

    // Skip to using the parent implementation if there is no fallback chain or
    // language. If the fallback chain just hasn't been computed yet, read it
    // directly from base storage to avoid a circular dependency on the
    // configuration factory.
    if ($this->language && (!isset($this->fallbackChain) || !empty($this->fallbackChain))) {
      $storage = $this
        ->getStorage($this->language
        ->getId());
      $loaded = $storage
        ->readMultiple($names);
      if ($missing = array_diff($names, array_keys($loaded))) {

        // Try fallback languages.
        if (!isset($this->fallbackChain)) {
          $this->fallbackChain = $this
            ->getFallbackChainFromBaseConfig($this->language
            ->getId());
        }
        $ancestors = $this->fallbackChain;
        while ($missing && ($ancestor = array_shift($ancestors))) {
          $fallback_storage = $this
            ->getStorage($ancestor);
          $loaded += $fallback_storage
            ->readMultiple($missing);
          $missing = array_diff($names, array_keys($loaded));
        }
      }
      return $loaded;
    }
    else {
      return parent::loadOverrides($names);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getOverride($langcode, $name) {
    $source_storage = $target_storage = $this
      ->getStorage($langcode);
    $data = $source_storage
      ->read($name);
    if (empty($data)) {

      // Try fallback languages.
      if ($ancestors = $this
        ->getFallbackChainFromConfigEntities($langcode)) {
        while (empty($data) && ($ancestor = array_shift($ancestors))) {
          $fallback_storage = $this
            ->getStorage($ancestor);
          if ($data = $fallback_storage
            ->read($name)) {
            $source_storage = $fallback_storage;
          }
        }
        $override = new LanguageHierarchyConfigOverride($name, $langcode, $source_storage, $target_storage, $this->typedConfigManager, $this->eventDispatcher);
        if (!empty($data)) {
          $override
            ->initWithData($data);
        }
        return $override;
      }
    }

    // If there is data for the specified language, or there are no fallbacks,
    // use the original implementation of ::getOverride().
    return parent::getOverride($langcode, $name);
  }

  /**
   * {@inheritdoc}
   */
  public function setLanguage(LanguageInterface $language = NULL) {
    $this->fallbackChain = $this
      ->getFallbackChainFromConfigEntities($language
      ->getId());
    return parent::setLanguage($language);
  }

  /**
   * Compute the fallback chain for a language code.
   *
   * Before the config factory is available for us to use to get proper language
   * configuration entities, the base storage and raw configuration values have
   * to be used directly to compute the fallback chain.
   */
  protected function getFallbackChainFromBaseConfig($langcode) {

    // The config prefix here has to be hardcoded because it would come from the
    // entity type definition, but the entity type manager cannot return that
    // yet as that indirectly depends on this config factory override service.
    $language_config = $this->baseStorage
      ->read('language.entity.' . $langcode);
    $fallbacks = [];
    while (!empty($language_config['third_party_settings']['language_hierarchy']['fallback_langcode'])) {
      $ancestor_langcode = $language_config['third_party_settings']['language_hierarchy']['fallback_langcode'];

      // Protect against infinite recursion due to unexpected configuration.
      if (in_array($ancestor_langcode, $fallbacks, TRUE)) {
        break;
      }
      else {
        $fallbacks[] = $ancestor_langcode;
        $language_config = $this->baseStorage
          ->read('language.entity.' . $ancestor_langcode);
      }
    }
    return $fallbacks;
  }

  /**
   * Compute the fallback chain for a language code.
   *
   * The entity type manager and language configuration entities should be
   * available by the time this is needed, as language overrides are loaded
   * during the Kernel request event. We cannot inject the entity type manager
   * as a dependency because that would create a circular dependency.
   *
   * @see \Drupal\language\EventSubscriber\LanguageRequestSubscriber::getSubscribedEvents()
   */
  protected function getFallbackChainFromConfigEntities($langcode) {

    /** @var \Drupal\language\ConfigurableLanguageInterface $language_config */
    $language_config = \Drupal::entityTypeManager()
      ->getStorage('configurable_language')
      ->load($langcode);
    $fallbacks = language_hierarchy_get_ancestors($language_config);
    return array_keys($fallbacks);
  }

}

Classes

Namesort descending Description
LanguageHierarchyConfigFactoryOverride Provides language overrides for the configuration factory, with fallbacks.