From e4760b88f29fc06d7d1e6965082338dad420995c Mon Sep 17 00:00:00 2001 From: Aina Sitraka <35221835+aynsix@users.noreply.github.com> Date: Thu, 6 Oct 2022 19:10:15 +0300 Subject: [PATCH] PHRAS-3694 bin/maintenance clean:users finish (#4139) * clean users * fix help * add usertype * relance user connection * unused option * test * send token to connect to phraseanet * add alpha on help * bump rc6, add alpha , fix input to integer Co-authored-by: jygaulier --- .../Phrasea/Authentication/Authenticator.php | 4 + .../Command/Maintenance/CleanUsersCommand.php | 333 +++++++++--------- .../Phrasea/ControllerProvider/Prod/Root.php | 33 +- lib/Alchemy/Phrasea/Core/Version.php | 2 +- lib/Alchemy/Phrasea/Model/Entities/User.php | 40 +++ .../Model/Manipulator/TokenManipulator.php | 1 + .../Mail/MailRequestInactifAccount.php | 39 ++ resources/locales/messages.de.xlf | 18 +- resources/locales/messages.en.xlf | 18 +- resources/locales/messages.fr.xlf | 18 +- resources/locales/messages.nl.xlf | 18 +- resources/locales/validators.de.xlf | 2 +- resources/locales/validators.en.xlf | 2 +- resources/locales/validators.fr.xlf | 2 +- resources/locales/validators.nl.xlf | 2 +- 15 files changed, 362 insertions(+), 170 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Notification/Mail/MailRequestInactifAccount.php diff --git a/lib/Alchemy/Phrasea/Authentication/Authenticator.php b/lib/Alchemy/Phrasea/Authentication/Authenticator.php index aea58bef06..5a28b7fc43 100644 --- a/lib/Alchemy/Phrasea/Authentication/Authenticator.php +++ b/lib/Alchemy/Phrasea/Authentication/Authenticator.php @@ -95,6 +95,10 @@ class Authenticator { $user = $session->getUser(); $user->setLastConnection($session->getCreated()); + // reset inactivity email when login + $user->setNbInactivityEmail(0); + $user->setLastInactivityEmail(null); + $this->em->persist($user); $this->em->flush(); diff --git a/lib/Alchemy/Phrasea/Command/Maintenance/CleanUsersCommand.php b/lib/Alchemy/Phrasea/Command/Maintenance/CleanUsersCommand.php index 22a75b7acd..b079c44b6d 100644 --- a/lib/Alchemy/Phrasea/Command/Maintenance/CleanUsersCommand.php +++ b/lib/Alchemy/Phrasea/Command/Maintenance/CleanUsersCommand.php @@ -11,10 +11,17 @@ namespace Alchemy\Phrasea\Command\Maintenance; +use Alchemy\Phrasea\Application\Helper\NotifierAware; use Alchemy\Phrasea\Command\Command; +use Alchemy\Phrasea\Core\LazyLocator; +use Alchemy\Phrasea\Model\Entities\User; +use Alchemy\Phrasea\Model\Manipulator\BasketManipulator; +use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Alchemy\Phrasea\Model\Manipulator\UserManipulator; use Alchemy\Phrasea\Model\Repositories\BasketRepository; use Alchemy\Phrasea\Model\Repositories\UserRepository; +use Alchemy\Phrasea\Notification\Mail\MailRequestInactifAccount; +use Alchemy\Phrasea\Notification\Receiver; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -22,49 +29,33 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; class CleanUsersCommand extends Command { - - const CLEAR_EMAIL = 8; - const REVOKE = 4; - const DELETE_DATA = 2; - const DELETE_ALL = 1; - - static private $mapListToColumn = [ - 'email' => 'email', - 'login' => 'login', - 'id' => 'usr_id', - ]; + use NotifierAware; public function __construct() { parent::__construct('clean:users'); $this - ->setDescription('Delete "sleepy" users (not connected since a long time)') -// ->addOption('action', null, InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, '"clear_email" ; "revoke" ; "delete_data" ; "delete_all"') - ->addOption('older_than', null, InputOption::VALUE_REQUIRED, 'delete older than \') - ->addOption('dry', null, InputOption::VALUE_NONE, 'dry run, count but don\'t delete') - ->addOption('list', null, InputOption::VALUE_REQUIRED, 'list only, don\'t delete') + ->setDescription('ALPHA - Delete "sleepy" users (not connected since a long time)') + ->addOption('inactivity_period', null, InputOption::VALUE_REQUIRED, 'cleanup older than \ days') + ->addOption('usertype', null, InputOption::VALUE_REQUIRED, 'specify type of user to clean') + ->addOption('grace_duration', null, InputOption::VALUE_REQUIRED, 'grace period in days after sending email') + ->addOption('max_relances', null, InputOption::VALUE_REQUIRED, 'number of email relance') + ->addOption('remove_basket', null, InputOption::VALUE_NONE, 'remove basket for user') + ->addOption('dry-run', null, InputOption::VALUE_NONE, 'dry run, list result users') ->addOption('show_sql', null, InputOption::VALUE_NONE, 'show sql pre-selecting users') ->addOption('yes', 'y', InputOption::VALUE_NONE, 'don\'t ask for confirmation') ->setHelp( "" -// . "action=clear-email will set the email to (null)\n" -// . "action=revoke will delete all access rights (= make user a \"ghost\")\n" -// . "action=delete_data will delete every appbox elements related to the user (rights, baskets, notifications, ...) but preserves dbox logs\n" -// . "action=delete_all will delete all db elements, including the dbox logs and the user itself\n" -// . "default = clear_email ; revoke ; delete_data" -// . "\n" - . "\ can be absolute or relative from now, e.g.:\n" - . "- 2022-01-01 (please use strict date format, do not add time)\n" - . "- 10 days\n" - . "- 2 weeks\n" - . "- 6 months\n" - . "- 1 year\n" - . "\ sepcifies the user column to be listed, set one of:\n" - . "- id\n" - . "- login\n" - . "- email" + . "\ integer to specify the number of inactivity days, value not 0 (zero)\n" + . "\specify type of user to clean : \n" + . "- admin \n" + . "- appowner \n" + . "- ghost \n" + . "- basket_owner \n" + . "- basket_participant \n" + . "- story_owner \n" ); } @@ -74,108 +65,89 @@ class CleanUsersCommand extends Command $dry = false; $show_sql = false; $yes = false; + $this->setDelivererLocator(new LazyLocator($this->container, 'notification.deliverer')); - // sanity check options - // + $cnx = $this->container->getApplicationBox()->get_connection(); - // --action (multiple values allowed) + $inactivityPeriod = $input->getOption('inactivity_period'); + + if (!preg_match("/^\d+$/", $inactivityPeriod)) { + $output->writeln("invalid value form '--inactivity_period' option(see possible value with --help)"); - /* - $action = 0; // actions requested, as bitfield - $actions = [ - 'CLEAR_EMAIL' => self::CLEAR_EMAIL, - 'REVOKE' => self::REVOKE, - 'DELETE_DATA' => self::DELETE_DATA, - 'DELETE_ALL' => self::DELETE_ALL - ]; - if(empty($action_parm = $input->getOption('action'))) { - $action_parm = ['clear_email', 'revoke', 'delete_data']; - } - foreach($action_parm as $a) { - $a = strtoupper($a); - if(!array_key_exists($a, $actions)) { - $output->writeln(sprintf("Invalid value \"%s\" for --action option.", $a)); - return 1; - } - $action |= $action[$a]; - } - if($action & self::DELETE_ALL && $action !== self::DELETE_ALL) { - $output->writeln(sprintf("Action \"delete_all\" cannot be mixed with other action.")); return 1; } - */ - // --older_than + $graceDuration = $input->getOption('grace_duration'); + + if (!preg_match("/^\d+$/", $graceDuration)) { + $output->writeln("invalid value form '--grace_duration' option(see possible value with --help)"); - $older_than = str_replace(['-', '/', ' '], '-', $input->getOption('older_than')); - if($older_than === "") { - $output->writeln("set '--older_than' option."); return 1; } - $matches = []; - preg_match("/(\d{4}-\d{2}-\d{2})|(\d+)-(day|week|month|year)s?/i", $older_than, $matches); - $n = count($matches); - if($n === 2) { - // yyyy-mm-dd - $clauses[] = "`last_connection` < " . $matches[1]; - } - elseif($n === 4 && empty($matches[1])) { - // 1-day ; 2-weeks ; ... - $expr = (int)$matches[2]; - $unit = strtoupper($matches[3]); - $clauses[] = sprintf("`last_connection` < DATE_SUB(NOW(), INTERVAL %d %s)", $expr, $unit); - } - else { - $output->writeln("invalid value form '--older_than' option. (see possible values with --help)"); + + $maxRelances = $input->getOption('max_relances'); + + if (!preg_match("/^\d+$/", $maxRelances)) { + $output->writeln("invalid value form '--max_relances' option(see possible value with --help)"); + return 1; } - $clauses[] = "`admin`=0"; // dont delete super admins + + $clauses[] = sprintf("`last_connection` < DATE_SUB(NOW(), INTERVAL %d day)", $inactivityPeriod); + + $sql_where_u = 1; + $sql_where_ub = 1; + + if ($input->getOption('usertype') == 'admin') { + $clauses[] = "`admin`=1"; + } else { + $clauses[] = "`admin`=0"; // dont delete super admins + } + + if ($input->getOption('usertype') == 'appowner') { + $clauses[] = "`ApiAccounts`.`id` IS NOT NULL"; + } else { + $clauses[] = "ISNULL(`ApiAccounts`.`id`)"; + } + + if ($input->getOption('usertype') == 'ghost') { + $sql_where_u = "`u`.`bids` IS NULL"; + $sql_where_ub = "`ub`.`sbids` IS NULL"; + } + + if ($input->getOption('usertype') == 'basket_owner') { + $clauses[] = "`Baskets`.`id` IS NOT NULL"; + } + + if ($input->getOption('usertype') == 'basket_participant') { + $clauses[] = "`BasketParticipants`.`id` IS NOT NULL"; + $clauses[] = "`B`.`user_id` != `BasketParticipants`.`user_id`"; + } + + if ($input->getOption('usertype') == 'story_owner') { + $clauses[] = "`StoryWZ`.`id` IS NOT NULL"; + } + $clauses[] = "`deleted`=0"; // dont delete twice $clauses[] = "ISNULL(`model_of`)"; // dont delete models $clauses[] = "`login`!='autoregister'"; // dont delete "autoregister" $clauses[] = "`login`!='guest'"; // dont delete "guest" - $clauses[] = "ISNULL(`ApiAccounts`.`id`)"; // dont delete api service accounts - // --dry - if($input->getOption('dry')) { + if ($input->getOption('dry-run')) { $dry = true; } - if($input->getOption('show_sql')) { + if ($input->getOption('show_sql')) { $show_sql = true; } - if($input->getOption('yes')) { + if ($input->getOption('yes')) { $yes = true; } - if(!is_null($list = $input->getOption('list'))) { - if(!array_key_exists($list, self::$mapListToColumn)) { - $output->writeln(sprintf("bad \"list\" value '%s' (see possible values with --help)", $list)); - return 1; - } - } - - // do the job - // $sql_where = join(") AND (", $clauses); - $cnx = $this->container->getApplicationBox()->get_connection(); - - $sql_count = "SELECT COUNT(`Users`.`id`) AS n FROM (`Users` LEFT JOIN `ApiAccounts` ON `ApiAccounts`.`user_id`=`Users`.`id`) WHERE (" . $sql_where . ")"; - if($show_sql) { - $output->writeln(sprintf("sql: \"%s\"", $sql_count)); - } - $stmt = $cnx->prepare($sql_count); - $stmt->execute(); - $n = $stmt->fetchColumn(0); - $stmt->closeCursor(); - if(!$list) { - $output->writeln(sprintf("Acting on %s users.", $n)); - } - - /** @var UserManipulator $userManipulator */ $userManipulator = $this->container['manipulator.user']; /** @var UserRepository $userRepository */ @@ -183,90 +155,133 @@ class CleanUsersCommand extends Command /** @var BasketRepository $basketRepository */ $basketRepository = $this->container['repo.baskets']; - $sql_list = "SELECT u.*, GROUP_CONCAT(`basusr`.`base_id` SEPARATOR ',') AS `bids`\n" + $sql_list = "SELECT * FROM \n" + . "(SELECT ub.*, GROUP_CONCAT(`basusr`.`base_id` SEPARATOR ',') AS `bids`\n" . "FROM\n" . "( SELECT `Users`.`id` AS `usr_id`, `Users`.`login`, `Users`.`email`, `Users`.`last_connection`, GROUP_CONCAT(`sbasusr`.`sbas_id` SEPARATOR ',') AS `sbids`\n" . " FROM (`Users` LEFT JOIN `ApiAccounts` ON `ApiAccounts`.`user_id` = `Users`.`id`) \n" . " LEFT JOIN `sbasusr` ON `sbasusr`.`usr_id` = `Users`.`id`\n" + . " LEFT JOIN Baskets ON Baskets.user_id = `Users`.`id`\n" + . " LEFT JOIN BasketParticipants ON BasketParticipants.user_id = `Users`.`id`\n" + . " LEFT JOIN Baskets as B ON B.id = BasketParticipants.basket_id \n" + . " LEFT JOIN StoryWZ ON StoryWZ.user_id = `Users`.`id`\n" . " WHERE (" . $sql_where . ")" - . " GROUP BY `sbasusr`.`usr_id`\n" - . ") AS u\n" - . "LEFT JOIN `basusr` ON `basusr`.`usr_id` = `u`.`usr_id` GROUP BY `basusr`.`usr_id`"; + . " GROUP BY `Users`.`id`\n" + . ") AS ub\n" + . "LEFT JOIN `basusr` ON `basusr`.`usr_id` = `ub`.`usr_id`" + . " WHERE " . $sql_where_ub ."\n" + . " GROUP BY `ub`.`usr_id`) AS u\n" + . " WHERE ". $sql_where_u ; - if($show_sql) { + if ($show_sql) { $output->writeln(sprintf("sql: \"%s\"", $sql_list)); } - if(!$yes && !$list) { + $stmt = $cnx->prepare($sql_list); + $stmt->execute(); + $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + if (!$yes && !$dry) { $helper = $this->getHelper('question'); - $question = new ConfirmationQuestion(sprintf("Confirm deletion of %s user(s) [y/n] : ", $n), false); + $question = new ConfirmationQuestion(sprintf("Confirm cleanup for %s user(s) [y/n] : ", count($rows)), false); if (!$helper->ask($input, $output, $question)) { return 0; } } - $stmt = $cnx->prepare($sql_list); - $stmt->execute(); - while( $row = ($stmt->fetch(\PDO::FETCH_ASSOC)) ) { + $usersList = []; + $nbUserRelanced = 0; + $nbUserDeleted = 0; + + foreach ( $rows as $row ) { if( !is_null($user = $userRepository->find($row['usr_id'])) ) { + $lastInactivityEmail = $user->getLastInactivityEmail(); + $nbRelance = $user->getNbInactivityEmail(); + $nowDate = new \DateTime(); - if ($list) { - $s = $row[self::$mapListToColumn[$list]]; - $output->write(sprintf("%s%s", $s, "\n")); - } - else { - $output->write(sprintf("%s : %s / %s (%s)", $row['usr_id'], $row['login'], $row['email'], $row['last_connection'])); + $interval = sprintf('P%dD', $graceDuration); - if (!$dry) { - $acl = $this->container->getAclForUser($user); + $nowDate->sub(new \DateInterval($interval)); + $action = "in grace period"; - // revoke bas rights - if (!is_null($row['bids'])) { - $bids = array_map(function ($bid) { - return (int)$bid; - }, explode(',', $row['bids'])); - $acl->revoke_access_from_bases($bids); + if (empty($lastInactivityEmail) || $lastInactivityEmail < $nowDate) { + // first, relance the user by email to have a grace period + if ($nbRelance < $maxRelances) { + if (!$dry) { + $this->relanceUser($user, $graceDuration); + $user->setNbInactivityEmail($nbRelance+1); + $user->setLastInactivityEmail(new \DateTime()); + $userManipulator->updateUser($user); } + $action = sprintf("max_relances=%d , found %d times relanced (will be relance if not --dry-run)", $maxRelances, $nbRelance); + $nbUserRelanced++; + } else { + if (!$dry) { + if ($input->getOption('remove_basket')) { + $baskets = $basketRepository->findBy(['user' => $user]); + $this->getBasketManipulator()->removeBaskets($baskets); + } + // delete user + $userManipulator->delete($user); + $output->write(sprintf("%s : %s / %s (%s)", $row['usr_id'], $row['login'], $row['email'], $row['last_connection'])); - // revoke sbas rights - $acl->revoke_unused_sbas_rights(); - - // delete user - $userManipulator->delete($user); - - $output->writeln(" deleted."); - } - else { - $output->writeln(" not deleted (dry mode)."); + $output->writeln(" deleted."); + } + $action = sprintf("max_relances=%d , found %d times relanced (will be deleted if not --dry-run)", $maxRelances, $nbRelance); + $nbUserDeleted++; } } + // else we are in grace period, nothing to do + + $usersList[] = [ + $user->getId(), + $user->getLogin(), + $user->getLastConnection()->format('Y-m-d h:m:s'), + $action + ]; } } + $stmt->closeCursor(); - -/* - // clear email - - if($action & self::CLEAR_EMAIL) { - $sql = "UPDATE `Users` SET `email`=NULL WHERE (" . $sql_where . ")"; - if($show_sql) { - $output->writeln(sprintf("sql: \"%s\"", $sql)); - } - if(!$dry) { - $cnx->exec($sql); - } + if ($dry) { + $output->writeln(sprintf("dry-run , %d users included in the given inactivity_period", count($rows))); + $userTable = $this->getHelperSet()->get('table'); + $headers = ['id', 'login', 'last_connection', 'action']; + $userTable + ->setHeaders($headers) + ->setRows($usersList) + ->render($output); + } else { + $output->writeln(sprintf("%d users relanced , %d in grace period, %d users deleted", $nbUserRelanced, (count($rows)-$nbUserDeleted-$nbUserRelanced), $nbUserDeleted)); } - // revoke rights - - if($action & self::REVOKE) { - $sql = "DELETE " - } -*/ - return 0; } + private function relanceUser(User $user, $graceDuration) + { + /** @var TokenManipulator $tokenManipulator */ + $tokenManipulator = $this->container['manipulator.token']; + $token = $tokenManipulator->create($user, TokenManipulator::TYPE_USER_RELANCE, new \DateTime("+{$graceDuration} day")); + + $receiver = Receiver::fromUser($user); + $mail = MailRequestInactifAccount::create($this->container, $receiver); + + $servername = $this->container['conf']->get('servername'); + $mail->setButtonUrl('http://'.$servername.'/prod/?LOG='.$token->getValue()); + $mail->setExpiration($token->getExpiration()); + + $this->deliver($mail); + } + + /** + * @return BasketManipulator + */ + private function getBasketManipulator() + { + return $this->container['manipulator.basket']; + } + } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Root.php b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Root.php index 7dcbdd490c..147e098931 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Root.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Root.php @@ -14,10 +14,12 @@ namespace Alchemy\Phrasea\ControllerProvider\Prod; use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Controller\Prod\RootController; use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; -use Alchemy\Phrasea\Helper; +use Alchemy\Phrasea\Model\Entities\Token; +use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Silex\Application; use Silex\ControllerProviderInterface; use Silex\ServiceProviderInterface; +use Symfony\Component\HttpFoundation\Request; class Root implements ControllerProviderInterface, ServiceProviderInterface { @@ -40,7 +42,7 @@ class Root implements ControllerProviderInterface, ServiceProviderInterface public function connect(Application $app) { $controllers = $this->createCollection($app); - + $controllers->before([$this, 'redirectOnLogRequests']); $controllers->before('controller.prod:assertAuthenticated'); $controllers->get('/', 'controller.prod:indexAction') @@ -48,4 +50,31 @@ class Root implements ControllerProviderInterface, ServiceProviderInterface return $controllers; } + + public function redirectOnLogRequests(Request $request, PhraseaApplication $app) + { + if (!$request->query->has('LOG')) { + return null; + } + + if ($app->getAuthenticator()->isAuthenticated()) { + $app->getAuthenticator()->closeAccount(); + } + + /** @var Token $token */ + $token = $app['repo.tokens']->findValidToken($request->query->get('LOG')); + + // actually just type user-relance can access here with token + // PHRAS-3694 + if (null === $token || $token->getType() != TokenManipulator::TYPE_USER_RELANCE) { + $app->addFlash('error', $app->trans('The URL you used is out of date, please login')); + + return $app->redirectPath('homepage'); + } + + /** @var Token $token */ + $app->getAuthenticator()->openAccount($token->getUser()); + + return $app->redirectPath('prod'); + } } diff --git a/lib/Alchemy/Phrasea/Core/Version.php b/lib/Alchemy/Phrasea/Core/Version.php index 1b4a0dd1e9..803a5f6ded 100644 --- a/lib/Alchemy/Phrasea/Core/Version.php +++ b/lib/Alchemy/Phrasea/Core/Version.php @@ -17,7 +17,7 @@ class Version * @var string */ - private $number = '4.1.6-rc5'; + private $number = '4.1.6-rc6'; /** * @var string diff --git a/lib/Alchemy/Phrasea/Model/Entities/User.php b/lib/Alchemy/Phrasea/Model/Entities/User.php index b2a22dd700..6db15b781f 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/User.php +++ b/lib/Alchemy/Phrasea/Model/Entities/User.php @@ -266,6 +266,16 @@ class User */ private $grantedApi = false; + /** + * @ORM\Column(type="integer", name="nb_inactivity_email", options={"default" = 0}) + */ + private $nbInactivityEmail = 0; + + /** + * @ORM\Column(type="datetime", name="last_inactivity_email", nullable=true) + */ + private $lastInactivityEmail; + /** * Constructor */ @@ -1078,6 +1088,36 @@ class User return $this; } + public function setNbInactivityEmail($nbEnactivityEmail) + { + $this->nbInactivityEmail = $nbEnactivityEmail; + } + + public function getNbInactivityEmail() + { + return $this->nbInactivityEmail; + } + + /** + * @param \DateTime|null $lastInactivityEmail + * + * @return $this + */ + public function setLastInactivityEmail($lastInactivityEmail) + { + $this->lastInactivityEmail = $lastInactivityEmail; + + return $this; + } + + /** + * @return \DateTime|null + */ + public function getLastInactivityEmail() + { + return $this->lastInactivityEmail; + } + /** * @return boolean */ diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/TokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/TokenManipulator.php index 8daf2c8750..9a22276c23 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/TokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/TokenManipulator.php @@ -36,6 +36,7 @@ class TokenManipulator implements ManipulatorInterface const TYPE_VIEW = 'view'; const TYPE_VALIDATE = 'validate'; const TYPE_RSS = 'rss'; + const TYPE_USER_RELANCE = 'user-relance'; /** @var Objectmanager */ private $om; diff --git a/lib/Alchemy/Phrasea/Notification/Mail/MailRequestInactifAccount.php b/lib/Alchemy/Phrasea/Notification/Mail/MailRequestInactifAccount.php new file mode 100644 index 0000000000..1f9cf34b80 --- /dev/null +++ b/lib/Alchemy/Phrasea/Notification/Mail/MailRequestInactifAccount.php @@ -0,0 +1,39 @@ +app->trans("mail:: inactif account"); + } + + /** + * @inheritDoc + */ + public function getMessage() + { + return $this->app->trans("mail:: your account is inactif and to be deleted!"); + } + + /** + * @inheritDoc + */ + public function getButtonText() + { + return $this->app->trans("mail:: connect to phraseanet"); + } + + /** + * @inheritDoc + */ + public function getButtonURL() + { + return $this->url; + } +} diff --git a/resources/locales/messages.de.xlf b/resources/locales/messages.de.xlf index d3fca40afd..633c76c693 100644 --- a/resources/locales/messages.de.xlf +++ b/resources/locales/messages.de.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. @@ -6613,6 +6613,7 @@ The URL you used is out of date, please login Die URL, die Sie benutzt haben, ist nicht mehr gültig. Bitte loggen Sie sich ein + ControllerProvider/Prod/Root.php Phrasea/ControllerProvider/Lightbox.php @@ -11509,11 +11510,26 @@ Mai classes/module/report.php + + mail:: connect to phraseanet + mail:: connect to phraseanet + Notification/Mail/MailRequestInactifAccount.php + + + mail:: inactif account + mail:: inactif account + Notification/Mail/MailRequestInactifAccount.php + mail:: validation: Vote will expire on %expire% Das Feedback läuft am ab %expire% Notification/Mail/MailInfoValidationRequest.php + + mail:: your account is inactif and to be deleted! + mail:: your account is inactif and to be deleted! + Notification/Mail/MailRequestInactifAccount.php + mail::share Open with Lightbox Mit Lightbox öffnen diff --git a/resources/locales/messages.en.xlf b/resources/locales/messages.en.xlf index ff4c6877fe..1b1913465b 100644 --- a/resources/locales/messages.en.xlf +++ b/resources/locales/messages.en.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. @@ -6616,6 +6616,7 @@ The URL you used is out of date, please login The URL you used is out of date. Please login. + ControllerProvider/Prod/Root.php Phrasea/ControllerProvider/Lightbox.php @@ -11512,11 +11513,26 @@ May classes/module/report.php + + mail:: connect to phraseanet + mail:: connect to phraseanet + Notification/Mail/MailRequestInactifAccount.php + + + mail:: inactif account + mail:: inactif account + Notification/Mail/MailRequestInactifAccount.php + mail:: validation: Vote will expire on %expire% Feedback session expires on %expire% Notification/Mail/MailInfoValidationRequest.php + + mail:: your account is inactif and to be deleted! + mail:: your account is inactif and to be deleted! + Notification/Mail/MailRequestInactifAccount.php + mail::share Open with Lightbox Open with Lightbox diff --git a/resources/locales/messages.fr.xlf b/resources/locales/messages.fr.xlf index b5d0dc7fa5..d369e7e1b7 100644 --- a/resources/locales/messages.fr.xlf +++ b/resources/locales/messages.fr.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. @@ -6613,6 +6613,7 @@ The URL you used is out of date, please login L'URL utilisée est périmé. Veuillez vous identifier. + ControllerProvider/Prod/Root.php Phrasea/ControllerProvider/Lightbox.php @@ -11510,11 +11511,26 @@ Si vous recevez cet e-mail sans l'avoir sollicité, merci de l'ignorer ou de le mai classes/module/report.php + + mail:: connect to phraseanet + mail:: connect to phraseanet + Notification/Mail/MailRequestInactifAccount.php + + + mail:: inactif account + mail:: inactif account + Notification/Mail/MailRequestInactifAccount.php + mail:: validation: Vote will expire on %expire% La validation expire le %expire% Notification/Mail/MailInfoValidationRequest.php + + mail:: your account is inactif and to be deleted! + mail:: your account is inactif and to be deleted! + Notification/Mail/MailRequestInactifAccount.php + mail::share Open with Lightbox Ouvrir avec Lightbox diff --git a/resources/locales/messages.nl.xlf b/resources/locales/messages.nl.xlf index 795553c6ae..d9a0b010aa 100644 --- a/resources/locales/messages.nl.xlf +++ b/resources/locales/messages.nl.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. @@ -6619,6 +6619,7 @@ The URL you used is out of date, please login De gebruikte URL is niet meer geldig, gelieve u aan te melden + ControllerProvider/Prod/Root.php Phrasea/ControllerProvider/Lightbox.php @@ -11515,11 +11516,26 @@ mei classes/module/report.php + + mail:: connect to phraseanet + mail:: connect to phraseanet + Notification/Mail/MailRequestInactifAccount.php + + + mail:: inactif account + mail:: inactif account + Notification/Mail/MailRequestInactifAccount.php + mail:: validation: Vote will expire on %expire% mail:: validation: Vote will expire on %expire% Notification/Mail/MailInfoValidationRequest.php + + mail:: your account is inactif and to be deleted! + mail:: your account is inactif and to be deleted! + Notification/Mail/MailRequestInactifAccount.php + mail::share Open with Lightbox mail::share Open with Lightbox diff --git a/resources/locales/validators.de.xlf b/resources/locales/validators.de.xlf index ae074d54fd..8b14f522a2 100644 --- a/resources/locales/validators.de.xlf +++ b/resources/locales/validators.de.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. diff --git a/resources/locales/validators.en.xlf b/resources/locales/validators.en.xlf index 708ef29288..209e8bc2e2 100644 --- a/resources/locales/validators.en.xlf +++ b/resources/locales/validators.en.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. diff --git a/resources/locales/validators.fr.xlf b/resources/locales/validators.fr.xlf index b98d30954a..cedf1a62bf 100644 --- a/resources/locales/validators.fr.xlf +++ b/resources/locales/validators.fr.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. diff --git a/resources/locales/validators.nl.xlf b/resources/locales/validators.nl.xlf index 8bcb8d12a7..f385c1cd86 100644 --- a/resources/locales/validators.nl.xlf +++ b/resources/locales/validators.nl.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.