Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for translation of News-Extension records with SUMM AI model #1

Draft
wants to merge 1 commit into
base: 12.4
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions Classes/Backend/EventListener/NewsRecordEventListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace DMK\MkContentAi\Backend\EventListener;

use DMK\MkContentAi\ContextMenu\ContentAiTranslationProvider;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Recordlist\Event\ModifyRecordListRecordActionsEvent;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add typo3/cms-extbase and typo3/cms-recordlist dependencies to composer.json.


class NewsRecordEventListener
{
protected ContentAiTranslationProvider $contentAiTranslationProvider;
private Typo3Version $typo3Version;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be protected as well


public function __construct(Typo3Version $typo3Version)
{
$this->typo3Version = $typo3Version;

if (11 === $this->typo3Version->getMajorVersion()) {
$this->contentAiTranslationProvider = GeneralUtility::makeInstance(ContentAiTranslationProvider::class, '', '');
}

if (12 === $this->typo3Version->getMajorVersion()) {
$this->contentAiTranslationProvider = GeneralUtility::makeInstance(ContentAiTranslationProvider::class);
}
}

public function modifyRecordListActions(ModifyRecordListRecordActionsEvent $event): void
{
$currentTable = $event->getTable();
$identifier = $event->getRecord()['uid'];
if ($currentTable === 'tx_news_domain_model_news' && !$event->hasAction('translateContentPlain')) {
$itemsConfiguration = $this->contentAiTranslationProvider->getItemsConfiguration();
if(isset($itemsConfiguration['translateContentPlain'])) {
$this->contentAiTranslationProvider->setContext($currentTable, $identifier);
$uriGenerated = $this->contentAiTranslationProvider->generateUrl('translateContentPlain');
$labelActionName = LocalizationUtility::translate($itemsConfiguration['translateContentPlain']['label']);
$translateContentPlainAction = $this->buildTranslateContentPlainAction($uriGenerated, $labelActionName);
$event->setAction($translateContentPlainAction, 'translateContentPlain', 'secondary');
}
}
}

private function buildTranslateContentPlainAction(UriInterface $uriGenerated, ?string $labelActionName): string
{
switch ($this->typo3Version->getMajorVersion()) {
case 11:
$translateContentPlainAction = '
<a href='.$uriGenerated.' class="btn btn-default" title="'.$labelActionName.'"><span class="t3js-icon icon icon-size-small icon-state-default">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<a href='.$uriGenerated.' class="btn btn-default" title="'.$labelActionName.'"><span class="t3js-icon icon icon-size-small icon-state-default">
<a href="'.$uriGenerated.'" class="btn btn-default" title="'.$labelActionName.'"><span class="t3js-icon icon icon-size-small icon-state-default">

<span class="icon-markup">
<svg class="icon-color"><use xlink:href="/typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-translate" /></svg>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we generate this markup with IconFactory as well (like below for v12)?

</span>
</span></a>';

return $translateContentPlainAction;

case 12:
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dependency injection (via constructor), if possible.

$translateContentPlainAction = '
<a href='.$uriGenerated.' class="dropdown-item dropdown-item-spaced" title="'.$labelActionName.'"><span class="t3js-icon icon icon-size-small icon-state-default icon-actions-translate">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<a href='.$uriGenerated.' class="dropdown-item dropdown-item-spaced" title="'.$labelActionName.'"><span class="t3js-icon icon icon-size-small icon-state-default icon-actions-translate">
<a href="'.$uriGenerated.'" class="dropdown-item dropdown-item-spaced" title="'.$labelActionName.'"><span class="t3js-icon icon icon-size-small icon-state-default icon-actions-translate">

<span class="icon-markup">
'.$iconFactory->getIcon('actions-translate', Icon::SIZE_SMALL)->getMarkup().'
</span>
</span></a>';

return $translateContentPlainAction;

default:
return '';
}
}
}
66 changes: 66 additions & 0 deletions Classes/Backend/Hooks/NewsContentHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

/*
* Copyright notice
*
* (c) DMK E-BUSINESS GmbH <[email protected]>
* All rights reserved
*
* This file is part of TYPO3 CMS-based extension "mkcontentai" by DMK E-BUSINESS GmbH.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

namespace DMK\MkContentAi\Backend\Hooks;

use DMK\MkContentAi\Domain\Model\News;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;

class NewsContentHandler
{
public function createNewsRecord(News $record, string $title, string $teaser, string $bodyText, string $targetLanguageType): void
{
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$currentTimestamp = time() + 1;

$translationInfo = LocalizationUtility::translate(
'labelNewsTranslation' . ucwords($targetLanguageType) .'Source',
'mkcontentai'
);
$bodyTextWithLink = sprintf(
$translationInfo . '</br>' . $bodyText,
GeneralUtility::getIndpEnv('TYPO3_SITE_URL'),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we access this from the global request object ($GLOBALS['TYPO3_REQUEST']) instead? I'd like to avoid GeneralUtility::getIndpEnv() as most of the information is already available through the request object.

$record->getPathSegment(),
$record->getTitle(),
date('d.m.Y', $record->getCrDate())
);

$newsRecordData = [
'pid' => $record->getPid(),
'title' => '(Transformed into '.$targetLanguageType.' language) '. strip_tags($title),
'teaser' => strip_tags($teaser),
'bodytext' => $bodyTextWithLink,
'tx_mkcontentai_original_news_uid' => $record->getUid(),
'datetime' => $currentTimestamp,
'crdate' => $currentTimestamp,
'tstamp' => $currentTimestamp,
'path_segment' => $targetLanguageType . '/' . $record->getPathSegment(),
];

$dataMap = [
'tx_news_domain_model_news' => [
'NEW_1' => $newsRecordData,
],
];

$dataHandler->start($dataMap, []);
$dataHandler->process_datamap();

}
}
237 changes: 237 additions & 0 deletions Classes/ContextMenu/ContentAiTranslationProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
<?php

/*
* Copyright notice
*
* (c) DMK E-BUSINESS GmbH <[email protected]>
* All rights reserved
*
* This file is part of TYPO3 CMS-based extension "mkcontentai" by DMK E-BUSINESS GmbH.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

namespace DMK\MkContentAi\ContextMenu;

use DMK\MkContentAi\Domain\Model\TtContent;
use DMK\MkContentAi\Domain\Repository\TtContentRepository;
use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Backend\ContextMenu\ItemProviders\AbstractProvider;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class ContentAiTranslationProvider extends AbstractProvider
{
/**
* @var array<string, array{
* type: string,
* label: string,
* iconIdentifier: string,
* callbackAction: string
* }>
*/
protected $itemsConfiguration = [
'translateContentEasy' => [
'type' => 'item',
'label' => 'LLL:EXT:mkcontentai/Resources/Private/Language/locallang_contentai.xlf:labelTextTranslateContentEasy',
'iconIdentifier' => 'actions-edit-copy',
'callbackAction' => 'translateContentEasy',
],
'translateContentPlain' => [
'type' => 'item',
'label' => 'LLL:EXT:mkcontentai/Resources/Private/Language/locallang_contentai.xlf:labelTextTranslateContentPlain',
'iconIdentifier' => 'actions-edit-copy',
'callbackAction' => 'translateContentPlain',
],
];

public function setContext(string $table, string $identifier, string $context = ''): void
{
$this->table = $table;
$this->identifier = $identifier;
$this->context = $context;
}

public function canHandle(): bool
{
return 'tt_content' === $this->table;
}

public function getPriority(): int
{
return 55;
}

/**
* @param array<string, array{
* type: string,
* label: string,
* iconIdentifier: string,
* additionalAttributes: array<string,string>,
* callbackAction: string
* }> $items
*
* @return array<string, array{
* type: string,
* label: string,
* iconIdentifier: string,
* additionalAttributes: array<string,string>,
* callbackAction: string
* }>
Comment on lines +77 to +83
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @return array<string, array{
* type: string,
* label: string,
* iconIdentifier: string,
* additionalAttributes: array<string,string>,
* callbackAction: string
* }>
* @return array<string, array{
* type: string,
* label: string,
* iconIdentifier: string,
* additionalAttributes: array<string,string>,
* callbackAction: string
* }>

*/
public function addItems(array $items): array
{
$this->initDisabledItems();
$localItems = $this->prepareItems($this->itemsConfiguration);

return $items + $localItems;
}

public function getItemsConfiguration(): array
{
return $this->itemsConfiguration;
}

