mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-08 18:44:30 +00:00
330 lines
14 KiB
PHP
330 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* This file is part of Phraseanet
|
|
*
|
|
* (c) 2005-2016 Alchemy
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
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\UserManipulator;
|
|
use Alchemy\Phrasea\Model\Repositories\BasketRepository;
|
|
use Alchemy\Phrasea\Model\Repositories\UserRepository;
|
|
use Alchemy\Phrasea\Notification\Mail\MailRequestInactifAccount;
|
|
use Alchemy\Phrasea\Notification\Mail\MailSuccessAccountInactifDelete;
|
|
use Alchemy\Phrasea\Notification\Receiver;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
|
|
class CleanUsersCommand extends Command
|
|
{
|
|
use NotifierAware;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct('clean:users');
|
|
|
|
$this
|
|
->setDescription('BETA - Delete "sleepy" users (not connected since a long time)')
|
|
->addOption('inactivity_period', null, InputOption::VALUE_REQUIRED, 'cleanup older than \<inactivity_period> days')
|
|
->addOption('usertype', null, InputOption::VALUE_REQUIRED, 'can specify type of user to clean, if not set types ghost, basket_owner, basket_participant, story_owner are included')
|
|
->addOption('grace_duration', null, InputOption::VALUE_REQUIRED, 'grace period in days after sending email')
|
|
->addOption('max_relances', null, InputOption::VALUE_REQUIRED, 'number of email reminders, if 0 no email sent, no grace email, no account deletion confirmation email')
|
|
->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(
|
|
"example : <info>bin/maintenance clean:user --dry-run --inactivity_period=90 --grace_duration=7 --max_relances=3 --remove_basket -y</info>"
|
|
. "\<INACTIVITY_PERIOD> <info>integer to specify the number of inactivity days, value not 0 (zero)</info>\n"
|
|
. "\<USERTYPE>can specify the only type of user to be clean : \n"
|
|
. "- <info>admin</info> \n"
|
|
. "- <info>appowner</info> \n"
|
|
. "- <info>ghost</info> \n"
|
|
. "- <info>basket_owner</info> \n"
|
|
. "- <info>basket_participant</info> \n"
|
|
. "- <info>story_owner</info> \n"
|
|
. "\<MAX_RELANCES><info> number of email reminders before deleting the user, if 0 no email sent</info> "
|
|
);
|
|
}
|
|
|
|
protected function doExecute(InputInterface $input, OutputInterface $output)
|
|
{
|
|
$clauses = []; // sql clauses
|
|
$dry = false;
|
|
$show_sql = false;
|
|
$yes = false;
|
|
$this->setDelivererLocator(new LazyLocator($this->container, 'notification.deliverer'));
|
|
|
|
$cnx = $this->container->getApplicationBox()->get_connection();
|
|
|
|
$inactivityPeriod = $input->getOption('inactivity_period');
|
|
|
|
if (!preg_match("/^\d+$/", $inactivityPeriod)) {
|
|
$output->writeln("<error>invalid value form '--inactivity_period' option</error>(see possible value with --help)");
|
|
|
|
return 1;
|
|
}
|
|
|
|
$graceDuration = $input->getOption('grace_duration');
|
|
|
|
if (!preg_match("/^\d+$/", $graceDuration)) {
|
|
$output->writeln("<error>invalid value form '--grace_duration' option</error>(see possible value with --help)");
|
|
|
|
return 1;
|
|
}
|
|
|
|
$maxRelances = $input->getOption('max_relances');
|
|
|
|
if (!preg_match("/^\d+$/", $maxRelances)) {
|
|
$output->writeln("<error>invalid value form '--max_relances' option</error>(see possible value with --help)");
|
|
|
|
return 1;
|
|
}
|
|
|
|
$clauses[] = sprintf("((`last_connection` IS NULL AND `Users`.`created` < DATE_SUB(NOW(), INTERVAL %d day)) OR (`last_connection` < DATE_SUB(NOW(), INTERVAL %d day)))", $inactivityPeriod, $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"
|
|
|
|
|
|
if ($input->getOption('dry-run')) {
|
|
$dry = true;
|
|
}
|
|
|
|
if ($input->getOption('show_sql')) {
|
|
$show_sql = true;
|
|
}
|
|
|
|
if ($input->getOption('yes')) {
|
|
$yes = true;
|
|
}
|
|
|
|
$sql_where = join(") AND (", $clauses);
|
|
|
|
/** @var UserManipulator $userManipulator */
|
|
$userManipulator = $this->container['manipulator.user'];
|
|
/** @var UserRepository $userRepository */
|
|
$userRepository = $this->container['repo.users'];
|
|
/** @var BasketRepository $basketRepository */
|
|
$basketRepository = $this->container['repo.baskets'];
|
|
|
|
$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 `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) {
|
|
$output->writeln(sprintf("sql: \"<info>%s</info>\"", $sql_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 cleanup for %s user(s) [y/n] : ", count($rows)), false);
|
|
|
|
if (!$helper->ask($input, $output, $question)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
$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();
|
|
|
|
$interval = sprintf('P%dD', $graceDuration);
|
|
|
|
$nowDate->sub(new \DateInterval($interval));
|
|
$action = "in grace period";
|
|
|
|
$isValidMail = true;
|
|
if (!\Swift_Validate::email($user->getEmail())) {
|
|
$isValidMail = false;
|
|
}
|
|
|
|
// compare on the day date
|
|
if (empty($lastInactivityEmail) || $lastInactivityEmail->format('Y-m-d') <= $nowDate->format('Y-m-d')) {
|
|
// first, relance the user by email to have a grace period
|
|
if (($nbRelance < $maxRelances) && $isValidMail) {
|
|
if (!$dry) {
|
|
$this->relanceUser($user, ($maxRelances - $nbRelance) * $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 and notify by mail
|
|
$this->doDelete($user, $userManipulator, $isValidMail, $maxRelances);
|
|
|
|
$output->write(sprintf("%s : %s / %s (%s)", $row['usr_id'], $row['login'], $row['email'], $row['last_connection']));
|
|
|
|
$output->writeln(" deleted.");
|
|
}
|
|
|
|
if ($isValidMail) {
|
|
$action = sprintf("max_relances=%d , found %d times relanced (will be deleted if not --dry-run)", $maxRelances, $nbRelance);
|
|
} else {
|
|
$action = "no valid address email for the user (will be deleted if not --dry-run)";
|
|
}
|
|
|
|
$nbUserDeleted++;
|
|
}
|
|
}
|
|
// else we are in grace period, nothing to do
|
|
|
|
$usersList[] = [
|
|
$user->getId(),
|
|
$user->getLogin(),
|
|
($user->getLastConnection() == null) ? 'never connected' : $user->getLastConnection()->format('Y-m-d h:m:s'),
|
|
$action
|
|
];
|
|
}
|
|
}
|
|
|
|
$stmt->closeCursor();
|
|
|
|
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));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private function relanceUser(User $user, $graceDuration)
|
|
{
|
|
try {
|
|
$receiver = Receiver::fromUser($user);
|
|
$mail = MailRequestInactifAccount::create($this->container, $receiver);
|
|
|
|
$mail->setLogin($user->getLogin());
|
|
$mail->setLocale($user->getLocale());
|
|
$mail->setLastConnection(($user->getLastConnection() == null) ? 'never connected': $user->getLastConnection()->format('Y-m-d'));
|
|
$mail->setDeleteDate((new \DateTime("+{$graceDuration} day"))->format('Y-m-d'));
|
|
|
|
// return 0 on failure
|
|
$this->deliver($mail);
|
|
} catch (\Exception $e) {
|
|
echo $e->getMessage();
|
|
}
|
|
}
|
|
|
|
private function doDelete(User $user, UserManipulator $userManipulator, $validMail, $maxRelances)
|
|
{
|
|
try {
|
|
if ($validMail && !empty($maxRelances)) {
|
|
$receiver = Receiver::fromUser($user);
|
|
$mail = MailSuccessAccountInactifDelete::create($this->container, $receiver);
|
|
$mail->setLastConnection(($user->getLastConnection() == null) ? 'never connected' : $user->getLastConnection()->format('Y-m-d'));
|
|
|
|
if ($user->getLastInactivityEmail() !== null) {
|
|
$mail->setLastInactivityEmail($user->getLastInactivityEmail()->format('Y-m-d'));
|
|
}
|
|
|
|
$mail->setLocale($user->getLocale());
|
|
$mail->setDisplayFooterText(false);
|
|
}
|
|
|
|
$userManipulator->delete($user);
|
|
|
|
if ($validMail && !empty($maxRelances)) {
|
|
// return 0 on failure
|
|
$this->deliver($mail);
|
|
}
|
|
} catch (\Exception $e) {
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return BasketManipulator
|
|
*/
|
|
private function getBasketManipulator()
|
|
{
|
|
return $this->container['manipulator.basket'];
|
|
}
|
|
|
|
}
|