Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
271
web/core/modules/config_translation/src/ConfigEntityMapper.php
Normal file
271
web/core/modules/config_translation/src/ConfigEntityMapper.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
199
web/core/modules/config_translation/src/ConfigMapperManager.php
Normal file
199
web/core/modules/config_translation/src/ConfigMapperManager.php
Normal 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'];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
485
web/core/modules/config_translation/src/ConfigNamesMapper.php
Normal file
485
web/core/modules/config_translation/src/ConfigNamesMapper.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\config_translation\Exception;
|
||||
|
||||
/**
|
||||
* Provides an exception for configuration mappers with multiple languages.
|
||||
*/
|
||||
class ConfigMapperLanguageException extends \RuntimeException {
|
||||
}
|
|
@ -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())));
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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())));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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]');
|
||||
}
|
||||
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue