boka3/include/Cron.php
Erik Thuning d981f6d190 Updating the algorithm for dropping users
Users who have taken actions in the system are now never dropped
automcatically. For the moment the actual dropping logic is commented out
so consequences for production can be checked beforehand
2025-09-25 14:45:05 +02:00

417 lines
14 KiB
PHP

<?php
class Cron {
private $now = 0;
private $sender = '';
private $error = '';
private $kvs;
private $ldap;
public function __construct($sender, $error, $prefix) {
$this->now = new DateTimeImmutable();
$this->sender = $sender;
$this->error = $error;
$this->subject_prefix = $prefix;
$this->warn_time = DateInterval::createFromDateString('3 days');
$this->warn_date = $this->now->add($this->warn_time);
$this->run_interval = DateInterval::createFromDateString('1 day');
$this->kvs = new Kvs();
$this->ldap = new Ldap();
$this->email_fragments = get_fragments('./email-fragments.txt');
$days = $this->warn_time->d;
$this->strings = array(
'en' => array(
'new' => array(
'single' => "The following loan has been registered in your name:",
'multi' => "The following loans have been registered in your name:",
'expiry' => "expires on",
'serial' => "serial number",
),
'extend' => array(
'single' => "The following loan has been extended:",
'multi' => "The following loans have been extended:",
'expiry' => "extended to",
'serial' => "serial number",
),
'expiring' => array(
'single' => "The following loan expires in less than $days days:",
'multi' => "The following loans expire in less than $days days:",
'expiry' => "expires on",
'serial' => "serial number",
),
'overdue' => array(
'single' => "The following loan has expired:",
'multi' => "The following loans have expired:",
'expiry' => "expired on",
'serial' => "serial number",
),
),
'sv' => array(
'new' => array(
'single' => "Följande lån har registrerats på din användare:",
'multi' => "Följande lån har registrerats på din användare:",
'expiry' => "går ut",
'serial' => "artikelnummer",
),
'extend' => array(
'single' => "Följande lån har förlängts:",
'multi' => "Följande lån har förlängts:",
'expiry' => "förlängt till",
'serial' => "artikelnummer",
),
'expiring' => array(
'single' => "Följande lån går ut om mindre än $days dagar:",
'multi' => "Följande lån går ut om mindre än $days dagar:",
'expiry' => "går ut",
'serial' => "artikelnummer",
),
'overdue' => array(
'single' => "Följande lån har gått ut:",
'multi' => "Följande lån har gått ut:",
'expiry' => "gick ut",
'serial' => "artikelnummer",
),
),
);
}
public function run() {
$this->run_receipts();
$this->run_reminders();
$this->run_cleanup();
}
private function run_receipts() {
begin_trans();
$get = prepare('select * from `pending_receipt`
where `send_time` < ?');
bind($get, 'i', $this->now->getTimestamp());
execute($get);
foreach(result_list($get) as $row) {
$user = new User($row['user']);
$since_time = $row['since_time'];
$new_loans = array();
$extended_loans = array();
foreach($user->get_loans('active') as $loan) {
if($loan->get_starttime() >= $since_time) {
$new_loans[] = $loan;
} else if($loan->get_last_extension() >= $since_time) {
$extended_loans[] = $loan;
}
}
if($new_loans || $extended_loans) {
$this->send_receipt($user, $new_loans, $extended_loans);
}
$delete = prepare('delete from `pending_receipt`
where `user` = ? and `send_time` < ?');
bind($delete, 'ii', $user->get_id(), $this->now->getTimestamp());
execute($delete);
}
commit_trans();
}
private function make_receipt_subject($num_new, $num_extended) {
$subject = $this->subject_prefix;
$messages = array();
if($num_new > 1) {
$messages[] = $num_new." nya";
} else if($num_new > 0) {
$messages[] = $num_new." nytt";
}
if($num_extended > 1) {
$messages[] = $num_extended." förlängda";
} else if($num_extended > 0) {
$messages[] = $num_extended." förlängt";
}
return $subject.implode(" och ", $messages)." lån";
}
private function send_receipt($user, $new, $extended) {
$uid = $user->get_name();
$name = $this->ldap->get_firstname($uid);
$new_count = count($new);
$extended_count = count($extended);
$subject = $this->make_receipt_subject($new_count, $extended_count);
$list_sv = array();
$list_en = array();
if($new_count > 0) {
$list_sv[] = $this->make_notice('sv', 'new', $new);
$list_en[] = $this->make_notice('en', 'new', $new);
}
if($extended_count > 0) {
$list_sv[] = $this->make_notice('sv', 'extend', $extended);
$list_en[] = $this->make_notice('en', 'extend', $extended);
}
$list_sv = implode("\n\n", $list_sv);
$list_en = implode("\n\n", $list_en);
$info_sv = array();
$info_en = array();
if($new_count > 0) {
$info_sv[] = $this->email_fragments['sv_new_loans_pickup'];
$info_en[] = $this->email_fragments['en_new_loans_pickup'];
}
$info_sv[] = "Svara på det här mailet om du har några frågor.";
$info_en[] = "Please reply to this email if you have any questions.";
$info_sv = implode(' ', $info_sv);
$info_en = implode(' ', $info_en);
$intro_sv = $this->email_fragments['sv_new_loans_intro'];
$intro_en = $this->email_fragments['en_new_loans_intro'];
$footer = $this->email_fragments['footer'];
$message = <<<EOF
Hej $name!
$intro_sv
$list_sv
$info_sv
----
$intro_en
$list_en
$info_en
$footer
EOF;
$this->send_email($uid, $subject, $message);
}
private function run_reminders() {
$lastrun = $this->kvs->get_value('lastrun', 0);
$nextrun = $this->now
->setTimestamp($lastrun)
->add($this->run_interval);
if($nextrun > $this->now) {
return;
}
$this->kvs->set_key('lastrun', $this->now->getTimestamp());
$users = get_items('user');
foreach($users as $user) {
$this->check_loans($user);
}
}
private function check_loans($user) {
$expiring = $user->get_expiring_loans($this->warn_date);
$overdue = $user->get_overdue_loans();
if($expiring || $overdue) {
$this->send_reminder($user, $expiring, $overdue);
}
}
private function make_reminder_subject($num_expiring, $num_expired) {
$subject = $this->subject_prefix;
$messages = array();
if($num_expiring > 0) {
$messages[] = $num_expiring." utgående";
}
if($num_expired > 1) {
$messages[] = $num_expired." försenade";
} elseif($num_expired > 0) {
$messages[] = $num_expired." försenat";
}
return $subject.implode(" och ", $messages)." lån";
}
private function make_notice($lang, $type, $list) {
if(!$list) {
return '';
}
if(!array_key_exists($lang, $this->strings)) {
throw new Exception("Invalid languange: $lang");
}
$strings = $this->strings[$lang];
if(!array_key_exists($type, $strings)) {
throw new Exception("Invalid type: $type");
}
$strings = $strings[$type];
$lines = array();
foreach($list as $loan) {
$product = $loan->get_product();
$serial = $product->get_serial();
$brand = $product->get_brand();
$name = $product->get_name();
$endtime = format_date($loan->get_endtime());
$lines[] = "$brand $name, ".$strings['serial']
." $serial, ".$strings['expiry']." $endtime";
}
$msg = $strings['single'];
if(count($list) > 1) {
$msg = $strings['multi'];
}
return $msg."\n\n".implode("\n", $lines);
}
private function make_return_info($lang, $count) {
switch($lang) {
case 'sv':
if($count > 1) {
$loan = "lånen";
$product = "artiklarna";
} else {
$loan = "lånet";
$product = "artikeln";
}
return replace(array('loan' => $loan,
'product' => $product),
$this->email_fragments['sv_return_info']);
break;
case 'en':
if($count > 1) {
$loan = "loans";
$product = "items";
} else {
$loan = "loan";
$product = "item";
}
return replace(array('loan' => $loan,
'product' => $product),
$this->email_fragments['en_return_info']);
break;
default:
throw new Exception("Invalid language: ".$lang);
}
}
private function send_reminder($user, $expiring, $overdue) {
$uid = $user->get_name();
$name = $this->ldap->get_firstname($uid);
$expiring_count = count($expiring);
$overdue_count = count($overdue);
$total = $expiring_count + $overdue_count;
$subject = $this->make_reminder_subject($expiring_count,
$overdue_count);
$intro_sv = $this->email_fragments['sv_reminder_intro'];
$intro_en = $this->email_fragments['en_reminder_intro'];
$info_sv = array();
$info_en = array();
if($expiring_count > 0) {
$info_sv[] = $this->make_notice('sv', 'expiring', $expiring);
$info_en[] = $this->make_notice('en', 'expiring', $expiring);
}
if($overdue_count > 0) {
$info_sv[] = $this->make_notice('sv', 'overdue', $overdue);
$info_en[] = $this->make_notice('en', 'overdue', $overdue);
}
$info_sv = implode("\n\n", $info_sv);
$info_en = implode("\n\n", $info_en);
$returns_sv = $this->make_return_info('sv', $total);
$returns_en = $this->make_return_info('en', $total);
$footer = $this->email_fragments['footer'];
$message = <<<EOF
Hej $name!
$intro_sv
$info_sv
$returns_sv
----
$intro_en
$info_en
$returns_en
$footer
EOF;
$this->send_email($uid, $subject, $message);
}
private function send_email($uid, $subject, $message) {
try {
mb_send_mail($this->ldap->get_user_email($uid),
$subject,
$message,
'From: '.$this->sender);
} catch(Exception $e) {
error_log($e->getMessage());
mb_send_mail($this->error,
"Kunde inte skicka mail",
"Mail kunde inte skickas till ".$uid);
}
}
private function run_cleanup() {
/*
Drop username and notes for all users who:
- has not already been purged
- has never initiated an action in the system
- was created more than one year ago
- has no active loans
- endtime of latest loan is more than one year in the past
*/
$cutoff = $this->now->sub(new DateInterval('P1Y'));
$stale_users = [];
foreach(get_ids('user') as $user_id) {
$user = new User($user_id);
if($user->get_name() === null) {
# User already purged
continue;
}
if($user->has_created_events()) {
# User is or has been admin, only purge manually
continue;
}
$created = $this->now->setTimestamp($user->get_creation_time());
if($created >= $cutoff) {
continue;
}
if(count($user->get_loans('active')) > 0) {
continue;
}
$latest_returntime = null;
foreach($user->get_loans('inactive') as $loan) {
$returntime = $this->now->setTimestamp(
$loan->get_returntime());
if($latest_returntime === null) {
$latest_returntime = $returntime;
} elseif($latest_returntime < $returntime) {
$latest_returntime = $returntime;
}
}
if($latest_returntime === null || $latest_returntime < $cutoff) {
$stale_users[] = $user;
}
}
foreach($stale_users as $user) {
$name = $user->get_name();
error_log("Dropping identifying data for user $name");
# Not dropping just yet, just logging who would be dropped
#$user->set_name(null);
#$user->set_notes('');
}
}
}
?>