From ecaed82a41d47c3a6f75edd3dfeb9a8e859381e8 Mon Sep 17 00:00:00 2001 From: Steve Wirt <Steve Wirt> Date: Mon, 27 Jan 2025 21:58:40 -0500 Subject: [PATCH] Issue #3498465: Add cron option to rebuild the alt text audit --- README.md | 23 +++++-- alt_text_validation.module | 9 +++ alt_text_validation.services.yml | 1 + src/Form/AltTextValidationSettingsForm.php | 54 ++++++++++++---- src/Service/Auditor.php | 71 ++++++++++++++++++++++ src/Service/AuditorInterface.php | 5 ++ 6 files changed, 146 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 17d0807..2961b99 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -This module is not functional yet. It is only here for contributors to work on it. +**This module is not functional yet.** It is only here for contributors to work on it. ## Introduction @@ -32,18 +32,29 @@ See: https://www.drupal.org/node/895232 for further information. ## Configuration -Go to /admin/config/content/alt-text-validation and configure. +Configuration steps are minimal: + + 1. Go to /admin/config/content/alt-text-validation and configure. + 2. Assign permissions for administering the module and viewing the report to Roles. + ## Report -The Alt Text Audit report is present at Reports >> Alt Text Report (/admin/reports/alt-text-report) for properly permed users. This report is a View that can be modified if needed. It requires the Auditor job be run to populate the report. **The Auditor is not fully complete. It currently only shows image data from fields of type 'image'.** -To populate the report you will need to run +The Alt Text Audit report is present at Reports >> Alt Text Report (/admin/reports/alt-text-report) for properly permed users. This report is a View that can be modified if needed. It requires the Auditor job be run to populate the report. The audit report will +show any occurrences of image fields on any entity. It also includes any html image tags found within text fields on any entity. + +There are currently two ways to initialize the report generation. Depending on the +size of your site, it may be an intensive process. +To populate the report you will need to either: + +1. Enable report generation in cron (/admin/reports/alt-text-report) - It will begin the generation with the next cron run. +2. Via Drush command. ``` drush alt-text-validation:queue-audit drush cron ``` -It may take several cron runs to complete the audit. You can assess the progress by -`drush queue:list` then look for `atv_entity_instances` +It uses the Queue API and may take several cron runs to fully populate the report. You can assess the progress by `drush queue:list` then look for `atv_entity_instances` +The top of the report contains information about when the report was started and finished. ## Drush Commands diff --git a/alt_text_validation.module b/alt_text_validation.module index e626077..33b5db8 100644 --- a/alt_text_validation.module +++ b/alt_text_validation.module @@ -27,3 +27,12 @@ function alt_text_validation_help($route_name, RouteMatchInterface $route_match) } return NULL; } + +/** + * Implements hook_cron(). + */ +function alt_text_validation_cron() { + /** @var \Drupal\alt_text_validation\Service\AuditorInterface $auditor */ + $auditor = \Drupal::service('alt_text_validation.auditor'); + $auditor->tryCron(); +} diff --git a/alt_text_validation.services.yml b/alt_text_validation.services.yml index ce927be..f8f1884 100644 --- a/alt_text_validation.services.yml +++ b/alt_text_validation.services.yml @@ -11,6 +11,7 @@ services: class: Drupal\alt_text_validation\Service\Auditor arguments: - '@alt_text_validation.audit_service' + - '@config.factory' - '@database' - '@entity_field.manager' - '@entity_type.manager' diff --git a/src/Form/AltTextValidationSettingsForm.php b/src/Form/AltTextValidationSettingsForm.php index 05d578e..1c170c8 100644 --- a/src/Form/AltTextValidationSettingsForm.php +++ b/src/Form/AltTextValidationSettingsForm.php @@ -14,7 +14,7 @@ class AltTextValidationSettingsForm extends ConfigFormBase { * {@inheritdoc} */ public function getFormId() { - return 'alt_text_validation_settings_form'; + return 'alt_text_validation_enabled_form'; } /** @@ -30,30 +30,60 @@ class AltTextValidationSettingsForm extends ConfigFormBase { public function buildForm(array $form, FormStateInterface $form_state) { $form = parent::buildForm($form, $form_state); $form['#title'] = $this->t('Alt Text Validation Settings'); - $form['alt_text_validation_setting_group'] = [ + $form['alt_text_validation_enabled_group'] = [ '#type' => 'fieldset', - '#title' => $this->t('Validation status'), ]; - $form['alt_text_validation_setting_group']['alt_text_validation_setting'] = [ + $form['alt_text_validation_enabled_group']['alt_text_validation_enabled'] = [ '#prefix' => '<div class="container-inline">', + '#title' => $this->t('Validate alt text on save'), '#type' => 'radios', '#options' => [ - 'validation_on' => $this->t('On (all active rules applied)'), - 'validation_off' => $this->t('Off (no rules applied)'), + '1' => $this->t('On (all active rules applied)'), + '0' => $this->t('Off (no rules applied)'), ], - '#default_value' => $this->config('alt_text_validation.settings')->get('alt_text_validation_setting'), + '#default_value' => $this->config('alt_text_validation.settings')->get('alt_text_validation_enabled') ?? 0, '#suffix' => '</div>', ]; - $form['alt_text_validation_setting_group_alert headings'] = [ + $form['alt_text_validation_enabled_group']['cron_enabled'] = [ + '#prefix' => '<div class="container-inline">', + '#title' => $this->t('Rebuild alt text report on cron'), + '#type' => 'radios', + '#options' => [ + '1' => $this->t('On'), + '0' => $this->t('Off'), + ], + '#default_value' => $this->config('alt_text_validation.settings')->get('cron_enabled') ?? 0, + '#suffix' => '</div>', + ]; + $form['alt_text_validation_enabled_group']['cron_delay'] = [ + '#prefix' => '<div class="container-inline">', + '#title' => $this->t('Days between rebuild'), + '#type' => 'number', + '#attributes' => [ + 'data-type' => 'number', + ], + '#size' => 3, + '#max' => 365, + '#min' => 1, + '#default_value' => $this->config('alt_text_validation.settings')->get('cron_delay') ?? 7, + '#suffix' => '</div>', + '#states' => [ + // Show this textfield only if the radio '1' is selected above. + 'visible' => [ + ':input[name="cron_enabled"]' => ['value' => '1'], + ], + ], + ]; + $form['alt_text_validation_enabled_group_alert headings'] = [ '#type' => 'fieldset', '#title' => $this->t('Customize alert headings'), ]; - $form['alt_text_validation_setting_group_alert headings']['heading_warn'] = [ + $form['alt_text_validation_enabled_group_alert headings']['heading_warn'] = [ '#type' => 'textfield', '#title' => $this->t('Warn heading text'), '#default_value' => $this->config('alt_text_validation.settings')->get('heading_warn') ? $this->config('alt_text_validation.settings')->get('heading_warn') : $this->t('Alt text warnings, please consider addressing them.'), ]; - $form['alt_text_validation_setting_group_alert headings']['heading_prevent'] = [ + $form['alt_text_validation_enabled_group_alert headings']['heading_prevent'] = [ '#type' => 'textfield', '#title' => $this->t('Prevent heading text'), '#default_value' => $this->config('alt_text_validation.settings')->get('heading_prevent') ? $this->config('alt_text_validation.settings')->get('heading_prevent') : $this->t('Alt text errors found. Page can not be saved.'), @@ -66,7 +96,9 @@ class AltTextValidationSettingsForm extends ConfigFormBase { */ public function submitForm(array &$form, FormStateInterface $form_state) { $config = $this->configFactory()->getEditable('alt_text_validation.settings'); - $config->set('alt_text_validation_setting', $form_state->getValue('alt_text_validation_setting')) + $config->set('alt_text_validation_enabled', $form_state->getValue('alt_text_validation_enabled')) + ->set('cron_enabled', $form_state->getValue('cron_enabled')) + ->set('cron_delay', $form_state->getValue('cron_delay')) ->set('heading_warn', $form_state->getValue('heading_warn')) ->set('heading_prevent', $form_state->getValue('heading_prevent')) ->save(); diff --git a/src/Service/Auditor.php b/src/Service/Auditor.php index 81aae94..9a6c476 100644 --- a/src/Service/Auditor.php +++ b/src/Service/Auditor.php @@ -3,6 +3,7 @@ namespace Drupal\alt_text_validation\Service; use Drupal\alt_text_validation\AtvCommonTrait; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Database\Connection; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; @@ -17,9 +18,17 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * Class for Auditor to process and build the audit. */ class Auditor implements AuditorInterface, ContainerInjectionInterface { + use AtvCommonTrait; use StringTranslationTrait; + /** + * The configuration for alt_text_validation. + * + * @var \Drupal\Core\Config\Config + */ + protected $atvConfig; + /** * The Alt Text Validation audit storage service. * @@ -74,6 +83,8 @@ class Auditor implements AuditorInterface, ContainerInjectionInterface { * * @param Drupal\alt_text_validation\Service\AuditStorageInterface $audit_storage * The audit storage service. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. * @param Drupal\Core\Database\Connection $database_connection * The database connection service. * @param Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager @@ -89,6 +100,7 @@ class Auditor implements AuditorInterface, ContainerInjectionInterface { */ final public function __construct( AuditStorageInterface $audit_storage, + ConfigFactoryInterface $config_factory, Connection $database_connection, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, @@ -97,6 +109,7 @@ class Auditor implements AuditorInterface, ContainerInjectionInterface { State $state, ) { $this->auditStorage = $audit_storage; + $this->atvConfig = $config_factory->getEditable('alt_text_validation.settings'); $this->databaseConnection = $database_connection; $this->entityFieldManager = $entity_field_manager; $this->entityTypeManager = $entity_type_manager; @@ -111,6 +124,7 @@ class Auditor implements AuditorInterface, ContainerInjectionInterface { public static function create(ContainerInterface $container) { return new static( $container->get('alt_text_validation.audit_storage'), + $container->get('config.factory'), $container->get('database'), $container->get('entity_field.manager'), $container->get('entity_type.manager'), @@ -186,4 +200,61 @@ class Auditor implements AuditorInterface, ContainerInjectionInterface { $this->queue->get(self::getEntityInstanceQueueName())->deleteQueue(); } + /** + * {@inheritdoc} + */ + public function tryCron(): void { + // Check settings to see if cron processing is enabled. + if (!empty($this->atvConfig->get('cron_enabled'))) { + // Check the last completed time. + $times = $this->state->getMultiple([ + self::getAuditStartTimeKey(), + self::getAuditEndTimeKey(), + ]); + $running = !empty($times[self::getAuditStartTimeKey()]) && empty($times[self::getAuditEndTimeKey()]); + $delay_is_right = $this->checkDelay($times[self::getAuditEndTimeKey()]); + if ((!$running && $delay_is_right) || $this->checkTooLong($times[self::getAuditStartTimeKey()])) { + $this->queueAllImages(); + } + } + } + + /** + * Checks if the delay between audits is sufficient. + * + * @param int|null $last_end_time + * The timestamp of the last completed audit. + * + * @return bool + * TRUE if the delay was sufficient, FALSE otherwise. + */ + protected function checkDelay(int|null $last_end_time): bool { + if (empty($last_end_time)) { + // It was not last run, so it is ready to run. + return TRUE; + } + $delay = $this->atvConfig->get('audit_delay') * 24 * 60 * 60; + return (time() - $last_end_time) >= $delay; + } + + /** + * Checks if it has been too long since run started, or never run. + * + * @param int|null $last_start_time + * The timestamp of the last completed audit. + * + * @return bool + * TRUE if the delay was sufficient, FALSE otherwise. + */ + protected function checkTooLong(int|null $last_start_time): bool { + if (empty($last_start_time)) { + // It has never run, so it is ready to run. + return TRUE; + } + // We will consider an additional 2 days beyond should mean something is + // stuck so run this again. + $delay = ($this->atvConfig->get('audit_delay') + 2) * 24 * 60 * 60; + return (time() - ($last_start_time)) >= $delay; + } + } diff --git a/src/Service/AuditorInterface.php b/src/Service/AuditorInterface.php index c97ac8b..38f44bf 100644 --- a/src/Service/AuditorInterface.php +++ b/src/Service/AuditorInterface.php @@ -12,4 +12,9 @@ interface AuditorInterface { */ public function queueAllImages(): void; + /** + * Checks to see if it is appropriate to run cron. + */ + public function tryCron(): void; + } -- GitLab