contrib modules updates

This commit is contained in:
2019-02-27 10:39:59 +01:00
parent 04a4b8895d
commit e3cf889820
579 changed files with 18343 additions and 4076 deletions
@@ -3,7 +3,7 @@ Translation Views
INTRODUCTION
------------
Provides fields and filter for views to display information about the current row, the **source** translation, in another **target** language. You can also add translation operation links to decide if you want to add/edit the content in the target language.
Provides fields and filter for views to display information about each row, the **source** translation, in another **target** language. You can also add translation operation links to decide if you want to add/edit the content in the target language.
Configurations for a demo view is imported on installation, named `Content translation jobs` which can be found at path `/translate/content`. If you want to get view for other translatable entities, you should build it yourself using our fields/filters.
@@ -17,16 +17,17 @@ FEATURES
- Target language: `field`,`filter`. Should in most cases be an exposed filter.
Also, there is an extra filter option: *Remove rows where source language is equal to target language*
These fields/filters will display information about the current content in the selected target language:
These fields/filters will display information about the each row in the selected target language:
- Translation outdated: `field`,`filter`
- Translation status: `field`,`filter`
- Target language equals default language: `field`,`filter`: Checks if the target language is the same as the original language of the node.
- Translation changed time: `field`
- Translation source equals row language: `field`,`filter`: Checks if the source translation of the target language is the same as the language of the row
- Source translation of target language equals row language: `field`,`filter`: Checks if the source translation of the row is the same as the target language
- Translation operations: `field`
- Translation moderation state: `field`
**Source translation**
- Translation counter: `field`,`filter`
- Translation counter: `field`,`filter`: count translations of oirignal language. You can also configure the field to include original language in the count.
REQUIREMENTS
------------
@@ -65,9 +66,12 @@ Exposed filters:
Hidden filters
- Source content outdated must be false: Make sure you do not translation from outdated nodes.
- Target language equals default language must be false: As there is no use to check status of original language as a target language.
- The target language must be untranslated or outdated: Make sure translated and updated content disappear from the list.
- The target language must be untranslated or the same as the source language of translation: This makes sure that once there is a translation in target language it will only be displayed if the source language is the correct source language. If target language is not translated you can use different language as source translation.
MODULE INTEGRATION
-------------
You can limit the target language filter list to the users registered translation skill if you're using [Local Translation](https://www.drupal.org/project/local_translation)
MAINTAINERS
-----------
Developed by [vlad.dancer](https://drupal.org/u/vladdancer)
@@ -612,7 +612,7 @@ display:
identifier: translation_target_language
required: true
remember: true
multiple: true
multiple: false
remember_roles:
anonymous: anonymous
authenticated: authenticated
@@ -679,7 +679,7 @@ display:
group_type: group
admin_label: ''
operator: '='
value: 2
value: '0'
group: 1
exposed: true
expose:
@@ -754,7 +754,7 @@ display:
group_type: group
admin_label: ''
operator: '='
value: 0
value: '0'
group: 2
exposed: false
expose:
@@ -819,79 +819,6 @@ display:
default_group_multiple: { }
group_items: { }
plugin_id: translation_views_source_equals_row
translation_status_2:
id: translation_status_2
table: node_translation
field: translation_status
relationship: none
group_type: group
admin_label: ''
operator: '='
value: 0
group: 3
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: node
plugin_id: translation_views_status
translation_outdated_1:
id: translation_outdated_1
table: node_translation_target
field: translation_outdated
relationship: none
group_type: group
admin_label: ''
operator: '='
value: '1'
group: 3
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: boolean
sorts:
created:
id: created
@@ -4,19 +4,15 @@ views.filter.translation_views_translation_count:
views.filter_value.translation_views_translation_count:
type: views.filter_value.numeric
label: 'Filter rows by amount of translations (without default language)'
label: 'Filter rows by amount of translations'
views.filter.translation_views_source_equals_row:
type: views.filter.boolean
value: 'A boolean indicating whether the translation source of target language is same as the row language'
views.filter.translation_views_status:
type: views_filter
label: 'A value indicating whether the node is translated into target language'
mapping:
value:
type: integer
label: 'Value'
type: views.filter.boolean
label: 'A boolean indicating whether the node is translated into target language'
views.filter.translation_views_target_language:
type: views_filter
@@ -28,10 +24,27 @@ views.filter.translation_views_target_language:
remove:
type: boolean
label: 'Remove rows where source language is equal to target language'
limit:
type: boolean
label: 'Enable limiting languages list using Local Translation module'
column:
type: mapping
label: 'Column'
mapping:
from:
type: string
lable: 'Source language'
to:
type: string
lable: 'Target language'
views.field.translation_views_translation_count:
type: views.field.numeric
label: 'Amount of translations (without default language)'
label: 'Amount of translations'
mapping:
include_original_language:
type: boolean
label: 'Include original language in count'
views.field.translation_views_operations:
type: views_field
@@ -48,3 +61,11 @@ views.field.translation_views_status:
views.field.translation_views_target_language:
type: views.field.language
label: 'The target language'
views.field.translation_moderation_state:
type: views_field
label: 'Translation moderation state'
mapping:
display_name:
type: string
label: 'Formatter'
@@ -1,78 +0,0 @@
<?php
namespace Drupal\translation_views;
use Drupal\content_translation\ContentTranslationHandlerInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Language\LanguageInterface;
/**
* Class EntityInfo.
*
* Special object to store common entity related properties,
* it will be used by all operations link builder functions,
* just as "trait" but for method's values.
*
* @package Drupal\translation_views
*/
class EntityInfo {
/**
* Source entity.
*
* @var \Drupal\Core\Entity\ContentEntityInterface
*/
public $entity;
/**
* The entity type id.
*
* @var string
*/
public $entityTypeId;
/**
* Entity type instance.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
public $entityType;
public $translations;
public $translatable;
public $rowLanguage;
public $access;
public $currentLanguage;
/**
* {@inheritdoc}
*/
public function __construct(ContentEntityInterface $entity, ContentTranslationHandlerInterface $access, LanguageInterface $language, LanguageInterface $row_lang) {
$this->entity = $entity;
$this->access = $access;
$this->rowLanguage = $row_lang;
$this->currentLanguage = $language;
$this->entityTypeId = $entity->getEntityTypeId();
$this->entityType = $entity->getEntityType();
$this->translations = $entity->getTranslationLanguages();
$this->translatable = $entity->isTranslatable();
}
/**
* An wrapper to get translation access by particular entity per operation.
*
* @see \Drupal\content_translation\ContentTranslationHandlerInterface::getTranslationAccess,
* for available operations.
*/
public function getTranslationAccess($op) {
$access = $this->access->getTranslationAccess($this->entity, $op);
return $access->isAllowed();
}
}
@@ -0,0 +1,81 @@
<?php
namespace Drupal\translation_views;
use Drupal\content_translation\ContentTranslationHandlerInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Language\LanguageInterface;
/**
* Class EntityTranslationInfo.
*
* Stores entity translation related propertie used by
* operations link builder functions as a trait.
*
* @package Drupal\translation_views
*/
class EntityTranslationInfo {
/**
* Entity.
*
* @var \Drupal\Core\Entity\ContentEntityInterface
*/
public $entity;
/**
* Entity type id.
*
* @var string
*/
public $entityTypeId;
/**
* Entity type instance.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
public $entityType;
/**
* Translation access handler.
*
* @var \Drupal\content_translation\ContentTranslationHandlerInterface
*/
public $accessHandler;
/**
* Source language.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
public $sourceLanguage;
/**
* Target language.
*
* @var \Drupal\Core\Language\LanguageInterface
*/
public $targetLanguage;
/**
* {@inheritdoc}
*/
public function __construct(ContentEntityInterface $entity, ContentTranslationHandlerInterface $accessHandler, LanguageInterface $targetLanguage, LanguageInterface $sourceLanguage) {
$this->entity = $entity;
$this->accessHandler = $accessHandler;
$this->sourceLanguage = $sourceLanguage;
$this->targetLanguage = $targetLanguage;
$this->entityTypeId = $entity->getEntityTypeId();
$this->entityType = $entity->getEntityType();
}
/**
* A wrapper for getting translation access per operation.
*
* @see \Drupal\content_translation\ContentTranslationHandlerInterface::getTranslationAccess,
* for available operations.
*/
public function getTranslationAccess($operation) {
$access = $this->accessHandler->getTranslationAccess($this->entity, $operation);
return $access->isAllowed();
}
}
@@ -2,6 +2,7 @@
namespace Drupal\translation_views\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\translation_views\TranslationCountTrait;
use Drupal\views\Plugin\views\field\NumericField;
@@ -57,4 +58,26 @@ class TranslationCountField extends NumericField implements ContainerFactoryPlug
$this->field_alias = $query->addField($join_alias, 'count_langs', $this->realField);
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['include_original_language'] = ['default' => FALSE];
return $options;
}
/**
* Provide option to include original language in count.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['include_original_language'] = [
'#title' => $this->t('Include original language in count'),
'#description' => $this->t("Enable to also count the original language."),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['include_original_language']),
];
parent::buildOptionsForm($form, $form_state);
}
}
@@ -0,0 +1,161 @@
<?php
namespace Drupal\translation_views\Plugin\views\field;
use Drupal\content_moderation\ModerationInformationInterface;
use Drupal\content_translation\ContentTranslationManager;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\ResultRow;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\translation_views\TranslationViewsTargetLanguage as TargetLanguage;
/**
* Provides a field that adds moderation state.
*
* @ingroup views_field_handlers
*
* @ViewsField("translation_moderation_state")
*/
class TranslationModerationState extends FieldPluginBase {
use TargetLanguage;
/**
* The moderation information service.
*
* @var \Drupal\content_moderation\ModerationInformationInterface
*/
protected $moderationInfo;
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
ModerationInformationInterface $moderation_info,
LanguageManagerInterface $language_manager,
EntityTypeManager $entity_type_manager
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->moderationInfo = $moderation_info;
$this->languageManager = $language_manager;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition
) {
return new static(
$configuration, $plugin_id, $plugin_definition,
$container->get('content_moderation.moderation_information'),
$container->get('language_manager'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['display_name'] = ['default' => TRUE];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$default_value = 'state';
$states = [
'state' => $this->t('Moderation State'),
'name' => $this->t('Machine Name'),
];
if (!empty($this->options['display_name'])) {
$default_value = $this->options['display_name'];
}
$form['display_name'] = [
'#type' => 'select',
'#title' => $this->t('Formatter'),
'#options' => $states,
'#required' => TRUE,
'#default_value' => $default_value,
];
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function query() {
}
/**
* {@inheritdoc}
*/
public function clickSortable() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
if ($values->_entity->id()) {
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$entityTypeId = $values->_entity->getEntityTypeId();
$storage = $this->entityTypeManager->getStorage($entityTypeId);
$entity = $storage->load($values->_entity->id());
$target_langcode = $this->getTargetLangcode();
$pending_revision_enabled = ContentTranslationManager::isPendingRevisionSupportEnabled($entityTypeId);
if (empty($target_langcode) || $target_langcode == '***LANGUAGE_site_default***') {
$target_langcode = $this->languageManager->getCurrentLanguage()->getId();
}
$translation_has_revision = $storage->getLatestTranslationAffectedRevisionId($values->_entity->id(), $target_langcode);
if ($entity && $pending_revision_enabled && $target_langcode && $translation_has_revision) {
$latest_revision = $storage->loadRevision($translation_has_revision);
if ($entity && $latest_revision->hasTranslation($target_langcode)) {
$workflow = $this->moderationInfo->getWorkflowForEntity($latest_revision);
$translation = $latest_revision->getTranslation($target_langcode);
$translation_state = $translation->moderation_state->value;
if (!empty($this->options['display_name']) && $workflow !== NULL) {
if ($this->options['display_name'] == 'state') {
$translation_label = $workflow->getTypePlugin()
->getState($translation_state)
->label();
$values->{$this->field_alias} = $translation_label ? $translation_label : NULL;
}
else {
$values->{$this->field_alias} = $translation_state ? $translation_state : NULL;
}
}
}
}
}
return parent::render($values);
}
}
@@ -2,12 +2,18 @@
namespace Drupal\translation_views\Plugin\views\field;
use Drupal\content_translation\ContentTranslationManager;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\translation_views\EntityInfo as HelperEntityInfo;
use Drupal\translation_views\EntityTranslationInfo;
use Drupal\translation_views\TranslationViewsTargetLanguage as TargetLanguage;
use Drupal\views\Plugin\views\field\EntityOperations;
use Drupal\views\ResultRow;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Renders translation operations links.
@@ -19,16 +25,65 @@ use Drupal\views\ResultRow;
class TranslationOperationsField extends EntityOperations {
use TargetLanguage;
/**
* Current user account object.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
public function __construct(
array $configuration,
$plugin_id,
array $plugin_definition,
EntityManagerInterface $entity_manager,
EntityTypeManager $entity_type_manager,
LanguageManagerInterface $language_manager,
AccountProxyInterface $account
) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager, $language_manager);
$this->currentUser = $account;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition
) {
return new static(
$configuration, $plugin_id, $plugin_definition,
$container->get('entity.manager'),
$container->get('entity_type.manager'),
$container->get('language_manager'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*
* Build operation links.
*/
public function render(ResultRow $values) {
/* @var ContentEntityInterface $entity */
$entity = $this->getEntity($values);
$langcode_key = $this->buildSourceEntityLangcodeKey($entity);
$operations = $this->getOperations($entity, $values->{$langcode_key});
/* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity($values);
$langcode_key = $this->buildSourceEntityLangcodeKey($entity);
$source_langcode = $values->{$langcode_key};
$operations = $this->getTranslationOperations($entity, $source_langcode);
if ($this->options['destination']) {
foreach ($operations as &$operation) {
@@ -39,7 +94,7 @@ class TranslationOperationsField extends EntityOperations {
}
}
$build = [
'#type' => 'operations',
'#type' => 'operations',
'#links' => $operations,
];
$build['#cache']['contexts'][] = 'url.query_args:target_language';
@@ -76,20 +131,21 @@ class TranslationOperationsField extends EntityOperations {
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The source entity to get context for decision.
* @param string $row_lang
* The target langcode of the row.
* @param string $source_langcode
* The langcode of the row.
*
* @return array
* Operation links' render array.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Core\Entity\EntityMalformedException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getOperations(ContentEntityInterface $entity, $row_lang) {
protected function getTranslationOperations(ContentEntityInterface $entity, $source_langcode) {
$links = [];
$current = $this->getTargetLanguage()
? $this->getTargetLanguage()
: $row_lang;
$target_langcode = $this->getTargetLangcode()
? $this->getTargetLangcode()
: $source_langcode;
/* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
$handler = $this->getEntityManager()
@@ -98,74 +154,159 @@ class TranslationOperationsField extends EntityOperations {
// Construct special object to store common properties,
// it will be used by all builder functions,
// just as "trait" but for methods.
$entity_info = new HelperEntityInfo(
$translation_info = new EntityTranslationInfo(
$entity,
$handler,
$this->languageManager->getLanguage($current),
$this->languageManager->getLanguage($row_lang)
$this->languageManager->getLanguage($target_langcode),
$this->languageManager->getLanguage($source_langcode)
);
$is_source = $entity->getUntranslated()->language()->getId() === $entity_info->rowLanguage->getId();
$is_default = static::isDefaultTranslation($translation_info);
// Build edit & delete link.
if (array_key_exists($current, $entity_info->translations)) {
if (array_key_exists($target_langcode, $entity->getTranslationLanguages())) {
// If the user is allowed to edit the entity we point the edit link to
// the entity form, otherwise if we are not dealing with the original
// language we point the link to the translation form.
if ($entity->access('update', NULL, TRUE)->isAllowed()
&& $entity_info->entityType->hasLinkTemplate('edit-form')
if ($entity->access('update', NULL)
&& $translation_info->entityType->hasLinkTemplate('edit-form')
) {
$links += $this->buildEditLink($entity_info, 'entity');
$links += $this->buildEditLink($translation_info, 'entity');
}
elseif (!$is_source && $entity_info->getTranslationAccess('update')) {
$links += $this->buildEditLink($entity_info, 'translation');
elseif (!$is_default
&& $translation_info->getTranslationAccess('update')
|| $this->checkForOperationTranslationPermission('update')
) {
$links += $this->buildEditLink($translation_info, 'translation');
}
// Build delete link.
if (!$is_source) {
if ($entity->access('delete')
&& $entity_info->entityType->hasLinkTemplate('delete-form')
) {
$links += $this->buildDeleteLink($entity_info, 'entity');
}
elseif ($entity_info->getTranslationAccess('update')) {
$links += $this->buildDeleteLink($entity_info, 'translation');
}
if ($entity->access('delete')
&& $translation_info->entityType->hasLinkTemplate('delete-form')
) {
$links += $this->buildDeleteLink($translation_info, 'entity');
}
elseif (!$is_default
&& $translation_info->getTranslationAccess('delete')
|| $this->checkForOperationTranslationPermission('delete')
) {
$links += $this->buildDeleteLink($translation_info, 'translation');
}
}
// Check if there are pending revisions.
elseif ($this->pendingRevisionExist($translation_info)) {
// If the user is allowed to edit the entity we point the edit link to
// the entity form, otherwise if we are not dealing with the original
// language we point the link to the translation form.
if ($entity->access('update', NULL)
&& $translation_info->entityType->hasLinkTemplate('edit-form')
) {
$links += $this->buildEditLink($translation_info, 'entity');
}
elseif (!$is_default
&& $translation_info->getTranslationAccess('update')
|| $this->checkForOperationTranslationPermission('update')
) {
$links += $this->buildEditLink($translation_info, 'translation');
}
}
// Build add link.
elseif (!empty($current)
&& $entity_info->translatable
&& $entity_info->getTranslationAccess('create')
elseif (!empty($target_langcode)
&& $translation_info->entity->isTranslatable()
&& $translation_info->getTranslationAccess('create')
) {
// No such translation.
$links += $this->buildAddLink($entity_info);
$links += $this->buildAddLink($translation_info);
}
return $links;
return $links;
}
/**
* Check if the current user has appropriate translation permission.
*
* @param string $operation
* Operation name.
*
* @return bool
* TRUE - if user permitted to perform specified operation, FALSE otherwise.
*/
protected function checkForOperationTranslationPermission($operation) {
if (!in_array($operation, ['create', 'update', 'delete'], TRUE)) {
return FALSE;
}
return $this->currentUser
->hasPermission("$operation content translations");
}
/**
* Check if target translation is the default translation.
*
* @param \Drupal\translation_views\EntityInfo $translation_info
* Entity info object.
*
* @return bool
* Checking result.
*/
protected static function isDefaultTranslation(EntityTranslationInfo $translation_info) {
$default_langcode = $translation_info->entity
->getUntranslated()
->language()
->getId();
$target_langcode = $translation_info->targetLanguage->getId();
return $default_langcode === $target_langcode;
}
/**
* Check if pending revision exist for this translation.
*
* @param \Drupal\translation_views\EntityInfo $translation_info
* Entity info object.
*
* @return bool
* TRUE - if pending revision exist, FALSE otherwise.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function pendingRevisionExist(EntityTranslationInfo $translation_info) {
$pending_revision_enabled = ContentTranslationManager::isPendingRevisionSupportEnabled($translation_info->entityTypeId);
if ($this->moduleHandler->moduleExists('content_moderation') && $pending_revision_enabled) {
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = $this->entityTypeManager->getStorage($translation_info->entityTypeId);
$entity = $storage->load($translation_info->entity->id());
$translation_has_revision = $storage->getLatestTranslationAffectedRevisionId(
$entity->id(),
$translation_info->targetLanguage->getId()
);
if ($translation_has_revision) {
return TRUE;
}
}
return FALSE;
}
/**
* Add link builder.
*/
protected function buildAddLink(HelperEntityInfo $entity_info) {
protected function buildAddLink(EntityTranslationInfo $translation_info) {
$links = [];
$add_url = new Url(
"entity.{$entity_info->entityTypeId}.content_translation_add",
"entity.{$translation_info->entityTypeId}.content_translation_add",
[
'source' => $entity_info->rowLanguage->getId(),
'target' => $entity_info->currentLanguage->getId(),
$entity_info->entityTypeId => $entity_info->entity->id(),
'source' => $translation_info->sourceLanguage->getId(),
'target' => $translation_info->targetLanguage->getId(),
$translation_info->entityTypeId => $translation_info->entity->id(),
],
[
'language' => $entity_info->currentLanguage,
'language' => $translation_info->targetLanguage,
]
);
$links['add'] = [
'title' => $this->t('Add'),
'url' => $add_url,
'url' => $add_url,
];
return $links;
}
@@ -173,7 +314,7 @@ class TranslationOperationsField extends EntityOperations {
/**
* Delete link builder.
*/
protected function buildDeleteLink(HelperEntityInfo $entity_info, $type = FALSE) {
protected function buildDeleteLink(EntityTranslationInfo $translation_info, $type = FALSE) {
$links = [];
if (!$type) {
@@ -182,22 +323,22 @@ class TranslationOperationsField extends EntityOperations {
if ($type == 'entity') {
$links['delete'] = [
'title' => $this->t('Delete'),
'url' => $entity_info->entity->urlInfo('delete-form'),
'language' => $entity_info->rowLanguage,
'title' => $this->t('Delete'),
'url' => $translation_info->entity->urlInfo('delete-form'),
'language' => $translation_info->targetLanguage,
];
}
elseif ($type == 'translation') {
$links['delete'] = [
'title' => $this->t('Delete'),
'url' => new Url(
"entity.{$entity_info->entityTypeId}.content_translation_delete",
'url' => new Url(
"entity.{$translation_info->entityTypeId}.content_translation_delete",
[
'language' => $entity_info->rowLanguage->getId(),
$entity_info->entityTypeId => $entity_info->entity->id(),
'language' => $translation_info->targetLanguage->getId(),
$translation_info->entityTypeId => $translation_info->entity->id(),
],
[
'language' => $entity_info->rowLanguage,
'language' => $translation_info->targetLanguage,
]
),
];
@@ -210,22 +351,22 @@ class TranslationOperationsField extends EntityOperations {
*
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
protected function buildEditLink(HelperEntityInfo $entity_info, $type = FALSE) {
protected function buildEditLink(EntityTranslationInfo $translation_info, $type = FALSE) {
$links = [];
if ($type == 'entity') {
$links['edit']['url'] = $entity_info->entity->toUrl('edit-form');
$links['edit']['language'] = $entity_info->rowLanguage;
$links['edit']['url'] = $translation_info->entity->toUrl('edit-form');
$links['edit']['language'] = $translation_info->targetLanguage;
}
elseif ($type == 'translation') {
$links['edit']['url'] = new Url(
"entity.{$entity_info->entityTypeId}.content_translation_edit",
"entity.{$translation_info->entityTypeId}.content_translation_edit",
[
'language' => $entity_info->rowLanguage->getId(),
$entity_info->entityTypeId => $entity_info->entity->id(),
'language' => $translation_info->targetLanguage->getId(),
$translation_info->entityTypeId => $translation_info->entity->id(),
],
[
'language' => $entity_info->rowLanguage,
'language' => $translation_info->targetLanguage,
]
);
;
@@ -41,7 +41,7 @@ class TranslationSourceLangcodeEqualsRowLangcodeField extends Boolean {
$this->query->addField(
NULL,
"IF($table_alias.content_translation_source = $base_table.langcode, 1, 0)",
"IF(($table_alias.content_translation_source = $base_table.langcode) OR ($table_alias.default_langcode = 1), 1, 0)",
'source_lang_eq_row'
);
}
@@ -57,13 +57,13 @@ class TranslationTargetLanguageField extends LanguageField implements ContainerF
* instead from ResultRow.
*/
public function render(ResultRow $values) {
$target_lang = $this->getTargetLanguage();
$target_langcode = $this->getTargetLangcode();
$languages = $this->options['native_language']
? $this->languageManager->getNativeLanguages()
: $this->languageManager->getLanguages();
$build['#markup'] = $languages[$target_lang]->getName();
$build['#markup'] = $languages[$target_langcode]->getName();
$build['#cache']['contexts'][] = 'url.query_args:' . self::$targetExposedKey;
return $build;
@@ -22,7 +22,7 @@ class TranslationSourceLangcodeEqualsRowLangcodeFilter extends BooleanOperator {
$this->query->addWhereExpression(
$this->options['group'],
"IF($table_alias.content_translation_source $this->operator $base_table.langcode, 1, 0) = :value", [
"IF(($table_alias.content_translation_source $this->operator $base_table.langcode) OR ($table_alias.default_langcode $this->operator 1), 1, 0) = :value", [
':value' => $this->value,
]
);
@@ -2,8 +2,7 @@
namespace Drupal\translation_views\Plugin\views\filter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
use Drupal\views\Plugin\views\filter\BooleanOperator;
/**
* Provides filtering by translation status.
@@ -12,85 +11,13 @@ use Drupal\views\Plugin\views\filter\FilterPluginBase;
*
* @ViewsFilter("translation_views_status")
*/
class TranslationStatusFilter extends FilterPluginBase {
const VALUE_UNTRANSLATED = 0;
const VALUE_TRANSLATED = 1;
const VALUE_BOTH = 2;
class TranslationStatusFilter extends BooleanOperator {
/**
* {@inheritdoc}
*/
public function buildExposeForm(&$form, FormStateInterface $form_state) {
parent::buildExposeForm($form, $form_state);
unset($form['expose']['multiple']);
unset($form['expose']['required']);
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['value']['default'] = FALSE;
return $options;
}
/**
* {@inheritdoc}
*
* Remove "all" option because we don't need it.
*/
public function buildExposedForm(&$form, FormStateInterface $form_state) {
parent::buildExposedForm($form, $form_state);
if (!empty($this->options['expose']['identifier'])) {
$value_id = $this->options['expose']['identifier'];
}
else {
$value_id = 'translation_views_status';
}
unset($form[$value_id]['#options']['All']);
}
/**
* {@inheritdoc}
*/
protected function valueForm(&$form, FormStateInterface $form_state) {
parent::valueForm($form, $form_state);
$exposed = $form_state->get('exposed');
if (!empty($this->options['exposed'])) {
$identifier = $this->options['expose']['identifier'];
$user_input = $form_state->getUserInput();
// We need set exposed input to our "BOTH" value,
// when there is no selected value by user yet,
// usually when there is no any changes in exposed filters.
if ($exposed && !isset($user_input[$identifier])) {
$user_input = $form_state->getUserInput();
$user_input[$identifier] = self::VALUE_BOTH;
$form_state->setUserInput($user_input);
$this->view->setExposedInput($user_input);
}
}
$form['value'] = [
'#type' => 'select',
'#title' => $this->t('Status'),
'#options' => [
self::VALUE_BOTH => t('Both'),
self::VALUE_UNTRANSLATED => t('Not translated'),
self::VALUE_TRANSLATED => t('Translated'),
],
'#multiple' => FALSE,
'#default_value' => $this->value,
];
public function getValueOptions() {
$this->valueOptions = [1 => $this->t('Translated'), 0 => $this->t('Not translated')];
}
/**
@@ -98,33 +25,24 @@ class TranslationStatusFilter extends FilterPluginBase {
*/
public function query() {
$table_alias = $this->ensureMyTable();
$status = $this->value;
$status = is_array($this->value)
? (int) reset($this->value)
: $this->value;
// We don't want to build and run query,
// when user specified both statuses.
if ($status == self::VALUE_BOTH) {
return FALSE;
if ($status == 0) {
$operation = '=';
}
if ($status == self::VALUE_UNTRANSLATED) {
$op = '=';
}
// Then self::VALUE_TRANSLATED is the case.
// Then status translated is the case.
else {
// Mysql FIND_IN_SET func will return position of element,
// when no element was found then it returns 0,
// so we use "> 0" as condition to filter out untranslated rows.
$op = '>';
$operation = '>';
$status = 0;
}
/* @var \Drupal\views\Plugin\views\query\Sql */
$this->query->addWhereExpression(
$this->options['group'],
"FIND_IN_SET(:langcode, $table_alias.langs) $op :status", [
"FIND_IN_SET(:langcode, $table_alias.langs) $operation :status", [
':langcode' => '***TRANSLATION_VIEWS_TARGET_LANG***',
':status' => $status,
]
@@ -2,6 +2,7 @@
namespace Drupal\translation_views\Plugin\views\filter;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
@@ -21,22 +22,40 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class TranslationTargetLanguageFilter extends FilterPluginBase implements ContainerFactoryPluginInterface {
use TargetLanguage;
/**
* Flag about module 'local_translation_content' existence.
*
* @var bool
*/
protected $localTranslation = FALSE;
/**
* Local Translation skills service.
*
* @var \Drupal\local_translation\Services\LocalTranslationUserSkills|null
*/
protected $skills = NULL;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration, $plugin_id, $plugin_definition,
$container->get('language_manager')
$container->get('language_manager'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager, ModuleHandlerInterface $handler) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->languageManager = $language_manager;
$this->localTranslation = $handler->moduleExists('local_translation_content');
if ($this->localTranslation && \Drupal::hasService('local_translation.user_skills')) {
$this->skills = \Drupal::service('local_translation.user_skills');
}
}
/**
@@ -48,12 +67,16 @@ class TranslationTargetLanguageFilter extends FilterPluginBase implements Contai
unset($form['expose']['multiple']);
unset($form['expose']['required']);
if ($this->localTranslation) {
// We need to force this option to allow users to use only the languages,
// specified as the user's translation skills.
$form['expose']['reduce']['#default_value'] = TRUE;
$form['expose']['reduce']['#disabled'] = TRUE;
}
$form['expose']['identifier'] = [
'#type' => 'hidden',
'#value' => $this->options['expose']['identifier'],
'#title' => $this->t('Filter identifier'),
'#size' => 40,
'#description' => $this->t('This will appear in the URL after the ? to identify this filter. Cannot be blank. Only letters, digits and the dot ("."), hyphen ("-"), underscore ("_"), and tilde ("~") characters are allowed.'),
'#value' => static::$targetExposedKey,
];
}
@@ -62,13 +85,76 @@ class TranslationTargetLanguageFilter extends FilterPluginBase implements Contai
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['expose']['identifier'] = static::$targetExposedKey;
$options['expose']['contains']['label'] = [
'default' => $this->t('Target language'),
];
$options['expose']['contains']['identifier'] = [
'default' => static::$targetExposedKey,
];
$options['value']['default'] = '';
$options['remove']['default'] = FALSE;
$options['remove']['default'] = TRUE;
$options['exposed']['default'] = TRUE;
if ($this->localTranslation) {
$options['limit'] = ['default' => FALSE];
$options['column'] = ['default' => ['from' => '', 'to' => 'to']];
}
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
if ($this->localTranslation) {
// Remove the values list - we will handle them on a background basis.
// Only if limited option is checked.
$form['value']['#states'] = [
'visible' => [
'input[name="options[limit]"]' => ['checked' => FALSE],
],
];
// Build values list independently in order to see all the options,
// while switching "limit" option without necessity to reload the form.
$form['value']['#options'] = $this->listLanguages(
LanguageInterface::STATE_ALL
| LanguageInterface::STATE_SITE_DEFAULT
| PluginBase::INCLUDE_NEGOTIATED
);
$end = $form['clear_markup_end'];
unset($form['clear_markup_end']);
$form['limit'] = [
'#type' => 'checkbox',
'#title' => $this->t('Limit target languages by translation skills'),
'#required' => FALSE,
'#default_value' => $this->options['limit'],
];
$form['column'] = [
'#type' => 'checkboxes',
'#options' => [
'from' => $this->t('Source languages'),
'to' => $this->t('Target languages'),
],
'#title' => $this->t('Languages by translation skills'),
'#required' => FALSE,
'#default_value' => $this->options['column'],
'#states' => [
'visible' => [
'input[name="options[limit]"]' => ['checked' => TRUE],
],
],
];
$form['clear_markup_end'] = $end;
$form['value']['#prefix'] = '<div class="views-group-box views-right-60">';
}
}
/**
* {@inheritdoc}
*/
@@ -77,26 +163,48 @@ class TranslationTargetLanguageFilter extends FilterPluginBase implements Contai
$exposed = $form_state->get('exposed');
$language_options = $this->buildLanguageOptions();
$default_langcode = $this->languageManager->getDefaultLanguage()->getId();
if (!empty($this->options['exposed'])) {
$identifier = $this->options['expose']['identifier'];
$user_input = $form_state->getUserInput();
// We need set exposed input when there is no selected value by user yet.
if ($exposed && !isset($user_input[$identifier])) {
$value = PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT;
$this->setExposedValue($identifier, $value, $form_state);
if ($exposed) {
if (!isset($this->options['limit'])) {
$this->options['limit'] = FALSE;
}
if (!$this->options['limit']) {
if (!isset($user_input[$identifier])
|| (isset($user_input[$identifier]) && $user_input[$identifier] === $default_langcode)
) {
$this->setExposedValue($identifier, PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT, $form_state);
}
}
elseif (isset($user_input[$identifier]) && !isset($language_options[$user_input[$identifier]])) {
$this->setExposedValue($identifier, array_keys($language_options)[0], $form_state);
$this->value = array_keys($language_options)[0];
}
}
}
$this->always_required = TRUE;
$form['value'] = [
'#type' => 'select',
'#title' => $this->t('Target language'),
'#options' => $this->buildLanguageOptions(),
'#multiple' => FALSE,
'#weight' => 0,
'#required' => TRUE,
'#default_value' => $this->value,
];
$form['expose']['identifier'] = [
'#type' => 'hidden',
'#value' => static::$targetExposedKey,
];
if (!$exposed) {
$form['remove'] = [
'#type' => 'checkbox',
@@ -113,16 +221,55 @@ class TranslationTargetLanguageFilter extends FilterPluginBase implements Contai
* Options are based on configurable languages or site default one.
*/
protected function buildLanguageOptions() {
$options = [];
if ($this->localTranslation && $this->options['limit']) {
$skills = $this->skills->getSkills(NULL, TRUE);
// Handle column options.
foreach ($this->options['column'] as $name => $column) {
if (!empty($column)) {
foreach ($skills as $langs) {
$this->processColumnOption($langs, $name, $options);
}
}
}
return $options;
}
$site_default = $this->languageManager->getDefaultLanguage();
$options = $this->listLanguages(LanguageInterface::STATE_CONFIGURABLE);
if (isset($options[$site_default->getId()])) {
unset($options[$site_default->getId()]);
}
return [PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT => $site_default->getName()] + $options;
}
/**
* Process column options.
*
* @param array $languages
* Languages array.
* @param string $column
* Column name.
*/
protected function processColumnOption(array $languages, $column, &$options) {
$key = "language_$column";
if (isset($languages[$key])) {
$key = $languages[$key];
$options[$key] = $this->languageManager
->getLanguage($key)
->getName();
}
}
/**
* {@inheritdoc}
*/
protected function listLanguages($flags = LanguageInterface::STATE_ALL, array $current_values = NULL) {
return array_map(function ($language) {
return (string) $language;
}, parent::listLanguages($flags, $current_values));
}
/**
* {@inheritdoc}
*/
@@ -29,7 +29,7 @@ class TranslationLanguageJoin extends JoinPluginBase implements ContainerFactory
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->database = $database;
$this->eid = $this->configuration['entity_pk'];
$this->eid = $this->configuration['entity_id'];
}
/**
@@ -53,14 +53,14 @@ class TranslationLanguageJoin extends JoinPluginBase implements ContainerFactory
if (!empty($this->configuration['langcodes_as_count'])) {
$query->addExpression("COUNT(efd.langcode)", 'count_langs');
if (isset($this->configuration['include_original_language'])
&& $this->configuration['include_original_language'] == FALSE) {
$query->where('efd.default_langcode != 1');
}
}
else {
$query->addExpression("GROUP_CONCAT(efd.langcode separator ',')", 'langs');
}
if (!empty($this->configuration['exclude_default_langcode'])) {
$query->where('efd.default_langcode != 1');
}
$query->groupBy('efd.' . $this->eid);
$this->configuration['table formula'] = $query;
@@ -34,9 +34,9 @@ trait TranslationCountTrait {
'field' => $keys['id'],
'table' => $query_base_table,
'left_table' => $query_base_table,
'exclude_default_langcode' => TRUE,
'include_original_language' => !empty($this->options['include_original_language']),
'langcodes_as_count' => TRUE,
'entity_pk' => $keys['id'],
'entity_id' => $keys['id'],
];
$tableAlias = $query->ensureTable($query_base_table, $this->relationship);
@@ -10,7 +10,7 @@ namespace Drupal\translation_views;
final class TranslationViewsFieldDefinitions {
/**
* Ttranslation status field definition.
* Translation status field definition.
*/
public static function buildStatusField() {
return [
@@ -34,7 +34,7 @@ final class TranslationViewsFieldDefinitions {
}
/**
* Translations count field definition.
* Translation count field definition.
*/
public static function buildCountField() {
return [
@@ -44,11 +44,11 @@ final class TranslationViewsFieldDefinitions {
'title' => t('Translation counter'),
'field' => [
'id' => 'translation_views_translation_count',
'help' => t('Amount of translations (without default language).'),
'help' => t('Amount of translations.'),
],
'filter' => [
'id' => 'translation_views_translation_count',
'help' => t('Filter rows by amount of translations (without default language).'),
'help' => t('Filter rows by amount of translations.'),
],
],
];
@@ -93,7 +93,7 @@ final class TranslationViewsFieldDefinitions {
$field['translation_source']['filter']['id'] = 'translation_views_source_equals_row';
$field['translation_source']['field']['help'] = t('A boolean indicating whether the translation source of target language is same as the row language');
$field['translation_source']['filter']['help'] = t('A boolean indicating whether the translation source of target language is same as the row language');
$field['translation_source']['title'] = t('Translation source equals row language');
$field['translation_source']['title'] = t('Source translation of target language equals row language');
return $field;
}
@@ -144,4 +144,21 @@ final class TranslationViewsFieldDefinitions {
return $field;
}
/**
* Translation moderation state field definition.
*/
public static function buildModerationStateField() {
return [
'translation_moderation_state' => [
'title' => t('Translation moderation state'),
'field' => [
'id' => 'translation_moderation_state',
'property' => 'translation_moderation_state',
'field_name' => t('Translation moderation state'),
'help' => t('Moderation state of the translation.'),
],
],
];
}
}
@@ -20,18 +20,24 @@ trait TranslationViewsTargetLanguage {
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Target exposed key.
*
* @var string
*/
public static $targetExposedKey = 'translation_target_language';
/**
* Get target language from exposed input.
*
* @return string
* Current selected target langcode
* The selected target langcode.
*/
protected function getTargetLanguage() {
protected function getTargetLangcode() {
$inputs = $this->view->getExposedInput();
$langcode = $inputs[self::$targetExposedKey];
$langcode = isset($inputs[self::$targetExposedKey])
? $inputs[self::$targetExposedKey]
: PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT;
return $langcode == PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT
? $this->languageManager->getDefaultLanguage()->getId()
@@ -0,0 +1,253 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
module:
- node
- translation_views
- user
id: test_local_translation_content_integration
label: 'Test local translation content integration'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: none
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: none
options:
offset: 0
style:
type: default
row:
type: 'entity:node'
options:
relationship: none
view_mode: teaser
fields:
title:
id: title
table: node_field_data
field: title
entity_type: node
entity_field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
settings:
link_to_entity: true
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: string
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
langcode:
id: langcode
table: node_field_data
field: langcode
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: language
settings:
link_to_entity: false
native_language: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
entity_field: langcode
plugin_id: field
filters:
translation_target_language:
id: translation_target_language
table: node_translation
field: translation_target_language
relationship: none
group_type: group
admin_label: ''
operator: '='
value: '***LANGUAGE_site_default***'
group: 1
exposed: true
expose:
operator_id: ''
label: 'Target language'
description: ''
use_operator: false
operator: ''
identifier: translation_target_language
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
remove: true
limit: false
column:
from: ''
to: 'to'
entity_type: node
plugin_id: translation_views_target_language
sorts: { }
title: 'Test local translation content filter'
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- 'user.node_grants:view'
- user.permissions
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: test-local-translation-content-filter
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- 'user.node_grants:view'
- user.permissions
tags: { }
@@ -0,0 +1,19 @@
name: 'Translation views Local Translation integration test'
type: module
description: ''
package: Testing
# core: 8.x
dependencies:
- drupal:node
- drupal:views
- drupal:translation_views
- drupal:content_translation
- drupal:language
- local_translation:local_translation
- local_translation:local_translation_content
# Information added by Drupal.org packaging script on 2019-02-07
version: '8.x-1.0-alpha5'
core: '8.x'
project: 'translation_views'
datestamp: 1549581183
@@ -358,7 +358,7 @@ display:
relationship: none
group_type: group
admin_label: ''
label: 'Target language equals default language'
label: 'Source translation of target language equals row language'
exclude: false
alter:
alter_text: false
@@ -514,6 +514,7 @@ display:
format_plural_string: !!binary MQNAY291bnQ=
prefix: ''
suffix: ''
include_original_language: false
entity_type: comment
plugin_id: translation_views_translation_count
translation_outdated:
@@ -577,7 +578,7 @@ display:
relationship: none
group_type: group
admin_label: ''
label: 'Translation source equals row language'
label: 'Source translation of target language equals row language'
exclude: false
alter:
alter_text: false
@@ -881,7 +882,7 @@ display:
group_type: group
admin_label: ''
operator: '='
value: 2
value: All
group: 1
exposed: true
expose:
@@ -0,0 +1,166 @@
uuid: 9f3298b4-2171-4aac-8684-01bdc45909c8
langcode: en
status: true
dependencies:
config:
- node.type.article
module:
- node
- translation_views
- user
id: content_moderation_integration_test
label: 'Content Moderation Integration test'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: some
options:
items_per_page: 10
offset: 0
style:
type: default
row:
type: fields
fields:
translation_moderation_state:
id: translation_moderation_state
table: node_translation
field: translation_moderation_state
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
display_name: state
entity_type: node
plugin_id: translation_moderation_state
filters:
type:
id: type
table: node_field_data
field: type
value:
article: article
entity_type: node
entity_field: type
plugin_id: bundle
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
entity_type: node
entity_field: created
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
title: 'Content Moderation Integration test'
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_interface'
- 'user.node_grants:view'
- user.permissions
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: content-moderation-integration-test
cache_metadata:
max-age: -1
contexts:
- 'languages:language_interface'
- 'user.node_grants:view'
- user.permissions
tags: { }
@@ -0,0 +1,260 @@
uuid: 0df3cfda-b782-4b0d-9810-3ca90a1638cc
langcode: en
status: true
dependencies:
config:
- node.type.article
module:
- node
- translation_views
- user
id: test_operations_links
label: test_operations_links
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: mini
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ‹‹
next: ››
style:
type: default
row:
type: fields
fields:
langcode:
id: langcode
table: node_field_data
field: langcode
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: language
settings:
link_to_entity: false
native_language: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
entity_field: langcode
plugin_id: field
translation_operations:
id: translation_operations
table: node_translation
field: translation_operations
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
destination: false
entity_type: node
plugin_id: translation_views_operations
filters:
status:
value: '1'
table: node_field_data
field: status
plugin_id: boolean
entity_type: node
entity_field: status
id: status
expose:
operator: ''
group: 1
type:
id: type
table: node_field_data
field: type
value:
article: article
entity_type: node
entity_field: type
plugin_id: bundle
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
entity_type: node
entity_field: created
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
title: Test
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: test_operations_links
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
@@ -322,7 +322,7 @@ display:
relationship: none
group_type: group
admin_label: ''
label: 'Target language equals default language'
label: 'Source translation of target language equals row language'
exclude: false
alter:
alter_text: false
@@ -478,6 +478,7 @@ display:
format_plural_string: !!binary MQNAY291bnQ=
prefix: ''
suffix: ''
include_original_language: false
entity_type: node
plugin_id: translation_views_translation_count
translation_outdated:
@@ -541,7 +542,7 @@ display:
relationship: none
group_type: group
admin_label: ''
label: 'Translation source equals row language'
label: 'Source translation of target language equals row language'
exclude: false
alter:
alter_text: false
@@ -836,7 +837,7 @@ display:
group_type: group
admin_label: ''
operator: '='
value: 2
value: All
group: 1
exposed: true
expose:
@@ -0,0 +1,63 @@
langcode: en
status: true
dependencies:
config:
- node.type.article
module:
- content_moderation
id: editorial
label: Editorial
type: content_moderation
type_settings:
states:
archived:
label: Archived
weight: 5
published: false
default_revision: true
draft:
label: Draft
published: false
default_revision: false
weight: -5
published:
label: Published
published: true
default_revision: true
weight: 0
transitions:
archive:
label: Archive
from:
- published
to: archived
weight: 2
archived_draft:
label: 'Restore to Draft'
from:
- archived
to: draft
weight: 3
archived_published:
label: Restore
from:
- archived
to: published
weight: 4
create_new_draft:
label: 'Create New Draft'
to: draft
weight: 0
from:
- draft
- published
publish:
label: Publish
to: published
weight: 1
from:
- draft
- published
entity_types:
node:
- article
@@ -9,9 +9,11 @@ dependencies:
- drupal:translation_views
- drupal:content_translation
- drupal:language
- drupal:content_moderation
- drupal:workflows
# Information added by Drupal.org packaging script on 2018-05-23
version: '8.x-1.0-alpha2'
# Information added by Drupal.org packaging script on 2019-02-07
version: '8.x-1.0-alpha5'
core: '8.x'
project: 'translation_views'
datestamp: 1527090186
datestamp: 1549581183
@@ -116,7 +116,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase {
'langcode' => $default_params[0],
'translation_target_language' => $default_params[1],
'translation_outdated' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => $orderColumn,
'sort' => $sort,
],
@@ -234,7 +234,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase {
$this->assertTextInRow(1, 'translation-changed', ':');
$this->assertTextInRow(1, 'translation-count', '0');
$this->assertTextInRow(1, 'translation-outdated', 'No');
$this->assertTextInRow(1, 'translation-source', 'No');
$this->assertTextInRow(1, 'translation-source', 'Yes');
$this->assertTextInRow(1, 'translation-status', 'Translated');
$this->assertTextInRow(1, 'translation-operations', 'Edit');
}
@@ -347,7 +347,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase {
2 => 'Yes',
]);
// Translation source equals row language.
// Source translation of target language equals row language.
$this->assertOrder('translation_source', 'asc', [
'All',
'***LANGUAGE_site_default***',
@@ -357,7 +357,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase {
2 => 'No',
3 => 'No',
4 => 'No',
5 => 'No',
5 => 'Yes',
6 => 'Yes',
]);
@@ -370,7 +370,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase {
5 => 'No',
4 => 'No',
3 => 'No',
2 => 'No',
2 => 'Yes',
1 => 'Yes',
]);
@@ -116,7 +116,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => '***LANGUAGE_site_default***',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
],
]);
$this->assertSession()->statusCodeEquals(200);
@@ -131,7 +131,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'de',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
],
]);
$this->assertSession()->statusCodeEquals(200);
@@ -146,7 +146,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'fr',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
],
]);
$this->assertSession()->statusCodeEquals(200);
@@ -161,7 +161,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => 'de',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
],
]);
$this->assertSession()->statusCodeEquals(200);
@@ -176,7 +176,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => 'fr',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
],
]);
$this->assertSession()->statusCodeEquals(200);
@@ -191,7 +191,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 1,
'translation_status' => '1',
],
]);
$this->assertSession()->statusCodeEquals(200);
@@ -206,7 +206,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 0,
'translation_status' => '0',
],
]);
$this->assertSession()->statusCodeEquals(200);
@@ -227,7 +227,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'title',
'sort' => 'asc',
],
@@ -247,7 +247,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'title',
'sort' => 'desc',
],
@@ -268,7 +268,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'langcode',
'sort' => 'asc',
],
@@ -288,7 +288,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'langcode',
'sort' => 'desc',
],
@@ -309,7 +309,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'translation_default',
'sort' => 'asc',
],
@@ -329,7 +329,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'translation_default',
'sort' => 'desc',
],
@@ -350,7 +350,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'translation_changed',
'sort' => 'asc',
],
@@ -370,7 +370,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'translation_changed',
'sort' => 'desc',
],
@@ -391,7 +391,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'translation_count',
'sort' => 'asc',
],
@@ -411,7 +411,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'translation_count',
'sort' => 'desc',
],
@@ -432,7 +432,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'translation_status',
'sort' => 'asc',
],
@@ -452,7 +452,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase {
'content_translation_source' => 'All',
'translation_target_language' => '***LANGUAGE_site_default***',
'translation_default' => 'All',
'translation_status' => 2,
'translation_status' => 'All',
'order' => 'translation_status',
'sort' => 'desc',
],
@@ -0,0 +1,103 @@
<?php
namespace Drupal\Tests\translation_views\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\workflows\Entity\Workflow;
/**
* Class ContentModerationIntegration.
*
* @package Drupal\Tests\translation_views\Functional
*
* @group translation_views
*/
class ContentModerationIntegration extends ViewTestBase {
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* {@inheritdoc}
*/
public static $modules = ['translation_views_test_views'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['content_moderation_integration_test'];
/**
* {@inheritdoc}
*/
public function setUp($import_test_views = TRUE) {
// Inherit set up from the parent class.
parent::setUp($import_test_views);
// Login as a root user.
$this->drupalLogin($this->rootUser);
// Import test views.
ViewTestData::createTestViews(get_class($this), ['translation_views_test_views']);
// Create additional language.
ConfigurableLanguage::createFromLangcode('af')->save();
// Enable translation for article nodes.
$this->drupalPostForm('admin/config/regional/content-language', [
"entity_types[node]" => 1,
"settings[node][article][translatable]" => 1,
"settings[node][article][settings][language][language_alterable]" => 1,
], 'Save configuration');
// Flush definitions caches.
\Drupal::entityTypeManager()->clearCachedDefinitions();
// Enable moderation state for article nodes.
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
$workflow->save();
// Logout.
$this->drupalLogout();
}
/**
* Test view's field "Translation Moderation State".
*/
public function testTranslationModerationFieldViews() {
// Login as a root user.
$this->drupalLogin($this->rootUser);
// Create node for test.
$node = $this->createNode(['type' => 'article']);
// Ensure we have moderation state "Draft" by default
// in the newly created node.
$this->assertTrue($node->hasField('moderation_state'));
$this->assertFalse($node->get('moderation_state')->isEmpty());
$this->assertEquals('draft', $node->get('moderation_state')->first()->getString());
// Go to the testing view's page.
$this->drupalGet('/content-moderation-integration-test');
$this->assertSession()->statusCodeEquals(200);
// Check that created node has "Draft" moderation state by default.
$this->assertSession()->elementTextContains(
'css',
'.views-field-translation-moderation-state span',
'Draft'
);
// Change moderation state to "Published".
$node->set('moderation_state', 'published')->save();
// Reload the testing view's page.
$this->drupalGet('/content-moderation-integration-test');
$this->assertSession()->statusCodeEquals(200);
// Check that the moderation state value has been changed in view's field.
$this->assertSession()->elementTextContains(
'css',
'.views-field-translation-moderation-state span',
'Published'
);
}
}
@@ -0,0 +1,221 @@
<?php
namespace Drupal\Tests\translation_views\Functional;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\Tests\BrowserTestBase;
/**
* Class LocalTranslationContentIntegrationTest.
*
* @package Drupal\Tests\translation_views\Functional
*
* @group translation_views
* @requires module local_translation
*/
class LocalTranslationContentIntegrationTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* {@inheritdoc}
*/
public static $modules = ['translation_views_local_translation_test'];
/**
* Local translation skills service.
*
* @var \Drupal\local_translation\Services\LocalTranslationUserSkills
*/
protected $skills;
/**
* User registered skills.
*
* @var array
*/
protected static $registeredSkills = ['en', 'fr'];
/**
* User unregistered skills.
*
* @var array
*/
protected static $unregisteredSkills = ['de', 'sq'];
/**
* Default language ID.
*
* @var string
*/
protected $defaultLanguage = 'en';
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->setUpTest();
}
/**
* Additional steps for tests set up.
*/
protected function setUpTest() {
$this->drupalLogin($this->rootUser);
$this->skills = $this->container->get('local_translation.user_skills');
$this->createLanguages();
$this->enableTranslation('node', 'article');
$this->drupalLogout();
}
/**
* Get array of all testing languages.
*
* @return array
* All testing langcodes array.
*/
private static function getAllTestingLanguages() {
return array_merge(static::$registeredSkills, static::$unregisteredSkills);
}
/**
* Change language settings for entity types.
*
* @param string $category
* Entity category (e.g. node).
* @param string $subcategory
* Entity subcategory (e.g. article).
*/
protected function enableTranslation($category, $subcategory) {
$this->drupalPostForm('admin/config/regional/content-language', [
"entity_types[$category]" => 1,
"settings[$category][$subcategory][translatable]" => 1,
"settings[$category][$subcategory][settings][language][language_alterable]" => 1,
], 'Save configuration');
\Drupal::entityTypeManager()->clearCachedDefinitions();
}
/**
* Register translation skills for testing.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function registerTestSkills() {
$this->skills->addSkill(static::$registeredSkills);
foreach (static::$registeredSkills as $skill) {
$this->assertTrue($this->skills->userHasSkill($skill));
}
}
/**
* Create additional languages for testing.
*/
protected function createLanguages() {
try {
foreach (static::getAllTestingLanguages() as $language) {
if ($language === $this->defaultLanguage) {
continue;
}
$this->assertEquals(1, ConfigurableLanguage::createFromLangcode($language)->save());
}
}
catch (EntityStorageException $e) {
$this->fail('Additional languages have not been created');
}
}
/**
* Simply check that all required modules have been installed.
*/
public function testDependencyInstallation() {
$this->assertTrue($this->container->get('module_handler')
->moduleExists('local_translation'));
$this->assertTrue($this->container->get('module_handler')
->moduleExists('local_translation_content'));
$this->assertTrue($this->container->has('local_translation.user_skills'));
}
/**
* Test local translation content integration for target language filter.
*/
public function testLocalTranslationLanguageFilterInView() {
$this->drupalLogin($this->rootUser);
$this->registerTestSkills();
for ($i = 1; $i <= 10; $i++) {
Node::create([
'type' => 'article',
'title' => $this->randomString(),
'langcode' => static::$registeredSkills[0],
])
->addTranslation(static::$registeredSkills[1], ['title' => $this->randomString()])
->save();
}
$this->drupalGet('/test-local-translation-content-filter');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->statusCodeNotEquals(404);
// Find langcode field element.
$langcode_field = $this->getSession()
->getPage()
->findField('translation_target_language');
$this->assertNotNull($langcode_field);
// Get all existing options of the langcode filter dropdown.
$options = $langcode_field->findAll('xpath', '//option');
$this->assertNotNull($options);
// Prepare array of options' values.
$language_options = array_map(function ($option) {
return $option->getAttribute('value') ?: $option->getText();
}, $options);
$this->assertCount(4, $language_options);
$this->assertContains('***LANGUAGE_site_default***', $language_options);
$this->assertContains('fr', $language_options);
$this->assertContains('de', $language_options);
$this->assertContains('sq', $language_options);
$this->drupalGet('/admin/structure/views/nojs/handler/test_local_translation_content_integration/page_1/filter/translation_target_language');
// Check for the default state of the options.
$this->assertSession()->checkboxNotChecked('options[limit]');
$this->assertSession()->checkboxNotChecked('options[column][from]');
$this->assertSession()->checkboxChecked('options[column][to]');
// Update options.
$this->drupalPostForm(NULL, [
'options[limit]' => 1,
'options[column][from]' => 1,
'options[column][to]' => 1,
], 'Apply');
$this->click('input[value="Save"]');
$this->drupalGet('/test-local-translation-content-filter');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->statusCodeNotEquals(404);
// Find langcode field element.
$langcode_field = $this->getSession()
->getPage()
->findField('translation_target_language');
$this->assertNotNull($langcode_field);
// Get all existing options of the langcode filter dropdown.
$options = $langcode_field->findAll('xpath', '//option');
$this->assertNotNull($options);
// Prepare array of options' values.
$language_options = array_map(function ($option) {
return $option->getAttribute('value') ?: $option->getText();
}, $options);
$this->assertCount(2, $language_options);
$this->assertContains('en', $language_options);
$this->assertContains('fr', $language_options);
$this->assertNotContains('de', $language_options);
$this->assertNotContains('sq', $language_options);
}
}
@@ -0,0 +1,211 @@
<?php
namespace Drupal\Tests\translation_views\Functional;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Class TranslationOperationTargetLanguageTest.
*
* @package Drupal\Tests\translation_views\Functional
*
* @group translation_views
*/
class TranslationOperationTargetLanguageTest extends ViewTestBase {
/**
* List of the additional language IDs to be created for the tests.
*
* @var array
*/
private static $langcodes = ['fr', 'de', 'it', 'af', 'sq'];
/**
* {@inheritdoc}
*/
public static $modules = [
'content_translation',
'node',
'translation_views',
'translation_views_test_views',
];
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* Testing views ID array.
*
* @var array
*/
public static $testViews = ['translation_views_all_filters_and_fields'];
/**
* Admin user.
*
* @var \Drupal\user\Entity\User
*/
private $adminUser;
/**
* Default language ID.
*
* @var string
*/
private $defaultLangcode;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->adminUser = $this->createUser([], 'test_admin', TRUE);
$this->drupalLogin($this->adminUser);
$this->defaultLangcode = \Drupal::languageManager()
->getDefaultLanguage()
->getId();
// Set up testing views.
ViewTestData::createTestViews(get_class($this), ['translation_views_test_views']);
try {
$this->setUpLanguages();
}
catch (EntityStorageException $e) {
$this->verbose($e->getMessage());
}
// Enable translation for Article nodes.
$this->enableTranslation('node', 'article');
// Create testing node.
$this->drupalPostForm('node/add/article', [
'title[0][value]' => $this->randomString(),
], 'Save');
$this->drupalLogout();
}
/**
* Set up languages.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
private function setUpLanguages() {
foreach (self::$langcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
}
/**
* Change language settings for entity types.
*
* @param string $category
* Entity category (e.g. node).
* @param string $subcategory
* Entity subcategory (e.g. article).
*/
private function enableTranslation($category, $subcategory) {
$this->drupalPostForm('admin/config/regional/content-language', [
"entity_types[$category]" => 1,
"settings[$category][$subcategory][translatable]" => 1,
"settings[$category][$subcategory][settings][language][language_alterable]" => 1,
], 'Save configuration');
\Drupal::entityTypeManager()->clearCachedDefinitions();
}
/**
* Translate node all specified languages.
*/
private function translateNode() {
$node = Node::load(1);
foreach (self::$langcodes as $langcode) {
if (!$node->hasTranslation($langcode)) {
$this->assertFalse($node->hasTranslation($langcode));
$node->addTranslation($langcode, ['title' => $this->randomMachineName()])->save();
$this->assertTrue($node->hasTranslation($langcode));
}
}
}
/**
* Generate random target language ID from available list.
*
* @return string
* Language ID.
*/
private function generateTargetLanguage() {
$target_language = self::$langcodes[mt_rand(0, 4)];
$this->assertNotNull($target_language);
return $target_language;
}
/**
* Assert that the response HTTP code is 200.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
private function assertResponseOk() {
$this->assertSession()->statusCodeEquals(200);
}
/**
* Test translation operations target language.
*
* @throws \Behat\Mink\Exception\ElementHtmlException
* @throws \Behat\Mink\Exception\ExpectationException
*/
public function testTranslationOperationsTargetLanguage() {
$base_selector = 'table > tbody > tr:nth-child(1) .views-field-translation-operations ul li';
$this->drupalLogin($this->adminUser);
// Check for "Add" links target languages.
$target_language = $this->generateTargetLanguage();
$this->drupalGet('translation-views-all-filters-and-fields', [
'query' => [
'content_translation_source' => 'All',
'translation_target_language' => $target_language,
'translation_default' => 'All',
'translation_status' => 'All',
],
]);
$this->assertResponseOk();
$expected_create_link = "/$target_language/node/1/translations/add/{$this->defaultLangcode}/$target_language";
$this->assertSession()
->elementAttributeContains(
'css',
"{$base_selector} a",
'href',
$expected_create_link
);
// Translate nodes.
$this->translateNode();
// Check for "Edit" and "Delete" links target languages.
$target_language = $this->generateTargetLanguage();
$this->drupalGet('translation-views-all-filters-and-fields', [
'query' => [
'content_translation_source' => 'All',
'translation_target_language' => $target_language,
'translation_default' => 'All',
'translation_status' => 'All',
],
]);
$this->assertResponseOk();
$this->assertSession()
->elementAttributeContains(
'css',
"{$base_selector}.edit a",
'href',
"/$target_language/node/1/edit"
);
$this->assertSession()
->elementAttributeContains(
'css',
"{$base_selector}.delete a",
'href',
"/$target_language/node/1/delete"
);
}
}
@@ -0,0 +1,285 @@
<?php
namespace Drupal\Tests\translation_views\Functional;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\user\Entity\Role;
use Drupal\views\Tests\ViewTestData;
/**
* Class TranslationOperationsFieldPermissionsTest.
*
* @group translation_views
*
* @package Drupal\Tests\translation_views\Functional
*/
class TranslationOperationsFieldPermissionsTest extends ViewTestBase {
/**
* List of the additional language IDs to be created for the tests.
*
* @var array
*/
private static $langcodes = ['fr', 'de', 'it', 'af', 'sq'];
/**
* User with permission to create translation.
*
* @var \Drupal\user\Entity\User
*/
private $userCreate;
/**
* User with permission to update translation.
*
* @var \Drupal\user\Entity\User
*/
private $userUpdate;
/**
* User with permission to delete translation.
*
* @var \Drupal\user\Entity\User
*/
private $userDelete;
/**
* Admin user.
*
* @var \Drupal\user\Entity\User
*/
private $adminUser;
/**
* {@inheritdoc}
*/
public static $modules = [
'content_translation',
'node',
'translation_views',
'translation_views_test_views',
];
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* Testing views ID array.
*
* @var array
*/
public static $testViews = ['test_operations_links'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->setUpUsers();
$this->drupalLogin($this->adminUser);
// Set up testing views.
ViewTestData::createTestViews(get_class($this), ['translation_views_test_views']);
try {
$this->setUpLanguages();
}
catch (EntityStorageException $e) {
$this->verbose($e->getMessage());
}
// Enable translation for Article nodes.
$this->enableTranslation('node', 'article');
// Create testing node.
$this->drupalPostForm('node/add/article', [
'title[0][value]' => $this->randomString(),
], 'Save');
$this->drupalLogout();
}
/**
* Set up users with different sets of permissions.
*/
private function setUpUsers() {
$this->adminUser = $this->createUser([], 'test_admin', TRUE);
$this->userCreate = $this->createUser(['create content translations']);
$this->userUpdate = $this->createUser(['update content translations']);
$this->userDelete = $this->createUser(['delete content translations']);
}
/**
* Set up languages.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
private function setUpLanguages() {
foreach (self::$langcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
}
/**
* Checks that text is in specific row.
*
* @param int $row_number
* Table row order number.
* @param string $css_class
* Part of the css class of required field.
* @param string $text
* Text that should be found in the element.
*
* @throws \Behat\Mink\Exception\ElementTextException
*/
private function assertTextInRow($row_number, $css_class, $text) {
$this->assertSession()
->elementTextContains(
'css',
".view-content > div:nth-child({$row_number}) .views-field-{$css_class}",
$text
);
}
/**
* Change language settings for entity types.
*
* @param string $category
* Entity category (e.g. node).
* @param string $subcategory
* Entity subcategory (e.g. article).
*/
private function enableTranslation($category, $subcategory) {
$this->drupalPostForm('admin/config/regional/content-language', [
"entity_types[$category]" => 1,
"settings[$category][$subcategory][translatable]" => 1,
"settings[$category][$subcategory][settings][language][language_alterable]" => 1,
], 'Save configuration');
\Drupal::entityTypeManager()->clearCachedDefinitions();
}
/**
* Go to the /translate/content page.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
private function goToTestingView() {
$this->drupalGet('/test_operations_links');
$this->assertSession()->statusCodeEquals(200);
}
/**
* Translate node all specified languages.
*/
private function translateNode() {
$node = Node::load(1);
foreach (self::$langcodes as $langcode) {
if (!$node->hasTranslation($langcode)) {
$node->addTranslation($langcode, ['title' => $this->randomMachineName()])
->save();
}
}
}
/**
* Add specific set of permissions to the "authenticated" role.
*
* @param array $permissions
* Permissions array.
*/
private function addPermissionsForAuthUser(array $permissions = []) {
if (!empty($permissions)) {
$role = Role::load(Role::AUTHENTICATED_ID);
if ($role instanceof Role) {
$this->grantPermissions($role, $permissions);
}
}
}
/**
* Test translation operation create permissions.
*
* @throws \Behat\Mink\Exception\ElementTextException
* @throws \Behat\Mink\Exception\ExpectationException
*/
public function testTranslationOperationsCreatePermissions() {
$default_language = \Drupal::languageManager()->getDefaultLanguage();
$target_language = static::$langcodes[mt_rand(0, 4)];
$this->assertNotNull($target_language);
$this->assertNotNull($default_language);
$this->drupalLogin($this->userCreate);
$this->assertTrue($this->userCreate->hasPermission('create content translations'));
$this->assertFalse($this->userCreate->hasPermission('translate any entity'));
$this->drupalGet('/translate/content', [
'query' => [
'langcode' => $default_language->getId(),
'translation_target_language' => $target_language,
'translation_outdated' => 'All',
'translation_status' => 'All',
],
]);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->elementNotExists(
'css',
'table > tbody > tr:nth-child(1) .views-field-translation-operations ul li a'
);
$this->addPermissionsForAuthUser(['translate any entity']);
$this->assertTrue($this->userCreate->hasPermission('translate any entity'));
$this->drupalGet('/translate/content', [
'query' => [
'langcode' => $default_language->getId(),
'translation_target_language' => $target_language,
'translation_outdated' => 'All',
'translation_status' => 'All',
],
]);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()
->elementTextContains(
'css',
"table > tbody > tr:nth-child(1) .views-field-translation-operations ul li a",
'Add'
);
}
/**
* Test translation operation update permissions.
*
* @throws \Behat\Mink\Exception\ElementTextException
* @throws \Behat\Mink\Exception\ExpectationException
*/
public function testTranslationOperationsUpdatePermissions() {
$this->translateNode();
$this->assertTrue($this->userUpdate->hasPermission('update content translations'));
$this->assertFalse($this->userUpdate->hasPermission('translate any entity'));
$this->drupalLogin($this->userUpdate);
$this->goToTestingView();
$this->assertTextInRow(
1,
'translation-operations ul li a',
'Edit'
);
}
/**
* Test translation operation delete permissions.
*
* @throws \Behat\Mink\Exception\ElementTextException
* @throws \Behat\Mink\Exception\ExpectationException
*/
public function testTranslationOperationsDeletePermissions() {
$this->translateNode();
$this->assertTrue($this->userDelete->hasPermission('delete content translations'));
$this->assertFalse($this->userDelete->hasPermission('translate any entity'));
$this->drupalLogin($this->userDelete);
$this->goToTestingView();
$this->assertTextInRow(
1,
'translation-operations ul li a',
'Delete'
);
}
}
@@ -7,8 +7,11 @@ dependencies:
- drupal:content_translation
- drupal:views
# Information added by Drupal.org packaging script on 2018-05-23
version: '8.x-1.0-alpha2'
test_dependencies:
- local_translation:local_translation
# Information added by Drupal.org packaging script on 2019-02-07
version: '8.x-1.0-alpha5'
core: '8.x'
project: 'translation_views'
datestamp: 1527090186
datestamp: 1549581183
@@ -4,8 +4,9 @@
* @file
* Provide hooks for translation_views module.
*
* Basicaly this file only used as workaround for this issue:
* https://www.drupal.org/node/2325463.
* Basicaly this file is only used as workaround for these issues:
* https://www.drupal.org/project/drupal/issues/2954032
* https://www.drupal.org/project/drupal/issues/2944278
*/
use Drupal\Core\Database\Query\AlterableInterface;
@@ -41,12 +42,12 @@ function translation_views_form_alter(&$form, FormStateInterface $form_state) {
}
$entity = $form_object->getEntity();
$op = $form_object->getOperation();
$operations = $form_object->getOperation();
if ($entity instanceof ContentEntityInterface
&& $entity->isTranslatable()
&& count($entity->getTranslationLanguages()) > 1
&& in_array($op, ['edit', 'add', 'default'], TRUE)
&& in_array($operations, ['edit', 'add', 'default'], TRUE)
) {
$form['source_langcode']['submit']['#submit'][] = 'translation_views_content_form_fix_destination';
}
@@ -66,9 +67,7 @@ function translation_views_content_form_fix_destination($form, FormStateInterfac
$options = [];
// Remove destination parameter from request,
// to allow "Change" button works like desired,
// see similar issue here: https://www.drupal.org/node/2325463.
// @todo: Create core issue for the content_translation module.
// to allow "Change" button works like desired
if ($query->has('destination')) {
$options['query']['destination'] = $query->get('destination');
$query->remove('destination');
@@ -2,7 +2,7 @@
/**
* @file
* Provide views extra filters/fields for translator needs.
* Provide views fields/filters for content translations.
*
* @ingroup views_module_handlers
*/
@@ -50,7 +50,7 @@ function translation_views_views_data_alter(array &$data) {
$translations_table =& $data[$key];
$translations_table_target =& $data[$target_key];
$pk = $entity_type->getKey('id');
$entity_id = $entity_type->getKey('id');
$translations_table['table'] = [
'group' => $group,
@@ -58,10 +58,10 @@ function translation_views_views_data_alter(array &$data) {
'join' => [
$field_data_table => [
'join_id' => 'translation_views_language_join',
'entity_pk' => $pk,
'entity_id' => $entity_id,
'type' => 'LEFT',
'left_field' => $pk,
'field' => $pk,
'left_field' => $entity_id,
'field' => $entity_id,
'table' => $field_data_table,
],
],
@@ -72,13 +72,17 @@ function translation_views_views_data_alter(array &$data) {
$translations_table += FieldDefinitions::buildOpLinksField();
$translations_table += FieldDefinitions::buildCountField();
$module_handler = \Drupal::service('module_handler');
if ($module_handler->moduleExists('content_moderation')) {
$translations_table += FieldDefinitions::buildModerationStateField();
}
$translations_table_target['table'] = [
'group' => $group,
'join' => [
$field_data_table => [
'type' => 'LEFT',
'left_field' => $pk,
'field' => $pk,
'left_field' => $entity_id,
'field' => $entity_id,
'table' => $field_data_table,
'extra' => [
[
@@ -99,9 +103,11 @@ function translation_views_views_data_alter(array &$data) {
$translations_table_target += FieldDefinitions::buildDefaultLangcodeField(
$data[$field_data_table]['default_langcode']
);
$translations_table_target += FieldDefinitions::buildChangedField(
$data[$field_data_table]['changed']
);
if (isset($data[$field_data_table]['changed'])) {
$translations_table_target += FieldDefinitions::buildChangedField(
$data[$field_data_table]['changed']
);
}
}
}
}
@@ -23,3 +23,6 @@ views.field.views_bulk_operations_bulk_form:
preconfiguration:
type: ignore
label: 'Preliminary configuration array'
clear_on_exposed:
type: boolean
label: 'Clear on exposed'
@@ -6,8 +6,8 @@ package: 'Views Bulk Operations'
dependencies:
- drupal:views_bulk_operations
# Information added by Drupal.org packaging script on 2018-07-02
version: '8.x-2.4'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-2.5'
core: '8.x'
project: 'views_bulk_operations'
datestamp: 1530516826
datestamp: 1550740388
@@ -6,8 +6,8 @@ package: 'Examples'
dependencies:
- drupal:views_bulk_operations
# Information added by Drupal.org packaging script on 2018-07-02
version: '8.x-2.4'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-2.5'
core: '8.x'
project: 'views_bulk_operations'
datestamp: 1530516826
datestamp: 1550740388
@@ -9,6 +9,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Ajax\AjaxResponse;
@@ -75,7 +76,12 @@ class ViewsBulkOperationsController extends ControllerBase implements ContainerI
$this->deleteTempstoreData();
$this->actionProcessor->executeProcessing($view_data);
return batch_process($view_data['redirect_url']);
if ($view_data['batch']) {
return batch_process($view_data['redirect_url']);
}
else {
return new RedirectResponse($view_data['redirect_url']->setAbsolute()->toString());
}
}
/**
@@ -215,6 +215,12 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe
'redirect_url' => Url::createFromRequest(clone $this->requestStack->getCurrentRequest()),
'exposed_input' => $this->view->getExposedInput(),
];
$query = $variable['redirect_url']->getOption('query');
if (!$query) {
$query = [];
}
$query += $variable['exposed_input'];
$variable['redirect_url']->setOption('query', $query);
// Create tempstore data object if it doesn't exist.
if (!is_array($this->tempStoreData)) {
@@ -624,7 +630,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe
$action_id = $form_state->getValue('action');
if (!empty($action_id)) {
$action = $this->actions[$action_id];
if ($this->isConfigurable($action)) {
if ($this->isActionConfigurable($action)) {
$actionObject = $this->actionManager->createInstance($action_id);
$form['header'][$this->options['id']]['configuration'] += $actionObject->buildConfigurationForm($form['header'][$this->options['id']]['configuration'], $form_state);
$form['header'][$this->options['id']]['configuration']['#config_included'] = TRUE;
@@ -778,7 +784,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe
$this->tempStoreData['list'] = [];
}
$configurable = $this->isConfigurable($action);
$configurable = $this->isActionConfigurable($action);
// Get configuration if using AJAX.
if ($configurable && empty($this->options['form_step'])) {
@@ -794,6 +800,11 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe
}
// Routing - determine redirect route.
// Set default redirection due to issue #2952498.
// TODO: remove the next line when core cause is eliminated.
$redirect_route = 'views_bulk_operations.execute_batch';
if ($this->options['form_step'] && $configurable) {
$redirect_route = 'views_bulk_operations.execute_configurable';
}
@@ -887,7 +898,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe
/**
* Check if an action is configurable.
*/
protected function isConfigurable($action) {
protected function isActionConfigurable($action) {
return (in_array('Drupal\Core\Plugin\PluginFormInterface', class_implements($action['class']), TRUE) || method_exists($action['class'], 'buildConfigurationForm'));
}
@@ -7,8 +7,8 @@ dependencies:
- drupal:views_bulk_operations
- drupal:node
# Information added by Drupal.org packaging script on 2018-07-02
version: '8.x-2.4'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-2.5'
core: '8.x'
project: 'views_bulk_operations'
datestamp: 1530516826
datestamp: 1550740388
@@ -6,8 +6,8 @@ package: 'Views'
dependencies:
- drupal:views (>=8.4)
# Information added by Drupal.org packaging script on 2018-07-02
version: '8.x-2.4'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-2.5'
core: '8.x'
project: 'views_bulk_operations'
datestamp: 1530516826
datestamp: 1550740388