Skip to content

Commit

Permalink
[TASK] Change Dropdown rendering for translations
Browse files Browse the repository at this point in the history
Getting rid of x-classing is the goal and with v12 an event is provided
manipulating the header of the RecordListController.

In favour of using this event instead of x-classing the original class, an event
listener is added generating the dropdown and adding top-most of the recordlist.

This has effects on positioning of the translation dropdown, which now is
rendered before the core dropdown instead of next to it.
  • Loading branch information
calien666 committed Dec 18, 2024
1 parent cb232b8 commit 23efd87
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 472 deletions.
130 changes: 0 additions & 130 deletions Build/phpstan/Core12/phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,50 +1,5 @@
parameters:
ignoreErrors:
-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryEntryRepository\\:\\:findEntriesByGlossary\\(\\) should return array\\<string, mixed\\> but returns array\\<int, array\\<string, mixed\\>\\>\\.$#"
count: 1
path: ../../../Classes/Domain/Repository/GlossaryEntryRepository.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryEntryRepository\\:\\:findEntryByUid\\(\\) should return array\\<non\\-empty\\-string, mixed\\> but returns array\\<string, mixed\\>\\.$#"
count: 1
path: ../../../Classes/Domain/Repository/GlossaryEntryRepository.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryRepository\\:\\:getGlossariesInRootByCurrentPage\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Classes/Domain/Repository/GlossaryRepository.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryRepository\\:\\:getGlossary\\(\\) should return array\\{uid\\: int, glossary_name\\: string, glossary_id\\: string, glossary_lastsync\\: int, glossary_ready\\: int\\}\\|null but returns non\\-empty\\-array\\<string, mixed\\>\\|null\\.$#"
count: 1
path: ../../../Classes/Domain/Repository/GlossaryRepository.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryRepository\\:\\:getGlossaryBySourceAndTargetForSync\\(\\) should return array\\{uid\\: int, glossary_name\\: string, glossary_id\\: string, glossary_lastsync\\: int, glossary_ready\\: int\\} but returns array\\{glossary_name\\: non\\-falsy\\-string, glossary_id\\: '', glossary_lastsync\\: 0, glossary_ready\\: 0, source_lang\\: string, target_lang\\: string, uid\\: string\\}\\.$#"
count: 1
path: ../../../Classes/Domain/Repository/GlossaryRepository.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryRepository\\:\\:getLocalizedEntries\\(\\) should return array\\<int, array\\{uid\\: int, term\\: string, l10n_parent\\: int\\}\\> but returns array\\<int\\|string, array\\<string, mixed\\>\\>\\.$#"
count: 1
path: ../../../Classes/Domain/Repository/GlossaryRepository.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryRepository\\:\\:getOriginalEntries\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Classes/Domain/Repository/GlossaryRepository.php

-
message: "#^Parameter \\#3 \\$page of method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryRepository\\:\\:getGlossaryBySourceAndTargetForSync\\(\\) expects array\\{uid\\: int, title\\: string\\}, array\\|null given\\.$#"
count: 1
path: ../../../Classes/Domain/Repository/GlossaryRepository.php

-
message: "#^Cannot call method getRequestUri\\(\\) on TYPO3\\\\CMS\\\\Core\\\\Http\\\\NormalizedParams\\|null\\.$#"
count: 1
path: ../../../Classes/Event/Listener/GlossarySyncButtonProvider.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Form\\\\Item\\\\SiteConfigSupportedLanguageItemsProcFunc\\:\\:getSupportedLanguageForField\\(\\) has parameter \\$configuration with no value type specified in iterable type array\\.$#"
count: 1
Expand All @@ -60,26 +15,11 @@ parameters:
count: 1
path: ../../../Classes/Hooks/ButtonBarHook.php

-
message: "#^Parameter \\#1 \\$uid of method WebVision\\\\Deepltranslate\\\\Core\\\\Domain\\\\Repository\\\\GlossaryEntryRepository\\:\\:findEntryByUid\\(\\) expects int, int\\|string given\\.$#"
count: 1
path: ../../../Classes/Hooks/Glossary/UpdatedGlossaryEntryTermHook.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Override\\\\Core12\\\\DatabaseRecordList\\:\\:makeLocalizationPanel\\(\\) has parameter \\$translations with no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Classes/Override/Core12/DatabaseRecordList.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Override\\\\Core12\\\\DeeplRecordListController\\:\\:languageSelector\\(\\) should return string but returns string\\|null\\.$#"
count: 1
path: ../../../Classes/Override/Core12/DeeplRecordListController.php

