ConfigurableLanguageManager.php 14.6 KB
Newer Older
1 2 3 4 5 6 7 8 9
<?php

/**
 * @file
 * Contains \Drupal\language\ConfigurableLanguageManager.
 */

namespace Drupal\language;

10
use Drupal\Core\Language\LanguageInterface;
11
use Drupal\Core\PhpStorage\PhpStorageFactory;
12
use Drupal\Core\Config\ConfigFactoryInterface;
13 14
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\Language;
15
use Drupal\Core\Language\LanguageDefault;
16
use Drupal\Core\Language\LanguageManager;
17
use Drupal\Core\Url;
18
use Drupal\language\Config\LanguageConfigFactoryOverrideInterface;
19
use Drupal\language\Entity\ConfigurableLanguage;
20
use Symfony\Component\HttpFoundation\RequestStack;
21 22 23 24 25 26 27 28 29

/**
 * Overrides default LanguageManager to provide configured languages.
 */
class ConfigurableLanguageManager extends LanguageManager implements ConfigurableLanguageManagerInterface {

  /**
   * The configuration storage service.
   *
30
   * @var \Drupal\Core\Config\ConfigFactoryInterface
31 32 33 34 35 36 37 38 39 40
   */
  protected $configFactory;

  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

41 42 43 44 45 46 47
  /**
   * The language configuration override service.
   *
   * @var \Drupal\language\Config\LanguageConfigFactoryOverrideInterface
   */
  protected $configFactoryOverride;

48 49 50
  /**
   * The request object.
   *
51
   * @var \Symfony\Component\HttpFoundation\RequestStack
52
   */
53
  protected $requestStack;
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

  /**
   * The language negotiator.
   *
   * @var \Drupal\language\LanguageNegotiatorInterface
   */
  protected $negotiator;

  /**
   * Local cache for language type configuration data.
   *
   * @var array
   */
  protected $languageTypes;

  /**
   * Local cache for language type information.
   *
   * @var array
   */
  protected $languageTypesInfo;

  /**
   * An array of language objects keyed by language type.
   *
79
   * @var \Drupal\Core\Language\LanguageInterface[]
80 81 82
   */
  protected $negotiatedLanguages;

83 84 85 86 87 88 89
  /**
   * An array of language negotiation method IDs keyed by language type.
   *
   * @var array
   */
  protected $negotiatedMethods;

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
  /**
   * Whether or not the language manager has been initialized.
   *
   * @var bool
   */
  protected $initialized = FALSE;

  /**
   * Whether already in the process of language initialization.
   *
   * @var bool
   */
  protected $initializing = FALSE;

  /**
105
   * {@inheritdoc}
106 107 108 109 110 111 112 113
   */
  public static function rebuildServices() {
    PhpStorageFactory::get('service_container')->deleteAll();
  }

  /**
   * Constructs a new ConfigurableLanguageManager object.
   *
114 115
   * @param \Drupal\Core\Language\LanguageDefault $default_language
   *   The default language service.
116
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
117
   *   The configuration factory service.
118 119
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler service.
120 121
   * @param \Drupal\language\Config\LanguageConfigFactoryOverrideInterface $config_override
   *   The language configuration override service.
122 123
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack object.
124
   */
125
  public function __construct(LanguageDefault $default_language, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, LanguageConfigFactoryOverrideInterface $config_override, RequestStack $request_stack) {
126
    $this->defaultLanguage = $default_language;
127 128
    $this->configFactory = $config_factory;
    $this->moduleHandler = $module_handler;
129
    $this->configFactoryOverride = $config_override;
130
    $this->requestStack = $request_stack;
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
  }

