image_to_media_swapper-2.x-dev/src/MediaSwapRecordTableService.php
src/MediaSwapRecordTableService.php
<?php
declare(strict_types=1);
namespace Drupal\image_to_media_swapper;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Link;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\file\FileInterface;
use Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface;
use Drupal\media\MediaInterface;
/**
* Service for building consistent MediaSwapRecord tables.
*/
final class MediaSwapRecordTableService {
use StringTranslationTrait;
/**
* The logger service.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
private LoggerChannelInterface $logger;
public function __construct(
private readonly EntityTypeManagerInterface $entityTypeManager,
private readonly Connection $connection,
private readonly DateFormatterInterface $dateFormatter,
private readonly ConfigFactoryInterface $configFactory,
private readonly LoggerChannelFactoryInterface $loggerFactory,
private readonly FileUrlGeneratorInterface $fileUrlGenerator,
) {
// Ensure the logger is initialized.
$this->logger = $this->loggerFactory->get('image_to_media_swapper');
}
/**
* Builds a table of MediaSwapRecord entities.
*
* @param string|null $from_time
* Optional timestamp to filter records created after this time.
* @param int $items_per_page
* Number of items to show per page. Defaults to 50.
* @param array $processing_status
* Optional array of processing statuses to filter by.
*
* @return array
* Render array for the table of media swap records.
*
* @throws \Exception
*/
public function buildTable(?string $from_time = NULL, int $items_per_page = 50, array $processing_status = []): array {
if ($from_time !== NULL) {
// Get the site's default timezone.
$time_zone = $this->configFactory->get('system.date')
->get('timezone.default');
// Get the date formatter
// Convert the provided time to the site's timezone.
$from_time_string = $this->dateFormatter
->format(strtotime($from_time), 'medium', 'short', $time_zone);
}
else {
$from_time_string = $this->t('All time');
}
$header = $this->buildHeader();
$output = [
'#type' => 'details',
'#title' => $this->t('Conversion Results @time', [
'@time' => $this->t('since @time', [
'@time' => $from_time_string,
]),
]),
'#open' => TRUE,
'#attributes' => ['class' => ['file-to-media-swapper-results']],
];
// Get the unique field selectors in the media_swap_records table.
$field_selectors_query = $this->connection
->select('media_swap_record', 'msr')
->fields('msr', ['field_selector'])
->groupBy('field_selector');
if (!empty($processing_status)) {
$field_selectors_query->condition('processing_status', $processing_status, 'IN');
}
$field_selectors = $field_selectors_query->execute()
->fetchAll(\PDO::FETCH_COLUMN);
foreach ($field_selectors as $field_selector) {
$swapRecordStorage = $this->entityTypeManager->getStorage('media_swap_record');
$query = $swapRecordStorage->getQuery()
->accessCheck(TRUE)
->sort('created', 'DESC')
->condition('field_selector', $field_selector, '=');
if (!empty($processing_status)) {
$query->condition('processing_status', $processing_status, 'IN');
}
$element_id = count($output);
$pager_id = 'pager_' . $field_selector;
if ($items_per_page > 0) {
$query->pager($items_per_page, $element_id, $pager_id);
}
if ($from_time !== NULL) {
$query->condition('created', strtotime($from_time), '>=');
}
$swapRecordIds = $query->execute();
if (!empty($swapRecordIds)) {
/** @var \Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface[] $swapRecords */
$records = $swapRecordStorage->loadMultiple($swapRecordIds);
if (!empty($records)) {
$rows = [];
/** @var \Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface $record */
foreach ($records as $record) {
$rows[] = $this->buildRow($record);
}
$output[$field_selector] = [
'#type' => 'details',
'#attributes' => ['class' => ['image-to-media-swapper-result-wrapper']],
'#title' => $this->t('Results for @field (@count records)', [
'@field' => $field_selector,
'@count' => count($records),
]),
];
$output[$field_selector]['table'] = [
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
];
}
}
// Add pager to the output if there are any records.
$output[$field_selector]['pager'] = [
'#type' => 'pager',
'#element' => $element_id,
'#id' => $pager_id,
'#weight' => 100,
];
}
return $output;
}
/**
* Builds a table header for MediaSwapRecord displays.
*/
public function buildHeader(): array {
return [
$this->t('Entity Type'),
$this->t('Bundle'),
$this->t('Target Entity'),
$this->t('Source File'),
$this->t('Target Media'),
$this->t('Status'),
$this->t('Category'),
$this->t('Processed'),
$this->t('Error Message'),
$this->t('Actions'),
];
}
/**
* Builds a table row for a MediaSwapRecord entity.
*
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
public function buildRow(MediaSwapRecordInterface $record): array {
$entity_type = $record->getTargetEntityType();
$entity_id = $record->getTargetEntityId();
$link = $this->getEntityWithRoute($entity_id, $entity_type);
$processing_status = $record->getProcessingStatus();
$processing_status_class = match ($processing_status) {
'completed' => 'color-success',
'failed' => 'color-error',
'processing' => 'color-warning',
default => 'color-status',
};
$processed_time = $record->getProcessedTime();
$processed_display = $processed_time ?
$this->dateFormatter->format($processed_time, 'short') :
$this->t('Not processed');
$sourceFileLink = $this->t('N/A');
if (!empty($record->getSourceFileId())) {
// Get the file entity path.
try {
$entity = $this->entityTypeManager->getStorage('file')
->load($record->getSourceFileId());
if ($entity instanceof FileInterface) {
$fileUri = $entity->getFileUri();
// Create a URL to the file from the URI.
$fileUrl = $this->fileUrlGenerator->generate($fileUri);
// Create a link to the file download URL.
$sourceFileLink = Link::fromTextAndUrl($this->t('File'), $fileUrl);
}
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
$this->logger->error($e->getMessage());
}
}
$mediaLink = $this->t('N/A');
if (!empty($record->getCreatedMediaId())) {
try {
$entity = $this->entityTypeManager->getStorage('media')
->load($record->getCreatedMediaId());
if ($entity instanceof MediaInterface) {
$mediaLink = $entity->toLink();
}
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
$this->logger->error($e->getMessage());
}
}
if ($processing_status === 'pending') {
// Add a button to re-check the record.
$status_check_button = Link::createFromRoute($this->t('Recheck'), 'image_to_media_swapper.recheck_record', ['media_swap_record' => $record->id()], [
'attributes' => [
'class' => ['button', 'button--small'],
'style' => 'margin-left: 10px;',
],
]);
}
return [
$entity_type,
$record->getTargetBundle(),
$link ?: $this->t('N/A'),
$sourceFileLink,
$mediaLink,
[
'data' => [
'#type' => 'html_tag',
'#tag' => 'span',
'#attributes' => ['class' => [$processing_status_class]],
'#value' => ucfirst($processing_status),
],
],
$record->getBatchCategory(),
$processed_display,
$record->getErrorMessage(),
$status_check_button ?? '',
];
}
/**
* Gets an entity link with appropriate route.
*
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
private function getEntityWithRoute(int $entity_id, string $type): ?Link {
try {
$entity = $this->entityTypeManager->getStorage($type)->load($entity_id);
}
catch (\Exception $e) {
return NULL;
}
if (!$entity instanceof EntityInterface) {
return NULL;
}
// Try to get canonical URL.
if ($entity->hasLinkTemplate('canonical')) {
return $entity->toLink();
}
// Try edit form.
if ($entity->hasLinkTemplate('edit-form')) {
return $entity->toLink($this->t('Edit'), 'edit-form');
}
// For paragraphs, try to link to parent entity.
if ($type === 'paragraph' && method_exists($entity, 'getParentEntity')) {
$parent = $entity->getParentEntity();
if ($parent instanceof EntityInterface) {
return $this->getEntityWithRoute($parent->id(), $parent->getEntityTypeId());
}
}
return NULL;
}
}
