diff --git a/mailpoet/lib/Automation/Engine/Data/Workflow.php b/mailpoet/lib/Automation/Engine/Data/Workflow.php index 5fb7af3dba..cb42a45fdb 100644 --- a/mailpoet/lib/Automation/Engine/Data/Workflow.php +++ b/mailpoet/lib/Automation/Engine/Data/Workflow.php @@ -167,14 +167,6 @@ class Workflow { return $step->toArray(); }, $this->steps) ), - 'trigger_keys' => Json::encode( - array_reduce($this->steps, function (array $triggerKeys, Step $step): array { - if ($step->getType() === Step::TYPE_TRIGGER) { - $triggerKeys[] = $step->getKey(); - } - return $triggerKeys; - }, []) - ), ]; } diff --git a/mailpoet/lib/Automation/Engine/Migrations/Migrator.php b/mailpoet/lib/Automation/Engine/Migrations/Migrator.php index d713d94e29..e0d917f788 100644 --- a/mailpoet/lib/Automation/Engine/Migrations/Migrator.php +++ b/mailpoet/lib/Automation/Engine/Migrations/Migrator.php @@ -39,7 +39,6 @@ class Migrator { CREATE TABLE {$this->prefix}workflow_versions ( id int(11) unsigned NOT NULL AUTO_INCREMENT, workflow_id int(11) unsigned NOT NULL, - trigger_keys longtext NOT NULL, steps longtext, created_at timestamp NOT NULL, updated_at timestamp NOT NULL, @@ -48,6 +47,14 @@ class Migrator { ); "); + $this->runQuery(" + CREATE TABLE {$this->prefix}workflow_triggers ( + workflow_id int(11) unsigned NOT NULL, + trigger_key varchar(255), + PRIMARY KEY (workflow_id, trigger_key) + ); + "); + $this->runQuery(" CREATE TABLE {$this->prefix}workflow_runs ( id int(11) unsigned NOT NULL AUTO_INCREMENT, @@ -86,6 +93,7 @@ class Migrator { $this->runQuery("DROP TABLE IF EXISTS {$this->prefix}workflow_runs"); $this->runQuery("DROP TABLE IF EXISTS {$this->prefix}workflow_run_logs"); $this->runQuery("DROP TABLE IF EXISTS {$this->prefix}workflow_versions"); + $this->runQuery("DROP TABLE IF EXISTS {$this->prefix}workflow_triggers"); // clean Action Scheduler data $this->runQuery(" diff --git a/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php b/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php index 3eb173de97..527558a658 100644 --- a/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php +++ b/mailpoet/lib/Automation/Engine/Storage/WorkflowStorage.php @@ -3,18 +3,22 @@ namespace MailPoet\Automation\Engine\Storage; use DateTimeImmutable; +use MailPoet\Automation\Engine\Data\Step; use MailPoet\Automation\Engine\Data\Workflow; use MailPoet\Automation\Engine\Exceptions; use MailPoet\Automation\Engine\Integration\Trigger; -use MailPoet\Automation\Engine\Utils\Json; use wpdb; class WorkflowStorage { /** @var string */ private $workflowTable; + /** @var string */ private $versionsTable; + /** @var string */ + private $triggersTable; + /** @var wpdb */ private $wpdb; @@ -22,6 +26,7 @@ class WorkflowStorage { global $wpdb; $this->workflowTable = $wpdb->prefix . 'mailpoet_workflows'; $this->versionsTable = $wpdb->prefix . 'mailpoet_workflow_versions'; + $this->triggersTable = $wpdb->prefix . 'mailpoet_workflow_triggers'; $this->wpdb = $wpdb; } @@ -34,6 +39,7 @@ class WorkflowStorage { } $id = $this->wpdb->insert_id; $this->insertWorkflowVersion($id, $workflow); + $this->insertWorkflowTriggers($id, $workflow); return $id; } @@ -47,6 +53,7 @@ class WorkflowStorage { throw Exceptions::databaseError($this->wpdb->last_error); } $this->insertWorkflowVersion($workflow->getId(), $workflow); + $this->insertWorkflowTriggers($workflow->getId(), $workflow); } public function getWorkflow(int $workflowId, int $versionId = null): ?Workflow { @@ -54,14 +61,14 @@ class WorkflowStorage { $versionTable = esc_sql($this->versionsTable); $query = !$versionId ? (string)$this->wpdb->prepare(" - SELECT workflow.*, version.id AS version_id, version.steps, version.trigger_keys + SELECT workflow.*, version.id AS version_id, version.steps FROM $workflowTable as workflow, $versionTable as version WHERE version.workflow_id = workflow.id AND workflow.id = %d ORDER BY version.id DESC LIMIT 0,1;", $workflowId ) : (string)$this->wpdb->prepare(" - SELECT workflow.*, version.id AS version_id, version.steps, version.trigger_keys + SELECT workflow.*, version.id AS version_id, version.steps FROM $workflowTable as workflow, $versionTable as version WHERE version.workflow_id = workflow.id AND version.id = %d", $versionId @@ -76,13 +83,13 @@ class WorkflowStorage { $versionTable = esc_sql($this->versionsTable); $query = $status ? (string)$this->wpdb->prepare(" - SELECT workflow.*, version.id AS version_id, version.steps, version.trigger_keys + SELECT workflow.*, version.id AS version_id, version.steps FROM $workflowTable AS workflow INNER JOIN $versionTable as version ON (version.workflow_id=workflow.id) WHERE version.id = (SELECT Max(id) FROM $versionTable WHERE workflow_id= version.workflow_id) AND workflow.status IN (%s) ORDER BY workflow.id DESC", implode(",", $status) ) : - "SELECT workflow.*, version.id AS version_id, version.steps, version.trigger_keys + "SELECT workflow.*, version.id AS version_id, version.steps FROM $workflowTable AS workflow INNER JOIN $versionTable as version ON (version.workflow_id=workflow.id) WHERE version.id = (SELECT Max(id) FROM $versionTable WHERE workflow_id= version.workflow_id) ORDER BY workflow.id DESC;"; @@ -101,35 +108,41 @@ class WorkflowStorage { /** @return string[] */ public function getActiveTriggerKeys(): array { $workflowTable = esc_sql($this->workflowTable); - $versionTable = esc_sql($this->versionsTable); - $query = (string)$this->wpdb->prepare(" - SELECT DISTINCT version.trigger_keys - FROM $workflowTable AS workflow, $versionTable as version - WHERE workflow.status = %s AND workflow.id=version.workflow_id - ORDER BY version.id DESC", - Workflow::STATUS_ACTIVE - ); - $result = $this->wpdb->get_col($query); + $triggersTable = esc_sql($this->triggersTable); - $triggerKeys = []; - foreach ($result as $item) { - /** @var string[] $keys */ - $keys = Json::decode($item); - $triggerKeys = array_merge($triggerKeys, $keys); - } - return array_unique($triggerKeys); + $query = (string)$this->wpdb->prepare( + " + SELECT DISTINCT triggers.trigger_key + FROM {$workflowTable} AS workflow + JOIN $triggersTable as triggers + WHERE workflow.status = %s AND workflow.id = triggers.workflow_id + ORDER BY trigger_key DESC + ", + Workflow::STATUS_ACTIVE + ); + return $this->wpdb->get_col($query); } /** @return Workflow[] */ public function getActiveWorkflowsByTrigger(Trigger $trigger): array { $workflowTable = esc_sql($this->workflowTable); $versionTable = esc_sql($this->versionsTable); - $query = (string)$this->wpdb->prepare(" - SELECT workflow.*, version.id AS version_id, version.steps, version.trigger_keys - FROM $workflowTable AS workflow INNER JOIN $versionTable as version ON (version.workflow_id=workflow.id) - WHERE workflow.status = %s AND version.trigger_keys LIKE %s AND version.id = (SELECT Max(id) FROM $versionTable WHERE workflow_id= version.workflow_id)", - Workflow::STATUS_ACTIVE, - '%' . $this->wpdb->esc_like($trigger->getKey()) . '%' + $triggersTable = esc_sql($this->triggersTable); + + $query = (string)$this->wpdb->prepare( + " + SELECT workflow.*, version.id AS version_id, version.steps + FROM $workflowTable AS workflow + INNER JOIN $triggersTable as t ON (t.workflow_id = workflow.id) + INNER JOIN $versionTable as version ON (version.workflow_id = workflow.id) + WHERE workflow.status = %s + AND t.trigger_key = %s + AND version.id = ( + SELECT MAX(id) FROM $versionTable WHERE workflow_id = version.workflow_id + ) + ", + Workflow::STATUS_ACTIVE, + $trigger->getKey() ); $data = $this->wpdb->get_results($query, ARRAY_A); @@ -170,6 +183,10 @@ class WorkflowStorage { if (!is_int($versionsDeleted)) { throw Exceptions::databaseError($this->wpdb->last_error); } + $triggersDeleted = $this->wpdb->delete($this->triggersTable, ['workflow_id' => $workflowId]); + if (!is_int($triggersDeleted)) { + throw Exceptions::databaseError($this->wpdb->last_error); + } $workflowDeleted = $this->wpdb->delete($workflowTable, ['id' => $workflowId]); if (!is_int($workflowDeleted)) { throw Exceptions::databaseError($this->wpdb->last_error); @@ -179,8 +196,10 @@ class WorkflowStorage { public function truncate(): bool { $workflowTable = esc_sql($this->workflowTable); $versionTable = esc_sql($this->versionsTable); - return $this->wpdb->query("truncate $workflowTable;") === true && - $this->wpdb->query("truncate $versionTable;") === true; + $triggersTable = esc_sql($this->versionsTable); + return $this->wpdb->query("truncate $workflowTable;") === true + && $this->wpdb->query("truncate $versionTable;") === true + && $this->wpdb->query("truncate $triggersTable;") === true; } public function getNameColumnLength(): int { @@ -193,7 +212,6 @@ class WorkflowStorage { private function getWorkflowHeaderData(Workflow $workflow): array { $workflowHeader = $workflow->toArray(); unset($workflowHeader['steps']); - unset($workflowHeader['trigger_keys']); return $workflowHeader; } @@ -202,7 +220,6 @@ class WorkflowStorage { $data = [ 'workflow_id' => $workflowId, 'steps' => $workflow->toArray()['steps'], - 'trigger_keys' => $workflow->toArray()['trigger_keys'], 'created_at' => $dateString, 'updated_at' => $dateString, ]; @@ -211,4 +228,46 @@ class WorkflowStorage { throw Exceptions::databaseError($this->wpdb->last_error); } } + + private function insertWorkflowTriggers(int $workflowId, Workflow $workflow): void { + $triggerKeys = []; + foreach ($workflow->getSteps() as $step) { + if ($step->getType() === Step::TYPE_TRIGGER) { + $triggerKeys[] = $step->getKey(); + } + } + + if (!$triggerKeys) { + return; + } + $triggersTable = esc_sql($this->triggersTable); + + // insert/update + $placeholders = implode(',', array_fill(0, count($triggerKeys), '(%d, %s)')); + $query = (string)$this->wpdb->prepare( + "INSERT IGNORE INTO {$triggersTable} (workflow_id, trigger_key) VALUES {$placeholders}", + array_merge( + ...array_map(function (string $key) use ($workflowId) { + return [$workflowId, $key]; + }, $triggerKeys) + ) + ); + + $result = $this->wpdb->query($query); + if ($result === false) { + throw Exceptions::databaseError($this->wpdb->last_error); + } + + // delete + $placeholders = implode(',', array_fill(0, count($triggerKeys), '%s')); + $query = (string)$this->wpdb->prepare( + "DELETE FROM {$triggersTable} WHERE workflow_id = %d AND trigger_key NOT IN ({$placeholders})", + array_merge([$workflowId], $triggerKeys) + ); + + $result = $this->wpdb->query($query); + if ($result === false) { + throw Exceptions::databaseError($this->wpdb->last_error); + } + } } diff --git a/mailpoet/tasks/phpstan/phpstan.neon b/mailpoet/tasks/phpstan/phpstan.neon index f90ede202b..407ff7026b 100644 --- a/mailpoet/tasks/phpstan/phpstan.neon +++ b/mailpoet/tasks/phpstan/phpstan.neon @@ -48,7 +48,7 @@ parameters: path: ../../lib/Automation/Engine/Storage/WorkflowRunStorage.php - message: "#^Cannot cast string|void to string\\.$#" - count: 5 + count: 7 path: ../../lib/Automation/Engine/Storage/WorkflowStorage.php - message: '/^Call to static method get_orders_table_name\(\) on an unknown class Automattic\\WooCommerce\\Internal\\DataStores\\Orders\\OrdersTableDataStore\.$/'