Extract workflow duplication logic to a controller, fix some issues

[MAILPOET-4540]
This commit is contained in:
Jan Jakes
2022-10-13 14:37:44 +02:00
committed by David Remer
parent 6158f5a64b
commit 89c43a5cb9
6 changed files with 115 additions and 45 deletions

View File

@ -0,0 +1,91 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Builder;
use MailPoet\Automation\Engine\Data\NextStep;
use MailPoet\Automation\Engine\Data\Step;
use MailPoet\Automation\Engine\Data\Workflow;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
use MailPoet\Automation\Engine\WordPress;
use MailPoet\Util\Security;
class DuplicateWorkflowController {
/** @var WordPress */
private $wordPress;
/** @var WorkflowStorage */
private $workflowStorage;
public function __construct(
WordPress $wordPress,
WorkflowStorage $workflowStorage
) {
$this->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);
}
}

View File

@ -191,8 +191,8 @@ class Workflow {
}, Json::decode($data['steps'])), }, Json::decode($data['steps'])),
new \WP_User((int)$data['author']) new \WP_User((int)$data['author'])
); );
$workflow->id = (int)($data['id'] ?? 0); $workflow->id = (int)$data['id'];
$workflow->versionId = (int)($data['version_id'] ?? 0); $workflow->versionId = (int)$data['version_id'];
$workflow->status = $data['status']; $workflow->status = $data['status'];
$workflow->createdAt = new DateTimeImmutable($data['created_at']); $workflow->createdAt = new DateTimeImmutable($data['created_at']);
$workflow->updatedAt = new DateTimeImmutable($data['updated_at']); $workflow->updatedAt = new DateTimeImmutable($data['updated_at']);

View File

@ -5,41 +5,28 @@ namespace MailPoet\Automation\Engine\Endpoints\Workflows;
use MailPoet\API\REST\Request; use MailPoet\API\REST\Request;
use MailPoet\API\REST\Response; use MailPoet\API\REST\Response;
use MailPoet\Automation\Engine\API\Endpoint; use MailPoet\Automation\Engine\API\Endpoint;
use MailPoet\Automation\Engine\Data\Workflow; use MailPoet\Automation\Engine\Builder\DuplicateWorkflowController;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Mappers\WorkflowMapper; use MailPoet\Automation\Engine\Mappers\WorkflowMapper;
use MailPoet\Automation\Engine\Storage\WorkflowStorage;
use MailPoet\Validator\Builder; use MailPoet\Validator\Builder;
class WorkflowsDuplicateEndpoint extends Endpoint { class WorkflowsDuplicateEndpoint extends Endpoint {
/** @var WorkflowMapper */ /** @var WorkflowMapper */
private $workflowMapper; private $workflowMapper;
/** @var WorkflowStorage */ /** @var DuplicateWorkflowController */
private $workflowStorage; private $duplicateController;
public function __construct( public function __construct(
WorkflowMapper $workflowMapper, DuplicateWorkflowController $duplicateController,
WorkflowStorage $workflowStorage WorkflowMapper $workflowMapper
) { ) {
$this->workflowMapper = $workflowMapper; $this->workflowMapper = $workflowMapper;
$this->workflowStorage = $workflowStorage; $this->duplicateController = $duplicateController;
} }
public function handle(Request $request): Response { public function handle(Request $request): Response {
$workflowId = $request->getParam('id'); $workflowId = intval($request->getParam('id'));
if (!is_int($workflowId)) { $duplicate = $this->duplicateController->duplicateWorkflow($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();
}
return new Response($this->workflowMapper->buildWorkflow($duplicate)); return new Response($this->workflowMapper->buildWorkflow($duplicate));
} }

View File

@ -37,28 +37,6 @@ class WorkflowStorage {
return $id; 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 { public function updateWorkflow(Workflow $workflow): void {
$oldRecord = $this->getWorkflow($workflow->getId()); $oldRecord = $this->getWorkflow($workflow->getId());
if ($oldRecord && $oldRecord->equals($workflow)) { if ($oldRecord && $oldRecord->equals($workflow)) {
@ -200,6 +178,13 @@ class WorkflowStorage {
$this->wpdb->query("truncate $versionTable;") === true; $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 { private function getWorkflowHeaderData(Workflow $workflow): array {
$workflowHeader = $workflow->toArray(); $workflowHeader = $workflow->toArray();
unset($workflowHeader['steps']); unset($workflowHeader['steps']);

View File

@ -2,6 +2,8 @@
namespace MailPoet\Automation\Engine; namespace MailPoet\Automation\Engine;
use WP_User;
class WordPress { class WordPress {
public function addAction(string $hookName, callable $callback, int $priority = 10, int $acceptedArgs = 1): bool { public function addAction(string $hookName, callable $callback, int $priority = 10, int $acceptedArgs = 1): bool {
return add_action($hookName, $callback, $priority, $acceptedArgs); return add_action($hookName, $callback, $priority, $acceptedArgs);
@ -12,6 +14,10 @@ class WordPress {
do_action($hookName, ...$arg); do_action($hookName, ...$arg);
} }
public function wpGetCurrentUser(): WP_User {
return wp_get_current_user();
}
/** @param mixed ...$args */ /** @param mixed ...$args */
public function currentUserCan(string $capability, ...$args): bool { public function currentUserCan(string $capability, ...$args): bool {
return current_user_can($capability, ...$args); return current_user_can($capability, ...$args);

View File

@ -112,6 +112,7 @@ class ContainerConfigurator implements IContainerConfigurator {
// Automation // Automation
$container->autowire(\MailPoet\Automation\Engine\API\API::class)->setPublic(true); $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\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\UpdateStepsController::class)->setPublic(true);
$container->autowire(\MailPoet\Automation\Engine\Builder\UpdateWorkflowController::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Builder\UpdateWorkflowController::class)->setPublic(true);
$container->autowire(\MailPoet\Automation\Engine\Control\ActionScheduler::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Control\ActionScheduler::class)->setPublic(true);