image_to_media_swapper-2.x-dev/src/MediaSwapFormService.php

src/MediaSwapFormService.php
<?php

declare(strict_types=1);

namespace Drupal\image_to_media_swapper;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface;

/**
 * Service for shared functionality between media swap forms.
 */
class MediaSwapFormService {

  use StringTranslationTrait;
  use LoggerChannelTrait;

  /**
   * Constructs a MediaSwapFormService object.
   *
   * @param \Drupal\image_to_media_swapper\BatchProcessorService $batchProcessorService
   *   The batch processor service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
   *   The current user service.
   * @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
   *   The date formatter service.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
   */
  public function __construct(
    protected BatchProcessorService $batchProcessorService,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected AccountProxyInterface $currentUser,
    protected DateFormatterInterface $dateFormatter,
    protected TimeInterface $time,
  ) {}

  /**
   * Gets text fields that contain image tags.
   *
   * @return array
   *   An associative array of field keys and their labels with image counts.
   */
  public function getFieldsWithImages(): array {
    $options = [];
    $allFields = $this->batchProcessorService->getEligibleTextFields();

    foreach ($allFields as $fieldKey => $fieldLabel) {
      $entities = $this->batchProcessorService->getEntitiesWithFiles($fieldKey, ['images']);
      if (!empty($entities)) {
        $count = count($entities);
        $options[$fieldKey] = $fieldLabel . ' (' . $count . ' entities with images)';
      }
    }

    return $options;
  }

  /**
   * Gets text fields that contain file links.
   *
   * @return array
   *   An associative array of field keys and their labels with link counts.
   */
  public function getFieldsWithFileLinks(): array {
    $options = [];
    $allFields = $this->batchProcessorService->getEligibleTextFields();

    foreach ($allFields as $fieldKey => $fieldLabel) {
      $entities = $this->batchProcessorService->getEntitiesWithFiles($fieldKey, ['links']);
      if (!empty($entities)) {
        $count = count($entities);
        $options[$fieldKey] = $fieldLabel . ' (' . $count . ' entities with file links)';
      }
    }

    return $options;
  }

  /**
   * Gets the count of pending items.
   *
   * @return int
   *   The number of pending items.
   */
  public function getPendingItemsCount(): int {
    $query = $this->entityTypeManager->getStorage('media_swap_record')
      ->getQuery()
      ->accessCheck()
      ->condition('processing_status', 'pending')
      ->count();

    return $query->execute();
  }

