diff --git a/mailpoet/lib/Automation/Engine/Builder/DuplicateWorkflowController.php b/mailpoet/lib/Automation/Engine/Builder/DuplicateWorkflowController.php new file mode 100644 index 0000000000..b02a65c897 --- /dev/null +++ b/mailpoet/lib/Automation/Engine/Builder/DuplicateWorkflowController.php @@ -0,0 +1,91 @@ +wordPress = $wordPress; + $this->workflowStorage = $workflowStorage; + } + + public function duplicateWorkflow(int $id): Workflow { + $workflow = $this->workflowStorage->getWorkflow($id); + if (!$workflow) { + throw Exceptions::workflowNotFound($id); + } + + $duplicate = new Workflow( + $this->getName($workflow->getName()), + $this->getSteps($workflow->getSteps()), + $this->wordPress->wpGetCurrentUser() + ); + $duplicate->setStatus(Workflow::STATUS_DRAFT); + + $workflowId = $this->workflowStorage->createWorkflow($duplicate); + $savedWorkflow = $this->workflowStorage->getWorkflow($workflowId); + if (!$savedWorkflow) { + throw new InvalidStateException('Workflow not found.'); + } + return $savedWorkflow; + } + + private function getName(string $name): string { + // translators: %s is the original workflow name. + $newName = sprintf(__('Copy of %s', 'mailpoet'), $name); + $maxLength = $this->workflowStorage->getNameColumnLength(); + if (strlen($newName) > $maxLength) { + $append = '…'; + return substr($newName, 0, $maxLength - strlen($append)) . $append; + } + return $newName; + } + + /** + * @param Step[] $steps + * @return Step[] + */ + private function getSteps(array $steps): array { + $newIds = []; + foreach ($steps as $step) { + $id = $step->getId(); + $newIds[$id] = $id === 'root' ? 'root' : $this->getId(); + } + + $newSteps = []; + foreach ($steps as $step) { + $newId = $newIds[$step->getId()]; + $newSteps[$newId] = new Step( + $newId, + $step->getType(), + $step->getKey(), + $step->getArgs(), + array_map(function (NextStep $nextStep) use ($newIds): NextStep { + return new NextStep($newIds[$nextStep->getId()]); + }, $step->getNextSteps()) + ); + } + return $newSteps; + } + + private function getId(): string { + return Security::generateRandomString(16); + } +} diff --git a/mailpoet/lib/Automation/Engine/Data/Workflow.php b/mailpoet/lib/Automation/Engine/Data/Workflow.php index 232f0dc8cf..2e7d166110 100644 --- a/mailpoet/lib/Automation/Engine/Data/Workflow.php +++ b/mailpoet/lib/Automation/Engine/Data/Workflow.php @@ -191,8 +191,8 @@ class Workflow { }, Json::decode($data['steps'])), new \WP_User((int)$data['author']) ); - $workflow->id = (int)($data['id'] ?? 0); - $workflow->versionId = (int)($data['version_id'] ?? 0); + $workflow->id = (int)$data['id']; + $workflow->versionId = (int)$data['version_id']; $workflow->status = $data['status']; $workflow->createdAt = new DateTimeImmutable($data['created_at']); $workflow->updatedAt = new DateTimeImmutable($data['updated_at']); diff --git a/mailpoet/lib/Automation/Engine/Endpoints/Workflows/WorkflowsDuplicateEndpoint.php b/mailpoet/lib/Automation/Engine/Endpoints/Workflows/WorkflowsDuplicateEndpoint.php index 8d970d64cf..02885beb56 100644 --- a/mailpoet/lib/Automation/Engine/Endpoints/Workflows/WorkflowsDuplicateEndpoint.php +++ b/mailpoet/lib/Automation/Engine/Endpoints/Workflows/WorkflowsDuplicateEndpoint.php @@ -5,41 +5,28 @@ namespace MailPoet\Automation\Engine\Endpoints\Workflows; use MailPoet\API\REST\Request; use MailPoet\API\REST\Response; use MailPoet\Automation\Engine\API\Endpoint; -use MailPoet\Automation\Engine\Data\Workflow; -use MailPoet\Automation\Engine\Exceptions\InvalidStateException; +use MailPoet\Automation\Engine\Builder\DuplicateWorkflowController; use MailPoet\Automation\Engine\Mappers\WorkflowMapper; -use MailPoet\Automation\Engine\Storage\WorkflowStorage; use MailPoet\Validator\Builder; class WorkflowsDuplicateEndpoint extends Endpoint { /** @var WorkflowMapper */ private $workflowMapper; - /** @var WorkflowStorage */ - private $workflowStorage; + /** @var DuplicateWorkflowController */ + private $duplicateController; public function __construct( - WorkflowMapper $workflowMapper, - WorkflowStorage $workflowStorage + DuplicateWorkflowController $duplicateController, + WorkflowMapper $workflowMapper ) { $this->workflowMapper = $workflowMapper; - $this->workflowStorage = $workflowStorage; + $this->duplicateController = $duplicateController; } public function handle(Request $request): Response { - $workflowId = $request->getParam('id'); - if (!is_int($workflowId)) { - throw InvalidStateException::create(); - } - $existingWorkflow = $this->workflowStorage->getWorkflow($workflowId); - if (!$existingWorkflow instanceof Workflow) { - throw InvalidStateException::create(); - } - $duplicateId = $this->workflowStorage->duplicateWorkflow($existingWorkflow); - $duplicate = $this->workflowStorage->getWorkflow($duplicateId); - if (!$duplicate instanceof Workflow) { - throw InvalidStateException::create(); - } + $workflowId = intval($request->getParam('id')); + $duplicate = $this->duplicateController->duplicateWorkflow($workflowId); return new Response($this->workflowMapper->buildWorkflow($duplicate)); } diff --git a/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php b/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php index c6224a4cdb..18f731ae34 100644 --- a/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php +++ b/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php @@ -37,28 +37,6 @@ class WorkflowStorage { return $id; } - public function duplicateWorkflow(Workflow $workflow): int { - $data = $workflow->toArray(); - $now = (new DateTimeImmutable())->format(DateTimeImmutable::W3C); - $data['created_at'] = $now; - $data['updated_at'] = $now; - $data['status'] = Workflow::STATUS_DRAFT; - $prefix = 'Copy of '; - $newName = $prefix . $workflow->getName(); - $nameColumnLengthInfo = $this->wpdb->get_col_length($this->workflowTable, 'name'); - $maxLength = is_array($nameColumnLengthInfo) - ? $nameColumnLengthInfo['length'] ?? 255 - : 255; - if (strlen($newName) > $maxLength) { - $truncateWith = '…'; - $truncationLength = strlen($truncateWith); - $newName = substr($newName, 0, $maxLength - $truncationLength) . $truncateWith; - } - $data['name'] = $newName; - $duplicate = Workflow::fromArray($data); - return $this->createWorkflow($duplicate); - } - public function updateWorkflow(Workflow $workflow): void { $oldRecord = $this->getWorkflow($workflow->getId()); if ($oldRecord && $oldRecord->equals($workflow)) { @@ -200,6 +178,13 @@ class WorkflowStorage { $this->wpdb->query("truncate $versionTable;") === true; } + public function getNameColumnLength(): int { + $nameColumnLengthInfo = $this->wpdb->get_col_length($this->workflowTable, 'name'); + return is_array($nameColumnLengthInfo) + ? $nameColumnLengthInfo['length'] ?? 255 + : 255; + } + private function getWorkflowHeaderData(Workflow $workflow): array { $workflowHeader = $workflow->toArray(); unset($workflowHeader['steps']); diff --git a/mailpoet/lib/Automation/Engine/WordPress.php b/mailpoet/lib/Automation/Engine/WordPress.php index be67aeba2d..2fb871e8b8 100644 --- a/mailpoet/lib/Automation/Engine/WordPress.php +++ b/mailpoet/lib/Automation/Engine/WordPress.php @@ -2,6 +2,8 @@ namespace MailPoet\Automation\Engine; +use WP_User; + class WordPress { public function addAction(string $hookName, callable $callback, int $priority = 10, int $acceptedArgs = 1): bool { return add_action($hookName, $callback, $priority, $acceptedArgs); @@ -12,6 +14,10 @@ class WordPress { do_action($hookName, ...$arg); } + public function wpGetCurrentUser(): WP_User { + return wp_get_current_user(); + } + /** @param mixed ...$args */ public function currentUserCan(string $capability, ...$args): bool { return current_user_can($capability, ...$args); diff --git a/mailpoet/lib/DI/ContainerConfigurator.php b/mailpoet/lib/DI/ContainerConfigurator.php index 319f694a74..504a25ca8b 100644 --- a/mailpoet/lib/DI/ContainerConfigurator.php +++ b/mailpoet/lib/DI/ContainerConfigurator.php @@ -112,6 +112,7 @@ class ContainerConfigurator implements IContainerConfigurator { // Automation $container->autowire(\MailPoet\Automation\Engine\API\API::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Builder\CreateWorkflowFromTemplateController::class)->setPublic(true); + $container->autowire(\MailPoet\Automation\Engine\Builder\DuplicateWorkflowController::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Builder\UpdateStepsController::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Builder\UpdateWorkflowController::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Control\ActionScheduler::class)->setPublic(true);