translators-8.x-1.x-dev/modules/translators_content/src/Controller/TranslatorsContentTranslationOverviewController.php
modules/translators_content/src/Controller/TranslatorsContentTranslationOverviewController.php
<?php
namespace Drupal\translators_content\Controller;
use Drupal\content_translation\ContentTranslationManagerInterface;
use Drupal\content_translation\Controller\ContentTranslationController;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\translators\Services\TranslatorSkills;
use Drupal\translators_content\Access\TranslatorsContentManageAccessCheck;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for entity translation controllers.
*/
class TranslatorsContentTranslationOverviewController extends ContentTranslationController {
/**
* User skills service.
*
* @var \Drupal\translators\Services\TranslatorSkills
*/
protected $translatorSkills;
/**
* Translator access manager.
*
* @var \Drupal\translators_content\Access\TranslatorsContentManageAccessCheck
*/
protected $accessManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('content_translation.manager'),
$container->get('entity_field.manager'),
$container->get('content_translation.manage_access'),
$container->get('translators.skills')
);
}
/**
* TranslatorsContentTranslationOverviewController constructor.
*
* @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
* Content translation manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\translators_content\Access\TranslatorsContentManageAccessCheck $access_manager
* Translator access manager.
* @param \Drupal\translators\Services\TranslatorSkills $translatorSkills
* User skills service.
*/
public function __construct(
ContentTranslationManagerInterface $manager,
EntityFieldManagerInterface $entity_field_manager,
TranslatorsContentManageAccessCheck $access_manager,
TranslatorSkills $translatorSkills
) {
parent::__construct($manager, $entity_field_manager);
$this->translatorSkills = $translatorSkills;
$this->accessManager = $access_manager;
}
/**
* Builds the translations overview page.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param string $entity_type_id
* (optional) The entity type ID.
* @param bool $filter
* The filter option to filter content translation links or no.
*
* @return array
* Array of page elements to render.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
public function overview(RouteMatchInterface $route_match, $entity_type_id = NULL, $filter = TRUE) {
$build = parent::overview($route_match, $entity_type_id);
// Post processing translation operations links.
$rows =& $build['content_translation_overview']['#rows'];
$this->postProcessTranslationsOperations(
$rows,
$route_match->getParameter($entity_type_id)
);
// Add translators settings to cacheability metadata.
$cacheability = CacheableMetadata::createFromRenderArray($build);
$cacheability = $cacheability
->merge(CacheableMetadata::createFromObject($this->config('translators.settings')));
$cacheability->applyTo($build);
if (!$filter || empty($this->config('translators.settings')->get('enable_filter_translation_overview_to_skills'))) {
return $build;
}
$translator_langcodes = $this->translatorSkills->getAllLangcodes();
if (empty($translator_langcodes)) {
$this->translatorSkills->showMissingTranslationSkillsWarning();
}
$user_langs_rows = $other_langs_rows = [];
$extracted = $this->extractLanguagesWithGroups($rows, $translator_langcodes);
if (isset($extracted[0]) && !empty($extracted[0])) {
$user_langs_rows = $extracted[0];
}
if (isset($extracted[1]) && !empty($extracted[1])) {
$other_langs_rows = $extracted[1];
}
if (!empty($user_langs_rows)) {
foreach ($user_langs_rows as $key => $row) {
$user_langs_rows[$key] = $rows[$row];
}
}
$rows = $user_langs_rows;
if (!empty($other_langs_rows)) {
$entity_type_id = $route_match->getParameter('entity_type_id');
$build['more_link'] = [
'#title' => $this->t('Show all languages'),
'#type' => 'link',
'#attributes' => [
'class' => [
'use-ajax',
'button', 'button--small',
'more-link',
'more-link-translations',
],
'id' => 'show-more-translations-link',
],
'#url' => Url::fromRoute(
$route_match->getRouteName() . '.more',
[
$entity_type_id => $route_match->getParameter($entity_type_id)->id(),
'method' => 'ajax',
]
),
];
$build['content_translation_overview']['#attributes']['id'] = 'content-translations-list';
$build['#attached']['library'][] = 'core/drupal.ajax';
}
return $build;
}
/**
* Get more languages.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* Route match interface.
* @param string|null $entity_type_id
* Entity type ID.
* @param string $method
* Method name. Values allowed - "noajax" and "ajax". Defaults to "ajax".
*
* @return array|\Drupal\Core\Ajax\AjaxResponse
* Array of languages or AJAX response.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
public function getMoreLanguages(RouteMatchInterface $route_match, $entity_type_id = NULL, $method = 'ajax') {
$build = self::overview($route_match, $entity_type_id, FALSE);
$rows =& $build['content_translation_overview']['#rows'];
$translator_langcodes = $this->translatorSkills->getAllLangcodes();
$user_langs_rows = $other_langs_rows = [];
$extracted = $this->extractLanguagesWithGroups($rows, $translator_langcodes);
if (isset($extracted[0]) && !empty($extracted[0])) {
$user_langs_rows = $extracted[0];
}
if (isset($extracted[1]) && !empty($extracted[1])) {
$other_langs_rows = $extracted[1];
}
$other_langs_rows = array_intersect_key($rows, array_flip($other_langs_rows));
if ($method == 'noajax') {
$rows = $other_langs_rows;
return $build;
}
elseif ($method == 'ajax') {
$response = new AjaxResponse();
foreach ($user_langs_rows as $key => $row) {
$user_langs_rows[$key] = $rows[$row];
}
$rows = array_merge($user_langs_rows, $other_langs_rows);
$replace = new ReplaceCommand('#content-translations-list', $build['content_translation_overview']);
$remove = new RemoveCommand('#show-more-translations-link');
$response->addCommand($replace);
$response->addCommand($remove);
return $response;
}
return [];
}
/**
* Extract language from row.
*
* @param array &$row
* Row array.
*
* @return mixed
* Extracted language from row.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
private function extractLanguageFromRow(array &$row) {
$label = reset($row);
self::extractDefaultLanguageName($label);
return !is_string($label)
? $this->languageManager->getDefaultLanguage()
: $this->getLanguageByLabel($label);
}
/**
* Extract languages with groups.
*
* @param array &$rows
* Rows array.
* @param array $translator_langcodes
* User languages array.
*
* @return array
* Languages array.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function extractLanguagesWithGroups(array &$rows, array $translator_langcodes) {
$groups = [];
$original_key = NULL;
foreach ($rows as $key => $row) {
$language = $this->extractLanguageFromRow($row);
$is_original = stripos((string) reset($row), 'original') !== FALSE;
$delta = $language instanceof LanguageInterface
&& (in_array($language->getId(), $translator_langcodes) || ($is_original && !empty($this->config('translators.settings')->get('always_display_original_language_translation_overview'))))
? 0 : 1;
if ($is_original) {
$original_key = $key;
}
$groups[$delta][] = $key;
}
// Move original language to the top of the array if visable.
if (!is_null($original_key) && !empty($groups[0]) && in_array($original_key, $groups[0])) {
$original_key = array_search($original_key, $groups[0]);
$original = $groups[0][$original_key];
unset($groups[0][$original_key]);
array_unshift($groups[0], $original);
}
return $groups;
}
/**
* Get language config entity.
*
* @param string|null $label
* Language label.
*
* @return \Drupal\Core\Language\LanguageInterface|mixed|null
* Language object if exists.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getLanguageByLabel($label = NULL) {
if (empty($label)) {
return NULL;
}
$languages = $this->entityTypeManager
->getStorage('configurable_language')
->loadByProperties(['label' => $label]);
return !empty($languages) ? reset($languages) : NULL;
}
/**
* Additional post processing function.
*
* Post processing translation operations links.
*
* @param array $lang_rows
* Language rows array.
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* Processed entity.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
protected function postProcessTranslationsOperations(array &$lang_rows, ContentEntityInterface $entity) {
$entity_type_id = $entity->getEntityTypeId();
$handler = $this->entityTypeManager()->getHandler($entity_type_id, 'translation');
foreach ($lang_rows as &$langs_row) {
if (!empty($langs_row) && is_array($langs_row)) {
$label = reset($langs_row);
self::extractDefaultLanguageName($label);
if (!empty($label) && is_string($label)) {
$key = self::getLastArrayKey($langs_row);
$operations =& $langs_row[$key]['data']['#links'];
$language = static::getLanguageByLabel($label);
$langcode = $language->id();
$is_default = $entity->getUntranslated()->language()->getId() == $langcode;
if ($entity->hasTranslation($langcode)) {
$entity = $entity->getTranslation($langcode);
// Build Edit links.
if ($entity->access('update')) {
$operations['edit'] = $this->buildEntityUrl($entity, 'Edit', $language);
}
elseif (!$is_default
&& $handler->getTranslationAccess($entity, 'update', $langcode)->isAllowed()) {
$operations['edit'] = $this->buildTranslationUrl($entity, 'Edit', $language);
}
else {
unset($operations['edit']);
}
// Build Delete links.
if ($entity->access('delete')) {
$operations['delete'] = $this->buildEntityUrl($entity, 'Delete', $language);
}
elseif (!$is_default
&& $handler->getTranslationAccess($entity, 'delete', $langcode)->isAllowed()) {
$operations['delete'] = $this->buildTranslationUrl($entity, 'Delete', $language);
}
else {
unset($operations['delete']);
}
}
else {
$source = $this->translatorSkills->getTranslationSourceLangcode($entity, $langcode);
if ($handler->getTranslationAccess($entity, 'create', $source, $langcode)->isAllowed()) {
$operations['add'] = $this->buildAddLink($entity, $source, $language);
}
else {
unset($operations['add']);
}
}
}
}
}
}
/**
* Build "Add" translation links.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* Entity object.
* @param string $source
* Source langcode.
* @param \Drupal\Core\Language\LanguageInterface $language
* Language object.
*
* @return array
* An array representing links.
*/
private function buildAddLink(ContentEntityInterface $entity, string $source, LanguageInterface $language) {
$entity_type_id = $entity->getEntityTypeId();
$route_name = "entity.$entity_type_id.content_translation_add";
$add_url = Url::fromRoute($route_name, [
'source' => $source,
'target' => $language->id(),
$entity_type_id => $entity->id(),
]);
return [
'url' => $add_url,
'language' => $language,
'title' => $this->t('Add'),
];
}
/**
* Build entity operation links.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* Entity object.
* @param string $label
* Operation label.
* @param \Drupal\Core\Language\LanguageInterface $language
* Language object.
*
* @return array
* An array representing links.
*/
private function buildEntityUrl(ContentEntityInterface $entity, string $label, LanguageInterface $language) {
$operation = strtolower($label);
return [
'url' => $entity->toUrl("$operation-form"),
'language' => $language,
'title' => $this->t($label),
];
}
/**
* Build translation operation links.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* Entity object.
* @param string $label
* Operation label.
* @param \Drupal\Core\Language\LanguageInterface $language
* Language object.
*
* @return array
* An array representing links.
*/
protected function buildTranslationUrl(ContentEntityInterface $entity, string $label, LanguageInterface $language) {
$operation = strtolower($label);
$options = ['language' => $language];
$url = $entity->toUrl("drupal:content-translation-$operation", $options)
->setRouteParameter('language', $language->getId());
return [
'url' => $url,
'title' => $this->t($label),
];
}
/**
* Extracting the default language label.
*
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup &$name
* Language name/label.
*/
private static function extractDefaultLanguageName(&$name) {
if ($name instanceof TranslatableMarkup) {
$name = $name->getArguments()['@language_name'];
}
}
/**
* Get last array key.
*
* @param array $array
* Array to be processed.
*
* @return mixed
* Last array key.
*/
private static function getLastArrayKey(array $array) {
$keys = array_keys($array);
return end($keys);
}
}
