auto_alter-8.x-1.x-dev/auto_alter.module
auto_alter.module
<?php
/**
* @file
* Contains auto_alter.module.
*/
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Url;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\file\Entity\File;
use Masterminds\HTML5;
/**
* Implements hook_modules_installed().
*/
function auto_alter_modules_installed($modules) {
if (in_array('auto_alter', $modules)) {
\Drupal::messenger()->addStatus(t('API key and Endpoint URL is not set and it is required for the module to work. Please set it up at the <a href=":settings">Image Automatic Alternative Text settings page</a>.', [
':settings' => Url::fromRoute('auto_alter.settings')->toString(),
]));
}
}
/**
* Implements hook_entity_presave().
*/
function auto_alter_entity_presave(EntityInterface $entity) {
// Handle translation population if enabled.
$config = \Drupal::config('auto_alter.settings');
// Check if entity is translatable before calling translation methods.
if ($config->get('alttext_ai_auto_populate_translations')
&& $entity instanceof ContentEntityInterface
&& $entity->isTranslatable()
&& !$entity->isDefaultTranslation()) {
$target_language = $entity->language()->getId();
$tempstore = \Drupal::service('tempstore.private')->get('auto_alter');
$descriptions = $tempstore->get('last_descriptions');
if ($descriptions && is_array($descriptions) && isset($descriptions[$target_language])) {
$entity_manager = \Drupal::service('entity_field.manager');
foreach ($entity_manager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_name => $field_definition) {
if ($field_definition->getType() === 'image') {
$field_items = $entity->get($field_name);
foreach ($field_items as $delta => $item) {
$current_alt = $item->alt ?? '';
$source_alt = $entity->getUntranslated()->get($field_name)->get($delta)->alt ?? '';
if (empty($current_alt) || $current_alt === $source_alt) {
$item->alt = $descriptions[$target_language];
}
}
}
}
}
return;
}
$suggestion = \Drupal::config('auto_alter.settings')->get('suggestion');
if (empty($suggestion)) {
return;
}
if (empty($suggestion)) {
return;
}
if ($entity instanceof ContentEntityInterface) {
$entity_manager = \Drupal::service('entity_field.manager');
foreach ($entity_manager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_name => $field_definition) {
if (!empty($field_definition->getTargetBundle())) {
if ($field_definition->getType() == 'image') {
$images = $entity->get($field_name);
foreach ($images as $key => $image) {
if (empty($image->alt)) {
$images[$key]->alt = auto_alter_get_description($image->target_id);
}
}
}
}
if ($field_definition->getType() == 'text_with_summary') {
$texts = $entity->get($field_name);
$changed = false;
foreach ($texts as $key => $text) {
$raw = $text->getValue();
$html5 = new HTML5(['disable_html_ns' => TRUE]);
$dom = $html5->loadHTML($raw['value']);
foreach($dom->getElementsByTagName('img') as $imgs) {
$nodes = [];
foreach($imgs as $img) {
$nodes['alt'] = $imgs->getAttribute('alt');
$nodes['src'] = $imgs->getAttribute('src');
$nodes['data-entity-uuid'] = $imgs->getAttribute('data-entity-uuid');
}
if (empty($nodes['alt'])) {
$changed = true;
$file = \Drupal::service('entity.repository')->loadEntityByUuid('file', $nodes['data-entity-uuid']);
if ($file instanceof File) {
$fid = $file->id();
$mime = $file->getMimeType();
if (strpos($mime,'image/') !== false) {
$description = auto_alter_get_description($file->id());
$raw['value'] = str_replace('data-entity-uuid="' . $nodes['data-entity-uuid'] . '"',
'data-entity-uuid="' . $nodes['data-entity-uuid'] . '" alt="' . $description . '"', $raw['value']);
}
}
}
}
$text->value = $raw['value'];
$texts[$key] = $text;
}
if ($changed) {
$entity->{$field_name} = $texts;
}
}
}
}
}
/**
* Implements hook_form_alter().
*/
function auto_alter_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$suggestion = \Drupal::config('auto_alter.settings')->get('suggestion');
if ($suggestion) {
if (isset($form['#entity_builders'])) {
$entity = $form_state->getFormObject()->getEntity();
$entity_manager = \Drupal::service('entity_field.manager');
if (!($entity instanceof FieldableEntityInterface)) {
return;
}
foreach ($entity_manager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_name => $field_definition) {
if (!empty($field_definition->getTargetBundle())) {
if ($field_definition->getType() == 'image') {
$images = $entity->get($field_name);
foreach ($images as $key => $image) {
$entity_manager = \Drupal::entityTypeManager();
if (isset($form[$field_name]['widget'][$key]) && $form[$field_name]['widget'][$key]['#alt_field'] && isset($form[$field_name]['widget'][$key]['#default_value']['alt']) && empty($form[$field_name]['widget'][$key]['#default_value']['alt'])) {
$form[$field_name]['widget'][$key]['#default_value']['alt'] = auto_alter_get_description($form[$field_name]['widget'][$key]['#default_value']['fids'][0]);
}
}
}
}
}
}
elseif ($form_id === 'media_directories_add_form' || $form_id === 'media_library_add_form_dropzonejs') {
$entity_type = 'media';
if (!empty($form_state->getStorage()[$entity_type])) {
$entity = $form_state->getStorage()[$entity_type][0];
$entity_manager = \Drupal::service('entity_field.manager');
if (!($entity instanceof FieldableEntityInterface)) {
return;
}
foreach ($entity_manager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_name => $field_definition) {
if (!empty($field_definition->getTargetBundle())) {
if ($field_definition->getType() === 'image') {
$images = $entity->get($field_name);
foreach ($images as $key => $image) {
if (isset($form['media'][0]['fields'][$field_name]['widget'][$key]) && $form['media'][0]['fields'][$field_name]['widget'][$key]['#alt_field'] && empty($form['media'][0]['fields'][$field_name]['widget'][$key]['#default_value']['alt'])) {
$form['media'][0]['fields'][$field_name]['widget'][$key]['#default_value']['alt'] = auto_alter_get_description($form['media'][0]['fields'][$field_name]['widget'][$key]['#default_value']['fids'][0]);
}
}
}
}
}
}
}
elseif ($form_id === 'media_library_add_form_upload') {
$triggering_element = $form_state->getTriggeringElement();
if (!empty($triggering_element) && $triggering_element['#name'] === 'upload_upload_button' && $form_state->getValue('upload')) {
$entity_type = 'media';
if (!empty($form_state->getStorage()[$entity_type])) {
$entity = $form_state->getStorage()[$entity_type][0];
$entity_manager = \Drupal::service('entity_field.manager');
if (!($entity instanceof FieldableEntityInterface)) {
return;
}
foreach ($entity_manager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_name => $field_definition) {
if (!empty($field_definition->getTargetBundle())) {
if ($field_definition->getType() === 'image') {
$images = $entity->get($field_name);
foreach ($images as $key => $image) {
if (isset($form['media'][0]['fields'][$field_name]['widget'][$key]) && $form['media'][0]['fields'][$field_name]['widget'][$key]['#alt_field'] && empty($form['media'][0]['fields'][$field_name]['widget'][$key]['#default_value']['alt'])) {
$form['media'][0]['fields'][$field_name]['widget'][$key]['#default_value']['alt'] = auto_alter_get_description($form['media'][0]['fields'][$field_name]['widget'][$key]['#default_value']['fids'][0]);
}
}
}
}
}
}
}
}
}
if (isset ($form_id) && $form_id == 'editor_image_dialog' ) {
//Set a wrapper for the ajax petition.
$form['suggestion'] = array(
'#type' => 'button',
'#value' => t('Get suggestion'),
'#limit_validation_errors' => [],
);
$fid = $form_state->getValue('fid');
if ($fid != null && (isset($fid[0]) || isset($fid['fids'][0]))) {
// $form['suggestion']['#attributes']['disabled'] = FALSE;
}
else {
// $form['suggestion']['#attributes']['disabled'] = TRUE;
}
$form['suggestion']['#prefix'] = '<div id="edit-alternative">';
$form['suggestion']['#suffix'] = '</div>';
$form['suggestion']['#ajax'] = [
'callback' => 'getAlternativeText',
'event' => 'click',
'wrapper' => 'editor-image-dialog-form',
];
$form['suggestion']['#weight'] = -5;
$form['attributes']['#weight'] = -10;
$form['fid']['#weight'] = -15;
/*
$form['attributes']['alt']['#ajax'] = [
'callback' => 'disableButton',
'event' => 'change',
'wrapper' => 'edit-alternative',
];
*/
}
}
/**
* @param array $form
* @param FormStateInterface $form_state
* @return AjaxResponse
*/
function getAlternativeText(array &$form, FormStateInterface $form_state) {
$fid = $form_state->getValue('fid');
if ($fid != null && isset($fid[0])) {
if (empty($form['attributes']['alt']['#value'])) {
$form['attributes']['alt']['#value'] = auto_alter_get_description($fid[0]);
}
}
elseif ($fid != null && isset($fid['fids'][0])) {
$form['attributes']['alt']['#value'] = auto_alter_get_description($fid['fids'][0]);
}
return $form;
}
function disableButton(array &$form, FormStateInterface $form_state) {
if (!empty($form['attributes']['alt']['#value'])) {
$form['suggestion']['#attributes']['disabled'] = TRUE;
}
else {
$form['suggestion']['#attributes']['disabled'] = FALSE;
}
return $form['suggestion'];
}
/**
* Implements auto_alter_get_description($fid).
*
* @param int $fid
* File ID.
*/
function auto_alter_get_description($fid) {
$engine = auto_alter_get_engine();
if ($engine->checkSetup() === FALSE) {
\Drupal::messenger()->addStatus(
t('The selected AI engine is not set and it is required for the module to work. Please set it up at the <a href=":settings">Image Automatic Alternative Text settings page</a>.', [
':settings' => Url::fromRoute('auto_alter.settings')->toString(),
])
);
return '';
}
$entity_manager = \Drupal::entityTypeManager();
$file = $entity_manager->getStorage('file')->load($fid);
$uri = $engine->getUri($file);
$alternate_text = auto_alter_get_description_by_uri($uri);
return $alternate_text;
}
function auto_alter_get_description_by_uri($uri) {
$engine = auto_alter_get_engine();
$status = \Drupal::config('auto_alter.settings')->get('status');
$path = \Drupal::service('file_system')->realpath($uri);
$path = $path ? $path : $uri;
$description = $engine->getDescription($path);
if (!empty($description)) {
$alternate_text = $description;
$moduleHandler = \Drupal::service('module_handler');
if ($moduleHandler->moduleExists('auto_alter_translate')) {
$active = \Drupal::config('auto_alter_translate.settings')->get('active');
$engine = \Drupal::configFactory()->getEditable('auto_alter.settings')->get('engine');
$region = \Drupal::config('auto_alter_translate.settings')
->get('region');
if ($active && $engine == 'azure_cognitive_services') {
$request = \Drupal::service('auto_alter_translate.get_translation')->gettranslation($alternate_text, $region);
if (!empty($request) && $request->getStatusCode() == 200) {
$trans = (array) json_decode($request->getBody());
$alternate_text = $trans[0]->translations[0]->text;
}
}
}
if ($status) {
\Drupal::messenger()->addStatus(t('Alternate text has been changed to: %text"', ['%text' => $alternate_text]));
}
return $alternate_text;
}
else {
if ($status) {
\Drupal::messenger()->addWarning(t('Automatic generation of Alternate text has failed'));
}
return '';
}
}
/**
* Returns the currently selected engine.
*
* @return \Drupal\auto_alter\DescribeImageServiceInterface
* The currently selected engine.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
function auto_alter_get_engine() {
$engine = \Drupal::config('auto_alter.settings')->get('engine');
/** @var \Drupal\auto_alter\Plugin\AutoAlterDescribeImagePluginManager $plugin_manager */
$plugin_manager = \Drupal::service('plugin.manager.auto_alter_describe_image');
/** @var \Drupal\auto_alter\DescribeImageServiceInterface $plugin */
$plugin = $plugin_manager->createInstance($engine);
return $plugin;
}
/**
* Implements hook_entity_translation_create().
*/
function auto_alter_entity_translation_create(\Drupal\Core\Entity\EntityInterface $translation) {
// Check if auto-populate translations is enabled.
$config = \Drupal::config('auto_alter.settings');
if (!$config->get('alttext_ai_auto_populate_translations')) {
return;
}
// Only process content entities that are translatable.
if (!($translation instanceof \Drupal\Core\Entity\ContentEntityInterface) || !$translation->isTranslatable()) {
return;
}
// Only process if this is a new translation (not the source).
if ($translation->isDefaultTranslation()) {
return;
}
$target_language = $translation->language()->getId();
// Get all image fields on this entity.
$image_fields = [];
foreach ($translation->getFieldDefinitions() as $field_name => $field_definition) {
if ($field_definition->getType() === 'image') {
$image_fields[] = $field_name;
}
}
if (empty($image_fields)) {
return;
}
// Process each image field.
foreach ($image_fields as $field_name) {
if (!$translation->hasField($field_name)) {
continue;
}
$field_items = $translation->get($field_name);
foreach ($field_items as $delta => $item) {
// Get the source alt text.
$source_alt = $translation->getUntranslated()->get($field_name)->get($delta)->alt ?? '';
if (empty($source_alt)) {
continue;
}
// Get the file entity.
$file = $item->entity;
if (!$file) {
continue;
}
// Try to get translated alt text.
$translated_alt = auto_alter_get_translated_alt($file, $target_language);
if ($translated_alt) {
$item->alt = $translated_alt;
}
}
}
}
/**
* Get translated alt text for a file.
*
* @param \Drupal\file\Entity\File $file
* The file entity.
* @param string $target_language
* The target language code.
*
* @return string|null
* The translated alt text, or NULL if not available.
*/
function auto_alter_get_translated_alt($file, $target_language) {
try {
$engine = auto_alter_get_engine();
// Check if engine supports multi-language.
if (!method_exists($engine, 'getDescriptions')) {
return NULL;
}
$uri = $engine->getUri($file);
$path = \Drupal::service('file_system')->realpath($uri);
$path = $path ? $path : $uri;
// Get all descriptions.
$descriptions = $engine->getDescriptions($path);
// Return the translation for target language if available.
return $descriptions[$target_language] ?? NULL;
}
catch (\Exception $e) {
\Drupal::logger('auto_alter')->error('Error getting translated alt text: @error', [
'@error' => $e->getMessage(),
]);
return NULL;
}
}
