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
+
+