auto_alter-8.x-1.x-dev/src/Plugin/AutoAlterDescribeImage/AlttextAi.php
src/Plugin/AutoAlterDescribeImage/AlttextAi.php
<?php
namespace Drupal\auto_alter\Plugin\AutoAlterDescribeImage;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\file\Entity\File;
use GuzzleHttp\ClientInterface;
use Drupal\Component\Utility\Xss;
use Drupal\image\Entity\ImageStyle;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\File\FileSystemInterface;
use GuzzleHttp\Exception\RequestException;
use Drupal\auto_alter\AutoAlterCredentials;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\auto_alter\DescribeImageServiceInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Alttext.ai integration.
*
* @AutoAlterDescribeImage(
* id = "alttext_ai",
* title = @Translation("Alttext.AI"),
* )
*/
class AlttextAi implements DescribeImageServiceInterface {
use StringTranslationTrait;
/**
* The file system service.
*
* @var Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The httpClient.
*
* @var GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The ConfigFactory.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
private $configFactory;
/**
* Logger Factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* The config object.
*
* @var \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* The messenger service.
*
* @var \Drupal\auto_alter\Plugin\AutoAlterDescribeImage\MessengerInterface
*/
protected $messenger;
/**
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Class constructor.
*/
public function __construct(FileSystemInterface $file_system, ClientInterface $http_client, LanguageManagerInterface $language_manager, ConfigFactory $configFactory, LoggerChannelFactoryInterface $loggerFactory, MessengerInterface $messenger, ModuleHandlerInterface $module_handler) {
$this->fileSystem = $file_system;
$this->httpClient = $http_client;
$this->languageManager = $language_manager;
$this->config = $configFactory->get('auto_alter.settings');
$this->loggerFactory = $loggerFactory->get('auto_alter');
$this->messenger = $messenger;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('file_system'),
$container->get('http_client'),
$container->get('language_manager'),
$container->get('config.factory'),
$container->get('logger.factory'),
$container->get('messenger'),
$container->get('module_handler')
);
}
/**
* {@inheritDoc}
*/
public function checkSetup() {
$credentials = new AutoAlterCredentials();
$credentials->setCredentials($this->config->get('credential_provider'), $this->config->get('credentials') ?? []);
$api_key = Xss::filter($credentials->getApikey());
if (empty($api_key)) {
return FALSE;
}
else {
return TRUE;
}
}
/**
* {@inheritDoc}
*/
public function getUri(File $file) {
$filesize = $file->getSize();
$uri = $file->get('uri')->value;
if ($filesize > 1048576) {
$style = ImageStyle::load('auto_alter_help');
$original_uri = $uri;
$uri = $style->buildUri($original_uri);
$style->createDerivative($original_uri, $uri);
}
return $uri;
}
/**
* {@inheritDoc}
*/
public function getDescription(string $uri_or_realpath) {
$path = $this->fileSystem->realpath($uri_or_realpath);
// We might want to force image upload from local environments.
if(empty($path) || !empty(getenv('ALTTEXT_AI_FORCE_IMAGE_UPLOAD'))) {
$json = [
'url' => \Drupal::service('file_url_generator')->generateAbsoluteString($uri_or_realpath),
];
}
else {
$json = [
'image' => [
'raw' => base64_encode(file_get_contents($path)),
],
'lang' => $this->languageManager->getCurrentLanguage()->getId(),
];
}
try {
$credentials = new AutoAlterCredentials();
$credentials->setCredentials($this->config->get('credential_provider'), $this->config->get('credentials') ?? []);
$apiKey = $credentials->getApikey();
$response = $this->httpClient->post('https://alttext.ai/api/v1/images', [
'headers' => ['X-API-Key' => $apiKey],
'json' => $json,
]);
if (empty($response)) {
$this->messenger->addWarning($this->t('The Alttext.AI service returned an empty response.'));
return '';
}
if ($response->getStatusCode() == 200) {
$data = json_decode($response->getBody(), TRUE);
if (!empty($data['alt_text'])) {
return $data['alt_text'];
}
}
else {
$this->messenger->addWarning($this->t('The Alttext.AI service returned an error: @error', [
'@error' => $response->getReasonPhrase(),
]));
}
}
catch (\Exception $e) {
$this->messenger->addWarning($this->t('The Alttext.AI service returned an error: @error', [
'@error' => $e->getMessage(),
]));
}
return '';
}
/**
* {@inheritDoc}
*/
public function getDescriptions(string $uri_or_realpath) {
$path = $this->fileSystem->realpath($uri_or_realpath);
// Get enabled languages for translation.
$enabled_languages = $this->config->get('alttext_ai_translation_languages') ?: [];
$current_lang = $this->languageManager->getCurrentLanguage()->getId();
// If no additional languages are enabled, just use the single description method.
if (empty($enabled_languages)) {
$description = $this->getDescription($uri_or_realpath);
return [$current_lang => $description];
}
// Build language string - always include current language.
$languages = array_unique(array_merge([$current_lang], $enabled_languages));
$lang_string = implode(',', $languages);
// We might want to force image upload from local environments.
if (empty($path) || !empty(getenv('ALTTEXT_AI_FORCE_IMAGE_UPLOAD'))) {
$json = [
'url' => \Drupal::service('file_url_generator')->generateAbsoluteString($uri_or_realpath),
'lang' => $lang_string,
];
}
else {
$json = [
'image' => [
'raw' => base64_encode(file_get_contents($path)),
],
'lang' => $lang_string,
];
}
try {
$credentials = new AutoAlterCredentials();
$credentials->setCredentials($this->config->get('credential_provider'), $this->config->get('credentials') ?? []);
$apiKey = $credentials->getApikey();
$response = $this->httpClient->post('https://alttext.ai/api/v1/images', [
'headers' => ['X-API-Key' => $apiKey],
'json' => $json,
]);
if (empty($response)) {
$this->messenger->addWarning($this->t('The Alttext.AI service returned an empty response.'));
return [$current_lang => ''];
}
if ($response->getStatusCode() == 200) {
$data = json_decode($response->getBody(), TRUE);
// Return alt_texts array if available, otherwise fallback to single alt_text.
return !empty($data['alt_texts']) ? $data['alt_texts'] : [$current_lang => $data['alt_text'] ?? ''];
}
}
catch (\Exception $e) {
$this->loggerFactory->error('Alttext.AI translation error: @error', ['@error' => $e->getMessage()]);
}
return [$current_lang => ''];
}
public function getPluginId() {
return 'alttext_ai';
}
/**
* {@inheritDoc}
*/
public function getPluginDefinition() {
return [
'id' => 'azure_cognitive_services',
'title' => 'Azure Cognitive Services',
];
}
public function buildConfigurationForm() {
$form = [];
// Translation settings.
$form['translation'] = [
'#id' => 'translation',
'#type' => 'details',
'#title' => $this->t('Translation Settings'),
'#description' => $this->t('Enable automatic translation of alt text using Alttext.AI. Each additional language beyond the first consumes one translation credit per image.'),
'#open' => FALSE,
'#weight' => -10,
];
// Get all enabled languages.
$languages = $this->languageManager->getLanguages();
$language_options = [];
foreach ($languages as $langcode => $language) {
$language_options[$langcode] = $language->getName();
}
$form['translation']['alttext_ai_translation_languages'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Languages for automatic translation'),
'#description' => $this->t('Select which languages should automatically receive translated alt text from Alttext.AI. The current content language will always be included. <strong>Note:</strong> Each additional language beyond the first consumes one translation credit per image.'),
'#options' => $language_options,
'#default_value' => $this->config->get('alttext_ai_translation_languages') ?: [],
];
$form['translation']['alttext_ai_auto_populate_translations'] = [
'#type' => 'checkbox',
'#title' => $this->t('Automatically populate translations'),
'#description' => $this->t('When creating a new content translation, automatically populate image alt text with the translated version instead of copying the original.'),
'#default_value' => $this->config->get('alttext_ai_auto_populate_translations') ?: FALSE,
];
$form['translation']['translation_info'] = [
'#type' => 'markup',
'#markup' => '<p>' . $this->t('Alttext.AI natively supports translation into multiple languages. When you request alt text for an image, you can automatically receive translations. Learn more in the <a href="@link" target="_blank">Alttext.AI API documentation</a>.', [
'@link' => 'https://alttext.ai/apidocs',
]) . '</p>',
];
$form['credentials'] = [
'#id' => 'credentials',
'#type' => 'details',
'#title' => $this->t('Credentials'),
'#open' => TRUE,
'#tree' => TRUE,
];
$form['credentials']['credential_provider'] = [
'#type' => 'select',
'#title' => $this->t('Credential provider'),
'#options' => [
'config' => $this->t('Local configuration'),
],
'#default_value' => $this->config->get('credential_provider'),
];
$form['credentials']['providers'] = [
'#type' => 'item',
'#id' => 'credentials_configuration',
];
$provider_config_state = [':input[name="credentials[credential_provider]"]' => ['value' => 'config']];
$form['credentials']['providers']['config']['api_key'] = [
'#type' => 'textfield',
'#title' => $this->t('API Key (config)'),
'#description' => $this->t('The API key for @link', [
'@link' => Link::fromTextAndUrl($this->t('Alttext.AI'), Url::fromUri('https://alttext.ai/'))->toString(),
]),
'#default_value' => $this->config->get('credentials.config.api_key'),
'#states' => [
'visible' => $provider_config_state,
'required' => $provider_config_state,
],
];
if (\Drupal::moduleHandler()->moduleExists('key')) {
$form['credentials']['credential_provider']['#options']['key'] = $this->t('Key Module');
$provider_key_state = [':input[name="credentials[credential_provider]"]' => ['value' => 'key']];
$form['credentials']['providers']['key']['api_key_key'] = [
'#type' => 'key_select',
'#title' => $this->t('API Key (Key)'),
'#default_value' => $this->config->get('credentials.key.api_key_key'),
'#empty_option' => $this->t('- Please select -'),
'#key_filters' => ['type' => 'authentication'],
'#description' => $this->t('Your API key stored as a secure key.'),
'#states' => [
'visible' => $provider_key_state,
'required' => $provider_key_state,
],
];
}
else {
$form['credentials']['credential_provider']['#value'] = 'config';
$form['credentials']['credential_provider']['#disabled'] = TRUE;
}
return $form;
}
/**
* {@inheritDoc}
*/
public function validateConfigurationForm($form_state) {
$credentials = new AutoAlterCredentials();
$credential_provider = $form_state->getValue([
'credentials',
'credential_provider',
]);
$credentials_values = $form_state->getValue(['credentials', 'providers']);
$credentials->setCredentials($credential_provider, $credentials_values ?? []);
$api_key = $credentials->getApikey();
// The API key is at least 32 characters long.
if (strlen($api_key) < 32) {
$form_state->setErrorByName('api_key', $this->t('The API key is invalid.'));
}
}
/**
* {@inheritDoc}
*/
public function submitConfigurationForm($form_state, $config) {
$values = $form_state->getValues();
// Save translation settings.
if (isset($values['alttext_ai_translation_languages'])) {
$translation_languages = array_filter($values['alttext_ai_translation_languages']);
$config->set('alttext_ai_translation_languages', array_values($translation_languages));
}
$config->set('alttext_ai_auto_populate_translations', $values['alttext_ai_auto_populate_translations'] ?? FALSE);
$credential_provider = $form_state->getValue([
'credentials',
'credential_provider',
]);
$credentials = $form_state->getValue([
'credentials',
'providers',
$credential_provider,
]);
$config
->set('credential_provider', $credential_provider)
->set('credentials', [])
->set("credentials.$credential_provider", $credentials)
->set('status', $values['status'])
->set('suggestion', $values['suggestion'])
->save();
}
}
