simple_tmgmt-1.0.x-dev/src/SimpleTmgmt.php
src/SimpleTmgmt.php
<?php
namespace Drupal\simple_tmgmt;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Link;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\tmgmt\Entity\Job;
use Drupal\tmgmt\Entity\JobItem;
use Drupal\tmgmt\Entity\Translator;
use Drupal\tmgmt\JobInterface;
use Drupal\tmgmt\JobItemInterface;
use Drupal\Core\Mail\MailManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\tmgmt\TranslatorInterface;
/**
* Class SimpleTmgmt.
*/
class SimpleTmgmt implements SimpleTmgmtInterface {
use StringTranslationTrait;
// @todo make use of a workflow or configure Machine translation providers
// For now, just hold a list of machine based translators.
const MACHINE_TRANSLATORS = ['simple_tmgmt_deepl', 'tmgmt_deepl', 'google', 'microsoft'];
/**
* Drupal\Core\Mail\MailManagerInterface definition.
*
* @var \Drupal\Core\Mail\MailManagerInterface
*/
protected $pluginManagerMail;
/**
* Drupal\Core\Session\AccountProxyInterface definition.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* Drupal\Core\Language\LanguageManagerInterface definition.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Drupal\Core\Render\RendererInterface definition.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Drupal\Core\Config\ConfigFactoryInterface definition.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* SimpleTmgmt constructor.
*
* @param \Drupal\Core\Mail\MailManagerInterface $plugin_manager_mail
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* @param \Drupal\Core\Render\RendererInterface $renderer
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
*/
public function __construct(
MailManagerInterface $plugin_manager_mail,
AccountProxyInterface $current_user,
LanguageManagerInterface $language_manager,
RendererInterface $renderer,
ConfigFactoryInterface $config_factory
) {
$this->pluginManagerMail = $plugin_manager_mail;
$this->currentUser = $current_user;
$this->languageManager = $language_manager;
$this->renderer = $renderer;
$this->configFactory = $config_factory;
}
/**
* Unset providers that could be used as a dependency of other providers.
*/
public static function translatorsToDisable() {
// @todo to be handled/configured by each provider.
// return ['deepl_pro', 'file_exchange'];
return [];
}
/**
* {@inheritdoc}.
*/
public function getActiveTranslators() {
// @todo could be refined with unsupported / custom filter.
$result = [];
$translators = Translator::loadMultiple();
foreach ($translators as $translator) {
if (
$translator->checkAvailable()->getSuccess() &&
!in_array($translator->id(), SimpleTmgmt::translatorsToDisable())
) {
$result[] = $translator;
}
}
return $result;
}
/**
* {@inheritdoc}.
*/
public function isTranslationSupported(TranslatorInterface $translator, LanguageInterface $sourceLanguage, $targetLangCode) {
// If the provider doesn't have remote language support at all, presume that
// it is not a remote translator, has there is not method related to
// machine based translations.
if (empty($translator->getSupportedRemoteLanguages())) {
TRUE;
}
return array_key_exists($targetLangCode, $translator->getSupportedTargetLanguages($sourceLanguage->getId()));
}
/**
* {@inheritdoc}.
*/
public function isMachineTranslator($translator) {
return in_array($translator, static::MACHINE_TRANSLATORS);
}
/**
* {@inheritdoc}.
*/
public function isManualTranslator($translator) {
return !in_array($translator, static::MACHINE_TRANSLATORS);
}
/**
* {@inheritDoc}.
*/
public function sendJobCreateMail(array $content, $to) {
if ($content['has_delivery_date']) {
$subject = $this->t('@node_title | @source_language to @target_language | @words words | @delivery_date', [
'@node_title' => $content['node_title'],
'@source_language' => $content['source_language'],
'@target_language' => $content['target_language'],
'@words' => $content['words'],
'@delivery_date' => $content['delivery_date'],
'@job_link' => $content['job_link'],
]);
}
else {
$subject = $this->t('@node_title | @source_language to @target_language | @words words', [
'@node_title' => $content['node_title'],
'@source_language' => $content['source_language'],
'@target_language' => $content['target_language'],
'@words' => $content['words'],
'@job_link' => $content['job_link'],
]);
}
$attachments = [];
$attachments[] = (object) [
'filename' => $content['file_name'],
'uri' => $content['file_uri'],
// @todo get filemime as it can vary.
'filemime' => 'application/xliff+xml',
];
return $this->sendMail($to, $subject, 'mail__job_create', $content, $attachments);
}
/**
* {@inheritDoc}.
*/
public function sendJobErrorMail(array $content, $to) {
$subject = $this->t('Translation request @error_code error for @translator', [
'@error_code' => $content['error_code'],
'@translator' => $content['translator'],
]);
return $this->sendMail($to, $subject, 'mail__job_error', $content);
}
/**
* Sends html mail with optional attachments.
*
* @param string $to
* @param string $subject
* @param string $theme
* @param array $content
* @param array $attachments
*
* @return array
*/
private function sendMail($to, $subject, $theme, array $content, array $attachments = []) {
$body = [
'#theme' => $theme,
'#content' => $content,
];
$fromMail = $this->currentUser->getEmail();
$langCode = $this->languageManager->getCurrentLanguage()->getId();
$sender = '"' . $this->currentUser->getDisplayName() . '" <' . $fromMail . '>';
$params = [
'headers' => [
'From' => $sender,
],
'subject' => $subject,
'from' => $sender,
'body' => $this->renderer->render($body),
'options' => [],
'files' => $attachments,
];
return $this->pluginManagerMail->mail(
'simple_tmgmt',
'job_create',
$to,
$langCode,
$params,
$fromMail,
TRUE
);
}
/**
* Creates a Drupal State key for an entity translation.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* @param $langCode
*
* @return string
*/
public static function getStateKey(EntityInterface $entity, $langCode) {
$stateKeyElements = [
'simple_tmgmt',
$entity->getEntityTypeId(),
$entity->id(),
$langCode,
];
return implode('_', $stateKeyElements);
}
/**
* {@inheritdoc}
*/
public function getJobCreateUrl(ContentEntityInterface $entity, $langcode, $default_provider) {
$result = Url::fromRoute('simple_tmgmt.job_create', [
'entity_type_id' => $entity->getEntityTypeId(),
'entity_id' => $entity->id(),
'langcode' => $langcode,
'default_provider' => $default_provider,
]);
return $result;
}
/**
* {@inheritdoc}
*/
public function setTranslationFormUrl(FormStateInterface $form_state) {
/** @var \Drupal\tmgmt\JobInterface $job */
$job = \Drupal::routeMatch()->getParameter('tmgmt_job');
if ($job instanceof JobInterface) {
$jobItems = $job->getItems();
if (!empty($jobItems)) {
reset($jobItems);
/** @var \Drupal\tmgmt\JobItemInterface $jobItem */
$jobItemKey = key($jobItems);
$jobItem = $jobItems[$jobItemKey];
if (!empty($jobItem->getSourceUrl())) {
$options = $jobItem->getSourceUrl()->getOptions();
if (array_key_exists('entity_type', $options) && array_key_exists('entity', $options)) {
// @var \Drupal\Core\Entity\ContentEntityInterface $entity
$entity = $options['entity'];
$translationFormUrl = Url::fromRoute('entity.' . $options['entity_type'] . '.content_translation_overview', [
$options['entity_type'] => $entity->id(),
]);
if ($translationFormUrl->isRouted()) {
$form_state->set('job_to_delete', $job);
$form_state->set('job_item_to_delete', $jobItem);
$form_state->set('translation_form_url', $translationFormUrl);
}
}
}
}
}
}
/**
* {@inheritdoc}.
*/
public function getHelpLinkMarkup() {
// @todo make the help link display configurable.
$config = $this->configFactory->get('simple_tmgmt.settings');
$helpLinkLabel = $config->get('help_link_label');
$helpUrl = $config->get('help_link_url');
if ($helpUrl) {
$helpLinkUrl = Url::fromUri($helpUrl);
}
else {
$helpLinkUrl = Url::fromRoute('help.page', ['name' => 'simple_tmgmt']);
}
$helpLink = Link::fromTextAndUrl($helpLinkLabel, $helpLinkUrl)
->toRenderable();
return $this->renderer->render($helpLink);
}
/**
* {@inheritdoc}.
*/
public function getJobFormDisabledActions() {
return $this->getFormDisabledActions('disabled_actions_job_form');
}
/**
* {@inheritdoc}.
*/
public function getJobItemFormDisabledActions() {
return $this->getFormDisabledActions('disabled_actions_job_item_form');
}
/**
* Returns the disabled form actions from the configuration.
*
* @param $config_key
*
* @return array
*/
private function getFormDisabledActions($config_key) {
$result = [];
$config = $this->configFactory->get('simple_tmgmt.settings');
$actions = $config->get($config_key);
if (is_array($actions)) {
foreach ($actions as $action => $value) {
if ((string) $value === "0") {
$result[] = $action;
}
}
}
return $result;
}
/**
* {@inheritdoc}.
*/
public function transformTranslationLink($link) {
$result = $link;
$dom = new \DOMDocument();
$dom->loadHTML($link);
try {
$aElement = $dom->getElementsByTagName('a')->item(0);
if ($aElement !== NULL) {
$jobItemHref = $aElement->getAttribute('href');
if (strpos($jobItemHref, '/admin/tmgmt/items/') !== FALSE) {
// Some setup can have an absolute href here, make it relative.
$jobItemHref = strstr($jobItemHref, '/admin/tmgmt/items/');
$jobItemUrl = Url::fromUri('internal:' . $jobItemHref);
if ($jobItemUrl->isRouted() && $jobItemUrl->getRouteName() === 'entity.tmgmt_job_item.canonical') {
$jobItemId = $jobItemUrl->getRouteParameters()['tmgmt_job_item'];
$jobItem = JobItem::load($jobItemId);
if ($jobItem instanceof JobItemInterface) {
$jobId = $jobItem->getJobId();
$jobUrl = Url::fromRoute('entity.tmgmt_job.canonical', [
'tmgmt_job' => $jobId,
]);
$job = Job::load($jobId);
if ($job instanceof JobInterface) {
$jobItemState = $jobItem->get('state')->getValue();
$jobState = $job->get('state')->getValue();
if (is_array($jobItemState) && is_array($jobState)) {
$jobItemStateValue = (int) $jobItemState[0]['value'];
$jobStateValue = (int) $jobState[0]['value'];
switch ($jobItemStateValue) {
// Replaces the link from 'items' to 'jobs' if the translation
// Job item / Job are pending (Job item / Job state = 'active').
// It means in this case that we want to upload the xliff file
// and not do the translation manually.
case JobItemInterface::STATE_ACTIVE:
if ($jobStateValue === JobInterface::STATE_ACTIVE) {
$options = $jobItemUrl->getOptions();
$jobHref = $jobUrl->toString();
// Replace the destination as well so we can redirect to the
// original items afterwards for translation review.
if (
array_key_exists('query', $options) &&
array_key_exists('destination', $options['query'])
) {
$jobItemHrefParts = explode('?', $jobItemHref);
if (is_string($jobItemHrefParts[0])) {
$options['query']['destination'] = $jobItemHrefParts[0];
$jobUrl->setOptions($options);
$jobHref = $jobUrl->toString();
}
}
// Replace the href.
$aElement->setAttribute('href', $jobHref);
// $aElement->setAttribute('target', '_blank');
// Replace the alt / title from the image.
$config = $this->configFactory->get('simple_tmgmt.settings');
$pendingTranslationLabel = $config->get('pending_manual_translation_label');
$xpath = new \DOMXPath($dom);
$imgElement = $xpath->query('//a/img')->item(0);
if ($imgElement instanceof \DOMElement) {
$imgElement->setAttribute('alt', $pendingTranslationLabel);
$imgElement->setAttribute('title', $pendingTranslationLabel);
}
// @todo other markup elements can be removed from the dom.
// as we presume here a simple link.
$result = $dom->saveHTML($aElement);
}
break;
// Just open in a new tab in this case.
case JobItemInterface::STATE_REVIEW:
// $aElement->setAttribute('target', '_blank');.
// @todo other markup elements can be removed from the dom.
// as we presume here a simple link.
$result = $dom->saveHTML($aElement);
break;
// If the Job Item is inactive and the translator plugin unknown
// it leads to an exception by default.
// Remove the link then as this is delegated to the
// Change the link to continue the Job.
case JobItemInterface::STATE_INACTIVE:
$xpath = new \DOMXPath($dom);
$node = $xpath->query('//a/text()')->item(0);
if ($node) {
// The provider cannot be set back as it is undefined
// in the inactive state.
// So redirecting in all cases to the node translation form.
if (!empty($jobItemUrl)) {
$jobUrl->setOptions($jobItemUrl->getOptions());
}
$link = Link::fromTextAndUrl($this->t('Continue'), $jobUrl)->toRenderable();
$result = $this->renderer->render($link);
}
break;
}
}
}
}
}
}
}
}
catch (\Exception $exception) {
\Drupal::logger('simple_tmgmt')->error($exception->getMessage());
}
return $result;
}
}