/**
* This method is called for each item this provider adds and checks if given item can be added.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function canRender(string $itemName, string $type): bool
{
$canRender = false;
$availableActions = ['translateContentEasy', 'translateContentPlain'];

if (in_array($itemName, $availableActions)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (in_array($itemName, $availableActions)) {
if (in_array($itemName, $availableActions, true)) {

$canRender = $this->isPageContent() && $this->isValidTypeOfRecord((int) $this->identifier);
}

return $canRender;
}

public function isPageContent(): bool
{
return 'tt_content' === $this->table;
}

public function generateUrl(string $itemName): UriInterface
{
$typo3Version = GeneralUtility::makeInstance(Typo3Version::class);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for GeneralUtility::makeInstance() here, because this class can be considered final:

Suggested change
$typo3Version = GeneralUtility::makeInstance(Typo3Version::class);
$typo3Version = new Typo3Version();

$majorVersion = $typo3Version->getMajorVersion();
$parameters = (11 === $majorVersion) ? $this->getParametersForVersion11($itemName) : $this->getParametersForVersion12();
$pathInfo = $this->getPathInfo($itemName, $majorVersion);

$this->updateParametersForItemName($parameters, $itemName, $majorVersion);

/**
* @var UriBuilder $uriBuilder
*/
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$extendedUrl = $uriBuilder->buildUriFromRoutePath(
$pathInfo,
$parameters
);

return $extendedUrl;
}

/**
* @return array<string>
*
* @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
*/
protected function getAdditionalAttributes(string $itemName): array
{
$typo3Version = GeneralUtility::makeInstance(Typo3Version::class);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use new Typo3Version() and move to constructor to avoid instantiating this class multiple times (already done in line 122).


$extendUrl = $this->generateUrl($itemName);

switch ($typo3Version->getMajorVersion()) {
case 12:
return [
'data-callback-module' => '@t3docs/mkcontentai/context-menu-actions',
'data-navigate-uri' => $extendUrl->__toString(),
];
case 11:
return [
'data-callback-module' => 'TYPO3/CMS/Mkcontentai/ContextMenu',
'data-navigate-uri' => $extendUrl->__toString(),
];
default:
throw new \RuntimeException('TYPO3 version not supported');
}
}

/**
* @param array<string, mixed> &$parameters
*/
private function updateParametersForItemName(array &$parameters, string $itemName, int $version): void
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this method is only relevant for TYPO3 v11. Can we move its contents to getParametersForVersion11()?

{
$actionMapping = [
'translateContentEasy' => 'translateContentEasy',
'translateContentPlain' => 'translateContentPlain',
];

if (11 === $version) {
$parameters['tx_mkcontentai_system_mkcontentaicontentai']['action'] = $actionMapping[$itemName] ?? '';
}
}

/**
* @return array<string, mixed>
*/
private function getParametersForVersion11(string $itemName): array
{
$arrayWithParameters = 'translateContentEasy' === $itemName || 'translateContentPlain' === $itemName ?
[
'tx_mkcontentai_system_mkcontentaicontentai' => [
'controller' => 'AiTranslation',
'uid' => $this->identifier,
'table' => $this->table,
],
] :
[];

return $arrayWithParameters;
}

/**
* @return array<string, mixed>
*/
private function getParametersForVersion12(): array
{
return ['uid' => $this->identifier, 'table' => $this->table];
}

private function getPathInfo(string $itemName, int $version): string
{
$pathInfoMapping = [
'translateContentEasy' => [
12 => '/module/mkcontentai/AiTranslation/translateContentEasy',
11 => '/module/system/MkcontentaiContentai',
],
'translateContentPlain' => [
12 => '/module/mkcontentai/AiTranslation/translateContentPlain',
11 => '/module/system/MkcontentaiContentai',
],
];

return $pathInfoMapping[$itemName][$version] ?? '';
}

private function isValidTypeOfRecord(int $uid): bool
{
$ttContentRepository = GeneralUtility::makeInstance(TtContentRepository::class);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dependency injection, if possible. If not, use a class property and assign its value in constructor like you did here.


/** @var TtContent|null $record */
$record = $ttContentRepository->findByUid($uid);
(null === $record) ? $recordType = '' : $recordType = $record->getCtype();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(null === $record) ? $recordType = '' : $recordType = $record->getCtype();
$recordType = $record === null ? '' : $record->getCtype();


$availableAction = ['text', 'textpic', 'textmedia'];

return in_array($recordType, $availableAction);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return in_array($recordType, $availableAction);
return in_array($recordType, $availableAction, true);

}
}
Loading