Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\language\Annotation\LanguageNegotiation.
*/
namespace Drupal\language\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a language negotiation annotation object.
*
* Plugin Namespace: Plugin\LanguageNegotiation
*
* For a working example, see
* \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser.
*
* @see \Drupal\language\LanguageNegotiator
* @see \Drupal\language\LanguageNegotiationMethodManager
* @see \Drupal\language\LanguageNegotiationMethodInterface
* @see hook_language_negotiation_info_alter()
* @see plugin_api
*
* @Annotation
*/
class LanguageNegotiation extends Plugin {
/**
* The language negotiation plugin ID.
*
* @var string
*/
public $id;
/**
* An array of allowed language types.
*
* If a language negotiation plugin does not specify which language types it
* should be used with, it will be available for all the configurable
* language types.
*
* @var string[]
* An array of language types, such as the
* \Drupal\Core\Language\LanguageInterface::TYPE_* constants.
*/
public $types;
/**
* The default weight of the language negotiation plugin.
*
* @var int
*/
public $weight;
/**
* The human-readable name of the language negotiation plugin.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $name;
/**
* The description of the language negotiation plugin.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $description;
/**
* The route pointing to the plugin's configuration page.
*
* @var string
*/
public $config_route_name;
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\language\Config\LanguageConfigCollectionNameTrait.
*/
namespace Drupal\language\Config;
use Drupal\Component\Utility\SafeMarkup;
/**
* Provides a common trait for working with language override collection names.
*/
trait LanguageConfigCollectionNameTrait {
/**
* Creates a configuration collection name based on a language code.
*
* @param string $langcode
* The language code.
*
* @return string
* The configuration collection name for a language code.
*/
protected function createConfigCollectionName($langcode) {
return 'language.' . $langcode;
}
/**
* Converts a configuration collection name to a language code.
*
* @param string $collection
* The configuration collection name.
*
* @return string
* The language code of the collection.
*
* @throws \InvalidArgumentException
* Exception thrown if the provided collection name is not in the format
* "language.LANGCODE".
*
* @see self::createConfigCollectionName()
*/
protected function getLangcodeFromCollectionName($collection) {
preg_match('/^language\.(.*)$/', $collection, $matches);
if (!isset($matches[1])) {
throw new \InvalidArgumentException(SafeMarkup::format('!collection is not a valid language override collection', array('!collection' => $collection)));
}
return $matches[1];
}
}

View file

@ -0,0 +1,225 @@
<?php
/**
* @file
* Contains \Drupal\language\Config\LanguageConfigFactoryOverride.
*/
namespace Drupal\language\Config;
use Drupal\Core\Config\ConfigCollectionInfo;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigFactoryOverrideBase;
use Drupal\Core\Config\ConfigRenameEvent;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Language\LanguageDefault;
use Drupal\Core\Language\LanguageInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Provides language overrides for the configuration factory.
*/
class LanguageConfigFactoryOverride extends ConfigFactoryOverrideBase implements LanguageConfigFactoryOverrideInterface, EventSubscriberInterface {
use LanguageConfigCollectionNameTrait;
/**
* The configuration storage.
*
* Do not access this directly. Should be accessed through self::getStorage()
* so that the cache of storages per langcode is used.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $baseStorage;
/**
* An array of configuration storages keyed by langcode.
*
* @var \Drupal\Core\Config\StorageInterface[]
*/
protected $storages;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* An event dispatcher instance to use for configuration events.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The language object used to override configuration data.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
protected $language;
/**
* Constructs the LanguageConfigFactoryOverride object.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The configuration storage engine.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* An event dispatcher instance to use for configuration events.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
*/
public function __construct(StorageInterface $storage, EventDispatcherInterface $event_dispatcher, TypedConfigManagerInterface $typed_config) {
$this->baseStorage = $storage;
$this->eventDispatcher = $event_dispatcher;
$this->typedConfigManager = $typed_config;
}
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
if ($this->language) {
$storage = $this->getStorage($this->language->getId());
return $storage->readMultiple($names);
}
return array();
}
/**
* {@inheritdoc}
*/
public function getOverride($langcode, $name) {
$storage = $this->getStorage($langcode);
$data = $storage->read($name);
$override = new LanguageConfigOverride(
$name,
$storage,
$this->typedConfigManager,
$this->eventDispatcher
);
if (!empty($data)) {
$override->initWithData($data);
}
return $override;
}
/**
* {@inheritdoc}
*/
public function getStorage($langcode) {
if (!isset($this->storages[$langcode])) {
$this->storages[$langcode] = $this->baseStorage->createCollection($this->createConfigCollectionName($langcode));
}
return $this->storages[$langcode];
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return $this->language ? $this->language->getId() : NULL;
}
/**
* {@inheritdoc}
*/
public function getLanguage() {
return $this->language;
}
/**
* {@inheritdoc}
*/
public function setLanguage(LanguageInterface $language = NULL) {
$this->language = $language;
return $this;
}
/**
* {@inheritdoc}
*/
public function setLanguageFromDefault(LanguageDefault $language_default = NULL) {
$this->language = $language_default ? $language_default->get() : NULL;
return $this;
}
/**
* {@inheritdoc}
*/
public function installLanguageOverrides($langcode) {
/** @var \Drupal\Core\Config\ConfigInstallerInterface $config_installer */
$config_installer = \Drupal::service('config.installer');
$config_installer->installCollectionDefaultConfig($this->createConfigCollectionName($langcode));
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
$langcode = $this->getLangcodeFromCollectionName($collection);
return $this->getOverride($langcode, $name);
}
/**
* {@inheritdoc}
*/
public function addCollections(ConfigCollectionInfo $collection_info) {
foreach (\Drupal::languageManager()->getLanguages() as $language) {
$collection_info->addCollection($this->createConfigCollectionName($language->getId()), $this);
}
}
/**
* {@inheritdoc}
*/
public function onConfigSave(ConfigCrudEvent $event) {
$config = $event->getConfig();
$name = $config->getName();
foreach (\Drupal::languageManager()->getLanguages() as $language) {
$config_translation = $this->getOverride($language->getId(), $name);
if (!$config_translation->isNew()) {
$this->filterOverride($config, $config_translation);
}
}
}
/**
* {@inheritdoc}
*/
public function onConfigRename(ConfigRenameEvent $event) {
$config = $event->getConfig();
$name = $config->getName();
$old_name = $event->getOldName();
foreach (\Drupal::languageManager()->getLanguages() as $language) {
$config_translation = $this->getOverride($language->getId(), $old_name);
if (!$config_translation->isNew()) {
$saved_config = $config_translation->get();
$storage = $this->getStorage($language->getId());
$storage->write($name, $saved_config);
$config_translation->delete();
}
}
}
/**
* {@inheritdoc}
*/
public function onConfigDelete(ConfigCrudEvent $event) {
$config = $event->getConfig();
$name = $config->getName();
foreach (\Drupal::languageManager()->getLanguages() as $language) {
$config_translation = $this->getOverride($language->getId(), $name);
if (!$config_translation->isNew()) {
$config_translation->delete();
}
}
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\language\Config\LanguageConfigFactoryOverrideInterface.
*/
namespace Drupal\language\Config;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageDefault;
/**
* Defines the interface for a configuration factory language override object.
*/
interface LanguageConfigFactoryOverrideInterface extends ConfigFactoryOverrideInterface {
/**
* Gets the language object used to override configuration data.
*
* @return \Drupal\Core\Language\LanguageInterface
* The language object used to override configuration data.
*/
public function getLanguage();
/**
* Sets the language to be used in configuration overrides.
*
* @param \Drupal\Core\Language\LanguageInterface $language
* The language object used to override configuration data.
*
* @return $this
*/
public function setLanguage(LanguageInterface $language = NULL);
/**
* Sets the language to be used in configuration overrides from the default.
*
* @param \Drupal\Core\Language\LanguageDefault $language_default
* The default language.
*
* @return $this
*/
public function setLanguageFromDefault(LanguageDefault $language_default = NULL);
/**
* Get language override for given language and configuration name.
*
* @param string $langcode
* Language code.
* @param string $name
* Configuration name.
*
* @return \Drupal\Core\Config\Config
* Configuration override object.
*/
public function getOverride($langcode, $name);
/**
* Returns the storage instance for a particular langcode.
*
* @param string $langcode
* Language code.
*
* @return \Drupal\Core\Config\StorageInterface
* The storage instance for a particular langcode.
*/
public function getStorage($langcode);
/**
* Installs available language configuration overrides for a given langcode.
*
* @param string $langcode
* Language code.
*/
public function installLanguageOverrides($langcode);
}

View file

@ -0,0 +1,98 @@
<?php
/**
* @file
* Contains \Drupal\language\Config\LanguageConfigOverride.
*/
namespace Drupal\language\Config;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\StorableConfigBase;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Defines language configuration overrides.
*/
class LanguageConfigOverride extends StorableConfigBase {
use LanguageConfigCollectionNameTrait;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Constructs a language override object.
*
* @param string $name
* The name of the configuration object being overridden.
* @param \Drupal\Core\Config\StorageInterface $storage
* A storage controller object to use for reading and writing the
* configuration override.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct($name, StorageInterface $storage, TypedConfigManagerInterface $typed_config, EventDispatcherInterface $event_dispatcher) {
$this->name = $name;
$this->storage = $storage;
$this->typedConfigManager = $typed_config;
$this->eventDispatcher = $event_dispatcher;
}
/**
* {@inheritdoc}
*/
public function save($has_trusted_data = FALSE) {
if (!$has_trusted_data) {
// @todo Use configuration schema to validate.
// https://www.drupal.org/node/2270399
// Perform basic data validation.
foreach ($this->data as $key => $value) {
$this->validateValue($key, $value);
}
}
$this->storage->write($this->name, $this->data);
// Invalidate the cache tags not only when updating, but also when creating,
// because a language config override object uses the same cache tag as the
// default configuration object. Hence creating a language override is like
// an update of configuration, but only for a specific language.
Cache::invalidateTags($this->getCacheTags());
$this->isNew = FALSE;
$this->eventDispatcher->dispatch(LanguageConfigOverrideEvents::SAVE_OVERRIDE, new LanguageConfigOverrideCrudEvent($this));
$this->originalData = $this->data;
return $this;
}
/**
* {@inheritdoc}
*/
public function delete() {
$this->data = array();
$this->storage->delete($this->name);
Cache::invalidateTags($this->getCacheTags());
$this->isNew = TRUE;
$this->eventDispatcher->dispatch(LanguageConfigOverrideEvents::DELETE_OVERRIDE, new LanguageConfigOverrideCrudEvent($this));
$this->originalData = $this->data;
return $this;
}
/**
* Returns the language code of this language override.
*
* @return string
* The language code.
*/
public function getLangcode() {
return $this->getLangcodeFromCollectionName($this->getStorage()->getCollectionName());
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\language\Config\LanguageConfigOverrideCrudEvent.
*/
namespace Drupal\language\Config;
use Symfony\Component\EventDispatcher\Event;
/**
* Provides a language override event for event listeners.
*
* @see \Drupal\Core\Config\ConfigCrudEvent
*/
class LanguageConfigOverrideCrudEvent extends Event {
/**
* Configuration object.
*
* @var \Drupal\language\Config\LanguageConfigOverride
*/
protected $override;
/**
* Constructs a configuration event object.
*
* @param \Drupal\language\Config\LanguageConfigOverride $override
* Configuration object.
*/
public function __construct(LanguageConfigOverride $override) {
$this->override = $override;
}
/**
* Gets configuration object.
*
* @return \Drupal\language\Config\LanguageConfigOverride
* The configuration object that caused the event to fire.
*/
public function getLanguageConfigOverride() {
return $this->override;
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* @file
* Contains \Drupal\language\Config\LanguageConfigOverrideEvents.
*/
namespace Drupal\language\Config;
/**
* Defines events for language configuration overrides.
*
* @see \Drupal\Core\Config\ConfigCrudEvent
*/
final class LanguageConfigOverrideEvents {
/**
* The name of the event fired when saving the configuration override.
*
* This event allows you to perform custom actions whenever a language config
* override is saved. The event listener method receives a
* \Drupal\language\Config\LanguageConfigOverrideCrudEvent instance.
*
* @Event
*
* @see \Drupal\language\Config\LanguageConfigOverrideCrudEvent
* @see \Drupal\language\Config\LanguageConfigOverride::save()
* @see \Drupal\locale\LocaleConfigSubscriber
*/
const SAVE_OVERRIDE = 'language.save_override';
/**
* The name of the event fired when deleting the configuration override.
*
* This event allows you to perform custom actions whenever a language config
* override is deleted. The event listener method receives a
* \Drupal\language\Config\LanguageConfigOverrideCrudEvent instance.
*
* @Event
*
* @see \Drupal\language\Config\LanguageConfigOverrideCrudEvent
* @see \Drupal\language\Config\LanguageConfigOverride::delete()
* @see \Drupal\locale\LocaleConfigSubscriber
*/
const DELETE_OVERRIDE = 'language.delete_override';
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\language\ConfigurableLanguageInterface.
*/
namespace Drupal\language;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
/**
* Provides an interface defining a language entity.
*/
interface ConfigurableLanguageInterface extends ConfigEntityInterface, LanguageInterface {
/**
* Sets the name of the language.
*
* @param string $name
* The human-readable English name of the language.
*
* @return $this
*/
public function setName($name);
/**
* Sets the weight of the language.
*
* @param int $weight
* The weight, used to order languages with larger positive weights sinking
* items toward the bottom of lists.
*
* @return $this
*/
public function setWeight($weight);
}

View file

@ -0,0 +1,484 @@
<?php
/**
* @file
* Contains \Drupal\language\ConfigurableLanguageManager.
*/
namespace Drupal\language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageDefault;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Url;
use Drupal\language\Config\LanguageConfigFactoryOverrideInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Overrides default LanguageManager to provide configured languages.
*/
class ConfigurableLanguageManager extends LanguageManager implements ConfigurableLanguageManagerInterface {
/**
* The configuration storage service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The language configuration override service.
*
* @var \Drupal\language\Config\LanguageConfigFactoryOverrideInterface
*/
protected $configFactoryOverride;
/**
* The request object.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* 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.
*
* @var \Drupal\Core\Language\LanguageInterface[]
*/
protected $negotiatedLanguages;
/**
* An array of language negotiation method IDs keyed by language type.
*
* @var array
*/
protected $negotiatedMethods;
/**
* 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;
/**
* {@inheritdoc}
*/
public static function rebuildServices() {
PhpStorageFactory::get('service_container')->deleteAll();
}
/**
* Constructs a new ConfigurableLanguageManager object.
*
* @param \Drupal\Core\Language\LanguageDefault $default_language
* The default language service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\language\Config\LanguageConfigFactoryOverrideInterface $config_override
* The language configuration override service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack object.
*/
public function __construct(LanguageDefault $default_language, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, LanguageConfigFactoryOverrideInterface $config_override, RequestStack $request_stack) {
$this->defaultLanguage = $default_language;
$this->configFactory = $config_factory;
$this->moduleHandler = $module_handler;
$this->configFactoryOverride = $config_override;
$this->requestStack = $request_stack;
}
/**
* {@inheritdoc}
*/
public function init() {
if (!$this->initialized) {
foreach ($this->getDefinedLanguageTypes() as $type) {
$this->getCurrentLanguage($type);
}
$this->initialized = TRUE;
}
}
/**
* {@inheritdoc}
*/
public function isMultilingual() {
return count($this->getLanguages(LanguageInterface::STATE_CONFIGURABLE)) > 1;
}
/**
* {@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;
}
/**
* {@inheritdoc}
*/
public function saveLanguageTypesConfiguration(array $values) {
$config = $this->configFactory->getEditable('language.types');
if (isset($values['configurable'])) {
$config->set('configurable', $values['configurable']);
}
if (isset($values['all'])) {
$config->set('all', $values['all']);
}
$config->save();
}
/**
* {@inheritdoc}
*/
public function getCurrentLanguage($type = LanguageInterface::TYPE_INTERFACE) {
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;
$negotiation = $this->negotiator->initializeType($type);
$this->negotiatedLanguages[$type] = reset($negotiation);
$this->negotiatedMethods[$type] = key($negotiation);
$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.
elseif ($type == LanguageInterface::TYPE_INTERFACE) {
return new Language(array('id' => LanguageInterface::LANGCODE_SYSTEM));
}
}
}
return $this->negotiatedLanguages[$type];
}
/**
* {@inheritdoc}
*/
public function reset($type = NULL) {
if (!isset($type)) {
$this->initialized = FALSE;
$this->negotiatedLanguages = array();
$this->negotiatedMethods = array();
$this->languageTypes = NULL;
$this->languageTypesInfo = NULL;
$this->languages = array();
if ($this->negotiator) {
$this->negotiator->reset();
}
}
elseif (isset($this->negotiatedLanguages[$type])) {
unset($this->negotiatedLanguages[$type]);
unset($this->negotiatedMethods[$type]);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getNegotiator() {
return $this->negotiator;
}
/**
* {@inheritdoc}
*/
public function setNegotiator(LanguageNegotiatorInterface $negotiator) {
$this->negotiator = $negotiator;
$this->initialized = FALSE;
$this->negotiatedLanguages = array();
}
/**
* {@inheritdoc}
*/
public function getLanguages($flags = LanguageInterface::STATE_CONFIGURABLE) {
// 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();
}
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.');
foreach ($this->configFactory->loadMultiple($config_ids) as $config) {
$data = $config->get();
$data['name'] = $data['label'];
$languages[$data['id']] = new Language($data);
}
Language::sort($languages);
// Filter the full list of languages based on the value of $flags.
$this->languages[$static_cache_id][$flags] = $this->filterLanguages($languages, $flags);
}
return $this->languages[$static_cache_id][$flags];
}
/**
* {@inheritdoc}
*/
public function getNativeLanguages() {
$languages = $this->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
$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;
}
/**
* {@inheritdoc}
*/
public function updateLockedLanguageWeights() {
// Get the weight of the last configurable language.
$configurable_languages = $this->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
$max_weight = end($configurable_languages)->getWeight();
$locked_languages = $this->getLanguages(LanguageInterface::STATE_LOCKED);
// 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();
}
}
}
/**
* {@inheritdoc}
*/
public function getFallbackCandidates(array $context = array()) {
if ($this->isMultilingual()) {
$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
// LanguageInterface::LANGCODE_NOT_SPECIFIED at the end. Interface
// translation fallback should only be based on explicit configuration
// gathered via the alter hooks below.
$candidates = array_keys($this->getLanguages());
$candidates[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
$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;
}
}
// 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 {
$candidates = parent::getFallbackCandidates($context);
}
return $candidates;
}
/**
* {@inheritdoc}
*/
public function getLanguageSwitchLinks($type, Url $url) {
$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')) {
$result = $this->negotiator->getNegotiationMethodInstance($method_id)->getLanguageSwitchLinks($this->requestStack->getCurrentRequest(), $type, $url);
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;
}
/**
* {@inheritdoc}
*/
public function setConfigOverrideLanguage(LanguageInterface $language = NULL) {
$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);
}
/**
* {@inheritdoc}
*/
public function getLanguageConfigOverrideStorage($langcode) {
return $this->configFactoryOverride->getStorage($langcode);
}
/**
* {@inheritdoc}
*/
public function getStandardLanguageListWithoutConfigured() {
$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;
}
/**
* {@inheritdoc}
*/
public function getNegotiatedLanguageMethod($type = LanguageInterface::TYPE_INTERFACE) {
if (isset($this->negotiatedLanguages[$type]) && isset($this->negotiatedMethods[$type])) {
return $this->negotiatedMethods[$type];
}
}
}

View file

@ -0,0 +1,112 @@
<?php
/**
* @file
* Contains \Drupal\language\ConfigurableLanguageManagerInterface.
*/
namespace Drupal\language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Common interface for language negotiation services.
*/
interface ConfigurableLanguageManagerInterface extends LanguageManagerInterface {
/**
* Rebuild the container to register services needed on multilingual sites.
*/
public static function rebuildServices();
/**
* Returns the language negotiator.
*
* @return \Drupal\language\LanguageNegotiatorInterface
* The language negotiator.
*/
public function getNegotiator();
/**
* Injects the language negotiator.
*
* @param \Drupal\language\LanguageNegotiatorInterface $negotiator
* The language negotiator.
*/
public function setNegotiator(LanguageNegotiatorInterface $negotiator);
/**
* Returns all the defined language types including fixed ones.
*
* A language type maybe configurable or fixed. A fixed language type is a
* type whose language negotiation methods are module-defined and not altered
* through the user interface.
*
* @return array
* An array of language type machine names.
*/
public function getDefinedLanguageTypes();
/**
* Stores language types configuration.
*
* @param array
* An indexed array with the following keys_
* - configurable: an array of configurable language type names.
* - all: an array of all the defined language type names.
*/
public function saveLanguageTypesConfiguration(array $config);
/**
* Updates locked system language weights.
*/
public function updateLockedLanguageWeights();
/**
* Gets a language config override object.
*
* @param string $langcode
* The language code for the override.
* @param string $name
* The language configuration object name.
*
* @return \Drupal\language\Config\LanguageConfigOverride
* The language config override object.
*/
public function getLanguageConfigOverride($langcode, $name);
/**
* Gets a language configuration override storage object.
*
* @param string $langcode
* The language code for the override.
*
* @return \Drupal\Core\Config\StorageInterface $storage
* A storage object to use for reading and writing the
* configuration override.
*/
public function getLanguageConfigOverrideStorage($langcode);
/**
* Returns the standard language list excluding already configured languages.
*
* @return array
* A list of standard language names keyed by langcode.
*/
public function getStandardLanguageListWithoutConfigured();
/**
* Gets the negotiated language method ID.
*
* @param string $type
* (optional) The language type; e.g., the interface or the content
* language.
*
* @return string
* The negotiated language method ID.
*/
public function getNegotiatedLanguageMethod($type = LanguageInterface::TYPE_INTERFACE);
}

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* Contains \Drupal\language\ContentLanguageSettingsException.
*/
namespace Drupal\language;
/**
* Exception thrown by ContentLanguageSettings when target bundle is not set.
*/
class ContentLanguageSettingsException extends \RuntimeException {}

View file

@ -0,0 +1,84 @@
<?php
/**
* @file
* Contains \Drupal\language\ContentLanguageSettingsInterface.
*/
namespace Drupal\language;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining language settings for content entities.
*/
interface ContentLanguageSettingsInterface extends ConfigEntityInterface {
/**
* Gets the entity type ID this config applies to.
*
* @return string
*/
public function getTargetEntityTypeId();
/**
* Gets the bundle this config applies to.
*
* @return string
*/
public function getTargetBundle();
/**
* Sets the bundle this config applies to.
*
* @param string $target_bundle
* The bundle.
*
* @return $this
*/
public function setTargetBundle($target_bundle);
/**
* Sets the default language code.
*
* @param string $default_langcode
* The default language code.
*
* @return $this;
*/
public function setDefaultLangcode($default_langcode);
/**
* Gets the default language code.
*
* @return string
*/
public function getDefaultLangcode();
/**
* Sets if the language must be alterable or not.
*
* @param bool $language_alterable
* Flag indicating if the language must be alterable.
*
* @return $this
*/
public function setLanguageAlterable($language_alterable);
/**
* Checks if the language is alterable or not.
*
* @return bool
*/
public function isLanguageAlterable();
/**
* Checks if this config object contains the default values in every property.
*
* @return bool
* True if all the properties contain the default values. False otherwise.
*/
public function isDefaultConfiguration();
}

View file

@ -0,0 +1,54 @@
<?php
/**
* @file
* Contains \Drupal\language\DefaultLanguageItem.
*/
namespace Drupal\language;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\LanguageItem;
use Drupal\Core\Language\Language;
/**
* Alternative plugin implementation of the 'language' field type.
*
* Replaces the Core 'language' entity field type implementation, changes the
* default values used.
*
* Required settings are:
* - target_type: The entity type to reference.
*
* @see language_field_info_alter().
*/
class DefaultLanguageItem extends LanguageItem {
/**
* {@inheritdoc}
*/
public function applyDefaultValue($notify = TRUE) {
// Default to LANGCODE_NOT_SPECIFIED.
$langcode = Language::LANGCODE_NOT_SPECIFIED;
if ($entity = $this->getEntity()) {
$langcode = $this->getDefaultLangcode($entity);
}
// Always notify otherwise default langcode will not be set correctly.
$this->setValue(array('value' => $langcode), TRUE);
return $this;
}
/**
* Provides default language code of given entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity whose language code to be loaded.
*
* @return string
* A string language code.
*/
public function getDefaultLangcode(EntityInterface $entity) {
return language_get_default_langcode($entity->getEntityTypeId(), $entity->bundle());
}
}

View file

@ -0,0 +1,121 @@
<?php
/**
* @file
* Contains \Drupal\language\Element\LanguageConfiguration.
*/
namespace Drupal\language\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\language\Entity\ContentLanguageSettings;
/**
* Provides language element configuration.
*
* @FormElement("language_configuration")
*/
class LanguageConfiguration extends FormElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#input' => TRUE,
'#tree' => TRUE,
'#process' => array(
array($class, 'processLanguageConfiguration'),
),
);
}
/**
* Process handler for the language_configuration form element.
*/
public static function processLanguageConfiguration(&$element, FormStateInterface $form_state, &$form) {
$options = isset($element['#options']) ? $element['#options'] : array();
// Avoid validation failure since we are moving the '#options' key in the
// nested 'language' select element.
unset($element['#options']);
/** @var ContentLanguageSettings $default_config */
$default_config = $element['#default_value'];
$element['langcode'] = array(
'#type' => 'select',
'#title' => t('Default language'),
'#options' => $options + static::getDefaultOptions(),
'#description' => t('Explanation of the language options is found on the <a href="@languages_list_page">languages list page</a>.', array('@languages_list_page' => \Drupal::url('entity.configurable_language.collection'))),
'#default_value' => ($default_config != NULL) ? $default_config->getDefaultLangcode() : LanguageInterface::LANGCODE_SITE_DEFAULT,
);
$element['language_alterable'] = array(
'#type' => 'checkbox',
'#title' => t('Show language selector on create and edit pages'),
'#default_value' => ($default_config != NULL) ? $default_config->isLanguageAlterable() : FALSE,
);
// Add the entity type and bundle information to the form if they are set.
// They will be used, in the submit handler, to generate the names of the
// configuration entities that will store the settings and are a way to uniquely
// identify the entity.
$language = $form_state->get('language') ?: [];
$language += array(
$element['#name'] => array(
'entity_type' => $element['#entity_information']['entity_type'],
'bundle' => $element['#entity_information']['bundle'],
),
);
$form_state->set('language', $language);
// Do not add the submit callback for the language content settings page,
// which is handled separately.
if ($form['#form_id'] != 'language_content_settings_form') {
// Determine where to attach the language_configuration element submit
// handler.
// @todo Form API: Allow form widgets/sections to declare #submit
// handlers.
$submit_name = isset($form['actions']['save_continue']) ? 'save_continue' : 'submit';
if (isset($form['actions'][$submit_name]['#submit']) && array_search('language_configuration_element_submit', $form['actions'][$submit_name]['#submit']) === FALSE) {
$form['actions'][$submit_name]['#submit'][] = 'language_configuration_element_submit';
}
elseif (array_search('language_configuration_element_submit', $form['#submit']) === FALSE) {
$form['#submit'][] = 'language_configuration_element_submit';
}
}
return $element;
}
/**
* Returns the default options for the language configuration form element.
*
* @return array
* An array containing the default options.
*/
protected static function getDefaultOptions() {
$language_options = array(
LanguageInterface::LANGCODE_SITE_DEFAULT => t("Site's default language (!language)", array('!language' => static::languageManager()->getDefaultLanguage()->getName())),
'current_interface' => t('Interface text language selected for page'),
'authors_default' => t("Author's preferred language"),
);
$languages = static::languageManager()->getLanguages(LanguageInterface::STATE_ALL);
foreach ($languages as $langcode => $language) {
$language_options[$langcode] = $language->isLocked() ? t('- @name -', array('@name' => $language->getName())) : $language->getName();
}
return $language_options;
}
/**
* Wraps the language manager.
*
* @return \Drupal\Core\Language\LanguageManagerInterface
*/
protected static function languageManager() {
return \Drupal::languageManager();
}
}

