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);
}
}
}