-
message: "#^Parameter \\#1 \\$string of function htmlspecialchars expects string, string\\|null given\\.$#"
count: 1
path: ../../../Classes/Override/Core12/DeeplRecordListController.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Override\\\\LocalizationController\\:\\:process\\(\\) has parameter \\$params with no value type specified in iterable type array\\.$#"
count: 1
Expand All @@ -95,11 +35,6 @@ parameters:
count: 3
path: ../../../Classes/Override/LocalizationController.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Service\\\\DeeplGlossaryService\\:\\:getPossibleGlossaryLanguageConfig\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Classes/Service/DeeplGlossaryService.php

-
message: "#^Property WebVision\\\\Deepltranslate\\\\Core\\\\Service\\\\LanguageService\\:\\:\\$possibleLangMatches type has no value type specified in iterable type array\\.$#"
count: 1
Expand Down Expand Up @@ -140,21 +75,6 @@ parameters:
count: 1
path: ../../../Classes/Utility/DeeplBackendUtility.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Utility\\\\DeeplBackendUtility\\:\\:buildTranslateDropdown\\(\\) has parameter \\$id with no type specified\\.$#"
count: 1
path: ../../../Classes/Utility/DeeplBackendUtility.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Utility\\\\DeeplBackendUtility\\:\\:buildTranslateDropdown\\(\\) has parameter \\$requestUri with no type specified\\.$#"
count: 1
path: ../../../Classes/Utility/DeeplBackendUtility.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Utility\\\\DeeplBackendUtility\\:\\:buildTranslateDropdown\\(\\) has parameter \\$siteLanguages with no type specified\\.$#"
count: 1
path: ../../../Classes/Utility/DeeplBackendUtility.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Utility\\\\HtmlUtility\\:\\:stripSpecificTags\\(\\) should return string but returns string\\|null\\.$#"
count: 1
Expand Down Expand Up @@ -235,56 +155,6 @@ parameters:
count: 1
path: ../../../Tests/Functional/Hooks/TranslateHookTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:buildDefaultLanguageConfiguration\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:buildErrorHandlingConfiguration\\(\\) has parameter \\$codes with no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:buildErrorHandlingConfiguration\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:buildLanguageConfiguration\\(\\) has parameter \\$fallbackIdentifiers with no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:buildLanguageConfiguration\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:failIfArrayIsNotEmpty\\(\\) has parameter \\$items with no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:mergeSiteConfiguration\\(\\) has parameter \\$overrides with no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:writeSiteConfiguration\\(\\) has parameter \\$errorHandling with no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:writeSiteConfiguration\\(\\) has parameter \\$languages with no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\GlossaryRegressionTest\\:\\:writeSiteConfiguration\\(\\) has parameter \\$site with no value type specified in iterable type array\\.$#"
count: 1
path: ../../../Tests/Functional/Regression/GlossaryRegressionTest.php

-
message: "#^Method WebVision\\\\Deepltranslate\\\\Core\\\\Tests\\\\Functional\\\\Regression\\\\LocalizationInlineRegressionTest\\:\\:buildDefaultLanguageConfiguration\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
Expand Down
45 changes: 45 additions & 0 deletions Classes/Event/Listener/RenderLocalizationSelect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace WebVision\Deepltranslate\Core\Event\Listener;

use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Backend\Controller\Event\RenderAdditionalContentToRecordListEvent;
use TYPO3\CMS\Core\Site\Entity\Site;
use WebVision\Deepltranslate\Core\Event\RenderLocalizationSelectAllowed;
use WebVision\Deepltranslate\Core\Form\TranslationDropdownGenerator;

