image_to_media_swapper-2.x-dev/src/Form/ManualSwapQueueForm.php
src/Form/ManualSwapQueueForm.php
<?php
declare(strict_types=1);
namespace Drupal\image_to_media_swapper\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Url;
use Drupal\image_to_media_swapper\BatchProcessorService;
use Drupal\image_to_media_swapper\MediaSwapFormService;
use Drupal\image_to_media_swapper\MediaSwapRecordTableService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a manual review queue form for image to media swapper.
*/
final class ManualSwapQueueForm extends FormBase {
/**
* The batch processor service.
*
* @var \Drupal\image_to_media_swapper\BatchProcessorService
*/
protected BatchProcessorService $batchProcessorService;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected StateInterface $state;
/**
* The route provider service.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected RouteProviderInterface $routeProvider;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected AccountProxyInterface $currentUser;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected DateFormatterInterface $dateFormatter;
/**
* The media swap form service.
*
* @var \Drupal\image_to_media_swapper\MediaSwapFormService
*/
protected MediaSwapFormService $mediaSwapFormService;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected ModuleHandlerInterface $moduleHandler;
/**
* Constructs the form.
*/
public function __construct(
RouteProviderInterface $routeProvider,
EntityTypeManagerInterface $entityTypeManager,
BatchProcessorService $batchProcessorService,
StateInterface $state,
private readonly MediaSwapRecordTableService $tableService,
AccountProxyInterface $currentUser,
DateFormatterInterface $dateFormatter,
MediaSwapFormService $mediaSwapFormService,
ModuleHandlerInterface $moduleHandler,
) {
$this->batchProcessorService = $batchProcessorService;
$this->entityTypeManager = $entityTypeManager;
$this->state = $state;
$this->routeProvider = $routeProvider;
$this->currentUser = $currentUser;
$this->dateFormatter = $dateFormatter;
$this->mediaSwapFormService = $mediaSwapFormService;
$this->moduleHandler = $moduleHandler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): self {
/** @var \Drupal\Core\Routing\RouteProviderInterface $routeProvider */
$routeProvider = $container->get('router.route_provider');
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
$entityTypeManager = $container->get('entity_type.manager');
/** @var \Drupal\image_to_media_swapper\BatchProcessorService $batchProcessorService */
$batchProcessorService = $container->get('image_to_media_swapper.batch_processor_service');
/** @var \Drupal\Core\State\StateInterface $drupalState */
$drupalState = $container->get('state');
/** @var \Drupal\image_to_media_swapper\MediaSwapRecordTableService $tableService */
$tableService = $container->get('image_to_media_swapper.table_service');
/** @var \Drupal\Core\Session\AccountProxyInterface $currentUser */
$currentUser = $container->get('current_user');
/** @var \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter */
$dateFormatter = $container->get('date.formatter');
/** @var \Drupal\image_to_media_swapper\MediaSwapFormService $mediaSwapFormService */
$mediaSwapFormService = $container->get('image_to_media_swapper.form_service');
/** @var \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler */
$moduleHandler = $container->get('module_handler');
return new self(
$routeProvider,
$entityTypeManager,
$batchProcessorService,
$drupalState,
$tableService,
$currentUser,
$dateFormatter,
$mediaSwapFormService,
$moduleHandler,
);
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'image_to_media_manual_swap_queue_form';
}
/**
* {@inheritdoc}
*
* @throws \Exception
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$form['description'] = [
'#markup' => $this->t('This form allows you to scan content for images and file links, then manually review and process each item individually.'),
];
$form['scan_options'] = [
'#type' => 'details',
'#title' => $this->t('Scan Options'),
'#open' => TRUE,
];
$form['scan_options']['description'] = [
'#markup' => $this->t('<p>Select text fields to scan for images and file links. Items will be added to a queue for manual review.</p>'),
];
// Get fields with images.
$imageFields = $this->mediaSwapFormService->getFieldsWithImages();
if (!empty($imageFields)) {
$form['scan_options']['image_selector'] = [
'#type' => 'select',
'#title' => $this->t('Text field with <img> tags to queue'),
'#options' => ['' => $this->t('- None -')] + $imageFields,
'#description' => $this->t('Select a field containing <img> tags to add to the manual review queue.'),
];
}
// Check if the module linkit is installed.
if ($this->moduleHandler->moduleExists('linkit')) {
// Get fields with file links.
$linkFields = $this->mediaSwapFormService->getFieldsWithFileLinks();
if (!empty($linkFields)) {
$form['scan_options']['link_selector'] = [
'#type' => 'select',
'#title' => $this->t('Text field with file links to queue'),
'#options' => ['' => $this->t('- None -')] + $linkFields,
'#description' => $this->t('Select a field containing <a> tags linking to files to add to the manual review queue.'),
];
}
}
else {
$linkit_url = Url::fromUri('https://www.drupal.org/project/linkit');
$form['scan_options']['link_selector'] = [
'#markup' => $this->t('<p><em>The @linkit module is not enabled, so scanning for file links is unavailable.</em></p>', [
'@linkit' => Link::fromTextAndUrl('Linkit', $linkit_url)
->toString(),
]
),
];
}
if (empty($imageFields) && empty($linkFields)) {
$form['scan_options']['no_fields'] = [
'#markup' => $this->t('<div class="messages messages--status">No text fields with images or file links were found.</div>'),
];
}
else {
$form['scan_options']['actions'] = [
'#type' => 'actions',
'submit' => [
'#type' => 'submit',
'#value' => $this->t('Scan and add to queue'),
'#submit' => ['::submitScanForm'],
'#button_type' => 'primary',
],
];
}
// Display pending items that need review.
$form['pending_items'] = [
'#type' => 'details',
'#title' => $this->t('Pending Items'),
'#open' => TRUE,
];
// Count total pending items.
$pendingCount = $this->mediaSwapFormService->getPendingItemsCount();
if ($pendingCount > 0) {
$form['pending_items']['info'] = [
'#markup' => $this->t('<p>There are @count items pending review. Process them one by one to convert images and file links to media entities.</p>', [
'@count' => $pendingCount,
]),
];
$form['pending_items']['items'] = $this->tableService->buildTable('-14 day', 20, ['pending']);
}
else {
$form['pending_items']['info'] = [
'#markup' => $this->t('<p>There are no items pending review.</p>'),
];
}
// Show recent queue items.
$form['recent_items'] = $this->tableService->buildTable('-14 day', 20);
$form['recent_items']['footer'] = [
'#markup' => Link::createFromRoute($this->t('View all records'),
'entity.media_swap_record.collection')->toString(),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
parent::validateForm($form, $form_state);
// Only check if this is the scan form submission.
if ($form_state->getTriggeringElement()['#submit'][0] === '::submitScanForm') {
$image_field = $form_state->getValue('image_selector');
$link_field = $form_state->getValue('link_selector');
// At least one field must be selected.
if (empty($image_field) && empty($link_field)) {
$form_state->setErrorByName('image_selector', $this->t('Please select at least one field to scan.'));
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
// Main submit handler is empty since we use specific submit handlers.
}
/**
* Submit handler for the scan form.
*/
public function submitScanForm(array &$form, FormStateInterface $form_state): void {
$image_field = $form_state->getValue('image_selector');
$link_field = $form_state->getValue('link_selector');
$totalAdded = 0;
// Process image field if selected.
if (!empty($image_field)) {
$added = $this->mediaSwapFormService->createPendingRecordsForField($image_field, 'images');
$totalAdded += $added;
$this->messenger()
->addStatus($this->t('Added @count entities with images to the review queue.', [
'@count' => $added,
]));
}
// Process link field if selected.
if (!empty($link_field)) {
$added = $this->mediaSwapFormService->createPendingRecordsForField($link_field, 'links');
$totalAdded += $added;
$this->messenger()
->addStatus($this->t('Added @count entities with file links to the review queue.', [
'@count' => $added,
]));
}
if ($totalAdded === 0) {
$this->messenger()
->addWarning($this->t('No new items were found to add to the queue.'));
}
}
/**
* Submit handler for the process all form.
*/
public function submitProcessAllForm(array &$form, FormStateInterface $form_state): void {
// Set up a batch process to handle all pending items.
$pendingRecords = $this->mediaSwapFormService->getPendingRecords();
if (empty($pendingRecords)) {
$this->messenger()->addStatus($this->t('No pending items to process.'));
return;
}
$operations = [];
foreach ($pendingRecords as $record) {
$operations[] = [
['\Drupal\image_to_media_swapper\BatchHandler', 'processRecord'],
[$record->id()],
];
}
$batch = [
'title' => $this->t('Processing queued items...'),
'operations' => $operations,
'finished' => [
'\Drupal\image_to_media_swapper\BatchHandler',
'batchFinished',
],
'init_message' => $this->t('Starting to process queued items...'),
'progress_message' => $this->t('Processed @current out of @total.'),
'error_message' => $this->t('An error occurred during processing.'),
];
batch_set($batch);
}
/**
* Submit handler for the recheck form.
*/
public function submitRecheckForm(array &$form, FormStateInterface $form_state): void {
$count = $this->mediaSwapFormService->recheckProcessedItems();
$this->messenger()
->addStatus($this->t('Rechecked @count items and updated their status.', [
'@count' => $count,
]));
}
}