View file

@ -0,0 +1,269 @@
<?php
/**
* @file
* Contains \Drupal\language\Entity\ConfigurableLanguage.
*/
namespace Drupal\language\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\language\ConfigurableLanguageManager;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\Exception\DeleteDefaultLanguageException;
use Drupal\language\ConfigurableLanguageInterface;
/**
* Defines the ConfigurableLanguage entity.
*
* @ConfigEntityType(
* id = "configurable_language",
* label = @Translation("Language"),
* handlers = {
* "list_builder" = "Drupal\language\LanguageListBuilder",
* "access" = "Drupal\language\LanguageAccessControlHandler",
* "form" = {
* "add" = "Drupal\language\Form\LanguageAddForm",
* "edit" = "Drupal\language\Form\LanguageEditForm",
* "delete" = "Drupal\language\Form\LanguageDeleteForm"
* }
* },
* admin_permission = "administer languages",
* config_prefix = "entity",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "weight" = "weight"
* },
* links = {
* "delete-form" = "/admin/config/regional/language/delete/{configurable_language}",
* "edit-form" = "/admin/config/regional/language/edit/{configurable_language}",
* "collection" = "/admin/config/regional/language",
* }
* )
*/
class ConfigurableLanguage extends ConfigEntityBase implements ConfigurableLanguageInterface {
/**
* The language ID (machine name).
*
* @var string
*/
protected $id;
/**
* The human-readable label for the language.
*
* @var string
*/
protected $label;
/**
* The direction of language, either DIRECTION_LTR or DIRECTION_RTL.
*
* @var integer
*/
protected $direction = self::DIRECTION_LTR;
/**
* The weight of the language, used in lists of languages.
*
* @var integer
*/
protected $weight = 0;
/**
* Locked languages cannot be edited.
*
* @var bool
*/
protected $locked = FALSE;
/**
* Used during saving to detect when the site becomes multilingual.
*
* This property is not saved to the language entity, but is needed for
* detecting when to rebuild the services.
*
* @see \Drupal\language\Entity\ConfigurableLanguage::preSave()
* @see \Drupal\language\Entity\ConfigurableLanguage::postSave()
*
* @var bool
*/
protected $preSaveMultilingual;
/**
* {@inheritdoc}
*/
public function isDefault() {
return static::getDefaultLangcode() == $this->id();
}
/**
* {@inheritdoc}
*/
public function isLocked() {
return (bool) $this->locked;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
// Store whether or not the site is already multilingual so that we can
// rebuild services if necessary during
// \Drupal\language\Entity\ConfigurableLanguage::postSave().
$this->preSaveMultilingual = \Drupal::languageManager()->isMultilingual();
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
$language_manager = \Drupal::languageManager();
$language_manager->reset();
if (!$this->isLocked() && $language_manager instanceof ConfigurableLanguageManagerInterface && !$this->isSyncing()) {
$language_manager->updateLockedLanguageWeights();
}
// Update URL Prefixes for all languages after the
// LanguageManagerInterface::getLanguages() cache is flushed.
language_negotiation_url_prefixes_update();
// If after adding this language the site will become multilingual, we need
// to rebuild language services.
if (!$this->preSaveMultilingual && !$update && $language_manager instanceof ConfigurableLanguageManagerInterface) {
$language_manager::rebuildServices();
}
if (!$update) {
// Install any available language configuration overrides for the language.
\Drupal::service('language.config_factory_override')->installLanguageOverrides($this->id());
}
}
/**
* {@inheritdoc}
*
* @throws \DeleteDefaultLanguageException
* Exception thrown if we're trying to delete the default language entity.
* This is not allowed as a site must have a default language.
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
$default_langcode = static::getDefaultLangcode();
foreach ($entities as $entity) {
if ($entity->id() == $default_langcode && !$entity->isUninstalling()) {
throw new DeleteDefaultLanguageException('Can not delete the default language');
}
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
$language_manager = \Drupal::languageManager();
$language_manager->reset();
$entity = reset($entities);
if ($language_manager instanceof ConfigurableLanguageManagerInterface && !$entity->isUninstalling() && !$entity->isSyncing()) {
$language_manager->updateLockedLanguageWeights();
}
// If after deleting this language the site will become monolingual, we need
// to rebuild language services.
if (!\Drupal::languageManager()->isMultilingual()) {
ConfigurableLanguageManager::rebuildServices();
}
}
/**
* Gets the default langcode.
*
* @return string
* The current default langcode.
*/
protected static function getDefaultLangcode() {
$language = \Drupal::service('language.default')->get();
return $language->getId();
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->label();
}
/**
* {@inheritdoc}
*/
public function setName($name) {
$this->label = $name;
return $this;
}
/**
* {@inheritdoc}
*/
public function getId() {
return $this->id();
}
/**
* {@inheritdoc}
*/
public function getDirection() {
return $this->direction;
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->weight;
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
$this->weight = $weight;
return $this;
}
/**
* Creates a configurable language object from a langcode.
*
* @param string $langcode
* The language code to use to create the object.
*
* @return $this
*
* @see \Drupal\Core\Language\LanguageManager::getStandardLanguageList()
*/
public static function createFromLangcode($langcode) {
$standard_languages = LanguageManager::getStandardLanguageList();
if (!isset($standard_languages[$langcode])) {
// Drupal does not know about this language, so we set its values with the
// best guess. The user will be able to edit afterwards.
return static::create(array(
'id' => $langcode,
'label' => $langcode,
));
}
else {
// A known predefined language, details will be filled in properly.
return static::create(array(
'id' => $langcode,
'label' => $standard_languages[$langcode][0],
'direction' => isset($standard_languages[$langcode][2]) ? $standard_languages[$langcode][2] : static::DIRECTION_LTR,
));
}
}
}

View file

@ -0,0 +1,210 @@
<?php
/**
* @file
* Contains \Drupal\language\Entity\ContentLanguageSettings.
*/
namespace Drupal\language\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\ContentLanguageSettingsException;
use Drupal\language\ContentLanguageSettingsInterface;
/**
* Defines the ContentLanguageSettings entity.
*
* @ConfigEntityType(
* id = "language_content_settings",
* label = @Translation("Content Language Settings"),
* admin_permission = "administer languages",
* config_prefix = "content_settings",
* entity_keys = {
* "id" = "id"
* },
* )
*/
class ContentLanguageSettings extends ConfigEntityBase implements ContentLanguageSettingsInterface {
/**
* The id. Combination of $target_entity_type_id.$target_bundle.
*
* @var string
*/
protected $id;
/**
* The entity type ID (machine name).
*
* @var string
*/
protected $target_entity_type_id;
/**
* The bundle (machine name).
*
* @var string
*/
protected $target_bundle;
/**
* The default language code.
*
* @var string
*/
protected $default_langcode = LanguageInterface::LANGCODE_SITE_DEFAULT;
/**
* Indicates if the language is alterable or not.
*
* @var bool
*/
protected $language_alterable = FALSE;
/**
* Constructs a ContentLanguageSettings object.
*
* In most cases, Field entities are created via
* entity_create('field_config', $values), where $values is the same
* parameter as in this constructor.
*
* @param array $values
* An array of the referring entity bundle with:
* - target_entity_type_id: The entity type.
* - target_bundle: The bundle.
* Other array elements will be used to set the corresponding properties on
* the class; see the class property documentation for details.
*
* @see entity_create()
*/
public function __construct(array $values, $entity_type = 'language_content_settings') {
if (empty($values['target_entity_type_id'])) {
throw new ContentLanguageSettingsException('Attempt to create content language settings without a target_entity_type_id.');
}
if (empty($values['target_bundle'])) {
throw new ContentLanguageSettingsException('Attempt to create content language settings without a target_bundle.');
}
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->target_entity_type_id . '.' . $this->target_bundle;
}
/**
* {@inheritdoc}
*/
public function getTargetEntityTypeId() {
return $this->target_entity_type_id;
}
/**
* {@inheritdoc}
*/
public function getTargetBundle() {
return $this->target_bundle;
}
/**
* {@inheritdoc}
*/
public function setTargetBundle($target_bundle) {
$this->target_bundle = $target_bundle;
return $this;
}
/**
* {@inheritdoc}
*/
public function setDefaultLangcode($default_langcode) {
$this->default_langcode = $default_langcode;
return $this;
}
/**
* {@inheritdoc}
*/
public function getDefaultLangcode() {
return $this->default_langcode;
}
/**
* {@inheritdoc}
*/
public function setLanguageAlterable($language_alterable) {
$this->language_alterable = $language_alterable;
return $this;
}
/**
* {@inheritdoc}
*/
public function isLanguageAlterable() {
return $this->language_alterable;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
$this->id = $this->id();
parent::preSave($storage);
}
/**
* {@inheritdoc}
*/
public function isDefaultConfiguration() {
return (!$this->language_alterable && $this->default_langcode == LanguageInterface::LANGCODE_SITE_DEFAULT);
}
/**
* Loads a content language config entity based on the entity type and bundle.
*
* @param string $entity_type_id
* ID of the entity type.
* @param string $bundle
* Bundle name.
*
* @return $this
* The content language config entity if one exists. Otherwise, returns
* default values.
*/
public static function loadByEntityTypeBundle($entity_type_id, $bundle) {
if ($entity_type_id == NULL || $bundle == NULL) {
return NULL;
}
$config = \Drupal::entityManager()->getStorage('language_content_settings')->load($entity_type_id . '.' . $bundle);
if ($config == NULL) {
$config = ContentLanguageSettings::create(['target_entity_type_id' => $entity_type_id, 'target_bundle' => $bundle]);
}
return $config;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
$bundle_entity_type_id = $this->entityManager()->getDefinition($this->target_entity_type_id)->getBundleEntityType();
if ($bundle_entity_type_id != 'bundle') {
// If the target entity type uses entities to manage its bundles then
// depend on the bundle entity.
if (!$bundle_entity = $this->entityManager()->getStorage($bundle_entity_type_id)->load($this->target_bundle)) {
throw new \LogicException(SafeMarkup::format('Missing bundle entity, entity type %type, entity id %bundle.', array('%type' => $bundle_entity_type_id, '%bundle' => $this->target_bundle)));
}
$this->addDependency('config', $bundle_entity->getConfigDependencyName());
}
return $this->dependencies;
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\language\EventSubscriber\ConfigSubscriber.
*/
namespace Drupal\language\EventSubscriber;
use Drupal\Core\Language\LanguageDefault;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Deletes the container if default language has changed.
*/
class ConfigSubscriber implements EventSubscriberInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The default language.
*
* @var \Drupal\Core\Language\LanguageDefault
*/
protected $languageDefault;
/**
* Constructs a new class object.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Language\LanguageDefault $language_default
* The default language.
*/
public function __construct(LanguageManagerInterface $language_manager, LanguageDefault $language_default) {
$this->languageManager = $language_manager;
$this->languageDefault = $language_default;
}
/**
* Causes the container to be rebuilt on the next request.
*
* @param ConfigCrudEvent $event
* The configuration event.
*/
public function onConfigSave(ConfigCrudEvent $event) {
$saved_config = $event->getConfig();
if ($saved_config->getName() == 'system.site' && $event->isChanged('default_langcode')) {
$language = $this->languageManager->getLanguage($saved_config->get('default_langcode'));
// During an import the language might not exist yet.
if ($language) {
$this->languageDefault->set($language);
$this->languageManager->reset();
language_negotiation_url_prefixes_update();
}
// Trigger a container rebuild on the next request by deleting compiled
// from PHP storage.
PhpStorageFactory::get('service_container')->deleteAll();
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[ConfigEvents::SAVE][] = array('onConfigSave', 0);
return $events;
}
}

View file

@ -0,0 +1,105 @@
<?php
/**
* @file
* Contains \Drupal\language\EventSubscriber\LanguageRequestSubscriber.
*/
namespace Drupal\language\EventSubscriber;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\LanguageNegotiatorInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Sets the $request property on the language manager.
*/
class LanguageRequestSubscriber implements EventSubscriberInterface {
/**
* The language manager service.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $negotiator;
/**
* The translation service.
*
* @var \Drupal\Core\StringTranslation\Translator\TranslatorInterface;
*/
protected $translation;
/**
* The current active user.
*
* @return \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a LanguageRequestSubscriber object.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager service.
* @param \Drupal\language\LanguageNegotiatorInterface
* The language negotiator.
* @param \Drupal\Core\StringTranslation\Translator\TranslatorInterface $translation;
* The translation service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
*/
public function __construct(ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, TranslatorInterface $translation, AccountInterface $current_user) {
$this->languageManager = $language_manager;
$this->negotiator = $negotiator;
$this->translation = $translation;
$this->currentUser = $current_user;
}
/**
* Sets the request on the language manager.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestLanguage(GetResponseEvent $event) {
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
$request = $event->getRequest();
$this->negotiator->setCurrentUser($this->currentUser);
$this->negotiator->reset();
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
$this->languageManager->setNegotiator($this->negotiator);
$this->languageManager->setConfigOverrideLanguage($this->languageManager->getCurrentLanguage());
}
// After the language manager has initialized, set the default langcode
// for the string translations.
$langcode = $this->languageManager->getCurrentLanguage()->getId();
$this->translation->setDefaultLangcode($langcode);
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestLanguage', 255);
return $events;
}
}

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* Contains \Drupal\language\Exception\DeleteDefaultLanguageException.
*/
namespace Drupal\language\Exception;
/**
* Exception thrown when deleting the default language.
*/
class DeleteDefaultLanguageException extends LanguageException {}

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* Contains \Drupal\language\Exception\LanguageException.
*/
namespace Drupal\language\Exception;
/**
* A base exception thrown in any language system operations.
*/
class LanguageException extends \RuntimeException {}

View file

@ -0,0 +1,162 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\ContentLanguageSettingsForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure the content language settings for this site.
*/
class ContentLanguageSettingsForm extends FormBase {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a ContentLanguageSettingsForm object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_content_settings_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$entity_types = $this->entityManager->getDefinitions();
$labels = array();
$default = array();
$bundles = $this->entityManager->getAllBundleInfo();
$language_configuration = array();
foreach ($entity_types as $entity_type_id => $entity_type) {
if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode')) {
continue;
}
$labels[$entity_type_id] = $entity_type->getLabel() ?: $entity_type_id;
$default[$entity_type_id] = FALSE;
// Check whether we have any custom setting.
foreach ($bundles[$entity_type_id] as $bundle => $bundle_info) {
$config = ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle);
if (!$config->isDefaultConfiguration()) {
$default[$entity_type_id] = $entity_type_id;
}
$language_configuration[$entity_type_id][$bundle] = $config;
}
}
asort($labels);
$form = array(
'#labels' => $labels,
'#attached' => array(
'library' => array(
'language/drupal.language.admin',
),
),
'#attributes' => array(
'class' => 'language-content-settings-form',
),
);
$form['entity_types'] = array(
'#title' => $this->t('Custom language settings'),
'#type' => 'checkboxes',
'#options' => $labels,
'#default_value' => $default,
);
$form['settings'] = array('#tree' => TRUE);
foreach ($labels as $entity_type_id => $label) {
$entity_type = $entity_types[$entity_type_id];
$form['settings'][$entity_type_id] = array(
'#title' => $label,
'#type' => 'container',
'#entity_type' => $entity_type_id,
'#theme' => 'language_content_settings_table',
'#bundle_label' => $entity_type->getBundleLabel() ?: $label,
'#states' => array(
'visible' => array(
':input[name="entity_types[' . $entity_type_id . ']"]' => array('checked' => TRUE),
),
),
);
foreach ($bundles[$entity_type_id] as $bundle => $bundle_info) {
$form['settings'][$entity_type_id][$bundle]['settings'] = array(
'#type' => 'item',
'#label' => $bundle_info['label'],
'language' => array(
'#type' => 'language_configuration',
'#entity_information' => array(
'entity_type' => $entity_type_id,
'bundle' => $bundle,
),
'#default_value' => $language_configuration[$entity_type_id][$bundle],
),
);
}
}
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save configuration'),
'#button_type' => 'primary',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
foreach ($form_state->getValue('settings') as $entity_type => $entity_settings) {
foreach ($entity_settings as $bundle => $bundle_settings) {
$config = ContentLanguageSettings::loadByEntityTypeBundle($entity_type, $bundle);
$config->setDefaultLangcode($bundle_settings['settings']['language']['langcode'])
->setLanguageAlterable($bundle_settings['settings']['language']['language_alterable'])
->save();
}
}
drupal_set_message($this->t('Settings successfully updated.'));
}
}

View file

@ -0,0 +1,170 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\LanguageAddForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Form\LanguageFormBase;
use Drupal\Core\Language\Language;
/**
* Controller for language addition forms.
*/
class LanguageAddForm extends LanguageFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
// @todo Remove in favour of base method.
return 'language_admin_add_form';
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form['#title'] = $this->t('Add language');
$predefined_languages = $this->languageManager->getStandardLanguageListWithoutConfigured();
$predefined_languages['custom'] = $this->t('Custom language...');
$predefined_default = $form_state->getValue('predefined_langcode', key($predefined_languages));
$form['predefined_langcode'] = array(
'#type' => 'select',
'#title' => $this->t('Language name'),
'#default_value' => $predefined_default,
'#options' => $predefined_languages,
);
$form['predefined_submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Add language'),
'#limit_validation_errors' => array(array('predefined_langcode'), array('predefined_submit')),
'#states' => array(
'invisible' => array(
'select#edit-predefined-langcode' => array('value' => 'custom'),
),
),
'#validate' => array('::validatePredefined'),
'#submit' => array('::submitForm', '::save'),
'#button_type' => 'primary',
);
$custom_language_states_conditions = array(
'select#edit-predefined-langcode' => array('value' => 'custom'),
);
$form['custom_language'] = array(
'#type' => 'container',
'#states' => array(
'visible' => $custom_language_states_conditions,
),
);
$this->commonForm($form['custom_language']);
$form['custom_language']['langcode']['#states'] = array(
'required' => $custom_language_states_conditions,
);
$form['custom_language']['label']['#states'] = array(
'required' => $custom_language_states_conditions,
);
$form['custom_language']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Add custom language'),
'#validate' => array('::validateCustom'),
'#submit' => array('::submitForm', '::save'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
parent::save($form, $form_state);
$t_args = array('%language' => $this->entity->label(), '%langcode' => $this->entity->id());
$this->logger('language')->notice('The %language (%langcode) language has been created.', $t_args);
drupal_set_message($this->t('The language %language has been created and can now be used.', $t_args));
if ($this->moduleHandler->moduleExists('block')) {
// Tell the user they have the option to add a language switcher block
// to their theme so they can switch between the languages.
drupal_set_message($this->t('Use one of the language switcher blocks to allow site visitors to switch between languages. You can enable these blocks on the <a href="@block-admin">block administration page</a>.', array('@block-admin' => $this->url('block.admin_display'))));
}
$form_state->setRedirectUrl($this->entity->urlInfo('collection'));
}
/**
* {@inheritdoc}
*/
public function actions(array $form, FormStateInterface $form_state) {
// No actions needed.
return array();
}
/**
* Validates the language addition form on custom language button.
*/
public function validateCustom(array $form, FormStateInterface $form_state) {
if ($form_state->getValue('predefined_langcode') == 'custom') {
$langcode = $form_state->getValue('langcode');
// Reuse the editing form validation routine if we add a custom language.
$this->validateCommon($form['custom_language'], $form_state);
if ($language = $this->languageManager->getLanguage($langcode)) {
$form_state->setErrorByName('langcode', $this->t('The language %language (%langcode) already exists.', array('%language' => $language->getName(), '%langcode' => $langcode)));
}
}
else {
$form_state->setErrorByName('predefined_langcode', $this->t('Use the <em>Add language</em> button to save a predefined language.'));
}
}
/**
* Element specific validator for the Add language button.
*/
public function validatePredefined($form, FormStateInterface $form_state) {
$langcode = $form_state->getValue('predefined_langcode');
if ($langcode == 'custom') {
$form_state->setErrorByName('predefined_langcode', $this->t('Fill in the language details and save the language with <em>Add custom language</em>.'));
}
else {
if ($language = $this->languageManager->getLanguage($langcode)) {
$form_state->setErrorByName('predefined_langcode', $this->t('The language %language (%langcode) already exists.', array('%language' => $language->getName(), '%langcode' => $langcode)));
}
}
}
/**
* {@inheritdoc}
*/
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
$langcode = $form_state->getValue('predefined_langcode');
if ($langcode == 'custom') {
$langcode = $form_state->getValue('langcode');
$label = $form_state->getValue('label');
$direction = $form_state->getValue('direction');
}
else {
$standard_languages = LanguageManager::getStandardLanguageList();
$label = $standard_languages[$langcode][0];
$direction = isset($standard_languages[$langcode][2]) ? $standard_languages[$langcode][2] : ConfigurableLanguage::DIRECTION_LTR;
}
$entity->set('id', $langcode);
$entity->set('label', $label);
$entity->set('direction', $direction);
// There is no weight on the edit form. Fetch all configurable languages
// ordered by weight and set the new language to be placed after them.
$languages = \Drupal::languageManager()->getLanguages(ConfigurableLanguage::STATE_CONFIGURABLE);
$last_language = end($languages);
$entity->setWeight($last_language->getWeight() + 1);
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\LanguageDeleteForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines a confirmation form for deleting a language entity.
*/
class LanguageDeleteForm extends EntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('Deleting a language will remove all interface translations associated with it, and content in this language will be set to be language neutral. This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_delete_form';
}
/**
* {@inheritdoc}
*/
protected function getDeletionMessage() {
return $this->t('The %language (%langcode) language has been removed.', array('%language' => $this->entity->label(), '%langcode' => $this->entity->id()));
}
/**
* {@inheritdoc}
*/
public function logDeletionMessage() {
$this->logger('language')->notice('The %language (%langcode) language has been removed.', array('%language' => $this->entity->label(), '%langcode' => $this->entity->id()));
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\LanguageEditForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\language\Form\LanguageFormBase;
/**
* Controller for language edit forms.
*/
class LanguageEditForm extends LanguageFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
// @todo Remove in favour of base method.
return 'language_admin_edit_form';
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$this->commonForm($form);
return parent::form($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function actions(array $form, FormStateInterface $form_state) {
$actions['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save language'),
'#validate' => array('::validateCommon'),
'#submit' => array('::submitForm', '::save'),
);
return $actions;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
parent::save($form, $form_state);
$form_state->setRedirectUrl($this->entity->urlInfo('collection'));
$this->logger('language')->notice('The %language (%langcode) language has been updated.', array('%language' => $this->entity->label(), '%langcode' => $this->entity->id()));
}
}

View file

@ -0,0 +1,114 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\LanguageFormBase.
*/
namespace Drupal\language\Form;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base form for language add and edit forms.
*/
abstract class LanguageFormBase extends EntityForm {
/**
* The configurable language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a ContentEntityForm object.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The configurable language manager.
*/
public function __construct(ConfigurableLanguageManagerInterface $language_manager) {
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('language_manager')
);
}
/**
* Common elements of the language addition and editing form.
*/
public function commonForm(array &$form) {
/* @var $language \Drupal\language\ConfigurableLanguageInterface */
$language = $this->entity;
if ($language->getId()) {
$form['langcode_view'] = array(
'#type' => 'item',
'#title' => $this->t('Language code'),
'#markup' => $language->id()
);
$form['langcode'] = array(
'#type' => 'value',
'#value' => $language->id()
);
}
else {
$form['langcode'] = array(
'#type' => 'textfield',
'#title' => $this->t('Language code'),
'#maxlength' => 12,
'#required' => TRUE,
'#default_value' => '',
'#disabled' => FALSE,
'#description' => $this->t('Use language codes as <a href="@w3ctags">defined by the W3C</a> for interoperability. <em>Examples: "en", "en-gb" and "zh-hant".</em>', array('@w3ctags' => 'http://www.w3.org/International/articles/language-tags/')),
);
}
$form['label'] = array(
'#type' => 'textfield',
'#title' => $this->t('Language name'),
'#maxlength' => 64,
'#default_value' => $language->label(),
'#required' => TRUE,
);
$form['direction'] = array(
'#type' => 'radios',
'#title' => $this->t('Direction'),
'#required' => TRUE,
'#description' => $this->t('Direction that text in this language is presented.'),
'#default_value' => $language->getDirection(),
'#options' => array(
LanguageInterface::DIRECTION_LTR => $this->t('Left to right'),
LanguageInterface::DIRECTION_RTL => $this->t('Right to left'),
),
);
return $form;
}
/**
* Validates the language editing element.
*/
public function validateCommon(array $form, FormStateInterface $form_state) {
// Ensure sane field values for langcode and name.
if (!isset($form['langcode_view']) && !preg_match('@^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$@', $form_state->getValue('langcode'))) {
$form_state->setErrorByName('langcode', $this->t('%field must be a valid language tag as <a href="@url">defined by the W3C</a>.', array(
'%field' => $form['langcode']['#title'],
'@url' => 'http://www.w3.org/International/articles/language-tags/',
)));
}
if ($form_state->getValue('label') != SafeMarkup::checkPlain($form_state->getValue('label'))) {
$form_state->setErrorByName('label', $this->t('%field cannot contain any markup.', array('%field' => $form['label']['#title'])));
}
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\NegotiationBrowserDeleteForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Form\ConfigFormBaseTrait;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines a confirmation form for deleting a browser language negotiation mapping.
*/
class NegotiationBrowserDeleteForm extends ConfirmFormBase {
use ConfigFormBaseTrait;
/**
* The browser language code to be deleted.
*
* @var string
*/
protected $browserLangcode;
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.mappings'];
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete %browser_langcode?', array('%browser_langcode' => $this->browserLangcode));
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('language.negotiation_browser');
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_browser_delete_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $browser_langcode = NULL) {
$this->browserLangcode = $browser_langcode;
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('language.mappings')
->clear('map.' . $this->browserLangcode)
->save();
$args = array(
'%browser' => $this->browserLangcode,
);
$this->logger('language')->notice('The browser language detection mapping for the %browser browser language code has been deleted.', $args);
drupal_set_message($this->t('The mapping for the %browser browser language code has been deleted.', $args));
$form_state->setRedirect('language.negotiation_browser');
}
}

View file

@ -0,0 +1,204 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\NegotiationBrowserForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure the browser language negotiation method for this site.
*/
class NegotiationBrowserForm extends ConfigFormBase {
/**
* The configurable language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* {@inheritdoc}
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler
*/
public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager ) {
parent::__construct($config_factory);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_browser_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.mappings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = array();
// Initialize a language list to the ones available, including English.
$languages = $this->languageManager->getLanguages();
$existing_languages = array();
foreach ($languages as $langcode => $language) {
$existing_languages[$langcode] = $language->getName();
}
// If we have no languages available, present the list of predefined languages
// only. If we do have already added languages, set up two option groups with
// the list of existing and then predefined languages.
if (empty($existing_languages)) {
$language_options = $this->languageManager->getStandardLanguageListWithoutConfigured();
}
else {
$language_options = array(
$this->t('Existing languages') => $existing_languages,
$this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
);
}
$form['mappings'] = array(
'#tree' => TRUE,
'#theme' => 'language_negotiation_configure_browser_form_table',
);
$mappings = $this->language_get_browser_drupal_langcode_mappings();
foreach ($mappings as $browser_langcode => $drupal_langcode) {
$form['mappings'][$browser_langcode] = array(
'browser_langcode' => array(
'#title' => $this->t('Browser language code'),
'#title_display' => 'invisible',
'#type' => 'textfield',
'#default_value' => $browser_langcode,
'#size' => 20,
'#required' => TRUE,
),
'drupal_langcode' => array(
'#title' => $this->t('Site language'),
'#title_display' => 'invisible',
'#type' => 'select',
'#options' => $language_options,
'#default_value' => $drupal_langcode,
'#required' => TRUE,
),
);
}
// Add empty row.
$form['new_mapping'] = array(
'#type' => 'details',
'#title' => $this->t('Add a new mapping'),
'#tree' => TRUE,
);
$form['new_mapping']['browser_langcode'] = array(
'#type' => 'textfield',
'#title' => $this->t('Browser language code'),
'#description' => $this->t('Use language codes as <a href="@w3ctags">defined by the W3C</a> for interoperability. <em>Examples: "en", "en-gb" and "zh-hant".</em>', array('@w3ctags' => 'http://www.w3.org/International/articles/language-tags/')),
'#size' => 20,
);
$form['new_mapping']['drupal_langcode'] = array(
'#type' => 'select',
'#title' => $this->t('Site language'),
'#options' => $language_options,
);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Array to check if all browser language codes are unique.
$unique_values = array();
// Check all mappings.
if ($form_state->hasValue('mappings')) {
$mappings = $form_state->getValue('mappings');
foreach ($mappings as $key => $data) {
// Make sure browser_langcode is unique.
if (array_key_exists($data['browser_langcode'], $unique_values)) {
$form_state->setErrorByName('mappings][new_mapping][browser_langcode', $this->t('Browser language codes must be unique.'));
}
elseif (preg_match('/[^a-z\-]/', $data['browser_langcode'])) {
$form_state->setErrorByName('mappings][new_mapping][browser_langcode', $this->t('Browser language codes can only contain lowercase letters and a hyphen(-).'));
}
$unique_values[$data['browser_langcode']] = $data['drupal_langcode'];
}
}
// Check new mapping.
$data = $form_state->getValue('new_mapping');
if (!empty($data['browser_langcode'])) {
// Make sure browser_langcode is unique.
if (array_key_exists($data['browser_langcode'], $unique_values)) {
$form_state->setErrorByName('mappings][' . $key . '][browser_langcode', $this->t('Browser language codes must be unique.'));
}
elseif (preg_match('/[^a-z\-]/', $data['browser_langcode'])) {
$form_state->setErrorByName('mappings][' . $key . '][browser_langcode', $this->t('Browser language codes can only contain lowercase letters and a hyphen(-).'));
}
$unique_values[$data['browser_langcode']] = $data['drupal_langcode'];
}
$form_state->set('mappings', $unique_values);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$mappings = $form_state->get('mappings');
if (!empty($mappings)) {
$config = $this->config('language.mappings');
$config->setData(['map' => $mappings]);
$config->save();
}
parent::submitForm($form, $form_state);
}
/**
* Retrieves the browser's langcode mapping configuration array.
*
* @return array
* The browser's langcode mapping configuration array.
*/
protected function language_get_browser_drupal_langcode_mappings() {
$config = $this->config('language.mappings');
if ($config->isNew()) {
return array();
}
return $config->get('map');
}
}

View file

@ -0,0 +1,344 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\NegotiationConfigureForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\LanguageNegotiatorInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure the selected language negotiation method for this site.
*/
class NegotiationConfigureForm extends ConfigFormBase {
/**
* Stores the configuration object for language.types.
*
* @var \Drupal\Core\Config\Config
*/
protected $languageTypes;
/**
* The language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $negotiator;
/**
* The block manager.
*
* @var \Drupal\Core\Block\BlockManagerInterface
*/
protected $blockManager;
/**
* The block storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface|null
*/
protected $blockStorage;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs a NegotiationConfigureForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\language\LanguageNegotiatorInterface $negotiator
* The language negotiation methods manager.
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
* The block manager.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\Entity\EntityStorageInterface $block_storage
* The block storage, or NULL if not available.
*/
public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, BlockManagerInterface $block_manager, ThemeHandlerInterface $theme_handler, EntityStorageInterface $block_storage = NULL) {
parent::__construct($config_factory);
$this->languageTypes = $this->config('language.types');
$this->languageManager = $language_manager;
$this->negotiator = $negotiator;
$this->blockManager = $block_manager;
$this->themeHandler = $theme_handler;
$this->blockStorage = $block_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$entity_manager = $container->get('entity.manager');
$block_storage = $entity_manager->hasHandler('block', 'storage') ? $entity_manager->getStorage('block') : NULL;
return new static(
$container->get('config.factory'),
$container->get('language_manager'),
$container->get('language_negotiator'),
$container->get('plugin.manager.block'),
$container->get('theme_handler'),
$block_storage
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.types'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$configurable = $this->languageTypes->get('configurable');
$form = array(
'#theme' => 'language_negotiation_configure_form',
'#language_types_info' => $this->languageManager->getDefinedLanguageTypesInfo(),
'#language_negotiation_info' => $this->negotiator->getNegotiationMethods(),
);
$form['#language_types'] = array();
foreach ($form['#language_types_info'] as $type => $info) {
// Show locked language types only if they are configurable.
if (empty($info['locked']) || in_array($type, $configurable)) {
$form['#language_types'][] = $type;
}
}
foreach ($form['#language_types'] as $type) {
$this->configureFormTable($form, $type);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#button_type' => 'primary',
'#value' => $this->t('Save settings'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$configurable_types = $form['#language_types'];
$stored_values = $this->languageTypes->get('configurable');
$customized = array();
$method_weights_type = array();
foreach ($configurable_types as $type) {
$customized[$type] = in_array($type, $stored_values);
$method_weights = array();
$enabled_methods = $form_state->getValue(array($type, 'enabled'));
$enabled_methods[LanguageNegotiationSelected::METHOD_ID] = TRUE;
$method_weights_input = $form_state->getValue(array($type, 'weight'));
if ($form_state->hasValue(array($type, 'configurable'))) {
$customized[$type] = !$form_state->isValueEmpty(array($type, 'configurable'));
}
foreach ($method_weights_input as $method_id => $weight) {
if ($enabled_methods[$method_id]) {
$method_weights[$method_id] = $weight;
}
}
$method_weights_type[$type] = $method_weights;
$this->languageTypes->set('negotiation.' . $type . '.method_weights', $method_weights_input)->save();
}
// Update non-configurable language types and the related language
// negotiation configuration.
$this->negotiator->updateConfiguration(array_keys(array_filter($customized)));
// Update the language negotiations after setting the configurability.
foreach ($method_weights_type as $type => $method_weights) {
$this->negotiator->saveConfiguration($type, $method_weights);
}
// Clear block definitions cache since the available blocks and their names
// may have been changed based on the configurable types.
if ($this->blockStorage) {
// If there is an active language switcher for a language type that has
// been made not configurable, deactivate it first.
$non_configurable = array_keys(array_diff($customized, array_filter($customized)));
$this->disableLanguageSwitcher($non_configurable);
}
$this->blockManager->clearCachedDefinitions();
$form_state->setRedirect('language.negotiation');
drupal_set_message($this->t('Language detection configuration saved.'));
}
/**
* Builds a language negotiation method configuration table.
*
* @param array $form
* The language negotiation configuration form.
* @param string $type
* The language type to generate the table for.
*/
protected function configureFormTable(array &$form, $type) {
$info = $form['#language_types_info'][$type];
$table_form = array(
'#title' => $this->t('@type language detection', array('@type' => $info['name'])),
'#tree' => TRUE,
'#description' => $info['description'],
'#language_negotiation_info' => array(),
'#show_operations' => FALSE,
'weight' => array('#tree' => TRUE),
);
// Only show configurability checkbox for the unlocked language types.
if (empty($info['locked'])) {
$configurable = $this->languageTypes->get('configurable');
$table_form['configurable'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Customize %language_name language detection to differ from Interface text language detection settings', array('%language_name' => $info['name'])),
'#default_value' => in_array($type, $configurable),
'#attributes' => array('class' => array('language-customization-checkbox')),
'#attached' => array(
'library' => array(
'language/drupal.language.admin'
),
),
);
}
$negotiation_info = $form['#language_negotiation_info'];
$enabled_methods = $this->languageTypes->get('negotiation.' . $type . '.enabled') ?: array();
$methods_weight = $this->languageTypes->get('negotiation.' . $type . '.method_weights') ?: array();
// Add missing data to the methods lists.
foreach ($negotiation_info as $method_id => $method) {
if (!isset($methods_weight[$method_id])) {
$methods_weight[$method_id] = isset($method['weight']) ? $method['weight'] : 0;
}
}
// Order methods list by weight.
asort($methods_weight);
foreach ($methods_weight as $method_id => $weight) {
// A language method might be no more available if the defining module has
// been disabled after the last configuration saving.
if (!isset($negotiation_info[$method_id])) {
continue;
}
$enabled = isset($enabled_methods[$method_id]);
$method = $negotiation_info[$method_id];
// List the method only if the current type is defined in its 'types' key.
// If it is not defined default to all the configurable language types.
$types = array_flip(isset($method['types']) ? $method['types'] : $form['#language_types']);
if (isset($types[$type])) {
$table_form['#language_negotiation_info'][$method_id] = $method;
$method_name = SafeMarkup::checkPlain($method['name']);
$table_form['weight'][$method_id] = array(
'#type' => 'weight',
'#title' => $this->t('Weight for !title language detection method', array('!title' => Unicode::strtolower($method_name))),
'#title_display' => 'invisible',
'#default_value' => $weight,
'#attributes' => array('class' => array("language-method-weight-$type")),
'#delta' => 20,
);
$table_form['title'][$method_id] = array('#markup' => $method_name);
$table_form['enabled'][$method_id] = array(
'#type' => 'checkbox',
'#title' => $this->t('Enable !title language detection method', array('!title' => Unicode::strtolower($method_name))),
'#title_display' => 'invisible',
'#default_value' => $enabled,
);
if ($method_id === LanguageNegotiationSelected::METHOD_ID) {
$table_form['enabled'][$method_id]['#default_value'] = TRUE;
$table_form['enabled'][$method_id]['#attributes'] = array('disabled' => 'disabled');
}
$table_form['description'][$method_id] = array('#markup' => Xss::filterAdmin($method['description']));
$config_op = array();
if (isset($method['config_route_name'])) {
$config_op['configure'] = array(
'title' => $this->t('Configure'),
'url' => Url::fromRoute($method['config_route_name']),
);
// If there is at least one operation enabled show the operation
// column.
$table_form['#show_operations'] = TRUE;
}
$table_form['operation'][$method_id] = array(
'#type' => 'operations',
'#links' => $config_op,
);
}
}
$form[$type] = $table_form;
}
/**
* Disables the language switcher blocks.
*
* @param array $language_types
* An array containing all language types whose language switchers need to
* be disabled.
*/
protected function disableLanguageSwitcher(array $language_types) {
$theme = $this->themeHandler->getDefault();
$blocks = $this->blockStorage->loadByProperties(array('theme' => $theme));
foreach ($language_types as $language_type) {
foreach ($blocks as $block) {
if ($block->getPluginId() == 'language_block:' . $language_type) {
$block->delete();
}
}
}
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\NegotiationSelectedForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Form\ConfigFormBase;
/**
* Configure the selected language negotiation method for this site.
*/
class NegotiationSelectedForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_selected_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.negotiation'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('language.negotiation');
$form['selected_langcode'] = array(
'#type' => 'language_select',
'#title' => $this->t('Language'),
'#languages' => LanguageInterface::STATE_CONFIGURABLE | LanguageInterface::STATE_SITE_DEFAULT,
'#default_value' => $config->get('selected_langcode'),
);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('language.negotiation')
->set('selected_langcode', $form_state->getValue('selected_langcode'))
->save();
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\NegotiationSessionForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure the session language negotiation method for this site.
*/
class NegotiationSessionForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_session_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.negotiation'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('language.negotiation');
$form['language_negotiation_session_param'] = array(
'#title' => $this->t('Request/session parameter'),
'#type' => 'textfield',
'#default_value' => $config->get('session.parameter'),
'#description' => $this->t('Name of the request/session parameter used to determine the desired language.'),
);
$form_state->setRedirect('language.negotiation');
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('language.negotiation')
->set('session.parameter', $form_state->getValue('language_negotiation_session_param'))
->save();
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,223 @@
<?php
/**
* @file
* Contains \Drupal\language\Form\NegotiationUrlForm.
*/
namespace Drupal\language\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
/**
* Configure the URL language negotiation method for this site.
*/
class NegotiationUrlForm extends ConfigFormBase {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new LanguageDeleteForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) {
parent::__construct($config_factory);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_negotiation_configure_url_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['language.negotiation'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
global $base_url;
$config = $this->config('language.negotiation');
$form['language_negotiation_url_part'] = array(
'#title' => $this->t('Part of the URL that determines language'),
'#type' => 'radios',
'#options' => array(
LanguageNegotiationUrl::CONFIG_PATH_PREFIX => $this->t('Path prefix'),
LanguageNegotiationUrl::CONFIG_DOMAIN => $this->t('Domain'),
),
'#default_value' => $config->get('url.source'),
);
$form['prefix'] = array(
'#type' => 'details',
'#tree' => TRUE,
'#title' => $this->t('Path prefix configuration'),
'#open' => TRUE,
'#description' => $this->t('Language codes or other custom text to use as a path prefix for URL language detection. For the selected fallback language, this value may be left blank. <strong>Modifying this value may break existing URLs. Use with caution in a production environment.</strong> Example: Specifying "deutsch" as the path prefix code for German results in URLs like "example.com/deutsch/contact".'),
'#states' => array(
'visible' => array(
':input[name="language_negotiation_url_part"]' => array(
'value' => (string) LanguageNegotiationUrl::CONFIG_PATH_PREFIX,
),
),
),
);
$form['domain'] = array(
'#type' => 'details',
'#tree' => TRUE,
'#title' => $this->t('Domain configuration'),
'#open' => TRUE,
'#description' => $this->t('The domain names to use for these languages. <strong>Modifying this value may break existing URLs. Use with caution in a production environment.</strong> Example: Specifying "de.example.com" as language domain for German will result in an URL like "http://de.example.com/contact".'),
'#states' => array(
'visible' => array(
':input[name="language_negotiation_url_part"]' => array(
'value' => (string) LanguageNegotiationUrl::CONFIG_DOMAIN,
),
),
),
);
$languages = $this->languageManager->getLanguages();
$prefixes = language_negotiation_url_prefixes();
$domains = language_negotiation_url_domains();
foreach ($languages as $langcode => $language) {
$t_args = array('%language' => $language->getName(), '%langcode' => $language->getId());
$form['prefix'][$langcode] = array(
'#type' => 'textfield',
'#title' => $language->isDefault() ? $this->t('%language (%langcode) path prefix (Default language)', $t_args) : $this->t('%language (%langcode) path prefix', $t_args),
'#maxlength' => 64,
'#default_value' => isset($prefixes[$langcode]) ? $prefixes[$langcode] : '',
'#field_prefix' => $base_url . '/',
);
$form['domain'][$langcode] = array(
'#type' => 'textfield',
'#title' => $this->t('%language (%langcode) domain', array('%language' => $language->getName(), '%langcode' => $language->getId())),
'#maxlength' => 128,
'#default_value' => isset($domains[$langcode]) ? $domains[$langcode] : '',
);
}
$form_state->setRedirect('language.negotiation');
return parent::buildForm($form, $form_state);
}
/**
* Implements \Drupal\Core\Form\FormInterface::validateForm().
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$languages = $this->languageManager->getLanguages();
// Count repeated values for uniqueness check.
$count = array_count_values($form_state->getValue('prefix'));
$default_langcode = $this->config('language.negotiation')->get('selected_langcode');
if ($default_langcode == LanguageInterface::LANGCODE_SITE_DEFAULT) {
$default_langcode = $this->languageManager->getDefaultLanguage()->getId();
}
foreach ($languages as $langcode => $language) {
$value = $form_state->getValue(array('prefix', $langcode));
if ($value === '') {
if (!($default_langcode == $langcode) && $form_state->getValue('language_negotiation_url_part') == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
// Throw a form error if the prefix is blank for a non-default language,
// although it is required for selected negotiation type.
$form_state->setErrorByName("prefix][$langcode", $this->t('The prefix may only be left blank for the <a href="@url">selected detection fallback language.</a>', [
'@url' => $this->getUrlGenerator()->generate('language.negotiation_selected'),
]));
}
}
elseif (strpos($value, '/') !== FALSE) {
// Throw a form error if the string contains a slash,
// which would not work.
$form_state->setErrorByName("prefix][$langcode", $this->t('The prefix may not contain a slash.'));
}
elseif (isset($count[$value]) && $count[$value] > 1) {
// Throw a form error if there are two languages with the same
// domain/prefix.
$form_state->setErrorByName("prefix][$langcode", $this->t('The prefix for %language, %value, is not unique.', array('%language' => $language->getName(), '%value' => $value)));
}
}
// Count repeated values for uniqueness check.
$count = array_count_values($form_state->getValue('domain'));
foreach ($languages as $langcode => $language) {
$value = $form_state->getValue(array('domain', $langcode));
if ($value === '') {
if ($form_state->getValue('language_negotiation_url_part') == LanguageNegotiationUrl::CONFIG_DOMAIN) {
// Throw a form error if the domain is blank for a non-default language,
// although it is required for selected negotiation type.
$form_state->setErrorByName("domain][$langcode", $this->t('The domain may not be left blank for %language.', array('%language' => $language->getName())));
}
}
elseif (isset($count[$value]) && $count[$value] > 1) {
// Throw a form error if there are two languages with the same
// domain/domain.
$form_state->setErrorByName("domain][$langcode", $this->t('The domain for %language, %value, is not unique.', array('%language' => $language->getName(), '%value' => $value)));
}
}
// Domain names should not contain protocol and/or ports.
foreach ($languages as $langcode => $language) {
$value = $form_state->getValue(array('domain', $langcode));
if (!empty($value)) {
// Ensure we have exactly one protocol when checking the hostname.
$host = 'http://' . str_replace(array('http://', 'https://'), '', $value);
if (parse_url($host, PHP_URL_HOST) != $value) {
$form_state->setErrorByName("domain][$langcode", $this->t('The domain for %language may only contain the domain name, not a trailing slash, protocol and/or port.', ['%language' => $language->getName()]));
}
}
}
parent::validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Save selected format (prefix or domain).
$this->config('language.negotiation')
->set('url.source', $form_state->getValue('language_negotiation_url_part'))
->save();
// Save new domain and prefix values.
language_negotiation_url_prefixes_save($form_state->getValue('prefix'));
language_negotiation_url_domains_save($form_state->getValue('domain'));
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,140 @@
<?php
/**
* @file
* Contains \Drupal\language\HttpKernel\PathProcessorLanguage.
*/
namespace Drupal\language\HttpKernel;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\LanguageNegotiatorInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Session\AccountInterface;
/**
* Processes the inbound path using path alias lookups.
*/
class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
/**
* A config factory for retrieving required config settings.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $config;
/**
* Language manager for retrieving the url language type.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The language negotiator.
*
* @var \Drupal\language\LanguageNegotiatorInterface
*/
protected $negotiator;
/**
* Local cache for language path processors.
*
* @var array
*/
protected $processors;
/**
* Flag indicating whether the site is multilingual.
*
* @var bool
*/
protected $multilingual;
/**
* Constructs a PathProcessorLanguage object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
* A config factory object for retrieving configuration settings.
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The configurable language manager.
* @param \Drupal\language\LanguageNegotiatorInterface
* The language negotiator.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
*/
public function __construct(ConfigFactoryInterface $config, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, AccountInterface $current_user) {
$this->config = $config;
$this->languageManager = $language_manager;
$this->negotiator = $negotiator;
$this->negotiator->setCurrentUser($current_user);
}
/**
* {@inheritdoc}
*/
public function processInbound($path, Request $request) {
if (!empty($path)) {
$scope = 'inbound';
if (!isset($this->processors[$scope])) {
$this->initProcessors($scope);
}
foreach ($this->processors[$scope] as $instance) {
$path = $instance->processInbound($path, $request);
}
}
return $path;
}
/**
* {@inheritdoc}
*/
public function processOutbound($path, &$options = array(), Request $request = NULL, CacheableMetadata $cacheable_metadata = NULL) {
if (!isset($this->multilingual)) {
$this->multilingual = $this->languageManager->isMultilingual();
}
if ($this->multilingual) {
$this->negotiator->reset();
$scope = 'outbound';
if (!isset($this->processors[$scope])) {
$this->initProcessors($scope);
}
foreach ($this->processors[$scope] as $instance) {
$path = $instance->processOutbound($path, $options, $request, $cacheable_metadata);
}
// No language dependent path allowed in this mode.
if (empty($this->processors[$scope])) {
unset($options['language']);
}
}
return $path;
}
/**
* Initializes the local cache for language path processors.
*
* @param string $scope
* The scope of the processors: "inbound" or "outbound".
*/
protected function initProcessors($scope) {
$interface = '\Drupal\Core\PathProcessor\\' . Unicode::ucfirst($scope) . 'PathProcessorInterface';
$this->processors[$scope] = array();
foreach ($this->languageManager->getLanguageTypes() as $type) {
foreach ($this->negotiator->getNegotiationMethods($type) as $method_id => $method) {
if (!isset($this->processors[$scope][$method_id])) {
$reflector = new \ReflectionClass($method['class']);
if ($reflector->implementsInterface($interface)) {
$this->processors[$scope][$method_id] = $this->negotiator->getNegotiationMethodInstance($method_id);
}
}
}
}
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageAccessControlHandler.
*/
namespace Drupal\language;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for the language entity type.
*
* @see \Drupal\language\Entity\Language
*/
class LanguageAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
public function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
switch ($operation) {
case 'update':
/* @var \Drupal\Core\Language\LanguageInterface $entity */
return AccessResult::allowedIf(!$entity->isLocked())->cacheUntilEntityChanges($entity)
->andIf(parent::checkAccess($entity, $operation, $langcode, $account));
case 'delete':
/* @var \Drupal\Core\Language\LanguageInterface $entity */
return AccessResult::allowedIf(!$entity->isLocked())->cacheUntilEntityChanges($entity)
->andIf(AccessResult::allowedIf(!$entity->isDefault())->cacheUntilEntityChanges($entity))
->andIf(parent::checkAccess($entity, $operation, $langcode, $account));
default:
// No opinion.
return AccessResult::neutral();
}
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageConverter.
*/
namespace Drupal\language;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Symfony\Component\Routing\Route;
/**
* Converts parameters for upcasting entity IDs to full objects.
*/
class LanguageConverter implements ParamConverterInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new LanguageConverter.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(LanguageManagerInterface $language_manager) {
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public function convert($value, $definition, $name, array $defaults) {
if (!empty($value)) {
return $this->languageManager->getLanguage($value);
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function applies($definition, $name, Route $route) {
return (!empty($definition['type']) && $definition['type'] == 'language');
}
}

View file

@ -0,0 +1,169 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageListBuilder.
*/
namespace Drupal\language;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\Entity\DraggableListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a class to build a listing of language entities.
*
* @see \Drupal\language\Entity\ConfigurableLanguage
*/
class LanguageListBuilder extends DraggableListBuilder {
/**
* {@inheritdoc}
*/
protected $entitiesKey = 'languages';
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('language_manager'),
$container->get('config.factory')
);
}
/**
* Constructs a new EntityListController object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage controller class.
* @param \Drupal\Core\Language\LanguageManagerInterface
* The language manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($entity_type, $storage);
$this->languageManager = $language_manager;
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function load() {
$entities = $this->storage->loadByProperties(array('locked' => FALSE));
// Sort the entities using the entity class's sort() method.
// See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
uasort($entities, array($this->entityType->getClass(), 'sort'));
return $entities;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'language_admin_overview_form';
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header = array(
'label' => t('Name'),
'default' => t('Default'),
) + parent::buildHeader();
return $header;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['label'] = $this->getLabel($entity);
$row['default'] = array(
'#type' => 'radio',
'#parents' => array('site_default_language'),
'#title' => t('Set @title as default', array('@title' => $entity->label())),
'#title_display' => 'invisible',
'#return_value' => $entity->id(),
'#id' => 'edit-site-default-language-' . $entity->id(),
);
// Mark the right language as default in the form.
if ($entity->id() == $this->languageManager->getDefaultLanguage()->getId()) {
$row['default']['#default_value'] = $entity->id();
}
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$form[$this->entitiesKey]['#languages'] = $this->entities;
$form['actions']['submit']['#value'] = t('Save configuration');
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (!isset($this->entities[$form_state->getValue('site_default_language')])) {
$form_state->setErrorByName('site_default_language', $this->t('Selected default language no longer exists.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
// Save the default language if changed.
$new_id = $form_state->getValue('site_default_language');
if ($new_id != $this->languageManager->getDefaultLanguage()->getId()) {
$this->configFactory->getEditable('system.site')->set('default_langcode', $new_id)->save();
$this->languageManager->reset();
}
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
$this->languageManager->updateLockedLanguageWeights();
}
drupal_set_message(t('Configuration saved.'));
// Force the redirection to the page with the language we have just
// selected as default.
$form_state->setRedirectUrl($this->entities[$new_id]->urlInfo('collection', array('language' => $this->entities[$new_id])));
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageNegotiationMethodBase.
*/
namespace Drupal\language;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Base class for language negotiation methods.
*/
abstract class LanguageNegotiationMethodBase implements LanguageNegotiationMethodInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $config;
/**
* The current active user.
*
* @return \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* {@inheritdoc}
*/
public function setLanguageManager(ConfigurableLanguageManagerInterface $language_manager) {
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public function setConfig(ConfigFactoryInterface $config) {
$this->config = $config;
}
/**
* {@inheritdoc}
*/
public function setCurrentUser(AccountInterface $current_user) {
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public function persist(LanguageInterface $language) {
// Default implementation persists nothing.
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageNegotiationMethodInterface.
*/
namespace Drupal\language;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Interface for language negotiation classes.
*/
interface LanguageNegotiationMethodInterface {
/**
* Injects the language manager.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager to be used to retrieve the language list and the
* already negotiated languages.
*/
public function setLanguageManager(ConfigurableLanguageManagerInterface $language_manager);
/**
* Injects the configuration factory.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
*/
public function setConfig(ConfigFactoryInterface $config);
/**
* Injects the current user.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
*/
public function setCurrentUser(AccountInterface $current_user);
/**
* Performs language negotiation.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* (optional) The current request. Defaults to NULL if it has not been
* initialized yet.
*
* @return string
* A valid language code or FALSE if the negotiation was unsuccessful.
*/
public function getLangcode(Request $request = NULL);
/**
* Notifies the plugin that the language code it returned has been accepted.
*
* @param \Drupal\Core\Language\LanguageInterface $language
* The accepted language.
*/
public function persist(LanguageInterface $language);
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageNegotiationMethodManager.
*/
namespace Drupal\language;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Manages language negotiation methods.
*/
class LanguageNegotiationMethodManager extends DefaultPluginManager {
/**
* Constructs a new LanguageNegotiationMethodManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* An object that implements CacheBackendInterface
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* An object that implements ModuleHandlerInterface
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/LanguageNegotiation', $namespaces, $module_handler, 'Drupal\language\LanguageNegotiationMethodInterface', 'Drupal\language\Annotation\LanguageNegotiation');
$this->cacheBackend = $cache_backend;
$this->cacheKeyPrefix = 'language_negotiation_plugins';
$this->cacheKey = 'language_negotiation_plugins';
$this->alterInfo('language_negotiation_info');
}
}

View file

@ -0,0 +1,366 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageNegotiator.
*/
namespace Drupal\language;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\Settings;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Class responsible for performing language negotiation.
*/
class LanguageNegotiator implements LanguageNegotiatorInterface {
/**
* The language negotiation method plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $negotiatorManager;
/**
* The language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The settings instance.
*
* @return \Drupal\Core\Site\Settings
*/
protected $settings;
/**
* The request stack object.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The current active user.
*
* @return \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Local cache for language negotiation method instances.
*
* @var array
*/
protected $methods;
/**
* An array of language objects keyed by method id.
*
* @var \Drupal\Core\Language\LanguageInterface[]
*/
protected $negotiatedLanguages = array();
/**
* Constructs a new LanguageNegotiator object.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Component\Plugin\PluginManagerInterface $negotiator_manager
* The language negotiation methods plugin manager
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Site\Settings $settings
* The settings instance.
*/
public function __construct(ConfigurableLanguageManagerInterface $language_manager, PluginManagerInterface $negotiator_manager, ConfigFactoryInterface $config_factory, Settings $settings, RequestStack $requestStack) {
$this->languageManager = $language_manager;
$this->negotiatorManager = $negotiator_manager;
$this->configFactory = $config_factory;
$this->settings = $settings;
$this->requestStack = $requestStack;
}
/**
* Initializes the injected language manager with the negotiator.
*
* This should be called right after instantiating the negotiator to make it
* available to the language manager without introducing a circular
* dependency.
*/
public function initLanguageManager() {
$this->languageManager->setNegotiator($this);
}
/**
* {@inheritdoc}
*/
public function reset() {
$this->negotiatedLanguages = array();
$this->methods = array();
}
/**
* {@inheritdoc}
*/
public function setCurrentUser(AccountInterface $current_user) {
$this->currentUser = $current_user;
$this->reset();
}
/**
* {@inheritdoc}
*/
public function initializeType($type) {
$language = NULL;
if ($this->currentUser) {
// Execute the language negotiation methods in the order they were set up
// and return the first valid language found.
foreach ($this->getEnabledNegotiators($type) as $method_id => $info) {
if (!isset($this->negotiatedLanguages[$method_id])) {
$this->negotiatedLanguages[$method_id] = $this->negotiateLanguage($type, $method_id);
}
// Since objects are references, we need to return a clone to prevent
// the language negotiation method cache from being unintentionally
// altered. The same methods might be used with different language types
// based on configuration.
$language = !empty($this->negotiatedLanguages[$method_id]) ? clone($this->negotiatedLanguages[$method_id]) : NULL;
if ($language) {
$this->getNegotiationMethodInstance($method_id)->persist($language);
break;
}
}
}
if (!$language) {
// If no other language was found use the default one.
$language = $this->languageManager->getDefaultLanguage();
$method_id = static::METHOD_ID;
}
return array($method_id => $language);
}
/**
* Gets enabled detection methods for the provided language type.
*
* @param string $type
* The language type.
*
* @return array
* An array of enabled detection methods for the provided language type.
*/
protected function getEnabledNegotiators($type) {
return $this->configFactory->get('language.types')->get('negotiation.' . $type . '.enabled') ?: array();
}
/**
* Performs language negotiation using the specified negotiation method.
*
* @param string $type
* The language type to be initialized.
* @param string $method_id
* The string identifier of the language negotiation method to use to detect
* language.
*
* @return \Drupal\Core\Language\LanguageInterface|null
* Negotiated language object for given type and method, FALSE otherwise.
*/
protected function negotiateLanguage($type, $method_id) {
$langcode = NULL;
$method = $this->negotiatorManager->getDefinition($method_id);
if (!isset($method['types']) || in_array($type, $method['types'])) {
$langcode = $this->getNegotiationMethodInstance($method_id)->getLangcode($this->requestStack->getCurrentRequest());
}
$languages = $this->languageManager->getLanguages();
return isset($languages[$langcode]) ? $languages[$langcode] : NULL;
}
/**
* {@inheritdoc}
*/
public function getNegotiationMethods($type = NULL) {
$definitions = $this->negotiatorManager->getDefinitions();
if (isset($type)) {
$enabled_methods = $this->getEnabledNegotiators($type);
$definitions = array_intersect_key($definitions, $enabled_methods);
}
return $definitions;
}
/**
* {@inheritdoc}
*/
public function getNegotiationMethodInstance($method_id) {
if (!isset($this->methods[$method_id])) {
$instance = $this->negotiatorManager->createInstance($method_id, array());
$instance->setLanguageManager($this->languageManager);
$instance->setConfig($this->configFactory);
$instance->setCurrentUser($this->currentUser);
$this->methods[$method_id] = $instance;
}
return $this->methods[$method_id];
}
/**
* {@inheritdoc}
*/
public function getPrimaryNegotiationMethod($type) {
$enabled_methods = $this->getEnabledNegotiators($type);
return empty($enabled_methods) ? LanguageNegotiatorInterface::METHOD_ID : key($enabled_methods);
}
/**
* {@inheritdoc}
*/
public function isNegotiationMethodEnabled($method_id, $type = NULL) {
$enabled = FALSE;
$language_types = !empty($type) ? array($type) : $this->languageManager->getLanguageTypes();
foreach ($language_types as $type) {
$enabled_methods = $this->getEnabledNegotiators($type);
if (isset($enabled_methods[$method_id])) {
$enabled = TRUE;
break;
}
}
return $enabled;
}
/**
* {@inheritdoc}
*/
function saveConfiguration($type, $enabled_methods) {
// As configurable language types might have changed, we reset the cache.
$this->languageManager->reset();
$definitions = $this->getNegotiationMethods();
$default_types = $this->languageManager->getLanguageTypes();
// Order the language negotiation method list by weight.
asort($enabled_methods);
foreach ($enabled_methods as $method_id => $weight) {
if (isset($definitions[$method_id])) {
$method = $definitions[$method_id];
// If the language negotiation method does not express any preference
// about types, make it available for any configurable type.
$types = array_flip(!empty($method['types']) ? $method['types'] : $default_types);
// Check whether the method is defined and has the right type.
if (!isset($types[$type])) {
unset($enabled_methods[$method_id]);
}
}
else {
unset($enabled_methods[$method_id]);
}
}
$this->configFactory->getEditable('language.types')->set('negotiation.' . $type . '.enabled', $enabled_methods)->save();
}
/**
* {@inheritdoc}
*/
function purgeConfiguration() {
// Ensure that we are getting the defined language negotiation information.
// An invocation of \Drupal\Core\Extension\ModuleHandler::install() or
// \Drupal\Core\Extension\ModuleHandler::uninstall() could invalidate the
// cached information.
$this->negotiatorManager->clearCachedDefinitions();
$this->languageManager->reset();
foreach ($this->languageManager->getDefinedLanguageTypesInfo() as $type => $info) {
$this->saveConfiguration($type, $this->getEnabledNegotiators($type));
}
}
/**
* {@inheritdoc}
*/
function updateConfiguration(array $types) {
// Ensure that we are getting the defined language negotiation information.
// An invocation of \Drupal\Core\Extension\ModuleHandler::install() or
// \Drupal\Core\Extension\ModuleHandler::uninstall() could invalidate the
// cached information.
$this->negotiatorManager->clearCachedDefinitions();
$this->languageManager->reset();
$language_types = array();
$language_types_info = $this->languageManager->getDefinedLanguageTypesInfo();
$method_definitions = $this->getNegotiationMethods();
foreach ($language_types_info as $type => $info) {
$configurable = in_array($type, $types);
// The default language negotiation settings, if available, are stored in
// $info['fixed'].
$has_default_settings = !empty($info['fixed']);
// Check whether the language type is unlocked. Only the status of
// unlocked language types can be toggled between configurable and
// non-configurable.
if (empty($info['locked'])) {
if (!$configurable && !$has_default_settings) {
// If we have an unlocked non-configurable language type without
// default language negotiation settings, we use the values
// negotiated for the interface language which, should always be
// available.
$method_weights = array(LanguageNegotiationUI::METHOD_ID);
$method_weights = array_flip($method_weights);
$this->saveConfiguration($type, $method_weights);
}
}
else {
// The language type is locked. Locked language types with default
// settings are always considered non-configurable. In turn if default
// settings are missing, the language type is always considered
// configurable.
// If the language type is locked we can just store its default language
// negotiation settings if it has some, since it is not configurable.
if ($has_default_settings) {
$method_weights = array();
// Default settings are in $info['fixed'].
foreach ($info['fixed'] as $weight => $method_id) {
if (isset($method_definitions[$method_id])) {
$method_weights[$method_id] = $weight;
}
}
$this->saveConfiguration($type, $method_weights);
}
else {
// It was missing default settings, so force it to be configurable.
$configurable = TRUE;
}
}
// Accumulate information for each language type so it can be saved later.
$language_types[$type] = $configurable;
}
// Store the language type configuration.
$config = array(
'configurable' => array_keys(array_filter($language_types)),
'all' => array_keys($language_types),
);
$this->languageManager->saveLanguageTypesConfiguration($config);
}
}

View file

@ -0,0 +1,214 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageNegotiatorInterface.
*/
namespace Drupal\language;
use Drupal\Core\Session\AccountInterface;
/**
* Common interface for language negotiation services.
*
* The language negotiation API is based on two major concepts:
* - Language types: types of translatable data (the types of data that a user
* can view or request).
* - Language negotiation methods: responsible for determining which language to
* use to present a particular piece of data to the user.
* Both language types and language negotiation methods are customizable.
*
* Drupal defines three built-in language types:
* - Interface language: The page's main language, used to present translated
* user interface elements such as titles, labels, help text, and messages.
* - Content language: The language used to present content that is available
* in more than one language.
* - URL language: The language associated with URLs. When generating a URL,
* this value will be used for URL's as a default if no explicit preference is
* provided.
* Modules can define additional language types through
* hook_language_types_info(), and alter existing language type definitions
* through hook_language_types_info_alter().
*
* Language types may be configurable or fixed. The language negotiation
* methods associated with a configurable language type can be explicitly
* set through the user interface. A fixed language type has predetermined
* (module-defined) language negotiation settings and, thus, does not appear in
* the configuration page. Here is a code snippet that makes the content
* language (which by default inherits the interface language's values)
* configurable:
* @code
* function mymodule_language_types_info_alter(&$language_types) {
* unset($language_types[LanguageInterface::TYPE_CONTENT]['fixed']);
* }
* @endcode
*
* The locked configuration property prevents one language type from being
* switched from customized to not customized, and vice versa.
* @see \Drupal\language\LanguageNegotiator::updateConfiguration()
*
* Every language type can have a different set of language negotiation methods
* assigned to it. Different language types often share the same language
* negotiation settings, but they can have independent settings if needed. If
* two language types are configured the same way, their language switcher
* configuration will be functionally identical and the same settings will act
* on both language types.
*
* Drupal defines the following built-in language negotiation methods:
* - URL: Determine the language from the URL (path prefix or domain).
* - Session: Determine the language from a request/session parameter.
* - User: Follow the user's language preference.
* - User admin language: Identify admin language from the user preferences.
* - Browser: Determine the language from the browser's language settings.
* - Selected language: Use the default site language.
* Language negotiation methods are simple plugin classes that implement a
* particular logic to return a language code. For instance, the URL method
* searches for a valid path prefix or domain name in the current request URL.
* If a language negotiation method does not return a valid language code, the
* next method associated to the language type (based on method weight) is
* invoked.
*
* Modules can define additional language negotiation methods by simply provide
* the related plugins, and alter existing methods through
* hook_language_negotiation_info_alter(). Here is an example snippet that lets
* path prefixes be ignored for administrative paths:
* @code
* function mymodule_language_negotiation_info_alter(&$negotiation_info) {
* // Replace the original plugin with our own implementation.
* $method_id = \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::METHOD_ID;
* $negotiation_info[$method_id]['class'] = 'Drupal\my_module\Plugin\LanguageNegotiation\MyLanguageNegotiationUrl';
* }
*
* class MyLanguageNegotiationUrl extends LanguageNegotiationUrl {
* public function getCurrentLanguage(Request $request = NULL) {
* if ($request) {
* // Use the original URL language negotiation method to get a valid
* // language code.
* $langcode = parent::getCurrentLanguage($request);
*
* // If we are on an administrative path, override with the default
* language.
* if ($request->query->has('q') && strtok($request->query->get('q'), '/') == 'admin') {
* return $this->languageManager->getDefaultLanguage()->getId();
* }
* return $langcode;
* }
* }
* }
* ?>
* @endcode
*
* For more information, see
* @link https://www.drupal.org/node/1497272 Language Negotiation API @endlink
*/
interface LanguageNegotiatorInterface {
/**
* The language negotiation method id for the language negotiator itself.
*/
const METHOD_ID = 'language-default';
/**
* Resets the negotiated languages and the method instances.
*/
public function reset();
/**
* Sets the current active user and resets all language types.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current active user.
*/
public function setCurrentUser(AccountInterface $current_user);
/**
* Initializes the specified language type.
*
* @param string $type
* The language type to be initialized.
*
* @return \Drupal\Core\Language\LanguageInterface[]
* Returns an array containing a single language keyed by the language
* negotiation method ID used to determine the language of the specified
* type. If negotiation is not possible the default language is returned.
*/
public function initializeType($type);
/**
* Returns the language negotiation methods enabled for a language type.
*
* @param string $type
* (optional) The language type. If no type is specified all the method
* definitions are returned.
*
* @return array[]
* An array of language negotiation method definitions keyed by method id.
*/
public function getNegotiationMethods($type = NULL);
/**
* Returns an instance of the specified language negotiation method.
*
* @param string $method_id
* The method identifier.
*
* @return \Drupal\language\LanguageNegotiationMethodInterface
*/
public function getNegotiationMethodInstance($method_id);
/**
* Returns the ID of the language type's primary language negotiation method.
*
* @param $type
* The language type.
*
* @return string
* The identifier of the primary language negotiation method for the given
* language type, or the default method if none exists.
*/
public function getPrimaryNegotiationMethod($type);
/**
* Checks whether a language negotiation method is enabled for a language type.
*
* @param $method_id
* The language negotiation method ID.
* @param $type
* (optional) The language type. If none is passed, all the configurable
* language types will be inspected.
*
* @return bool
* TRUE if the method is enabled for at least one of the given language
* types, or FALSE otherwise.
*/
public function isNegotiationMethodEnabled($method_id, $type = NULL);
/**
* Saves a list of language negotiation methods for a language type.
*
* @param string $type
* The language type.
* @param int[] $enabled_methods
* An array of language negotiation method weights keyed by method ID.
*/
function saveConfiguration($type, $enabled_methods);
/**
* Resave the configuration to purge missing negotiation methods.
*/
function purgeConfiguration();
/**
* Updates the configuration based on the given language types.
*
* Stores the list of the language types along with information about their
* configurable state. Stores the default settings if the language type is
* not configurable.
*
* @param string[] $types
* An array of configurable language types.
*/
function updateConfiguration(array $types);
}

View file

@ -0,0 +1,106 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageServiceProvider.
*/
namespace Drupal\language;
use Drupal\Core\Config\BootstrapConfigStorageFactory;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\Language\LanguageInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Overrides the language_manager service to point to language's module one.
*/
class LanguageServiceProvider extends ServiceProviderBase {
const CONFIG_PREFIX = 'language.entity.';
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
// The following services are needed only on multilingual sites.
if ($this->isMultilingual()) {
$container->register('language_request_subscriber', 'Drupal\language\EventSubscriber\LanguageRequestSubscriber')
->addTag('event_subscriber')
->addArgument(new Reference('language_manager'))
->addArgument(new Reference('language_negotiator'))
->addArgument(new Reference('string_translation'))
->addArgument(new Reference('current_user'));
$container->register('path_processor_language', 'Drupal\language\HttpKernel\PathProcessorLanguage')
->addTag('path_processor_inbound', array('priority' => 300))
->addTag('path_processor_outbound', array('priority' => 100))
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('language_manager'))
->addArgument(new Reference('language_negotiator'))
->addArgument(new Reference('current_user'));
}
}
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
$definition = $container->getDefinition('language_manager');
$definition->setClass('Drupal\language\ConfigurableLanguageManager')
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('module_handler'))
->addArgument(new Reference('language.config_factory_override'))
->addArgument(new Reference('request_stack'));
if ($default_language_values = $this->getDefaultLanguageValues()) {
$container->setParameter('language.default_values', $default_language_values);
}
// For monolingual sites, we explicitly set the default language for the
// language config override service as there is no language negotiation.
if (!$this->isMultilingual()) {
$container->getDefinition('language.config_factory_override')
->addMethodCall('setLanguageFromDefault', array(new Reference('language.default')));
}
}
/**
* Checks whether the site is multilingual.
*
* @return bool
* TRUE if the site is multilingual, FALSE otherwise.
*/
protected function isMultilingual() {
// Assign the prefix to a local variable so it can be used in an anonymous
// function.
$prefix = static::CONFIG_PREFIX;
// @todo Try to swap out for config.storage to take advantage of database
// and caching. This might prove difficult as this is called before the
// container has finished building.
$config_storage = BootstrapConfigStorageFactory::get();
$config_ids = array_filter($config_storage->listAll($prefix), function($config_id) use ($prefix) {
return $config_id != $prefix . LanguageInterface::LANGCODE_NOT_SPECIFIED && $config_id != $prefix . LanguageInterface::LANGCODE_NOT_APPLICABLE;
});
return count($config_ids) > 1;
}
/**
* Gets the default language values.
*
* @return array|bool
* Returns the default language values for the language configured in
* system.site:default_langcode if the corresponding configuration entity
* exists, otherwise FALSE.
*/
protected function getDefaultLanguageValues() {
$config_storage = BootstrapConfigStorageFactory::get();
$system = $config_storage->read('system.site');
$default_language = $config_storage->read(static::CONFIG_PREFIX . $system['default_langcode']);
if (is_array($default_language)) {
return $default_language;
}
return FALSE;
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\language\LanguageSwitcherInterface.
*/
namespace Drupal\language;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Request;
/**
* Interface for language switcher classes.
*/
interface LanguageSwitcherInterface {
/**
* Returns language switch links.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param string $type
* The language type.
* @param \Drupal\Core\Url $url
* The URL the switch links will be relative to.
*
* @return array
* An array of link arrays keyed by language code.
*/
public function getLanguageSwitchLinks(Request $request, $type, Url $url);
}

View file

@ -0,0 +1,121 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\Block\LanguageBlock.
*/
namespace Drupal\language\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'Language switcher' block.
*
* @Block(
* id = "language_block",
* admin_label = @Translation("Language switcher"),
* category = @Translation("System"),
* deriver = "Drupal\language\Plugin\Derivative\LanguageBlock"
* )
*/
class LanguageBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The path matcher.
*
* @var \Drupal\Core\Path\PathMatcherInterface
*/
protected $pathMatcher;
/**
* Constructs an LanguageBlock object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
* The path matcher.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager, PathMatcherInterface $path_matcher) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->languageManager = $language_manager;
$this->pathMatcher = $path_matcher;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('language_manager'),
$container->get('path.matcher')
);
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
$access = $this->languageManager->isMultilingual() ? AccessResult::allowed() : AccessResult::forbidden();
return $access->addCacheTags(['config:configurable_language_list']);
}
/**
* {@inheritdoc}
*/
public function build() {
$build = array();
$route_name = $this->pathMatcher->isFrontPage() ? '<front>' : '<current>';
$type = $this->getDerivativeId();
$links = $this->languageManager->getLanguageSwitchLinks($type, Url::fromRoute($route_name));
if (isset($links->links)) {
$build = array(
'#theme' => 'links__language_block',
'#links' => $links->links,
'#attributes' => array(
'class' => array(
"language-switcher-{$links->method_id}",
),
),
'#set_active_class' => TRUE,
);
}
return $build;
}
/**
* {@inheritdoc}
*
* @todo Make cacheable in https://www.drupal.org/node/2232375.
*/
public function getCacheMaxAge() {
return 0;
}
}

View file

@ -0,0 +1,156 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\Condition\Language.
*/
namespace Drupal\language\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'Language' condition.
*
* @Condition(
* id = "language",
* label = @Translation("Language"),
* context = {
* "language" = @ContextDefinition("language", label = @Translation("Language"))
* }
* )
*
*/
class Language extends ConditionPluginBase implements ContainerFactoryPluginInterface {
/**
* The Language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Creates a new Language instance.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param array $configuration
* The plugin configuration, i.e. an array with configuration values keyed
* by configuration option name. The special key 'context' may be used to
* initialize the defined contexts by setting it to an array of context
* values keyed by context names.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
*/
public function __construct(LanguageManagerInterface $language_manager, array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('language_manager'),
$configuration,
$plugin_id,
$plugin_definition
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
if ($this->languageManager->isMultilingual()) {
// Fetch languages.
$languages = $this->languageManager->getLanguages();
$langcodes_options = array();
foreach ($languages as $language) {
$langcodes_options[$language->getId()] = $language->getName();
}
$form['langcodes'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Language selection'),
'#default_value' => $this->configuration['langcodes'],
'#options' => $langcodes_options,
'#description' => $this->t('Select languages to enforce. If none are selected, all languages will be allowed.'),
);
}
else {
$form['langcodes'] = array(
'#type' => 'value',
'#default_value' => $this->configuration['langcodes'],
);
}
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['langcodes'] = array_filter($form_state->getValue('langcodes'));
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function summary() {
$language_list = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
$selected = $this->configuration['langcodes'];
// Reduce the language list to an array of language names.
$language_names = array_reduce($language_list, function(&$result, $item) use ($selected) {
// If the current item of the $language_list array is one of the selected
// languages, add it to the $results array.
if (!empty($selected[$item->getId()])) {
$result[$item->getId()] = $item->getName();
}
return $result;
}, array());
// If we have more than one language selected, separate them by commas.
if (count($this->configuration['langcodes']) > 1) {
$languages = implode(', ', $language_names);
}
else {
// If we have just one language just grab the only present value.
$languages = array_pop($language_names);
}
if (!empty($this->configuration['negate'])) {
return t('The language is not @languages.', array('@languages' => $languages));
}
return t('The language is @languages.', array('@languages' => $languages));
}
/**
* {@inheritdoc}
*/
public function evaluate() {
if (empty($this->configuration['langcodes']) && !$this->isNegated()) {
return TRUE;
}
$language = $this->getContextValue('language');
// Language visibility settings.
return !empty($this->configuration['langcodes'][$language->getId()]);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array('langcodes' => array()) + parent::defaultConfiguration();
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\Derivative\LanguageBlock.
*/
namespace Drupal\language\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\language\ConfigurableLanguageManagerInterface;
/**
* Provides language switcher block plugin definitions for all languages.
*/
class LanguageBlock extends DeriverBase {
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$language_manager = \Drupal::languageManager();
if ($language_manager instanceof ConfigurableLanguageManagerInterface) {
$info = $language_manager->getDefinedLanguageTypesInfo();
$configurable_types = $language_manager->getLanguageTypes();
foreach ($configurable_types as $type) {
$this->derivatives[$type] = $base_plugin_definition;
$this->derivatives[$type]['admin_label'] = t('Language switcher (!type)', array('!type' => $info[$type]['name']));
}
// If there is just one configurable type then change the title of the
// block.
if (count($configurable_types) == 1) {
$this->derivatives[reset($configurable_types)]['admin_label'] = t('Language switcher');
}
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser.
*/
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\Component\Utility\UserAgent;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Class for identifying language from the browser Accept-language HTTP header.
*
* @LanguageNegotiation(
* id = \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser::METHOD_ID,
* weight = -2,
* name = @Translation("Browser"),
* description = @Translation("Language from the browser's language settings."),
* config_route_name = "language.negotiation_browser"
* )
*/
class LanguageNegotiationBrowser extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-browser';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
if ($this->languageManager && $request && $request->server->get('HTTP_ACCEPT_LANGUAGE')) {
$http_accept_language = $request->server->get('HTTP_ACCEPT_LANGUAGE');
$langcodes = array_keys($this->languageManager->getLanguages());
$mappings = $this->config->get('language.mappings')->get('map');
$langcode = UserAgent::getBestMatchingLangcode($http_accept_language, $langcodes, $mappings);
// Internal page cache with multiple languages and browser negotiation
// could lead to wrong cached sites. Therefore disabling the internal
// page cache.
// @todo Solve more elegantly in https://www.drupal.org/node/2430335.
\Drupal::service('page_cache_kill_switch')->trigger();
}
return $langcode;
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected.
*/
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Class for identifying language from a selected language.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected::METHOD_ID,
* weight = 12,
* name = @Translation("Selected language"),
* description = @Translation("Language based on a selected language."),
* config_route_name = "language.negotiation_selected"
* )
*/
class LanguageNegotiationSelected extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-selected';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
if ($this->languageManager) {
$langcode = $this->config->get('language.negotiation')->get('selected_langcode');
}
return $langcode;
}
}

View file

@ -0,0 +1,163 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSession.
*/
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Url;
use Drupal\language\LanguageNegotiationMethodBase;
use Drupal\language\LanguageSwitcherInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Identify language from a request/session parameter.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSession::METHOD_ID,
* weight = -6,
* name = @Translation("Session"),
* description = @Translation("Language from a request/session parameter."),
* config_route_name = "language.negotiation_session"
* )
*/
class LanguageNegotiationSession extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface {
/**
* Flag used to determine whether query rewriting is active.
*
* @var bool
*/
protected $queryRewrite;
/**
* The query parameter name to rewrite.
*
* @var string
*/
protected $queryParam;
/**
* The query parameter value to be set.
*
* @var string
*/
protected $queryValue;
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-session';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$config = $this->config->get('language.negotiation')->get('session');
$param = $config['parameter'];
$langcode = $request && $request->query->get($param) ? $request->query->get($param) : NULL;
if (!$langcode && isset($_SESSION[$param])) {
$langcode = $_SESSION[$param];
}
return $langcode;
}
/**
* {@inheritdoc}
*/
public function persist(LanguageInterface $language) {
// We need to update the session parameter with the request value only if we
// have an authenticated user.
$langcode = $language->getId();
if ($langcode && $this->languageManager) {
$languages = $this->languageManager->getLanguages();
if ($this->currentUser->isAuthenticated() && isset($languages[$langcode])) {
$config = $this->config->get('language.negotiation')->get('session');
$_SESSION[$config['parameter']] = $langcode;
}
}
}
/**
* {@inheritdoc}
*/
public function processOutbound($path, &$options = array(), Request $request = NULL, CacheableMetadata $cacheable_metadata = NULL) {
if ($request) {
// The following values are not supposed to change during a single page
// request processing.
if (!isset($this->queryRewrite)) {
if ($this->currentUser->isAnonymous()) {
$languages = $this->languageManager->getLanguages();
$config = $this->config->get('language.negotiation')->get('session');
$this->queryParam = $config['parameter'];
$this->queryValue = $request->query->has($this->queryParam) ? $request->query->get($this->queryParam) : NULL;
$this->queryRewrite = isset($languages[$this->queryValue]);
}
else {
$this->queryRewrite = FALSE;
}
}
// If the user is anonymous, the user language negotiation method is
// enabled, and the corresponding option has been set, we must preserve
// any explicit user language preference even with cookies disabled.
if ($this->queryRewrite) {
if (isset($options['query']) && is_string($options['query'])) {
$query = array();
parse_str($options['query'], $query);
$options['query'] = $query;
}
if (!isset($options['query'][$this->queryParam])) {
$options['query'][$this->queryParam] = $this->queryValue;
}
if ($cacheable_metadata) {
// Cached URLs that have been processed by this outbound path
// processor must be:
$cacheable_metadata
// - invalidated when the language negotiation config changes, since
// another query parameter may be used to determine the language.
->addCacheTags($this->config->get('language.negotiation')->getCacheTags())
// - varied by the configured query parameter.
->addCacheContexts(['url.query_args:' . $this->queryParam]);
}
}
}
return $path;
}
/**
* {@inheritdoc}
*/
public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
$links = array();
$config = $this->config->get('language.negotiation')->get('session');
$param = $config['parameter'];
$language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $this->languageManager->getCurrentLanguage($type)->getId();
$query = array();
parse_str($request->getQueryString(), $query);
foreach ($this->languageManager->getNativeLanguages() as $language) {
$langcode = $language->getId();
$links[$langcode] = array(
'url' => $url,
'title' => $language->getName(),
'attributes' => array('class' => array('language-link')),
'query' => $query,
);
if ($language_query != $langcode) {
$links[$langcode]['query'][$param] = $langcode;
}
else {
$links[$langcode]['attributes']['class'][] = 'session-active';
}
}
return $links;
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI.
*/
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Identifies the language from the interface text language selected for page.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI::METHOD_ID,
* types = {Drupal\Core\Language\LanguageInterface::TYPE_CONTENT},
* weight = 9,
* name = @Translation("Interface"),
* description = @Translation("Use the detected interface language.")
* )
*/
class LanguageNegotiationUI extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-interface';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
return $this->languageManager ? $this->languageManager->getCurrentLanguage()->getId() : NULL;
}
}

View file

@ -0,0 +1,213 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl.
*/
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Url;
use Drupal\language\LanguageNegotiationMethodBase;
use Drupal\language\LanguageSwitcherInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Class for identifying language via URL prefix or domain.
*
* @LanguageNegotiation(
* id = \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::METHOD_ID,
* types = {\Drupal\Core\Language\LanguageInterface::TYPE_INTERFACE,
* \Drupal\Core\Language\LanguageInterface::TYPE_CONTENT,
* \Drupal\Core\Language\LanguageInterface::TYPE_URL},
* weight = -8,
* name = @Translation("URL"),
* description = @Translation("Language from the URL (Path prefix or domain)."),
* config_route_name = "language.negotiation_url"
* )
*/
class LanguageNegotiationUrl extends LanguageNegotiationMethodBase implements InboundPathProcessorInterface, OutboundPathProcessorInterface, LanguageSwitcherInterface {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-url';
/**
* URL language negotiation: use the path prefix as URL language indicator.
*/
const CONFIG_PATH_PREFIX = 'path_prefix';
/**
* URL language negotiation: use the domain as URL language indicator.
*/
const CONFIG_DOMAIN = 'domain';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
if ($request && $this->languageManager) {
$languages = $this->languageManager->getLanguages();
$config = $this->config->get('language.negotiation')->get('url');
switch ($config['source']) {
case LanguageNegotiationUrl::CONFIG_PATH_PREFIX:
$request_path = urldecode(trim($request->getPathInfo(), '/'));
$path_args = explode('/', $request_path);
$prefix = array_shift($path_args);
// Search prefix within added languages.
$negotiated_language = FALSE;
foreach ($languages as $language) {
if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
$negotiated_language = $language;
break;
}
}
if ($negotiated_language) {
$langcode = $negotiated_language->getId();
}
break;
case LanguageNegotiationUrl::CONFIG_DOMAIN:
// Get only the host, not the port.
$http_host = $request->getHost();
foreach ($languages as $language) {
// Skip the check if the language doesn't have a domain.
if (!empty($config['domains'][$language->getId()])) {
// Ensure that there is exactly one protocol in the URL when
// checking the hostname.
$host = 'http://' . str_replace(array('http://', 'https://'), '', $config['domains'][$language->getId()]);
$host = parse_url($host, PHP_URL_HOST);
if ($http_host == $host) {
$langcode = $language->getId();
break;
}
}
}
break;
}
}
return $langcode;
}
/**
* Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processInbound().
*/
public function processInbound($path, Request $request) {
$config = $this->config->get('language.negotiation')->get('url');
$parts = explode('/', trim($path, '/'));
$prefix = array_shift($parts);
// Search prefix within added languages.
foreach ($this->languageManager->getLanguages() as $language) {
if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
// Rebuild $path with the language removed.
$path = '/' . implode('/', $parts);
break;
}
}
return $path;
}
/**
* Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processOutbound().
*/
public function processOutbound($path, &$options = array(), Request $request = NULL, CacheableMetadata $cacheable_metadata = NULL) {
$url_scheme = 'http';
$port = 80;
if ($request) {
$url_scheme = $request->getScheme();
$port = $request->getPort();
}
$languages = array_flip(array_keys($this->languageManager->getLanguages()));
// Language can be passed as an option, or we go for current URL language.
if (!isset($options['language'])) {
$language_url = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL);
$options['language'] = $language_url;
}
// We allow only added languages here.
elseif (!is_object($options['language']) || !isset($languages[$options['language']->getId()])) {
return $path;
}
$config = $this->config->get('language.negotiation')->get('url');
if ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
if (is_object($options['language']) && !empty($config['prefixes'][$options['language']->getId()])) {
$options['prefix'] = $config['prefixes'][$options['language']->getId()] . '/';
if ($cacheable_metadata) {
$cacheable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL]);
}
}
}
elseif ($config['source'] == LanguageNegotiationUrl::CONFIG_DOMAIN) {
if (is_object($options['language']) && !empty($config['domains'][$options['language']->getId()])) {
// Save the original base URL. If it contains a port, we need to
// retain it below.
if (!empty($options['base_url'])) {
// The colon in the URL scheme messes up the port checking below.
$normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']);
}
// Ask for an absolute URL with our modified base URL.
$options['absolute'] = TRUE;
$options['base_url'] = $url_scheme . '://' . $config['domains'][$options['language']->getId()];
// In case either the original base URL or the HTTP host contains a
// port, retain it.
if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
list(, $port) = explode(':', $normalized_base_url);
$options['base_url'] .= ':' . $port;
}
elseif (($url_scheme == 'http' && $port != 80) || ($url_scheme == 'https' && $port != 443)) {
$options['base_url'] .= ':' . $port;
}
if (isset($options['https'])) {
if ($options['https'] === TRUE) {
$options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
}
elseif ($options['https'] === FALSE) {
$options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
}
}
// Add Drupal's subfolder from the base_path if there is one.
$options['base_url'] .= rtrim(base_path(), '/');
if ($cacheable_metadata) {
$cacheable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL, 'url.site']);
}
}
}
return $path;
}
/**
* {@inheritdoc}
*/
public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
$links = array();
foreach ($this->languageManager->getNativeLanguages() as $language) {
$links[$language->getId()] = array(
'url' => $url,
'title' => $language->getName(),
'language' => $language,
'attributes' => array('class' => array('language-link')),
);
}
return $links;
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrlFallback.
*/
namespace Drupal\language\Plugin\LanguageNegotiation;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Class that determines the language to be assigned to URLs when none is
* detected.
*
* The language negotiation process has a fallback chain that ends with the
* default language negotiation method. Each built-in language type has a
* separate initialization:
* - Interface language, which is the only configurable one, always gets a valid
* value. If no request-specific language is detected, the default language
* will be used.
* - Content language merely inherits the interface language by default.
* - URL language is detected from the requested URL and will be used to rewrite
* URLs appearing in the page being rendered. If no language can be detected,
* there are two possibilities:
* - If the default language has no configured path prefix or domain, then the
* default language is used. This guarantees that (missing) URL prefixes are
* preserved when navigating through the site.
* - If the default language has a configured path prefix or domain, a
* requested URL having an empty prefix or domain is an anomaly that must be
* fixed. This is done by introducing a prefix or domain in the rendered
* page matching the detected interface language.
*
* @LanguageNegotiation(
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrlFallback::METHOD_ID,
* types = {Drupal\Core\Language\LanguageInterface::TYPE_URL},
* weight = 8,
* name = @Translation("URL fallback"),
* description = @Translation("Use an already detected language for URLs if none is found.")
* )
*/
class LanguageNegotiationUrlFallback extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-url-fallback';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
if ($this->languageManager) {
$default = $this->languageManager->getDefaultLanguage();
$config = $this->config->get('language.negotiation')->get('url');
$prefix = ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX);
// If the default language is not configured to convey language
// information, a missing URL language information indicates that URL
// language should be the default one, otherwise we fall back to an
// already detected language.
if (($prefix && empty($config['prefixes'][$default->getId()])) || (!$prefix && empty($config['domains'][$default->getId()]))) {
$langcode = $default->getId();
}
else {
$langcode = $this->languageManager->getCurrentLanguage()->getId();
}
}
return $langcode;
}
}

View file

@ -0,0 +1,50 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\AdminPathEntityConverterLanguageTest.
*/
namespace Drupal\language\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Test administration path based conversion of entities.
*
* @group language
*/
class AdminPathEntityConverterLanguageTest extends WebTestBase {
public static $modules = array('language', 'language_test');
protected function setUp() {
parent::setUp();
$permissions = array(
'access administration pages',
'administer site configuration',
);
$this->drupalLogin($this->drupalCreateUser($permissions));
ConfigurableLanguage::createFromLangcode('es')->save();
}
/**
* Tests the translated and untranslated config entities are loaded properly.
*/
public function testConfigUsingCurrentLanguage() {
\Drupal::languageManager()
->getLanguageConfigOverride('es', 'language.entity.es')
->set('label', 'Español')
->save();
$this->drupalGet('es/admin/language_test/entity_using_current_language/es');
$this->assertNoRaw(t('Loaded %label.', array('%label' => 'Spanish')));
$this->assertRaw(t('Loaded %label.', array('%label' => 'Español')));
$this->drupalGet('es/admin/language_test/entity_using_original_language/es');
$this->assertRaw(t('Loaded %label.', array('%label' => 'Spanish')));
$this->assertNoRaw(t('Loaded %label.', array('%label' => 'Español')));
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\Condition\LanguageConditionTest.
*/
namespace Drupal\language\Tests\Condition;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\KernelTestBase;
use Drupal\Core\Condition\ConditionManager;
/**
* Tests that the language condition, provided by the language module, is
* working properly.
*
* @group language
*/
class LanguageConditionTest extends KernelTestBase {
/**
* The condition plugin manager.
*
* @var \Drupal\Core\Condition\ConditionManager
*/
protected $manager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'language');
protected function setUp() {
parent::setUp();
$this->installConfig(array('language'));
// Setup Italian.
ConfigurableLanguage::createFromLangcode('it')->save();
$this->manager = $this->container->get('plugin.manager.condition');
}
/**
* Test the language condition.
*/
public function testConditions() {
// Grab the language condition and configure it to check the content
// language.
$language = \Drupal::languageManager()->getLanguage('en');
$condition = $this->manager->createInstance('language')
->setConfig('langcodes', array('en' => 'en', 'it' => 'it'))
->setContextValue('language', $language);
$this->assertTrue($condition->execute(), 'Language condition passes as expected.');
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'The language is English, Italian.');
// Change to Italian only.
$condition->setConfig('langcodes', array('it' => 'it'));
$this->assertFalse($condition->execute(), 'Language condition fails as expected.');
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'The language is Italian.');
// Negate the condition
$condition->setConfig('negate', TRUE);
$this->assertTrue($condition->execute(), 'Language condition passes as expected.');
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'The language is not Italian.');
// Change the default language to Italian.
$language = \Drupal::languageManager()->getLanguage('it');
$condition = $this->manager->createInstance('language')
->setConfig('langcodes', array('en' => 'en', 'it' => 'it'))
->setContextValue('language', $language);
$this->assertTrue($condition->execute(), 'Language condition passes as expected.');
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'The language is English, Italian.');
// Change to Italian only.
$condition->setConfig('langcodes', array('it' => 'it'));
$this->assertTrue($condition->execute(), 'Language condition passes as expected.');
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'The language is Italian.');
// Negate the condition
$condition->setConfig('negate', TRUE);
$this->assertFalse($condition->execute(), 'Language condition fails as expected.');
// Check for the proper summary.
$this->assertEqual($condition->summary(), 'The language is not Italian.');
}
}

View file

@ -0,0 +1,146 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\EntityDefaultLanguageTest.
*/
namespace Drupal\language\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\simpletest\KernelTestBase;
/**
* Tests default language code is properly generated for entities.
*
* @group language
*/
class EntityDefaultLanguageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'node', 'field', 'text', 'user');
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->installEntitySchema('user');
// Activate Spanish language, so there are two languages activated.
$language = $this->container->get('entity.manager')->getStorage('configurable_language')->create(array(
'id' => 'es',
));
$language->save();
// Create a new content type which has Undefined language by default.
$this->createContentType('ctund', LanguageInterface::LANGCODE_NOT_SPECIFIED);
// Create a new content type which has Spanish language by default.
$this->createContentType('ctes', 'es');
}
/**
* Tests that default language code is properly set for new nodes.
*/
public function testEntityTranslationDefaultLanguageViaCode() {
// With language module activated, and a content type that is configured to
// have no language by default, a new node of this content type will have
// "und" language code when language is not specified.
$node = $this->createNode('ctund');
$this->assertEqual($node->langcode->value, LanguageInterface::LANGCODE_NOT_SPECIFIED);
// With language module activated, and a content type that is configured to
// have no language by default, a new node of this content type will have
// "es" language code when language is specified as "es".
$node = $this->createNode('ctund', 'es');
$this->assertEqual($node->langcode->value, 'es');
// With language module activated, and a content type that is configured to
// have language "es" by default, a new node of this content type will have
// "es" language code when language is not specified.
$node = $this->createNode('ctes');
$this->assertEqual($node->langcode->value, 'es');
// With language module activated, and a content type that is configured to
// have language "es" by default, a new node of this content type will have
// "en" language code when language "en" is specified.
$node = $this->createNode('ctes', 'en');
$this->assertEqual($node->langcode->value, 'en');
// Disable language module.
$this->disableModules(array('language'));
// With language module disabled, and a content type that is configured to
// have no language specified by default, a new node of this content type
// will have site's default language code when language is not specified.
$node = $this->createNode('ctund');
$this->assertEqual($node->langcode->value, 'en');
// With language module disabled, and a content type that is configured to
// have no language specified by default, a new node of this type will have
// "es" language code when language "es" is specified.
$node = $this->createNode('ctund', 'es');
$this->assertEqual($node->langcode->value, 'es');
// With language module disabled, and a content type that is configured to
// have language "es" by default, a new node of this type will have site's
// default language code when language is not specified.
$node = $this->createNode('ctes');
$this->assertEqual($node->langcode->value, 'en');
// With language module disabled, and a content type that is configured to
// have language "es" by default, a new node of this type will have "en"
// language code when language "en" is specified.
$node = $this->createNode('ctes', 'en');
$this->assertEqual($node->langcode->value, 'en');
}
/**
* Creates a new node content type.
*
* @param name
* The content type name.
* @param $langcode
* Default language code of the nodes of this type.
*/
protected function createContentType($name, $langcode) {
$content_type = $this->container->get('entity.manager')->getStorage('node_type')->create(array(
'name' => 'Test ' . $name,
'title_label' => 'Title',
'type' => $name,
'create_body' => FALSE,
));
$content_type->save();
ContentLanguageSettings::loadByEntityTypeBundle('node', $name)
->setLanguageAlterable(FALSE)
->setDefaultLangcode($langcode)
->save();
}
/**
* Creates a new node of given type and language using Entity API.
*
* @param $type
* The node content type.
* @param $langcode
* (optional) Language code to pass to entity create.
*
* @return \Drupal\node\NodeInterface
* The node created.
*/
protected function createNode($type, $langcode = NULL) {
$values = array(
'type' => $type,
'title' => $this->randomString(),
);
if (!empty($langcode)) {
$values['langcode'] = $langcode;
}
$node = $this->container->get('entity.manager')->getStorage('node')->create($values);
return $node;
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\EntityUrlLanguageTest.
*/
namespace Drupal\language\Tests;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the language of entity URLs.
* @group language
*/
class EntityUrlLanguageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'entity_test', 'user', 'system'];
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('configurable_language');
$this->installSchema('system', 'router');
\Drupal::service('router.builder')->rebuild();
// In order to reflect the changes for a multilingual site in the container
// we have to rebuild it.
ConfigurableLanguage::create(['id' => 'es'])->save();
ConfigurableLanguage::create(['id' => 'fr'])->save();
$this->config('language.types')->setData([
'configurable' => ['language_interface'],
'negotiation' => ['language_interface' => ['enabled' => ['language-url' => 0]]],
])->save();
$this->config('language.negotiation')->setData([
'url' => [
'source' => 'path_prefix',
'prefixes' => ['en' => 'en', 'es' => 'es', 'fr' => 'fr']
],
])->save();
$this->kernel->rebuildContainer();
$this->container = $this->kernel->getContainer();
\Drupal::setContainer($this->container);
}
/**
* Ensures that entity URLs in a language have the right language prefix.
*/
public function testEntityUrlLanguage() {
$entity = EntityTest::create();
$entity->addTranslation('es', ['name' => 'name spanish']);
$entity->addTranslation('fr', ['name' => 'name french']);
$entity->save();
$this->assertTrue(strpos($entity->urlInfo()->toString(), '/en/entity_test/' . $entity->id()) !== FALSE);
$this->assertTrue(strpos($entity->getTranslation('es')->urlInfo()->toString(), '/es/entity_test/' . $entity->id()) !== FALSE);
$this->assertTrue(strpos($entity->getTranslation('fr')->urlInfo()->toString(), '/fr/entity_test/' . $entity->id()) !== FALSE);
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageBlockSettingsVisibilityTest.
*/
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests that the language settings on block config appears correctly.
*
* @group language
*/
class LanguageBlockSettingsVisibilityTest extends WebTestBase {
public static $modules = array('block', 'language');
public function testUnnecessaryLanguageSettingsVisibility() {
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer blocks'));
$this->drupalLogin($admin_user);
$this->drupalPostForm('admin/config/regional/language/add', array('predefined_langcode' => 'hu'), t('Add language'));
$this->drupalGet('admin/structure/block/add/system_menu_block:admin/stark');
$this->assertNoFieldByXPath('//input[@id="edit-visibility-language-langcodes-und"]', NULL, '\'Not specified\' option does not appear at block config, language settings section.');
$this->assertNoFieldByXpath('//input[@id="edit-visibility-language-langcodes-zxx"]', NULL, '\'Not applicable\' option does not appear at block config, language settings section.');
$this->assertFieldByXPath('//input[@id="edit-visibility-language-langcodes-en"]', NULL, '\'English\' option appears at block config, language settings section.');
$this->assertFieldByXpath('//input[@id="edit-visibility-language-langcodes-hu"]', NULL, '\'Hungarian\' option appears at block config, language settings section.');
}
}

View file

@ -0,0 +1,97 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageBrowserDetectionTest.
*/
namespace Drupal\language\Tests;
use Drupal\Component\Utility\UserAgent;
use Drupal\Core\Language\Language;
use Drupal\simpletest\WebTestBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests browser language detection.
*
* @group language
*/
class LanguageBrowserDetectionTest extends WebTestBase {
public static $modules = array('language');
/**
* Tests for adding, editing and deleting mappings between browser language
* codes and Drupal language codes.
*/
function testUIBrowserLanguageMappings() {
// User to manage languages.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
// Check that the configure link exists.
$this->drupalGet('admin/config/regional/language/detection');
$this->assertLinkByHref('admin/config/regional/language/detection/browser');
// Check that defaults are loaded from language.mappings.yml.
$this->drupalGet('admin/config/regional/language/detection/browser');
$this->assertField('edit-mappings-zh-cn-browser-langcode', 'zh-cn', 'Chinese browser language code found.');
$this->assertField('edit-mappings-zh-cn-drupal-langcode', 'zh-hans-cn', 'Chinese Drupal language code found.');
// Delete zh-cn language code.
$browser_langcode = 'zh-cn';
$this->drupalGet('admin/config/regional/language/detection/browser/delete/' . $browser_langcode);
$message = t('Are you sure you want to delete @browser_langcode?', array(
'@browser_langcode' => $browser_langcode,
));
$this->assertRaw($message);
// Confirm the delete.
$edit = array();
$this->drupalPostForm('admin/config/regional/language/detection/browser/delete/' . $browser_langcode, $edit, t('Confirm'));
// We need raw here because %browser will add HTML.
$t_args = array(
'%browser' => $browser_langcode,
);
$this->assertRaw(t('The mapping for the %browser browser language code has been deleted.', $t_args), 'The test browser language code has been deleted.');
// Check we went back to the browser negotiation mapping overview.
$this->assertUrl(\Drupal::url('language.negotiation_browser', [], ['absolute' => TRUE]));
// Check that ch-zn no longer exists.
$this->assertNoField('edit-mappings-zh-cn-browser-langcode', 'Chinese browser language code no longer exists.');
// Add a new custom mapping.
$edit = array(
'new_mapping[browser_langcode]' => 'xx',
'new_mapping[drupal_langcode]' => 'en',
);
$this->drupalPostForm('admin/config/regional/language/detection/browser', $edit, t('Save configuration'));
$this->assertUrl(\Drupal::url('language.negotiation_browser', [], ['absolute' => TRUE]));
$this->assertField('edit-mappings-xx-browser-langcode', 'xx', 'Browser language code found.');
$this->assertField('edit-mappings-xx-drupal-langcode', 'en', 'Drupal language code found.');
// Add the same custom mapping again.
$this->drupalPostForm('admin/config/regional/language/detection/browser', $edit, t('Save configuration'));
$this->assertText('Browser language codes must be unique.');
// Change browser language code of our custom mapping to zh-sg.
$edit = array(
'mappings[xx][browser_langcode]' => 'zh-sg',
'mappings[xx][drupal_langcode]' => 'en',
);
$this->drupalPostForm('admin/config/regional/language/detection/browser', $edit, t('Save configuration'));
$this->assertText(t('Browser language codes must be unique.'));
// Change Drupal language code of our custom mapping to zh-hans.
$edit = array(
'mappings[xx][browser_langcode]' => 'xx',
'mappings[xx][drupal_langcode]' => 'zh-hans',
);
$this->drupalPostForm('admin/config/regional/language/detection/browser', $edit, t('Save configuration'));
$this->assertUrl(\Drupal::url('language.negotiation_browser', [], ['absolute' => TRUE]));
$this->assertField('edit-mappings-xx-browser-langcode', 'xx', 'Browser language code found.');
$this->assertField('edit-mappings-xx-drupal-langcode', 'zh-hans', 'Drupal language code found.');
}
}

View file

@ -0,0 +1,97 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageConfigOverrideImportTest.
*/
namespace Drupal\language\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Ensures the language config overrides can be synchronized.
*
* @group language
*/
class LanguageConfigOverrideImportTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'config', 'locale', 'config_translation');
/**
* Tests that language can be enabled and overrides are created during a sync.
*/
public function testConfigOverrideImport() {
ConfigurableLanguage::createFromLangcode('fr')->save();
/* @var \Drupal\Core\Config\StorageInterface $staging */
$staging = \Drupal::service('config.storage.staging');
$this->copyConfig(\Drupal::service('config.storage'), $staging);
// Uninstall the language module and its dependencies so we can test
// enabling the language module and creating overrides at the same time
// during a configuration synchronization.
\Drupal::service('module_installer')->uninstall(array('language'));
// Ensure that the current site has no overrides registered to the
// ConfigFactory.
$this->rebuildContainer();
/* @var \Drupal\Core\Config\StorageInterface $override_staging */
$override_staging = $staging->createCollection('language.fr');
// Create some overrides in staging.
$override_staging->write('system.site', array('name' => 'FR default site name'));
$override_staging->write('system.maintenance', array('message' => 'FR message: @site is currently under maintenance. We should be back shortly. Thank you for your patience'));
$this->configImporter()->import();
$this->rebuildContainer();
\Drupal::service('router.builder')->rebuild();
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.site');
$this->assertEqual('FR default site name', $override->get('name'));
$this->drupalGet('fr');
$this->assertText('FR default site name');
$this->drupalLogin($this->rootUser);
$this->drupalGet('admin/config/development/maintenance/translate/fr/edit');
$this->assertText('FR message: @site is currently under maintenance. We should be back shortly. Thank you for your patience');
}
/**
* Tests that configuration events are not fired during a sync of overrides.
*/
public function testConfigOverrideImportEvents() {
// Enable the config_events_test module so we can record events occurring.
\Drupal::service('module_installer')->install(array('config_events_test'));
$this->rebuildContainer();
ConfigurableLanguage::createFromLangcode('fr')->save();
/* @var \Drupal\Core\Config\StorageInterface $staging */
$staging = \Drupal::service('config.storage.staging');
$this->copyConfig(\Drupal::service('config.storage'), $staging);
/* @var \Drupal\Core\Config\StorageInterface $override_staging */
$override_staging = $staging->createCollection('language.fr');
// Create some overrides in staging.
$override_staging->write('system.site', array('name' => 'FR default site name'));
\Drupal::state()->set('config_events_test.event', FALSE);
$this->configImporter()->import();
$this->rebuildContainer();
\Drupal::service('router.builder')->rebuild();
// Test that no config save event has been fired during the import because
// language configuration overrides do not fire events.
$event_recorder = \Drupal::state()->get('config_events_test.event', FALSE);
$this->assertFalse($event_recorder);
$this->drupalGet('fr');
$this->assertText('FR default site name');
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageConfigOverrideInstallTest.
*/
namespace Drupal\language\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\KernelTestBase;
/**
* Ensures the language config overrides can be installed.
*
* @group language
*/
class LanguageConfigOverrideInstallTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'config_events_test');
/**
* Tests the configuration events are not fired during install of overrides.
*/
public function testLanguageConfigOverrideInstall() {
ConfigurableLanguage::createFromLangcode('de')->save();
// Need to enable test module after creating the language otherwise saving
// the language will install the configuration.
$this->enableModules(array('language_config_override_test'));
\Drupal::state()->set('config_events_test.event', FALSE);
$this->installConfig(array('language_config_override_test'));
$event_recorder = \Drupal::state()->get('config_events_test.event', FALSE);
$this->assertFalse($event_recorder);
$config = \Drupal::service('language.config_factory_override')->getOverride('de', 'language_config_override_test.settings');
$this->assertEqual($config->get('name'), 'Deutsch');
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageConfigSchemaTest.
*/
namespace Drupal\language\Tests;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\simpletest\WebTestBase;
/**
* Ensures the language config schema is correct.
*
* @group language
*/
class LanguageConfigSchemaTest extends WebTestBase {
use SchemaCheckTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'menu_link_content');
/**
* A user with administrative permissions.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create user.
$this->adminUser = $this->drupalCreateUser(array('administer languages'));
$this->drupalLogin($this->adminUser);
}
/**
* Tests whether the language config schema is valid.
*/
function testValidLanguageConfigSchema() {
// Make sure no language configuration available by default.
$config_data = $this->config('language.settings')->get();
$this->assertTrue(empty($config_data));
$settings_path = 'admin/config/regional/content-language';
// Enable translation for menu link.
$edit['entity_types[menu_link_content]'] = TRUE;
$edit['settings[menu_link_content][menu_link_content][settings][language][language_alterable]'] = TRUE;
// Enable translation for user.
$edit['entity_types[user]'] = TRUE;
$edit['settings[user][user][settings][language][language_alterable]'] = TRUE;
$edit['settings[user][user][settings][language][langcode]'] = 'en';
$this->drupalPostForm($settings_path, $edit, t('Save configuration'));
$config_data = $this->config('language.content_settings.menu_link_content.menu_link_content');
// Make sure configuration saved correctly.
$this->assertTrue($config_data->get('language_alterable'));
$this->assertConfigSchema(\Drupal::service('config.typed'), $config_data->getName(), $config_data->get());
}
}

View file

@ -0,0 +1,257 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageConfigurationElementTest.
*/
namespace Drupal\language\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\simpletest\WebTestBase;
/**
* Tests the features of the language configuration element field.
*
* @group language
*/
class LanguageConfigurationElementTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('taxonomy', 'node', 'language', 'language_elements_test', 'field_ui');
protected function setUp() {
parent::setUp();
$user = $this->drupalCreateUser(array('access administration pages', 'administer languages', 'administer content types'));
$this->drupalLogin($user);
}
/**
* Tests the language settings have been saved.
*/
public function testLanguageConfigurationElement() {
$this->drupalGet('language-tests/language_configuration_element');
$edit['lang_configuration[langcode]'] = 'current_interface';
$edit['lang_configuration[language_alterable]'] = FALSE;
$this->drupalPostForm(NULL, $edit, 'Save');
$lang_conf = ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'some_bundle');
// Check that the settings have been saved.
$this->assertEqual($lang_conf->getDefaultLangcode(), 'current_interface');
$this->assertFalse($lang_conf->isLanguageAlterable());
$this->drupalGet('language-tests/language_configuration_element');
$this->assertOptionSelected('edit-lang-configuration-langcode', 'current_interface');
$this->assertNoFieldChecked('edit-lang-configuration-language-alterable');
// Reload the page and save again.
$this->drupalGet('language-tests/language_configuration_element');
$edit['lang_configuration[langcode]'] = 'authors_default';
$edit['lang_configuration[language_alterable]'] = TRUE;
$this->drupalPostForm(NULL, $edit, 'Save');
$lang_conf = ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'some_bundle');
// Check that the settings have been saved.
$this->assertEqual($lang_conf->getDefaultLangcode(), 'authors_default');
$this->assertTrue($lang_conf->isLanguageAlterable());
$this->drupalGet('language-tests/language_configuration_element');
$this->assertOptionSelected('edit-lang-configuration-langcode', 'authors_default');
$this->assertFieldChecked('edit-lang-configuration-language-alterable');
// Test if content type settings have been saved.
$edit = array(
'name' => 'Page',
'type' => 'page',
'language_configuration[langcode]' => 'authors_default',
'language_configuration[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/types/add', $edit, 'Save and manage fields');
// Make sure the settings are saved when creating the content type.
$this->drupalGet('admin/structure/types/manage/page');
$this->assertOptionSelected('edit-language-configuration-langcode', 'authors_default');
$this->assertFieldChecked('edit-language-configuration-language-alterable');
}
/**
* Tests that the language_get_default_langcode() returns the correct values.
*/
public function testDefaultLangcode() {
// Add some custom languages.
foreach (array('aa', 'bb', 'cc') as $language_code) {
ConfigurableLanguage::create(array(
'id' => $language_code,
'label' => $this->randomMachineName(),
))->save();
}
// Fixed language.
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode('bb')
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$this->assertEqual($langcode, 'bb');
// Current interface.
ContentLanguageSettings::loadByEntityTypeBundle('entity_test', 'custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode('current_interface')
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
$this->assertEqual($langcode, $language_interface->getId());
// Site's default.
$old_default = \Drupal::languageManager()->getDefaultLanguage();
// Ensure the language entity default value is correct.
$configurable_language = entity_load('configurable_language', $old_default->getId());
$this->assertTrue($configurable_language->isDefault(), 'The en language entity is flagged as the default language.');
$this->config('system.site')->set('default_langcode', 'cc')->save();
ContentLanguageSettings::loadByEntityTypeBundle('entity_test','custom_bundle')
->setLanguageAlterable(TRUE)
->setDefaultLangcode(LanguageInterface::LANGCODE_SITE_DEFAULT)
->save();
$langcode = language_get_default_langcode('entity_test', 'custom_bundle');
$this->assertEqual($langcode, 'cc');
// Ensure the language entity default value is correct.
$configurable_language = entity_load('configurable_language', $old_default->getId());
$this->assertFalse($configurable_language->isDefault(), 'The en language entity is not flagged as the default language.');
$configurable_language = entity_load('configurable_language', 'cc');
// Check calling the
// \Drupal\language\ConfigurableLanguageInterface::isDefault() method
// directly.
$this->assertTrue($configurable_language->isDefault(), 'The cc language entity is flagged as the default language.');
// Check the default value of a language field when authors preferred option
// is selected.
// Create first an user and assign a preferred langcode to him.
$some_user = $this->drupalCreateUser();
$some_user->preferred_langcode = 'bb';
$some_user->save();
$this->drupalLogin($some_user);
ContentLanguageSettings::create([
'target_entity_type_id' => 'entity_test',
'target_bundle' => 'some_bundle',
])->setLanguageAlterable(TRUE)
->setDefaultLangcode('authors_default')
->save();
$this->drupalGet('language-tests/language_configuration_element_test');
$this->assertOptionSelected('edit-langcode', 'bb');
}
/**
* Tests that the configuration is updated when the node type is changed.
*/
public function testNodeTypeUpdate() {
// Create the article content type first if the profile used is not the
// standard one.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
}
$admin_user = $this->drupalCreateUser(array('administer content types'));
$this->drupalLogin($admin_user);
$edit = array(
'language_configuration[langcode]' => 'current_interface',
'language_configuration[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
// Check the language default configuration for the articles.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', 'article');
$uuid = $configuration->uuid();
$this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been saved on the Article content type.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been saved on the Article content type.');
// Rename the article content type.
$edit = array(
'type' => 'article_2'
);
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
// Check that we still have the settings for the new node type.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', 'article_2');
$this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been kept on the new Article content type.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been kept on the new Article content type.');
$this->assertEqual($configuration->uuid(), $uuid, 'The language configuration uuid has been kept on the new Article content type.');
}
/**
* Tests the language settings are deleted on bundle delete.
*/
public function testNodeTypeDelete() {
// Create the article content type first if the profile used is not the
// standard one.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array(
'type' => 'article',
'name' => 'Article'
));
}
$admin_user = $this->drupalCreateUser(array('administer content types'));
$this->drupalLogin($admin_user);
// Create language configuration for the articles.
$edit = array(
'language_configuration[langcode]' => 'authors_default',
'language_configuration[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
// Check the language default configuration for articles is present.
$configuration = \Drupal::entityManager()->getStorage('language_content_settings')->load('node.article');
$this->assertTrue($configuration, 'The language configuration is present.');
// Delete 'article' bundle.
$this->drupalPostForm('admin/structure/types/manage/article/delete', array(), t('Delete'));
// Check that the language configuration has been deleted.
\Drupal::entityManager()->getStorage('language_content_settings')->resetCache();
$configuration = \Drupal::entityManager()->getStorage('language_content_settings')->load('node.article');
$this->assertFalse($configuration, 'The language configuration was deleted after bundle was deleted.');
}
/**
* Tests that the configuration is updated when a vocabulary is changed.
*/
public function testTaxonomyVocabularyUpdate() {
$vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => 'Country',
'vid' => 'country',
));
$vocabulary->save();
$admin_user = $this->drupalCreateUser(array('administer taxonomy'));
$this->drupalLogin($admin_user);
$edit = array(
'default_language[langcode]' => 'current_interface',
'default_language[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/taxonomy/manage/country', $edit, t('Save'));
// Check the language default configuration.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', 'country');
$uuid = $configuration->uuid();
$this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been saved on the Country vocabulary.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been saved on the Country vocabulary.');
// Rename the vocabulary.
$edit = array(
'vid' => 'nation'
);
$this->drupalPostForm('admin/structure/taxonomy/manage/country', $edit, t('Save'));
// Check that we still have the settings for the new vocabulary.
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', 'nation');
$this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been kept on the new Country vocabulary.');
$this->assertTrue($configuration->isLanguageAlterable(), 'The alterable language configuration has been kept on the new Country vocabulary.');
$this->assertEqual($configuration->uuid(), $uuid, 'The language configuration uuid has been kept on the new Country vocabulary.');
}
}

View file

@ -0,0 +1,223 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageConfigurationTest.
*/
namespace Drupal\language\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Adds and configures languages to check negotiation changes.
*
* @group language
*/
class LanguageConfigurationTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language');
/**
* Functional tests for adding, editing and deleting languages.
*/
function testLanguageConfiguration() {
// Ensure the after installing the language module the weight of the English
// language is still 0.
$this->assertEqual(ConfigurableLanguage::load('en')->getWeight(), 0, 'The English language has a weight of 0.');
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
// Check if the Default English language has no path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[en]"]', '', 'Default English has no path prefix.');
// Check that Add language is a primary button.
$this->drupalGet('admin/config/regional/language/add');
$this->assertFieldByXPath('//input[contains(@class, "button--primary")]', 'Add language', 'Add language is a primary button');
// Add predefined language.
$edit = array(
'predefined_langcode' => 'fr',
);
$this->drupalPostForm(NULL, $edit, 'Add language');
$this->assertText('French');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE]), [], 'Correct page redirection.');
// Langcode for Languages is always 'en'.
$language = $this->config('language.entity.fr')->get();
$this->assertEqual($language['langcode'], 'en');
// Check if the Default English language has no path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[en]"]', '', 'Default English has no path prefix.');
// Check if French has a path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', 'French has a path prefix.');
// Check if we can change the default language.
$this->drupalGet('admin/config/regional/language');
$this->assertFieldChecked('edit-site-default-language-en', 'English is the default language.');
// Change the default language.
$edit = array(
'site_default_language' => 'fr',
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->rebuildContainer();
$this->assertFieldChecked('edit-site-default-language-fr', 'Default language updated.');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE, 'langcode' => 'fr']), [], 'Correct page redirection.');
// Check if a valid language prefix is added after changing the default
// language.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[en]"]', 'en', 'A valid path prefix has been added to the previous default language.');
// Check if French still has a path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', 'French still has a path prefix.');
// Check that prefix can be changed.
$edit = array(
'prefix[fr]' => 'french',
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'french', 'French path prefix has changed.');
// Check that the prefix can be removed.
$edit = array(
'prefix[fr]' => '',
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->assertNoText(t('The prefix may only be left blank for the selected detection fallback language.'), 'The path prefix can be removed for the default language');
// Change default negotiation language.
$this->config('language.negotiation')->set('selected_langcode', 'fr')->save();
// Check that the prefix of a language that is not the negotiation one
// cannot be changed to empty string.
$edit = array(
'prefix[en]' => '',
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->assertText(t('The prefix may only be left blank for the selected detection fallback language.'));
// Check that prefix cannot be changed to contain a slash.
$edit = array(
'prefix[en]' => 'foo/bar',
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->assertText(t('The prefix may not contain a slash.'), 'English prefix cannot be changed to contain a slash.');
// Remove English language and add a new Language to check if langcode of
// Language entity is 'en'.
$this->drupalPostForm('admin/config/regional/language/delete/en', array(), t('Delete'));
$this->rebuildContainer();
$this->assertRaw(t('The %language (%langcode) language has been removed.', array('%language' => 'English', '%langcode' => 'en')));
// Ensure that French language has a weight of 1 after being created through
// the UI.
$french = ConfigurableLanguage::load('fr');
$this->assertEqual($french->getWeight(), 1, 'The French language has a weight of 1.');
// Ensure that French language can now have a weight of 0.
$french->setWeight(0)->save();
$this->assertEqual($french->getWeight(), 0, 'The French language has a weight of 0.');
// Ensure that new languages created through the API get a weight of 0.
$afrikaans = ConfigurableLanguage::createFromLangcode('af');
$afrikaans->save();
$this->assertEqual($afrikaans->getWeight(), 0, 'The Afrikaans language has a weight of 0.');
// Ensure that a new language can be created with any weight.
$arabic = ConfigurableLanguage::createFromLangcode('ar');
$arabic->setWeight(4)->save();
$this->assertEqual($arabic->getWeight(), 4, 'The Arabic language has a weight of 0.');
$edit = array(
'predefined_langcode' => 'de',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, 'Add language');
$language = $this->config('language.entity.de')->get();
$this->assertEqual($language['langcode'], 'fr');
// Ensure that German language has a weight of 5 after being created through
// the UI.
$french = ConfigurableLanguage::load('de');
$this->assertEqual($french->getWeight(), 5, 'The German language has a weight of 5.');
}
/**
* Functional tests for setting system language weight on adding, editing and deleting languages.
*/
function testLanguageConfigurationWeight() {
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
$this->checkConfigurableLanguageWeight();
// Add predefined language.
$edit = array(
'predefined_langcode' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, 'Add language');
$this->checkConfigurableLanguageWeight('after adding new language');
// Re-ordering languages.
$edit = array(
'languages[en][weight]' => $this->getHighestConfigurableLanguageWeight() + 1,
);
$this->drupalPostForm('admin/config/regional/language', $edit, 'Save configuration');
$this->checkConfigurableLanguageWeight('after re-ordering');
// Remove predefined language.
$edit = array(
'confirm' => 1,
);
$this->drupalPostForm('admin/config/regional/language/delete/fr', $edit, 'Delete');
$this->checkConfigurableLanguageWeight('after deleting a language');
}
/**
* Validates system languages are ordered after configurable languages.
*
* @param string $state
* (optional) A string for customizing assert messages, containing the
* description of the state of the check, for example: 'after re-ordering'.
* Defaults to 'by default'.
*/
protected function checkConfigurableLanguageWeight($state = 'by default') {
// Reset language list.
\Drupal::languageManager()->reset();
$max_configurable_language_weight = $this->getHighestConfigurableLanguageWeight();
$replacements = array('@event' => $state);
foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $locked_language) {
$replacements['%language'] = $locked_language->getName();
$this->assertTrue($locked_language->getWeight() > $max_configurable_language_weight, format_string('System language %language has higher weight than configurable languages @event', $replacements));
}
}
/**
* Helper to get maximum weight of configurable (unlocked) languages.
*
* @return int
* Maximum weight of configurable languages.
*/
protected function getHighestConfigurableLanguageWeight(){
$max_weight = 0;
/* @var $languages \Drupal\Core\Language\LanguageInterface[] */
$languages = entity_load_multiple('configurable_language', NULL, TRUE);
foreach ($languages as $language) {
if (!$language->isLocked()) {
$max_weight = max($max_weight, $language->getWeight());
}
}
return $max_weight;
}
}

View file

@ -0,0 +1,105 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageCustomLanguageConfigurationTest.
*/
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
/**
* Adds and configures custom languages.
*
* @group language
*/
class LanguageCustomLanguageConfigurationTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language');
/**
* Functional tests for adding, editing and deleting languages.
*/
public function testLanguageConfiguration() {
// Create user with permissions to add and remove languages.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
// Add custom language.
$edit = array(
'predefined_langcode' => 'custom',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
// Test validation on missing values.
$this->assertText(t('!name field is required.', array('!name' => t('Language code'))));
$this->assertText(t('!name field is required.', array('!name' => t('Language name'))));
$empty_language = new Language();
$this->assertFieldChecked('edit-direction-' . $empty_language->getDirection(), 'Consistent usage of language direction.');
$this->assertUrl(\Drupal::url('language.add', array(), array('absolute' => TRUE)), [], 'Correct page redirection.');
// Test validation of invalid values.
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => 'white space',
'label' => '<strong>evil markup</strong>',
'direction' => LanguageInterface::DIRECTION_LTR,
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertRaw(t('%field must be a valid language tag as <a href="@url">defined by the W3C</a>.', array(
'%field' => t('Language code'),
'@url' => 'http://www.w3.org/International/articles/language-tags/',
)));
$this->assertRaw(t('%field cannot contain any markup.', array('%field' => t('Language name'))));
$this->assertUrl(\Drupal::url('language.add', array(), array('absolute' => TRUE)), [], 'Correct page redirection.');
// Test adding a custom language with a numeric region code.
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => 'es-419',
'label' => 'Latin American Spanish',
'direction' => LanguageInterface::DIRECTION_LTR,
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertRaw(t(
'The language %language has been created and can now be used.',
array('%language' => $edit['label'])
));
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', array(), array('absolute' => TRUE)), [], 'Correct page redirection.');
// Test validation of existing language values.
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => 'de',
'label' => 'German',
'direction' => LanguageInterface::DIRECTION_LTR,
);
// Add the language the first time.
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertRaw(t(
'The language %language has been created and can now be used.',
array('%language' => $edit['label'])
));
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', array(), array('absolute' => TRUE)), [], 'Correct page redirection.');
// Add the language a second time and confirm that this is not allowed.
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertRaw(t(
'The language %language (%langcode) already exists.',
array('%language' => $edit['label'], '%langcode' => $edit['langcode'])
));
$this->assertUrl(\Drupal::url('language.add', array(), array('absolute' => TRUE)), [], 'Correct page redirection.');
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageDependencyInjectionTest.
*/
namespace Drupal\language\Tests;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Language\Language;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Exception\DeleteDefaultLanguageException;
/**
* Compares the default language from $GLOBALS against the dependency injected
* language object.
*
* @group language
*/
class LanguageDependencyInjectionTest extends LanguageTestBase {
/**
* Test dependency injected languages against a new Language object.
*
* @see \Drupal\Core\Language\LanguageInterface
*/
function testDependencyInjectedNewLanguage() {
$expected = $this->languageManager->getDefaultLanguage();
$result = $this->languageManager->getCurrentLanguage();
foreach ($expected as $property => $value) {
$this->assertEqual($expected->$property, $result->$property, format_string('The dependency injected language object %prop property equals the new Language object %prop property.', array('%prop' => $property)));
}
}
/**
* Test dependency injected Language object against a new default language
* object.
*
* @see \Drupal\Core\Language\Language
*/
function testDependencyInjectedNewDefaultLanguage() {
$default_language = ConfigurableLanguage::load(\Drupal::languageManager()->getDefaultLanguage()->getId());
// Change the language default object to different values.
ConfigurableLanguage::createFromLangcode('fr')->save();
$this->config('system.site')->set('default_langcode', 'fr')->save();
// The language system creates a Language object which contains the
// same properties as the new default language object.
$result = \Drupal::languageManager()->getCurrentLanguage();
$this->assertIdentical($result->getId(), 'fr');
// Delete the language to check that we fallback to the default.
try {
entity_delete_multiple('configurable_language', array('fr'));
$this->fail('Expected DeleteDefaultLanguageException thrown.');
}
catch (DeleteDefaultLanguageException $e) {
$this->pass('Expected DeleteDefaultLanguageException thrown.');
}
// Re-save the previous default language and the delete should work.
$this->config('system.site')->set('default_langcode', $default_language->getId())->save();
entity_delete_multiple('configurable_language', array('fr'));
$result = \Drupal::languageManager()->getCurrentLanguage();
$this->assertIdentical($result->getId(), $default_language->getId());
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageFallbackTest.
*/
namespace Drupal\language\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the language fallback behavior.
*
* @group language
*/
class LanguageFallbackTest extends LanguageTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$i = 0;
foreach (array('af', 'am', 'ar') as $langcode) {
$language = ConfigurableLanguage::createFromLangcode($langcode);
$language->set('weight', $i--);
$language->save();
}
}
/**
* Tests language fallback candidates.
*/
public function testCandidates() {
$language_list = $this->languageManager->getLanguages();
$expected = array_keys($language_list + array(LanguageInterface::LANGCODE_NOT_SPECIFIED => NULL));
// Check that language fallback candidates by default are all the available
// languages sorted by weight.
$candidates = $this->languageManager->getFallbackCandidates();
$this->assertEqual(array_values($candidates), $expected, 'Language fallback candidates are properly returned.');
// Check that candidates are alterable.
$this->state->set('language_test.fallback_alter.candidates', TRUE);
$expected = array_slice($expected, 0, count($expected) - 1);
$candidates = $this->languageManager->getFallbackCandidates();
$this->assertEqual(array_values($candidates), $expected, 'Language fallback candidates are alterable.');
// Check that candidates are alterable for specific operations.
$this->state->set('language_test.fallback_alter.candidates', FALSE);
$this->state->set('language_test.fallback_operation_alter.candidates', TRUE);
$expected[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
$expected[] = LanguageInterface::LANGCODE_NOT_APPLICABLE;
$candidates = $this->languageManager->getFallbackCandidates(array('operation' => 'test'));
$this->assertEqual(array_values($candidates), $expected, 'Language fallback candidates are alterable for specific operations.');
// Check that when the site is monolingual no language fallback is applied.
$langcodes_to_delete = array();
foreach ($language_list as $langcode => $language) {
if (!$language->isDefault()) {
$langcodes_to_delete[] = $langcode;
}
}
entity_delete_multiple('configurable_language', $langcodes_to_delete);
$candidates = $this->languageManager->getFallbackCandidates();
$this->assertEqual(array_values($candidates), array(LanguageInterface::LANGCODE_DEFAULT), 'Language fallback is not applied when the Language module is not enabled.');
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageListModuleInstallTest.
*/
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests enabling Language if a module exists that calls
* LanguageManager::getLanguages() during installation.
*
* @group language
*/
class LanguageListModuleInstallTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language_test');
/**
* Tests enabling Language.
*/
function testModuleInstallLanguageList() {
// Since LanguageManager::getLanguages() uses static caches we need to do
// this by enabling the module using the UI.
$admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
$this->drupalLogin($admin_user);
$edit = array();
$edit['modules[Multilingual][language][enable]'] = 'language';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->assertEqual(\Drupal::state()->get('language_test.language_count_preinstall', 0), 1, 'Using LanguageManager::getLanguages() returns 1 language during Language installation.');
// Get updated module list by rebuilding container.
$this->rebuildContainer();
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('language'), 'Language module is enabled');
}
}

View file

@ -0,0 +1,213 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageListTest.
*/
namespace Drupal\language\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
/**
* Adds a new language and tests changing its status and the default language.
*
* @group language
*/
class LanguageListTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language');
/**
* Functional tests for adding, editing and deleting languages.
*/
function testLanguageList() {
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
// Get the weight of the last language.
$languages = \Drupal::service('language_manager')->getLanguages();
$last_language_weight = end($languages)->getWeight();
// Add predefined language.
$edit = array(
'predefined_langcode' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
$this->assertText('French', 'Language added successfully.');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE]));
// Get the weight of the last language and check that the weight is one unit
// heavier than the last configurable language.
$this->rebuildContainer();
$languages = \Drupal::service('language_manager')->getLanguages();
$last_language = end($languages);
$this->assertEqual($last_language->getWeight(), $last_language_weight + 1);
$this->assertEqual($last_language->getId(), $edit['predefined_langcode']);
// Add custom language.
$langcode = 'xx';
$name = $this->randomMachineName(16);
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'label' => $name,
'direction' => Language::DIRECTION_LTR,
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE]));
$this->assertRaw('"edit-languages-' . $langcode .'-weight"', 'Language code found.');
$this->assertText(t($name), 'Test language added.');
$language = \Drupal::service('language_manager')->getLanguage($langcode);
$english = \Drupal::service('language_manager')->getLanguage('en');
// Check if we can change the default language.
$path = 'admin/config/regional/language';
$this->drupalGet($path);
$this->assertFieldChecked('edit-site-default-language-en', 'English is the default language.');
// Change the default language.
$edit = array(
'site_default_language' => $langcode,
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->rebuildContainer();
$this->assertNoFieldChecked('edit-site-default-language-en', 'Default language updated.');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE, 'language' => $language]));
// Ensure we can't delete the default language.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
$this->assertResponse(403, 'Failed to delete the default language.');
// Ensure 'Edit' link works.
$this->drupalGet('admin/config/regional/language');
$this->clickLink(t('Edit'));
$this->assertTitle(t('Edit language | Drupal'), 'Page title is "Edit language".');
// Edit a language.
$name = $this->randomMachineName(16);
$edit = array(
'label' => $name,
);
$this->drupalPostForm('admin/config/regional/language/edit/' . $langcode, $edit, t('Save language'));
$this->assertRaw($name, 'The language has been updated.');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE, 'language' => $language]));
// Change back the default language.
$edit = array(
'site_default_language' => 'en',
);
$this->drupalPostForm($path, $edit, t('Save configuration'));
$this->rebuildContainer();
// Ensure 'delete' link works.
$this->drupalGet('admin/config/regional/language');
$this->clickLink(t('Delete'));
$this->assertText(t('Are you sure you want to delete the language'), '"Delete" link is correct.');
// Delete a language.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
// First test the 'cancel' link.
$this->clickLink(t('Cancel'));
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE, 'language' => $english]));
$this->assertRaw($name, 'The language was not deleted.');
// Delete the language for real. This a confirm form, we do not need any
// fields changed.
$this->drupalPostForm('admin/config/regional/language/delete/' . $langcode, array(), t('Delete'));
// We need raw here because %language and %langcode will add HTML.
$t_args = array('%language' => $name, '%langcode' => $langcode);
$this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), 'The test language has been removed.');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE, 'language' => $english]));
// Verify that language is no longer found.
$this->drupalGet('admin/config/regional/language/delete/' . $langcode);
$this->assertResponse(404, 'Language no longer found.');
// Delete French.
$this->drupalPostForm('admin/config/regional/language/delete/fr', array(), t('Delete'));
// Make sure the "language_count" state has been updated correctly.
$this->rebuildContainer();
// We need raw here because %language and %langcode will add HTML.
$t_args = array('%language' => 'French', '%langcode' => 'fr');
$this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), 'The French language has been removed.');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE]));
// Verify that language is no longer found.
$this->drupalGet('admin/config/regional/language/delete/fr');
$this->assertResponse(404, 'Language no longer found.');
// Make sure the "language_count" state has not changed.
// Ensure we can delete the English language. Right now English is the only
// language so we must add a new language and make it the default before
// deleting English.
$langcode = 'xx';
$name = $this->randomMachineName(16);
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'label' => $name,
'direction' => Language::DIRECTION_LTR,
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE]));
$this->assertText($name, 'Name found.');
// Check if we can change the default language.
$path = 'admin/config/regional/language';
$this->drupalGet($path);
$this->assertFieldChecked('edit-site-default-language-en', 'English is the default language.');
// Change the default language.
$edit = array(
'site_default_language' => $langcode,
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$this->rebuildContainer();
$this->assertNoFieldChecked('edit-site-default-language-en', 'Default language updated.');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE, 'language' => $language]));
$this->drupalPostForm('admin/config/regional/language/delete/en', array(), t('Delete'));
// We need raw here because %language and %langcode will add HTML.
$t_args = array('%language' => 'English', '%langcode' => 'en');
$this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), 'The English language has been removed.');
$this->rebuildContainer();
// Ensure we can't delete a locked language.
$this->drupalGet('admin/config/regional/language/delete/und');
$this->assertResponse(403, 'Can not delete locked language');
// Ensure that NL cannot be set default when it's not available.
$this->drupalGet('admin/config/regional/language');
$extra_values = '&site_default_language=nl';
$this->drupalPostForm(NULL, array(), t('Save configuration'), array(), array(), NULL, $extra_values);
$this->assertText(t('Selected default language no longer exists.'));
$this->assertNoFieldChecked('edit-site-default-language-xx', 'The previous default language got deselected.');
}
/**
* Functional tests for the language states (locked or configurable).
*/
function testLanguageStates() {
// Add some languages, and also lock some of them.
ConfigurableLanguage::create(array('label' => $this->randomMachineName(), 'id' => 'l1'))->save();
ConfigurableLanguage::create(array('label' => $this->randomMachineName(), 'id' => 'l2', 'locked' => TRUE))->save();
ConfigurableLanguage::create(array('label' => $this->randomMachineName(), 'id' => 'l3'))->save();
ConfigurableLanguage::create(array('label' => $this->randomMachineName(), 'id' => 'l4', 'locked' => TRUE))->save();
$expected_locked_languages = array('l4' => 'l4', 'l2' => 'l2', 'und' => 'und', 'zxx' => 'zxx');
$expected_all_languages = array('l4' => 'l4', 'l3' => 'l3', 'l2' => 'l2', 'l1' => 'l1', 'en' => 'en', 'und' => 'und', 'zxx' => 'zxx');
$expected_conf_languages = array('l3' => 'l3', 'l1' => 'l1', 'en' => 'en');
$locked_languages = $this->container->get('language_manager')->getLanguages(LanguageInterface::STATE_LOCKED);
$this->assertEqual(array_diff_key($expected_locked_languages, $locked_languages), array(), 'Locked languages loaded correctly.');
$all_languages = $this->container->get('language_manager')->getLanguages(LanguageInterface::STATE_ALL);
$this->assertEqual(array_diff_key($expected_all_languages, $all_languages), array(), 'All languages loaded correctly.');
$conf_languages = $this->container->get('language_manager')->getLanguages();
$this->assertEqual(array_diff_key($expected_conf_languages, $conf_languages), array(), 'Configurable languages loaded correctly.');
}
}

View file

@ -0,0 +1,177 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageNegotiationInfoTest.
*/
namespace Drupal\language\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
use Drupal\simpletest\WebTestBase;
/**
* Tests alterations to language types/negotiation info.
*
* @group language
*/
class LanguageNegotiationInfoTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'view the administration theme'));
$this->drupalLogin($admin_user);
$this->drupalPostForm('admin/config/regional/language/add', array('predefined_langcode' => 'it'), t('Add language'));
}
/**
* Returns the configurable language manager.
*
* @return \Drupal\language\ConfigurableLanguageManager
*/
protected function languageManager() {
return $this->container->get('language_manager');
}
/**
* Sets state flags for language_test module.
*
* Ensures to correctly update data both in the child site and the test runner
* environment.
*
* @param array $values
* The key/value pairs to set in state.
*/
protected function stateSet(array $values) {
// Set the new state values.
$this->container->get('state')->setMultiple($values);
// Refresh in-memory static state/config caches and static variables.
$this->refreshVariables();
// Refresh/rewrite language negotiation configuration, in order to pick up
// the manipulations performed by language_test module's info alter hooks.
$this->container->get('language_negotiator')->purgeConfiguration();
}
/**
* Tests alterations to language types/negotiation info.
*/
function testInfoAlterations() {
$this->stateSet(array(
// Enable language_test type info.
'language_test.language_types' => TRUE,
// Enable language_test negotiation info (not altered yet).
'language_test.language_negotiation_info' => TRUE,
// Alter LanguageInterface::TYPE_CONTENT to be configurable.
'language_test.content_language_type' => TRUE,
));
$this->container->get('module_installer')->install(array('language_test'));
$this->resetAll();
// Check that fixed language types are properly configured without the need
// of saving the language negotiation settings.
$this->checkFixedLanguageTypes();
$type = LanguageInterface::TYPE_CONTENT;
$language_types = $this->languageManager()->getLanguageTypes();
$this->assertTrue(in_array($type, $language_types), 'Content language type is configurable.');
// Enable some core and custom language negotiation methods. The test
// language type is supposed to be configurable.
$test_type = 'test_language_type';
$interface_method_id = LanguageNegotiationUI::METHOD_ID;
$test_method_id = 'test_language_negotiation_method';
$form_field = $type . '[enabled]['. $interface_method_id .']';
$edit = array(
$form_field => TRUE,
$type . '[enabled][' . $test_method_id . ']' => TRUE,
$test_type . '[enabled][' . $test_method_id . ']' => TRUE,
$test_type . '[configurable]' => TRUE,
);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Alter language negotiation info to remove interface language negotiation
// method.
$this->stateSet(array(
'language_test.language_negotiation_info_alter' => TRUE,
));
$negotiation = $this->config('language.types')->get('negotiation.' . $type . '.enabled');
$this->assertFalse(isset($negotiation[$interface_method_id]), 'Interface language negotiation method removed from the stored settings.');
$this->drupalGet('admin/config/regional/language/detection');
$this->assertNoFieldByName($form_field, NULL, 'Interface language negotiation method unavailable.');
// Check that type-specific language negotiation methods can be assigned
// only to the corresponding language types.
foreach ($this->languageManager()->getLanguageTypes() as $type) {
$form_field = $type . '[enabled][test_language_negotiation_method_ts]';
if ($type == $test_type) {
$this->assertFieldByName($form_field, NULL, format_string('Type-specific test language negotiation method available for %type.', array('%type' => $type)));
}
else {
$this->assertNoFieldByName($form_field, NULL, format_string('Type-specific test language negotiation method unavailable for %type.', array('%type' => $type)));
}
}
// Check language negotiation results.
$this->drupalGet('');
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
foreach ($this->languageManager()->getDefinedLanguageTypes() as $type) {
$langcode = $last[$type];
$value = $type == LanguageInterface::TYPE_CONTENT || strpos($type, 'test') !== FALSE ? 'it' : 'en';
$this->assertEqual($langcode, $value, format_string('The negotiated language for %type is %language', array('%type' => $type, '%language' => $value)));
}
// Uninstall language_test and check that everything is set back to the
// original status.
$this->container->get('module_installer')->uninstall(array('language_test'));
$this->rebuildContainer();
// Check that only the core language types are available.
foreach ($this->languageManager()->getDefinedLanguageTypes() as $type) {
$this->assertTrue(strpos($type, 'test') === FALSE, format_string('The %type language is still available', array('%type' => $type)));
}
// Check that fixed language types are properly configured, even those
// previously set to configurable.
$this->checkFixedLanguageTypes();
// Check that unavailable language negotiation methods are not present in
// the negotiation settings.
$negotiation = $this->config('language.types')->get('negotiation.' . $type . '.enabled');
$this->assertFalse(isset($negotiation[$test_method_id]), 'The disabled test language negotiation method is not part of the content language negotiation settings.');
// Check that configuration page presents the correct options and settings.
$this->assertNoRaw(t('Test language detection'), 'No test language type configuration available.');
$this->assertNoRaw(t('This is a test language negotiation method'), 'No test language negotiation method available.');
}
/**
* Check that language negotiation for fixed types matches the stored one.
*/
protected function checkFixedLanguageTypes() {
$configurable = $this->languageManager()->getLanguageTypes();
foreach ($this->languageManager()->getDefinedLanguageTypesInfo() as $type => $info) {
if (!in_array($type, $configurable) && isset($info['fixed'])) {
$negotiation = $this->config('language.types')->get('negotiation.' . $type . '.enabled');
$equal = count($info['fixed']) == count($negotiation);
while ($equal && list($id) = each($negotiation)) {
list(, $info_id) = each($info['fixed']);
$equal = $info_id == $id;
}
$this->assertTrue($equal, format_string('language negotiation for %type is properly set up', array('%type' => $type)));
}
}
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguagePathMonolingualTest.
*/
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Confirm that paths are not changed on monolingual non-English sites.
*
* @group language
*/
class LanguagePathMonolingualTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'path');
protected function setUp() {
parent::setUp();
// Create and login user.
$web_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer site configuration'));
$this->drupalLogin($web_user);
// Enable French language.
$edit = array();
$edit['predefined_langcode'] = 'fr';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Make French the default language.
$edit = array(
'site_default_language' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language', $edit, t('Save configuration'));
// Delete English.
$this->drupalPostForm('admin/config/regional/language/delete/en', array(), t('Delete'));
// Changing the default language causes a container rebuild. Therefore need
// to rebuild the container in the test environment.
$this->rebuildContainer();
// Verify that French is the only language.
$this->container->get('language_manager')->reset();
$this->assertFalse(\Drupal::languageManager()->isMultilingual(), 'Site is mono-lingual');
$this->assertEqual(\Drupal::languageManager()->getDefaultLanguage()->getId(), 'fr', 'French is the default language');
// Set language detection to URL.
$edit = array('language_interface[enabled][language-url]' => TRUE);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
}
/**
* Verifies that links do not have language prefixes in them.
*/
function testPageLinks() {
// Navigate to 'admin/config' path.
$this->drupalGet('admin/config');
// Verify that links in this page do not have a 'fr/' prefix.
$this->assertNoLinkByHref('/fr/', 'Links do not contain language prefix');
// Verify that links in this page can be followed and work.
$this->clickLink(t('Languages'));
$this->assertResponse(200, 'Clicked link results in a valid page');
$this->assertText(t('Add language'), 'Page contains the add language text');
}
}

View file

@ -0,0 +1,93 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageSelectorTranslatableTest.
*/
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the content translation settings language selector options.
*
* @group language
*/
class LanguageSelectorTranslatableTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'language',
'content_translation',
'node',
'comment',
'field_ui',
'entity_test',
'locale',
);
/**
* The user with administrator privileges.
*
* @var \Drupal\user\Entity\User;
*/
public $administrator;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create user and set permissions.
$this->administrator = $this->drupalCreateUser($this->getAdministratorPermissions(), 'administrator');
$this->drupalLogin($this->administrator);
}
/**
* Returns an array of permissions needed for the translator.
*/
protected function getAdministratorPermissions() {
return array_filter(
array('translate interface',
'administer content translation',
'create content translations',
'update content translations',
'delete content translations',
'administer languages',
)
);
}
/**
* Tests content translation language selectors are correctly translated.
*/
public function testLanguageStringSelector() {
// Add another language.
$edit = array('predefined_langcode' => 'es');
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Translate the string English in Spanish (Inglés). Override config entity.
$name_translation = 'Inglés';
\Drupal::languageManager()
->getLanguageConfigOverride('es', 'language.entity.en')
->set('label', $name_translation)
->save();
// Check content translation overview selector.
$path = 'es/admin/config/regional/content-language';
$this->drupalGet($path);
// Get en language from selector.
$elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => 'edit-settings-node-node-settings-language-langcode', ':option' => 'en'));
// Check that the language text is translated.
$this->assertEqual((string) $elements[0], $name_translation, 'Checking the option string English is translated to Spanish.');
}
}

View file

