mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-16 06:23:18 +00:00
PHRAS-4078_ExpiringRight-to-core (#4526)
* wip do not merge * fix event EXPORT_CREATE for basket download * fix bad conf (not a worker) * fix bad table name
This commit is contained in:
@@ -14,6 +14,7 @@ namespace KonsoleKommander;
|
|||||||
use Alchemy\Phrasea\CLI;
|
use Alchemy\Phrasea\CLI;
|
||||||
use Alchemy\Phrasea\Command\ApplyRightsCommand;
|
use Alchemy\Phrasea\Command\ApplyRightsCommand;
|
||||||
use Alchemy\Phrasea\Command\BuildMissingSubdefs;
|
use Alchemy\Phrasea\Command\BuildMissingSubdefs;
|
||||||
|
use Alchemy\Phrasea\Command\ExpiringRights\AlertExpiringRightsCommand;
|
||||||
use Alchemy\Phrasea\Command\Record\BuildPermalinks;
|
use Alchemy\Phrasea\Command\Record\BuildPermalinks;
|
||||||
use Alchemy\Phrasea\Command\Record\BuildSubdefs;
|
use Alchemy\Phrasea\Command\Record\BuildSubdefs;
|
||||||
use Alchemy\Phrasea\Command\CheckConfig;
|
use Alchemy\Phrasea\Command\CheckConfig;
|
||||||
@@ -194,6 +195,8 @@ $cli->command(new SendValidationRemindersCommand());
|
|||||||
|
|
||||||
$cli->command(new NetworkProxiesTestCommand('network-proxies:test'));
|
$cli->command(new NetworkProxiesTestCommand('network-proxies:test'));
|
||||||
|
|
||||||
|
$cli->command(new AlertExpiringRightsCommand());
|
||||||
|
|
||||||
$cli->loadPlugins();
|
$cli->loadPlugins();
|
||||||
|
|
||||||
$cli->run();
|
$cli->run();
|
||||||
|
@@ -489,3 +489,42 @@ translator:
|
|||||||
# end of job : change coll status
|
# end of job : change coll status
|
||||||
set_status: 10xxxx
|
set_status: 10xxxx
|
||||||
set_collection: online
|
set_collection: online
|
||||||
|
expiring-rights:
|
||||||
|
version: 3
|
||||||
|
jobs:
|
||||||
|
# "I want to alert owners that records have expired"
|
||||||
|
- rights-expired-owners:
|
||||||
|
active: false
|
||||||
|
target: "owners"
|
||||||
|
databox: "db_with_rights"
|
||||||
|
collection: [ "Promo", "Selections" ]
|
||||||
|
expire_field: ExpireDate
|
||||||
|
prior_notice: -60
|
||||||
|
set_status: 01xxxx
|
||||||
|
alerts:
|
||||||
|
- method: webhook
|
||||||
|
recipient: ["bob@a.fr", "joe@b.com"]
|
||||||
|
|
||||||
|
# "I want to alert users who have downloaded that a document rights will expire in 60 days"
|
||||||
|
- rights-60-downloaders:
|
||||||
|
active: false
|
||||||
|
target: "downloaders"
|
||||||
|
databox: "db_with_rights"
|
||||||
|
collection: [ "Promo", "Selections" ]
|
||||||
|
downloaded: [ "document", "preview" ]
|
||||||
|
expire_field: "ExpirationDate"
|
||||||
|
prior_notice: -60
|
||||||
|
alerts:
|
||||||
|
- method: "webhook"
|
||||||
|
|
||||||
|
# "I want to alert users who have downloaded that a document rights has expired"
|
||||||
|
- rights-expired-dowloaders:
|
||||||
|
active: false
|
||||||
|
target: "downloaders"
|
||||||
|
databox: "db_with_rights"
|
||||||
|
collection: [ "Promo", "Selections" ]
|
||||||
|
downloaded: [ "document", "preview" ]
|
||||||
|
expire_field: "ExpirationDate"
|
||||||
|
prior_notice: 0
|
||||||
|
alerts:
|
||||||
|
- method: "webhook"
|
||||||
|
@@ -20,6 +20,7 @@ use Alchemy\Phrasea\Application\RouteLoader;
|
|||||||
use Alchemy\Phrasea\Authorization\AuthorizationServiceProvider;
|
use Alchemy\Phrasea\Authorization\AuthorizationServiceProvider;
|
||||||
use Alchemy\Phrasea\Core\Event\Subscriber\BasketSubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\BasketSubscriber;
|
||||||
use Alchemy\Phrasea\Core\Event\Subscriber\BridgeSubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\BridgeSubscriber;
|
||||||
|
use Alchemy\Phrasea\Core\Event\Subscriber\ExpiringRightsSubscriber;
|
||||||
use Alchemy\Phrasea\Core\Event\Subscriber\ExportSubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\ExportSubscriber;
|
||||||
use Alchemy\Phrasea\Core\Event\Subscriber\FeedEntrySubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\FeedEntrySubscriber;
|
||||||
use Alchemy\Phrasea\Core\Event\Subscriber\LazaretSubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\LazaretSubscriber;
|
||||||
@@ -27,6 +28,7 @@ use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaInstallSubscriber;
|
|||||||
use Alchemy\Phrasea\Core\Event\Subscriber\RegistrationSubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\RegistrationSubscriber;
|
||||||
use Alchemy\Phrasea\Core\Event\Subscriber\ValidationSubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\ValidationSubscriber;
|
||||||
use Alchemy\Phrasea\Core\Event\Subscriber\WebhookUserEventSubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\WebhookUserEventSubscriber;
|
||||||
|
use Alchemy\Phrasea\Core\LazyLocator;
|
||||||
use Alchemy\Phrasea\Core\MetaProvider\DatabaseMetaProvider;
|
use Alchemy\Phrasea\Core\MetaProvider\DatabaseMetaProvider;
|
||||||
use Alchemy\Phrasea\Core\MetaProvider\HttpStackMetaProvider;
|
use Alchemy\Phrasea\Core\MetaProvider\HttpStackMetaProvider;
|
||||||
use Alchemy\Phrasea\Core\MetaProvider\MediaUtilitiesMetaServiceProvider;
|
use Alchemy\Phrasea\Core\MetaProvider\MediaUtilitiesMetaServiceProvider;
|
||||||
@@ -767,6 +769,8 @@ class Application extends SilexApplication
|
|||||||
$dispatcher->addSubscriber(new WebhookUserEventSubscriber($app));
|
$dispatcher->addSubscriber(new WebhookUserEventSubscriber($app));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$dispatcher->addSubscriber(new ExpiringRightsSubscriber($app, new LazyLocator($app, 'phraseanet.appbox')));
|
||||||
|
|
||||||
return $dispatcher;
|
return $dispatcher;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@@ -0,0 +1,591 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of phraseanet-plugins.
|
||||||
|
*
|
||||||
|
* (c) Alchemy <info@alchemy.fr>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Command\ExpiringRights;
|
||||||
|
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Command\Command;
|
||||||
|
use Alchemy\Phrasea\Model\Manipulator\WebhookEventManipulator;
|
||||||
|
use appbox;
|
||||||
|
use collection;
|
||||||
|
use databox;
|
||||||
|
use Doctrine\DBAL\DBALException;
|
||||||
|
use PDO;
|
||||||
|
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use function igorw\get_in;
|
||||||
|
|
||||||
|
|
||||||
|
class AlertExpiringRightsCommand extends Command
|
||||||
|
{
|
||||||
|
const RIGHT_SHORTENED = 'shortened';
|
||||||
|
const RIGHT_EXTENDED = 'extended';
|
||||||
|
const RIGHT_EXPIRING = 'expiring';
|
||||||
|
|
||||||
|
/** @var InputInterface $input */
|
||||||
|
private $input;
|
||||||
|
/** @var OutputInterface $output */
|
||||||
|
private $output;
|
||||||
|
/** @var appbox $appbox */
|
||||||
|
private $appbox;
|
||||||
|
/** @var array $databoxes */
|
||||||
|
private $databoxes;
|
||||||
|
|
||||||
|
private $now = null;
|
||||||
|
|
||||||
|
public function configure()
|
||||||
|
{
|
||||||
|
$this->setName("workflow:expiring:run")
|
||||||
|
->setDescription('alert owners and users of expiring records')
|
||||||
|
->addOption('dry', null, InputOption::VALUE_NONE, "Dry run (list alerts but don't insert in webhooks).")
|
||||||
|
->addOption('show-sql', null, InputOption::VALUE_NONE, "Show the selection sql.")
|
||||||
|
->addOption('dump-webhooks', null, InputOption::VALUE_NONE, "Show the webhooks data.")
|
||||||
|
->addOption('now', null, InputOption::VALUE_REQUIRED, "for testing : fake the 'today' date (format=YYYYMMDD).")
|
||||||
|
// ->setHelp('')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$this->input = $input;
|
||||||
|
// add cool styles
|
||||||
|
$style = new OutputFormatterStyle('black', 'yellow'); // , array('bold'));
|
||||||
|
$output->getFormatter()->setStyle('warning', $style);
|
||||||
|
$this->output = $output;
|
||||||
|
|
||||||
|
// sanitize parameters
|
||||||
|
if(($now = $input->getOption('now')) !== null) {
|
||||||
|
if(preg_match("/^[0-9]{8}$/", $now) === 1) {
|
||||||
|
$this->now = $now;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->output->writeln(sprintf("<error>bad format for 'now' (%s) option (must be YYYYMMDD)</error>", $now));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->appbox = $this->container['phraseanet.appbox'];
|
||||||
|
|
||||||
|
// list databoxes and collections to access by id or by name
|
||||||
|
$this->databoxes = [];
|
||||||
|
foreach ($this->appbox->get_databoxes() as $databox) {
|
||||||
|
$sbas_id = $databox->get_sbas_id();
|
||||||
|
$sbas_name = $databox->get_dbname();
|
||||||
|
$this->databoxes[$sbas_id] = [
|
||||||
|
'dbox' => $databox,
|
||||||
|
'collections' => []
|
||||||
|
];
|
||||||
|
$this->databoxes[$sbas_name] = &$this->databoxes[$sbas_id];
|
||||||
|
// list all collections
|
||||||
|
foreach ($databox->get_collections() as $collection) {
|
||||||
|
$coll_id = $collection->get_coll_id();
|
||||||
|
$coll_name = $collection->get_name();
|
||||||
|
$this->databoxes[$sbas_id]['collections'][$coll_id] = $collection;
|
||||||
|
$this->databoxes[$sbas_id]['collections'][$coll_name] = &$this->databoxes[$sbas_id]['collections'][$coll_id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// play jobs
|
||||||
|
$ret = 0;
|
||||||
|
foreach ($this->container['conf']->get(['expiring-rights', 'jobs'], []) as $jobname => &$job) {
|
||||||
|
if($job['active']) {
|
||||||
|
if (!$this->playJob($jobname, $job)) {
|
||||||
|
$this->output->writeln(sprintf("<error>job skipped</error>"));
|
||||||
|
$ret = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $jobname
|
||||||
|
* @param $job
|
||||||
|
* @return bool
|
||||||
|
* @throws DBALException
|
||||||
|
*/
|
||||||
|
private function playJob($jobname, $job)
|
||||||
|
{
|
||||||
|
$this->output->writeln(sprintf("\n\n<info>======== Playing job \"%s\" ========</info>\n", $jobname));
|
||||||
|
|
||||||
|
// ensure that the job syntax is ok
|
||||||
|
if (!$this->sanitizeJob($job)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($job['target']) {
|
||||||
|
case "owners":
|
||||||
|
return $this->playJobOwners($jobname, $job);
|
||||||
|
break;
|
||||||
|
case "downloaders":
|
||||||
|
return $this->playJobDownloaders($jobname, $job);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->output->writeln(sprintf("alert>bad target \"%s\" (should be \"owners\" or \"downloaders\")</alert>\n", $job['target']));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function playJobOwners($jobname, $job)
|
||||||
|
{
|
||||||
|
// ensure that the job syntax is ok
|
||||||
|
if (!$this->sanitizeJobOwners($job)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_in($job, ['active'], false) === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build sql where clause
|
||||||
|
$wheres = [];
|
||||||
|
|
||||||
|
// clause on databox ?
|
||||||
|
$d = $job['databox'];
|
||||||
|
if (!is_string($d) && !is_int($d)) {
|
||||||
|
$this->output->writeln(sprintf("<error>bad databox clause</error>"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!array_key_exists($d, $this->databoxes)) {
|
||||||
|
$this->output->writeln(sprintf("<error>unknown databox (%s)</error>", $d));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the sbas_id for the databox of this job
|
||||||
|
/** @var Databox $dbox */
|
||||||
|
$dbox = $this->databoxes[$d]['dbox'];
|
||||||
|
$sbas_id = $dbox->get_sbas_id();
|
||||||
|
|
||||||
|
// filter on collections ?
|
||||||
|
$collList = [];
|
||||||
|
foreach (get_in($job, ['collection'], []) as $c) {
|
||||||
|
/** @var collection $coll */
|
||||||
|
if (($coll = get_in($this->databoxes[$sbas_id], ['collections', $c])) !== null) {
|
||||||
|
$collList[] = $dbox->get_connection()->quote($coll->get_coll_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($collList)) {
|
||||||
|
if(count($collList) === 1) {
|
||||||
|
$wheres[] = "r.`coll_id`=" . $collList[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$wheres[] = "r.`coll_id` IN(" . join(',', $collList) . ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clause on sb (negated)
|
||||||
|
$mask = get_in($job, ['set_status']);
|
||||||
|
if ($mask === null) {
|
||||||
|
$this->output->writeln(sprintf("<error>missing 'set_status' clause</error>"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$m = preg_replace('/[^0-1]/', 'x', trim($mask));
|
||||||
|
if (strlen($m) > 32) {
|
||||||
|
$this->output->writeln(sprintf("<error>status mask (%s) too long</error>", $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("<error>empty status mask</error>"));
|
||||||
|
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 . ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// clause on expiration date
|
||||||
|
// the NOW() can be faked for testing
|
||||||
|
$expire_field_id = null;
|
||||||
|
foreach ($dbox->get_meta_structure() as $dbf) {
|
||||||
|
if ($dbf->get_name() === $job['expire_field']) {
|
||||||
|
$expire_field_id = $dbf->get_id();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($expire_field_id === null) {
|
||||||
|
$this->output->writeln(sprintf("<error>unknown field (%s)</error>", $job['expire_field']));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$now = $this->now === null ? "NOW()" : $this->appbox->get_connection()->quote($this->now);
|
||||||
|
$delta = (int)$job['prior_notice'];
|
||||||
|
if ($delta > 0) {
|
||||||
|
$value = "(`expire`+INTERVAL " . $delta . " DAY)";
|
||||||
|
}
|
||||||
|
elseif ($delta < 0) {
|
||||||
|
$value = "(`expire`-INTERVAL " . -$delta . " DAY)";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$value = "`expire`";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_where = count($wheres) > 0 ? " WHERE " . join(" AND ", $wheres) : "";
|
||||||
|
$sql = "SELECT t.*, DATEDIFF(`expire`, " . $now . ") AS 'expire_in' FROM (\n"
|
||||||
|
. " SELECT r.`record_id`, CAST(CAST(m.`value` AS DATE) AS DATETIME) AS `expire`\n"
|
||||||
|
. " FROM `record` AS r INNER JOIN `metadatas` AS m\n"
|
||||||
|
. " ON m.`record_id`=r.`record_id` AND m.`meta_struct_id`=" . $dbox->get_connection()->quote($expire_field_id, PDO::PARAM_INT) . "\n"
|
||||||
|
. $sql_where
|
||||||
|
. ") AS t WHERE " . $now . ">=" . $value;
|
||||||
|
|
||||||
|
if ($this->input->getOption('show-sql')) {
|
||||||
|
$this->output->writeln(sprintf("sql: %s", $sql));
|
||||||
|
}
|
||||||
|
|
||||||
|
// play sql
|
||||||
|
$records = [];
|
||||||
|
$stmt = $dbox->get_connection()->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($this->input->getOption('dry')) {
|
||||||
|
$this->output->writeln(sprintf("<info>dry mode: updates on %d records NOT executed</info>", $stmt->rowCount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stmt->closeCursor();
|
||||||
|
|
||||||
|
$n_records = count($records);
|
||||||
|
if($n_records === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
foreach ($job['alerts'] as $alert) {
|
||||||
|
$method = get_in($alert, ['method']);
|
||||||
|
switch($method) {
|
||||||
|
case "webhook":
|
||||||
|
$payload = [
|
||||||
|
'job' => $jobname,
|
||||||
|
'delta' => $delta,
|
||||||
|
'email' => $alert['recipient'],
|
||||||
|
'sbas_id' => $dbox->get_sbas_id(),
|
||||||
|
'base' => $dbox->get_viewname(),
|
||||||
|
'records' => $records
|
||||||
|
];
|
||||||
|
if ($this->input->getOption('dry')) {
|
||||||
|
$this->output->writeln(
|
||||||
|
sprintf(
|
||||||
|
"dry run : webhook about %d record(s) to [%s] NOT inserted",
|
||||||
|
$n_records, join(',', $alert['recipient'])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->output->writeln(
|
||||||
|
sprintf(
|
||||||
|
"Inserting webhook about %d record(s) to [%s]",
|
||||||
|
$n_records, join(',', $alert['recipient'])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
/** @var WebhookEventManipulator $manipulator */
|
||||||
|
$webhookManipulator = $this->container['manipulator.webhook-event'];
|
||||||
|
$webhookManipulator->create(
|
||||||
|
"Expiring.Rights.Records",
|
||||||
|
"Expiring.Rights",
|
||||||
|
$payload
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if($this->input->getOption("dump-webhooks")) {
|
||||||
|
$this->output->writeln("webhook: \"Expiring.Rights.Records\", \"Expiring.Rights\"\npayload=");
|
||||||
|
$this->output->writeln(json_encode($payload, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->output->writeln(sprintf("<warning>bad or undefined alert method (%s), ignored</warning>", $method));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function playJobDownloaders($jobname, $job)
|
||||||
|
{
|
||||||
|
// ensure that the job syntax is ok
|
||||||
|
if (!$this->sanitizeJobDownloaders($job)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_in($job, ['active'], false) === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build sql where clause
|
||||||
|
$wheres = [
|
||||||
|
'`job` = ' . $this->appbox->get_connection()->quote($jobname)
|
||||||
|
];
|
||||||
|
|
||||||
|
// clause on databox
|
||||||
|
$databox = $job['databox'];
|
||||||
|
if(!is_string($databox) && !is_int($databox)) {
|
||||||
|
$this->output->writeln(sprintf("<error>bad databox clause</error>"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!array_key_exists($databox, $this->databoxes)) {
|
||||||
|
$this->output->writeln(sprintf("<error>unknown databox (%s)</error>", $job['databox']));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// find the sbas_id for the databox of this job
|
||||||
|
/** @var Databox $dbox */
|
||||||
|
$dbox = $this->databoxes[$databox]['dbox'];
|
||||||
|
$sbas_id = $dbox->get_sbas_id();
|
||||||
|
$wheres[] = "(`sbas_id`=" . $dbox->get_connection()->quote($sbas_id) . ")";
|
||||||
|
|
||||||
|
$wheres[] = "(ISNULL(`alerted`) OR !ISNULL(`new_expire`))";
|
||||||
|
|
||||||
|
// clause on expiration date
|
||||||
|
// the NOW() can be faked for testing
|
||||||
|
$now = $this->now === null ? "NOW()" : $this->appbox->get_connection()->quote($this->now);
|
||||||
|
$delta = (int)$job['prior_notice'];
|
||||||
|
if ($delta > 0) {
|
||||||
|
$value = "(real_expire+INTERVAL " . $delta . " DAY)";
|
||||||
|
} elseif ($delta < 0) {
|
||||||
|
$value = "(real_expire-INTERVAL " . -$delta . " DAY)";
|
||||||
|
} else {
|
||||||
|
$value = "real_expire";
|
||||||
|
}
|
||||||
|
|
||||||
|
// build SELECT sql
|
||||||
|
$sql_where = join("\n AND", $wheres);
|
||||||
|
$sql = "SELECT t.*, DATEDIFF(real_expire, " . $now . ") AS 'expire_in' FROM (\n"
|
||||||
|
. " SELECT *, COALESCE(`expire`, `new_expire`) AS `real_expire` FROM `ExpiringRights`\n"
|
||||||
|
. " WHERE " . $sql_where . "\n"
|
||||||
|
. ") AS t\n"
|
||||||
|
. "WHERE !ISNULL(real_expire) AND " . $now . ">=" . $value . "\n"
|
||||||
|
. "ORDER BY `user_id`";
|
||||||
|
|
||||||
|
if($this->input->getOption('show-sql')) {
|
||||||
|
$this->output->writeln(sprintf("%s", $sql));
|
||||||
|
}
|
||||||
|
|
||||||
|
// play sql
|
||||||
|
$usersById = [];
|
||||||
|
|
||||||
|
$n_records = [
|
||||||
|
"all" => 0,
|
||||||
|
self::RIGHT_EXPIRING => 0,
|
||||||
|
self::RIGHT_EXTENDED => 0,
|
||||||
|
self::RIGHT_SHORTENED => 0
|
||||||
|
];
|
||||||
|
$stmt = $this->appbox->get_connection()->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||||
|
$type = null;
|
||||||
|
if($row['new_expire'] !== null) {
|
||||||
|
if($row['expire'] === null || $row['alerted'] === null) {
|
||||||
|
// same thing as new expire
|
||||||
|
$type = self::RIGHT_EXPIRING;
|
||||||
|
}
|
||||||
|
elseif($row['new_expire'] < $row['expire']) {
|
||||||
|
// shortened
|
||||||
|
$type = self::RIGHT_SHORTENED;
|
||||||
|
}
|
||||||
|
elseif($row['new_expire'] > $row['expire']) {
|
||||||
|
// extended
|
||||||
|
$type = self::RIGHT_EXTENDED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// same date (should not happen ?)
|
||||||
|
$type = self::RIGHT_EXPIRING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// new_expire === null
|
||||||
|
if($row['alerted'] === null) {
|
||||||
|
$type = self::RIGHT_EXPIRING;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// nothing to do (should not happen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($type !== null) {
|
||||||
|
$user_id = $row['user_id'];
|
||||||
|
if(!array_key_exists($user_id, $usersById)) {
|
||||||
|
$usersById[$user_id] = [
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'email' => $row['email'],
|
||||||
|
'records' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
unset($row['id'], $row['job'], $row['user_id'], $row['email'], $row['sbas_id'], $row['base']);
|
||||||
|
$row['type'] = $type;
|
||||||
|
$usersById[$user_id]['records'][] = $row;
|
||||||
|
|
||||||
|
$n_records[$type]++;
|
||||||
|
}
|
||||||
|
$n_records['all']++;
|
||||||
|
}
|
||||||
|
$stmt->closeCursor();
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'job' => $jobname,
|
||||||
|
'sbas_id' => $sbas_id,
|
||||||
|
'base' => $dbox->get_viewname(),
|
||||||
|
'delta' => $delta,
|
||||||
|
'users' => array_values($usersById)
|
||||||
|
];
|
||||||
|
unset($usersById);
|
||||||
|
|
||||||
|
if($n_records['all'] > 0 && !$this->input->getOption('dry')) {
|
||||||
|
// build UPDATE sql
|
||||||
|
$sql = "UPDATE `ExpiringRights` SET expire=COALESCE(new_expire, expire), new_expire=NULL, alerted=" . $now . "\n"
|
||||||
|
. " WHERE " . $sql_where;
|
||||||
|
$stmt = $this->appbox->get_connection()->prepare($sql);
|
||||||
|
$stmt->execute([]);
|
||||||
|
$this->appbox->get_connection()->exec($sql);
|
||||||
|
$stmt->closeCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->writeln(
|
||||||
|
sprintf(
|
||||||
|
"%d records selected (%s expiring, %s shortened, %s extended)",
|
||||||
|
$n_records['all'],
|
||||||
|
$n_records[self::RIGHT_EXPIRING],
|
||||||
|
$n_records[self::RIGHT_SHORTENED],
|
||||||
|
$n_records[self::RIGHT_EXTENDED]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($n_records['all'] > 0 ) {
|
||||||
|
foreach ($job['alerts'] as $alert) {
|
||||||
|
switch ($alert['method']) {
|
||||||
|
case 'webhook':
|
||||||
|
if ($this->input->getOption('dry')) {
|
||||||
|
$this->output->writeln(
|
||||||
|
sprintf(
|
||||||
|
"<info>dry run : webhook about %d record(s) NOT inserted</info>",
|
||||||
|
$n_records['all']
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->output->writeln(
|
||||||
|
sprintf(
|
||||||
|
"<info>webhook about %d record(s) inserted</info>",
|
||||||
|
$n_records['all']
|
||||||
|
)
|
||||||
|
);
|
||||||
|
/** @var WebhookEventManipulator $manipulator */
|
||||||
|
$webhookManipulator = $this->container['manipulator.webhook-event'];
|
||||||
|
$webhookManipulator->create(
|
||||||
|
"Expiring.Rights.Downloaded",
|
||||||
|
"Expiring.Rights",
|
||||||
|
$payload
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if($this->input->getOption("dump-webhooks")) {
|
||||||
|
$this->output->writeln("webhook: \"Expiring.Rights.Downloaded\", \"Expiring.Rights\"\npayload=");
|
||||||
|
$this->output->writeln(json_encode($payload, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
$this->output->writeln(sprintf("<error>unknown alert method \"%s\"</error>", $alert['method']));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check that a yaml->php block is ok against rules
|
||||||
|
*
|
||||||
|
* @param array $object
|
||||||
|
* @param array $rules
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function sanitize(array $object, array $rules)
|
||||||
|
{
|
||||||
|
$object_ok = true;
|
||||||
|
|
||||||
|
foreach ($rules as $key => $fsanitize) {
|
||||||
|
if (!array_key_exists($key, $object) || !($fsanitize($object[$key]))) {
|
||||||
|
$this->output->writeln(sprintf("<error>missing or bad format setting \"%s\"</error>", $key));
|
||||||
|
$object_ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $object_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check that a job (first level block) is ok
|
||||||
|
*
|
||||||
|
* @param $job
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function sanitizeJob($job)
|
||||||
|
{
|
||||||
|
return $this->sanitize(
|
||||||
|
$job,
|
||||||
|
[
|
||||||
|
'active' => "is_bool",
|
||||||
|
'target' => function($v) {return in_array($v, ['owners', 'downloaders']);},
|
||||||
|
'databox' => "is_string",
|
||||||
|
'prior_notice' => 'is_int',
|
||||||
|
'expire_field' => 'is_string',
|
||||||
|
'alerts' => 'is_array'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeJobOwners($job)
|
||||||
|
{
|
||||||
|
return $this->sanitize(
|
||||||
|
$job,
|
||||||
|
[
|
||||||
|
'set_status' => "is_string",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeJobDownloaders($job)
|
||||||
|
{
|
||||||
|
return true; // sanitizeJob is enough
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -67,6 +67,12 @@ class DownloadController extends Controller
|
|||||||
$list['include_report'] = $request->request->get('include_report') === 'INCLUDE_REPORT';
|
$list['include_report'] = $request->request->get('include_report') === 'INCLUDE_REPORT';
|
||||||
$list['include_businessfields'] = (bool)$request->request->get('businessfields');
|
$list['include_businessfields'] = (bool)$request->request->get('businessfields');
|
||||||
|
|
||||||
|
$lst = [];
|
||||||
|
foreach ($list['files'] as $file) {
|
||||||
|
$lst[] = $this->getApplicationBox()->get_collection($file['base_id'])->get_databox()->get_sbas_id() . '_' . $file['record_id'];
|
||||||
|
}
|
||||||
|
$lst = join(';', $lst);
|
||||||
|
|
||||||
$token = $this->getTokenManipulator()->createDownloadToken($this->getAuthenticatedUser(), serialize($list));
|
$token = $this->getTokenManipulator()->createDownloadToken($this->getAuthenticatedUser(), serialize($list));
|
||||||
|
|
||||||
$this->getDispatcher()->dispatch(PhraseaEvents::EXPORT_CREATE, new ExportEvent(
|
$this->getDispatcher()->dispatch(PhraseaEvents::EXPORT_CREATE, new ExportEvent(
|
||||||
@@ -136,6 +142,12 @@ class DownloadController extends Controller
|
|||||||
$records[sprintf('%s_%s', $sbasId, $file['record_id'])] = $record;
|
$records[sprintf('%s_%s', $sbasId, $file['record_id'])] = $record;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$lst = [];
|
||||||
|
foreach ($list['files'] as $file) {
|
||||||
|
$lst[] = $this->getApplicationBox()->get_collection($file['base_id'])->get_databox()->get_sbas_id() . '_' . $file['record_id'];
|
||||||
|
}
|
||||||
|
$lst = join(';', $lst);
|
||||||
|
|
||||||
$token = $this->getTokenManipulator()->createDownloadToken($this->getAuthenticatedUser(), serialize($list));
|
$token = $this->getTokenManipulator()->createDownloadToken($this->getAuthenticatedUser(), serialize($list));
|
||||||
|
|
||||||
$pusher_auth_key =$this->getConf()->get(['download_async', 'enabled'], false) ? $this->getConf()->get(['externalservice', 'pusher', 'auth_key'], '') : null;
|
$pusher_auth_key =$this->getConf()->get(['download_async', 'enabled'], false) ? $this->getConf()->get(['externalservice', 'pusher', 'auth_key'], '') : null;
|
||||||
|
@@ -0,0 +1,223 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Core\Event\Subscriber;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Application;
|
||||||
|
use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware;
|
||||||
|
use Alchemy\Phrasea\Core\Event\ExportEvent;
|
||||||
|
use Alchemy\Phrasea\Core\Event\RecordEdit;
|
||||||
|
use Alchemy\Phrasea\Core\PhraseaEvents;
|
||||||
|
use appbox;
|
||||||
|
use databox;
|
||||||
|
use Doctrine\DBAL\DBALException;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
|
class ExpiringRightsSubscriber implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
use DataboxLoggerAware;
|
||||||
|
|
||||||
|
/** @var Application */
|
||||||
|
private $app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
private $appboxLocator;
|
||||||
|
|
||||||
|
public function __construct(Application $app, callable $appboxLocator)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
$this->appboxLocator = $appboxLocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getJobsByDb($sbas_id)
|
||||||
|
{
|
||||||
|
static $jobsByDb = [];
|
||||||
|
static $jobsByName = null;
|
||||||
|
|
||||||
|
if($jobsByName === null) {
|
||||||
|
$jobsByName = [];
|
||||||
|
foreach ($this->app['conf']->get(['expiring-rights', 'jobs'], []) as $jobname => &$job) {
|
||||||
|
// nb: we must include inactive jobs so every download is recorded
|
||||||
|
$job['_c'] = $job['collection'];
|
||||||
|
unset($job['collection']);
|
||||||
|
$jobsByName[$jobname] = $job;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sbas_id = (int)$sbas_id;
|
||||||
|
if(!array_key_exists($sbas_id, $jobsByDb)) {
|
||||||
|
$jobsByDb[$sbas_id] = [];
|
||||||
|
/** @var appbox $abox */
|
||||||
|
$abox = $this->app['phraseanet.appbox'];
|
||||||
|
$dbox = $abox->get_databox($sbas_id);
|
||||||
|
foreach($jobsByName as $jobname => &$job) {
|
||||||
|
if($job['databox'] === $sbas_id || $job['databox'] === $dbox->get_dbname()) {
|
||||||
|
// patch the collections filter to have id's and names
|
||||||
|
if(!array_key_exists('collection', $job)) {
|
||||||
|
$job['collection'] = [];
|
||||||
|
foreach ($dbox->get_collections() as $coll) {
|
||||||
|
if(empty($job['_c']) ||
|
||||||
|
in_array($coll->get_coll_id(), $job['_c'], true) ||
|
||||||
|
in_array($coll->get_name(), $job['_c'], true))
|
||||||
|
{
|
||||||
|
$job['collection'][] = $coll->get_coll_id();
|
||||||
|
$job['collection'][] = $coll->get_name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$jobsByDb[$sbas_id][$jobname] = &$jobsByName[$jobname];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_key_exists($sbas_id, $jobsByDb) ? $jobsByDb[$sbas_id] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when someone downloads, if the base has a declared "expire_field"
|
||||||
|
* insert an entry in our table
|
||||||
|
*
|
||||||
|
* @param ExportEvent $event
|
||||||
|
* @param string $eventName
|
||||||
|
*/
|
||||||
|
public function onExportCreate(ExportEvent $event, $eventName)
|
||||||
|
{
|
||||||
|
$user_id = $event->getUser()->getId();
|
||||||
|
$email = $event->getUser()->getEmail();
|
||||||
|
/** @var appbox $abox */
|
||||||
|
$abox = $this->getApplicationBox();
|
||||||
|
$stmt = null;
|
||||||
|
|
||||||
|
foreach(explode(';', $event->getList()) as $sbid_rid) {
|
||||||
|
list($sbas_id, $record_id) = explode('_', $sbid_rid);
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var databox $dbox */
|
||||||
|
$dbox = $abox->get_databox($sbas_id);
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($this->getJobsByDb($sbas_id) as $jobname => $job) {
|
||||||
|
if($job['target'] !== "downloaders") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = $dbox->get_record($record_id);
|
||||||
|
|
||||||
|
// get the expire_field unique value
|
||||||
|
$expire_value = null;
|
||||||
|
$expire_field = $job['expire_field'];
|
||||||
|
$fields = $record->getCaption([$expire_field]);
|
||||||
|
if (array_key_exists($expire_field, $fields) && count($fields[$expire_field]) === 1) {
|
||||||
|
try {
|
||||||
|
$expire_value = (new \DateTime($fields[$expire_field][0]))->format('Y-m-d'); // drop hour, minutes...
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
// bad date format ? set null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// insert
|
||||||
|
if(!$stmt) {
|
||||||
|
// first sql
|
||||||
|
$sql = "INSERT INTO `ExpiringRights` (job, downloaded, user_id, email, sbas_id, base, collection, record_id, title, expire)\n"
|
||||||
|
. " VALUES (:job, NOW(), :user_id, :email, :sbas_id, :base, :collection, :record_id, :title, :expire)";
|
||||||
|
$stmt = $abox->get_connection()->prepare($sql);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$stmt->execute([
|
||||||
|
':job' => $jobname,
|
||||||
|
':user_id' => $user_id,
|
||||||
|
':email' => $email,
|
||||||
|
':sbas_id' => $sbas_id,
|
||||||
|
':base' => $dbox->get_viewname(), // fallback of get_label() because we may not know the current language
|
||||||
|
':collection' => $record->getCollection()->get_name(),
|
||||||
|
':record_id' => $record_id,
|
||||||
|
':title' => $record->get_title(),
|
||||||
|
':expire' => $expire_value
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
// duplicate (same job+user+sbas_id+record_id) ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if($stmt) {
|
||||||
|
$stmt->closeCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when the "expire_field" is edited, update the new value for every downloaded record
|
||||||
|
*
|
||||||
|
* @param RecordEdit $event
|
||||||
|
* @param string $eventName
|
||||||
|
* @throws DBALException
|
||||||
|
*/
|
||||||
|
public function onRecordEdit(RecordEdit $event, $eventName)
|
||||||
|
{
|
||||||
|
$record = $event->getRecord();
|
||||||
|
$sbas_id = $record->getDataboxId();
|
||||||
|
|
||||||
|
// get settings for this databox
|
||||||
|
foreach($this->getJobsByDb($sbas_id) as $job) {
|
||||||
|
if($job['target'] !== "downloaders") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$expire_field = $job['expire_field'];
|
||||||
|
$expire_value = null;
|
||||||
|
$record_id = $record->getRecordId();
|
||||||
|
$fields = $record->getCaption([$expire_field]);
|
||||||
|
if (array_key_exists($expire_field, $fields) && count($fields[$expire_field]) > 0) {
|
||||||
|
$expire_value = $fields[$expire_field][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var appbox $abox */
|
||||||
|
$abox = $this->getApplicationBox();
|
||||||
|
$sql = "UPDATE `ExpiringRights` SET new_expire = :new_expire\n"
|
||||||
|
. " WHERE sbas_id = :sbas_id AND record_id = :record_id AND (IFNULL(expire, 0) != IFNULL(:expire, 0) OR !ISNULL(new_expire))";
|
||||||
|
$stmt = $abox->get_connection()->prepare($sql);
|
||||||
|
$stmt->execute($p = [
|
||||||
|
':new_expire' => $expire_value,
|
||||||
|
':sbas_id' => $sbas_id,
|
||||||
|
':record_id' => $record_id,
|
||||||
|
':expire' => $expire_value
|
||||||
|
]);
|
||||||
|
$stmt->closeCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
/** @uses onRecordEdit */
|
||||||
|
PhraseaEvents::RECORD_EDIT => 'onRecordEdit',
|
||||||
|
/** @uses onExportCreate */
|
||||||
|
PhraseaEvents::EXPORT_CREATE => 'onExportCreate',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return appbox
|
||||||
|
*/
|
||||||
|
private function getApplicationBox()
|
||||||
|
{
|
||||||
|
static $appbox = null;
|
||||||
|
if($appbox === null) {
|
||||||
|
$callable = $this->appboxLocator;
|
||||||
|
$appbox = $callable();
|
||||||
|
}
|
||||||
|
return $appbox;
|
||||||
|
}
|
||||||
|
}
|
@@ -17,7 +17,7 @@ class Version
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private $number = '4.1.8';
|
private $number = '4.1.9';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
|
268
lib/classes/patch/419PHRAS4078.php
Normal file
268
lib/classes/patch/419PHRAS4078.php
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Application;
|
||||||
|
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
|
||||||
|
use Doctrine\DBAL\Exception\TableExistsException;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class patch_419PHRAS4078 implements patchInterface
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $release = '4.1.9';
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $concern = [base::APPLICATION_BOX];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function get_release()
|
||||||
|
{
|
||||||
|
return $this->release;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getDoctrineMigrations()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function require_all_upgrades()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function concern()
|
||||||
|
{
|
||||||
|
return $this->concern;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function apply(base $base, Application $app)
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
$this->logger = $app['logger'];
|
||||||
|
|
||||||
|
if ($base->get_base_type() === base::DATA_BOX) {
|
||||||
|
$this->patch_databox($base, $app);
|
||||||
|
} elseif ($base->get_base_type() === base::APPLICATION_BOX) {
|
||||||
|
$this->patch_appbox($base, $app);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function patch_databox(databox $databox, Application $app)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private function patch_appbox(base $appbox, Application $app)
|
||||||
|
{
|
||||||
|
$this->migrateConfig();
|
||||||
|
$this->migrateTable($appbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TABLENAME = "_ExpiringRights";
|
||||||
|
const TABLENAME3 = "ExpiringRights";
|
||||||
|
const CONFIG_DIR = "/config/plugins/expirating-rights-plugin/";
|
||||||
|
const CONFIG_FILE = "configuration.yml";
|
||||||
|
/** @var Application $app */
|
||||||
|
private $app;
|
||||||
|
/** @var LoggerInterface $logger */
|
||||||
|
private $logger;
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
private function migrateConfig()
|
||||||
|
{
|
||||||
|
/** @var PropertyAccess $conf */
|
||||||
|
$phrconfig = $this->app['conf'];
|
||||||
|
|
||||||
|
if ($phrconfig->has(['expiring-rights'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// locate the config for the ExpiringRights plugin (v1 or v3)
|
||||||
|
$config_dir = $this->app['root.path'] . self::CONFIG_DIR;
|
||||||
|
$config_file = $config_dir . self::CONFIG_FILE;
|
||||||
|
$piconfig = ['jobs' => []];
|
||||||
|
if (file_exists($config_file)) {
|
||||||
|
try {
|
||||||
|
$piconfig = Yaml::parse(file_get_contents($config_file));
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
$piconfig = ['jobs' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('databoxes', $piconfig)) {
|
||||||
|
// migrate the job settings to v3
|
||||||
|
$jobs3 = [];
|
||||||
|
foreach ($piconfig['jobs'] as $job) {
|
||||||
|
$jobname = $job['job'];
|
||||||
|
|
||||||
|
// find databox
|
||||||
|
$found = 0;
|
||||||
|
foreach ($piconfig['databoxes'] as $db) {
|
||||||
|
if ($db['databox'] === $job['databox']) {
|
||||||
|
unset($job['job']);
|
||||||
|
$jobs3[$jobname] = $job;
|
||||||
|
$jobs3[$jobname]['target'] = "downloader";
|
||||||
|
$jobs3[$jobname]['collection'] = array_key_exists('collection', $db) ? $db['collection'] : [];
|
||||||
|
$jobs3[$jobname]['downloaded'] = array_key_exists('downloaded', $db) ? $db['downloaded'] : [];
|
||||||
|
$jobs3[$jobname]['expire_field'] = $db['expire_field'];
|
||||||
|
$found++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($found != 1) {
|
||||||
|
$msg = sprintf("error migrating job \"%s\": databox not found or not unique", $jobname);
|
||||||
|
$this->logger->error(sprintf("<error>%s</error>", $msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$piconfig = [
|
||||||
|
'version' => 3,
|
||||||
|
'jobs' => $jobs3
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
rename($config_file, $config_file . "_bkp");
|
||||||
|
}
|
||||||
|
|
||||||
|
$phrconfig->set(['plugins', 'expirating-rights-plugin', 'enabled'], false);
|
||||||
|
$phrconfig->set(['expiring-rights'], $piconfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function migrateTable(appbox $appbox)
|
||||||
|
{
|
||||||
|
// create the table
|
||||||
|
$sql = "CREATE TABLE `ExpiringRights` (\n"
|
||||||
|
. "`id` int(11) unsigned NOT NULL AUTO_INCREMENT,\n"
|
||||||
|
. "`job` char(128) DEFAULT NULL,\n"
|
||||||
|
. "`downloaded` datetime DEFAULT NULL,\n"
|
||||||
|
. "`user_id` int(11) unsigned DEFAULT NULL,\n"
|
||||||
|
. "`email` char(128) DEFAULT NULL,\n"
|
||||||
|
. "`sbas_id` int(11) unsigned DEFAULT NULL,\n"
|
||||||
|
. "`base` char(50) DEFAULT NULL,\n"
|
||||||
|
. "`collection` char(50) DEFAULT NULL,\n"
|
||||||
|
. "`record_id` int(11) unsigned DEFAULT NULL,\n"
|
||||||
|
. "`title` char(200) DEFAULT NULL,\n"
|
||||||
|
. "`expire` datetime DEFAULT NULL,\n"
|
||||||
|
. "`new_expire` datetime DEFAULT NULL,\n"
|
||||||
|
. "`alerted` datetime DEFAULT NULL,\n"
|
||||||
|
. "PRIMARY KEY (`id`),\n"
|
||||||
|
. "KEY `job` (`job`),\n"
|
||||||
|
. "KEY `sbas_id` (`sbas_id`),\n"
|
||||||
|
. "KEY `expire` (`expire`),\n"
|
||||||
|
. "KEY `new_expire` (`new_expire`),\n"
|
||||||
|
. "KEY `alerted` (`alerted`),\n"
|
||||||
|
. "UNIQUE KEY `unique` (job,user_id,sbas_id,record_id)"
|
||||||
|
. ") ENGINE=InnoDB CHARSET=utf8;";
|
||||||
|
|
||||||
|
$stmt = $appbox->get_connection()->prepare($sql);
|
||||||
|
try {
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
catch(TableExistsException $e) {
|
||||||
|
// table v3 already exists, skip migration
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch(\Exception $e) {
|
||||||
|
// fatal
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$stmt->closeCursor();
|
||||||
|
|
||||||
|
// migrate data from v-1 ?
|
||||||
|
try {
|
||||||
|
$appbox->get_connection()->exec("SELECT `id` FROM `_ExpiringRights` LIMIT 1");
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
// failed : no table to copy ?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var PropertyAccess $conf */
|
||||||
|
$phrconfig = $this->app['conf'];
|
||||||
|
|
||||||
|
if (!$phrconfig->has(['expiring-rights'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// table v3 was just created, insert from table v-1
|
||||||
|
$sql = "INSERT INTO `ExpiringRights` (\n"
|
||||||
|
. " `job`, \n"
|
||||||
|
. " `alerted`,\n"
|
||||||
|
. " `base`,\n"
|
||||||
|
. " `collection`,\n"
|
||||||
|
. " `downloaded`,\n"
|
||||||
|
. " `email`,\n"
|
||||||
|
. " `expire`,\n"
|
||||||
|
. " `new_expire`,\n"
|
||||||
|
. " `record_id`,\n"
|
||||||
|
. " `sbas_id`,\n"
|
||||||
|
. " `title`,\n"
|
||||||
|
. " `user_id`)\n"
|
||||||
|
. "SELECT\n"
|
||||||
|
. " :job AS `job`,\n"
|
||||||
|
. " MAX(`alerted`),\n"
|
||||||
|
. " `base`,\n"
|
||||||
|
. " `collection`,\n"
|
||||||
|
. " MAX(`downloaded`),\n"
|
||||||
|
. " `email`,\n"
|
||||||
|
. " MAX(`expire`),\n"
|
||||||
|
. " MAX(`new_expire`),\n"
|
||||||
|
. " `record_id`,\n"
|
||||||
|
. " `sbas_id`,\n"
|
||||||
|
. " `title`,\n"
|
||||||
|
. " `user_id`\n"
|
||||||
|
. "FROM `_ExpiringRights` GROUP BY `user_id`, `sbas_id`, `record_id` ORDER BY `id` ASC";
|
||||||
|
$stmtCopy = $appbox->get_connection()->prepare($sql);
|
||||||
|
|
||||||
|
$config = $phrconfig->get(['expiring-rights']);
|
||||||
|
foreach ($config['jobs'] as $jobname => $job) {
|
||||||
|
|
||||||
|
// copy v-1 rows to v3 (add job)
|
||||||
|
$stmtCopy->execute([':job' => $jobname]);
|
||||||
|
|
||||||
|
// fix alerted too early
|
||||||
|
$delta = $job['prior_notice'];
|
||||||
|
if ($delta > 0) {
|
||||||
|
$value = "(`expire`+INTERVAL " . $delta . " DAY)";
|
||||||
|
} elseif ($delta < 0) {
|
||||||
|
$value = "(`expire`-INTERVAL " . -$delta . " DAY)";
|
||||||
|
} else {
|
||||||
|
$value = "`expire`";
|
||||||
|
}
|
||||||
|
$sql = "UPDATE `ExpiringRights` SET `alerted`=NULL WHERE `job` = :job AND `alerted` < " . $value;
|
||||||
|
$stmtEarly = $appbox->get_connection()->prepare($sql);
|
||||||
|
$stmtEarly->execute([':job' => $jobname]);
|
||||||
|
$stmtEarly->closeCursor();
|
||||||
|
}
|
||||||
|
$stmtCopy->closeCursor();
|
||||||
|
|
||||||
|
// fix bad date
|
||||||
|
$sql = "UPDATE `ExpiringRights` SET `expire`=NULL WHERE `expire`='0000-00-00'";
|
||||||
|
$stmt = $appbox->get_connection()->prepare($sql);
|
||||||
|
$stmt->execute([]);
|
||||||
|
$stmt->closeCursor();
|
||||||
|
|
||||||
|
// fix useless new_expire
|
||||||
|
$sql = "UPDATE `ExpiringRights` SET `new_expire`=NULL WHERE `new_expire` = `expire`";
|
||||||
|
$stmt = $appbox->get_connection()->prepare($sql);
|
||||||
|
$stmt->execute([]);
|
||||||
|
$stmt->closeCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user