From 5b4512e3a6cf4f068729bda9ade193ea8c6c9db1 Mon Sep 17 00:00:00 2001 From: jygaulier Date: Tue, 11 Mar 2025 11:48:30 +0100 Subject: [PATCH 1/3] add 'status' filter (for "owners" job) ```json // type 'target' => 'owners', // filters 'databox' => 'db_databox1', 'status' => '00xxxx', 'collection' => ['test', 'boo'], 'expire_field' => 'ExpireDate', 'prior_notice' => -30, // action 'set_status' => '01xxxx', 'alerts' => [ [ 'method' => 'webhook', 'recipient' => [ 'bob@a.fr', 'joe@b.com', ], ], ], ``` --- .../AlertExpiringRightsCommand.php | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php b/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php index f354aee59d..4591adf922 100644 --- a/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php +++ b/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php @@ -190,6 +190,28 @@ private function playJobOwners($jobname, $job) } } + // filter on sb + $mask = get_in($job, ['status']); + if ($mask !== null) { + $m = preg_replace('/[^0-1]/', 'x', trim($mask)); + if (strlen($m) > 32) { + $this->output->writeln(sprintf("status mask (%s) too long", $mask)); + + return false; + } + $mask_xor = str_replace(' ', '0', ltrim(str_replace(['0', 'x'], [' ', ' '], $m))); + $mask_and = str_replace(' ', '0', ltrim(str_replace(['x', '0'], [' ', '1'], $m))); + if ($mask_xor && $mask_and) { + $wheres[] = '((r.`status` ^ 0b' . $mask_xor . ') & 0b' . $mask_and . ') = 0'; + } + elseif ($mask_xor) { + $wheres[] = '(r.`status` ^ 0b' . $mask_xor . ') = 0'; + } + elseif ($mask_and) { + $wheres[] = '(r.`status` & 0b' . $mask_and . ') = 0'; + } + } + // clause on sb (negated) $mask = get_in($job, ['set_status']); if ($mask === null) { @@ -198,7 +220,7 @@ private function playJobOwners($jobname, $job) } $m = preg_replace('/[^0-1]/', 'x', trim($mask)); if (strlen($m) > 32) { - $this->output->writeln(sprintf("status mask (%s) too long", $mask)); + $this->output->writeln(sprintf("set_status mask (%s) too long", $mask)); return false; } $mask_xor = str_replace(' ', '0', ltrim(str_replace(array('0', 'x'), array(' ', ' '), $m))); From e1edf4cf63fe84630dd7b59a0b402f74fb0190cd Mon Sep 17 00:00:00 2001 From: jygaulier Date: Wed, 12 Mar 2025 12:22:32 +0100 Subject: [PATCH 2/3] add 'set_collection' action (for "owners" job) acts on records using api, not sql --- .../AlertExpiringRightsCommand.php | 124 ++++++++++-------- 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php b/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php index 4591adf922..5471101b03 100644 --- a/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php +++ b/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php @@ -154,6 +154,9 @@ private function playJobOwners($jobname, $job) return true; } + // actions by api + $actions = []; + // build sql where clause $wheres = []; @@ -173,7 +176,7 @@ private function playJobOwners($jobname, $job) $dbox = $this->databoxes[$d]['dbox']; $sbas_id = $dbox->get_sbas_id(); - // filter on collections ? + // filter on collections $collList = []; foreach (get_in($job, ['collection'], []) as $c) { /** @var collection $coll */ @@ -190,13 +193,11 @@ private function playJobOwners($jobname, $job) } } - // filter on sb - $mask = get_in($job, ['status']); - if ($mask !== null) { - $m = preg_replace('/[^0-1]/', 'x', trim($mask)); - if (strlen($m) > 32) { - $this->output->writeln(sprintf("status mask (%s) too long", $mask)); - + // filter on status + if (($status = get_in($job, ['status'])) !== null) { + $m = preg_replace('/[^0-1]/', 'x', trim($status)); + if (strlen($m) == 0 || strlen($m) > 32) { + $this->output->writeln(sprintf("invalid status (%s)", $status)); return false; } $mask_xor = str_replace(' ', '0', ltrim(str_replace(['0', 'x'], [' ', ' '], $m))); @@ -212,38 +213,34 @@ private function playJobOwners($jobname, $job) } } - // clause on sb (negated) - $mask = get_in($job, ['set_status']); - if ($mask === null) { - $this->output->writeln(sprintf("missing 'set_status' clause")); - return false; - } - $m = preg_replace('/[^0-1]/', 'x', trim($mask)); - if (strlen($m) > 32) { - $this->output->writeln(sprintf("set_status mask (%s) too long", $mask)); - return false; - } - $mask_xor = str_replace(' ', '0', ltrim(str_replace(array('0', 'x'), array(' ', ' '), $m))); - $mask_and = str_replace(' ', '0', ltrim(str_replace(array('x', '0'), array(' ', '1'), $m))); - if ($mask_xor && $mask_and) { - $wheres[] = '((r.`status` ^ 0b' . $mask_xor . ') & 0b' . $mask_and . ') != 0'; - } elseif ($mask_xor) { - $wheres[] = '(r.`status` ^ 0b' . $mask_xor . ') != 0'; - } elseif ($mask_and) { - $wheres[] = '(r.`status` & 0b' . $mask_and . ') != 0'; - } else { - $this->output->writeln(sprintf("empty status mask")); - return false; - } - // set status - $set_status = "`status`"; - $set_or = str_replace(' ', '0', ltrim(str_replace(array('0', 'x'), array(' ', ' '), $m))); - $set_nand = str_replace(' ', '0', ltrim(str_replace(array('x', '1', '0'), array(' ', ' ', '1'), $m))); - if($set_or) { - $set_status = "(" . $set_status . " | 0b" . $set_or . ")"; - } - if($set_nand) { - $set_status = "(" . $set_status . " & ~0b" . $set_nand . ")"; + // filter on negated set_status + if(($set_status = get_in($job, ['set_status'])) !== null) { + $m = preg_replace('/[^0-1]/', 'x', trim($set_status)); + if (strlen($m) == 0 || strlen($m) > 32) { + $this->output->writeln(sprintf("invalid set_status (%s)", $set_status)); + return false; + } + $mask_xor = str_replace(' ', '0', ltrim(str_replace(['0', 'x'], [' ', ' '], $m))); + $mask_and = str_replace(' ', '0', ltrim(str_replace(['x', '0'], [' ', '1'], $m))); + if ($mask_xor && $mask_and) { + $wheres[] = '((r.`status` ^ 0b' . $mask_xor . ') & 0b' . $mask_and . ') != 0'; + } + elseif ($mask_xor) { + $wheres[] = '(r.`status` ^ 0b' . $mask_xor . ') != 0'; + } + elseif ($mask_and) { + $wheres[] = '(r.`status` & 0b' . $mask_and . ') != 0'; + } + + // set status by actions api + foreach (str_split(strrev($m)) as $bit => $val) { + if ($val == '0' || $val == '1') { + if(!array_key_exists('status', $actions)) { + $actions['status'] = []; + } + $actions['status'][] = ['bit' => $bit, 'state' => $val=='1']; + } + } } // clause on expiration date @@ -292,18 +289,42 @@ private function playJobOwners($jobname, $job) $this->output->writeln(sprintf("dry mode: updates on %d records NOT executed", $stmt->rowCount())); } + // change collection by actions api + if($set_collection = get_in($job, ['set_collection'])) { + /** @var collection $coll */ + if (($coll = get_in($this->databoxes[$sbas_id], ['collections', $set_collection])) !== null) { + $actions['base_id'] = $coll->get_coll_id(); + } + else { + $this->output->writeln(sprintf("unknown collection (%s)", $c)); + return false; + } + } + + if(empty($actions)) { + $this->output->writeln(sprintf("no actions defined")); + return false; + } + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $record = $dbox->get_record($row['record_id']); $row['collection'] = $record->getCollection()->get_name(); $row['title'] = $record->get_title(); $records[] = $row; - $sql = "UPDATE `record` SET `status`=" . $set_status . " WHERE record_id=" . $dbox->get_connection()->quote($row['record_id']); - if ($this->input->getOption('show-sql')) { - $this->output->writeln(sprintf("sql: %s", $sql)); - } - if (!$this->input->getOption('dry')) { - $dbox->get_connection()->exec($sql); + // update by api + if(!empty($actions)) { + $js = json_encode($actions); + $this->output->writeln(sprintf("on databox(%s), record(%s) :%s js=%s \n", + $sbas_id, + $row['record_id'], + $this->input->getOption('dry') ? " [DRY]" : '', + $js + )); + + if(!$this->input->getOption('dry')) { + $record->setMetadatasByActions(json_decode($js, false)); // false: setMetadatasByActions expects object, not array ! + } } } $stmt->closeCursor(); @@ -597,12 +618,11 @@ private function sanitizeJob($job) private function sanitizeJobOwners($job) { - return $this->sanitize( - $job, - [ - 'set_status' => "is_string", - ] - ); + if(get_in($job, ['set_status']) === null && get_in($job, ['set_collection']) === null) { + $this->output->writeln(sprintf("missing 'set_status' or 'set_collection' setting")); + return false; + } + return true; } private function sanitizeJobDownloaders($job) From 9249de571d41230a0717ebe36428120702fbb3b7 Mon Sep 17 00:00:00 2001 From: jygaulier Date: Wed, 12 Mar 2025 12:34:21 +0100 Subject: [PATCH 3/3] add negated clause on 'set_collection' --- .../AlertExpiringRightsCommand.php | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php b/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php index 5471101b03..d3b2fc9804 100644 --- a/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php +++ b/lib/Alchemy/Phrasea/Command/ExpiringRights/AlertExpiringRightsCommand.php @@ -243,6 +243,21 @@ private function playJobOwners($jobname, $job) } } + // filter on negated set_collection + if($set_collection = get_in($job, ['set_collection'])) { + /** @var collection $coll */ + if (($coll = get_in($this->databoxes[$sbas_id], ['collections', $set_collection])) !== null) { + $wheres[] = "r.`coll_id`!=" . $dbox->get_connection()->quote($coll->get_coll_id()); + + // change collection by actions api + $actions['base_id'] = $coll->get_coll_id(); + } + else { + $this->output->writeln(sprintf("unknown collection (%s)", $c)); + return false; + } + } + // clause on expiration date // the NOW() can be faked for testing $expire_field_id = null; @@ -289,18 +304,6 @@ private function playJobOwners($jobname, $job) $this->output->writeln(sprintf("dry mode: updates on %d records NOT executed", $stmt->rowCount())); } - // change collection by actions api - if($set_collection = get_in($job, ['set_collection'])) { - /** @var collection $coll */ - if (($coll = get_in($this->databoxes[$sbas_id], ['collections', $set_collection])) !== null) { - $actions['base_id'] = $coll->get_coll_id(); - } - else { - $this->output->writeln(sprintf("unknown collection (%s)", $c)); - return false; - } - } - if(empty($actions)) { $this->output->writeln(sprintf("no actions defined")); return false;