  /**
   * {@inheritdoc}
   */
  public function init() {
    if (!$this->initialized) {
      foreach ($this->getDefinedLanguageTypes() as $type) {
        $this->getCurrentLanguage($type);
      }
      $this->initialized = TRUE;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function isMultilingual() {
149
    return count($this->getLanguages(LanguageInterface::STATE_CONFIGURABLE)) > 1;
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
  }

  /**
   * {@inheritdoc}
   */
  public function getLanguageTypes() {
    $this->loadLanguageTypesConfiguration();
    return $this->languageTypes['configurable'];
  }

  /**
   * {@inheritdoc}
   */
  public function getDefinedLanguageTypes() {
    $this->loadLanguageTypesConfiguration();
    return $this->languageTypes['all'];
  }

  /**
   * Retrieves language types from the configuration storage.
   *
   * @return array
   *   An array of language type names.
   */
  protected function loadLanguageTypesConfiguration() {
    if (!$this->languageTypes) {
      $this->languageTypes = $this->configFactory->get('language.types')->get() ?: array('configurable' => array(), 'all' => parent::getLanguageTypes());
    }
    return $this->languageTypes;
  }

  /**
   * {@inheritdoc}
   */
  public function getDefinedLanguageTypesInfo() {
    if (!isset($this->languageTypesInfo)) {
      $info = $this->moduleHandler->invokeAll('language_types_info');
      // Let other modules alter the list of language types.
      $this->moduleHandler->alter('language_types_info', $info);
      $this->languageTypesInfo = $info;
    }
    return $this->languageTypesInfo;
  }

  /**
195
   * {@inheritdoc}
196
   */
197
  public function saveLanguageTypesConfiguration(array $values) {
198
    $config = $this->configFactory->getEditable('language.types');
199 200 201 202 203 204 205
    if (isset($values['configurable'])) {
      $config->set('configurable', $values['configurable']);
    }
    if (isset($values['all'])) {
      $config->set('all', $values['all']);
    }
    $config->save();
206 207 208 209 210
  }

  /**
   * {@inheritdoc}
   */
211
  public function getCurrentLanguage($type = LanguageInterface::TYPE_INTERFACE) {
212 213 214 215 216 217 218
    if (!isset($this->negotiatedLanguages[$type])) {
      // Ensure we have a valid value for this language type.
      $this->negotiatedLanguages[$type] = $this->getDefaultLanguage();

      if ($this->negotiator && $this->isMultilingual()) {
        if (!$this->initializing) {
          $this->initializing = TRUE;
219 220 221
          $negotiation = $this->negotiator->initializeType($type);
          $this->negotiatedLanguages[$type] = reset($negotiation);
          $this->negotiatedMethods[$type] = key($negotiation);
222 223 224 225 226 227 228 229
          $this->initializing = FALSE;
        }
        // If the current interface language needs to be retrieved during
        // initialization we return the system language. This way string
        // translation calls happening during initialization will return the
        // original strings which can be translated by calling them again
        // afterwards. This can happen for instance while parsing negotiation
        // method definitions.
230 231
        elseif ($type == LanguageInterface::TYPE_INTERFACE) {
          return new Language(array('id' => LanguageInterface::LANGCODE_SYSTEM));
232 233 234 235 236 237 238 239 240 241 242 243 244 245
        }
      }
    }

    return $this->negotiatedLanguages[$type];
  }

  /**
   * {@inheritdoc}
   */
  public function reset($type = NULL) {
    if (!isset($type)) {
      $this->initialized = FALSE;
      $this->negotiatedLanguages = array();
246
      $this->negotiatedMethods = array();
247 248
      $this->languageTypes = NULL;
      $this->languageTypesInfo = NULL;
249
      $this->languages = array();
250 251 252 253 254 255
      if ($this->negotiator) {
        $this->negotiator->reset();
      }
    }
    elseif (isset($this->negotiatedLanguages[$type])) {
      unset($this->negotiatedLanguages[$type]);
256
      unset($this->negotiatedMethods[$type]);
257
    }
258
    return $this;
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
  }

  /**
   * {@inheritdoc}
   */
  public function getNegotiator() {
    return $this->negotiator;
  }

  /**
   * {@inheritdoc}
   */
  public function setNegotiator(LanguageNegotiatorInterface $negotiator) {
    $this->negotiator = $negotiator;
    $this->initialized = FALSE;
    $this->negotiatedLanguages = array();
  }

  /**
   * {@inheritdoc}
   */
280
  public function getLanguages($flags = LanguageInterface::STATE_CONFIGURABLE) {
281 282 283 284 285 286 287
    // If a config override is set, cache using that language's ID.
    if ($override_language = $this->getConfigOverrideLanguage()) {
      $static_cache_id = $override_language->getId();
    }
    else {
      $static_cache_id = $this->getCurrentLanguage()->getId();
    }
288

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    if (!isset($this->languages[$static_cache_id][$flags])) {
      // Initialize the language list with the default language and default
      // locked languages. These cannot be removed. This serves as a fallback
      // list if this method is invoked while the language module is installed
      // and the configuration entities for languages are not yet fully
      // imported.
      $default = $this->getDefaultLanguage();
      $languages = array($default->getId() => $default);
      $languages += $this->getDefaultLockedLanguages($default->getWeight());

      // Load configurable languages on top of the defaults. Ideally this could
      // use the entity API to load and instantiate ConfigurableLanguage
      // objects. However the entity API depends on the language system, so that
      // would result in infinite loops. We use the configuration system
      // directly and instantiate runtime Language objects. When language
      // entities are imported those cover the default and locked languages, so
      // site-specific configuration will prevail over the fallback values.
      // Having them in the array already ensures if this is invoked in the
      // middle of importing language configuration entities, the defaults are
      // always present.
      $config_ids = $this->configFactory->listAll('language.entity.');
310 311 312
      foreach ($this->configFactory->loadMultiple($config_ids) as $config) {
        $data = $config->get();
        $data['name'] = $data['label'];
313
        $languages[$data['id']] = new Language($data);
314
      }
315
      Language::sort($languages);
316

317 318
      // Filter the full list of languages based on the value of $flags.
      $this->languages[$static_cache_id][$flags] = $this->filterLanguages($languages, $flags);
319 320
    }

321
    return $this->languages[$static_cache_id][$flags];
322 323
  }

324 325 326 327
  /**
   * {@inheritdoc}
   */
  public function getNativeLanguages() {
328
    $languages = $this->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
329 330 331 332 333 334 335 336 337 338 339 340 341
    $natives = array();

    $original_language = $this->getConfigOverrideLanguage();

    foreach ($languages as $langcode => $language) {
      $this->setConfigOverrideLanguage($language);
      $natives[$langcode] = ConfigurableLanguage::load($langcode);
    }
    $this->setConfigOverrideLanguage($original_language);
    Language::sort($natives);
    return $natives;
  }

342 343 344 345
  /**
   * {@inheritdoc}
   */
  public function updateLockedLanguageWeights() {
346 347 348
    // Get the weight of the last configurable language.
    $configurable_languages = $this->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
    $max_weight = end($configurable_languages)->getWeight();
349

350
    $locked_languages = $this->getLanguages(LanguageInterface::STATE_LOCKED);
351 352 353 354 355 356 357 358 359 360
    // Update locked language weights to maintain the existing order, if
    // necessary.
    if (reset($locked_languages)->getWeight() <= $max_weight) {
      foreach ($locked_languages as $language) {
        // Update system languages weight.
        $max_weight++;
        ConfigurableLanguage::load($language->getId())
          ->setWeight($max_weight)
          ->save();
      }
361 362 363 364 365 366
    }
  }

  /**
   * {@inheritdoc}
   */
367
  public function getFallbackCandidates(array $context = array()) {
368
    if ($this->isMultilingual()) {
369 370 371 372
      $candidates = array();
      if (empty($context['operation']) || $context['operation'] != 'locale_lookup') {
        // If the fallback context is not locale_lookup, initialize the
        // candidates with languages ordered by weight and add
373
        // LanguageInterface::LANGCODE_NOT_SPECIFIED at the end. Interface
374 375 376
        // translation fallback should only be based on explicit configuration
        // gathered via the alter hooks below.
        $candidates = array_keys($this->getLanguages());
377
        $candidates[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
378 379 380 381 382 383 384
        $candidates = array_combine($candidates, $candidates);

        // The first candidate should always be the desired language if
        // specified.
        if (!empty($context['langcode'])) {
          $candidates = array($context['langcode'] => $context['langcode']) + $candidates;
        }
385 386 387 388 389 390 391 392 393 394 395 396
      }

      // Let other modules hook in and add/change candidates.
      $type = 'language_fallback_candidates';
      $types = array();
      if (!empty($context['operation'])) {
        $types[] = $type . '_' .  $context['operation'];
      }
      $types[] = $type;
      $this->moduleHandler->alter($types, $candidates, $context);
    }
    else {
397
      $candidates = parent::getFallbackCandidates($context);
398 399 400 401 402 403 404 405
    }

    return $candidates;
  }

  /**
   * {@inheritdoc}
   */
406
  public function getLanguageSwitchLinks($type, Url $url) {
407 408 409 410 411 412 413
    $links = FALSE;

    if ($this->negotiator) {
      foreach ($this->negotiator->getNegotiationMethods($type) as $method_id => $method) {
        $reflector = new \ReflectionClass($method['class']);

        if ($reflector->implementsInterface('\Drupal\language\LanguageSwitcherInterface')) {
414
          $result = $this->negotiator->getNegotiationMethodInstance($method_id)->getLanguageSwitchLinks($this->requestStack->getCurrentRequest(), $type, $url);
415 416 417 418 419 420 421 422 423 424 425 426 427 428

          if (!empty($result)) {
            // Allow modules to provide translations for specific links.
            $this->moduleHandler->alter('language_switch_links', $result, $type, $path);
            $links = (object) array('links' => $result, 'method_id' => $method_id);
            break;
          }
        }
      }
    }

    return $links;
  }

429 430 431
  /**
   * {@inheritdoc}
   */
432
  public function setConfigOverrideLanguage(LanguageInterface $language = NULL) {
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
    $this->configFactoryOverride->setLanguage($language);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getConfigOverrideLanguage() {
    return $this->configFactoryOverride->getLanguage();
  }

  /**
   * {@inheritdoc}
   */
  public function getLanguageConfigOverride($langcode, $name) {
    return $this->configFactoryOverride->getOverride($langcode, $name);
  }

451 452 453 454 455 456 457
  /**
   * {@inheritdoc}
   */
  public function getLanguageConfigOverrideStorage($langcode) {
    return $this->configFactoryOverride->getStorage($langcode);
  }

458 459 460
  /**
   * {@inheritdoc}
   */
461
  public function getStandardLanguageListWithoutConfigured() {
462 463 464 465 466 467 468 469 470 471 472 473 474
    $languages = $this->getLanguages();
    $predefined = $this->getStandardLanguageList();
    foreach ($predefined as $key => $value) {
      if (isset($languages[$key])) {
        unset($predefined[$key]);
        continue;
      }
      $predefined[$key] = $this->t($value[0]);
    }
    asort($predefined);
    return $predefined;
  }

475 476 477 478 479 480 481 482 483
  /**
   * {@inheritdoc}
   */
  public function getNegotiatedLanguageMethod($type = LanguageInterface::TYPE_INTERFACE) {
    if (isset($this->negotiatedLanguages[$type]) && isset($this->negotiatedMethods[$type])) {
      return $this->negotiatedMethods[$type];
    }
  }

484
}