wse-1.0.x-dev/modules/wse_task_monitor/src/Hook/WorkspacePublishingTaskMonitor.php

modules/wse_task_monitor/src/Hook/WorkspacePublishingTaskMonitor.php
<?php

declare(strict_types=1);

namespace Drupal\wse_task_monitor\Hook;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\wse_task_monitor\WorkspaceTask;
use Drupal\wse_task_monitor\WorkspaceTaskMonitorInterface;
use Drupal\wse_task_monitor\WorkspaceTaskRepositoryInterface;
use Drupal\workspaces\Event\WorkspacePrePublishEvent;
use Drupal\workspaces\Event\WorkspacePostPublishEvent;
use Drupal\workspaces\WorkspaceAssociationInterface;
use Drupal\workspaces\WorkspaceInterface;
use Drupal\workspaces\WorkspaceManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Workspace publishing task monitor with integrated hooks and events.
 */
class WorkspacePublishingTaskMonitor implements WorkspaceTaskMonitorInterface, EventSubscriberInterface {

  /**
   * Tracked entities for the current publishing task.
   */
  protected array $trackedEntities = [];

  /**
   * The active task ID for the current publishing operation.
   */
  protected ?string $activeTaskId = NULL;

  public function __construct(
    protected readonly WorkspaceManagerInterface $workspaceManager,
    protected readonly WorkspaceAssociationInterface $workspaceAssociation,
    protected readonly FileSystemInterface $fileSystem,
    protected readonly TimeInterface $time,
    protected readonly WorkspaceTaskRepositoryInterface $repository,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      WorkspacePrePublishEvent::class => 'onPrePublish',
      WorkspacePostPublishEvent::class => 'onPostPublish',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function createTask(WorkspaceInterface $workspace, array $context = []): WorkspaceTask {
    // Validate required context.
    if (!isset($context['tracked_entities'])) {
      throw new \InvalidArgumentException('Publishing tasks require tracked_entities in context');
    }

    $task_id = $this->repository->generateTaskId();
    $task = new WorkspaceTask(
      $task_id,
      $workspace->id(),
      self::class,
      'Publishing workspace: ' . $workspace->label(),
      [
        'total_entities' => $context['total_entities'],
        'tracked_entities' => $context['tracked_entities'],
      ]
    );

    $task->start('Publishing workspace...');
    $this->repository->save($task);

    return $task;
  }

  /**
   * Handles workspace pre-publish event.
   */
  public function onPrePublish(WorkspacePrePublishEvent $event): void {
    $workspace = $event->getWorkspace();
    $published_revisions = $event->getPublishedRevisionIds();

    // Store tracked entities in memory for entityUpdate.
    $this->trackedEntities = $published_revisions;

    // Count total entities to be published.
    $total_entities = count($published_revisions, COUNT_RECURSIVE) - count($published_revisions);

    // Create the task and store task ID.
    $task = $this->createTask(
      $workspace,
      [
        'tracked_entities' => $published_revisions,
        'total_entities' => $total_entities,
      ]
    );
    $this->activeTaskId = $task->getId();

    // Create initial progress file so entityUpdate can find the task.
    $this->writeProgressToFile($this->activeTaskId, 0, $total_entities, 0);
  }

  /**
   * Handles workspace post-publish event.
   */
  public function onPostPublish(WorkspacePostPublishEvent $event): void {
    // Skip if no active task.
    if (!$this->activeTaskId) {
      return;
    }

    $task = $this->repository->find($this->activeTaskId);

    // Complete the task.
    $completionMessage = sprintf(
      'Successfully published %d items from workspace: %s',
      $task->getMetadataValue('total_entities', 0),
      $event->getWorkspace()->label()
    );

    $task->complete($completionMessage);
    $this->repository->save($task);

    // Clean up temp file for this specific task.
    $this->cleanup($task);

    // Clear internal tracking properties.
    $this->trackedEntities = [];
    $this->activeTaskId = NULL;
  }

  /**
   * Handles entity update events for task progress tracking.
   */
  #[Hook('entity_update')]
  public function entityUpdate(EntityInterface $entity): void {
    // Skip if no active task or entity is not revisionable.
    if (!$this->activeTaskId || empty($this->trackedEntities) || !$entity->getEntityType()->isRevisionable()) {
      return;
    }

    // Check if this entity revision is tracked by the active task.
    /** @var \Drupal\Core\Entity\RevisionableInterface $entity */
    if (!isset($this->trackedEntities[$entity->getEntityTypeId()][$entity->getRevisionId()])) {
      return;
    }

    // Get current progress from file.
    $temp_dir = $this->fileSystem->getTempDirectory();
    $progress_file_path = $temp_dir . '/wse_publish_' . $this->activeTaskId . '.json';

    if (!file_exists($progress_file_path)) {
      return;
    }

    $content = file_get_contents($progress_file_path);
    if (!$content) {
      return;
    }

    $progress_data = json_decode($content, TRUE);
    if (!$progress_data) {
      return;
    }

    // Calculate progress.
    try {
      $total_entities = (int) ($progress_data['total'] ?? 1);
      $processed_entities = (int) ($progress_data['processed'] ?? 0) + 1;
      $ratio = $total_entities > 0 ? ($processed_entities / $total_entities) : 1.0;
      $progress_percentage = intval($ratio * 100 + 0.5);

      // Write updated progress to file.
      $this->writeProgressToFile($this->activeTaskId, $processed_entities, $total_entities, $progress_percentage);
    }
    catch (\Exception) {
      return;
    }
  }

  /**
   * Write progress data to temp file for real-time monitoring.
   */
  protected function writeProgressToFile(string $task_id, int $processed_entities, int $total_entities, int $progress_percentage): void {
    $temp_dir = $this->fileSystem->getTempDirectory();
    $progress_file_path = $temp_dir . '/wse_publish_' . $task_id . '.json';

    $progress_data = [
      'task_id' => $task_id,
      'total' => $total_entities,
      'processed' => $processed_entities,
      'progress' => $progress_percentage,
      'timestamp' => $this->time->getRequestTime(),
    ];

    file_put_contents($progress_file_path, json_encode($progress_data));
  }

  /**
   * {@inheritdoc}
   */
  public function getTaskProgress(WorkspaceTask $task): ?array {
    $temp_dir = $this->fileSystem->getTempDirectory();
    $progress_file_path = $temp_dir . '/wse_publish_' . $task->getId() . '.json';

    if (!file_exists($progress_file_path)) {
      return NULL;
    }

    $content = file_get_contents($progress_file_path);
    if (!$content) {
      return NULL;
    }

    $progress_data = json_decode($content, TRUE);
    if (!$progress_data) {
      return NULL;
    }

    // Check if file is stale (older than 5 minutes).
    if (isset($progress_data['timestamp']) && ($this->time->getRequestTime() - $progress_data['timestamp'] > 300)) {
      unlink($progress_file_path);
      return NULL;
    }

    $result = [
      'progress' => $progress_data['progress'] ?? 0,
      'message' => sprintf(
        'Publishing items (%d of %d)',
        $progress_data['processed'] ?? 0,
        $progress_data['total']
      ),
    ];

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function cleanup(WorkspaceTask $task): void {
    $temp_dir = $this->fileSystem->getTempDirectory();
    $progress_file_path = $temp_dir . '/wse_publish_' . $task->getId() . '.json';
    if (file_exists($progress_file_path)) {
      unlink($progress_file_path);
    }
  }

}

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

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