Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,93 @@
<?php
/**
* @file
* Hooks provided by the Configuration Translation module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Introduce dynamic translation tabs for translation of configuration.
*
* This hook augments MODULE.config_translation.yml as well as
* THEME.config_translation.yml files to collect dynamic translation mapper
* information. If your information is static, just provide such a YAML file
* with your module containing the mapping.
*
* Note that while themes can provide THEME.config_translation.yml files this
* hook is not invoked for themes.
*
* @param array $info
* An associative array of configuration mapper information. Use an entity
* name for the key (for entity mapping) or a unique string for configuration
* name list mapping. The values of the associative array are arrays
* themselves in the same structure as the *.config_translation.yml files.
*
* @see hook_config_translation_info_alter()
* @see \Drupal\config_translation\ConfigMapperManagerInterface
* @see \Drupal\config_translation\Routing\RouteSubscriber::routes()
*/
function hook_config_translation_info(&$info) {
$entity_manager = \Drupal::entityManager();
$route_provider = \Drupal::service('router.route_provider');
// If field UI is not enabled, the base routes of the type
// "entity.field_config.{$entity_type}_field_edit_form" are not defined.
if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
// Add fields entity mappers to all fieldable entity types defined.
foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
$base_route = NULL;
try {
$base_route = $route_provider->getRouteByName('entity.field_config.' . $entity_type_id . '_field_edit_form');
}
catch (RouteNotFoundException $e) {
// Ignore non-existent routes.
}
// Make sure entity type has field UI enabled and has a base route.
if ($entity_type->get('field_ui_base_route') && !empty($base_route)) {
$info[$entity_type_id . '_fields'] = array(
'base_route_name' => 'entity.field_config.' . $entity_type_id . '_field_edit_form',
'entity_type' => 'field_config',
'title' => t('Title'),
'class' => '\Drupal\config_translation\ConfigFieldMapper',
'base_entity_type' => $entity_type_id,
'weight' => 10,
);
}
}
}
}
/**
* Alter existing translation tabs for translation of configuration.
*
* This hook is useful to extend existing configuration mappers with new
* configuration names, for example when altering existing forms with new
* settings stored elsewhere. This allows the translation experience to also
* reflect the compound form element in one screen.
*
* @param array $info
* An associative array of discovered configuration mappers. Use an entity
* name for the key (for entity mapping) or a unique string for configuration
* name list mapping. The values of the associative array are arrays
* themselves in the same structure as the *.config_translation.yml files.
*
* @see hook_translation_info()
* @see \Drupal\config_translation\ConfigMapperManagerInterface
*/
function hook_config_translation_info_alter(&$info) {
// Add additional site settings to the site information screen, so it shows
// up on the translation screen. (Form alter in the elements whose values are
// stored in this config file using regular form altering on the original
// configuration form.)
$info['system.site_information_settings']['names'][] = 'example.site.setting';
}
/**
* @} End of "addtogroup hooks".
*/

View file

@ -0,0 +1,9 @@
name: 'Configuration Translation'
type: module
description: 'Provides a translation interface for configuration.'
package: Multilingual
version: VERSION
core: 8.x
configure: config_translation.mapper_list
dependencies:
- locale

View file

@ -0,0 +1,5 @@
drupal.config_translation.admin:
version: VERSION
css:
theme:
css/config_translation.admin.css: {}

View file

@ -0,0 +1,3 @@
config_translation.contextual_links:
deriver: 'Drupal\config_translation\Plugin\Derivative\ConfigTranslationContextualLinks'
weight: 100

View file

@ -0,0 +1,6 @@
config_translation.mapper_list:
title: 'Configuration translation'
parent: system.admin_config_regional
description: 'Translate the configuration.'
route_name: config_translation.mapper_list
weight: 30

View file

@ -0,0 +1,3 @@
config_translation.local_tasks:
deriver: 'Drupal\config_translation\Plugin\Derivative\ConfigTranslationLocalTasks'
weight: 100

View file

@ -0,0 +1,198 @@
<?php
/**
* @file
* Configuration Translation module.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\field\FieldConfigInterface;
/**
* Implements hook_help().
*/
function config_translation_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.config_translation':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Configuration Translation module allows you to translate configuration text; for example, the site name, vocabularies, menus, or date formats. Together with the modules <a href=":language">Language</a>, <a href=":content-translation">Content Translation</a>, and <a href=":locale">Interface Translation</a>, it allows you to build multilingual websites. For more information, see the <a href=":doc_url">online documentation for the Configuration Translation module</a>.', array(':doc_url' => 'https://www.drupal.org/documentation/modules/config_translation', ':config' => \Drupal::url('help.page', array('name' => 'config')), ':language' => \Drupal::url('help.page', array('name' => 'language')), ':locale' => \Drupal::url('help.page', array('name' => 'locale')), ':content-translation' => (\Drupal::moduleHandler()->moduleExists('content_translation')) ? \Drupal::url('help.page', array('name' => 'content_translation')) : '#')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Enabling translation') . '</dt>';
$output .= '<dd>' . t('In order to translate configuration, the website must have at least two <a href=":url">languages</a>.', array(':url' => \Drupal::url('entity.configurable_language.collection'))) . '</dd>';
$output .= '<dt>' . t('Translating configuration text') . '</dt>';
$output .= '<dd>' . t('Users with the <em>Translate user edited configuration</em> permission can access the configuration translation overview, and manage translations for specific languages. The <a href=":translation-page">Configuration translation</a> page shows a list of all configuration text that can be translated, either as individual items or as lists. After you click on <em>Translate</em>, you are provided with a list of all languages. You can <em>add</em> or <em>edit</em> a translation for a specific language. Users with specific configuration permissions can also <em>edit</em> the text for the site\'s default language. For some configuration text items (for example for the site information), the specific translation pages can also be accessed directly from their configuration pages.', array(':translation-page' => \Drupal::url('config_translation.mapper_list'))) . '</dd>';
$output .= '<dt>' . t('Translating date formats') . '</dt>';
$output .= '<dd>' . t('You can choose to translate date formats on the <a href=":translation-page">Configuration translation</a> page. This allows you not only to translate the label text, but also to set a language-specific <em>PHP date format</em>.', array(':translation-page' => \Drupal::url('config_translation.mapper_list'))) . '</dd>';
$output .= '</dl>';
return $output;
case 'config_translation.mapper_list':
$output = '<p>' . t('This page lists all configuration items on your site that have translatable text, like your site name, role names, etc.') . '</p>';
return $output;
}
}
/**
* Implements hook_theme().
*/
function config_translation_theme() {
return array(
'config_translation_manage_form_element' => array(
'render element' => 'element',
'template' => 'config_translation_manage_form_element',
),
);
}
/**
* Implements hook_themes_installed().
*/
function config_translation_themes_installed() {
// Themes can provide *.config_translation.yml declarations.
// @todo Make ThemeHandler trigger an event instead and make
// ConfigMapperManager plugin manager subscribe to it.
// @see https://www.drupal.org/node/2206347
\Drupal::service('plugin.manager.config_translation.mapper')->clearCachedDefinitions();
}
/**
* Implements hook_themes_uninstalled().
*/
function config_translation_themes_uninstalled() {
// Themes can provide *.config_translation.yml declarations.
// @todo Make ThemeHandler trigger an event instead and make
// ConfigMapperManager plugin manager subscribe to it.
// @see https://www.drupal.org/node/2206347
\Drupal::service('plugin.manager.config_translation.mapper')->clearCachedDefinitions();
}
/**
* Implements hook_entity_type_alter().
*/
function config_translation_entity_type_alter(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
foreach ($entity_types as $entity_type_id => $entity_type) {
if ($entity_type->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
if ($entity_type_id == 'block') {
$class = 'Drupal\config_translation\Controller\ConfigTranslationBlockListBuilder';
}
elseif ($entity_type_id == 'field_config') {
$class = 'Drupal\config_translation\Controller\ConfigTranslationFieldListBuilder';
// Will be filled in dynamically, see \Drupal\field\Entity\FieldConfig::linkTemplates().
$entity_type->setLinkTemplate('config-translation-overview', $entity_type->getLinkTemplate('edit-form') . '/translate');
}
else {
$class = 'Drupal\config_translation\Controller\ConfigTranslationEntityListBuilder';
}
$entity_type->setHandlerClass('config_translation_list', $class);
if ($entity_type->hasLinkTemplate('edit-form')) {
$entity_type->setLinkTemplate('config-translation-overview', $entity_type->getLinkTemplate('edit-form') . '/translate');
}
}
}
}
/**
* Implements hook_config_translation_info().
*/
function config_translation_config_translation_info(&$info) {
$entity_manager = \Drupal::entityManager();
// If field UI is not enabled, the base routes of the type
// "entity.field_config.{$entity_type}_field_edit_form" are not defined.
if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
// Add fields entity mappers to all fieldable entity types defined.
foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
// Make sure entity type has field UI enabled and has a base route.
if ($entity_type->get('field_ui_base_route')) {
$info[$entity_type_id . '_fields'] = array(
'base_route_name' => "entity.field_config.{$entity_type_id}_field_edit_form",
'entity_type' => 'field_config',
'class' => '\Drupal\config_translation\ConfigFieldMapper',
'base_entity_type' => $entity_type_id,
'weight' => 10,
);
}
}
}
// Discover configuration entities automatically.
foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
// Determine base path for entities automatically if provided via the
// configuration entity.
if (
!$entity_type->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface') ||
!$entity_type->hasLinkTemplate('edit-form')
) {
// Do not record this entity mapper if the entity type does not
// provide a base route. We'll surely not be able to do anything with
// it anyway. Configuration entities with a dynamic base path, such as
// fields, need special treatment. See above.
continue;
}
// Use the entity type as the plugin ID.
$base_route_name = "entity.$entity_type_id.edit_form";
$info[$entity_type_id] = array(
'class' => '\Drupal\config_translation\ConfigEntityMapper',
'base_route_name' => $base_route_name,
'title' => $entity_type->getLowercaseLabel(),
'names' => array(),
'entity_type' => $entity_type_id,
'weight' => 10,
);
}
}
/**
* Implements hook_entity_operation().
*/
function config_translation_entity_operation(EntityInterface $entity) {
$operations = array();
$entity_type = $entity->getEntityType();
if ($entity_type->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface') &&
$entity->hasLinkTemplate('config-translation-overview') &&
\Drupal::currentUser()->hasPermission('translate configuration')) {
$link_template = 'config-translation-overview';
if ($entity instanceof FieldConfigInterface) {
$link_template = "config-translation-overview.{$entity->getTargetEntityTypeId()}";
}
$operations['translate'] = array(
'title' => t('Translate'),
'weight' => 50,
'url' => $entity->urlInfo($link_template),
);
}
return $operations;
}
/**
* Implements hook_config_schema_info_alter().
*/
function config_translation_config_schema_info_alter(&$definitions) {
$map = array(
'label' => '\Drupal\config_translation\FormElement\Textfield',
'text' => '\Drupal\config_translation\FormElement\Textarea',
'date_format' => '\Drupal\config_translation\FormElement\DateFormat',
'text_format' => '\Drupal\config_translation\FormElement\TextFormat',
'mapping' => '\Drupal\config_translation\FormElement\ListElement',
'sequence' => '\Drupal\config_translation\FormElement\ListElement',
'plural_label' => '\Drupal\config_translation\FormElement\PluralVariants',
);
// Enhance the text and date type definitions with classes to generate proper
// form elements in ConfigTranslationFormBase. Other translatable types will
// appear as a one line textfield.
foreach ($definitions as $type => &$definition) {
if (isset($map[$type]) && !isset($definition['form_element_class'])) {
$definition['form_element_class'] = $map[$type];
}
}
}

View file

@ -0,0 +1,4 @@
translate configuration:
title: 'Translate configuration'
description: 'Translate any configuration including those shipped with modules and themes.'
restrict access: true

View file

@ -0,0 +1,14 @@
config_translation.mapper_list:
path: '/admin/config/regional/config-translation'
defaults:
_title: 'Configuration translation'
_controller: '\Drupal\config_translation\Controller\ConfigTranslationMapperList::render'
requirements:
_permission: 'translate configuration'
config_translation.entity_list:
path: '/admin/config/regional/config-translation/{mapper_id}'
defaults:
_controller: '\Drupal\config_translation\Controller\ConfigTranslationListController::listing'
requirements:
_permission: 'translate configuration'

View file

@ -0,0 +1,27 @@
services:
config_translation.route_subscriber:
class: Drupal\config_translation\Routing\RouteSubscriber
arguments: ['@plugin.manager.config_translation.mapper']
tags:
- { name: event_subscriber }
config_translation.access.overview:
class: Drupal\config_translation\Access\ConfigTranslationOverviewAccess
arguments: ['@plugin.manager.config_translation.mapper', '@language_manager']
tags:
- { name: access_check, applies_to: _config_translation_overview_access }
config_translation.access.form:
class: Drupal\config_translation\Access\ConfigTranslationFormAccess
arguments: ['@plugin.manager.config_translation.mapper', '@language_manager']
tags:
- { name: access_check, applies_to: _config_translation_form_access }
plugin.manager.config_translation.mapper:
class: Drupal\config_translation\ConfigMapperManager
arguments:
- '@cache.discovery'
- '@language_manager'
- '@module_handler'
- '@config.typed'
- '@theme_handler'

View file

@ -0,0 +1,24 @@
/**
* @file
* Styles for Configuration Translation.
*/
/**
* Hide the label, in an accessible way, for responsive screens which show the
* form in one column.
*/
.translation-set__translated label {
clip: rect(1px, 1px, 1px, 1px);
height: 1px;
overflow: hidden;
position: absolute;
width: 1px;
}
@media screen and (min-width: 38em) {
.translation-set__translated label {
height: auto;
position: inherit;
width: auto;
}
}

View file

@ -0,0 +1,15 @@
id: d6_i18n_system_maintenance
label: Maintenance page configuration
migration_tags:
- Drupal 6
source:
plugin: i18n_variable
variables:
- site_offline_message
process:
langcode: language
message: site_offline_message
destination:
plugin: config
config_name: system.maintenance
translations: true

View file

@ -0,0 +1,39 @@
id: d6_i18n_system_site
label: Site configuration
migration_tags:
- Drupal 6
source:
plugin: i18n_variable
constants:
slash: '/'
variables:
- site_name
- site_mail
- site_slogan
- site_frontpage
- site_403
- site_404
process:
langcode: language
name: site_name
mail: site_mail
slogan: site_slogan
'page/front':
plugin: concat
source:
- constants/slash
- site_frontpage
'page/403':
plugin: concat
source:
- constants/slash
- site_403
'page/404':
plugin: concat
source:
- constants/slash
- site_404
destination:
plugin: config
config_name: system.site
translations: true

View file

@ -0,0 +1,69 @@
id: d6_i18n_user_mail
label: User mail configuration
migration_tags:
- Drupal 6
source:
plugin: i18n_variable
variables:
- user_mail_status_activated_subject
- user_mail_status_activated_body
- user_mail_password_reset_subject
- user_mail_password_reset_body
- user_mail_status_deleted_subject
- user_mail_status_deleted_body
- user_mail_register_admin_created_subject
- user_mail_register_admin_created_body
- user_mail_register_no_approval_required_subject
- user_mail_register_no_approval_required_body
- user_mail_register_pending_approval_subject
- user_mail_register_pending_approval_body
- user_mail_status_blocked_subject
- user_mail_status_blocked_body
process:
langcode: language
'status_activated/subject':
plugin: convert_tokens
source: user_mail_status_activated_subject
'status_activated/body':
plugin: convert_tokens
source: user_mail_status_activated_body
'password_reset/subject':
plugin: convert_tokens
source: user_mail_password_reset_subject
'password_reset/body':
plugin: convert_tokens
source: user_mail_password_reset_body
'cancel_confirm/subject':
plugin: convert_tokens
source: user_mail_status_deleted_subject
'cancel_confirm/body':
plugin: convert_tokens
source: user_mail_status_deleted_body
'register_admin_created/subject':
plugin: convert_tokens
source: user_mail_register_admin_created_subject
'register_admin_created/body':
plugin: convert_tokens
source: user_mail_register_admin_created_body
'register_no_approval_required/subject':
plugin: convert_tokens
source: user_mail_register_no_approval_required_subject
'register_no_approval_required/body':
plugin: convert_tokens
source: user_mail_register_no_approval_required_body
'register_pending_approval/subject':
plugin: convert_tokens
source: user_mail_register_pending_approval_subject
'register_pending_approval/body':
plugin: convert_tokens
source: user_mail_register_pending_approval_body
'status_blocked/subject':
plugin: convert_tokens
source: user_mail_status_blocked_subject
'status_blocked/body':
plugin: convert_tokens
source: user_mail_status_blocked_body
destination:
plugin: config
config_name: user.mail
translations: true

View file

@ -0,0 +1,30 @@
id: d6_i18n_user_settings
label: User configuration
migration_tags:
- Drupal 6
source:
plugin: i18n_variable
variables:
- user_mail_status_blocked_notify
- user_mail_status_activated_notify
- user_email_verification
- user_register
- anonymous
process:
langcode: language
'notify/status_blocked': user_mail_status_blocked_notify
'notify/status_activated': user_mail_status_activated_notify
verify_mail: user_email_verification
register:
plugin: static_map
source: user_register
default_value: visitors_admin_approval
map:
2: visitors_admin_approval
1: visitors
0: admin_only
anonymous: anonymous
destination:
plugin: config
config_name: user.settings
translations: true

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\config_translation\Access;
use Drupal\config_translation\ConfigMapperInterface;
use Drupal\config_translation\Exception\ConfigMapperLanguageException;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Checks access for displaying the translation add, edit, and delete forms.
*/
class ConfigTranslationFormAccess extends ConfigTranslationOverviewAccess {
/**
* Checks access to the overview based on permissions and translatability.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route_match to check against.
* @param \Drupal\Core\Session\AccountInterface $account
* The account to check access for.
* @param string $langcode
* The language code of the target language.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(RouteMatchInterface $route_match, AccountInterface $account, $langcode = NULL) {
$mapper = $this->getMapperFromRouteMatch($route_match);
try {
$source_langcode = $mapper->getLangcode();
$source_language = $this->languageManager->getLanguage($source_langcode);
$target_language = $this->languageManager->getLanguage($langcode);
return $this->doCheckAccess($account, $mapper, $source_language, $target_language);
}
catch (ConfigMapperLanguageException $exception) {
return AccessResult::forbidden();
}
}
/**
* Checks access given an account, configuration mapper, and source language.
*
* In addition to the checks performed by
* ConfigTranslationOverviewAccess::doCheckAccess() this makes sure the target
* language is not locked and the target language is not the source language.
*
* Although technically configuration can be overlaid with translations in the
* same language, that is logically not a good idea.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to check access for.
* @param \Drupal\config_translation\ConfigMapperInterface $mapper
* The configuration mapper to check access for.
* @param \Drupal\Core\Language\LanguageInterface|null $source_language
* The source language to check for, if any.
* @param \Drupal\Core\Language\LanguageInterface|null $target_language
* The target language to check for, if any.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The result of the access check.
*
* @see \Drupal\config_translation\Access\ConfigTranslationOverviewAccess::doCheckAccess()
*/
protected function doCheckAccess(AccountInterface $account, ConfigMapperInterface $mapper, $source_language = NULL, $target_language = NULL) {
$base_access_result = parent::doCheckAccess($account, $mapper, $source_language);
$access =
$target_language &&
!$target_language->isLocked() &&
(!$source_language || ($target_language->getId() !== $source_language->getId()));
return $base_access_result->andIf(AccessResult::allowedIf($access));
}
}

View file

@ -0,0 +1,116 @@
<?php
namespace Drupal\config_translation\Access;
use Drupal\config_translation\ConfigMapperInterface;
use Drupal\config_translation\Exception\ConfigMapperLanguageException;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Checks access for displaying the configuration translation overview.
*/
class ConfigTranslationOverviewAccess implements AccessInterface {
/**
* The mapper plugin discovery service.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $configMapperManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a ConfigTranslationOverviewAccess object.
*
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The mapper plugin discovery service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager service.
*/
public function __construct(ConfigMapperManagerInterface $config_mapper_manager, LanguageManagerInterface $language_manager) {
$this->configMapperManager = $config_mapper_manager;
$this->languageManager = $language_manager;
}
/**
* Checks access to the overview based on permissions and translatability.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route_match to check against.
* @param \Drupal\Core\Session\AccountInterface $account
* The account to check access for.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(RouteMatchInterface $route_match, AccountInterface $account) {
$mapper = $this->getMapperFromRouteMatch($route_match);
try {
$langcode = $mapper->getLangcode();
}
catch (ConfigMapperLanguageException $exception) {
// ConfigTranslationController shows a helpful message if the language
// codes do not match, so do not let that prevent granting access.
$langcode = 'en';
}
$source_language = $this->languageManager->getLanguage($langcode);
return $this->doCheckAccess($account, $mapper, $source_language);
}
/**
* Gets a configuration mapper using a route match.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match to populate the mapper with.
*
* @return \Drupal\config_translation\ConfigMapperInterface
* The configuration mapper.
*/
protected function getMapperFromRouteMatch(RouteMatchInterface $route_match) {
$mapper = $this->configMapperManager->createInstance($route_match->getRouteObject()
->getDefault('plugin_id'));
$mapper->populateFromRouteMatch($route_match);
return $mapper;
}
/**
* Checks access given an account, configuration mapper, and source language.
*
* Grants access if the proper permission is granted to the account, the
* configuration has translatable pieces, and the source language is not
* locked given it is present.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to check access for.
* @param \Drupal\config_translation\ConfigMapperInterface $mapper
* The configuration mapper to check access for.
* @param \Drupal\Core\Language\LanguageInterface|null $source_language
* The source language to check for, if any.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The result of the access check.
*/
protected function doCheckAccess(AccountInterface $account, ConfigMapperInterface $mapper, $source_language = NULL) {
$access =
$account->hasPermission('translate configuration') &&
$mapper->hasSchema() &&
$mapper->hasTranslatable() &&
(!$source_language || !$source_language->isLocked());
return AccessResult::allowedIf($access)->cachePerPermissions();
}
}

View file

@ -0,0 +1,271 @@
<?php
namespace Drupal\config_translation;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Url;
use Drupal\locale\LocaleConfigManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
/**
* Configuration mapper for configuration entities.
*/
class ConfigEntityMapper extends ConfigNamesMapper {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Configuration entity type name.
*
* @var string
*/
protected $entityType;
/**
* Loaded entity instance to help produce the translation interface.
*
* @var \Drupal\Core\Config\Entity\ConfigEntityInterface
*/
protected $entity;
/**
* The label for the entity type.
*
* @var string
*/
protected $typeLabel;
/**
* Constructs a ConfigEntityMapper.
*
* @param string $plugin_id
* The config mapper plugin ID.
* @param mixed $plugin_definition
* An array of plugin information as documented in
* ConfigNamesMapper::__construct() with the following additional keys:
* - entity_type: The name of the entity type this mapper belongs to.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
* @param \Drupal\locale\LocaleConfigManager $locale_config_manager
* The locale configuration manager.
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The mapper plugin discovery service.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
* The string translation manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct($plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config, LocaleConfigManager $locale_config_manager, ConfigMapperManagerInterface $config_mapper_manager, RouteProviderInterface $route_provider, TranslationInterface $translation_manager, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
parent::__construct($plugin_id, $plugin_definition, $config_factory, $typed_config, $locale_config_manager, $config_mapper_manager, $route_provider, $translation_manager, $language_manager);
$this->setType($plugin_definition['entity_type']);
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
// Note that we ignore the plugin $configuration because mappers have
// nothing to configure in themselves.
return new static (
$plugin_id,
$plugin_definition,
$container->get('config.factory'),
$container->get('config.typed'),
$container->get('locale.config_manager'),
$container->get('plugin.manager.config_translation.mapper'),
$container->get('router.route_provider'),
$container->get('string_translation'),
$container->get('entity.manager'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function populateFromRouteMatch(RouteMatchInterface $route_match) {
parent::populateFromRouteMatch($route_match);
$entity = $route_match->getParameter($this->entityType);
$this->setEntity($entity);
}
/**
* Gets the entity instance for this mapper.
*
* @return \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
* The configuration entity.
*/
public function getEntity() {
return $this->entity;
}
/**
* Sets the entity instance for this mapper.
*
* This method can only be invoked when the concrete entity is known, that is
* in a request for an entity translation path. After this method is called,
* the mapper is fully populated with the proper display title and
* configuration names to use to check permissions or display a translation
* screen.
*
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
* The configuration entity to set.
*
* @return bool
* TRUE, if the entity was set successfully; FALSE otherwise.
*/
public function setEntity(ConfigEntityInterface $entity) {
if (isset($this->entity)) {
return FALSE;
}
$this->entity = $entity;
// Add the list of configuration IDs belonging to this entity. We add on a
// possibly existing list of names. This allows modules to alter the entity
// page with more names if form altering added more configuration to an
// entity. This is not a Drupal 8 best practice (ideally the configuration
// would have pluggable components), but this may happen as well.
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type_info */
$entity_type_info = $this->entityManager->getDefinition($this->entityType);
$this->addConfigName($entity_type_info->getConfigPrefix() . '.' . $entity->id());
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getTitle() {
return $this->entity->label() . ' ' . $this->pluginDefinition['title'];
}
/**
* {@inheritdoc}
*/
public function getBaseRouteParameters() {
return array($this->entityType => $this->entity->id());
}
/**
* Set entity type for this mapper.
*
* This should be set in initialization. A mapper that knows its type but
* not yet its names is still useful for router item and tab generation. The
* concrete entity only turns out later with actual controller invocations,
* when the setEntity() method is invoked before the rest of the methods are
* used.
*
* @param string $entity_type
* The entity type to set.
*
* @return bool
* TRUE if the entity type was set correctly; FALSE otherwise.
*/
public function setType($entity_type) {
if (isset($this->entityType)) {
return FALSE;
}
$this->entityType = $entity_type;
return TRUE;
}
/**
* Gets the entity type from this mapper.
*
* @return string
*/
public function getType() {
return $this->entityType;
}
/**
* {@inheritdoc}
*/
public function getTypeName() {
$entity_type_info = $this->entityManager->getDefinition($this->entityType);
return $entity_type_info->getLabel();
}
/**
* {@inheritdoc}
*/
public function getTypeLabel() {
$entityType = $this->entityManager->getDefinition($this->entityType);
return $entityType->getLabel();
}
/**
* {@inheritdoc}
*/
public function getOperations() {
return array(
'list' => array(
'title' => $this->t('List'),
'url' => Url::fromRoute('config_translation.entity_list', [
'mapper_id' => $this->getPluginId(),
]),
),
);
}
/**
* {@inheritdoc}
*/
public function getContextualLinkGroup() {
// @todo Contextual groups do not map to entity types in a predictable
// way. See https://www.drupal.org/node/2134841 to make them predictable.
switch ($this->entityType) {
case 'menu':
case 'block':
return $this->entityType;
case 'view':
return 'entity.view.edit_form';
default:
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function getOverviewRouteName() {
return 'entity.' . $this->entityType . '.config_translation_overview';
}
/**
* {@inheritdoc}
*/
protected function processRoute(Route $route) {
// Add entity upcasting information.
$parameters = $route->getOption('parameters') ?: array();
$parameters += array(
$this->entityType => array(
'type' => 'entity:' . $this->entityType,
)
);
$route->setOption('parameters', $parameters);
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Drupal\config_translation;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Configuration mapper for fields.
*
* On top of plugin definition values on ConfigEntityMapper, the plugin
* definition for field mappers are required to contain the following
* additional keys:
* - base_entity_type: The name of the entity type the fields are attached to.
*/
class ConfigFieldMapper extends ConfigEntityMapper {
/**
* Loaded entity instance to help produce the translation interface.
*
* @var \Drupal\field\FieldConfigInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
public function getBaseRouteParameters() {
$parameters = parent::getBaseRouteParameters();
$base_entity_info = $this->entityManager->getDefinition($this->pluginDefinition['base_entity_type']);
$bundle_parameter_key = $base_entity_info->getBundleEntityType() ?: 'bundle';
$parameters[$bundle_parameter_key] = $this->entity->getTargetBundle();
return $parameters;
}
/**
* {@inheritdoc}
*/
public function getOverviewRouteName() {
return 'entity.field_config.config_translation_overview.' . $this->pluginDefinition['base_entity_type'];
}
/**
* {@inheritdoc}
*/
public function getTypeLabel() {
$base_entity_info = $this->entityManager->getDefinition($this->pluginDefinition['base_entity_type']);
return $this->t('@label fields', array('@label' => $base_entity_info->getLabel()));
}
/**
* {@inheritdoc}
*/
public function setEntity(ConfigEntityInterface $entity) {
if (parent::setEntity($entity)) {
// Field storage config can also contain translatable values. Add the name
// of the config as well to the list of configs for this entity.
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
$field_storage = $this->entity->getFieldStorageDefinition();
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type_info */
$entity_type_info = $this->entityManager->getDefinition($field_storage->getEntityTypeId());
$this->addConfigName($entity_type_info->getConfigPrefix() . '.' . $field_storage->id());
return TRUE;
}
return FALSE;
}
}

View file

@ -0,0 +1,302 @@
<?php
namespace Drupal\config_translation;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Defines an interface for configuration mapper.
*/
interface ConfigMapperInterface {
/**
* Returns title of this translation page.
*
* @return string
* The page title.
*/
public function getTitle();
/**
* Sets the route collection.
*
* @param \Symfony\Component\Routing\RouteCollection $collection
* The route collection.
*/
public function setRouteCollection(RouteCollection $collection);
/**
* Returns the name of the base route the mapper is attached to.
*
* @return string
* The name of the base route the mapper is attached to.
*/
public function getBaseRouteName();
/**
* Returns the route parameters for the base route the mapper is attached to.
*
* @return array
*/
public function getBaseRouteParameters();
/**
* Returns the base route object the mapper is attached to.
*
* @return \Symfony\Component\Routing\Route
* The base route object the mapper is attached to.
*/
public function getBaseRoute();
/**
* Returns a processed path for the base route the mapper is attached to.
*
* @return string
* Processed path with placeholders replaced.
*/
public function getBasePath();
/**
* Returns route name for the translation overview route.
*
* @return string
* Route name for the mapper.
*/
public function getOverviewRouteName();
/**
* Returns the route parameters for the translation overview route.
*
* @return array
*/
public function getOverviewRouteParameters();
/**
* Returns the route object for a translation overview route.
*
* @return \Symfony\Component\Routing\Route
* The route object for the translation page.
*/
public function getOverviewRoute();
/**
* Returns a processed path for the translation overview route.
*
* @return string
* Processed path with placeholders replaced.
*/
public function getOverviewPath();
/**
* Returns route name for the translation add form route.
*
* @return string
* Route name for the mapper.
*/
public function getAddRouteName();
/**
* Returns the route parameters for the translation add form route.
*
* @return array
*/
public function getAddRouteParameters();
/**
* Returns the route object for a translation add form route.
*
* @return \Symfony\Component\Routing\Route
* The route object for the translation page.
*/
public function getAddRoute();
/**
* Returns route name for the translation edit form route.
*
* @return string
* Route name for the mapper.
*/
public function getEditRouteName();
/**
* Returns the route parameters for the translation edit form route.
*
* @return array
*/
public function getEditRouteParameters();
/**
* Returns the route object for a translation edit form route.
*
* @return \Symfony\Component\Routing\Route
* The route object for the translation page.
*/
public function getEditRoute();
/**
* Returns route name for the translation deletion route.
*
* @return string
* Route name for the mapper.
*/
public function getDeleteRouteName();
/**
* Returns the route parameters for the translation deletion route.
*
* @return array
*/
public function getDeleteRouteParameters();
/**
* Returns the route object for the translation deletion route.
*
* @return \Symfony\Component\Routing\Route
* The route object for the translation page.
*/
public function getDeleteRoute();
/**
* Returns an array of configuration names for the mapper.
*
* @return array
* An array of configuration names for the mapper.
*/
public function getConfigNames();
/**
* Adds the given configuration name to the list of names.
*
* Note that it is the responsibility of the calling code to ensure that the
* configuration exists.
*
* @param string $name
* Configuration name.
*/
public function addConfigName($name);
/**
* Returns the weight of the mapper.
*
* @return int
* The weight of the mapper.
*/
public function getWeight();
/**
* Returns an array with all configuration data.
*
* @return array
* Configuration data keyed by configuration names.
*/
public function getConfigData();
/**
* Returns the original language code of the configuration.
*
* @throws \RuntimeException
* Throws an exception if the language codes in the config files don't
* match.
*/
public function getLangcode();
/**
* Returns the language code of a configuration object given its name.
*
* @param string $config_name
* The name of the configuration object.
*
* @return string
* The language code of the configuration object.
*/
public function getLangcodeFromConfig($config_name);
/**
* Sets the original language code.
*
* @param string $langcode
* The langcode.
*
* @return $this
*/
public function setLangcode($langcode);
/**
* Returns the name of the type of data the mapper encapsulates.
*
* @return string
* The name of the type of data the mapper encapsulates.
*/
public function getTypeName();
/**
* Provides an array of information to build a list of operation links.
*
* @return array
* An associative array of operation link data for this list, keyed by
* operation name, containing the following key-value pairs:
* - title: The localized title of the operation.
* - href: The path for the operation.
* - options: An array of URL options for the path.
* - weight: The weight of this operation.
*/
public function getOperations();
/**
* Returns the label of the type of data the mapper encapsulates.
*
* @return string
* The label of the type of data the mapper encapsulates.
*/
public function getTypeLabel();
/**
* Checks that all pieces of this configuration mapper have a schema.
*
* @return bool
* TRUE if all of the elements have schema, FALSE otherwise.
*/
public function hasSchema();
/**
* Checks if pieces of this configuration mapper have translatables.
*
* @return bool
* TRUE if at least one of the configuration elements has translatables,
* FALSE otherwise.
*/
public function hasTranslatable();
/**
* Checks whether there is already a translation for this mapper.
*
* @param \Drupal\Core\Language\LanguageInterface $language
* A language object.
*
* @return bool
* TRUE if any of the configuration elements have a translation in the
* given language, FALSE otherwise.
*/
public function hasTranslation(LanguageInterface $language);
/**
* Populate the config mapper with request data.
*
* @todo Replace $request with RouteMatch https://www.drupal.org/node/2295255.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*/
public function populateFromRouteMatch(RouteMatchInterface $route_match);
/**
* Returns the name of the contextual link group to add contextual links to.
*
* @return string|null
* A contextual link group name or null if no link should be added.
*/
public function getContextualLinkGroup();
}

View file

@ -0,0 +1,199 @@
<?php
namespace Drupal\config_translation;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Plugin\Discovery\InfoHookDecorator;
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\Core\Plugin\Factory\ContainerFactory;
use Drupal\Core\TypedData\TraversableTypedDataInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Manages plugins for configuration translation mappers.
*/
class ConfigMapperManager extends DefaultPluginManager implements ConfigMapperManagerInterface {
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* {@inheritdoc}
*/
protected $defaults = array(
'title' => '',
'names' => array(),
'weight' => 20,
'class' => '\Drupal\config_translation\ConfigNamesMapper',
);
/**
* Constructs a ConfigMapperManager.
*
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* The cache backend.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
* The typed config manager.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, TypedConfigManagerInterface $typed_config_manager, ThemeHandlerInterface $theme_handler) {
$this->typedConfigManager = $typed_config_manager;
$this->factory = new ContainerFactory($this, '\Drupal\config_translation\ConfigMapperInterface');
// Let others alter definitions with hook_config_translation_info_alter().
$this->moduleHandler = $module_handler;
$this->themeHandler = $theme_handler;
$this->alterInfo('config_translation_info');
// Config translation only uses an info hook discovery, cache by language.
$cache_key = 'config_translation_info_plugins' . ':' . $language_manager->getCurrentLanguage()->getId();
$this->setCacheBackend($cache_backend, $cache_key, array('config_translation_info_plugins'));
}
/**
* {@inheritdoc}
*/
protected function getDiscovery() {
if (!isset($this->discovery)) {
// Look at all themes and modules.
// @todo If the list of installed modules and themes is changed, new
// definitions are not picked up immediately and obsolete definitions
// are not removed, because the list of search directories is only
// compiled once in this constructor. The current code only works due to
// coincidence: The request that installs (for instance, a new theme)
// does not instantiate this plugin manager at the beginning of the
// request; when routes are being rebuilt at the end of the request,
// this service only happens to get instantiated with the updated list
// of installed themes.
$directories = array();
foreach ($this->moduleHandler->getModuleList() as $name => $module) {
$directories[$name] = $module->getPath();
}
foreach ($this->themeHandler->listInfo() as $theme) {
$directories[$theme->getName()] = $theme->getPath();
}
// Check for files named MODULE.config_translation.yml and
// THEME.config_translation.yml in module/theme roots.
$this->discovery = new YamlDiscovery('config_translation', $directories);
$this->discovery = new InfoHookDecorator($this->discovery, 'config_translation_info');
$this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
}
return $this->discovery;
}
/**
* {@inheritdoc}
*/
public function getMappers(RouteCollection $collection = NULL) {
$mappers = array();
foreach ($this->getDefinitions() as $id => $definition) {
$mappers[$id] = $this->createInstance($id);
if ($collection) {
$mappers[$id]->setRouteCollection($collection);
}
}
return $mappers;
}
/**
* {@inheritdoc}
*/
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
if (!isset($definition['base_route_name'])) {
throw new InvalidPluginDefinitionException($plugin_id, "The plugin definition of the mapper '$plugin_id' does not contain a base_route_name.");
}
}
/**
* {@inheritdoc}
*/
public function buildDataDefinition(array $definition, $value = NULL, $name = NULL, $parent = NULL) {
return $this->typedConfigManager->buildDataDefinition($definition, $value, $name, $parent);
}
/**
* {@inheritdoc}
*/
protected function findDefinitions() {
$definitions = $this->getDiscovery()->getDefinitions();
foreach ($definitions as $plugin_id => &$definition) {
$this->processDefinition($definition, $plugin_id);
}
if ($this->alterHook) {
$this->moduleHandler->alter($this->alterHook, $definitions);
}
// If this plugin was provided by a module that does not exist, remove the
// plugin definition.
foreach ($definitions as $plugin_id => $plugin_definition) {
if (isset($plugin_definition['provider']) && !in_array($plugin_definition['provider'], array('core', 'component')) && (!$this->moduleHandler->moduleExists($plugin_definition['provider']) && !in_array($plugin_definition['provider'], array_keys($this->themeHandler->listInfo())))) {
unset($definitions[$plugin_id]);
}
}
return $definitions;
}
/**
* {@inheritdoc}
*/
public function hasTranslatable($name) {
return $this->findTranslatable($this->typedConfigManager->get($name));
}
/**
* Returns TRUE if at least one translatable element is found.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $element
* Configuration schema element.
*
* @return bool
* A boolean indicating if there is at least one translatable element.
*/
protected function findTranslatable(TypedDataInterface $element) {
// In case this is a sequence or a mapping check whether any child element
// is translatable.
if ($element instanceof TraversableTypedDataInterface) {
foreach ($element as $child_element) {
if ($this->findTranslatable($child_element)) {
return TRUE;
}
}
// If none of the child elements are translatable, return FALSE.
return FALSE;
}
else {
$definition = $element->getDataDefinition();
return isset($definition['translatable']) && $definition['translatable'];
}
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\config_translation;
use Drupal\Component\Plugin\PluginManagerInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides a common interface for config mapper managers.
*/
interface ConfigMapperManagerInterface extends PluginManagerInterface {
/**
* Returns an array of all mappers.
*
* @param \Symfony\Component\Routing\RouteCollection $collection
* The route collection used to initialize the mappers.
*
* @return \Drupal\config_translation\ConfigMapperInterface[]
* An array of all mappers.
*/
public function getMappers(RouteCollection $collection = NULL);
/**
* Returns TRUE if the configuration data has translatable items.
*
* @param string $name
* Configuration key.
*
* @return bool
* A boolean indicating if the configuration data has translatable items.
*/
public function hasTranslatable($name);
}

View file

@ -0,0 +1,485 @@
<?php
namespace Drupal\config_translation;
use Drupal\config_translation\Exception\ConfigMapperLanguageException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\locale\LocaleConfigManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Configuration mapper base implementation.
*/
class ConfigNamesMapper extends PluginBase implements ConfigMapperInterface, ContainerFactoryPluginInterface {
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* The typed configuration manager.
*
* @var \Drupal\locale\LocaleConfigManager
*/
protected $localeConfigManager;
/**
* The mapper plugin discovery service.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $configMapperManager;
/**
* The route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The base route object that the mapper is attached to.
*
* @return \Symfony\Component\Routing\Route
*/
protected $baseRoute;
/**
* The available routes.
*
* @var \Symfony\Component\Routing\RouteCollection
*/
protected $routeCollection;
/**
* The language code of the language this mapper, if any.
*
* @var string|null
*/
protected $langcode = NULL;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a ConfigNamesMapper.
*
* @param $plugin_id
* The config mapper plugin ID.
* @param mixed $plugin_definition
* An array of plugin information with the following keys:
* - title: The title of the mapper, used for generating page titles.
* - base_route_name: The route name of the base route this mapper is
* attached to.
* - names: (optional) An array of configuration names.
* - weight: (optional) The weight of this mapper, used in mapper listings.
* Defaults to 20.
* - list_controller: (optional) Class name for list controller used to
* generate lists of this type of configuration.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
* @param \Drupal\locale\LocaleConfigManager $locale_config_manager
* The locale configuration manager.
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The mapper plugin discovery service.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException
* Throws an exception if the route specified by the 'base_route_name' in
* the plugin definition could not be found by the route provider.
*/
public function __construct($plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config, LocaleConfigManager $locale_config_manager, ConfigMapperManagerInterface $config_mapper_manager, RouteProviderInterface $route_provider, TranslationInterface $string_translation, LanguageManagerInterface $language_manager) {
$this->pluginId = $plugin_id;
$this->pluginDefinition = $plugin_definition;
$this->routeProvider = $route_provider;
$this->configFactory = $config_factory;
$this->typedConfigManager = $typed_config;
$this->localeConfigManager = $locale_config_manager;
$this->configMapperManager = $config_mapper_manager;
$this->stringTranslation = $string_translation;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
// Note that we ignore the plugin $configuration because mappers have
// nothing to configure in themselves.
return new static (
$plugin_id,
$plugin_definition,
$container->get('config.factory'),
$container->get('config.typed'),
$container->get('locale.config_manager'),
$container->get('plugin.manager.config_translation.mapper'),
$container->get('router.route_provider'),
$container->get('string_translation'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function setRouteCollection(RouteCollection $collection) {
$this->routeCollection = $collection;
}
/**
* {@inheritdoc}
*/
public function getTitle() {
// A title from a *.config_translation.yml. Should be translated for
// display in the current page language.
return $this->t($this->pluginDefinition['title']);
}
/**
* {@inheritdoc}
*/
public function getBaseRouteName() {
return $this->pluginDefinition['base_route_name'];
}
/**
* {@inheritdoc}
*/
public function getBaseRouteParameters() {
return array();
}
/**
* {@inheritdoc}
*/
public function getBaseRoute() {
if ($this->routeCollection) {
return $this->routeCollection->get($this->getBaseRouteName());
}
else {
return $this->routeProvider->getRouteByName($this->getBaseRouteName());
}
}
/**
* Allows to process all config translation routes.
*
* @param \Symfony\Component\Routing\Route $route
* The route object to process.
*/
protected function processRoute(Route $route) {
}
/**
* {@inheritdoc}
*/
public function getBasePath() {
return Url::fromRoute($this->getBaseRouteName(), $this->getBaseRouteParameters())->getInternalPath();
}
/**
* {@inheritdoc}
*/
public function getOverviewRouteName() {
return 'config_translation.item.overview.' . $this->getBaseRouteName();
}
/**
* {@inheritdoc}
*/
public function getOverviewRouteParameters() {
return $this->getBaseRouteParameters();
}
/**
* {@inheritdoc}
*/
public function getOverviewRoute() {
$route = new Route(
$this->getBaseRoute()->getPath() . '/translate',
array(
'_controller' => '\Drupal\config_translation\Controller\ConfigTranslationController::itemPage',
'plugin_id' => $this->getPluginId(),
),
array('_config_translation_overview_access' => 'TRUE')
);
$this->processRoute($route);
return $route;
}
/**
* {@inheritdoc}
*/
public function getOverviewPath() {
return Url::fromRoute($this->getOverviewRouteName(), $this->getOverviewRouteParameters())->getInternalPath();
}
/**
* {@inheritdoc}
*/
public function getAddRouteName() {
return 'config_translation.item.add.' . $this->getBaseRouteName();
}
/**
* {@inheritdoc}
*/
public function getAddRouteParameters() {
// If sub-classes provide route parameters in getBaseRouteParameters(), they
// probably also want to provide those for the add, edit, and delete forms.
$parameters = $this->getBaseRouteParameters();
$parameters['langcode'] = $this->langcode;
return $parameters;
}
/**
* {@inheritdoc}
*/
public function getAddRoute() {
$route = new Route(
$this->getBaseRoute()->getPath() . '/translate/{langcode}/add',
array(
'_form' => '\Drupal\config_translation\Form\ConfigTranslationAddForm',
'plugin_id' => $this->getPluginId(),
),
array('_config_translation_form_access' => 'TRUE')
);
$this->processRoute($route);
return $route;
}
/**
* {@inheritdoc}
*/
public function getEditRouteName() {
return 'config_translation.item.edit.' . $this->getBaseRouteName();
}
/**
* {@inheritdoc}
*/
public function getEditRouteParameters() {
return $this->getAddRouteParameters();
}
/**
* {@inheritdoc}
*/
public function getEditRoute() {
$route = new Route(
$this->getBaseRoute()->getPath() . '/translate/{langcode}/edit',
array(
'_form' => '\Drupal\config_translation\Form\ConfigTranslationEditForm',
'plugin_id' => $this->getPluginId(),
),
array('_config_translation_form_access' => 'TRUE')
);
$this->processRoute($route);
return $route;
}
/**
* {@inheritdoc}
*/
public function getDeleteRouteName() {
return 'config_translation.item.delete.' . $this->getBaseRouteName();
}
/**
* {@inheritdoc}
*/
public function getDeleteRouteParameters() {
return $this->getAddRouteParameters();
}
/**
* {@inheritdoc}
*/
public function getDeleteRoute() {
$route = new Route(
$this->getBaseRoute()->getPath() . '/translate/{langcode}/delete',
array(
'_form' => '\Drupal\config_translation\Form\ConfigTranslationDeleteForm',
'plugin_id' => $this->getPluginId(),
),
array('_config_translation_form_access' => 'TRUE')
);
$this->processRoute($route);
return $route;
}
/**
* {@inheritdoc}
*/
public function getConfigNames() {
return $this->pluginDefinition['names'];
}
/**
* {@inheritdoc}
*/
public function addConfigName($name) {
$this->pluginDefinition['names'][] = $name;
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->pluginDefinition['weight'];
}
/**
* {@inheritdoc}
*/
public function populateFromRouteMatch(RouteMatchInterface $route_match) {
$this->langcode = $route_match->getParameter('langcode');
}
/**
* {@inheritdoc}
*/
public function getTypeLabel() {
return $this->getTitle();
}
/**
* {@inheritdoc}
*/
public function getLangcode() {
$langcodes = array_map([$this, 'getLangcodeFromConfig'], $this->getConfigNames());
if (count(array_unique($langcodes)) > 1) {
throw new ConfigMapperLanguageException('A config mapper can only contain configuration for a single language.');
}
return reset($langcodes);
}
/**
* {@inheritdoc}
*/
public function getLangcodeFromConfig($config_name) {
// Default to English if no language code was provided in the file.
// Although it is a best practice to include a language code, if the
// developer did not think about a multilingual use case, we fall back
// on assuming the file is English.
return $this->configFactory->get($config_name)->get('langcode') ?: 'en';
}
/**
* {@inheritdoc}
*/
public function setLangcode($langcode) {
$this->langcode = $langcode;
return $this;
}
/**
* {@inheritdoc}
*/
public function getConfigData() {
$config_data = array();
foreach ($this->getConfigNames() as $name) {
$config_data[$name] = $this->configFactory->getEditable($name)->get();
}
return $config_data;
}
/**
* {@inheritdoc}
*/
public function hasSchema() {
foreach ($this->getConfigNames() as $name) {
if (!$this->typedConfigManager->hasConfigSchema($name)) {
return FALSE;
}
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function hasTranslatable() {
foreach ($this->getConfigNames() as $name) {
if ($this->configMapperManager->hasTranslatable($name)) {
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function hasTranslation(LanguageInterface $language) {
foreach ($this->getConfigNames() as $name) {
if ($this->localeConfigManager->hasTranslation($name, $language->getId())) {
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getTypeName() {
return $this->t('Settings');
}
/**
* {@inheritdoc}
*/
public function getOperations() {
return array(
'translate' => array(
'title' => $this->t('Translate'),
'url' => Url::fromRoute($this->getOverviewRouteName(), $this->getOverviewRouteParameters()),
),
);
}
/**
* {@inheritdoc}
*/
public function getContextualLinkGroup() {
return NULL;
}
}

View file

@ -0,0 +1,99 @@
<?php
namespace Drupal\config_translation\Controller;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the config translation list builder for blocks.
*/
class ConfigTranslationBlockListBuilder extends ConfigTranslationEntityListBuilder {
/**
* An array of theme info keyed by theme name.
*
* @var array
*/
protected $themes = array();
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ThemeHandlerInterface $theme_handler) {
parent::__construct($entity_type, $storage);
$this->themes = $theme_handler->listInfo();
}
/**
* {@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('theme_handler')
);
}
/**
* {@inheritdoc}
*/
public function getFilterLabels() {
$info = parent::getFilterLabels();
$info['placeholder'] = $this->t('Enter block, theme or category');
$info['description'] = $this->t('Enter a part of the block, theme or category to filter by.');
return $info;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$theme = $entity->getTheme();
$plugin_definition = $entity->getPlugin()->getPluginDefinition();
$row['label'] = array(
'data' => $entity->label(),
'class' => 'table-filter-text-source',
);
$row['theme'] = array(
'data' => $this->themes[$theme]->info['name'],
'class' => 'table-filter-text-source',
);
$row['category'] = array(
'data' => $plugin_definition['category'],
'class' => 'table-filter-text-source',
);
$row['operations']['data'] = $this->buildOperations($entity);
return $row;
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['label'] = $this->t('Block');
$header['theme'] = $this->t('Theme');
$header['category'] = $this->t('Category');
$header['operations'] = $this->t('Operations');
return $header;
}
/**
* {@inheritdoc}
*/
public function sortRows($a, $b) {
return $this->sortRowsMultiple($a, $b, array('theme', 'category', 'label'));
}
}

View file

@ -0,0 +1,256 @@
<?php
namespace Drupal\config_translation\Controller;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\config_translation\Exception\ConfigMapperLanguageException;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
/**
* Provides page callbacks for the configuration translation interface.
*/
class ConfigTranslationController extends ControllerBase {
/**
* The configuration mapper manager.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $configMapperManager;
/**
* The menu link access service.
*
* @var \Drupal\Core\Access\AccessManagerInterface
*/
protected $accessManager;
/**
* The dynamic router service.
*
* @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
*/
protected $router;
/**
* The path processor service.
*
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
*/
protected $pathProcessor;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The language manager.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
*/
protected $languageManager;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a ConfigTranslationController.
*
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The configuration mapper manager.
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
* The menu link access service.
* @param \Symfony\Component\Routing\Matcher\RequestMatcherInterface $router
* The dynamic router service.
* @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor
* The inbound path processor.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(ConfigMapperManagerInterface $config_mapper_manager, AccessManagerInterface $access_manager, RequestMatcherInterface $router, InboundPathProcessorInterface $path_processor, AccountInterface $account, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
$this->configMapperManager = $config_mapper_manager;
$this->accessManager = $access_manager;
$this->router = $router;
$this->pathProcessor = $path_processor;
$this->account = $account;
$this->languageManager = $language_manager;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.config_translation.mapper'),
$container->get('access_manager'),
$container->get('router'),
$container->get('path_processor_manager'),
$container->get('current_user'),
$container->get('language_manager'),
$container->get('renderer')
);
}
/**
* Language translations overview page for a configuration name.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* Page request object.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param string $plugin_id
* The plugin ID of the mapper.
*
* @return array
* Page render array.
*/
public function itemPage(Request $request, RouteMatchInterface $route_match, $plugin_id) {
/** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
$mapper = $this->configMapperManager->createInstance($plugin_id);
$mapper->populateFromRouteMatch($route_match);
$page = array();
$page['#title'] = $this->t('Translations for %label', array('%label' => $mapper->getTitle()));
$languages = $this->languageManager->getLanguages();
if (count($languages) == 1) {
drupal_set_message($this->t('In order to translate configuration, the website must have at least two <a href=":url">languages</a>.', array(':url' => $this->url('entity.configurable_language.collection'))), 'warning');
}
try {
$original_langcode = $mapper->getLangcode();
$operations_access = TRUE;
}
catch (ConfigMapperLanguageException $exception) {
$items = [];
foreach ($mapper->getConfigNames() as $config_name) {
$langcode = $mapper->getLangcodeFromConfig($config_name);
$items[] = $this->t('@name: @langcode', [
'@name' => $config_name,
'@langcode' => $langcode,
]);
}
$message = [
'message' => ['#markup' => $this->t('The configuration objects have different language codes so they cannot be translated:')],
'items' => [
'#theme' => 'item_list',
'#items' => $items,
],
];
drupal_set_message($this->renderer->renderPlain($message), 'warning');
$original_langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
$operations_access = FALSE;
}
if (!isset($languages[$original_langcode])) {
// If the language is not configured on the site, create a dummy language
// object for this listing only to ensure the user gets useful info.
$language_name = $this->languageManager->getLanguageName($original_langcode);
$languages[$original_langcode] = new Language(array('id' => $original_langcode, 'name' => $language_name));
}
// We create a fake request object to pass into
// ConfigMapperInterface::populateFromRouteMatch() for the different languages.
// Creating a separate request for each language and route is neither easily
// possible nor performant.
$fake_request = $request->duplicate();
$page['languages'] = array(
'#type' => 'table',
'#header' => array($this->t('Language'), $this->t('Operations')),
);
foreach ($languages as $language) {
$langcode = $language->getId();
// This is needed because
// ConfigMapperInterface::getAddRouteParameters(), for example,
// needs to return the correct language code for each table row.
$fake_route_match = RouteMatch::createFromRequest($fake_request);
$mapper->populateFromRouteMatch($fake_route_match);
$mapper->setLangcode($langcode);
// Prepare the language name and the operations depending on whether this
// is the original language or not.
if ($langcode == $original_langcode) {
$language_name = '<strong>' . $this->t('@language (original)', array('@language' => $language->getName())) . '</strong>';
// Check access for the path/route for editing, so we can decide to
// include a link to edit or not.
$edit_access = $this->accessManager->checkNamedRoute($mapper->getBaseRouteName(), $route_match->getRawParameters()->all(), $this->account);
// Build list of operations.
$operations = array();
if ($edit_access) {
$operations['edit'] = array(
'title' => $this->t('Edit'),
'url' => Url::fromRoute($mapper->getBaseRouteName(), $mapper->getBaseRouteParameters(), ['query' => ['destination' => $mapper->getOverviewPath()]]),
);
}
}
else {
$language_name = $language->getName();
$operations = array();
// If no translation exists for this language, link to add one.
if (!$mapper->hasTranslation($language)) {
$operations['add'] = array(
'title' => $this->t('Add'),
'url' => Url::fromRoute($mapper->getAddRouteName(), $mapper->getAddRouteParameters()),
);
}
else {
// Otherwise, link to edit the existing translation.
$operations['edit'] = array(
'title' => $this->t('Edit'),
'url' => Url::fromRoute($mapper->getEditRouteName(), $mapper->getEditRouteParameters()),
);
$operations['delete'] = array(
'title' => $this->t('Delete'),
'url' => Url::fromRoute($mapper->getDeleteRouteName(), $mapper->getDeleteRouteParameters()),
);
}
}
$page['languages'][$langcode]['language'] = array(
'#markup' => $language_name,
);
$page['languages'][$langcode]['operations'] = array(
'#type' => 'operations',
'#links' => $operations,
// Even if the mapper contains multiple language codes, the source
// configuration can still be edited.
'#access' => ($langcode == $original_langcode) || $operations_access,
);
}
return $page;
}
}

View file

@ -0,0 +1,134 @@
<?php
namespace Drupal\config_translation\Controller;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
/**
* Defines the configuration translation list builder for entities.
*/
class ConfigTranslationEntityListBuilder extends ConfigEntityListBuilder implements ConfigTranslationEntityListBuilderInterface {
/**
* Provides user facing strings for the filter element.
*
* @return array
*/
protected function getFilterLabels() {
return array(
'placeholder' => $this->t('Enter label'),
'description' => $this->t('Enter a part of the label or description to filter by.'),
);
}
/**
* {@inheritdoc}
*/
public function render() {
$build = parent::render();
$filter = $this->getFilterLabels();
usort($build['table']['#rows'], array($this, 'sortRows'));
$build['filters'] = array(
'#type' => 'container',
'#attributes' => array(
'class' => array('table-filter', 'js-show'),
),
'#weight' => -10,
);
$build['filters']['text'] = array(
'#type' => 'search',
'#title' => $this->t('Search'),
'#size' => 30,
'#placeholder' => $filter['placeholder'],
'#attributes' => array(
'class' => array('table-filter-text'),
'data-table' => '.config-translation-entity-list',
'autocomplete' => 'off',
'title' => $filter['description'],
),
);
$build['table']['#attributes']['class'][] = 'config-translation-entity-list';
$build['table']['#weight'] = 0;
$build['#attached']['library'][] = 'system/drupal.system.modules';
return $build;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['label']['data'] = $entity->label();
$row['label']['class'][] = 'table-filter-text-source';
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['label'] = $this->t('Label');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
foreach (array_keys($operations) as $operation) {
// This is a translation UI for translators. Show the translation
// operation only.
if (!($operation == 'translate')) {
unset($operations[$operation]);
}
}
return $operations;
}
/**
* {@inheritdoc}
*/
public function sortRows($a, $b) {
return $this->sortRowsMultiple($a, $b, array('label'));
}
/**
* Sorts an array by multiple criteria.
*
* @param array $a
* First item for comparison.
* @param array $b
* Second item for comparison.
* @param array $keys
* The array keys to sort on.
*
* @return int
* The comparison result for uasort().
*/
protected function sortRowsMultiple($a, $b, $keys) {
$key = array_shift($keys);
$a_value = (is_array($a) && isset($a[$key]['data'])) ? $a[$key]['data'] : '';
$b_value = (is_array($b) && isset($b[$key]['data'])) ? $b[$key]['data'] : '';
if ($a_value == $b_value && !empty($keys)) {
return $this->sortRowsMultiple($a, $b, $keys);
}
return strnatcasecmp($a_value, $b_value);
}
/**
* {@inheritdoc}
*/
public function setMapperDefinition($mapper_definition) {
// @todo Why is this method called on all config list controllers?
return $this;
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\config_translation\Controller;
use Drupal\Core\Entity\EntityListBuilderInterface;
/**
* Defines an interface for configuration translation entity list builders.
*/
interface ConfigTranslationEntityListBuilderInterface extends EntityListBuilderInterface {
/**
* Sorts an array by value.
*
* @param array $a
* First item for comparison.
* @param array $b
* Second item for comparison.
*
* @return int
* The comparison result for uasort().
*/
public function sortRows($a, $b);
/**
* Sets the config translation mapper definition.
*
* @param mixed $mapper_definition
* The plugin definition of the config translation mapper.
*
* @return $this
*/
public function setMapperDefinition($mapper_definition);
}

View file

@ -0,0 +1,171 @@
<?php
namespace Drupal\config_translation\Controller;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the config translation list builder for field entities.
*/
class ConfigTranslationFieldListBuilder extends ConfigTranslationEntityListBuilder {
/**
* The name of the entity type the fields are attached to.
*
* @var string
*/
protected $baseEntityType = '';
/**
* An array containing the base entity type's definition.
*
* @var array
*/
protected $baseEntityInfo = array();
/**
* The bundle info for the base entity type.
*
* @var array
*/
protected $baseEntityBundles = array();
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
$entity_manager = $container->get('entity.manager');
return new static(
$entity_type,
$entity_manager->getStorage($entity_type->id()),
$entity_manager
);
}
/**
* Constructs a new ConfigTranslationFieldListBuilder object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage class.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, EntityManagerInterface $entity_manager) {
parent::__construct($entity_type, $storage);
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function setMapperDefinition($mapper_definition) {
$this->baseEntityType = $mapper_definition['base_entity_type'];
$this->baseEntityInfo = $this->entityManager->getDefinition($this->baseEntityType);
$this->baseEntityBundles = $this->entityManager->getBundleInfo($this->baseEntityType);
return $this;
}
/**
* {@inheritdoc}
*/
public function load() {
// It is not possible to use the standard load method, because this needs
// all field entities only for the given baseEntityType.
$ids = \Drupal::entityQuery('field_config')
->condition('id', $this->baseEntityType . '.', 'STARTS_WITH')
->execute();
return $this->storage->loadMultiple($ids);
}
/**
* {@inheritdoc}
*/
public function getFilterLabels() {
$info = parent::getFilterLabels();
$bundle = $this->baseEntityInfo->getBundleLabel() ?: $this->t('Bundle');
$bundle = Unicode::strtolower($bundle);
$info['placeholder'] = $this->t('Enter field or @bundle', array('@bundle' => $bundle));
$info['description'] = $this->t('Enter a part of the field or @bundle to filter by.', array('@bundle' => $bundle));
return $info;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['label'] = array(
'data' => $entity->label(),
'class' => 'table-filter-text-source',
);
if ($this->displayBundle()) {
$bundle = $entity->get('bundle');
$row['bundle'] = array(
'data' => $this->baseEntityBundles[$bundle]['label'],
'class' => 'table-filter-text-source',
);
}
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['label'] = $this->t('Field');
if ($this->displayBundle()) {
$header['bundle'] = $this->baseEntityInfo->getBundleLabel() ?: $this->t('Bundle');
}
return $header + parent::buildHeader();
}
/**
* Controls the visibility of the bundle column on field list pages.
*
* @return bool
* Whenever the bundle is displayed or not.
*/
public function displayBundle() {
// The bundle key is explicitly defined in the entity definition.
if ($this->baseEntityInfo->getKey('bundle')) {
return TRUE;
}
// There is more than one bundle defined.
if (count($this->baseEntityBundles) > 1) {
return TRUE;
}
// The defined bundle ones not match the entity type name.
if (!empty($this->baseEntityBundles) && !isset($this->baseEntityBundles[$this->baseEntityType])) {
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function sortRows($a, $b) {
return $this->sortRowsMultiple($a, $b, array('bundle', 'label'));
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace Drupal\config_translation\Controller;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Defines the configuration translation list controller.
*/
class ConfigTranslationListController extends ControllerBase {
/**
* The mapper manager.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $mapperManager;
/**
* Constructs a new ConfigTranslationListController object.
*
* @param \Drupal\config_translation\ConfigMapperManagerInterface $mapper_manager
* The config mapper manager.
*/
public function __construct(ConfigMapperManagerInterface $mapper_manager) {
$this->mapperManager = $mapper_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.config_translation.mapper')
);
}
/**
* Provides the listing page for any entity type.
*
* @param string $mapper_id
* The name of the mapper.
*
* @return array
* A render array as expected by drupal_render().
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* Throws an exception if a mapper plugin could not be instantiated from the
* mapper definition in the constructor.
*/
public function listing($mapper_id) {
$mapper_definition = $this->mapperManager->getDefinition($mapper_id);
$mapper = $this->mapperManager->createInstance($mapper_id, $mapper_definition);
if (!$mapper) {
throw new NotFoundHttpException();
}
$entity_type = $mapper->getType();
// If the mapper, for example the mapper for fields, has a custom list
// controller defined, use it. Other mappers, for examples the ones for
// node_type and block, fallback to the generic configuration translation
// list controller.
$build = $this->entityManager()
->getHandler($entity_type, 'config_translation_list')
->setMapperDefinition($mapper_definition)
->render();
$build['#title'] = $mapper->getTypeLabel();
return $build;
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\config_translation\Controller;
use Drupal\config_translation\ConfigMapperInterface;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the configuration translation mapper list.
*
* Groups all defined configuration mapper instances by weight.
*/
class ConfigTranslationMapperList extends ControllerBase {
/**
* A array of configuration mapper instances.
*
* @var \Drupal\config_translation\ConfigMapperInterface[]
*/
protected $mappers;
/**
* Constructs a new ConfigTranslationMapperList object.
*
* @param \Drupal\config_translation\ConfigMapperInterface[] $mappers
* The configuration mapper manager.
*/
public function __construct(array $mappers) {
$this->mappers = $mappers;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.config_translation.mapper')->getMappers()
);
}
/**
* Builds the mappers as a renderable array for table.html.twig.
*
* @return array
* Renderable array with config translation mappers.
*/
public function render() {
$build = array(
'#type' => 'table',
'#header' => $this->buildHeader(),
'#rows' => array(),
);
$mappers = array();
foreach ($this->mappers as $mapper) {
if ($row = $this->buildRow($mapper)) {
$mappers[$mapper->getWeight()][] = $row;
}
}
// Group by mapper weight and sort by label.
ksort($mappers);
foreach ($mappers as $weight => $mapper) {
usort($mapper, function ($a, $b) {
$a_title = (isset($a['label'])) ? $a['label'] : '';
$b_title = (isset($b['label'])) ? $b['label'] : '';
return strnatcasecmp($a_title, $b_title);
});
$mappers[$weight] = $mapper;
}
foreach ($mappers as $mapper) {
$build['#rows'] = array_merge($build['#rows'], $mapper);
}
return $build;
}
/**
* Builds a row for a mapper in the mapper listing.
*
* @param \Drupal\config_translation\ConfigMapperInterface $mapper
* The mapper.
*
* @return array
* A render array structure of fields for this mapper.
*/
public function buildRow(ConfigMapperInterface $mapper) {
$row['label'] = $mapper->getTypeLabel();
$row['operations']['data'] = $this->buildOperations($mapper);
return $row;
}
/**
* Builds the header row for the mapper listing.
*
* @return array
* A render array structure of header strings.
*/
public function buildHeader() {
$row['Label'] = $this->t('Label');
$row['operations'] = $this->t('Operations');
return $row;
}
/**
* Builds a renderable list of operation links for the entity.
*
* @param \Drupal\config_translation\ConfigMapperInterface $mapper
* The mapper.
*
* @return array
* A renderable array of operation links.
*
* @see \Drupal\Core\Entity\EntityList::buildOperations()
*/
protected function buildOperations(ConfigMapperInterface $mapper) {
// Retrieve and sort operations.
$operations = $mapper->getOperations();
uasort($operations, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
$build = array(
'#type' => 'operations',
'#links' => $operations,
);
return $build;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Drupal\config_translation\Exception;
/**
* Provides an exception for configuration mappers with multiple languages.
*/
class ConfigMapperLanguageException extends \RuntimeException {
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\config_translation\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Defines a form for adding configuration translations.
*/
class ConfigTranslationAddForm extends ConfigTranslationFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'config_translation_add_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, RouteMatchInterface $route_match = NULL, $plugin_id = NULL, $langcode = NULL) {
$form = parent::buildForm($form, $form_state, $route_match, $plugin_id, $langcode);
$form['#title'] = $this->t('Add @language translation for %label', array(
'%label' => $this->mapper->getTitle(),
'@language' => $this->language->getName(),
));
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
drupal_set_message($this->t('Successfully saved @language translation.', array('@language' => $this->language->getName())));
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace Drupal\config_translation\Form;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Builds a form to delete configuration translation.
*/
class ConfigTranslationDeleteForm extends ConfirmFormBase {
/**
* The language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The configuration mapper manager.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $configMapperManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The configuration translation to be deleted.
*
* @var \Drupal\config_translation\ConfigMapperInterface
*/
protected $mapper;
/**
* The language of configuration translation.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
protected $language;
/**
* Constructs a ConfigTranslationDeleteForm.
*
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language override configuration storage.
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The configuration mapper manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ConfigurableLanguageManagerInterface $language_manager, ConfigMapperManagerInterface $config_mapper_manager, ModuleHandlerInterface $module_handler) {
$this->languageManager = $language_manager;
$this->configMapperManager = $config_mapper_manager;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('language_manager'),
$container->get('plugin.manager.config_translation.mapper'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete the @language translation of %label?', array('%label' => $this->mapper->getTitle(), '@language' => $this->language->getName()));
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url($this->mapper->getOverviewRouteName(), $this->mapper->getOverviewRouteParameters());
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'config_translation_delete_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, RouteMatchInterface $route_match = NULL, $plugin_id = NULL, $langcode = NULL) {
/** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
$mapper = $this->configMapperManager->createInstance($plugin_id);
$mapper->populateFromRouteMatch($route_match);
$language = $this->languageManager->getLanguage($langcode);
if (!$language) {
throw new NotFoundHttpException();
}
$this->mapper = $mapper;
$this->language = $language;
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
foreach ($this->mapper->getConfigNames() as $name) {
$this->languageManager->getLanguageConfigOverride($this->language->getId(), $name)->delete();
}
// Flush all persistent caches.
$this->moduleHandler->invokeAll('cache_flush');
foreach (Cache::getBins() as $cache_backend) {
$cache_backend->deleteAll();
}
drupal_set_message($this->t('@language translation of %label was deleted', array('%label' => $this->mapper->getTitle(), '@language' => $this->language->getName())));
$form_state->setRedirectUrl($this->getCancelUrl());
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\config_translation\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Defines a form for editing configuration translations.
*/
class ConfigTranslationEditForm extends ConfigTranslationFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'config_translation_edit_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, RouteMatchInterface $route_match = NULL, $plugin_id = NULL, $langcode = NULL) {
$form = parent::buildForm($form, $form_state, $route_match, $plugin_id, $langcode);
$form['#title'] = $this->t('Edit @language translation for %label', array(
'%label' => $this->mapper->getTitle(),
'@language' => $this->language->getName(),
));
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
drupal_set_message($this->t('Successfully updated @language translation.', array('@language' => $this->language->getName())));
}
}

View file

@ -0,0 +1,249 @@
<?php
namespace Drupal\config_translation\Form;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\Form\BaseFormIdInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Provides a base form for configuration translations.
*/
abstract class ConfigTranslationFormBase extends FormBase implements BaseFormIdInterface {
/**
* The typed configuration manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* The configuration mapper manager.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $configMapperManager;
/**
* The mapper for configuration translation.
*
* @var \Drupal\config_translation\ConfigMapperInterface
*/
protected $mapper;
/**
* The language manager.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The language of the configuration translation.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
protected $language;
/**
* The language of the configuration translation source.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
protected $sourceLanguage;
/**
* An array of base language configuration data keyed by configuration names.
*
* @var array
*/
protected $baseConfigData = array();
/**
* Constructs a ConfigTranslationFormBase.
*
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
* The typed configuration manager.
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The configuration mapper manager.
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The configurable language manager.
*/
public function __construct(TypedConfigManagerInterface $typed_config_manager, ConfigMapperManagerInterface $config_mapper_manager, ConfigurableLanguageManagerInterface $language_manager) {
$this->typedConfigManager = $typed_config_manager;
$this->configMapperManager = $config_mapper_manager;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.typed'),
$container->get('plugin.manager.config_translation.mapper'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return 'config_translation_form';
}
/**
* Implements \Drupal\Core\Form\FormInterface::buildForm().
*
* Builds configuration form with metadata and values from the source
* language.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* (optional) The route match.
* @param string $plugin_id
* (optional) The plugin ID of the mapper.
* @param string $langcode
* (optional) The language code of the language the form is adding or
* editing.
*
* @return array
* The form structure.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* Throws an exception if the language code provided as a query parameter in
* the request does not match an active language.
*/
public function buildForm(array $form, FormStateInterface $form_state, RouteMatchInterface $route_match = NULL, $plugin_id = NULL, $langcode = NULL) {
/** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
$mapper = $this->configMapperManager->createInstance($plugin_id);
$mapper->populateFromRouteMatch($route_match);
$language = $this->languageManager->getLanguage($langcode);
if (!$language) {
throw new NotFoundHttpException();
}
$this->mapper = $mapper;
$this->language = $language;
// ConfigTranslationFormAccess will not grant access if this raises an
// exception, so we can call this without a try-catch block here.
$langcode = $this->mapper->getLangcode();
$this->sourceLanguage = $this->languageManager->getLanguage($langcode);
// Get base language configuration to display in the form before setting the
// language to use for the form. This avoids repetitively settings and
// resetting the language to get original values later.
$this->baseConfigData = $this->mapper->getConfigData();
// Set the translation target language on the configuration factory.
$original_language = $this->languageManager->getConfigOverrideLanguage();
$this->languageManager->setConfigOverrideLanguage($this->language);
// Add some information to the form state for easier form altering.
$form_state->set('config_translation_mapper', $this->mapper);
$form_state->set('config_translation_language', $this->language);
$form_state->set('config_translation_source_language', $this->sourceLanguage);
$form['#attached']['library'][] = 'config_translation/drupal.config_translation.admin';
// Even though this is a nested form, we do not set #tree to TRUE because
// the form value structure is generated by using #parents for each element.
// @see \Drupal\config_translation\FormElement\FormElementBase::getElements()
$form['config_names'] = array('#type' => 'container');
foreach ($this->mapper->getConfigNames() as $name) {
$form['config_names'][$name] = array('#type' => 'container');
$schema = $this->typedConfigManager->get($name);
$source_config = $this->baseConfigData[$name];
$translation_config = $this->configFactory()->get($name)->get();
if ($form_element = $this->createFormElement($schema)) {
$parents = array('config_names', $name);
$form['config_names'][$name] += $form_element->getTranslationBuild($this->sourceLanguage, $this->language, $source_config, $translation_config, $parents);
}
}
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save translation'),
'#button_type' => 'primary',
);
// Set the configuration language back.
$this->languageManager->setConfigOverrideLanguage($original_language);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_values = $form_state->getValue(array('translation', 'config_names'));
foreach ($this->mapper->getConfigNames() as $name) {
$schema = $this->typedConfigManager->get($name);
// Set configuration values based on form submission and source values.
$base_config = $this->configFactory()->getEditable($name);
$config_translation = $this->languageManager->getLanguageConfigOverride($this->language->getId(), $name);
$element = $this->createFormElement($schema);
$element->setConfig($base_config, $config_translation, $form_values[$name]);
// If no overrides, delete language specific configuration file.
$saved_config = $config_translation->get();
if (empty($saved_config)) {
$config_translation->delete();
}
else {
$config_translation->save();
}
}
$form_state->setRedirect(
$this->mapper->getOverviewRoute(),
$this->mapper->getOverviewRouteParameters()
);
}
/**
* Creates a form element builder.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $schema
* Schema definition of configuration.
*
* @return \Drupal\config_translation\FormElement\ElementInterface|null
* The element builder object if possible.
*/
public static function createFormElement(TypedDataInterface $schema) {
$definition = $schema->getDataDefinition();
// Form element classes can be specified even for non-translatable elements
// such as the ListElement form element which is used for Mapping and
// Sequence schema elements.
if (isset($definition['form_element_class'])) {
if (!$definition->getLabel()) {
$definition->setLabel(t('n/a'));
}
$class = $definition['form_element_class'];
return $class::create($schema);
}
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\config_translation\FormElement;
use Drupal\Core\Language\LanguageInterface;
/**
* Defines the date format element for the configuration translation interface.
*/
class DateFormat extends FormElementBase {
/**
* {@inheritdoc}
*/
public function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');
$description = $this->t('A user-defined date format. See the <a href="http://php.net/manual/function.date.php">PHP manual</a> for available options.');
$format = $this->t('Displayed as %date_format', array('%date_format' => $date_formatter->format(REQUEST_TIME, 'custom', $translation_config)));
return [
'#type' => 'textfield',
'#description' => $description,
'#field_suffix' => ' <small data-drupal-date-formatter="preview">' . $format . '</small>',
'#attributes' => [
'data-drupal-date-formatter' => 'source',
],
'#attached' => [
'drupalSettings' => ['dateFormats' => $date_formatter->getSampleDateFormats($translation_language->getId())],
'library' => ['system/drupal.system.date'],
],
] + parent::getTranslationElement($translation_language, $source_config, $translation_config);
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Drupal\config_translation\FormElement;
use Drupal\Core\Config\Config;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\language\Config\LanguageConfigOverride;
/**
* Provides an interface for configuration translation form elements.
*/
interface ElementInterface {
/**
* Creates a form element instance from a schema definition.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $schema
* The configuration schema.
*
* @return static
*/
public static function create(TypedDataInterface $schema);
/**
* Builds a render array containg the source and translation form elements.
*
* @param \Drupal\Core\Language\LanguageInterface $source_language
* The source language of the configuration object.
* @param \Drupal\Core\Language\LanguageInterface $translation_language
* The language to display the translation form for.
* @param mixed $source_config
* The configuration value of the element in the source language.
* @param mixed $translation_config
* The configuration value of the element in the language to translate to.
* @param array $parents
* Parents array for the element in the form.
* @param string|null $base_key
* (optional) Base key to be used for the elements in the form. NULL for
* top-level form elements.
*
* @return array
* A render array consisting of the source and translation elements for the
* source value.
*/
public function getTranslationBuild(LanguageInterface $source_language, LanguageInterface $translation_language, $source_config, $translation_config, array $parents, $base_key = NULL);
/**
* Sets configuration based on a nested form value array.
*
* If the configuration values are the same as the source configuration, the
* override should be removed from the translation configuration.
*
* @param \Drupal\Core\Config\Config $base_config
* Base configuration values, in the source language.
* @param \Drupal\language\Config\LanguageConfigOverride $config_translation
* Translation configuration override data.
* @param mixed $config_values
* The configuration value of the element taken from the form values.
* @param string|null $base_key
* (optional) The base key that the schema and the configuration values
* belong to. This should be NULL for the top-level configuration object and
* be populated consecutively when recursing into the configuration
* structure.
*/
public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL);
}

View file

@ -0,0 +1,185 @@
<?php
namespace Drupal\config_translation\FormElement;
use Drupal\Core\Config\Config;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\language\Config\LanguageConfigOverride;
/**
* Provides a common base class for form elements.
*/
abstract class FormElementBase implements ElementInterface {
use StringTranslationTrait;
/**
* The schema element this form is for.
*
* @var \Drupal\Core\TypedData\TypedDataInterface
*/
protected $element;
/**
* The data definition of the element this form element is for.
*
* @var \Drupal\Core\TypedData\DataDefinitionInterface
*/
protected $definition;
/**
* Constructs a FormElementBase.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $element
* The schema element this form element is for.
*/
public function __construct(TypedDataInterface $element) {
$this->element = $element;
$this->definition = $element->getDataDefinition();
}
/**
* {@inheritdoc}
*/
public static function create(TypedDataInterface $schema) {
return new static($schema);
}
/**
* {@inheritdoc}
*/
public function getTranslationBuild(LanguageInterface $source_language, LanguageInterface $translation_language, $source_config, $translation_config, array $parents, $base_key = NULL) {
$build['#theme'] = 'config_translation_manage_form_element';
// For accessibility we make source and translation appear next to each
// other in the source for each element, which is why we utilize the
// 'source' and 'translation' sub-keys for the form. The form values,
// however, should mirror the configuration structure, so that we can
// traverse the configuration schema and still access the right
// configuration values in ConfigTranslationFormBase::setConfig().
// Therefore we make the 'source' and 'translation' keys the top-level
// keys in $form_state['values'].
$build['source'] = $this->getSourceElement($source_language, $source_config);
$build['translation'] = $this->getTranslationElement($translation_language, $source_config, $translation_config);
$build['source']['#parents'] = array_merge(array('source'), $parents);
$build['translation']['#parents'] = array_merge(array('translation'), $parents);
return $build;
}
/**
* Returns the source element for a given configuration definition.
*
* This can be either a render array that actually outputs the source values
* directly or a read-only form element with the source values depending on
* what is considered to provide a more intuitive user interface for the
* translator.
*
* @param \Drupal\Core\Language\LanguageInterface $source_language
* Thee source language of the configuration object.
* @param mixed $source_config
* The configuration value of the element in the source language.
*
* @return array
* A render array for the source value.
*/
protected function getSourceElement(LanguageInterface $source_language, $source_config) {
if ($source_config) {
$value = '<span lang="' . $source_language->getId() . '">' . nl2br($source_config) . '</span>';
}
else {
$value = $this->t('(Empty)');
}
return array(
'#type' => 'item',
'#title' => $this->t('@label <span class="visually-hidden">(@source_language)</span>', array(
// Labels originate from configuration schema and are translatable.
'@label' => $this->t($this->definition->getLabel()),
'@source_language' => $source_language->getName(),
)),
'#markup' => $value,
);
}
/**
* Returns the translation form element for a given configuration definition.
*
* For complex data structures (such as mappings) that are translatable
* wholesale but contain non-translatable properties, the form element is
* responsible for checking access to the source value of those properties. In
* case of formatted text, for example, access to the source text format must
* be checked. If the translator does not have access to the text format, the
* textarea must be disabled and the translator may not be able to translate
* this particular configuration element. If the translator does have access
* to the text format, the element must be locked down to that particular text
* format; in other words, the format may not be changed by the translator
* (because the text format property is not itself translatable).
*
* In addition, the form element is responsible for checking whether the
* value of such non-translatable properties in the translated configuration
* is equal to the corresponding source values. If not, that means that the
* source value has changed after the translation was added. In this case -
* again - the translation of this element must be disabled if the translator
* does not have access to the source value of the non-translatable property.
* For example, if a formatted text element, whose source format was plain
* text when it was first translated, gets changed to the Full HTML format,
* simply changing the format of the translation would lead to an XSS
* vulnerability as the translated text, that was intended to be escaped,
* would now be displayed unescaped. Thus, if the translator does not have
* access to the Full HTML format, the translation for this particular element
* may not be updated at all (the textarea must be disabled). Only if access
* to the Full HTML format is granted, an explicit translation taking into
* account the updated source value(s) may be submitted.
*
* In the specific case of formatted text this logic is implemented by
* utilizing a form element of type 'text_format' and its #format and
* #allowed_formats properties. The access logic explained above is then
* handled by the 'text_format' element itself, specifically by
* filter_process_format(). In case such a rich element is not available for
* translation of complex data, similar access logic must be implemented
* manually.
*
* @param \Drupal\Core\Language\LanguageInterface $translation_language
* The language to display the translation form for.
* @param mixed $source_config
* The configuration value of the element in the source language.
* @param mixed $translation_config
* The configuration value of the element in the language to translate to.
*
* @return array
* Form API array to represent the form element.
*
* @see \Drupal\config_translation\FormElement\TextFormat
* @see filter_process_format()
*/
protected function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
// Add basic properties that apply to all form elements.
return array(
'#title' => $this->t('@label <span class="visually-hidden">(@source_language)</span>', array(
// Labels originate from configuration schema and are translatable.
'@label' => $this->t($this->definition->getLabel()),
'@source_language' => $translation_language->getName(),
)),
'#default_value' => $translation_config,
'#attributes' => array('lang' => $translation_language->getId()),
);
}
/**
* {@inheritdoc}
*/
public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL) {
// Save the configuration values, if they are different from the source
// values in the base configuration. Otherwise remove the override.
if ($base_config->get($base_key) !== $config_values) {
$config_translation->set($base_key, $config_values);
}
else {
$config_translation->clear($base_key);
}
}
}

View file

@ -0,0 +1,131 @@
<?php
namespace Drupal\config_translation\FormElement;
use Drupal\Core\Config\Config;
use Drupal\Core\Language\LanguageInterface;
use Drupal\config_translation\Form\ConfigTranslationFormBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TraversableTypedDataInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\language\Config\LanguageConfigOverride;
/**
* Defines the list element for the configuration translation interface.
*/
class ListElement implements ElementInterface {
use StringTranslationTrait;
/**
* The schema element this form is for.
*
* @var \Drupal\Core\TypedData\TraversableTypedDataInterface
*/
protected $element;
/**
* Constructs a ListElement.
*
* @param \Drupal\Core\TypedData\TraversableTypedDataInterface $element
* The schema element this form element is for.
*/
public function __construct(TraversableTypedDataInterface $element) {
$this->element = $element;
}
/**
* {@inheritdoc}
*/
public static function create(TypedDataInterface $schema) {
return new static($schema);
}
/**
* {@inheritdoc}
*/
public function getTranslationBuild(LanguageInterface $source_language, LanguageInterface $translation_language, $source_config, $translation_config, array $parents, $base_key = NULL) {
$build = array();
foreach ($this->element as $key => $element) {
$sub_build = array();
$element_key = isset($base_key) ? "$base_key.$key" : $key;
$definition = $element->getDataDefinition();
if ($form_element = ConfigTranslationFormBase::createFormElement($element)) {
$element_parents = array_merge($parents, array($key));
$sub_build += $form_element->getTranslationBuild($source_language, $translation_language, $source_config[$key], $translation_config[$key], $element_parents, $element_key);
if (empty($sub_build)) {
continue;
}
// Build the sub-structure and include it with a wrapper in the form if
// there are any translatable elements there.
$build[$key] = array();
if ($element instanceof TraversableTypedDataInterface) {
$build[$key] = array(
'#type' => 'details',
'#title' => $this->getGroupTitle($definition, $sub_build),
'#open' => empty($base_key),
);
}
$build[$key] += $sub_build;
}
}
return $build;
}
/**
* {@inheritdoc}
*/
public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL) {
foreach ($this->element as $key => $element) {
$element_key = isset($base_key) ? "$base_key.$key" : $key;
if ($form_element = ConfigTranslationFormBase::createFormElement($element)) {
// Traverse into the next level of the configuration.
$value = isset($config_values[$key]) ? $config_values[$key] : NULL;
$form_element->setConfig($base_config, $config_translation, $value, $element_key);
}
}
}
/**
* Returns the title for the 'details' element of a group of schema elements.
*
* For some configuration elements the same element structure can be repeated
* multiple times (for example views displays, filters, etc.). Thus, we try to
* find a more usable title for the details summary. First check if there is
* an element which is called title or label and use its value. Then check if
* there is an element which contains these words and use those. Fall back
* to the generic definition label if no such element is found.
*
* @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
* The definition of the schema element.
* @param array $group_build
* The renderable array for the group of schema elements.
*
* @return string
* The title for the group of schema elements.
*/
protected function getGroupTitle(DataDefinitionInterface $definition, array $group_build) {
$title = '';
if (isset($group_build['title']['source'])) {
$title = $group_build['title']['source']['#markup'];
}
elseif (isset($group_build['label']['source'])) {
$title = $group_build['label']['source']['#markup'];
}
else {
foreach (array_keys($group_build) as $title_key) {
if (isset($group_build[$title_key]['source']) && (strpos($title_key, 'title') !== FALSE || strpos($title_key, 'label') !== FALSE)) {
$title = $group_build[$title_key]['source']['#markup'];
break;
}
}
}
return (!empty($title) ? (strip_tags($title) . ' ') : '') . $this->t($definition['label']);
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Drupal\config_translation\FormElement;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\Config;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Config\LanguageConfigOverride;
/**
* Defines form elements for plurals in configuration translation.
*/
class PluralVariants extends FormElementBase {
/**
* {@inheritdoc}
*/
protected function getSourceElement(LanguageInterface $source_language, $source_config) {
$plurals = $this->getNumberOfPlurals($source_language->getId());
$values = explode(LOCALE_PLURAL_DELIMITER, $source_config);
$element = array(
'#type' => 'fieldset',
'#title' => SafeMarkup::format('@label <span class="visually-hidden">(@source_language)</span>', array(
// Labels originate from configuration schema and are translatable.
'@label' => $this->t($this->definition->getLabel()),
'@source_language' => $source_language->getName(),
)),
'#tree' => TRUE,
);
for ($i = 0; $i < $plurals; $i++) {
$element[$i] = array(
'#type' => 'item',
// @todo Should use better labels https://www.drupal.org/node/2499639
'#title' => $i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form'),
'#markup' => SafeMarkup::format('<span lang="@langcode">@value</span>', array(
'@langcode' => $source_language->getId(),
'@value' => isset($values[$i]) ? $values[$i] : $this->t('(Empty)'),
)),
);
}
return $element;
}
/**
* {@inheritdoc}
*/
protected function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
$plurals = $this->getNumberOfPlurals($translation_language->getId());
$values = explode(LOCALE_PLURAL_DELIMITER, $translation_config);
$element = array(
'#type' => 'fieldset',
'#title' => SafeMarkup::format('@label <span class="visually-hidden">(@translation_language)</span>', array(
// Labels originate from configuration schema and are translatable.
'@label' => $this->t($this->definition->getLabel()),
'@translation_language' => $translation_language->getName(),
)),
'#tree' => TRUE,
);
for ($i = 0; $i < $plurals; $i++) {
$element[$i] = array(
'#type' => 'textfield',
// @todo Should use better labels https://www.drupal.org/node/2499639
'#title' => $i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form'),
'#default_value' => isset($values[$i]) ? $values[$i] : '',
'#attributes' => array('lang' => $translation_language->getId()),
);
}
return $element;
}
/**
* {@inheritdoc}
*/
public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL) {
$config_values = implode(LOCALE_PLURAL_DELIMITER, $config_values);
parent::setConfig($base_config, $config_translation, $config_values, $base_key);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\config_translation\FormElement;
use Drupal\Core\Language\LanguageInterface;
/**
* Defines the text_format element for the configuration translation interface.
*/
class TextFormat extends FormElementBase {
/**
* {@inheritdoc}
*/
public function getSourceElement(LanguageInterface $source_language, $source_config) {
// Instead of the formatted output show a disabled textarea. This allows for
// easier side-by-side comparison, especially with formats with text
// editors.
return $this->getTranslationElement($source_language, $source_config, $source_config) + array(
'#value' => $source_config['value'],
'#disabled' => TRUE,
'#allow_focus' => TRUE,
);
}
/**
* {@inheritdoc}
*/
public function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
return array(
'#type' => 'text_format',
// Override the #default_value property from the parent class.
'#default_value' => $translation_config['value'],
'#format' => $translation_config['format'],
// @see \Drupal\config_translation\Element\FormElementBase::getTranslationElement()
'#allowed_formats' => array($source_config['format']),
) + parent::getTranslationElement($translation_language, $source_config, $translation_config);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\config_translation\FormElement;
use Drupal\Core\Language\LanguageInterface;
/**
* Defines the textarea element for the configuration translation interface.
*/
class Textarea extends FormElementBase {
/**
* {@inheritdoc}
*/
public function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
// Estimate a comfortable size of the input textarea.
$rows_words = ceil(str_word_count($translation_config) / 5);
$rows_newlines = substr_count($translation_config, "\n" ) + 1;
$rows = max($rows_words, $rows_newlines);
return array(
'#type' => 'textarea',
'#rows' => $rows,
) + parent::getTranslationElement($translation_language, $source_config, $translation_config);
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Drupal\config_translation\FormElement;
use Drupal\Core\Language\LanguageInterface;
/**
* Defines the textfield element for the configuration translation interface.
*/
class Textfield extends FormElementBase {
/**
* {@inheritdoc}
*/
public function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
return array(
'#type' => 'textfield',
) + parent::getTranslationElement($translation_language, $source_config, $translation_config);
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\config_translation\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides dynamic contextual links for configuration translation.
*/
class ConfigTranslationContextualLinks extends DeriverBase implements ContainerDeriverInterface {
/**
* The mapper plugin discovery service.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $mapperManager;
/**
* Constructs a new ConfigTranslationContextualLinks.
*
* @param \Drupal\config_translation\ConfigMapperManagerInterface $mapper_manager
* The mapper plugin discovery service.
*/
public function __construct(ConfigMapperManagerInterface $mapper_manager) {
$this->mapperManager = $mapper_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('plugin.manager.config_translation.mapper')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
// Create contextual links for all mappers.
$mappers = $this->mapperManager->getMappers();
foreach ($mappers as $plugin_id => $mapper) {
// @todo Contextual groups do not map to entity types in a predictable
// way. See https://www.drupal.org/node/2134841 to make them
// predictable.
$group_name = $mapper->getContextualLinkGroup();
if (empty($group_name)) {
continue;
}
/** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
$route_name = $mapper->getOverviewRouteName();
$this->derivatives[$route_name] = $base_plugin_definition;
$this->derivatives[$route_name]['config_translation_plugin_id'] = $plugin_id;
$this->derivatives[$route_name]['class'] = '\Drupal\config_translation\Plugin\Menu\ContextualLink\ConfigTranslationContextualLink';
$this->derivatives[$route_name]['route_name'] = $route_name;
$this->derivatives[$route_name]['group'] = $group_name;
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Drupal\config_translation\Plugin\Derivative;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides dynamic local tasks for config translation.
*/
class ConfigTranslationLocalTasks extends DeriverBase implements ContainerDeriverInterface {
/**
* The mapper plugin discovery service.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $mapperManager;
/**
* The base plugin ID.
*
* @var string
*/
protected $basePluginId;
/**
* Constructs a new ConfigTranslationLocalTasks.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Drupal\config_translation\ConfigMapperManagerInterface $mapper_manager
* The mapper plugin discovery service.
*/
public function __construct($base_plugin_id, ConfigMapperManagerInterface $mapper_manager) {
$this->basePluginId = $base_plugin_id;
$this->mapperManager = $mapper_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('plugin.manager.config_translation.mapper')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$mappers = $this->mapperManager->getMappers();
foreach ($mappers as $plugin_id => $mapper) {
/** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
$route_name = $mapper->getOverviewRouteName();
$base_route = $mapper->getBaseRouteName();
if (!empty($base_route)) {
$this->derivatives[$route_name] = $base_plugin_definition;
$this->derivatives[$route_name]['config_translation_plugin_id'] = $plugin_id;
$this->derivatives[$route_name]['class'] = '\Drupal\config_translation\Plugin\Menu\LocalTask\ConfigTranslationLocalTask';
$this->derivatives[$route_name]['route_name'] = $route_name;
$this->derivatives[$route_name]['base_route'] = $base_route;
}
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Drupal\config_translation\Plugin\Menu\ContextualLink;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Menu\ContextualLinkDefault;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Defines a contextual link plugin with a dynamic title.
*/
class ConfigTranslationContextualLink extends ContextualLinkDefault {
use StringTranslationTrait;
/**
* The mapper plugin discovery service.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $mapperManager;
/**
* {@inheritdoc}
*/
public function getTitle() {
// Use the custom 'config_translation_plugin_id' plugin definition key to
// retrieve the title. We need to retrieve a runtime title (as opposed to
// storing the title on the plugin definition for the link) because it
// contains translated parts that we need in the runtime language.
$type_name = Unicode::strtolower($this->mapperManager()->createInstance($this->pluginDefinition['config_translation_plugin_id'])->getTypeLabel());
return $this->t('Translate @type_name', array('@type_name' => $type_name));
}
/**
* Gets the mapper manager.
*
* @return \Drupal\config_translation\ConfigMapperManagerInterface
* The mapper manager.
*/
protected function mapperManager() {
if (!$this->mapperManager) {
$this->mapperManager = \Drupal::service('plugin.manager.config_translation.mapper');
}
return $this->mapperManager;
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Drupal\config_translation\Plugin\Menu\LocalTask;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Menu\LocalTaskDefault;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Defines a local task plugin with a dynamic title.
*/
class ConfigTranslationLocalTask extends LocalTaskDefault {
use StringTranslationTrait;
/**
* The mapper plugin discovery service.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $mapperManager;
/**
* {@inheritdoc}
*/
public function getTitle() {
// Take custom 'config_translation_plugin_id' plugin definition key to
// retrieve title. We need to retrieve a runtime title (as opposed to
// storing the title on the plugin definition for the link) because
// it contains translated parts that we need in the runtime language.
$type_name = Unicode::strtolower($this->mapperManager()->createInstance($this->pluginDefinition['config_translation_plugin_id'])->getTypeLabel());
return $this->t('Translate @type_name', array('@type_name' => $type_name));
}
/**
* Gets the mapper manager.
*
* @return \Drupal\config_translation\ConfigMapperManagerInterface
* The mapper manager.
*/
protected function mapperManager() {
if (!$this->mapperManager) {
$this->mapperManager = \Drupal::service('plugin.manager.config_translation.mapper');
}
return $this->mapperManager;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\config_translation\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\RouteCollection;
/**
* Listens to the dynamic route events.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* The mapper plugin discovery service.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface
*/
protected $mapperManager;
/**
* Constructs a new RouteSubscriber.
*
* @param \Drupal\config_translation\ConfigMapperManagerInterface $mapper_manager
* The mapper plugin discovery service.
*/
public function __construct(ConfigMapperManagerInterface $mapper_manager) {
$this->mapperManager = $mapper_manager;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
$mappers = $this->mapperManager->getMappers($collection);
foreach ($mappers as $mapper) {
$collection->add($mapper->getOverviewRouteName(), $mapper->getOverviewRoute());
$collection->add($mapper->getAddRouteName(), $mapper->getAddRoute());
$collection->add($mapper->getEditRouteName(), $mapper->getEditRoute());
$collection->add($mapper->getDeleteRouteName(), $mapper->getDeleteRoute());
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Come after field_ui.
$events[RoutingEvents::ALTER] = array('onAlterRoutes', -110);
return $events;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\config_translation\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Tests the content translation behaviours on date formats.
*
* @group config_translation
*/
class ConfigTranslationDateFormatUiTest extends WebTestBase {
public static $modules = array(
'language',
'config_translation',
'system'
);
protected function setUp() {
parent::setUp();
// Enable additional languages.
$langcodes = ['de', 'es'];
foreach ($langcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
$user = $this->drupalCreateUser(array(
'administer site configuration',
'translate configuration',
));
$this->drupalLogin($user);
}
/**
* Tests date format translation behaviour.
*/
public function testDateFormatUI() {
$this->drupalGet('admin/config/regional/date-time');
// Assert translation link unlocked date format.
$this->assertLinkByHref('admin/config/regional/date-time/formats/manage/medium/translate');
// Assert translation link locked date format.
$this->assertLinkByHref('admin/config/regional/date-time/formats/manage/html_datetime/translate');
// Date pattern is visible on unlocked date formats.
$this->drupalGet('admin/config/regional/date-time/formats/manage/medium/translate/de/add');
$this->assertField('translation[config_names][core.date_format.medium][pattern]');
// Date pattern is not visible on locked date formats.
$this->drupalGet('admin/config/regional/date-time/formats/manage/html_datetime/translate/es/add');
$this->assertNoField('translation[config_names][core.date_format.html_datetime][pattern]');
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\config_translation\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Tests for altering configuration translation forms.
*
* @group config_translation
*/
class ConfigTranslationFormTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_translation', 'config_translation_test', 'editor');
/**
* The plugin ID of the mapper to test.
*
* @var string
*/
protected $pluginId;
/**
* The language code of the language to use for testing.
*
* @var string
*/
protected $langcode;
protected function setUp() {
parent::setUp();
$definitions = \Drupal::service('plugin.manager.config_translation.mapper')->getDefinitions();
$this->pluginId = key($definitions);
$this->langcode = 'xx';
ConfigurableLanguage::create(array('id' => $this->langcode, 'label' => 'XX'))->save();
\Drupal::state()->set('config_translation_test_alter_form_alter', TRUE);
}
/**
* Tests altering of the configuration translation forms.
*/
public function testConfigTranslationFormAlter() {
$form_builder = \Drupal::formBuilder();
$add_form = $form_builder->getForm('Drupal\config_translation\Form\ConfigTranslationAddForm', \Drupal::routeMatch(), $this->pluginId, $this->langcode);
$edit_form = $form_builder->getForm('Drupal\config_translation\Form\ConfigTranslationEditForm', \Drupal::routeMatch(), $this->pluginId, $this->langcode);
// Test that hook_form_BASE_FORM_ID_alter() was called for the base form ID
// 'config_translation_form'.
$this->assertTrue($add_form['#base_altered']);
$this->assertTrue($edit_form['#base_altered']);
// Test that hook_form_FORM_ID_alter() was called for the form IDs
// 'config_translation_add_form' and 'config_translation_edit_form'.
$this->assertTrue($add_form['#altered']);
$this->assertTrue($edit_form['#altered']);
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\config_translation\Tests;
use Drupal\simpletest\InstallerTestBase;
/**
* Installs the config translation module on a site installed in non english.
*
* @group config_translation
*/
class ConfigTranslationInstallTest extends InstallerTestBase {
/**
* {@inheritdoc}
*/
protected $langcode = 'eo';
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* {@inheritdoc}
*/
protected function setUpLanguage() {
// Place custom local translations in the translations directory.
mkdir(DRUPAL_ROOT . '/' . $this->siteDirectory . '/files/translations', 0777, TRUE);
file_put_contents(DRUPAL_ROOT . '/' . $this->siteDirectory . '/files/translations/drupal-8.0.0.eo.po', $this->getPo('eo'));
parent::setUpLanguage();
$this->translations['Save and continue'] = 'Save and continue eo';
}
/**
* Returns the string for the test .po file.
*
* @param string $langcode
* The language code.
* @return string
* Contents for the test .po file.
*/
protected function getPo($langcode) {
return <<<ENDPO
msgid ""
msgstr ""
msgid "Save and continue"
msgstr "Save and continue $langcode"
msgid "Anonymous"
msgstr "Anonymous $langcode"
msgid "Language"
msgstr "Language $langcode"
ENDPO;
}
public function testConfigTranslation() {
$this->drupalPostForm('admin/config/regional/language/add', ['predefined_langcode' => 'en'], t('Add custom language'));
$this->drupalPostForm('admin/config/regional/language/add', ['predefined_langcode' => 'fr'], t('Add custom language'));
$edit = [
'modules[Multilingual][config_translation][enable]' => TRUE,
];
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->drupalGet('/admin/structure/types/manage/article/fields');
$this->assertResponse(200);
}
}

View file

@ -0,0 +1,504 @@
<?php
namespace Drupal\config_translation\Tests;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Utility\Unicode;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\contact\Entity\ContactForm;
use Drupal\filter\Entity\FilterFormat;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Visit all lists.
*
* @group config_translation
* @see \Drupal\config_translation\Tests\ConfigTranslationViewListUiTest
*/
class ConfigTranslationListUiTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'block',
'config_translation',
'contact',
'block_content',
'field',
'field_ui',
'menu_ui',
'node',
'shortcut',
'taxonomy',
'image',
'responsive_image',
'toolbar',
);
/**
* Admin user with all needed permissions.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
$permissions = array(
'access site-wide contact form',
'administer blocks',
'administer contact forms',
'administer content types',
'administer block_content fields',
'administer filters',
'administer menu',
'administer node fields',
'administer permissions',
'administer shortcuts',
'administer site configuration',
'administer taxonomy',
'administer account settings',
'administer languages',
'administer image styles',
'administer responsive images',
'translate configuration',
);
// Create and log in user.
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
// Enable import of translations. By default this is disabled for automated
// tests.
$this->config('locale.settings')
->set('translation.import_enabled', TRUE)
->save();
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests the block listing for the translate operation.
*
* There are no blocks placed in the testing profile. Add one, then check
* for Translate operation.
*/
protected function doBlockListTest() {
// Add a test block, any block will do.
// Set the machine name so the translate link can be built later.
$id = Unicode::strtolower($this->randomMachineName(16));
$this->drupalPlaceBlock('system_powered_by_block', array('id' => $id));
// Get the Block listing.
$this->drupalGet('admin/structure/block');
$translate_link = 'admin/structure/block/manage/' . $id . '/translate';
// Test if the link to translate the block is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the menu listing for the translate operation.
*/
protected function doMenuListTest() {
// Create a test menu to decouple looking for translate operations link so
// this does not test more than necessary.
$this->drupalGet('admin/structure/menu/add');
// Lowercase the machine name.
$menu_name = Unicode::strtolower($this->randomMachineName(16));
$label = $this->randomMachineName(16);
$edit = array(
'id' => $menu_name,
'description' => '',
'label' => $label,
);
// Create the menu by posting the form.
$this->drupalPostForm('admin/structure/menu/add', $edit, t('Save'));
// Get the Menu listing.
$this->drupalGet('admin/structure/menu');
$translate_link = 'admin/structure/menu/manage/' . $menu_name . '/translate';
// Test if the link to translate the menu is on the page.
$this->assertLinkByHref($translate_link);
// Check if the Link is not added if you are missing 'translate
// configuration' permission.
$permissions = array(
'administer menu',
);
$this->drupalLogin($this->drupalCreateUser($permissions));
// Get the Menu listing.
$this->drupalGet('admin/structure/menu');
$translate_link = 'admin/structure/menu/manage/' . $menu_name . '/translate';
// Test if the link to translate the menu is NOT on the page.
$this->assertNoLinkByHref($translate_link);
// Log in as Admin again otherwise the rest will fail.
$this->drupalLogin($this->adminUser);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the vocabulary listing for the translate operation.
*/
protected function doVocabularyListTest() {
// Create a test vocabulary to decouple looking for translate operations
// link so this does not test more than necessary.
$vocabulary = Vocabulary::create([
'name' => $this->randomMachineName(),
'description' => $this->randomMachineName(),
'vid' => Unicode::strtolower($this->randomMachineName()),
]);
$vocabulary->save();
// Get the Taxonomy listing.
$this->drupalGet('admin/structure/taxonomy');
$translate_link = 'admin/structure/taxonomy/manage/' . $vocabulary->id() . '/translate';
// Test if the link to translate the vocabulary is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the custom block listing for the translate operation.
*/
public function doCustomContentTypeListTest() {
// Create a test custom block type to decouple looking for translate
// operations link so this does not test more than necessary.
$block_content_type = BlockContentType::create(array(
'id' => Unicode::strtolower($this->randomMachineName(16)),
'label' => $this->randomMachineName(),
'revision' => FALSE
));
$block_content_type->save();
// Get the custom block type listing.
$this->drupalGet('admin/structure/block/block-content/types');
$translate_link = 'admin/structure/block/block-content/manage/' . $block_content_type->id() . '/translate';
// Test if the link to translate the custom block type is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the contact forms listing for the translate operation.
*/
public function doContactFormsListTest() {
// Create a test contact form to decouple looking for translate operations
// link so this does not test more than necessary.
$contact_form = ContactForm::create([
'id' => Unicode::strtolower($this->randomMachineName(16)),
'label' => $this->randomMachineName(),
]);
$contact_form->save();
// Get the contact form listing.
$this->drupalGet('admin/structure/contact');
$translate_link = 'admin/structure/contact/manage/' . $contact_form->id() . '/translate';
// Test if the link to translate the contact form is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the content type listing for the translate operation.
*/
public function doContentTypeListTest() {
// Create a test content type to decouple looking for translate operations
// link so this does not test more than necessary.
$content_type = $this->drupalCreateContentType(array(
'type' => Unicode::strtolower($this->randomMachineName(16)),
'name' => $this->randomMachineName(),
));
// Get the content type listing.
$this->drupalGet('admin/structure/types');
$translate_link = 'admin/structure/types/manage/' . $content_type->id() . '/translate';
// Test if the link to translate the content type is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the formats listing for the translate operation.
*/
public function doFormatsListTest() {
// Create a test format to decouple looking for translate operations
// link so this does not test more than necessary.
$filter_format = FilterFormat::create(array(
'format' => Unicode::strtolower($this->randomMachineName(16)),
'name' => $this->randomMachineName(),
));
$filter_format->save();
// Get the format listing.
$this->drupalGet('admin/config/content/formats');
$translate_link = 'admin/config/content/formats/manage/' . $filter_format->id() . '/translate';
// Test if the link to translate the format is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the shortcut listing for the translate operation.
*/
public function doShortcutListTest() {
// Create a test shortcut to decouple looking for translate operations
// link so this does not test more than necessary.
$shortcut = ShortcutSet::create(array(
'id' => Unicode::strtolower($this->randomMachineName(16)),
'label' => $this->randomString(),
));
$shortcut->save();
// Get the shortcut listing.
$this->drupalGet('admin/config/user-interface/shortcut');
$translate_link = 'admin/config/user-interface/shortcut/manage/' . $shortcut->id() . '/translate';
// Test if the link to translate the shortcut is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the role listing for the translate operation.
*/
public function doUserRoleListTest() {
// Create a test role to decouple looking for translate operations
// link so this does not test more than necessary.
$role_id = Unicode::strtolower($this->randomMachineName(16));
$this->drupalCreateRole(array(), $role_id);
// Get the role listing.
$this->drupalGet('admin/people/roles');
$translate_link = 'admin/people/roles/manage/' . $role_id . '/translate';
// Test if the link to translate the role is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the language listing for the translate operation.
*/
public function doLanguageListTest() {
// Create a test language to decouple looking for translate operations
// link so this does not test more than necessary.
ConfigurableLanguage::createFromLangcode('ga')->save();
// Get the language listing.
$this->drupalGet('admin/config/regional/language');
$translate_link = 'admin/config/regional/language/edit/ga/translate';
// Test if the link to translate the language is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the image style listing for the translate operation.
*/
public function doImageStyleListTest() {
// Get the image style listing.
$this->drupalGet('admin/config/media/image-styles');
$translate_link = 'admin/config/media/image-styles/manage/medium/translate';
// Test if the link to translate the style is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the responsive image mapping listing for the translate operation.
*/
public function doResponsiveImageListTest() {
$edit = array();
$edit['label'] = $this->randomMachineName();
$edit['id'] = strtolower($edit['label']);
$edit['fallback_image_style'] = 'thumbnail';
$this->drupalPostForm('admin/config/media/responsive-image-style/add', $edit, t('Save'));
$this->assertRaw(t('Responsive image style %label saved.', array('%label' => $edit['label'])));
// Get the responsive image style listing.
$this->drupalGet('admin/config/media/responsive-image-style');
$translate_link = 'admin/config/media/responsive-image-style/' . $edit['id'] . '/translate';
// Test if the link to translate the style is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests the field listing for the translate operation.
*/
public function doFieldListTest() {
// Create a base content type.
$content_type = $this->drupalCreateContentType(array(
'type' => Unicode::strtolower($this->randomMachineName(16)),
'name' => $this->randomMachineName(),
));
// Create a block content type.
$block_content_type = BlockContentType::create(array(
'id' => 'basic',
'label' => 'Basic',
'revision' => FALSE
));
$block_content_type->save();
$field = FieldConfig::create([
// The field storage is guaranteed to exist because it is supplied by the
// block_content module.
'field_storage' => FieldStorageConfig::loadByName('block_content', 'body'),
'bundle' => $block_content_type->id(),
'label' => 'Body',
'settings' => array('display_summary' => FALSE),
]);
$field->save();
// Look at a few fields on a few entity types.
$pages = array(
array(
'list' => 'admin/structure/types/manage/' . $content_type->id() . '/fields',
'field' => 'node.' . $content_type->id() . '.body',
),
array(
'list' => 'admin/structure/block/block-content/manage/basic/fields',
'field' => 'block_content.basic.body',
),
);
foreach ($pages as $values) {
// Get fields listing.
$this->drupalGet($values['list']);
$translate_link = $values['list'] . '/' . $values['field'] . '/translate';
// Test if the link to translate the field is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
}
/**
* Tests the date format listing for the translate operation.
*/
public function doDateFormatListTest() {
// Get the date format listing.
$this->drupalGet('admin/config/regional/date-time');
$translate_link = 'admin/config/regional/date-time/formats/manage/long/translate';
// Test if the link to translate the format is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests a given settings page for the translate operation.
*
* @param string $link
* URL of the settings page to test.
*/
public function doSettingsPageTest($link) {
// Get the settings page.
$this->drupalGet($link);
$translate_link = $link . '/translate';
// Test if the link to translate the settings page is present.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
}
/**
* Tests if translate link is added to operations in all configuration lists.
*/
public function testTranslateOperationInListUi() {
// All lists based on paths provided by the module.
$this->doBlockListTest();
$this->doMenuListTest();
$this->doVocabularyListTest();
$this->doCustomContentTypeListTest();
$this->doContactFormsListTest();
$this->doContentTypeListTest();
$this->doFormatsListTest();
$this->doShortcutListTest();
$this->doUserRoleListTest();
$this->doLanguageListTest();
$this->doImageStyleListTest();
$this->doResponsiveImageListTest();
$this->doDateFormatListTest();
$this->doFieldListTest();
// Views is tested in Drupal\config_translation\Tests\ConfigTranslationViewListUiTest
// Test the maintenance settings page.
$this->doSettingsPageTest('admin/config/development/maintenance');
// Test the site information settings page.
$this->doSettingsPageTest('admin/config/system/site-information');
// Test the account settings page.
$this->doSettingsPageTest('admin/config/people/accounts');
// Test the RSS settings page.
$this->doSettingsPageTest('admin/config/services/rss-publishing');
}
}

View file

@ -0,0 +1,172 @@
<?php
namespace Drupal\config_translation\Tests;
use Drupal\Component\Utility\Html;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Translate settings and entities to various languages.
*
* @group config_translation
*/
class ConfigTranslationOverviewTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'config_test',
'config_translation',
'config_translation_test',
'contact',
'contextual',
'entity_test_operation',
'views',
'views_ui',
];
/**
* Languages to enable.
*
* @var array
*/
protected $langcodes = array('fr', 'ta');
/**
* String translation storage object.
*
* @var \Drupal\locale\StringStorageInterface
*/
protected $localeStorage;
protected function setUp() {
parent::setUp();
$permissions = array(
'translate configuration',
'administer languages',
'administer site configuration',
'administer contact forms',
'access site-wide contact form',
'access contextual links',
'administer views',
);
// Create and log in user.
$this->drupalLogin($this->drupalCreateUser($permissions));
// Add languages.
foreach ($this->langcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
$this->localeStorage = $this->container->get('locale.storage');
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests the config translation mapper page.
*/
public function testMapperListPage() {
$this->drupalGet('admin/config/regional/config-translation');
$this->assertLinkByHref('admin/config/regional/config-translation/config_test');
$this->assertLinkByHref('admin/config/people/accounts/translate');
// Make sure there is only a single operation for each dropbutton, either
// 'List' or 'Translate'.
foreach ($this->cssSelect('ul.dropbutton') as $i => $dropbutton) {
$this->assertIdentical(1, $dropbutton->count());
foreach ($dropbutton->li as $link) {
$this->assertTrue(((string) $link->a === 'Translate') || ((string) $link->a === 'List'));
}
}
$labels = array(
'&$nxd~i0',
'some "label" with quotes',
$this->randomString(),
);
foreach ($labels as $label) {
$test_entity = entity_create('config_test', array(
'id' => $this->randomMachineName(),
'label' => $label,
));
$test_entity->save();
$base_url = 'admin/structure/config_test/manage/' . $test_entity->id();
$this->drupalGet('admin/config/regional/config-translation/config_test');
$this->assertLinkByHref($base_url . '/translate');
$this->assertEscaped($test_entity->label());
// Make sure there is only a single 'Translate' operation for each
// dropbutton.
foreach ($this->cssSelect('ul.dropbutton') as $i => $dropbutton) {
$this->assertIdentical(1, $dropbutton->count());
foreach ($dropbutton->li as $link) {
$this->assertIdentical('Translate', (string) $link->a);
}
}
$entity_type = \Drupal::entityManager()->getDefinition($test_entity->getEntityTypeId());
$this->drupalGet($base_url . '/translate');
$title = $test_entity->label() . ' ' . $entity_type->getLowercaseLabel();
$title = 'Translations for <em class="placeholder">' . Html::escape($title) . '</em>';
$this->assertRaw($title);
$this->assertRaw('<th>' . t('Language') . '</th>');
$this->drupalGet($base_url);
$this->assertLink(t('Translate @title', array('@title' => $entity_type->getLowercaseLabel())));
}
}
/**
* Tests availability of hidden entities in the translation overview.
*/
public function testHiddenEntities() {
// Hidden languages are only available to translate through the
// configuration translation listings.
$this->drupalGet('admin/config/regional/config-translation/configurable_language');
$this->assertText('Not applicable');
$this->assertLinkByHref('admin/config/regional/language/edit/zxx/translate');
$this->assertText('Not specified');
$this->assertLinkByHref('admin/config/regional/language/edit/und/translate');
// Hidden date formats are only available to translate through the
// configuration translation listings. Test a couple of them.
$this->drupalGet('admin/config/regional/config-translation/date_format');
$this->assertText('HTML Date');
$this->assertLinkByHref('admin/config/regional/date-time/formats/manage/html_date/translate');
$this->assertText('HTML Year');
$this->assertLinkByHref('admin/config/regional/date-time/formats/manage/html_year/translate');
}
/**
* Tests that overrides do not affect listing screens.
*/
public function testListingPageWithOverrides() {
$original_label = 'Default';
$overridden_label = 'Overridden label';
$config_test_storage = $this->container->get('entity.manager')->getStorage('config_test');
// Set up an override.
$settings['config']['config_test.dynamic.dotted.default']['label'] = (object) array(
'value' => $overridden_label,
'required' => TRUE,
);
$this->writeSettings($settings);
// Test that the overridden label is loaded with the entity.
$this->assertEqual($config_test_storage->load('dotted.default')->label(), $overridden_label);
// Test that the original label on the listing page is intact.
$this->drupalGet('admin/config/regional/config-translation/config_test');
$this->assertText($original_label);
$this->assertNoText($overridden_label);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,76 @@
<?php
namespace Drupal\config_translation\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Verifies theme configuration translation settings.
*
* @group config_translation
*/
class ConfigTranslationUiThemeTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['config_translation', 'config_translation_test'];
/**
* Languages to enable.
*
* @var array
*/
protected $langcodes = array('fr', 'ta');
/**
* Administrator user for tests.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
$admin_permissions = [
'administer themes',
'administer languages',
'administer site configuration',
'translate configuration',
];
// Create and log in user.
$this->adminUser = $this->drupalCreateUser($admin_permissions);
// Add languages.
foreach ($this->langcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
}
/**
* Tests that theme provided *.config_translation.yml files are found.
*/
public function testThemeDiscovery() {
// Install the test theme and rebuild routes.
$theme = 'config_translation_test_theme';
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/appearance');
$elements = $this->xpath('//a[normalize-space()=:label and contains(@href, :theme)]', [
':label' => 'Install and set as default',
':theme' => $theme,
]);
$this->drupalGet($GLOBALS['base_root'] . $elements[0]['href'], ['external' => TRUE]);
$translation_base_url = 'admin/config/development/performance/translate';
$this->drupalGet($translation_base_url);
$this->assertResponse(200);
$this->assertLinkByHref("$translation_base_url/fr/add");
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\config_translation\Tests;
use Drupal\views_ui\Tests\UITestBase;
/**
* Visit view list and test if translate is available.
*
* @group config_translation
*/
class ConfigTranslationViewListUiTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('node', 'test_view');
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_translation', 'views_ui');
protected function setUp() {
parent::setUp();
$permissions = array(
'administer views',
'translate configuration',
);
// Create and log in user.
$this->drupalLogin($this->drupalCreateUser($permissions));
}
/**
* Tests views_ui list to see if translate link is added to operations.
*/
public function testTranslateOperationInViewListUi() {
// Views UI List 'admin/structure/views'.
$this->drupalGet('admin/structure/views');
$translate_link = 'admin/structure/views/view/test_view/translate';
// Test if the link to translate the test_view is on the page.
$this->assertLinkByHref($translate_link);
// Test if the link to translate actually goes to the translate page.
$this->drupalGet($translate_link);
$this->assertRaw('<th>' . t('Language') . '</th>');
// Test that the 'Edit' tab appears.
$this->assertLinkByHref('admin/structure/views/view/test_view');
}
}

View file

@ -0,0 +1,23 @@
{#
/**
* @file
* Default theme implementation for a form element in config_translation.
*
* Available variables:
* - element: Array that represents the element shown in the form.
* - source: The source of the translation.
* - translation: The translation for the target language.
*
* @see template_preprocess()
*
* @ingroup themeable
*/
#}
<div class="translation-set clearfix">
<div class="layout-column layout-column--half translation-set__source">
{{ element.source }}
</div>
<div class="layout-column layout-column--half translation-set__translated">
{{ element.translation }}
</div>
</div>

View file

@ -0,0 +1,10 @@
id: test
label: 'Test'
langcode: en
content:
value: "<p><strong>Hello World</strong></p>"
format: plain_text
animals:
- kitten
- llama
- elephant

View file

@ -0,0 +1,21 @@
# Schema for the configuration files of the Configuration translation test module.
config_translation_test.content:
type: config_object
label: 'Content'
mapping:
id:
type: string
label: 'Category identifier'
label:
type: label
label: 'Label'
content:
type: text_format
label: 'Content'
animals:
type: sequence
label: 'Animals'
sequence:
type: label

View file

@ -0,0 +1,6 @@
# Attach to file settings for testing. The base route does not matter.
system.file_system_settings:
title: 'Test config translation'
base_route_name: system.file_system_settings
names:
- config_translation_test.content

View file

@ -0,0 +1,9 @@
name: 'Configuration Translation Test'
description: 'Helpers to test the configuration translation system'
type: module
package: Testing
version: VERSION
core: 8.x
dependencies:
- config_translation
- config_test

View file

@ -0,0 +1,7 @@
# Add a default local task for the file system settings page, so that the local
# task added by Configuration Translation becomes visible. This facilitates
# manual testing.
system.file_system_settings:
route_name: system.file_system_settings
title: Settings
base_route: system.file_system_settings

View file

@ -0,0 +1,85 @@
<?php
/**
* @file
* Configuration Translation Test module.
*/
use Drupal\Core\Extension\Extension;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_system_info_alter().
*/
function config_translation_test_system_info_alter(array &$info, Extension $file, $type) {
// @see \Drupal\config_translation\Tests\ConfigTranslationUiThemeTest
if ($file->getType() == 'theme' && $file->getName() == 'config_translation_test_theme') {
$info['hidden'] = FALSE;
}
}
/**
* Implements hook_entity_type_alter().
*/
function config_translation_test_entity_type_alter(array &$entity_types) {
// Remove entity definition for these entity types from config_test module.
unset($entity_types['config_test_no_status']);
unset($entity_types['config_query_test']);
}
/**
* Implements hook_config_translation_info_alter().
*/
function config_translation_test_config_translation_info_alter(&$info) {
if (\Drupal::state()->get('config_translation_test_config_translation_info_alter')) {
// Limit account settings config files to only one of them.
$info['entity.user.admin_form']['names'] = array('user.settings');
// Add one more config file to the site information page.
$info['system.site_information_settings']['names'][] = 'system.rss';
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for ConfigTranslationFormBase.
*
* Adds a list of configuration names to the top of the configuration
* translation form.
*
* @see \Drupal\config_translation\Form\ConfigTranslationFormBase
*/
function config_translation_test_form_config_translation_form_alter(&$form, FormStateInterface $form_state) {
if (\Drupal::state()->get('config_translation_test_alter_form_alter')) {
$form['#base_altered'] = TRUE;
}
}
/**
* Implements hook_form_FORM_ID_alter() for ConfigTranslationAddForm.
*
* Changes the title to include the source language.
*
* @see \Drupal\config_translation\Form\ConfigTranslationAddForm
*/
function config_translation_test_form_config_translation_add_form_alter(&$form, FormStateInterface $form_state) {
if (\Drupal::state()->get('config_translation_test_alter_form_alter')) {
$form['#altered'] = TRUE;
}
}
/**
* Implements hook_form_FORM_ID_alter() for ConfigTranslationEditForm.
*
* Adds a column to the configuration translation edit form that shows the
* current translation. Note that this column would not be displayed by default,
* as the columns are hardcoded in
* config_translation_manage_form_element.html.twig. The template would need to
* be overridden for the column to be displayed.
*
* @see \Drupal\config_translation\Form\ConfigTranslationEditForm
*/
function config_translation_test_form_config_translation_edit_form_alter(&$form, FormStateInterface $form_state) {
if (\Drupal::state()->get('config_translation_test_alter_form_alter')) {
$form['#altered'] = TRUE;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\Tests\config_translation\Kernel\Migrate\d6;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade i18n maintenance variables to system.*.yml.
*
* @group migrate_drupal_6
*/
class MigrateI18nSystemMaintenanceTest extends MigrateDrupal6TestBase {
public static $modules = ['language', 'config_translation'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('d6_i18n_system_maintenance');
}
/**
* Tests migration of system (maintenance) variables to system.maintenance.yml.
*/
public function testSystemMaintenance() {
$config = \Drupal::service('language_manager')->getLanguageConfigOverride('fr', 'system.maintenance');
$this->assertIdentical('fr - Drupal is currently under maintenance. We should be back shortly. Thank you for your patience.', $config->get('message'));
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Drupal\Tests\config_translation\Kernel\Migrate\d6;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade i18n_strings site variables to system.*.yml.
*
* @group migrate_drupal_6
*/
class MigrateI18nSystemSiteTest extends MigrateDrupal6TestBase {
public static $modules = ['language', 'config_translation'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('d6_i18n_system_site');
}
/**
* Tests migration of system (site) variables to system.site.yml.
*/
public function testSystemSite() {
$config_translation = \Drupal::service('language_manager')->getLanguageConfigOverride('fr', 'system.site');
$this->assertIdentical('fr site name', $config_translation->get('name'));
$this->assertIdentical('fr_site_mail@example.com', $config_translation->get('mail'));
$this->assertIdentical('fr Migrate rocks', $config_translation->get('slogan'));
$this->assertIdentical('/fr-user', $config_translation->get('page.403'));
$this->assertIdentical('/fr-page-not-found', $config_translation->get('page.404'));
$this->assertIdentical('/node', $config_translation->get('page.front'));
$this->assertIdentical(NULL, $config_translation->get('admin_compact_mode'));
$config_translation = \Drupal::service('language_manager')->getLanguageConfigOverride('zu', 'system.site');
$this->assertIdentical('zu - site_name', $config_translation->get('name'));
$this->assertIdentical('site_mail@example.com', $config_translation->get('mail'));
$this->assertIdentical('Migrate rocks', $config_translation->get('slogan'));
$this->assertIdentical('/zu-user', $config_translation->get('page.403'));
$this->assertIdentical('/zu-page-not-found', $config_translation->get('page.404'));
$this->assertIdentical('/node', $config_translation->get('page.front'));
$this->assertIdentical(NULL, $config_translation->get('admin_compact_mode'));
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\Tests\config_translation\Kernel\Migrate\d6;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade i18n variables to user.*.yml.
*
* @group migrate_drupal_6
*/
class MigrateI18nUserConfigsTest extends MigrateDrupal6TestBase {
use SchemaCheckTestTrait;
public static $modules = ['language', 'locale', 'config_translation'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('locale',
['locales_source', 'locales_target', 'locales_location']);
$this->executeMigrations(['d6_i18n_user_mail', 'd6_i18n_user_settings']);
}
/**
* Tests migration of i18n user variables to user.mail.yml.
*/
public function testUserMail() {
$config = \Drupal::service('language_manager')->getLanguageConfigOverride('fr', 'user.mail');
$this->assertIdentical('fr - Account details for [user:name] at [site:name] (approved)', $config->get('status_activated.subject'));
$this->assertIdentical("fr - [user:name],\r\n\r\nYour account at [site:name] has been activated.\r\n\r\nYou may now log in by clicking on this link or copying and pasting it in your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis is a one-time login, so it can be used only once.\r\n\r\nAfter logging in, you will be redirected to [user:edit-url] so you can change your password.\r\n\r\nOnce you have set your own password, you will be able to log in to [site:login-url] in the future using:\r\n\r\nusername: [user:name]\r\n", $config->get('status_activated.body'));
$this->assertIdentical('fr - Replacement login information for [user:name] at [site:name]', $config->get('password_reset.subject'));
$this->assertIdentical("fr - [user:name],\r\n\r\nA request to reset the password for your account has been made at [site:name].\r\n\r\nYou may now log in to [site:url-brief] by clicking on this link or copying and pasting it in your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis is a one-time login, so it can be used only once. It expires after one day and nothing will happen if it's not used.\r\n\r\nAfter logging in, you will be redirected to [user:edit-url] so you can change your password.", $config->get('password_reset.body'));
$this->assertIdentical('fr - Account details for [user:name] at [site:name] (deleted)', $config->get('cancel_confirm.subject'));
$this->assertIdentical("fr - [user:name],\r\n\r\nYour account on [site:name] has been deleted.", $config->get('cancel_confirm.body'));
$this->assertIdentical('fr - An administrator created an account for you at [site:name]', $config->get('register_admin_created.subject'));
$this->assertIdentical("fr - [user:name],\r\n\r\nA site administrator at [site:name] has created an account for you. You may now log in to [site:login-url] using the following username and password:\r\n\r\nusername: [user:name]\r\npassword: \r\n\r\nYou may also log in by clicking on this link or copying and pasting it in your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis is a one-time login, so it can be used only once.\r\n\r\nAfter logging in, you will be redirected to [user:edit-url] so you can change your password.\r\n\r\n\r\n-- [site:name] team", $config->get('register_admin_created.body'));
$this->assertIdentical('fr - Account details for [user:name] at [site:name]', $config->get('register_no_approval_required.subject'));
$this->assertIdentical("fr - [user:name],\r\n\r\nThank you for registering at [site:name]. You may now log in to [site:login-url] using the following username and password:\r\n\r\nusername: [user:name]\r\npassword: \r\n\r\nYou may also log in by clicking on this link or copying and pasting it in your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis is a one-time login, so it can be used only once.\r\n\r\nAfter logging in, you will be redirected to [user:edit-url] so you can change your password.\r\n\r\n\r\n-- [site:name] team", $config->get('register_no_approval_required.body'));
$this->assertIdentical('fr - Account details for [user:name] at [site:name] (pending admin approval)', $config->get('register_pending_approval.subject'));
$this->assertIdentical("fr - [user:name],\r\n\r\nThank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another email containing information about how to log in, set your password, and other details.\r\n\r\n\r\n-- [site:name] team", $config->get('register_pending_approval.body'));
$this->assertIdentical('fr - Account details for [user:name] at [site:name] (blocked)', $config->get('status_blocked.subject'));
$this->assertIdentical("fr - [user:name],\r\n\r\nYour account on [site:name] has been blocked.", $config->get('status_blocked.body'));
$this->assertConfigSchema(\Drupal::service('config.typed'), 'user.mail', $config->get());
$config = \Drupal::service('language_manager')->getLanguageConfigOverride('zu', 'user.mail');
$this->assertIdentical('zu - An administrator created an account for you at [site:name]', $config->get('register_admin_created.subject'));
$this->assertIdentical("zu - [user:name],\r\n\r\nA site administrator at [site:name] has created an account for you. You may now log in to [site:login-url] using the following username and password:\r\n\r\nusername: [user:name]\r\npassword: \r\n\r\nYou may also log in by clicking on this link or copying and pasting it in your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis is a one-time login, so it can be used only once.\r\n\r\nAfter logging in, you will be redirected to [user:edit-url] so you can change your password.\r\n\r\n\r\n-- [site:name] team", $config->get('register_admin_created.body'));
}
/**
* Tests migration of i18n user variables to user.settings.yml.
*/
public function testUserSettings() {
$config = \Drupal::service('language_manager')->getLanguageConfigOverride('fr', 'user.settings');
$this->assertIdentical(1, $config->get('notify.status_blocked'));
$this->assertIdentical(0, $config->get('notify.status_activated'));
$this->assertIdentical(0, $config->get('verify_mail'));
$this->assertIdentical('admin_only', $config->get('register'));
$this->assertIdentical('fr Guest', $config->get('anonymous'));
$config = \Drupal::service('language_manager')->getLanguageConfigOverride('zu', 'user.settings');
$this->assertIdentical(1, $config->get('notify.status_blocked'));
$this->assertIdentical(0, $config->get('notify.status_activated'));
$this->assertIdentical(0, $config->get('verify_mail'));
$this->assertIdentical('admin_only', $config->get('register'));
$this->assertIdentical('Guest', $config->get('anonymous'));
}
}

View file

@ -0,0 +1,218 @@
<?php
namespace Drupal\Tests\config_translation\Unit;
use Drupal\config_translation\ConfigEntityMapper;
use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Routing\Route;
/**
* Tests the functionality provided by the configuration entity mapper.
*
* @group config_translation
*/
class ConfigEntityMapperTest extends UnitTestCase {
/**
* The configuration entity mapper to test.
*
* @var \Drupal\config_translation\ConfigEntityMapper
*/
protected $configEntityMapper;
/**
* The entity manager used for testing.
*
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
/**
* The entity instance used for testing.
*
* @var \Drupal\Core\Entity\EntityInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entity;
/**
* The route provider used for testing.
*
* @var \Drupal\Core\Routing\RouteProviderInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $routeProvider;
/**
* The mocked language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface $language_manager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $languageManager;
protected function setUp() {
$this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$this->entity = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityInterface');
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
$this->routeProvider
->expects($this->any())
->method('getRouteByName')
->with('entity.configurable_language.edit_form')
->will($this->returnValue(new Route('/admin/config/regional/language/edit/{configurable_language}')));
$definition = array(
'class' => '\Drupal\config_translation\ConfigEntityMapper',
'base_route_name' => 'entity.configurable_language.edit_form',
'title' => '@label language',
'names' => array(),
'entity_type' => 'configurable_language',
'route_name' => 'config_translation.item.overview.entity.configurable_language.edit_form',
);
$typed_config_manager = $this->getMock('Drupal\Core\Config\TypedConfigManagerInterface');
$locale_config_manager = $this->getMockBuilder('Drupal\locale\LocaleConfigManager')
->disableOriginalConstructor()
->getMock();
$this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
$this->configEntityMapper = new ConfigEntityMapper(
'configurable_language',
$definition,
$this->getConfigFactoryStub(),
$typed_config_manager,
$locale_config_manager,
$this->getMock('Drupal\config_translation\ConfigMapperManagerInterface'),
$this->routeProvider,
$this->getStringTranslationStub(),
$this->entityManager,
$this->languageManager
);
}
/**
* Tests ConfigEntityMapper::setEntity() and ConfigEntityMapper::getEntity().
*/
public function testEntityGetterAndSetter() {
$this->entity
->expects($this->once())
->method('id')
->with()
->will($this->returnValue('entity_id'));
$entity_type = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityTypeInterface');
$entity_type
->expects($this->any())
->method('getConfigPrefix')
->will($this->returnValue('config_prefix'));
$this->entityManager
->expects($this->once())
->method('getDefinition')
->with('configurable_language')
->will($this->returnValue($entity_type));
// No entity is set.
$this->assertNull($this->configEntityMapper->getEntity());
$result = $this->configEntityMapper->setEntity($this->entity);
$this->assertTrue($result);
// Ensure that the getter provides the entity.
$this->assertEquals($this->entity, $this->configEntityMapper->getEntity());
// Ensure that the configuration name was added to the mapper.
$plugin_definition = $this->configEntityMapper->getPluginDefinition();
$this->assertTrue(in_array('config_prefix.entity_id', $plugin_definition['names']));
// Make sure setEntity() returns FALSE when called a second time.
$result = $this->configEntityMapper->setEntity($this->entity);
$this->assertFalse($result);
}
/**
* Tests ConfigEntityMapper::getOverviewRouteParameters().
*/
public function testGetOverviewRouteParameters() {
$entity_type = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityTypeInterface');
$this->entityManager
->expects($this->once())
->method('getDefinition')
->with('configurable_language')
->will($this->returnValue($entity_type));
$this->configEntityMapper->setEntity($this->entity);
$this->entity
->expects($this->once())
->method('id')
->with()
->will($this->returnValue('entity_id'));
$result = $this->configEntityMapper->getOverviewRouteParameters();
$this->assertSame(array('configurable_language' => 'entity_id'), $result);
}
/**
* Tests ConfigEntityMapper::getType().
*/
public function testGetType() {
$result = $this->configEntityMapper->getType();
$this->assertSame('configurable_language', $result);
}
/**
* Tests ConfigEntityMapper::getTypeName().
*/
public function testGetTypeName() {
$entity_type = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityTypeInterface');
$entity_type->expects($this->once())
->method('getLabel')
->will($this->returnValue('test'));
$this->entityManager
->expects($this->once())
->method('getDefinition')
->with('configurable_language')
->will($this->returnValue($entity_type));
$result = $this->configEntityMapper->getTypeName();
$this->assertSame('test', $result);
}
/**
* Tests ConfigEntityMapper::getTypeLabel().
*/
public function testGetTypeLabel() {
$entity_type = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityTypeInterface');
$entity_type->expects($this->once())
->method('getLabel')
->will($this->returnValue('test'));
$this->entityManager
->expects($this->once())
->method('getDefinition')
->with('configurable_language')
->will($this->returnValue($entity_type));
$result = $this->configEntityMapper->getTypeLabel();
$this->assertSame('test', $result);
}
/**
* Tests ConfigEntityMapper::getOperations().
*/
public function testGetOperations() {
$result = $this->configEntityMapper->getOperations();
$expected = array(
'list' => array(
'title' => 'List',
'url' => Url::fromRoute('config_translation.entity_list', ['mapper_id' => 'configurable_language']),
),
);
$this->assertEquals($expected, $result);
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace Drupal\Tests\config_translation\Unit;
use Drupal\config_translation\ConfigFieldMapper;
use Drupal\Tests\UnitTestCase;
/**
* Tests the functionality provided by the configuration field mapper.
*
* @group config_translation
*
* @coversDefaultClass \Drupal\config_translation\ConfigFieldMapper
*/
class ConfigFieldMapperTest extends UnitTestCase {
/**
* The configuration field mapper to test.
*
* @var \Drupal\config_translation\ConfigFieldMapper
*/
protected $configFieldMapper;
/**
* The field config instance used for testing.
*
* @var \Drupal\field\FieldConfigInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entity;
/**
* The entity manager used for testing.
*
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$this->entity = $this->getMock('Drupal\field\FieldConfigInterface');
$definition = array(
'class' => '\Drupal\config_translation\ConfigFieldMapper',
'base_route_name' => 'entity.field_config.node_field_edit_form',
'title' => '@label field',
'names' => array(),
'entity_type' => 'field_config',
);
$locale_config_manager = $this->getMockBuilder('Drupal\locale\LocaleConfigManager')
->disableOriginalConstructor()
->getMock();
$this->configFieldMapper = new ConfigFieldMapper(
'node_fields',
$definition,
$this->getConfigFactoryStub(),
$this->getMock('Drupal\Core\Config\TypedConfigManagerInterface'),
$locale_config_manager,
$this->getMock('Drupal\config_translation\ConfigMapperManagerInterface'),
$this->getMock('Drupal\Core\Routing\RouteProviderInterface'),
$this->getStringTranslationStub(),
$this->entityManager,
$this->getMock('Drupal\Core\Language\LanguageManagerInterface')
);
}
/**
* Tests ConfigFieldMapper::setEntity().
*
* @covers ::setEntity
*/
public function testSetEntity() {
$entity_type = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityTypeInterface');
$entity_type
->expects($this->any())
->method('getConfigPrefix')
->will($this->returnValue('config_prefix'));
$this->entityManager
->expects($this->any())
->method('getDefinition')
->will($this->returnValue($entity_type));
$field_storage = $this->getMock('Drupal\field\FieldStorageConfigInterface');
$field_storage
->expects($this->any())
->method('id')
->will($this->returnValue('field_storage_id'));
$this->entity
->expects($this->any())
->method('getFieldStorageDefinition')
->will($this->returnValue($field_storage));
$result = $this->configFieldMapper->setEntity($this->entity);
$this->assertTrue($result);
// Ensure that the configuration name was added to the mapper.
$plugin_definition = $this->configFieldMapper->getPluginDefinition();
$this->assertTrue(in_array('config_prefix.field_storage_id', $plugin_definition['names']));
// Make sure setEntity() returns FALSE when called a second time.
$result = $this->configFieldMapper->setEntity($this->entity);
$this->assertFalse($result);
}
}

View file

@ -0,0 +1,175 @@
<?php
namespace Drupal\Tests\config_translation\Unit;
use Drupal\config_translation\ConfigMapperManager;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\TypedData\DataDefinition;
/**
* Tests the functionality provided by configuration translation mapper manager.
*
* @group config_translation
*/
class ConfigMapperManagerTest extends UnitTestCase {
/**
* The configuration mapper manager to test.
*
* @var \Drupal\config_translation\ConfigMapperManager
*/
protected $configMapperManager;
/**
* The typed configuration manager used for testing.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $typedConfigManager;
protected function setUp() {
$language = new Language(array('id' => 'en'));
$language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
$language_manager->expects($this->once())
->method('getCurrentLanguage')
->with(LanguageInterface::TYPE_INTERFACE)
->will($this->returnValue($language));
$this->typedConfigManager = $this->getMockBuilder('Drupal\Core\Config\TypedConfigManagerInterface')
->getMock();
$module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$theme_handler = $this->getMock('Drupal\Core\Extension\ThemeHandlerInterface');
$this->configMapperManager = new ConfigMapperManager(
$this->getMock('Drupal\Core\Cache\CacheBackendInterface'),
$language_manager,
$module_handler,
$this->typedConfigManager,
$theme_handler
);
}
/**
* Tests ConfigMapperManager::hasTranslatable().
*
* @param \Drupal\Core\TypedData\TypedDataInterface $element
* The schema element to test.
* @param bool $expected
* The expected return value of ConfigMapperManager::hasTranslatable().
*
* @dataProvider providerTestHasTranslatable
*/
public function testHasTranslatable(TypedDataInterface $element, $expected) {
$this->typedConfigManager
->expects($this->once())
->method('get')
->with('test')
->will($this->returnValue($element));
$result = $this->configMapperManager->hasTranslatable('test');
$this->assertSame($expected, $result);
}
/**
* Provides data for ConfigMapperManager::testHasTranslatable()
*
* @return array
* An array of arrays, where each inner array contains the schema element
* to test as the first key and the expected result of
* ConfigMapperManager::hasTranslatable() as the second key.
*/
public function providerTestHasTranslatable() {
return array(
array($this->getElement(array()), FALSE),
array($this->getElement(array('aaa' => 'bbb')), FALSE),
array($this->getElement(array('translatable' => FALSE)), FALSE),
array($this->getElement(array('translatable' => TRUE)), TRUE),
array($this->getNestedElement(array(
$this->getElement(array()),
)), FALSE),
array($this->getNestedElement(array(
$this->getElement(array('translatable' => TRUE)),
)), TRUE),
array($this->getNestedElement(array(
$this->getElement(array('aaa' => 'bbb')),
$this->getElement(array('ccc' => 'ddd')),
$this->getElement(array('eee' => 'fff')),
)), FALSE),
array($this->getNestedElement(array(
$this->getElement(array('aaa' => 'bbb')),
$this->getElement(array('ccc' => 'ddd')),
$this->getElement(array('translatable' => TRUE)),
)), TRUE),
array($this->getNestedElement(array(
$this->getElement(array('aaa' => 'bbb')),
$this->getNestedElement(array(
$this->getElement(array('ccc' => 'ddd')),
$this->getElement(array('eee' => 'fff')),
)),
$this->getNestedElement(array(
$this->getElement(array('ggg' => 'hhh')),
$this->getElement(array('iii' => 'jjj')),
)),
)), FALSE),
array($this->getNestedElement(array(
$this->getElement(array('aaa' => 'bbb')),
$this->getNestedElement(array(
$this->getElement(array('ccc' => 'ddd')),
$this->getElement(array('eee' => 'fff')),
)),
$this->getNestedElement(array(
$this->getElement(array('ggg' => 'hhh')),
$this->getElement(array('translatable' => TRUE)),
)),
)), TRUE),
);
}
/**
* Returns a mocked schema element.
*
* @param array $definition
* The definition of the schema element.
*
* @return \Drupal\Core\Config\Schema\Element
* The mocked schema element.
*/
protected function getElement(array $definition) {
$data_definition = new DataDefinition($definition);
$element = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
$element->expects($this->any())
->method('getDataDefinition')
->will($this->returnValue($data_definition));
return $element;
}
/**
* Returns a mocked nested schema element.
*
* @param array $elements
* An array of simple schema elements.
*
* @return \Drupal\Core\Config\Schema\Mapping
* A nested schema element, containing the passed-in elements.
*/
protected function getNestedElement(array $elements) {
// ConfigMapperManager::findTranslatable() checks for
// \Drupal\Core\TypedData\TraversableTypedDataInterface, but mocking that
// directly does not work, because we need to implement \IteratorAggregate
// in order for getIterator() to be called. Therefore we need to mock
// \Drupal\Core\Config\Schema\ArrayElement, but that is abstract, so we
// need to mock one of the subclasses of it.
$nested_element = $this->getMockBuilder('Drupal\Core\Config\Schema\Mapping')
->disableOriginalConstructor()
->getMock();
$nested_element->expects($this->once())
->method('getIterator')
->will($this->returnValue(new \ArrayIterator($elements)));
return $nested_element;
}
}

View file

@ -0,0 +1,683 @@
<?php
/**
* @file
* Contains \Drupal\Tests\config_translation\Unit\ConfigNamesMapperTest.
*/
namespace Drupal\Tests\config_translation\Unit;
use Drupal\config_translation\ConfigNamesMapper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Language\Language;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Routing\Route;
/**
* Tests the functionality provided by the configuration names mapper.
*
* @group config_translation
*/
class ConfigNamesMapperTest extends UnitTestCase {
/**
* The plugin definition of the test mapper.
*
* @var array
*/
protected $pluginDefinition;
/**
* The configuration names mapper to test.
*
* @see \Drupal\config_translation\ConfigNamesMapper
*
* @var \Drupal\Tests\config_translation\Unit\TestConfigNamesMapper
*/
protected $configNamesMapper;
/**
* The locale configuration manager.
*
* @var \Drupal\locale\LocaleConfigManager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $localeConfigManager;
/**
* The locale configuration manager.
*
* @var \Drupal\locale\LocaleConfigManager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $typedConfigManager;
/**
* The configuration mapper manager.
*
* @var \Drupal\config_translation\ConfigMapperManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $configMapperManager;
/**
* The base route used for testing.
*
* @var \Symfony\Component\Routing\Route
*/
protected $baseRoute;
/**
* The route provider used for testing.
*
* @var \Drupal\Core\Routing\RouteProviderInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $routeProvider;
/**
* The mocked URL generator.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $urlGenerator;
/**
* The mocked language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface $language_manager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $languageManager;
protected function setUp() {
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
$this->pluginDefinition = array(
'class' => '\Drupal\config_translation\ConfigNamesMapper',
'base_route_name' => 'system.site_information_settings',
'title' => 'System information',
'names' => array('system.site'),
'weight' => 42,
);
$this->typedConfigManager = $this->getMock('Drupal\Core\Config\TypedConfigManagerInterface');
$this->localeConfigManager = $this->getMockBuilder('Drupal\locale\LocaleConfigManager')
->disableOriginalConstructor()
->getMock();
$this->configMapperManager = $this->getMock('Drupal\config_translation\ConfigMapperManagerInterface');
$this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
$container = new ContainerBuilder();
$container->set('url_generator', $this->urlGenerator);
\Drupal::setContainer($container);
$this->baseRoute = new Route('/admin/config/system/site-information');
$this->routeProvider
->expects($this->any())
->method('getRouteByName')
->with('system.site_information_settings')
->will($this->returnValue($this->baseRoute));
$this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
$this->configNamesMapper = new TestConfigNamesMapper(
'system.site_information_settings',
$this->pluginDefinition,
$this->getConfigFactoryStub(),
$this->typedConfigManager,
$this->localeConfigManager,
$this->configMapperManager,
$this->routeProvider,
$this->getStringTranslationStub(),
$this->languageManager
);
}
/**
* Tests ConfigNamesMapper::getTitle().
*/
public function testGetTitle() {
$result = $this->configNamesMapper->getTitle();
$this->assertSame($this->pluginDefinition['title'], (string) $result);
}
/**
* Tests ConfigNamesMapper::getBaseRouteName().
*/
public function testGetBaseRouteName() {
$result = $this->configNamesMapper->getBaseRouteName();
$this->assertSame($this->pluginDefinition['base_route_name'], $result);
}
/**
* Tests ConfigNamesMapper::getBaseRouteParameters().
*/
public function testGetBaseRouteParameters() {
$result = $this->configNamesMapper->getBaseRouteParameters();
$this->assertSame(array(), $result);
}
/**
* Tests ConfigNamesMapper::getBaseRoute().
*/
public function testGetBaseRoute() {
$result = $this->configNamesMapper->getBaseRoute();
$this->assertSame($this->baseRoute, $result);
}
/**
* Tests ConfigNamesMapper::getBasePath().
*/
public function testGetBasePath() {
$this->urlGenerator->expects($this->once())
->method('getPathFromRoute')
->with('system.site_information_settings', [])
->willReturn('/admin/config/system/site-information');
$result = $this->configNamesMapper->getBasePath();
$this->assertSame('/admin/config/system/site-information', $result);
}
/**
* Tests ConfigNamesMapper::getOverviewRouteName().
*/
public function testGetOverviewRouteName() {
$result = $this->configNamesMapper->getOverviewRouteName();
$expected = 'config_translation.item.overview.' . $this->pluginDefinition['base_route_name'];
$this->assertSame($expected, $result);
}
/**
* Tests ConfigNamesMapper::getOverviewRouteParameters().
*/
public function testGetOverviewRouteParameters() {
$result = $this->configNamesMapper->getOverviewRouteParameters();
$this->assertSame(array(), $result);
}
/**
* Tests ConfigNamesMapper::getOverviewRoute().
*/
public function testGetOverviewRoute() {
$expected = new Route('/admin/config/system/site-information/translate',
array(
'_controller' => '\Drupal\config_translation\Controller\ConfigTranslationController::itemPage',
'plugin_id' => 'system.site_information_settings',
),
array(
'_config_translation_overview_access' => 'TRUE',
)
);
$result = $this->configNamesMapper->getOverviewRoute();
$this->assertSame(serialize($expected), serialize($result));
}
/**
* Tests ConfigNamesMapper::getOverviewPath().
*/
public function testGetOverviewPath() {
$this->urlGenerator->expects($this->once())
->method('getPathFromRoute')
->with('config_translation.item.overview.system.site_information_settings', [])
->willReturn('/admin/config/system/site-information/translate');
$result = $this->configNamesMapper->getOverviewPath();
$this->assertSame('/admin/config/system/site-information/translate', $result);
}
/**
* Tests ConfigNamesMapper::getAddRouteName().
*/
public function testGetAddRouteName() {
$result = $this->configNamesMapper->getAddRouteName();
$expected = 'config_translation.item.add.' . $this->pluginDefinition['base_route_name'];
$this->assertSame($expected, $result);
}
/**
* Tests ConfigNamesMapper::getAddRouteParameters().
*/
public function testGetAddRouteParameters() {
$route_match = new RouteMatch('example', new Route('/test/{langcode}'), ['langcode' => 'xx']);
$this->configNamesMapper->populateFromRouteMatch($route_match);
$expected = array('langcode' => 'xx');
$result = $this->configNamesMapper->getAddRouteParameters();
$this->assertSame($expected, $result);
}
/**
* Tests ConfigNamesMapper::getAddRoute().
*/
public function testGetAddRoute() {
$expected = new Route('/admin/config/system/site-information/translate/{langcode}/add',
array(
'_form' => '\Drupal\config_translation\Form\ConfigTranslationAddForm',
'plugin_id' => 'system.site_information_settings',
),
array(
'_config_translation_form_access' => 'TRUE',
)
);
$result = $this->configNamesMapper->getAddRoute();
$this->assertSame(serialize($expected), serialize($result));
}
/**
* Tests ConfigNamesMapper::getEditRouteName().
*/
public function testGetEditRouteName() {
$result = $this->configNamesMapper->getEditRouteName();
$expected = 'config_translation.item.edit.' . $this->pluginDefinition['base_route_name'];
$this->assertSame($expected, $result);
}
/**
* Tests ConfigNamesMapper::getEditRouteParameters().
*/
public function testGetEditRouteParameters() {
$route_match = new RouteMatch('example', new Route('/test/{langcode}'), ['langcode' => 'xx']);
$this->configNamesMapper->populateFromRouteMatch($route_match);
$expected = array('langcode' => 'xx');
$result = $this->configNamesMapper->getEditRouteParameters();
$this->assertSame($expected, $result);
}
/**
* Tests ConfigNamesMapper::getEditRoute().
*/
public function testGetEditRoute() {
$expected = new Route('/admin/config/system/site-information/translate/{langcode}/edit',
array(
'_form' => '\Drupal\config_translation\Form\ConfigTranslationEditForm',
'plugin_id' => 'system.site_information_settings',
),
array(
'_config_translation_form_access' => 'TRUE',
)
);
$result = $this->configNamesMapper->getEditRoute();
$this->assertSame(serialize($expected), serialize($result));
}
/**
* Tests ConfigNamesMapper::getDeleteRouteName().
*/
public function testGetDeleteRouteName() {
$result = $this->configNamesMapper->getDeleteRouteName();
$expected = 'config_translation.item.delete.' . $this->pluginDefinition['base_route_name'];
$this->assertSame($expected, $result);
}
/**
* Tests ConfigNamesMapper::getDeleteRouteParameters().
*/
public function testGetDeleteRouteParameters() {
$route_match = new RouteMatch('example', new Route('/test/{langcode}'), ['langcode' => 'xx']);
$this->configNamesMapper->populateFromRouteMatch($route_match);
$expected = array('langcode' => 'xx'); $result = $this->configNamesMapper->getDeleteRouteParameters();
$this->assertSame($expected, $result);
}
/**
* Tests ConfigNamesMapper::getRoute().
*/
public function testGetDeleteRoute() {
$expected = new Route('/admin/config/system/site-information/translate/{langcode}/delete',
array(
'_form' => '\Drupal\config_translation\Form\ConfigTranslationDeleteForm',
'plugin_id' => 'system.site_information_settings',
),
array(
'_config_translation_form_access' => 'TRUE',
)
);
$result = $this->configNamesMapper->getDeleteRoute();
$this->assertSame(serialize($expected), serialize($result));
}
/**
* Tests ConfigNamesMapper::getConfigNames().
*/
public function testGetConfigNames() {
$result = $this->configNamesMapper->getConfigNames();
$this->assertSame($this->pluginDefinition['names'], $result);
}
/**
* Tests ConfigNamesMapper::addConfigName().
*/
public function testAddConfigName() {
$names = $this->configNamesMapper->getConfigNames();
$this->configNamesMapper->addConfigName('test');
$names[] = 'test';
$result = $this->configNamesMapper->getConfigNames();
$this->assertSame($names, $result);
}
/**
* Tests ConfigNamesMapper::getWeight().
*/
public function testGetWeight() {
$result = $this->configNamesMapper->getWeight();
$this->assertSame($this->pluginDefinition['weight'], $result);
}
/**
* Tests ConfigNamesMapper::populateFromRouteMatch().
*/
public function testPopulateFromRouteMatch() {
// Make sure the language code is not set initially.
$this->assertSame(NULL, $this->configNamesMapper->getInternalLangcode());
// Test that an empty request does not set the language code.
$route_match = new RouteMatch('example', new Route('/test/{langcode}'));
$this->configNamesMapper->populateFromRouteMatch($route_match);
$this->assertSame(NULL, $this->configNamesMapper->getInternalLangcode());
// Test that a request with a 'langcode' attribute sets the language code.
$route_match = new RouteMatch('example', new Route('/test/{langcode}'), ['langcode' => 'xx']);
$this->configNamesMapper->populateFromRouteMatch($route_match);
$this->assertSame('xx', $this->configNamesMapper->getInternalLangcode());
// Test that the language code gets unset with the wrong request.
$route_match = new RouteMatch('example', new Route('/test/{langcode}'));
$this->configNamesMapper->populateFromRouteMatch($route_match);
$this->assertSame(NULL, $this->configNamesMapper->getInternalLangcode());
}
/**
* Tests ConfigNamesMapper::getTypeLabel().
*/
public function testGetTypeLabel() {
$result = $this->configNamesMapper->getTypeLabel();
$this->assertSame($this->pluginDefinition['title'], (string) $result);
}
/**
* Tests ConfigNamesMapper::getLangcode().
*/
public function testGetLangcode() {
// Test that the getLangcode() falls back to 'en', if no explicit language
// code is provided.
$config_factory = $this->getConfigFactoryStub(array(
'system.site' => array('key' => 'value'),
));
$this->configNamesMapper->setConfigFactory($config_factory);
$result = $this->configNamesMapper->getLangcode();
$this->assertSame('en', $result);
// Test that getLangcode picks up the language code provided by the
// configuration.
$config_factory = $this->getConfigFactoryStub(array(
'system.site' => array('langcode' => 'xx'),
));
$this->configNamesMapper->setConfigFactory($config_factory);
$result = $this->configNamesMapper->getLangcode();
$this->assertSame('xx', $result);
// Test that getLangcode() works for multiple configuration names.
$this->configNamesMapper->addConfigName('system.maintenance');
$config_factory = $this->getConfigFactoryStub(array(
'system.site' => array('langcode' => 'xx'),
'system.maintenance' => array('langcode' => 'xx'),
));
$this->configNamesMapper->setConfigFactory($config_factory);
$result = $this->configNamesMapper->getLangcode();
$this->assertSame('xx', $result);
// Test that getLangcode() throws an exception when different language codes
// are given.
$config_factory = $this->getConfigFactoryStub(array(
'system.site' => array('langcode' => 'xx'),
'system.maintenance' => array('langcode' => 'yy'),
));
$this->configNamesMapper->setConfigFactory($config_factory);
try {
$this->configNamesMapper->getLangcode();
$this->fail();
}
catch (\RuntimeException $e) {
}
}
/**
* Tests ConfigNamesMapper::getConfigData().
*/
public function testGetConfigData() {
$configs = array(
'system.site' => array(
'name' => 'Drupal',
'slogan' => 'Come for the software, stay for the community!',
),
'system.maintenance' => array(
'enabled' => FALSE,
'message' => '@site is currently under maintenance.',
),
'system.rss' => array(
'items' => array(
'limit' => 10,
'view_mode' => 'rss',
),
),
);
$this->configNamesMapper->setConfigNames(array_keys($configs));
$config_factory = $this->getConfigFactoryStub($configs);
$this->configNamesMapper->setConfigFactory($config_factory);
$result = $this->configNamesMapper->getConfigData();
$this->assertSame($configs, $result);
}
/**
* Tests ConfigNamesMapper::hasSchema().
*
* @param array $mock_return_values
* An array of values that the mocked locale configuration manager should
* return for hasConfigSchema().
* @param bool $expected
* The expected return value of ConfigNamesMapper::hasSchema().
*
* @dataProvider providerTestHasSchema
*/
public function testHasSchema(array $mock_return_values, $expected) {
// As the configuration names are arbitrary, simply use integers.
$config_names = range(1, count($mock_return_values));
$this->configNamesMapper->setConfigNames($config_names);
$map = array();
foreach ($config_names as $i => $config_name) {
$map[] = array($config_name, $mock_return_values[$i]);
}
$this->typedConfigManager
->expects($this->any())
->method('hasConfigSchema')
->will($this->returnValueMap($map));
$result = $this->configNamesMapper->hasSchema();
$this->assertSame($expected, $result);
}
/**
* Provides data for ConfigMapperTest::testHasSchema().
*
* @return array
* An array of arrays, where each inner array has an array of values that
* the mocked locale configuration manager should return for
* hasConfigSchema() as the first value and the expected return value of
* ConfigNamesMapper::hasSchema() as the second value.
*/
public function providerTestHasSchema() {
return array(
array(array(TRUE), TRUE),
array(array(FALSE), FALSE),
array(array(TRUE, TRUE, TRUE), TRUE),
array(array(TRUE, FALSE, TRUE), FALSE),
);
}
/**
* Tests ConfigNamesMapper::hasTranslatable().
*
* @param array $mock_return_values
* An array of values that the mocked configuration mapper manager should
* return for hasTranslatable().
* @param bool $expected
* The expected return value of ConfigNamesMapper::hasTranslatable().
*
* @dataProvider providerTestHasTranslatable
*/
public function testHasTranslatable(array $mock_return_values, $expected) {
// As the configuration names are arbitrary, simply use integers.
$config_names = range(1, count($mock_return_values));
$this->configNamesMapper->setConfigNames($config_names);
$map = array();
foreach ($config_names as $i => $config_name) {
$map[] = isset($mock_return_values[$i]) ? array($config_name, $mock_return_values[$i]) : array();
}
$this->configMapperManager
->expects($this->any())
->method('hasTranslatable')
->will($this->returnValueMap($map));
$result = $this->configNamesMapper->hasTranslatable();
$this->assertSame($expected, $result);
}
/**
* Provides data for ConfigNamesMapperTest::testHasTranslatable().
*
* @return array
* An array of arrays, where each inner array has an array of values that
* the mocked configuration mapper manager should return for
* hasTranslatable() as the first value and the expected return value of
* ConfigNamesMapper::hasTranslatable() as the second value.
*/
public function providerTestHasTranslatable() {
return array(
array(array(), FALSE),
array(array(TRUE), TRUE),
array(array(FALSE), FALSE),
array(array(TRUE, TRUE, TRUE), TRUE),
array(array(FALSE, FALSE, FALSE), FALSE),
array(array(TRUE, FALSE, TRUE), TRUE),
);
}
/**
* Tests ConfigNamesMapper::hasTranslation().
*
* @param array $mock_return_values
* An array of values that the mocked configuration mapper manager should
* return for hasTranslation().
* @param bool $expected
* The expected return value of ConfigNamesMapper::hasTranslation().
*
* @dataProvider providerTestHasTranslation
*/
public function testHasTranslation(array $mock_return_values, $expected) {
$language = new Language();
// As the configuration names are arbitrary, simply use integers.
$config_names = range(1, count($mock_return_values));
$this->configNamesMapper->setConfigNames($config_names);
$map = array();
foreach ($config_names as $i => $config_name) {
$map[] = array($config_name, $language->getId(), $mock_return_values[$i]);
}
$this->localeConfigManager
->expects($this->any())
->method('hasTranslation')
->will($this->returnValueMap($map));
$result = $this->configNamesMapper->hasTranslation($language);
$this->assertSame($expected, $result);
}
/**
* Provides data for for ConfigNamesMapperTest::testHasTranslation().
*
* @return array
* An array of arrays, where each inner array has an array of values that
* the mocked configuration mapper manager should return for
* hasTranslation() as the first value and the expected return value of
* ConfigNamesMapper::hasTranslation() as the second value.
*/
public function providerTestHasTranslation() {
return array(
array(array(TRUE), TRUE),
array(array(FALSE), FALSE),
array(array(TRUE, TRUE, TRUE), TRUE),
array(array(FALSE, FALSE, TRUE), TRUE),
array(array(FALSE, FALSE, FALSE), FALSE),
);
}
/**
* Tests ConfigNamesMapper::getTypeName().
*/
public function testGetTypeName() {
$result = $this->configNamesMapper->getTypeName();
$this->assertSame('Settings', (string) $result);
}
/**
* Tests ConfigNamesMapper::hasTranslation().
*/
public function testGetOperations() {
$expected = array(
'translate' => array(
'title' => 'Translate',
'url' => Url::fromRoute('config_translation.item.overview.system.site_information_settings'),
),
);
$result = $this->configNamesMapper->getOperations();
$this->assertEquals($expected, $result);
}
}
/**
* Defines a test mapper class.
*/
class TestConfigNamesMapper extends ConfigNamesMapper {
/**
* Gets the internal language code of this mapper, if any.
*
* This method is not to be confused with
* ConfigMapperInterface::getLangcode().
*
* @return string|null
* The language code of this mapper if it is set; NULL otherwise.
*/
public function getInternalLangcode() {
return isset($this->langcode) ? $this->langcode : NULL;
}
/**
* Sets the list of configuration names.
*
* @param array $config_names
*/
public function setConfigNames(array $config_names) {
$this->pluginDefinition['names'] = $config_names;
}
/**
* Sets the configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory to set.
*/
public function setConfigFactory(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
}
}

View file

@ -0,0 +1,6 @@
# Attach to performance settings for testing. The base route does not matter.
system.performance_settings:
title: 'Theme translation test'
base_route_name: system.performance_settings
names:
- system.site

View file

@ -0,0 +1,5 @@
name: 'Configuration Translation Test Theme'
type: theme
description: 'Theme for testing the configuration translation mapper system'
version: VERSION
core: 8.x