simple_tmgmt-1.0.x-dev/simple_tmgmt.module
simple_tmgmt.module
<?php
/**
* @file
* Contains simple_tmgmt.module.
*/
use Drupal\workflows\WorkflowInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\Core\GeneratedLink;
use Drupal\Core\Render\Markup;
use Drupal\tmgmt\JobInterface;
use Drupal\tmgmt\JobItemInterface;
use Drupal\simple_tmgmt\SimpleTmgmt;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Implements hook_help().
*/
function simple_tmgmt_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the simple_tmgmt module.
case 'help.page.simple_tmgmt':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('UX experiments to simplify the workflow for basic use cases and integration with other modules.') . '</p>';
// @todo add Simple TMGMT help page on DO
$output .= '<p><a href="https://www.drupal.org/docs/8/modules/translation-management-tool">' . t('Translation Management documentation.') . '</a></p>';
return $output;
default:
}
}
/**
* Implements hook_theme().
*/
function simple_tmgmt_theme() {
return [
// Custom mail templates.
'mail__job_create' => [
'variables' => [
'content' => [],
],
],
'mail__job_error' => [
'variables' => [
'content' => [],
],
],
// Override tmgmt_data_items_form to optionally
// remove the validation tick boxes.
'tmgmt_data_items_form' => [
'render element' => 'element',
],
];
}
/**
* Implements hook_mail().
*/
function simple_tmgmt_mail($key, &$message, $params) {
switch ($key) {
case 'job_create':
$message['headers'] = $params['headers'];
$message['headers']['Content-Type'] = SWIFTMAILER_FORMAT_HTML;
$message['from'] = $params['from'];
$message['subject'] = $params['subject'];
$message['body'][] = $params['body'];
$message['options'] = $params['options'];
$message['files'] = $params['files'];
break;
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Step 1, translation form.
*/
function simple_tmgmt_form_tmgmt_content_translate_form_alter(&$form, FormStateInterface $form_state, $form_id) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $form_state->get('entity');
// Does not cover yet forms for configuration entities.
if (!$entity instanceof ContentEntityInterface) {
return;
}
$config = \Drupal::configFactory()->get('simple_tmgmt.settings');
// Remove the add to cart operation.
// @todo provide configuration/permission
// if (array_key_exists('add_to_cart', $form['actions'])) {
// $form['actions']['add_to_cart']['#access'] = FALSE;
// }.
// Remove disabled languages from the translation form.
// This needs to be covered by https://www.drupal.org/project/disable_language/issues/3091554
// But as we are already making use of the TMGMT form override this issue
// fix has an easy workaround.
if (
Drupal::moduleHandler()->moduleExists('disable_language') &&
!Drupal::currentUser()->hasPermission('create content in disabled language')
) {
$disabledLanguages = \Drupal::service('disable_language.disable_language_manager')->getDisabledLanguages();
foreach (array_keys($disabledLanguages) as $langCode) {
if (array_key_exists($langCode, $form['languages']['#options'])) {
unset($form['languages']['#options'][$langCode]);
}
}
}
// Add the help link.
/** @var \Drupal\simple_tmgmt\SimpleTmgmt $simpleTmgmt */
$simpleTmgmt = \Drupal::service('simple_tmgmt');
$form['actions']['help'] = [
'#type' => 'markup',
'#markup' => $simpleTmgmt->getHelpLinkMarkup(),
];
// Add the manual and machine providers actions only to translatable items.
if (array_key_exists('languages', $form) && array_key_exists('#options', $form['languages'])) {
// Get untranslatable items.
// Source and languages that are already the subject of a Job
// are already excluded by TMGMT.
$availableLanguages = \Drupal::languageManager()->getLanguages();
$availableLanguageIds = array_keys($availableLanguages);
$untranslatableLanguages = [];
foreach ($form['languages'] as $langCode => $tableSelectElement) {
if (
in_array($langCode, $availableLanguageIds) &&
$form['languages'][$langCode]['#disabled'] === TRUE
) {
$untranslatableLanguages[] = $langCode;
}
}
// @todo refactor so it is the responsibility of tmgmt_content_moderation.
if (\Drupal::moduleHandler()->moduleExists('tmgmt_content_moderation')) {
/** @var \Drupal\tmgmt_content_moderation\TmgmtContentModerationInterface $tmgmtContentModeration */
$tmgmtContentModeration = \Drupal::service('tmgmt_content_moderation');
if (!$tmgmtContentModeration->isSourceStateValidForTranslation($entity)) {
$untranslatableLanguages = $availableLanguageIds;
}
}
// Disable checkboxes if 'translate again' is not set.
if ($config->get('translate_again') !== 1) {
$availableEntityTranslations = array_keys($entity->getTranslationLanguages(FALSE));
foreach ($availableEntityTranslations as $availableEntityTranslation) {
$form['languages'][$availableEntityTranslation] = [
'#type' => 'checkbox',
'#disabled' => TRUE,
];
$untranslatableLanguages[] = $availableEntityTranslation;
}
}
$hasAllTranslations = count($untranslatableLanguages) === count($availableLanguages);
// When all translations have been completed, remove the translation request action.
if ($hasAllTranslations) {
$form['actions']['request']['#access'] = FALSE;
$form['actions']['translations_completed'] = [
'#type' => 'markup',
'#markup' => t('All translation requests are pending or have been completed.'),
'#weight' => -10,
];
}
// Remove operation links when several translations are requested.
else {
$form['#attached']['library'][] = 'simple_tmgmt/entity_translate_form_behaviors';
}
// Get available / active translation providers (Translators).
$activeProviders = $simpleTmgmt->getActiveTranslators();
// Get column indexes.
$headerIndex = 0;
foreach ($form['languages']['#header'] as $header) {
$headerIndex++;
if ($header instanceof TranslatableMarkup) {
if ($header->getUntranslatedString() === 'Pending Translations') {
$pendingTranslationsColumnIndex = $headerIndex;
}
}
}
// Modify links and operations with available providers.
foreach ($form['languages']['#options'] as $langCode => &$languageRow) {
$currentColumnIndex = 0;
foreach ($languageRow as $rowKey => &$rowItem) {
$currentColumnIndex++;
// Modify "Pending translations" links.
if (
$rowItem instanceof GeneratedLink &&
$currentColumnIndex === $pendingTranslationsColumnIndex
) {
$link = $rowItem->getGeneratedLink();
$transformedLink = $simpleTmgmt->transformTranslationLink($link);
$rowItem->setGeneratedLink($transformedLink);
}
// Modify "Operations" links.
if (
is_array($rowItem) &&
array_key_exists('data', $rowItem) &&
array_key_exists('#type', $rowItem['data']) &&
$rowItem['data']['#type'] === 'operations'
) {
$addTmgmtOperations = FALSE;
$isNewContent = array_key_exists('add', $languageRow[$rowKey]['data']['#links']);
// Only add TMGMT translators operations if 'translate again' is set
// for existing content.
if (
!$isNewContent &&
$config->get('translate_again') === 1
) {
$addTmgmtOperations = TRUE;
}
// Always add TMGMT translators operations while creating content.
else {
$addTmgmtOperations = TRUE;
}
if ($addTmgmtOperations) {
// Add providers.
if (!in_array($langCode, $untranslatableLanguages)) {
// Change 'add' operation title.
if ($isNewContent) {
$languageRow[$rowKey]['data']['#links']['add']['title'] = $config->get('operation_add');
}
// Check if the translation is possible to add the provider link.
$supportedProviders = [];
foreach ($activeProviders as $activeProvider) {
if ($simpleTmgmt->isTranslationSupported($activeProvider, $entity->language(), $langCode)) {
$supportedProviders[$activeProvider->id()] = [
'title' => empty($overriddenLabel) ? $activeProvider->label() : $overriddenLabel,
'url' => $simpleTmgmt->getJobCreateUrl($entity, $langCode, $activeProvider->id()),
];
}
}
if (!empty($supportedProviders)) {
$insertBeforeKey = 'add';
$index = array_search($insertBeforeKey, array_keys($languageRow[$rowKey]['data']['#links']));
array_splice($languageRow[$rowKey]['data']['#links'], $index, 0, $supportedProviders);
}
}
// Do not allow to add a manual translation when a Job is in progress.
else {
unset($languageRow[$rowKey]['data']['#links']['add']);
}
}
}
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Step 2, Job edit form.
*/
function simple_tmgmt_form_tmgmt_job_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
/** @var \Drupal\simple_tmgmt\SimpleTmgmtInterface $simpleTmgmt */
$simpleTmgmt = \Drupal::service('simple_tmgmt');
// Remove disabled actions.
$disabledActions = $simpleTmgmt->getJobFormDisabledActions();
foreach ($disabledActions as $action) {
if (array_key_exists($action, $form['actions'])) {
$form['actions'][$action]['#access'] = FALSE;
}
}
// Add redirect to the node translation form for the "Delete" action.
if (array_key_exists('delete', $form['actions'])) {
$simpleTmgmt->setTranslationFormUrl($form_state);
$form['actions']['delete']['#submit'][] = '_simple_tmgmt_delete_job_form_submit_handler';
}
// Disable target language select element.
// @todo make it configurable.
// if (array_key_exists('target_language', $form['info'])) {
// $form['info']['target_language']['#disabled'] = TRUE;
// }.
if (array_key_exists('translator', $form['translator_wrapper'])) {
// Override default settings title.
$form['translator_wrapper']['settings']['#title'] = t('Settings');
$translatorsToRemove = SimpleTmgmt::translatorsToDisable();
foreach ($form['translator_wrapper']['translator']['#options'] as $optionKey => $option) {
if (in_array($optionKey, $translatorsToRemove)) {
unset($form['translator_wrapper']['translator']['#options'][$optionKey]);
}
}
if (array_key_exists('submit_all', $form['translator_wrapper'])) {
// Depending on the state of several Job Items for the same entity,
// it can happen that the "Submit all" checkbox
// that applies to several Jobs is displayed even if they
// are not in the scope of a batch.
// Holding an internal state for this case so we can safely remove
// this checkbox if necessary.
$hasSeveralJobs = FALSE;
if (
array_key_exists('progress_details', $form) &&
array_key_exists('job_list', $form['progress_details'])
) {
$hasSeveralJobs = $form['progress_details']['job_list']['#items'] > 1;
}
$form['translator_wrapper']['submit_all']['#access'] = $hasSeveralJobs;
// Check by default 'submit all translations jobs with the same settings'.
$form['translator_wrapper']['submit_all']['#default_value'] = TRUE;
}
// Add custom submit handler
// for redirection based on the translator provider.
$form['actions']['submit']['#submit'][] = '_simple_tmgmt_form_tmgmt_job_edit_form_submit_handler';
// Select default provider from the url parameter if any.
// Redirection based on the provider are set by the destination
// from the JobController in this case.
// @todo possibly refactor redirection with the custom submit handler.
// @todo fails when the first provider is unsupported (e.g. DeepL not translatable)
$defaultProvider = \Drupal::request()->get('default_provider');
if (
!empty($defaultProvider) &&
// in_array($defaultProvider, $validProviders) &&.
$form_state->getValue('translator') !== $defaultProvider
) {
/** @var \Drupal\tmgmt\JobInterface $job */
$job = \Drupal::routeMatch()->getParameter('tmgmt_job');
$form['translator_wrapper']['translator']['#default_value'] = $defaultProvider;
// @todo ideally trigger ajax change, disabling translator to prevent on change issues.
// $form['translator_wrapper']['translator']['#disabled'] = TRUE;
// Unset the logo if the provider is manual.
if (
array_key_exists('logo', $form['translator_wrapper']) &&
$defaultProvider === 'simple_tmgmt_file_mail'
) {
unset($form['translator_wrapper']['logo']);
}
/** @var \Drupal\tmgmt\TranslatorManager $translatorManager */
$translatorManager = \Drupal::service('plugin.manager.tmgmt.translator');
$settingsForm = [];
if (!$job->hasTranslator()) {
return $settingsForm;
}
$translator = $job->getTranslator();
$translatorResult = $translator->checkAvailable();
if (!$translatorResult->getSuccess()) {
$settingsForm['#description'] = $translatorResult->getReason();
return $settingsForm;
}
$translatorResult = $translator->checkTranslatable($job);
if ($job->getTargetLangcode() && !$translatorResult->getSuccess()) {
$settingsForm['#description'] = $translatorResult->getReason();
return $settingsForm;
}
$plugin_ui = $translatorManager->createUIInstance($defaultProvider);
$settingsForm = $plugin_ui->checkoutSettingsForm($settingsForm, $form_state, $job);
$form['translator_wrapper']['settings'] = [
'#type' => 'details',
'#title' => t('Settings'),
'#prefix' => '<div id="tmgmt-ui-translator-settings">',
'#suffix' => '</div>',
'#tree' => TRUE,
'#open' => TRUE,
] + $settingsForm;
}
}
if (array_key_exists('job_items_wrapper', $form)) {
// Hide suggestions details.
if (array_key_exists('suggestions', $form['job_items_wrapper'])) {
$form['job_items_wrapper']['suggestions']['#access'] = FALSE;
}
if (array_key_exists('items', $form['job_items_wrapper'])) {
// Open Job Items by default.
$form['job_items_wrapper']['items']['#open'] = TRUE;
// Some changes are only possible if there is one single job item.
// As a mail is sent with the node title, prevent sending multiple items
// while using manual translation.
$itemsAmount = count($form['job_items_wrapper']['items']['view']['#rows']);
if ($itemsAmount === 1) {
/** @var \Drupal\views\ViewExecutable $view */
$view = $form['job_items_wrapper']['items']['view']['#view'];
$view->execute();
$result = $view->result;
/** @var \Drupal\tmgmt\JobItemInterface $jobItem */
$jobItem = $result[0]->_entity;
// Replace the label with markup, and set the default value
// using the node title.
$form['label']['widget'][0]['value']['#access'] = FALSE;
$form['label']['widget'][0]['value']['#default_value'] = $jobItem->getSourceLabel();
$form['label']['#markup'] = '<h1>' . $jobItem->getSourceLabel() . '</h1>';
// In this case the Job items are redundant, remove them.
$form['job_items_wrapper']['items']['#access'] = FALSE;
}
}
}
}
/**
* Custom submit handler to redirect based on the selected translator provider.
*/
function _simple_tmgmt_form_tmgmt_job_edit_form_submit_handler(array &$form, FormStateInterface $form_state) {
// Set form redirect for Machine translator.
/** @var \Drupal\simple_tmgmt\SimpleTmgmtInterface $simpleTmgmt */
$simpleTmgmt = \Drupal::service('simple_tmgmt');
if ($simpleTmgmt->isMachineTranslator($form_state->getValue('translator'))) {
$job = \Drupal::routeMatch()->getParameter('tmgmt_job');
if ($job instanceof JobInterface) {
try {
$jobItemStorage = \Drupal::entityTypeManager()->getStorage('tmgmt_job_item');
$jobItems = $jobItemStorage->loadByProperties([
'tjid' => $job->id(),
]);
if (!empty($jobItems)) {
$jobItem = reset($jobItems);
if ($jobItem instanceof JobItemInterface) {
$form_state->setRedirect('entity.tmgmt_job_item.canonical', [
'tmgmt_job_item' => $jobItem->id(),
]);
}
}
}
catch (\Exception $exception) {
\Drupal::messenger()->addError($exception->getMessage());
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Step 3, Job item edit form.
*/
function simple_tmgmt_form_tmgmt_job_item_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
/** @var \Drupal\simple_tmgmt\SimpleTmgmtInterface $simpleTmgmt */
$simpleTmgmt = \Drupal::service('simple_tmgmt');
// Remove disabled actions.
$disabledActions = $simpleTmgmt->getJobItemFormDisabledActions();
foreach ($disabledActions as $action) {
if (array_key_exists($action, $form['actions'])) {
$form['actions'][$action]['#access'] = FALSE;
}
}
// Changes the UX for the tick boxes status (red/green, message).
$form['#attached']['library'][] = 'simple_tmgmt/job_item_edit_form_behaviors';
/** @var \Drupal\tmgmt\JobItemInterface $jobItem */
$jobItem = \Drupal::routeMatch()->getParameter('tmgmt_job_item');
if (
!$jobItem instanceof JobItemInterface ||
!$jobItem->getSourceUrl() instanceof Url
) {
return;
}
// There might be another way to get the entity?
$options = $jobItem->getSourceUrl()->getOptions();
if (array_key_exists('entity', $options)) {
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $options['entity'];
// Add a custom submit handler if not handled by tmgmt_content_moderation.
// @todo cover other cases for other entity types:
if ($entity instanceof ContentEntityInterface) {
$hasEntityWorkflow = FALSE;
if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
/** @var \Drupal\content_moderation\ModerationInformationInterface $contentModerationInformation */
$contentModerationInformation = \Drupal::service('content_moderation.moderation_information');
$hasEntityWorkflow = $contentModerationInformation->getWorkflowForEntityTypeAndBundle($entity->getEntityTypeId(), $entity->bundle()) instanceof WorkflowInterface;
}
if (!$hasEntityWorkflow) {
// When the job is saved as completed, redirect to the translated node.
$form['actions']['accept']['#submit'][] = '_simple_tmgmt_form_tmgmt_job_item_edit_form_submit_handler';
}
}
}
}
/**
* Custom submit handler to redirect to the translated entity once the Job Item is completed.
*/
function _simple_tmgmt_form_tmgmt_job_item_edit_form_submit_handler(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\tmgmt\JobItemInterface $jobItem */
$jobItem = \Drupal::routeMatch()->getParameter('tmgmt_job_item');
// Source entity url (e.g. node).
$entityUrl = $jobItem->getSourceUrl();
$targetLanguage = $jobItem->getJob()->getTargetLanguage();
$entityUrl->setOption('language', $targetLanguage);
$form_state->setRedirectUrl($entityUrl);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Transforms translation links.
*
* Step 4, translation overview form (/admin/tmgmt/sources).
*/
function simple_tmgmt_form_tmgmt_overview_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (!empty($form['items']['#options'])) {
/** @var \Drupal\simple_tmgmt\SimpleTmgmtInterface $simpleTmgmt */
$simpleTmgmt = \Drupal::service('simple_tmgmt');
foreach ($form['items']['#options'] as $entityItemKey => $entityItem) {
foreach ($entityItem as $entryKey => $entryValue) {
if (
strpos($entryKey, 'langcode') === 0 &&
array_key_exists('data', $entryValue)
) {
$markup = (string) $entryValue['data'];
$transformedMarkup = $simpleTmgmt->transformTranslationLink($markup);
$form['items']['#options'][$entityItemKey][$entryKey]['data'] = Markup::create($transformedMarkup);
}
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* When the job is deleted, redirect to the node translation form.
*/
function simple_tmgmt_form_tmgmt_job_delete_form_alter(&$form, FormStateInterface $form_state, $form_id) {
/** @var \Drupal\simple_tmgmt\SimpleTmgmtInterface $simpleTmgmt */
$simpleTmgmt = \Drupal::service('simple_tmgmt');
$simpleTmgmt->setTranslationFormUrl($form_state);
$form['actions']['submit']['#submit'][] = '_simple_tmgmt_delete_job_form_submit_handler';
}
/**
* Custom submit handler to redirect to the entity translation form once the Job and Job item are deleted.
*/
function _simple_tmgmt_delete_job_form_submit_handler(array &$form, FormStateInterface $form_state) {
$entityTypeManager = \Drupal::entityTypeManager();
if (
$form_state->get('job_item_to_delete') instanceof JobItemInterface &&
$form_state->get('job_to_delete') instanceof JobInterface &&
$form_state->get('translation_form_url') instanceof Url
) {
try {
$tmgmtJobItemStorage = $entityTypeManager->getStorage('tmgmt_job_item');
$tmgmtJobStorage = $entityTypeManager->getStorage('tmgmt_job');
$tmgmtJobItemStorage->delete([$form_state->get('job_item_to_delete')]);
$tmgmtJobStorage->delete([$form_state->get('job_to_delete')]);
$translationFormUrl = $form_state->get('translation_form_url');
$form_state->setRedirectUrl($translationFormUrl);
}
catch (\Exception $exception) {
\Drupal::messenger()->addError($exception->getMessage());
}
}
}
/**
* Implements hook_preprocess_HOOK().
*
* Removes the TMGMT status message that relates to semantic integrity
* as it could be misleading for some roles.
*/
// Function simple_tmgmt_preprocess_status_messages(&$variables) {
// if (array_key_exists('status', $variables['message_list'])) {
// $statusMessages = $variables['message_list']['status'];
// $alteredMessages = array_filter($statusMessages, function ($message, $key) {
// $messagesToRemove = 'Please check also the HTML code of the element in the review process.';
// // Cast Markup instances.
// $message = (string) $message;
// return strpos($message, $messagesToRemove) === FALSE;
// }, ARRAY_FILTER_USE_BOTH);
// if (empty($alteredMessages)) {
// // Prevent to display empty status message box.
// unset($variables['message_list']['status']);
// }
// else {
// $variables['message_list']['status'] = $alteredMessages;
// }
// }
// }.
/**
* Implements hook_theme_registry_alter().
*/
// Function simple_tmgmt_theme_registry_alter(&$theme_registry) {
// // Override tmgmt_data_items_form to optionally remove the validation tick boxes.
// // They might not be wanted in some use cases (already translated content that will be reviewed in one step).
// // @todo make it configurable first.
// $theme_registry['tmgmt_data_items_form']['path'] = drupal_get_path('module', 'simple_tmgmt') . '/templates';
// }.