@ -0,0 +1,408 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageSwitchingTest.
*/
namespace Drupal\language\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\WebTestBase;
/**
* Functional tests for the language switching feature.
*
* @group language
*/
class LanguageSwitchingTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('locale', 'language', 'block', 'language_test');
protected function setUp() {
parent::setUp();
// Create and login user.
$admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
}
/**
* Functional tests for the language switcher block.
*/
function testLanguageBlock() {
// Add language.
$edit = array(
'predefined_langcode' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Set the native language name.
$this->saveNativeLanguageName('fr', 'français');
// Enable URL language detection and selection.
$edit = array('language_interface[enabled][language-url]' => '1');
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Enable the language switching block.
$block = $this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, array(
'id' => 'test_language_block',
// Ensure a 2-byte UTF-8 sequence is in the tested output.
'label' => $this->randomMachineName(8) . '×',
));
$this->doTestLanguageBlockAuthenticated($block->label());
$this->doTestLanguageBlockAnonymous($block->label());
}
/**
* For authenticated users, the "active" class is set by JavaScript.
*
* @param string $block_label
* The label of the language switching block.
*
* @see testLanguageBlock()
*/
protected function doTestLanguageBlockAuthenticated($block_label) {
// Assert that the language switching block is displayed on the frontpage.
$this->drupalGet('');
$this->assertText($block_label, 'Language switcher block found.');
// Assert that each list item and anchor element has the appropriate data-
// attributes.
list($language_switcher) = $this->xpath('//div[@id=:id]', array(':id' => 'block-test-language-block'));
$list_items = array();
$anchors = array();
$labels = array();
foreach ($language_switcher->ul->li as $list_item) {
$classes = explode(" ", (string) $list_item['class']);
list($langcode) = array_intersect($classes, array('en', 'fr'));
$list_items[] = array(
'langcode_class' => $langcode,
'data-drupal-link-system-path' => (string) $list_item['data-drupal-link-system-path'],
);
$anchors[] = array(
'hreflang' => (string) $list_item->a['hreflang'],
'data-drupal-link-system-path' => (string) $list_item->a['data-drupal-link-system-path'],
);
$labels[] = (string) $list_item->a;
}
$expected_list_items = array(
0 => array('langcode_class' => 'en', 'data-drupal-link-system-path' => 'user/2'),
1 => array('langcode_class' => 'fr', 'data-drupal-link-system-path' => 'user/2'),
);
$this->assertIdentical($list_items, $expected_list_items, 'The list items have the correct attributes that will allow the drupal.active-link library to mark them as active.');
$expected_anchors = array(
0 => array('hreflang' => 'en', 'data-drupal-link-system-path' => 'user/2'),
1 => array('hreflang' => 'fr', 'data-drupal-link-system-path' => 'user/2'),
);
$this->assertIdentical($anchors, $expected_anchors, 'The anchors have the correct attributes that will allow the drupal.active-link library to mark them as active.');
$settings = $this->getDrupalSettings();
$this->assertIdentical($settings['path']['currentPath'], 'user/2', 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertIdentical($settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertIdentical($labels, array('English', 'français'), 'The language links labels are in their own language on the language switcher block.');
}
/**
* For anonymous users, the "active" class is set by PHP.
*
* @param string $block_label
* The label of the language switching block.
*
* @see testLanguageBlock()
*/
protected function doTestLanguageBlockAnonymous($block_label) {
$this->drupalLogout();
// Assert that the language switching block is displayed on the frontpage.
$this->drupalGet('');
$this->assertText($block_label, 'Language switcher block found.');
// Assert that only the current language is marked as active.
list($language_switcher) = $this->xpath('//div[@id=:id]', array(':id' => 'block-test-language-block'));
$links = array(
'active' => array(),
'inactive' => array(),
);
$anchors = array(
'active' => array(),
'inactive' => array(),
);
$labels = array();
foreach ($language_switcher->ul->li as $link) {
$classes = explode(" ", (string) $link['class']);
list($langcode) = array_intersect($classes, array('en', 'fr'));
if (in_array('is-active', $classes)) {
$links['active'][] = $langcode;
}
else {
$links['inactive'][] = $langcode;
}
$anchor_classes = explode(" ", (string) $link->a['class']);
if (in_array('is-active', $anchor_classes)) {
$anchors['active'][] = $langcode;
}
else {
$anchors['inactive'][] = $langcode;
}
$labels[] = (string) $link->a;
}
$this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language list item is marked as active on the language switcher block.');
$this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language anchor is marked as active on the language switcher block.');
$this->assertIdentical($labels, array('English', 'français'), 'The language links labels are in their own language on the language switcher block.');
}
/**
* Test language switcher links for domain based negotiation.
*/
function testLanguageBlockWithDomain() {
// Add the Italian language.
ConfigurableLanguage::createFromLangcode('it')->save();
// Rebuild the container so that the new language is picked up by services
// that hold a list of languages.
$this->rebuildContainer();
$languages = $this->container->get('language_manager')->getLanguages();
// Enable browser and URL language detection.
$edit = array(
'language_interface[enabled][language-url]' => TRUE,
'language_interface[weight][language-url]' => -10,
);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Do not allow blank domain.
$edit = array(
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => '',
);
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
$this->assertText(t('The domain may not be left blank for English'), 'The form does not allow blank domains.');
// Change the domain for the Italian language.
$edit = array(
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => \Drupal::request()->getHost(),
'domain[it]' => 'it.example.com',
);
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved'), 'Domain configuration is saved.');
// Enable the language switcher block.
$this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, array('id' => 'test_language_block'));
$this->drupalGet('');
/** @var \Drupal\Core\Routing\UrlGenerator $generator */
$generator = $this->container->get('url_generator');
// Verfify the English URL is correct
list($english_link) = $this->xpath('//div[@id=:id]/ul/li/a[@hreflang=:hreflang]', array(
':id' => 'block-test-language-block',
':hreflang' => 'en',
));
$english_url = $generator->generateFromPath('user/2', array('language' => $languages['en']));
$this->assertEqual($english_url, (string) $english_link['href']);
// Verfify the Italian URL is correct
list($italian_link) = $this->xpath('//div[@id=:id]/ul/li/a[@hreflang=:hreflang]', array(
':id' => 'block-test-language-block',
':hreflang' => 'it',
));
$italian_url = $generator->generateFromPath('user/2', array('language' => $languages['it']));
$this->assertEqual($italian_url, (string) $italian_link['href']);
}
/**
* Test active class on links when switching languages.
*/
function testLanguageLinkActiveClass() {
// Add language.
$edit = array(
'predefined_langcode' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Enable URL language detection and selection.
$edit = array('language_interface[enabled][language-url]' => '1');
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
$this->doTestLanguageLinkActiveClassAuthenticated();
$this->doTestLanguageLinkActiveClassAnonymous();
}
/**
* Check the path-admin class, as same as on default language.
*/
function testLanguageBodyClass() {
$searched_class = 'path-admin';
// Add language.
$edit = array(
'predefined_langcode' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Enable URL language detection and selection.
$edit = array('language_interface[enabled][language-url]' => '1');
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Check if the default (English) admin/config page has the right class.
$this->drupalGet('admin/config');
$class = $this->xpath('//body[contains(@class, :class)]', array(':class' => $searched_class));
$this->assertTrue(isset($class[0]), t('The path-admin class appears on default language.'));
// Check if the French admin/config page has the right class.
$this->drupalGet('fr/admin/config');
$class = $this->xpath('//body[contains(@class, :class)]', array(':class' => $searched_class));
$this->assertTrue(isset($class[0]), t('The path-admin class same as on default language.'));
// The testing profile sets the user/login page as the frontpage. That
// redirects authenticated users to their profile page, so check with an
// anonymous user instead.
$this->drupalLogout();
// Check if the default (English) frontpage has the right class.
$this->drupalGet('<front>');
$class = $this->xpath('//body[contains(@class, :class)]', array(':class' => 'path-frontpage'));
$this->assertTrue(isset($class[0]), 'path-frontpage class found on the body tag');
// Check if the French frontpage has the right class.
$this->drupalGet('fr');
$class = $this->xpath('//body[contains(@class, :class)]', array(':class' => 'path-frontpage'));
$this->assertTrue(isset($class[0]), 'path-frontpage class found on the body tag with french as the active language');
}
/**
* For authenticated users, the "active" class is set by JavaScript.
*
* @see testLanguageLinkActiveClass()
*/
protected function doTestLanguageLinkActiveClassAuthenticated() {
$function_name = '#type link';
$path = 'language_test/type-link-active-class';
// Test links generated by _l() on an English page.
$current_language = 'English';
$this->drupalGet($path);
// Language code 'none' link should be active.
$langcode = 'none';
$links = $this->xpath('//a[@id = :id and @data-drupal-link-system-path = :path]', array(':id' => 'no_lang_link', ':path' => $path));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Language code 'en' link should be active.
$langcode = 'en';
$links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'en_link', ':lang' => 'en', ':path' => $path));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Language code 'fr' link should not be active.
$langcode = 'fr';
$links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'fr_link', ':lang' => 'fr', ':path' => $path));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to NOT mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Verify that drupalSettings contains the correct values.
$settings = $this->getDrupalSettings();
$this->assertIdentical($settings['path']['currentPath'], $path, 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertIdentical($settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
// Test links generated by _l() on a French page.
$current_language = 'French';
$this->drupalGet('fr/language_test/type-link-active-class');
// Language code 'none' link should be active.
$langcode = 'none';
$links = $this->xpath('//a[@id = :id and @data-drupal-link-system-path = :path]', array(':id' => 'no_lang_link', ':path' => $path));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Language code 'en' link should not be active.
$langcode = 'en';
$links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'en_link', ':lang' => 'en', ':path' => $path));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to NOT mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Language code 'fr' link should be active.
$langcode = 'fr';
$links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'fr_link', ':lang' => 'fr', ':path' => $path));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Verify that drupalSettings contains the correct values.
$settings = $this->getDrupalSettings();
$this->assertIdentical($settings['path']['currentPath'], $path, 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
$this->assertIdentical($settings['path']['currentLanguage'], 'fr', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
}
/**
* For anonymous users, the "active" class is set by PHP.
*
* @see testLanguageLinkActiveClass()
*/
protected function doTestLanguageLinkActiveClassAnonymous() {
$function_name = '#type link';
$this->drupalLogout();
// Test links generated by _l() on an English page.
$current_language = 'English';
$this->drupalGet('language_test/type-link-active-class');
// Language code 'none' link should be active.
$langcode = 'none';
$links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'no_lang_link', ':class' => 'is-active'));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Language code 'en' link should be active.
$langcode = 'en';
$links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'en_link', ':class' => 'is-active'));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Language code 'fr' link should not be active.
$langcode = 'fr';
$links = $this->xpath('//a[@id = :id and not(contains(@class, :class))]', array(':id' => 'fr_link', ':class' => 'is-active'));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is NOT marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Test links generated by _l() on a French page.
$current_language = 'French';
$this->drupalGet('fr/language_test/type-link-active-class');
// Language code 'none' link should be active.
$langcode = 'none';
$links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'no_lang_link', ':class' => 'is-active'));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Language code 'en' link should not be active.
$langcode = 'en';
$links = $this->xpath('//a[@id = :id and not(contains(@class, :class))]', array(':id' => 'en_link', ':class' => 'is-active'));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is NOT marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
// Language code 'fr' link should be active.
$langcode = 'fr';
$links = $this->xpath('//a[@id = :id and contains(@class, :class)]', array(':id' => 'fr_link', ':class' => 'is-active'));
$this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
}
/**
* Saves the native name of a language entity in configuration as a label.
*
* @param string $langcode
* The language code of the language.
* @param string $label
* The native name of the language.
*/
protected function saveNativeLanguageName($langcode, $label) {
\Drupal::service('language.config_factory_override')
->getOverride($langcode, 'language.entity.' . $langcode)->set('label', $label)->save();
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageTestBase.
*/
namespace Drupal\language\Tests;
use Drupal\simpletest\KernelTestBase;
/**
* Test for dependency injected language object.
*/
abstract class LanguageTestBase extends KernelTestBase {
public static $modules = array('system', 'language', 'language_test');
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The state storage service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('language'));
$this->state = $this->container->get('state');
// Ensure we are building a new Language object for each test.
$this->languageManager = $this->container->get('language_manager');
$this->languageManager->reset();
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageTourTest.
*/
namespace Drupal\language\Tests;
use Drupal\tour\Tests\TourTestBase;
/**
* Tests tour functionality.
*
* @group tour
*/
class LanguageTourTest extends TourTestBase {
/**
* An admin user with administrative permissions for views.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'tour');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(array('administer languages', 'access tour'));
$this->drupalLogin($this->adminUser);
}
/**
* Tests language tour tip availability.
*/
public function testLanguageTour() {
$this->drupalGet('admin/config/regional/language');
$this->assertTourTips();
}
/**
* Go to add language page and check the tour tooltips.
*/
public function testLanguageAddTour() {
$this->drupalGet('admin/config/regional/language/add');
$this->assertTourTips();
}
/**
* Go to edit language page and check the tour tooltips.
*/
public function testLanguageEditTour() {
$this->drupalGet('admin/config/regional/language/edit/en');
$this->assertTourTips();
}
}

View file

@ -0,0 +1,532 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageUILanguageNegotiationTest.
*/
namespace Drupal\language\Tests;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSession;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUser;
use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\language\LanguageNegotiatorInterface;
use Drupal\block\Entity\Block;
/**
* Tests the language UI for language switching.
*
* The uses cases that get tested, are:
* - URL (path) > default: Test that the URL prefix setting gets precedence over
* the default language. The browser language preference does not have any
* influence.
* - URL (path) > browser > default: Test that the URL prefix setting gets
* precedence over the browser language preference, which in turn gets
* precedence over the default language.
* - URL (domain) > default: Tests that the URL domain setting gets precedence
* over the default language.
*
* The paths that are used for each of these, are:
* - admin/config: Tests the UI using the precedence rules.
* - zh-hans/admin/config: Tests the UI in Chinese.
* - blah-blah/admin/config: Tests the 404 page.
*
* @group language
*/
class LanguageUILanguageNegotiationTest extends WebTestBase {
/**
* Modules to enable.
*
* We marginally use interface translation functionality here, so need to use
* the locale module instead of language only, but the 90% of the test is
* about the negotiation process which is solely in language module.
*
* @var array
*/
public static $modules = array('locale', 'language_test', 'block', 'user', 'content_translation');
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages', 'administer blocks'));
$this->drupalLogin($admin_user);
}
/**
* Tests for language switching by URL path.
*/
function testUILanguageNegotiation() {
// A few languages to switch to.
// This one is unknown, should get the default lang version.
$langcode_unknown = 'blah-blah';
// For testing browser lang preference.
$langcode_browser_fallback = 'vi';
// For testing path prefix.
$langcode = 'zh-hans';
// For setting browser language preference to 'vi'.
$http_header_browser_fallback = array("Accept-Language: $langcode_browser_fallback;q=1");
// For setting browser language preference to some unknown.
$http_header_blah = array("Accept-Language: blah;q=1");
// Setup the site languages by installing two languages.
// Set the default language in order for the translated string to be registered
// into database when seen by t(). Without doing this, our target string
// is for some reason not found when doing translate search. This might
// be some bug.
$default_language = \Drupal::languageManager()->getDefaultLanguage();
ConfigurableLanguage::createFromLangcode($langcode_browser_fallback)->save();
$this->config('system.site')->set('default_langcode', $langcode_browser_fallback)->save();
ConfigurableLanguage::createFromLangcode($langcode)->save();
// We will look for this string in the admin/config screen to see if the
// corresponding translated string is shown.
$default_string = 'Hide descriptions';
// First visit this page to make sure our target string is searchable.
$this->drupalGet('admin/config');
// Now the t()'ed string is in db so switch the language back to default.
// This will rebuild the container so we need to rebuild the container in
// the test environment.
$this->config('system.site')->set('default_langcode', $default_language->getId())->save();
$this->config('language.negotiation')->set('url.prefixes.en', '')->save();
$this->rebuildContainer();
// Translate the string.
$language_browser_fallback_string = "In $langcode_browser_fallback In $langcode_browser_fallback In $langcode_browser_fallback";
$language_string = "In $langcode In $langcode In $langcode";
// Do a translate search of our target string.
$search = array(
'string' => $default_string,
'langcode' => $langcode_browser_fallback,
);
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
$textarea = current($this->xpath('//textarea'));
$lid = (string) $textarea[0]['name'];
$edit = array(
$lid => $language_browser_fallback_string,
);
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
$search = array(
'string' => $default_string,
'langcode' => $langcode,
);
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
$textarea = current($this->xpath('//textarea'));
$lid = (string) $textarea[0]['name'];
$edit = array(
$lid => $language_string,
);
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
// Configure selected language negotiation to use zh-hans.
$edit = array('selected_langcode' => $langcode);
$this->drupalPostForm('admin/config/regional/language/detection/selected', $edit, t('Save configuration'));
$test = array(
'language_negotiation' => array(LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationSelected::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SELECTED: UI language is switched based on selected language.',
);
$this->runTest($test);
// An invalid language is selected.
$this->config('language.negotiation')->set('selected_langcode', NULL)->save();
$test = array(
'language_negotiation' => array(LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SELECTED > DEFAULT: UI language is switched based on selected language.',
);
$this->runTest($test);
// No selected language is available.
$this->config('language.negotiation')->set('selected_langcode', $langcode_unknown)->save();
$test = array(
'language_negotiation' => array(LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SELECTED > DEFAULT: UI language is switched based on selected language.',
);
$this->runTest($test);
$tests = array(
// Default, browser preference should have no influence.
array(
'language_negotiation' => array(LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.',
),
// Language prefix.
array(
'language_negotiation' => array(LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => "$langcode/admin/config",
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationUrl::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix',
),
// Default, go by browser preference.
array(
'language_negotiation' => array(LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationBrowser::METHOD_ID),
'path' => 'admin/config',
'expect' => $language_browser_fallback_string,
'expected_method_id' => LanguageNegotiationBrowser::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'URL (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference',
),
// Prefix, switch to the language.
array(
'language_negotiation' => array(LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationBrowser::METHOD_ID),
'path' => "$langcode/admin/config",
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationUrl::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'URL (PATH) > BROWSER: with language prefix, UI language is based on path prefix',
),
// Default, browser language preference is not one of site's lang.
array(
'language_negotiation' => array(LanguageNegotiationUrl::METHOD_ID, LanguageNegotiationBrowser::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_blah,
'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language',
),
);
foreach ($tests as $test) {
$this->runTest($test);
}
// Unknown language prefix should return 404.
$definitions = \Drupal::languageManager()->getNegotiator()->getNegotiationMethods();
$this->config('language.types')
->set('negotiation.' . LanguageInterface::TYPE_INTERFACE . '.enabled', array_flip(array_keys($definitions)))
->save();
$this->drupalGet("$langcode_unknown/admin/config", array(), $http_header_browser_fallback);
$this->assertResponse(404, "Unknown language path prefix should return 404");
// Set preferred langcode for user to NULL.
$account = $this->loggedInUser;
$account->preferred_langcode = NULL;
$account->save();
$test = array(
'language_negotiation' => array(LanguageNegotiationUser::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => array(),
'message' => 'USER > DEFAULT: no preferred user language setting, the UI language is default',
);
$this->runTest($test);
// Set preferred langcode for user to unknown language.
$account = $this->loggedInUser;
$account->preferred_langcode = $langcode_unknown;
$account->save();
$test = array(
'language_negotiation' => array(LanguageNegotiationUser::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => array(),
'message' => 'USER > DEFAULT: invalid preferred user language setting, the UI language is default',
);
$this->runTest($test);
// Set preferred langcode for user to non default.
$account->preferred_langcode = $langcode;
$account->save();
$test = array(
'language_negotiation' => array(LanguageNegotiationUser::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationUser::METHOD_ID,
'http_header' => array(),
'message' => 'USER > DEFAULT: defined preferred user language setting, the UI language is based on user setting',
);
$this->runTest($test);
// Set preferred admin langcode for user to NULL.
$account->preferred_admin_langcode = NULL;
$account->save();
$test = array(
'language_negotiation' => array(LanguageNegotiationUserAdmin::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => array(),
'message' => 'USER ADMIN > DEFAULT: no preferred user admin language setting, the UI language is default',
);
$this->runTest($test);
// Set preferred admin langcode for user to unknown language.
$account->preferred_admin_langcode = $langcode_unknown;
$account->save();
$test = array(
'language_negotiation' => array(LanguageNegotiationUserAdmin::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => array(),
'message' => 'USER ADMIN > DEFAULT: invalid preferred user admin language setting, the UI language is default',
);
$this->runTest($test);
// Set preferred admin langcode for user to non default.
$account->preferred_admin_langcode = $langcode;
$account->save();
$test = array(
'language_negotiation' => array(LanguageNegotiationUserAdmin::METHOD_ID, LanguageNegotiationSelected::METHOD_ID),
'path' => 'admin/config',
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationUserAdmin::METHOD_ID,
'http_header' => array(),
'message' => 'USER ADMIN > DEFAULT: defined preferred user admin language setting, the UI language is based on user setting',
);
$this->runTest($test);
// Go by session preference.
$language_negotiation_session_param = $this->randomMachineName();
$edit = array('language_negotiation_session_param' => $language_negotiation_session_param);
$this->drupalPostForm('admin/config/regional/language/detection/session', $edit, t('Save configuration'));
$tests = array(
array(
'language_negotiation' => array(LanguageNegotiationSession::METHOD_ID),
'path' => "admin/config",
'expect' => $default_string,
'expected_method_id' => LanguageNegotiatorInterface::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SESSION > DEFAULT: no language given, the UI language is default',
),
array(
'language_negotiation' => array(LanguageNegotiationSession::METHOD_ID),
'path' => 'admin/config',
'path_options' => ['query' => [$language_negotiation_session_param => $langcode]],
'expect' => $language_string,
'expected_method_id' => LanguageNegotiationSession::METHOD_ID,
'http_header' => $http_header_browser_fallback,
'message' => 'SESSION > DEFAULT: language given, UI language is determined by session language preference',
),
);
foreach ($tests as $test) {
$this->runTest($test);
}
}
protected function runTest($test) {
$test += array('path_options' => []);
if (!empty($test['language_negotiation'])) {
$method_weights = array_flip($test['language_negotiation']);
$this->container->get('language_negotiator')->saveConfiguration(LanguageInterface::TYPE_INTERFACE, $method_weights);
}
if (!empty($test['language_negotiation_url_part'])) {
$this->config('language.negotiation')
->set('url.source', $test['language_negotiation_url_part'])
->save();
}
if (!empty($test['language_test_domain'])) {
\Drupal::state()->set('language_test.domain', $test['language_test_domain']);
}
$this->container->get('language_manager')->reset();
$this->drupalGet($test['path'], $test['path_options'], $test['http_header']);
$this->assertText($test['expect'], $test['message']);
$this->assertText(t('Language negotiation method: @name', array('@name' => $test['expected_method_id'])));
}
/**
* Test URL language detection when the requested URL has no language.
*/
function testUrlLanguageFallback() {
// Add the Italian language.
$langcode_browser_fallback = 'it';
ConfigurableLanguage::createFromLangcode($langcode_browser_fallback)->save();
$languages = $this->container->get('language_manager')->getLanguages();
// Enable the path prefix for the default language: this way any unprefixed
// URL must have a valid fallback value.
$edit = array('prefix[en]' => 'en');
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
// Enable browser and URL language detection.
$edit = array(
'language_interface[enabled][language-browser]' => TRUE,
'language_interface[enabled][language-url]' => TRUE,
'language_interface[weight][language-browser]' => -8,
'language_interface[weight][language-url]' => -10,
);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
$this->drupalGet('admin/config/regional/language/detection');
// Enable the language switcher block.
$this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE, array('id' => 'test_language_block'));
// Log out, because for anonymous users, the "active" class is set by PHP
// (which means we can easily test it here), whereas for authenticated users
// it is set by JavaScript.
$this->drupalLogout();
// Access the front page without specifying any valid URL language prefix
// and having as browser language preference a non-default language.
$http_header = array("Accept-Language: $langcode_browser_fallback;q=1");
$language = new Language(array('id' => ''));
$this->drupalGet('', array('language' => $language), $http_header);
// Check that the language switcher active link matches the given browser
// language.
$args = array(':id' => 'block-test-language-block', ':url' => \Drupal::url('<front>') . $langcode_browser_fallback);
$fields = $this->xpath('//div[@id=:id]//a[@class="language-link is-active" and starts-with(@href, :url)]', $args);
$this->assertTrue($fields[0] == $languages[$langcode_browser_fallback]->getName(), 'The browser language is the URL active language');
// Check that URLs are rewritten using the given browser language.
$fields = $this->xpath('//strong[@class="site-name"]/a[@rel="home" and @href=:url]', $args);
$this->assertTrue($fields[0] == 'Drupal', 'URLs are rewritten using the browser language.');
}
/**
* Tests URL handling when separate domains are used for multiple languages.
*/
function testLanguageDomain() {
global $base_url;
// Get the current host URI we're running on.
$base_url_host = parse_url($base_url, PHP_URL_HOST);
// Add the Italian language.
ConfigurableLanguage::createFromLangcode('it')->save();
$languages = $this->container->get('language_manager')->getLanguages();
// Enable browser and URL language detection.
$edit = array(
'language_interface[enabled][language-url]' => TRUE,
'language_interface[weight][language-url]' => -10,
);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Do not allow blank domain.
$edit = array(
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => '',
);
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
$this->assertText('The domain may not be left blank for English', 'The form does not allow blank domains.');
$this->rebuildContainer();
// Change the domain for the Italian language.
$edit = array(
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => $base_url_host,
'domain[it]' => 'it.example.com',
);
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
$this->assertText('The configuration options have been saved', 'Domain configuration is saved.');
$this->rebuildContainer();
// Try to use an invalid domain.
$edit = [
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => $base_url_host,
'domain[it]' => 'it.example.com/',
];
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
$this->assertRaw(t('The domain for %language may only contain the domain name, not a trailing slash, protocol and/or port.', ['%language' => 'Italian']));
// Build the link we're going to test.
$link = 'it.example.com' . rtrim(base_path(), '/') . '/admin';
// Test URL in another language: http://it.example.com/admin.
// Base path gives problems on the testbot, so $correct_link is hard-coded.
// @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test).
$italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString();
$url_scheme = \Drupal::request()->isSecure() ? 'https://' : 'http://';
$correct_link = $url_scheme . $link;
$this->assertEqual($italian_url, $correct_link, format_string('The right URL (@url) in accordance with the chosen language', array('@url' => $italian_url)));
// Test HTTPS via options.
$italian_url = Url::fromRoute('system.admin', [], ['https' => TRUE, 'language' => $languages['it']])->toString();
$correct_link = 'https://' . $link;
$this->assertTrue($italian_url == $correct_link, format_string('The right HTTPS URL (via options) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
// Test HTTPS via current URL scheme.
$request = Request::create('', 'GET', array(), array(), array(), array('HTTPS' => 'on'));
$this->container->get('request_stack')->push($request);
$italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString();
$correct_link = 'https://' . $link;
$this->assertTrue($italian_url == $correct_link, format_string('The right URL (via current URL scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
}
/**
* Tests persistence of negotiation settings for the content language type.
*/
public function testContentCustomization() {
// Customize content language settings from their defaults.
$edit = array(
'language_content[configurable]' => TRUE,
'language_content[enabled][language-url]' => FALSE,
'language_content[enabled][language-session]' => TRUE,
);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Check if configurability persisted.
$config = $this->config('language.types');
$this->assertTrue(in_array('language_interface', $config->get('configurable')), 'Interface language is configurable.');
$this->assertTrue(in_array('language_content', $config->get('configurable')), 'Content language is configurable.');
// Ensure configuration was saved.
$this->assertFalse(array_key_exists('language-url', $config->get('negotiation.language_content.enabled')), 'URL negotiation is not enabled for content.');
$this->assertTrue(array_key_exists('language-session', $config->get('negotiation.language_content.enabled')), 'Session negotiation is enabled for content.');
}
/**
* Tests if the language switcher block gets deleted when a language type has been made not configurable.
*/
public function testDisableLanguageSwitcher() {
$block_id = 'test_language_block';
// Enable the language switcher block.
$this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_CONTENT, array('id' => $block_id));
// Check if the language switcher block has been created.
$block = Block::load($block_id);
$this->assertTrue($block, 'Language switcher block was created.');
// Make sure language_content is not configurable.
$edit = array(
'language_content[configurable]' => FALSE,
);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
$this->assertResponse(200);
// Check if the language switcher block has been removed.
$block = Block::load($block_id);
$this->assertFalse($block, 'Language switcher block was removed.');
}
}

View file

@ -0,0 +1,164 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageUrlRewritingTest.
*/
namespace Drupal\language\Tests;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\simpletest\WebTestBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests that URL rewriting works as expected.
*
* @group language
*/
class LanguageUrlRewritingTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'language_test');
/**
* An user with permissions to administer languages.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
// Create and login user.
$this->webUser = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($this->webUser);
// Install French language.
$edit = array();
$edit['predefined_langcode'] = 'fr';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Enable URL language detection and selection.
$edit = array('language_interface[enabled][language-url]' => 1);
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Check that drupalSettings contains path prefix.
$this->drupalGet('fr/admin/config/regional/language/detection');
$this->assertRaw('"pathPrefix":"fr\/"', 'drupalSettings path prefix contains language code.');
}
/**
* Check that non-installed languages are not considered.
*/
function testUrlRewritingEdgeCases() {
// Check URL rewriting with a non-installed language.
$non_existing = new Language(array('id' => $this->randomMachineName()));
$this->checkUrl($non_existing, 'Path language is ignored if language is not installed.', 'URL language negotiation does not work with non-installed languages');
// Check that URL rewriting is not applied to subrequests.
$this->drupalGet('language_test/subrequest');
$this->assertText($this->webUser->getUsername(), 'Page correctly retrieved');
}
/**
* Check URL rewriting for the given language.
*
* The test is performed with a fixed URL (the default front page) to simply
* check that language prefixes are not added to it and that the prefixed URL
* is actually not working.
*
* @param \Drupal\Core\Language\LanguageInterface $language
* The language object.
* @param string $message1
* Message to display in assertion that language prefixes are not added.
* @param string $message2
* The message to display confirming prefixed URL is not working.
*/
private function checkUrl(LanguageInterface $language, $message1, $message2) {
$options = array('language' => $language, 'script' => '');
$base_path = trim(base_path(), '/');
$rewritten_path = trim(str_replace($base_path, '', \Drupal::url('<front>', array(), $options)), '/');
$segments = explode('/', $rewritten_path, 2);
$prefix = $segments[0];
$path = isset($segments[1]) ? $segments[1] : $prefix;
// If the rewritten URL has not a language prefix we pick a random prefix so
// we can always check the prefixed URL.
$prefixes = language_negotiation_url_prefixes();
$stored_prefix = isset($prefixes[$language->getId()]) ? $prefixes[$language->getId()] : $this->randomMachineName();
if ($this->assertNotEqual($stored_prefix, $prefix, $message1)) {
$prefix = $stored_prefix;
}
$this->drupalGet("$prefix/$path");
$this->assertResponse(404, $message2);
}
/**
* Check URL rewriting when using a domain name and a non-standard port.
*/
function testDomainNameNegotiationPort() {
global $base_url;
$language_domain = 'example.fr';
// Get the current host URI we're running on.
$base_url_host = parse_url($base_url, PHP_URL_HOST);
$edit = array(
'language_negotiation_url_part' => LanguageNegotiationUrl::CONFIG_DOMAIN,
'domain[en]' => $base_url_host,
'domain[fr]' => $language_domain
);
$this->drupalPostForm('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
// Rebuild the container so that the new language gets picked up by services
// that hold the list of languages.
$this->rebuildContainer();
// Enable domain configuration.
$this->config('language.negotiation')
->set('url.source', LanguageNegotiationUrl::CONFIG_DOMAIN)
->save();
// Reset static caching.
$this->container->get('language_manager')->reset();
// In case index.php is part of the URLs, we need to adapt the asserted
// URLs as well.
$index_php = strpos(\Drupal::url('<front>', array(), array('absolute' => TRUE)), 'index.php') !== FALSE;
$request = Request::createFromGlobals();
$server = $request->server->all();
$request = $this->prepareRequestForGenerator(TRUE, array('HTTP_HOST' => $server['HTTP_HOST'] . ':88'));
// Create an absolute French link.
$language = \Drupal::languageManager()->getLanguage('fr');
$url = Url::fromRoute('<none>', [], [
'absolute' => TRUE,
'language' => $language,
])->toString();
$expected = ($index_php ? 'http://example.fr:88/index.php' : 'http://example.fr:88') . rtrim(base_path(), '/') . '/';
$this->assertEqual($url, $expected, 'The right port is used.');
// If we set the port explicitly, it should not be overridden.
$url = Url::fromRoute('<none>', [], [
'absolute' => TRUE,
'language' => $language,
'base_url' => $request->getBaseUrl() . ':90',
])->toString();
$expected = $index_php ? 'http://example.fr:90/index.php' : 'http://example.fr:90' . rtrim(base_path(), '/') . '/';
$this->assertEqual($url, $expected, 'A given port is not overridden.');
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\Views\ArgumentLanguageTest.
*/
namespace Drupal\language\Tests\Views;
use Drupal\views\Views;
/**
* Tests the argument language handler.
*
* @group language
* @see \Drupal\language\Plugin\views\argument\Language.php
*/
class ArgumentLanguageTest extends LanguageTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_view');
/**
* Tests the language argument.
*/
public function testArgument() {
$view = Views::getView('test_view');
foreach (array('en' => 'John', 'xx-lolspeak' => 'George') as $langcode => $name) {
$view->setDisplay();
$view->displayHandlers->get('default')->overrideOption('arguments', array(
'langcode' => array(
'id' => 'langcode',
'table' => 'views_test_data',
'field' => 'langcode',
),
));
$this->executeView($view, array($langcode));
$expected = array(array(
'name' => $name,
));
$this->assertIdenticalResultset($view, $expected, array('views_test_data_name' => 'name'));
$view->destroy();
}
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\Views\FieldLanguageTest.
*/
namespace Drupal\language\Tests\Views;
use Drupal\views\Views;
/**
* Tests the field language handler.
*
* @group language
* @see \Drupal\language\Plugin\views\field\Language
*/
class FieldLanguageTest extends LanguageTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_view');
/**
* Tests the language field.
*/
public function testField() {
$view = Views::getView('test_view');
$view->setDisplay();
$view->displayHandlers->get('default')->overrideOption('fields', array(
'langcode' => array(
'id' => 'langcode',
'table' => 'views_test_data',
'field' => 'langcode',
),
));
$this->executeView($view);
$this->assertEqual($view->field['langcode']->advancedRender($view->result[0]), 'English');
$this->assertEqual($view->field['langcode']->advancedRender($view->result[1]), 'Lolspeak');
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\Views\FilterLanguageTest.
*/
namespace Drupal\language\Tests\Views;
use Drupal\views\Views;
/**
* Tests the filter language handler.
*
* @group language
* @see \Drupal\language\Plugin\views\filter\Language
*/
class FilterLanguageTest extends LanguageTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_view');
/**
* Tests the language filter.
*/
public function testFilter() {
$view = Views::getView('test_view');
foreach (array('en' => 'John', 'xx-lolspeak' => 'George') as $langcode => $name) {
$view->setDisplay();
$view->displayHandlers->get('default')->overrideOption('filters', array(
'langcode' => array(
'id' => 'langcode',
'table' => 'views_test_data',
'field' => 'langcode',
'value' => array($langcode),
),
));
$this->executeView($view);
$expected = array(array(
'name' => $name,
));
$this->assertIdenticalResultset($view, $expected, array('views_test_data_name' => 'name'));
$view->destroy();
}
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\Views\LanguageTestBase.
*/
namespace Drupal\language\Tests\Views;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\views\Tests\ViewUnitTestBase;
/**
* Defines the base class for all Language handler tests.
*/
abstract class LanguageTestBase extends ViewUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'language');
protected function setUp() {
parent::setUp();
$this->installConfig(array('language'));
// Create another language beside English.
ConfigurableLanguage::create(array('id' => 'xx-lolspeak', 'label' => 'Lolspeak'))->save();
}
/**
* Overrides \Drupal\views\Tests\ViewTestBase::schemaDefinition().
*/
protected function schemaDefinition() {
$schema = parent::schemaDefinition();
$schema['views_test_data']['fields']['langcode'] = array(
'description' => 'The {language}.langcode of this beatle.',
'type' => 'varchar',
'length' => 12,
'default' => '',
);
return $schema;
}
/**
* Overrides \Drupal\views\Tests\ViewTestBase::schemaDefinition().
*/
protected function viewsData() {
$data = parent::viewsData();
$data['views_test_data']['langcode'] = array(
'title' => t('Langcode'),
'help' => t('Langcode'),
'field' => array(
'id' => 'language',
),
'argument' => array(
'id' => 'language',
),
'filter' => array(
'id' => 'language',
),
);
return $data;
}
/**
* Overrides \Drupal\views\Tests\ViewTestBase::dataSet().
*/
protected function dataSet() {
$data = parent::dataSet();
$data[0]['langcode'] = 'en';
$data[1]['langcode'] = 'xx-lolspeak';
$data[2]['langcode'] = '';
$data[3]['langcode'] = '';
$data[4]['langcode'] = '';
return $data;
}
}