final class RenderLocalizationSelect
{
public function __construct(
private readonly TranslationDropdownGenerator $generator,
private readonly EventDispatcherInterface $eventDispatcher
) {
}

public function __invoke(RenderAdditionalContentToRecordListEvent $event): void
{
$request = $event->getRequest();
// Check, if some event listener doesn't allow rendering here.
// For use cases see Event
$renderingAllowedEvent = $this->eventDispatcher->dispatch(new RenderLocalizationSelectAllowed($request));
if ($renderingAllowedEvent->renderingAllowed === false) {
return;
}
/** @var Site $site */
$site = $request->getAttribute('site');
$siteLanguages = $site->getLanguages();
$options = $this->generator->buildTranslateDropdownOptions($siteLanguages, (int)$request->getQueryParams()['id'], $request->getUri());
if ($options !== '') {
$additionalHeader = '<div class="form-row">'
. '<div class="form-group">'
. '<select class="form-select" name="createNewLanguage" data-global-event="change" data-action-navigate="$value">'
. $options
. '</select>'
. '</div>'
. '</div>';
$event->addContentAbove($additionalHeader);
}
}
}
20 changes: 20 additions & 0 deletions Classes/Event/RenderLocalizationSelectAllowed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace WebVision\Deepltranslate\Core\Event;

use Psr\Http\Message\RequestInterface;

/**
* Event deciding if the localization dropdown should be rendered.
* Could be used avoiding rendering for special cases, e.g., glossary or access denied.
*/
final class RenderLocalizationSelectAllowed
{
public function __construct(
public readonly RequestInterface $request,
public bool $renderingAllowed = true
) {
}
}
122 changes: 122 additions & 0 deletions Classes/Form/TranslationDropdownGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

declare(strict_types=1);

namespace WebVision\Deepltranslate\Core\Form;

use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use WebVision\Deepltranslate\Core\Utility\DeeplBackendUtility;

/**
* Generates the dropdown for language selector
*
* @internal only for usage inside deepltranslate-core, no public API
*/
final class TranslationDropdownGenerator
{
public function __construct()
{
}

/**
* @param iterable<SiteLanguage> $siteLanguages
* @throws \Doctrine\DBAL\Exception
* @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
*/
public function buildTranslateDropdownOptions(
$siteLanguages,
int $id,
string|UriInterface $requestUri
): string {
$availableTranslations = [];
foreach ($siteLanguages as $siteLanguage) {
if (
$siteLanguage->getLanguageId() === 0
|| $siteLanguage->getLanguageId() === -1
) {
continue;
}
$availableTranslations[$siteLanguage->getLanguageId()] = $siteLanguage->getTitle();
}
// Then, subtract the languages which are already on the page:
$localizationParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
$languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'];
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(
GeneralUtility::makeInstance(
WorkspaceRestriction::class,
(int)$this->getBackendUser()?->workspace
)
);
$statement = $queryBuilder
->select('uid', $languageField)
->from('pages')
->where(
$queryBuilder->expr()->eq(
$localizationParentField,
$queryBuilder->createNamedParameter($id, Connection::PARAM_INT)
)
)
->executeQuery();
while ($pageTranslation = $statement->fetchAssociative()) {
unset($availableTranslations[(int)$pageTranslation[$languageField]]);
}
// If any languages are left, make selector:
if (!empty($availableTranslations)) {
$output = '';
foreach ($availableTranslations as $languageUid => $languageTitle) {
// check if language can be translated with DeepL
// otherwise continue to next
if (!DeeplBackendUtility::checkCanBeTranslated($id, $languageUid)) {
continue;
}
// Build localize command URL to DataHandler (tce_db)
// which redirects to FormEngine (record_edit)
// which, when finished editing should return back to the current page (returnUrl)
$parameters = [
'justLocalized' => 'pages:' . $id . ':' . $languageUid,
'returnUrl' => (string)$requestUri,
];
$redirectUrl = DeeplBackendUtility::buildBackendRoute('record_edit', $parameters);
$params = [];
$params['redirect'] = $redirectUrl;
$params['cmd']['pages'][$id]['localize'] = $languageUid;
$params['cmd']['localization']['custom']['mode'] = 'deepl';
$targetUrl = DeeplBackendUtility::buildBackendRoute('tce_db', $params);
$output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
}
if ($output !== '') {
$output = sprintf(
'<option value="">%s</option>%s',
htmlspecialchars($this->getLocalization()->sL('LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:backend.label')),
$output
);
}

return $output;
}
return '';
}

private function getLocalization(): LanguageService
{
return GeneralUtility::makeInstance(LanguageServiceFactory::class)
->createFromUserPreferences($this->getBackendUser());
}

private function getBackendUser(): ?BackendUserAuthentication
{
return $GLOBALS['BE_USER'] ?? null;
}
}
Loading

0 comments on commit 23efd87

Please sign in to comment.