  /**
   * Creates pending MediaSwapRecords for entities with a specific field.
   *
   * @param string $fieldSelector
   *   The field selector in the format 'entity_type.bundle.field_name'.
   * @param string $category
   *   The category ('images', 'links', or 'mixed').
   *
   * @return int
   *   The number of records created.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function createPendingRecordsForField(string $fieldSelector, string $category): int {
    $fieldSelectorParts = explode('.', $fieldSelector);
    if (count($fieldSelectorParts) !== 3) {
      throw new \InvalidArgumentException('Field selector must be in the format "entity_type.bundle.field_name".');
    }

    $entityTypeId = $fieldSelectorParts[0];
    $bundle = $fieldSelectorParts[1];

    // Get entities that have content to process.
    $entities = $this->batchProcessorService->getEntitiesWithFiles($fieldSelector, [$category]);

    if (empty($entities)) {
      return 0;
    }

    $count = 0;
    try {
      $swapRecordStorage = $this->entityTypeManager->getStorage('media_swap_record');

      // Check for existing pending records to avoid duplicates.
      $existingRecords = $swapRecordStorage->getQuery()
        ->condition('field_selector', $fieldSelector)
        ->condition('processing_status', 'pending')
        ->accessCheck()
        ->execute();

      $existingEntityIds = [];
      if (!empty($existingRecords)) {
        /** @var \Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface[] $existingEntities */
        $existingEntities = $swapRecordStorage->loadMultiple($existingRecords);
        foreach ($existingEntities as $record) {
          $existingEntityIds[$record->getTargetEntityId()] = TRUE;
        }
      }

      // Create pending records for each entity.
      foreach ($entities as $entity) {
        // Skip if there's already a pending record for this entity and field.
        if (isset($existingEntityIds[$entity->id()])) {
          continue;
        }

        /** @var \Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface $swapRecord */
        $swapRecord = $swapRecordStorage->create([
          'field_selector' => $fieldSelector,
          'target_entity_type' => $entityTypeId,
          'target_bundle' => $bundle,
          'target_entity_id' => $entity->id(),
          'batch_category' => $category,
          'processing_status' => 'pending',
          'processed_time' => $this->dateFormatter->format($this->time
            ->getRequestTime(), 'custom', 'U'),
          'uid' => $this->currentUser->id(),
        ]);

        $swapRecord->save();
        $count++;
      }
    }
    catch (InvalidPluginDefinitionException | PluginNotFoundException  $e) {
      $this->loggerFactory->get('media_swap_record_service')
        ->error($e->getMessage());
    }

    return $count;
  }

  /**
   * Gets all pending records.
   *
   * @return \Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface[]
   *   An array of pending MediaSwapRecord entities.
   */
  public function getPendingRecords(): array {
    try {
      $query = $this->entityTypeManager->getStorage('media_swap_record')
        ->getQuery()
        ->accessCheck()
        ->condition('processing_status', 'pending')
        ->sort('id', 'ASC');
      $results = $query->execute();
    }
    catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
      $this->loggerFactory->get('media_swap_record_service')
        ->error($e->getMessage());
    }

    if (empty($results)) {
      return [];
    }

    try {
      return $this->entityTypeManager->getStorage('media_swap_record')
        ->loadMultiple($results);
    }
    catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
      $this->loggerFactory->get('media_swap_record_service')
        ->error($e->getMessage());
    }
    return [];
  }

  /**
   * Rechecks processed items to update their status.
   *
   * @return int
   *   The number of items rechecked.
   */
  public function recheckProcessedItems(): int {
    // This will check the status of pending records and mark them as completed
    // if they've been manually processed.
    $count = 0;
    $pendingRecords = $this->getPendingRecords();

    foreach ($pendingRecords as $record) {
      // Get the target entity.
      try {
        $entityTypeId = $record->getTargetEntityType();
        $entityId = $record->getTargetEntityId();
        $entity = $this->entityTypeManager->getStorage($entityTypeId)
          ->load($entityId);

        if (!$entity) {
          // Entity doesn't exist anymore.
          $record->setProcessingStatus('failed');
          $record->setErrorMessage('Target entity no longer exists.');
          $record->save();
          $count++;
          continue;
        }

        // Get the field selector parts.
        $needsProcessing = $this->isProcessing($record, $entity);

        // If it doesn't need processing anymore, mark as completed.
        if (!$needsProcessing) {
          $record->setProcessingStatus('completed');
          $record->save();
          $count++;
        }
      }
      catch (\Exception $e) {
        $this->loggerFactory->get('image_to_media_swapper')
          ->error('Error rechecking media swap record @id: @message', [
            '@id' => $record->id(),
            '@message' => $e->getMessage(),
          ]);
      }
    }

    return $count;
  }

  /**
   * Rechecks a single media swap record.
   *
   * @param \Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface $media_swap_record
   *   The ID of the MediaSwapRecord to recheck.
   *
   * @return bool
   *   TRUE if the record was successfully rechecked, FALSE otherwise.
   */
  public function recheckSingleRecord(MediaSwapRecordInterface $media_swap_record): bool {
    try {
      // Only recheck pending records.
      if ($media_swap_record->getProcessingStatus() !== 'pending') {
        return FALSE;
      }

      // Get the target entity.
      $entityTypeId = $media_swap_record->getTargetEntityType();
      $entityId = $media_swap_record->getTargetEntityId();
      $entity = $this->entityTypeManager->getStorage($entityTypeId)
        ->load($entityId);

      if (!$entity) {
        // Entity doesn't exist anymore.
        $media_swap_record->setProcessingStatus('failed');
        $media_swap_record->setErrorMessage('Target entity no longer exists.');
        $media_swap_record->save();
        return TRUE;
      }

      // Get the field selector parts.
      $needsProcessing = $this->isProcessing($media_swap_record, $entity);

      // If it doesn't need processing anymore, mark as completed.
      if (!$needsProcessing) {
        $media_swap_record->setProcessingStatus('completed');
        $media_swap_record->save();
      }

      return TRUE;
    }
    catch (\Exception $e) {
      $this->loggerFactory->get('image_to_media_swapper')
        ->error('Error rechecking media swap record @id: @message', [
          '@id' => $media_swap_record,
          '@message' => $e->getMessage(),
        ]);
      return FALSE;
    }
  }

  /**
   * Determines if the entity still needs processing based on its content.
   *
   * @param \Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface $media_swap_record
   *   The media swap record.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The target entity.
   *
   * @return bool
   *   TRUE if the entity still needs processing, FALSE otherwise.
   */
  protected function isProcessing(MediaSwapRecordInterface $media_swap_record, EntityInterface $entity): bool {
    $fieldSelector = $media_swap_record->getFieldSelector();
    $fieldSelectorParts = explode('.', $fieldSelector);
    $fieldName = $fieldSelectorParts[2];

    // Check if the entity still has content that needs processing.
    $category = $media_swap_record->getBatchCategory();
    $richText = $entity->get($fieldName)->value ?? '';
    $needsProcessing = FALSE;

    if ($category === 'images' && str_contains($richText, '<img')) {
      $needsProcessing = TRUE;
    }
    elseif ($category === 'links' && $this->batchProcessorService->containsFileLinks($richText)) {
      $needsProcessing = TRUE;
    }
    elseif ($category === 'mixed' && (str_contains($richText, '<img') || $this->batchProcessorService->containsFileLinks($richText))) {
      $needsProcessing = TRUE;
    }
    return $needsProcessing;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc