diff --git a/Classes/Backend/EventListener/CustomFileControlsEventListener.php b/Classes/Backend/EventListener/CustomFileControlsEventListener.php new file mode 100644 index 0000000..9dd79ab --- /dev/null +++ b/Classes/Backend/EventListener/CustomFileControlsEventListener.php @@ -0,0 +1,56 @@ + + * 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\EventListener; + +use TYPO3\CMS\Backend\Form\Event\CustomFileControlsEvent; +use TYPO3\CMS\Backend\Form\NodeFactory; +use TYPO3\CMS\Core\Imaging\Icon; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +final class CustomFileControlsEventListener +{ + /** + * @var NodeFactory + */ + public $nodeFactory; + + /** + * @var IconFactory + */ + public $iconFactory; + + public function __construct() + { + $this->nodeFactory = GeneralUtility::makeInstance(NodeFactory::class); + $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class); + } + + public function handleEvent(CustomFileControlsEvent $event): void + { + $item = '
'; + + $event->addControl($item); + + $pageRenderer = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Page\PageRenderer::class); + $pageRenderer->loadRequireJsModule('TYPO3/CMS/Mkcontentai/BackendPrompt'); + } +} diff --git a/Classes/Backend/Form/Element/InputTextWithAiAltTextSupportElement.php b/Classes/Backend/Form/Element/InputTextWithAiAltTextSupportElement.php new file mode 100644 index 0000000..a36d177 --- /dev/null +++ b/Classes/Backend/Form/Element/InputTextWithAiAltTextSupportElement.php @@ -0,0 +1,62 @@ + + * 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\Form\Element; + +use TYPO3\CMS\Backend\Form\Element\InputTextElement; +use TYPO3\CMS\Core\Imaging\Icon; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Information\Typo3Version; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class InputTextWithAiAltTextSupportElement extends InputTextElement +{ + /** + * @return array + */ + public function render(): array + { + $resultArray = parent::render(); + + if ('sys_file_reference' !== $this->data['tableName'] || 'alternative' !== $this->data['fieldName']) { + return $resultArray; + } + + $html = explode(LF, $resultArray['html']); + $fileUid = $this->data['databaseRow']['uid_local'][0]['uid']; + $typoThreeVersion = GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion(); + $pageLanguageUid = $this->data['databaseRow']['sys_language_uid'][0]; + + if (11 === $typoThreeVersion) { + $pageLanguageUid = $this->data['databaseRow']['sys_language_uid']; + } + + $iconFactory = GeneralUtility::makeInstance(IconFactory::class); + $item[] = '
+
'; + + array_splice($html, 3, 0, $item); + $resultArray['html'] = implode(LF, $html); + $resultArray['requireJsModules'][] = 'TYPO3/CMS/Mkcontentai/AltText'; + + return $resultArray; + } +} diff --git a/Classes/ContextMenu/ContentAiItemProvider.php b/Classes/ContextMenu/ContentAiItemProvider.php index 2207b11..97dbd22 100644 --- a/Classes/ContextMenu/ContentAiItemProvider.php +++ b/Classes/ContextMenu/ContentAiItemProvider.php @@ -56,6 +56,12 @@ class ContentAiItemProvider extends AbstractProvider 'iconIdentifier' => 'actions-rocket', 'callbackAction' => 'extend', ], + 'alt' => [ + 'type' => 'item', + 'label' => 'Alt text generate', + 'iconIdentifier' => 'actions-rocket', + 'callbackAction' => 'alt', + ], ]; public function canHandle(): bool @@ -99,6 +105,10 @@ private function generateUrl(string $itemName): Uri if ('extend' === $itemName) { $parameters['tx_mkcontentai_system_mkcontentaicontentai']['action'] = 'cropAndExtend'; } + if ('alt' === $itemName) { + $parameters['tx_mkcontentai_system_mkcontentaicontentai']['controller'] = 'AiText'; + $parameters['tx_mkcontentai_system_mkcontentaicontentai']['action'] = 'altText'; + } /** * @var UriBuilder $uriBuilder @@ -124,6 +134,7 @@ protected function canRender(string $itemName, string $type): bool switch ($itemName) { case 'upscale': case 'extend': + case 'alt': $canRender = $this->isImage(); break; } diff --git a/Classes/Controller/AiImageController.php b/Classes/Controller/AiImageController.php index 71437b2..5b1e693 100644 --- a/Classes/Controller/AiImageController.php +++ b/Classes/Controller/AiImageController.php @@ -18,7 +18,7 @@ namespace DMK\MkContentAi\Controller; use DMK\MkContentAi\Domain\Model\Image; -use DMK\MkContentAi\Http\Client\ClientInterface; +use DMK\MkContentAi\Http\Client\ImageApiInterface; use DMK\MkContentAi\Http\Client\OpenAiClient; use DMK\MkContentAi\Http\Client\StabilityAiClient; use DMK\MkContentAi\Http\Client\StableDiffusionClient; @@ -54,7 +54,7 @@ class AiImageController extends BaseController 3 => StabilityAiClient::class, ]; - public ClientInterface $client; + public ImageApiInterface $client; public function initializeAction(): void { @@ -88,6 +88,7 @@ public function initializeAction(): void } $actionMethodName = $this->request->getControllerActionName(); if (!in_array($actionMethodName, $this->client->getAllowedOperations())) { + $this->controllerContext = $this->buildControllerContext(); $this->addFlashMessage($actionMethodName.' is not allowed for current API '.get_class($this->client), '', AbstractMessage::ERROR); $this->redirect('filelist'); } @@ -95,14 +96,14 @@ public function initializeAction(): void } /** - * @return array{client?:ClientInterface, clientClass?:string, error?:string} + * @return array{client?:ImageApiInterface, clientClass?:string, error?:string} */ private function initializeClient(): array { try { $imageEngineKey = SettingsController::getImageAiEngine(); $client = GeneralUtility::makeInstance($this::GENERATOR_ENGINE[$imageEngineKey]); - if (is_a($client, ClientInterface::class)) { + if (is_a($client, ImageApiInterface::class)) { return [ 'client' => $client, 'clientClass' => get_class($client), @@ -124,11 +125,26 @@ private function initializeClient(): array */ public function filelistAction(): void { - $fileService = GeneralUtility::makeInstance(FileService::class, $this->client->getFolderName()); + $clientResponse = $this->initializeClient(); + + if (!isset($clientResponse['client'])) { + $this->addFlashMessage('Please set AI client in settings first', '', AbstractMessage::WARNING); + $this->view->assignMultiple( + [ + 'files' => [], + 'client' => null, + ] + ); + + return; + } + + $client = $clientResponse['client']; + $fileService = GeneralUtility::makeInstance(FileService::class, $client->getFolderName()); $this->view->assignMultiple( [ 'files' => $fileService->getFiles(), - 'client' => $this->client, + 'client' => $client, ] ); } @@ -211,7 +227,12 @@ public function promptAction(): void public function promptResultAction(string $text): void { try { - $images = $this->client->image($text); + $clientResponse = $this->initializeClient(); + $client = $clientResponse['client'] ?? null; + if (null === $client) { + throw new \Exception(isset($clientResponse['error']) ? $clientResponse['error'] : 'Something went wrong'); + } + $images = $client->image($text); } catch (\Exception $e) { $this->addFlashMessage($e->getMessage(), '', AbstractMessage::ERROR); $this->redirect('prompt'); diff --git a/Classes/Controller/AiTextController.php b/Classes/Controller/AiTextController.php new file mode 100644 index 0000000..dae7263 --- /dev/null +++ b/Classes/Controller/AiTextController.php @@ -0,0 +1,89 @@ + + * 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\Controller; + +use DMK\MkContentAi\Service\AiAltTextService; +use DMK\MkContentAi\Service\SiteLanguageService; +use TYPO3\CMS\Backend\Routing\UriBuilder; +use TYPO3\CMS\Core\Messaging\AbstractMessage; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Domain\Model\File; + +/** + * This file is part of the "DMK Content AI" Extension for TYPO3 CMS. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * (c) 2023 + */ + +/** + * ImageController. + */ +class AiTextController extends BaseController +{ + public AiAltTextService $aiAltTextService; + public SiteLanguageService $siteLanguageService; + + public function __construct(AiAltTextService $aiAltTextService, SiteLanguageService $siteLanguageService) + { + $this->aiAltTextService = $aiAltTextService; + $this->siteLanguageService = $siteLanguageService; + } + + public function altTextAction(File $file): void + { + $this->view->assignMultiple( + [ + 'file' => $file, + 'altText' => $this->getAltTextForFile($file), + 'languageName' => $this->siteLanguageService->getFullLanguageName(), + ] + ); + } + + public function altTextSaveAction(File $file): void + { + $altText = $this->getAltTextForFile($file); + + $metadata = $file->getOriginalResource()->getMetaData(); + $metadata->offsetSet('alternative', $altText); + $metadata->save(); + + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); + $metaDataUid = $file->getOriginalResource()->getMetaData()->get()['uid']; + $editUrl = $uriBuilder->buildUriFromRoute('record_edit', [ + 'edit[sys_file_metadata]['.$metaDataUid.']' => 'edit', + ]); + $this->redirectToUri($editUrl); + } + + private function getAltTextForFile(File $file): string + { + $altTextFromFile = ''; + + try { + $altTextFromFile = $this->aiAltTextService->getAltText($file); + } catch (\Exception $e) { + $this->addFlashMessage($e->getMessage(), '', AbstractMessage::ERROR); + } + + return $altTextFromFile; + } +} diff --git a/Classes/Controller/AjaxController.php b/Classes/Controller/AjaxController.php index 9e93d5a..fc633f4 100644 --- a/Classes/Controller/AjaxController.php +++ b/Classes/Controller/AjaxController.php @@ -1,5 +1,7 @@ fileService = GeneralUtility::makeInstance(FileService::class); + $this->aiAltTextService = GeneralUtility::makeInstance(AiAltTextService::class); + $this->siteLanguageService = GeneralUtility::makeInstance(SiteLanguageService::class); + } + /** * @throws \GuzzleHttp\Exception\GuzzleException */ @@ -43,4 +61,40 @@ public function blobImage(ServerRequestInterface $request): ResponseInterface return $response->withHeader('Content-Type', 'text/plain'); } + + public function getAltText(ServerRequestInterface $request): ResponseInterface + { + /** @var string[] $requestBody */ + $requestBody = $request->getParsedBody(); + + /** @var Response $response */ + $response = GeneralUtility::makeInstance(Response::class); + + $fileUid = (string) isset($requestBody['fileUid']) ? $requestBody['fileUid'] : null; + $pageLanguageUid = isset($requestBody['systemLanguageUid']) ? (int) $requestBody['systemLanguageUid'] : null; + + if (empty($fileUid)) { + return $response->withHeader('Content-Type', 'text/plain'); + } + + $file = $this->fileService->getFileById($fileUid); + + if (null === $file) { + return $response->withStatus(404, '')->withHeader('Content-Type', 'text/plain'); + } + + try { + $languageIsoCode = $this->siteLanguageService->getLanguageIsoCodeByUid($pageLanguageUid); + $altText = $this->aiAltTextService->getAltText($file, $languageIsoCode); + $response->getBody()->write($altText); + } catch (\Exception $e) { + $response = $response->withStatus(500) + ->withHeader('Content-Type', 'text/plain'); + $response->getBody()->write($e->getMessage()); + + return $response; + } + + return $response->withHeader('Content-Type', 'text/plain'); + } } diff --git a/Classes/Controller/BaseController.php b/Classes/Controller/BaseController.php index d120acd..798e097 100644 --- a/Classes/Controller/BaseController.php +++ b/Classes/Controller/BaseController.php @@ -29,7 +29,7 @@ public function initializeAction(): void if (11 === $typo3Version->getMajorVersion()) { $cropperPath = PathUtility::getPublicResourceWebPath('EXT:mkcontentai/Resources/Public/JavaScript/cropper'); } - $pageRenderer = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Page\PageRenderer::class); + $pageRenderer = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Page\PageRenderer::class); $pageRenderer->loadRequireJsModule('TYPO3/CMS/Mkcontentai/MkContentAi'); $pageRenderer->addRequireJsConfiguration( [ diff --git a/Classes/Controller/SettingsController.php b/Classes/Controller/SettingsController.php index 0f6300c..03e2284 100644 --- a/Classes/Controller/SettingsController.php +++ b/Classes/Controller/SettingsController.php @@ -15,66 +15,103 @@ namespace DMK\MkContentAi\Controller; +use DMK\MkContentAi\Http\Client\AltTextClient; use DMK\MkContentAi\Http\Client\ClientInterface; use DMK\MkContentAi\Http\Client\OpenAiClient; use DMK\MkContentAi\Http\Client\StabilityAiClient; use DMK\MkContentAi\Http\Client\StableDiffusionClient; +use DMK\MkContentAi\Service\SiteLanguageService; use TYPO3\CMS\Core\Messaging\AbstractMessage; use TYPO3\CMS\Core\Registry; use TYPO3\CMS\Core\Utility\GeneralUtility; class SettingsController extends BaseController { - public function settingsAction(string $openAiApiKeyValue = null, string $stableDiffusionApiValue = null, string $stabilityAiApiValue = null, int $imageAiEngine = 0, string $stableDiffusionModel = 'empty'): void + /** + * Configure settings for various AI engines. + * + * @param string $openAiApiKeyValue API key for OpenAI client + * @param array $stableDiffusionValues Array with specific keys and values + * @param string $stabilityAiApiValue API key for Stability AI client + * @param string $altTextAiApiValue API key for Alt Text AI client + * @param int $imageAiEngine Indicator of which AI engine to use for image processing + */ + public function settingsAction(string $openAiApiKeyValue = '', array $stableDiffusionValues = [], string $stabilityAiApiValue = '', string $altTextAiApiValue = '', int $imageAiEngine = 0): void { $openAi = GeneralUtility::makeInstance(OpenAiClient::class); - if ($openAiApiKeyValue) { - $this->setApiKey($openAiApiKeyValue, $openAi); - } + $this->setApiKey($openAiApiKeyValue, $openAi); $stableDiffusion = GeneralUtility::makeInstance(StableDiffusionClient::class); - if ($stableDiffusionApiValue) { - $this->setApiKey($stableDiffusionApiValue, $stableDiffusion); - } + $this->setApiKey($stableDiffusionValues['api'] ?? '', $stableDiffusion); $stabilityAi = GeneralUtility::makeInstance(StabilityAiClient::class); - if ($stabilityAiApiValue) { - $this->setApiKey($stabilityAiApiValue, $stabilityAi); - } + $this->setApiKey($stabilityAiApiValue, $stabilityAi); + + $altTextAi = GeneralUtility::makeInstance(AltTextClient::class); + $this->setApiKey($altTextAiApiValue, $altTextAi); + + /** @var SiteLanguageService $siteLanguageService */ + $siteLanguageService = GeneralUtility::makeInstance(SiteLanguageService::class); if ($imageAiEngine) { $registry = GeneralUtility::makeInstance(Registry::class); $registry->set(AiImageController::class, AiImageController::GENERATOR_ENGINE_KEY, $imageAiEngine); } - if ($this->request->hasArgument('stableDiffusionModel')) { - $stableDiffusion->setCurrentModel($stableDiffusionModel); + if ($this->request->hasArgument('stableDiffusionValues')) { + $stableDiffusionValues = $this->request->getArgument('stableDiffusionValues'); + if (is_array($stableDiffusionValues)) { + $stableDiffusionModel = $stableDiffusionValues['model']; + $stableDiffusion->setCurrentModel($stableDiffusionModel); + } + } + + if ($this->request->hasArgument('altTextAiLanguage')) { + /** @var string $altTextAiLanguage */ + $altTextAiLanguage = $this->request->getArgument('altTextAiLanguage'); + if (isset($altTextAiLanguage)) { + $this->setLanguage($altTextAiLanguage, $altTextAi, $siteLanguageService); + } } $this->view->assignMultiple( [ 'openAiApiKey' => $openAi->getMaskedApiKey(), 'stableDiffusionApiKey' => $stableDiffusion->getMaskedApiKey(), + 'stableDiffusionModel' => $stableDiffusion->getCurrentModel(), 'stabilityAiApiValue' => $stabilityAi->getMaskedApiKey(), - 'currentStabeDiffusionModel' => $stableDiffusion->getCurrentModel(), + 'altTextAiApiValue' => $altTextAi->getMaskedApiKey(), 'imageAiEngine' => SettingsController::getImageAiEngine(), + 'altTextAiLanguage' => $siteLanguageService->getAllAvailableLanguages(), + 'selectedAltTextAiLanguage' => $siteLanguageService->getLanguage(), ] ); - if ($stableDiffusionApiValue) { - try { - $this->view->assignMultiple( - [ - 'stabeDiffusionModels' => array_merge( - [ - 'none' => [ - 'model_id' => '', - ], + try { + $this->view->assignMultiple( + [ + 'stabeDiffusionModels' => array_merge( + [ + 'none' => [ + 'model_id' => '', ], - $stableDiffusion->modelList() - ), - ] - ); + ], + $stableDiffusion->modelList() + ), + ] + ); + } catch (\Exception $e) { + $this->addFlashMessage($e->getMessage(), '', AbstractMessage::ERROR); + } + } + + private function setLanguage(string $language, ClientInterface $client, SiteLanguageService $siteLanguageService): void + { + if ($language) { + $siteLanguageService->setLanguage($language); + $this->addFlashMessage('Language was saved.'); + try { + $client->validateApiCall(); } catch (\Exception $e) { $this->addFlashMessage($e->getMessage(), '', AbstractMessage::ERROR); } @@ -83,12 +120,14 @@ public function settingsAction(string $openAiApiKeyValue = null, string $stableD private function setApiKey(string $key, ClientInterface $client): void { - $client->setApiKey($key); - $this->addFlashMessage('API key was saved.'); - try { - $client->validateApiCall(); - } catch (\Exception $e) { - $this->addFlashMessage($e->getMessage(), '', AbstractMessage::ERROR); + if ($key) { + $client->setApiKey($key); + $this->addFlashMessage('API key was saved.'); + try { + $client->validateApiCall(); + } catch (\Exception $e) { + $this->addFlashMessage($e->getMessage(), '', AbstractMessage::ERROR); + } } } diff --git a/Classes/Http/Client/AltTextClient.php b/Classes/Http/Client/AltTextClient.php new file mode 100644 index 0000000..41358e8 --- /dev/null +++ b/Classes/Http/Client/AltTextClient.php @@ -0,0 +1,133 @@ + + * 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\Http\Client; + +use DMK\MkContentAi\Service\SiteLanguageService; +use DMK\MkContentAi\Utility\AiUtility; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\FormDataPart; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use TYPO3\CMS\Extbase\Domain\Model\File; + +class AltTextClient extends BaseClient implements ClientInterface +{ + private SiteLanguageService $siteLanguageService; + + private HttpClientInterface $client; + + public function __construct(SiteLanguageService $siteLanguageService) + { + $this->siteLanguageService = $siteLanguageService; + $this->client = HttpClient::create(); + } + + /** + * Returns an array with authorization headers. + * + * @return array + */ + private function getAuthorizationHeader(): array + { + return [ + 'X-API-Key' => $this->getApiKey(), + ]; + } + + public function getAltTextForFile(File $file, string $languageIsoCode = null): string + { + $localFile = $file->getOriginalResource()->getForLocalProcessing(); + + if (null === $languageIsoCode) { + $languageIsoCode = $this->siteLanguageService->getLanguage(); + } + $formFields = [ + 'image[raw]' => DataPart::fromPath($localFile), + 'image[asset_id]' => AiUtility::getAiAssetId($file->getOriginalResource()->getUid(), $languageIsoCode), + ]; + + if (null !== $languageIsoCode) { + $formFields['lang'] = $languageIsoCode; + } elseif (null !== $this->siteLanguageService->getLanguage()) { + $formFields['lang'] = $this->siteLanguageService->getLanguage(); + } + + $formData = new FormDataPart($formFields); + + $headers = array_merge($this->getAuthorizationHeader(), $formData->getPreparedHeaders()->toArray()); + + $response = $this->client->request('POST', 'https://alttext.ai/api/v1/images', [ + 'headers' => $headers, + 'body' => $formData->bodyToIterable(), + ]); + + $response = $this->validateResponse($response->getContent()); + + return $response->alt_text; + } + + public function getByAssetId(int $assetId, string $languageIsoCode = null): string + { + if (null === $languageIsoCode) { + $languageIsoCode = $this->siteLanguageService->getLanguage(); + } + + $assetIdWithLangIsoCode = AiUtility::getAiAssetId($assetId, $languageIsoCode); + + $response = $this->client->request('GET', 'https://alttext.ai/api/v1/images/'.$assetIdWithLangIsoCode, [ + 'headers' => $this->getAuthorizationHeader(), + ]); + + $response = $this->validateResponse($response->getContent()); + + return $response->alt_text; + } + + public function getAccount(): \stdClass + { + $response = $this->client->request('GET', 'https://alttext.ai/api/v1/account', [ + 'headers' => $this->getAuthorizationHeader(), + ]); + + $response = $this->validateResponse($response->getContent()); + + return $response; + } + + /** + * @param string|bool $response + * + * @throws \Exception + */ + public function validateResponse($response): \stdClass + { + if (!is_string($response)) { + throw new \Exception('Response is not string'); + } + $response = json_decode($response); + + return $response; + } + + public function validateApiCall(): \stdClass + { + $response = $this->getAccount(); + + return $response; + } +} diff --git a/Classes/Http/Client/ClientInterface.php b/Classes/Http/Client/ClientInterface.php index 060c10c..a271248 100644 --- a/Classes/Http/Client/ClientInterface.php +++ b/Classes/Http/Client/ClientInterface.php @@ -15,36 +15,9 @@ namespace DMK\MkContentAi\Http\Client; -use DMK\MkContentAi\Domain\Model\Image; -use TYPO3\CMS\Extbase\Domain\Model\File; - interface ClientInterface { public function validateApiCall(): \stdClass; public function setApiKey(string $apiKey): void; - - /** - * @return array - */ - public function image(string $text): array; - - /** - * @return array - */ - public function createImageVariation(File $file): array; - - public function upscale(File $file): Image; - - /** - * @return array - */ - public function extend(string $sourceImagePath, string $direction): array; - - public function getFolderName(): string; - - /** - * @return array - */ - public function getAllowedOperations(): array; } diff --git a/Classes/Http/Client/ImageApiInterface.php b/Classes/Http/Client/ImageApiInterface.php new file mode 100644 index 0000000..0224d43 --- /dev/null +++ b/Classes/Http/Client/ImageApiInterface.php @@ -0,0 +1,46 @@ + + * 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\Http\Client; + +use DMK\MkContentAi\Domain\Model\Image; +use TYPO3\CMS\Extbase\Domain\Model\File; + +interface ImageApiInterface extends ClientInterface +{ + /** + * @return array + */ + public function image(string $text): array; + + /** + * @return array + */ + public function createImageVariation(File $file): array; + + public function upscale(File $file): Image; + + /** + * @return array + */ + public function extend(string $sourceImagePath, string $direction): array; + + public function getFolderName(): string; + + /** + * @return array + */ + public function getAllowedOperations(): array; +} diff --git a/Classes/Http/Client/OpenAiClient.php b/Classes/Http/Client/OpenAiClient.php index fad5db1..33a09c6 100644 --- a/Classes/Http/Client/OpenAiClient.php +++ b/Classes/Http/Client/OpenAiClient.php @@ -22,7 +22,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Domain\Model\File; -class OpenAiClient extends BaseClient implements ClientInterface +class OpenAiClient extends BaseClient implements ImageApiInterface { public function __construct() { diff --git a/Classes/Http/Client/StabilityAiClient.php b/Classes/Http/Client/StabilityAiClient.php index bdaaec4..42f1556 100644 --- a/Classes/Http/Client/StabilityAiClient.php +++ b/Classes/Http/Client/StabilityAiClient.php @@ -22,7 +22,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Domain\Model\File; -class StabilityAiClient extends BaseClient implements ClientInterface +class StabilityAiClient extends BaseClient implements ImageApiInterface { private const API_LINK = 'https://api.stability.ai/'; diff --git a/Classes/Http/Client/StableDiffusionClient.php b/Classes/Http/Client/StableDiffusionClient.php index 9f0d92f..e05b593 100644 --- a/Classes/Http/Client/StableDiffusionClient.php +++ b/Classes/Http/Client/StableDiffusionClient.php @@ -22,7 +22,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Domain\Model\File; -class StableDiffusionClient extends BaseClient implements ClientInterface +class StableDiffusionClient extends BaseClient implements ImageApiInterface { private const API_LINK = 'https://stablediffusionapi.com/api/v3/'; @@ -319,7 +319,7 @@ public function getApiLink(): string return self::API_LINK; } - public function setCurrentModel(string $modelName): void + public function setCurrentModel(?string $modelName): void { $registry = $this->getRegistry(); $class = $this->getClass(); diff --git a/Classes/Service/AiAltTextService.php b/Classes/Service/AiAltTextService.php new file mode 100644 index 0000000..b607ed7 --- /dev/null +++ b/Classes/Service/AiAltTextService.php @@ -0,0 +1,49 @@ + + * 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\Service; + +use DMK\MkContentAi\Http\Client\AltTextClient; +use TYPO3\CMS\Extbase\Domain\Model\File; + +class AiAltTextService +{ + public AltTextClient $altTextClient; + + public function __construct(AltTextClient $altTextClient) + { + $this->altTextClient = $altTextClient; + } + + /** + * @throws \Exception + */ + public function getAltText(File $file, string $languageIsoCode = null): string + { + try { + $altText = $this->altTextClient->getByAssetId($file->getOriginalResource()->getUid(), $languageIsoCode); + } catch (\Exception $e) { + if (404 != $e->getCode()) { + throw new \Exception($e->getMessage()); + } + + return $this->altTextClient->getAltTextForFile($file, $languageIsoCode); + } + + return $altText; + } +} diff --git a/Classes/Service/FileService.php b/Classes/Service/FileService.php index 96c18a6..379de25 100644 --- a/Classes/Service/FileService.php +++ b/Classes/Service/FileService.php @@ -17,22 +17,26 @@ use TYPO3\CMS\Core\Imaging\GraphicalFunctions; use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Resource\StorageRepository; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Domain\Model\File; class FileService { private StorageRepository $storageRepository; + private ResourceFactory $resourceFactory; public GraphicalFunctions $graphicalFunctions; private string $path = 'mkcontentai'; - public function __construct(string $folder) + public function __construct(string $folder = null) { $this->path = 'mkcontentai/'.$folder; $this->storageRepository = GeneralUtility::makeInstance(StorageRepository::class); $this->graphicalFunctions = GeneralUtility::makeInstance(GraphicalFunctions::class); + $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); } /** @@ -143,4 +147,17 @@ public function saveTempBase64Image(string $base64): string return $tempFile; } + + public function getFileById(string $fileId): ?File + { + try { + $fileOriginalResource = $this->resourceFactory->getFileObject((int) $fileId); + $file = new File(); + $file->setOriginalResource($fileOriginalResource); + } catch (\Exception $e) { + return null; + } + + return $file; + } } diff --git a/Classes/Service/SiteLanguageService.php b/Classes/Service/SiteLanguageService.php new file mode 100644 index 0000000..8e83dfd --- /dev/null +++ b/Classes/Service/SiteLanguageService.php @@ -0,0 +1,109 @@ + + * 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\Service; + +use TYPO3\CMS\Core\Registry; +use TYPO3\CMS\Core\Site\SiteFinder; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class SiteLanguageService +{ + private const SELECTED_LANGUAGE = 'language'; + + private Registry $registry; + + private SiteFinder $siteFinder; + + public function __construct() + { + $this->registry = GeneralUtility::makeInstance(Registry::class); + $this->siteFinder = GeneralUtility::makeInstance(SiteFinder::class); + } + + public function getLanguage(): ?string + { + return $this->registry->get(__CLASS__, self::SELECTED_LANGUAGE); + } + + public function getFullLanguageName(): ?string + { + $allSites = $this->siteFinder->getAllSites(); + + foreach ($allSites as $site) { + $siteLanguages = $site->getAllLanguages(); + + foreach ($siteLanguages as $siteLanguage) { + /** @var array $language */ + $language = $siteLanguage->toArray(); + if ($language['twoLetterIsoCode'] === $this->getLanguage()) { + return $language['title']; + } + } + } + + return $this->getLanguage(); + } + + public function setLanguage(string $language): void + { + $this->registry->set(__CLASS__, self::SELECTED_LANGUAGE, $language); + } + + /** + * @return array + */ + public function getAllAvailableLanguages(): array + { + $allSites = $this->siteFinder->getAllSites(); + $languageCode = []; + + foreach ($allSites as $site) { + $siteLanguages = $site->getAllLanguages(); + + foreach ($siteLanguages as $siteLanguage) { + /** @var array $language */ + $language = $siteLanguage->toArray(); + $languageCode[$language['twoLetterIsoCode']] = $language['title']; + } + } + + return $languageCode; + } + + public function getLanguageIsoCodeByUid(?int $uid): ?string + { + $allSites = $this->siteFinder->getAllSites(); + $languages = []; + + foreach ($allSites as $site) { + $siteLanguages = $site->getAllLanguages(); + + foreach ($siteLanguages as $siteLanguage) { + /** @var array $language */ + $language = $siteLanguage->toArray(); + $languages[$language['languageId']] = $language; + } + } + + if (isset($languages[$uid])) { + return $languages[$uid]['twoLetterIsoCode']; + } + + return null; + } +} diff --git a/Classes/User/InlineControl/ImageGenerationButton.php b/Classes/User/InlineControl/ImageGenerationButton.php index 23080b5..ede9789 100644 --- a/Classes/User/InlineControl/ImageGenerationButton.php +++ b/Classes/User/InlineControl/ImageGenerationButton.php @@ -45,7 +45,7 @@ public function render(): string $item .= htmlspecialchars('AI generation of image by text prompt'); $item .= ''; - $pageRenderer = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Page\PageRenderer::class); + $pageRenderer = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Page\PageRenderer::class); $pageRenderer->loadRequireJsModule('TYPO3/CMS/Mkcontentai/BackendPrompt'); return $item; diff --git a/Classes/Utility/AiUtility.php b/Classes/Utility/AiUtility.php new file mode 100644 index 0000000..9e2ee1f --- /dev/null +++ b/Classes/Utility/AiUtility.php @@ -0,0 +1,36 @@ + + * 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\Utility; + +class AiUtility +{ + public static function getAiAssetId(int $fileUid, ?string $languageIsoCode): string + { + return $fileUid.'-'.$languageIsoCode.'-'.self::getSubStringEncryptionKey(); + } + + /** + * @SuppressWarnings(PHPMD.Superglobals) + */ + private static function getSubStringEncryptionKey(): string + { + $encryptionKey = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']; + + return substr($encryptionKey, 0, -86); + } +} diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php index 8897b27..e41333c 100644 --- a/Configuration/Backend/AjaxRoutes.php +++ b/Configuration/Backend/AjaxRoutes.php @@ -16,10 +16,14 @@ return [ 'blob_image' => [ 'path' => '/blob/image', - 'target' => \DMK\MkContentAi\Controller\AjaxController::class.'::blobImage', + 'target' => DMK\MkContentAi\Controller\AjaxController::class.'::blobImage', ], 'image_prompt' => [ 'path' => '/image/prompt', - 'target' => \DMK\MkContentAi\Controller\AiImageController::class.'::promptResultAjaxAction', + 'target' => DMK\MkContentAi\Controller\AiImageController::class.'::promptResultAjaxAction', + ], + 'alt_text' => [ + 'path' => '/image/alt-text', + 'target' => DMK\MkContentAi\Controller\AjaxController::class.'::getAltText', ], ]; diff --git a/Configuration/Backend/Modules.php b/Configuration/Backend/Modules.php index 02ce565..524d1be 100644 --- a/Configuration/Backend/Modules.php +++ b/Configuration/Backend/Modules.php @@ -25,12 +25,15 @@ 'labels' => 'LLL:EXT:mkcontentai/Resources/Private/Language/locallang_contentai.xlf', 'extensionName' => 'Mkcontentai', 'controllerActions' => [ - \DMK\MkContentAi\Controller\AiImageController::class => [ + DMK\MkContentAi\Controller\AiImageController::class => [ 'filelist', 'variants', 'prompt', 'promptResult', 'saveFile', 'upscale', 'extend', 'cropAndExtend', ], - \DMK\MkContentAi\Controller\SettingsController::class => [ + DMK\MkContentAi\Controller\SettingsController::class => [ 'settings', ], + DMK\MkContentAi\Controller\AiTextController::class => [ + 'altText', 'altTextSave', + ], ], ], ]; diff --git a/Configuration/Icons.php b/Configuration/Icons.php index ce400bc..04b985f 100644 --- a/Configuration/Icons.php +++ b/Configuration/Icons.php @@ -15,7 +15,7 @@ return [ 'mkcontentai' => [ - 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + 'provider' => TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, 'source' => 'EXT:mkcontentai/Resources/Public/Icons/Extension.svg', ], ]; diff --git a/Configuration/JavaScriptModules.php b/Configuration/JavaScriptModules.php index f64494f..3c3cc89 100644 --- a/Configuration/JavaScriptModules.php +++ b/Configuration/JavaScriptModules.php @@ -17,6 +17,7 @@ 'dependencies' => ['core', 'backend'], 'tags' => [ 'backend.contextmenu', + 'backend.form', ], 'imports' => [ '@t3docs/mkcontentai/' => 'EXT:mkcontentai/Resources/Public/JavaScript/', diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 512a2d9..4ddd2af 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -7,3 +7,18 @@ services: DMK\MkContentAi\: resource: '../Classes/*' exclude: '../Classes/Domain/Model/*' + + DMK\MkContentAi\Backend\EventListener\CustomFileControlsEventListener: + tags: + - name: event.listener + method: handleEvent + event: TYPO3\CMS\Backend\Form\Event\CustomFileControlsEvent + + DMK\MkContentAi\Service\SiteLanguageService: + public: true + + DMK\MkContentAi\Http\Client\AltTextClient: + public: true + + DMK\MkContentAi\Service\AiAltTextService: + public: true diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php index 4997d63..2bbac0f 100644 --- a/Configuration/TCA/Overrides/sys_template.php +++ b/Configuration/TCA/Overrides/sys_template.php @@ -15,4 +15,4 @@ defined('TYPO3') || exit; -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('mkcontentai', 'Configuration/TypoScript', 'DMK Content AI'); +TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('mkcontentai', 'Configuration/TypoScript', 'DMK Content AI'); diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php index 2874f98..cf926fc 100644 --- a/Configuration/TCA/Overrides/tt_content.php +++ b/Configuration/TCA/Overrides/tt_content.php @@ -13,4 +13,4 @@ * of the License, or any later version. */ -$GLOBALS['TCA']['tt_content']['columns']['image']['config']['customControls']['addHeader']['userFunc'] = \DMK\MkContentAi\User\InlineControl\ImageGenerationButton::class.'->render'; +$GLOBALS['TCA']['tt_content']['columns']['image']['config']['customControls']['addHeader']['userFunc'] = DMK\MkContentAi\User\InlineControl\ImageGenerationButton::class.'->render'; diff --git a/Resources/Private/Templates/AiText/AltText.html b/Resources/Private/Templates/AiText/AltText.html new file mode 100644 index 0000000..90c927f --- /dev/null +++ b/Resources/Private/Templates/AiText/AltText.html @@ -0,0 +1,28 @@ + + + + + +

AI alt tag generation

+ +

+ AI generated {languageName} alt text for image is "{altText}". In order to change language - go to + Settings + +

+ + +
+ +

+ + +
+ +
+
+
+ diff --git a/Resources/Private/Templates/Settings/Settings.html b/Resources/Private/Templates/Settings/Settings.html index aaa7620..096df6b 100644 --- a/Resources/Private/Templates/Settings/Settings.html +++ b/Resources/Private/Templates/Settings/Settings.html @@ -34,37 +34,35 @@

OpenAI settings:

Stable Diffusion settings:

- + Currently stored Stable Diffusion API key: {stableDiffusionApiKey}
- - -
-
+ + +
+ Currently used model (https://stablediffusionapi.com/models): + + + {stableDiffusionModel} + + + none selected + + +
- - Currently used model (https://stablediffusionapi.com/models): - - - {currentStabeDiffusionModel} - - - none selected - - -
-

-
+

StabilityAI settings:

- Currently stored SStabilityAI API key: {stabilityAiApiValue}
+ Currently stored StabilityAI API key: {stabilityAiApiValue}
@@ -72,5 +70,18 @@

StabilityAI settings:

+

AltText AI settings:

+ + Currently stored AltText AI API key (alttext.ai): {altTextAiApiValue}
+
+ + + + +
+ +
+
+ diff --git a/Resources/Public/JavaScript/AltText.js b/Resources/Public/JavaScript/AltText.js new file mode 100644 index 0000000..c5d4d66 --- /dev/null +++ b/Resources/Public/JavaScript/AltText.js @@ -0,0 +1,51 @@ +require(['jquery'], function ($) { + $(document).ready(function () { + $(document).on('click', '.alt-refresh', function (event) { + const button = $(this); + const spinner = button.find('.spinner-border'); + spinner.show(); + + $.ajax({ + type: 'POST', + url: TYPO3.settings.ajaxUrls.alt_text, + data: + { + fileUid: $(this).parent().data('uid-local'), + systemLanguageUid: $(this).parent().data('sys-language-uid') + }, + success: function (response) { + require(['TYPO3/CMS/Backend/Notification'], function (Notification) { + Notification.success('Alt text has been successfully generated by AI'); + }); + + // whole form div (palette) where alternative fields are displayed + const alternativeDivContainer = button.parent().parent().parent().parent(); + const alternativeCheckbox = alternativeDivContainer.find('input[type=checkbox]'); + + if (alternativeCheckbox.prop('checked') === false) { + alternativeCheckbox.click(); + } + + alternativeDivContainer.find('input[type=text]').val(response); + const hiddenAlternativeInput = alternativeDivContainer.find('input[type=hidden]')[1]; + $(hiddenAlternativeInput).val(response); + }, + error: function (response) { + require(['TYPO3/CMS/Backend/Notification'], function (Notification) { + let errorMessage = 'Unexpected error occurred, please try refresh text later'; + + if (response.responseText !== '') { + errorMessage = response.responseText; + } + + Notification.error(errorMessage); + }); + }, + complete: function () { + spinner.hide(); + } + }); + event.stopPropagation(); + }); + }); +}); diff --git a/Resources/Public/JavaScript/ContextMenu.js b/Resources/Public/JavaScript/ContextMenu.js index 2a7a45a..2af8867 100644 --- a/Resources/Public/JavaScript/ContextMenu.js +++ b/Resources/Public/JavaScript/ContextMenu.js @@ -26,7 +26,12 @@ define(function () { ContextMenu.extend = function (table, uid) { top.TYPO3.Backend.ContentContainer.setUrl($(this).attr('data-navigate-uri')); }; - - + /** + * @param {string} table + * @param {int} uid of the page + */ + ContextMenu.alt = function (table, uid) { + top.TYPO3.Backend.ContentContainer.setUrl($(this).attr('data-navigate-uri')); + }; return ContextMenu; }); diff --git a/Resources/Public/JavaScript/context-menu-actions.js b/Resources/Public/JavaScript/context-menu-actions.js index 3574a9d..682dc34 100644 --- a/Resources/Public/JavaScript/context-menu-actions.js +++ b/Resources/Public/JavaScript/context-menu-actions.js @@ -7,6 +7,14 @@ class ContextMenuActions { upscale(table, uid, data) { top.TYPO3.Backend.ContentContainer.setUrl(data.navigateUri); }; + + extend(table, uid, data) { + top.TYPO3.Backend.ContentContainer.setUrl(data.navigateUri); + }; + + alt(table, uid, data) { + top.TYPO3.Backend.ContentContainer.setUrl(data.navigateUri); + }; } export default new ContextMenuActions(); diff --git a/Tests/UnitTestsBootstrap.php b/Tests/UnitTestsBootstrap.php index 3ab40ff..d689af2 100644 --- a/Tests/UnitTestsBootstrap.php +++ b/Tests/UnitTestsBootstrap.php @@ -16,7 +16,7 @@ use TYPO3\CMS\Core\Information\Typo3Version; call_user_func(function () { - $testbase = new \TYPO3\TestingFramework\Core\Testbase(); + $testbase = new TYPO3\TestingFramework\Core\Testbase(); // These if's are for core testing (package typo3/cms) only. cms-composer-installer does // not create the autoload-include.php file that sets these env vars and sets composer @@ -34,42 +34,42 @@ $testbase->defineSitePath(); - $requestType = \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_BE | \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI; - \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::run(0, $requestType); + $requestType = TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_BE | TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI; + TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::run(0, $requestType); - $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath().'/typo3conf/ext'); - $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath().'/typo3temp/assets'); - $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath().'/typo3temp/var/tests'); - $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath().'/typo3temp/var/transient'); + $testbase->createDirectory(TYPO3\CMS\Core\Core\Environment::getPublicPath().'/typo3conf/ext'); + $testbase->createDirectory(TYPO3\CMS\Core\Core\Environment::getPublicPath().'/typo3temp/assets'); + $testbase->createDirectory(TYPO3\CMS\Core\Core\Environment::getPublicPath().'/typo3temp/var/tests'); + $testbase->createDirectory(TYPO3\CMS\Core\Core\Environment::getPublicPath().'/typo3temp/var/transient'); // Retrieve an instance of class loader and inject to core bootstrap $classLoader = require $testbase->getPackagesPath().'/autoload.php'; - \TYPO3\CMS\Core\Core\Bootstrap::initializeClassLoader($classLoader); + TYPO3\CMS\Core\Core\Bootstrap::initializeClassLoader($classLoader); // Initialize default TYPO3_CONF_VARS - $configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager(); + $configurationManager = new TYPO3\CMS\Core\Configuration\ConfigurationManager(); $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration(); - $cache = new \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend( + $cache = new TYPO3\CMS\Core\Cache\Frontend\PhpFrontend( 'core', - new \TYPO3\CMS\Core\Cache\Backend\NullBackend('production', []) + new TYPO3\CMS\Core\Cache\Backend\NullBackend('production', []) ); // Set all packages to active if (version_compare((new Typo3Version())->getVersion(), '11.3.0', '>')) { - $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( - \TYPO3\CMS\Core\Package\UnitTestPackageManager::class, - \TYPO3\CMS\Core\Core\Bootstrap::createPackageCache($cache) + $packageManager = TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( + TYPO3\CMS\Core\Package\UnitTestPackageManager::class, + TYPO3\CMS\Core\Core\Bootstrap::createPackageCache($cache) ); } else { - $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( - \TYPO3\CMS\Core\Package\UnitTestPackageManager::class, + $packageManager = TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( + TYPO3\CMS\Core\Package\UnitTestPackageManager::class, $cache ); } - \TYPO3\CMS\Core\Utility\GeneralUtility::setSingletonInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager); - \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::setPackageManager($packageManager); + TYPO3\CMS\Core\Utility\GeneralUtility::setSingletonInstance(TYPO3\CMS\Core\Package\PackageManager::class, $packageManager); + TYPO3\CMS\Core\Utility\ExtensionManagementUtility::setPackageManager($packageManager); $testbase->dumpClassLoadingInformation(); - \TYPO3\CMS\Core\Utility\GeneralUtility::purgeInstances(); + TYPO3\CMS\Core\Utility\GeneralUtility::purgeInstances(); }); diff --git a/ext_localconf.php b/ext_localconf.php index 57c5951..42c3629 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -21,4 +21,8 @@ = ButtonBarHook::class.'->getButtons'; $GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders'][1697195476] = - \DMK\MkContentAi\ContextMenu\ContentAiItemProvider::class; + DMK\MkContentAi\ContextMenu\ContentAiItemProvider::class; + +$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][TYPO3\CMS\Backend\Form\Element\InputTextElement::class] = [ + 'className' => DMK\MkContentAi\Backend\Form\Element\InputTextWithAiAltTextSupportElement::class, +]; diff --git a/ext_tables.php b/ext_tables.php index 635e2c2..9bc6815 100644 --- a/ext_tables.php +++ b/ext_tables.php @@ -16,14 +16,15 @@ defined('TYPO3') or exit; (static function () { - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule( + TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule( 'Mkcontentai', 'system', 'contentai', '', [ - \DMK\MkContentAi\Controller\AiImageController::class => 'filelist, variants, prompt, promptResult, saveFile, upscale, extend, cropAndExtend', - \DMK\MkContentAi\Controller\SettingsController::class => 'settings', + DMK\MkContentAi\Controller\AiImageController::class => 'filelist, variants, prompt, promptResult, saveFile, upscale, extend, cropAndExtend', + DMK\MkContentAi\Controller\SettingsController::class => 'settings', + DMK\MkContentAi\Controller\AiTextController::class => 'altText, altTextSave', ], [ 'access' => 'user,group', @@ -32,6 +33,6 @@ ] ); - \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addLLrefForTCAdescr('tx_mkcontentai_domain_model_image', 'EXT:mkcontentai/Resources/Private/Language/locallang_csh_tx_mkcontentai_domain_model_image.xlf'); - \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::allowTableOnStandardPages('tx_mkcontentai_domain_model_image'); + TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addLLrefForTCAdescr('tx_mkcontentai_domain_model_image', 'EXT:mkcontentai/Resources/Private/Language/locallang_csh_tx_mkcontentai_domain_model_image.xlf'); + TYPO3\CMS\Core\Utility\ExtensionManagementUtility::allowTableOnStandardPages('tx_mkcontentai_domain_model_image'); })(); diff --git a/phpmd.xml b/phpmd.xml index 01fa2c7..52f0da5 100644 --- a/phpmd.xml +++ b/phpmd.xml @@ -144,5 +144,4 @@ - diff --git a/phpstan.neon b/phpstan.neon index 3cbe6a3..f506b8c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -18,7 +18,6 @@ parameters: - '#Method DMK\\MkContentAi\\Service\\ExtendService::getImages\(\) has parameter \$source with no type specified.#' - '#Method DMK\\MkContentAi\\Service\\ExtendService::getImageDimensions\(\) has parameter \$source with no type specified.#' - '#Method DMK\\MkContentAi\\Service\\ExtendService::combinedImage\(\) has no return type specified.#' - - '#Method DMK\\MkContentAi\\Service\\FileService::getStorage\(\) should return TYPO3\\CMS\\Core\\Resource\\ResourceStorage but returns TYPO3\\CMS\\Core\\Resource\\Storage.#' - message: '#Parameter \$event of method DMK\\MkContentAi\\Backend\\EventListener\\CustomFileControlsEventListener::handleEvent\(\) has invalid type TYPO3\\CMS\\Backend\\Form\\Event\\CustomFileControlsEvent\.#' path: Classes/Backend/EventListener/CustomFileControlsEventListener.php