From 15f4597637cdd6e041fce61a186145cc93bb96b1 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Mon, 2 May 2022 15:21:56 +0200
Subject: [PATCH 01/18] Fixed formatting bugs in the outgoing emails

---
 include/Cron.php | 48 +++++++++++++++++++++++++++---------------------
 1 file changed, 27 insertions(+), 21 deletions(-)

diff --git a/include/Cron.php b/include/Cron.php
index ad6d26b..eeb26bd 100644
--- a/include/Cron.php
+++ b/include/Cron.php
@@ -9,8 +9,8 @@ class Cron {
         $this->now = new DateTimeImmutable();
         $this->sender = $sender;
         $this->error = $error;
-        $warn_time = DateInterval::createFromDateString('3 days');
-        $this->warn_date = $this->now->add($warn_time);
+        $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();
@@ -58,7 +58,7 @@ class Cron {
         if(!$expiring) {
             return '';
         }
-        $days = $this->warn_date->d;
+        $days = $this->warn_time->d;
         switch($lang) {
             case 'sv':
                 $msg = "Följande lån går ut om mindre än ".$days." dagar:";
@@ -76,16 +76,16 @@ class Cron {
             default:
                 throw new Exception("Invalid language: ".$lang);
         }
-        $msg .= "\n\n";
+        $lines = array();
         foreach($expiring as $loan) {
             $product = $loan->get_product();
             $serial = $product->get_serial();
             $brand = $product->get_brand();
             $name = $product->get_name();
             $endtime = format_date($loan->get_endtime());
-            $msg .= $serial.": ".$brand." ".$name.$itemglue.$endtime;
+            $lines[] = $serial.": ".$brand." ".$name.$itemglue.$endtime;
         }
-        return $msg;
+        return $msg."\n\n".implode("\n", $lines);
     }
 
     private function make_overdue_notice($lang, $overdue) {
@@ -108,16 +108,16 @@ class Cron {
             default:
                 throw new Exception("Invalid language: ".$lang);
         }
-        $msg .= "\n\n";
+        $lines = array();
         foreach($overdue as $loan) {
             $product = $loan->get_product();
             $serial = $product->get_serial();
             $brand = $product->get_brand();
             $name = $product->get_name();
             $endtime = format_date($loan->get_endtime());
-            $msg .= $serial.": ".$brand." ".$name.$itemglue.$endtime;
+            $lines[] = $serial.": ".$brand." ".$name.$itemglue.$endtime;
         }
-        return $msg;
+        return $msg."\n\n".implode("\n", $lines);
     }
 
     private function make_return_info($lang, $count) {
@@ -148,25 +148,31 @@ class Cron {
 
     }
 
-    private function send_reminder($user, $expiring, $expired) {
+    private function send_reminder($user, $expiring, $overdue) {
         $uid = $user->get_name();
         $name = $this->ldap->get_firstname($uid);
 
-        $subject = $this->make_subject(count($expiring), count($expired));
+        $expiring_count = count($expiring);
+        $overdue_count = count($overdue);
+        $total = $expiring_count + $overdue_count;
+
+        $subject = $this->make_subject($expiring_count, $overdue_count);
 
         $info_sv = array();
-        $info_sv[] = $this->make_expiring_notice('sv', $expiring);
-        $info_sv[] = $this->make_overdue_notice('sv', $expired);
-        $info_sv = implode("\n\n", $info_sv);
-        $returns_sv = $this->make_return_info(
-            'sv', count($expiring) + count($expired));
-
         $info_en = array();
-        $info_en[] = $this->make_expiring_notice('en', $expiring);
-        $info_en[] = $this->make_overdue_notice('en', $expired);
+        if($expiring_count > 0) {
+            $info_sv[] = $this->make_expiring_notice('sv', $expiring);
+            $info_en[] = $this->make_expiring_notice('en', $expiring);
+        }
+        if($overdue_count > 0) {
+            $info_sv[] = $this->make_overdue_notice('sv', $overdue);
+            $info_en[] = $this->make_overdue_notice('en', $overdue);
+        }
+        $info_sv = implode("\n\n", $info_sv);
+        $returns_sv = $this->make_return_info('sv', $total);
+
         $info_en = implode("\n\n", $info_en);
-        $returns_en = $this->make_return_info(
-            'en', count($expiring) + count($expired));
+        $returns_en = $this->make_return_info('en', $total);
 
         $message = <<<EOF
 Hej $name!

From 79d9f45c383a061ad1899a4bd14c1145deb25b1d Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Mon, 16 May 2022 13:49:22 +0200
Subject: [PATCH 02/18] Refactored the reminder generaion

---
 include/Cron.php | 106 +++++++++++++++++++++++------------------------
 1 file changed, 52 insertions(+), 54 deletions(-)

diff --git a/include/Cron.php b/include/Cron.php
index eeb26bd..a8a5159 100644
--- a/include/Cron.php
+++ b/include/Cron.php
@@ -14,6 +14,38 @@ class Cron {
         $this->run_interval = DateInterval::createFromDateString('1 day');
         $this->kvs = new Kvs();
         $this->ldap = new Ldap();
+
+        $days = $this->warn_time->d;
+        $this->strings = array(
+            'en' => array(
+                '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(
+                '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() {
@@ -54,68 +86,34 @@ class Cron {
         return $subject.implode(" och ", $messages);
     }
 
-    private function make_expiring_notice($lang, $expiring) {
-        if(!$expiring) {
+    private function make_notice($lang, $type, $list) {
+        if(!$list) {
             return '';
         }
-        $days = $this->warn_time->d;
-        switch($lang) {
-            case 'sv':
-                $msg = "Följande lån går ut om mindre än ".$days." dagar:";
-                $itemglue = ", går ut ";
-                break;
-            case 'en':
-                if(count($expiring) == 1) {
-                    $loanstr = "loan expires";
-                } else {
-                    $loanstr = "loans expire";
-                }
-                $msg = "The following ".$loanstr." in less than ".$days." days:";
-                $itemglue = ", expires on ";
-                break;
-            default:
-                throw new Exception("Invalid language: ".$lang);
+        if(!array_key_exists($lang, $this->strings)) {
+            throw new Exception("Invalid languange: $lang");
         }
-        $lines = array();
-        foreach($expiring 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[] = $serial.": ".$brand." ".$name.$itemglue.$endtime;
+        $strings = $this->strings[$lang];
+        if(!array_key_exists($type, $strings)) {
+            throw new Exception("Invalid type: $type");
         }
-        return $msg."\n\n".implode("\n", $lines);
-    }
+        $strings = $strings[$type];
 
-    private function make_overdue_notice($lang, $overdue) {
-        if(!$overdue) {
-            return '';
-        }
-        switch($lang) {
-            case 'sv':
-                $msg = "Följande lån har gått ut:";
-                $itemglue = ", gick ut ";
-                break;
-            case 'en':
-                if(count($overdue) == 1) {
-                    $msg = "The following loan has expired:";
-                } else {
-                    $msg = "The following loans have expired:";
-                }
-                $itemglue = ", expired on ";
-                break;
-            default:
-                throw new Exception("Invalid language: ".$lang);
+        $msg = $strings['single'];
+        if(count($list) > 1) {
+            $msg = $strings['multi'];
         }
+
         $lines = array();
-        foreach($overdue as $loan) {
+        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[] = $serial.": ".$brand." ".$name.$itemglue.$endtime;
+
+            $lines[] = "$brand $name, ".$strings['serial']
+                      ." $serial, ".$strings['expiry']." $endtime";
         }
         return $msg."\n\n".implode("\n", $lines);
     }
@@ -161,12 +159,12 @@ class Cron {
         $info_sv = array();
         $info_en = array();
         if($expiring_count > 0) {
-            $info_sv[] = $this->make_expiring_notice('sv', $expiring);
-            $info_en[] = $this->make_expiring_notice('en', $expiring);
+            $info_sv[] = $this->make_notice('sv', 'expiring', $expiring);
+            $info_en[] = $this->make_notice('en', 'expiring', $expiring);
         }
         if($overdue_count > 0) {
-            $info_sv[] = $this->make_overdue_notice('sv', $overdue);
-            $info_en[] = $this->make_overdue_notice('en', $overdue);
+            $info_sv[] = $this->make_notice('sv', 'overdue', $overdue);
+            $info_en[] = $this->make_notice('en', 'overdue', $overdue);
         }
         $info_sv = implode("\n\n", $info_sv);
         $returns_sv = $this->make_return_info('sv', $total);

From c1ba468807b5ceb9201c5b08888857e70055d928 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Tue, 19 Jul 2022 15:27:31 +0200
Subject: [PATCH 03/18] Whitespace cleanup

---
 include/User.php | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/include/User.php b/include/User.php
index a34ca03..ba218b4 100644
--- a/include/User.php
+++ b/include/User.php
@@ -3,7 +3,7 @@ class User extends Entity {
     private $id = 0;
     private $name = '';
     private $notes = '';
-    
+
     public static function create_user($name) {
         $ins_user = prepare('insert into `user`(`name`) values (?)');
         bind($ins_user, 's', $name);
@@ -34,7 +34,7 @@ class User extends Entity {
         $this->id = $id;
         $this->update_fields();
     }
-    
+
     private function update_fields() {
         $get = prepare('select * from `user` where `id`=?');
         bind($get, 'i', $this->id);
@@ -125,11 +125,11 @@ class User extends Entity {
     public function get_id() {
         return $this->id;
     }
-    
+
     public function get_name() {
         return $this->name;
     }
-    
+
     public function set_name($newname) {
         $update = prepare('update `user` set `name`=? where `id`=?');
         bind($update, 'si', $newname, $this->id);
@@ -141,7 +141,7 @@ class User extends Entity {
     public function get_notes() {
         return $this->notes;
     }
-    
+
     public function set_notes($newnotes) {
         $update = prepare('update `user` set `notes`=? where `id`=?');
         bind($update, 'si', $newnotes, $this->id);
@@ -151,7 +151,7 @@ class User extends Entity {
     }
 
     public function get_loans($type = 'both') {
-        $statement = "select `id` from `event` 
+        $statement = "select `id` from `event`
                       left join `loan` on `event`.`id` = `loan`.`event`
                       where
                           `type`='loan' and `user`=?";

From 396a3f067e2394b288a87fef6c07e44c90a6734b Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Tue, 19 Jul 2022 15:29:58 +0200
Subject: [PATCH 04/18] Config item $reminder_sender changed to $sender

---
 config.php.example | 2 +-
 cron.php           | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/config.php.example b/config.php.example
index 982e551..9a6fbb8 100644
--- a/config.php.example
+++ b/config.php.example
@@ -7,7 +7,7 @@ $db_pass = 'dbpassword';
 $db_name = 'dbuser';
 
 # Address to use as the sender for reminder emails
-$reminder_sender = 'noreply@example.com';
+$sender = 'noreply@example.com';
 
 # Address to send cron error messages to
 $error_address = 'root@example.com';
diff --git a/cron.php b/cron.php
index d2170df..5e52909 100755
--- a/cron.php
+++ b/cron.php
@@ -7,7 +7,7 @@ require('./include/functions.php');
 
 header('Content-Type: text/html; charset=UTF-8');
 
-$cron = new Cron($reminder_sender, $error_address);
+$cron = new Cron($sender, $error_address);
 $cron->run();
 
 ?>

From a16e5f2479b45db07e92f3bc253750302d6e7c3c Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Tue, 19 Jul 2022 15:48:35 +0200
Subject: [PATCH 05/18] Whitespace cleanup

---
 include/Event.php | 6 +++---
 include/Loan.php  | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/include/Event.php b/include/Event.php
index 865a5d3..711bb5b 100644
--- a/include/Event.php
+++ b/include/Event.php
@@ -44,7 +44,7 @@ class Event {
         $event_id = $insert->insert_id;
         return new Event($event_id);
     }
-    
+
     public function __construct($id) {
         $search = prepare('select `id` from `event`
                            where `id`=?');
@@ -57,7 +57,7 @@ class Event {
         $this->id = $result['id'];
         $this->update_fields();
     }
-    
+
     protected function update_fields() {
         $get = prepare('select * from `event` where `id`=?');
         bind($get, 'i', $this->id);
@@ -83,7 +83,7 @@ class Event {
     public function get_returntime() {
         return $this->returntime;
     }
-    
+
     public function is_active() {
         if($this->returntime === null) {
             return true;
diff --git a/include/Loan.php b/include/Loan.php
index 08311b7..fe8346b 100644
--- a/include/Loan.php
+++ b/include/Loan.php
@@ -15,7 +15,7 @@ class Loan extends Event {
         commit_trans();
         return new Loan($event_id);
     }
-    
+
     public function __construct($id) {
         parent::__construct($id);
         $search = prepare('select * from `loan` where `event`=?');
@@ -27,7 +27,7 @@ class Loan extends Event {
         }
         $this->update_fields();
     }
-    
+
     protected function update_fields() {
         parent::update_fields();
         $get = prepare('select * from `loan` where `event`=?');

From 04af074849a7b602d9c58b440e4115376c2d6c34 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Tue, 19 Jul 2022 16:57:42 +0200
Subject: [PATCH 06/18] Whitespace cleanup

---
 include/Ajax.php | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/include/Ajax.php b/include/Ajax.php
index 4ede200..1fa756d 100644
--- a/include/Ajax.php
+++ b/include/Ajax.php
@@ -1,14 +1,14 @@
 <?php
 class Ajax extends Responder {
     private $action = '';
-    
+
     public function __construct() {
         parent::__construct();
         if(isset($_GET['action'])) {
             $this->action = $_GET['action'];
         }
     }
-    
+
     public function render() {
         $out = '';
         switch($this->action) {
@@ -98,7 +98,7 @@ class Ajax extends Responder {
             return new Failure('Artikeln är redan utlånad.');
         }
     }
-    
+
     private function return_product() {
         $product = null;
         try {
@@ -138,7 +138,7 @@ class Ajax extends Responder {
         }
         return new Failure('Lån saknas.');
     }
-    
+
     private function start_inventory() {
         try {
             Inventory::begin();
@@ -147,7 +147,7 @@ class Ajax extends Responder {
             return new Failure('Inventering redan igång.');
         }
     }
-    
+
     private function end_inventory() {
         $inventory = Inventory::get_active();
         if($inventory === null) {
@@ -156,7 +156,7 @@ class Ajax extends Responder {
         $inventory->end();
         return new Success('Inventering avslutad.');
     }
-    
+
     private function inventory_product() {
         $inventory = Inventory::get_active();
         if($inventory === null) {
@@ -273,7 +273,7 @@ class Ajax extends Responder {
         }
         return new Success('Ändringarna sparade.');
     }
-    
+
     private function update_user() {
         $id = $_POST['id'];
         $name = $_POST['name'];
@@ -355,7 +355,7 @@ class Ajax extends Responder {
             return new Failure('Det finns ingen mall med det namnet.');
         }
     }
-    
+
     private function suggest() {
         return new Success(suggest($_POST['type']));
     }

From 311402e1b894a74cac0af05941fba5ac8867ca0e Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Tue, 19 Jul 2022 17:00:08 +0200
Subject: [PATCH 07/18] Initial implementation of loan receipts

---
 include/Cron.php     | 105 ++++++++++++++++++++++++++++++++++++++++---
 include/Loan.php     |  16 ++++++-
 pending_receipts.sql |   9 ++++
 3 files changed, 122 insertions(+), 8 deletions(-)
 create mode 100644 pending_receipts.sql

diff --git a/include/Cron.php b/include/Cron.php
index a8a5159..66e65d2 100644
--- a/include/Cron.php
+++ b/include/Cron.php
@@ -18,6 +18,10 @@ class Cron {
         $days = $this->warn_time->d;
         $this->strings = array(
             'en' => array(
+                'new' => array(
+                    'expiry' => "expires on",
+                    '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:",
@@ -32,6 +36,10 @@ class Cron {
                 ),
             ),
             'sv' => array(
+                'new' => array(
+                    'expiry' => "går ut",
+                    '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:",
@@ -49,6 +57,81 @@ class Cron {
     }
 
     public function run() {
+        $this->run_receipts();
+        $this->run_reminders();
+    }
+
+    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'];
+
+            $notify_loans = array();
+            foreach($user->get_loans('active') as $loan) {
+                if($loan->get_starttime() >= $since_time) {
+                    $notify_loans[] = $loan;
+                }
+            }
+            $this->send_receipt($user, $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 send_receipt($user, $loans) {
+        $count = count($loans);
+        if($count > 1) {
+            $new_sv = "nya lån";
+            $new_en = "new loans";
+            $have = "have";
+        } else {
+            $new_sv = "nytt lån";
+            $new_en = "new loan";
+            $have = "has";
+        }
+        $subject = "DSV Helpdesk: $count $new_sv";
+
+        $intro_sv = "";
+        $intro_en = "$count $new_en $have been registered to your user:";
+
+        $list_sv = $this->make_notice('sv', 'new', $loans);
+        $list_en = $this->make_notice('en', 'new', $loans);
+
+        $message = <<<EOF
+Hej $name!
+
+Det här är ett automatiskt meddelande om att $count $new_sv har registrerats på din användare:
+
+$list_sv
+
+Eventuella artiklar du inte hämtat ut redan kan hämtas från Helpdesk. Svara på det här mailet om du har några frågor.
+
+----
+
+This is an automated message to inform you that $count $new_en $have been registered in your name:
+
+$list_en
+
+Any products you haven't already picked up can be collected from Helpdesk. Please reply to this email if you have any questions.
+
+Mvh
+DSV Helpdesk
+helpdesk@dsv.su.se
+08 - 16 16 48
+
+EOF;
+        $this->send_email($uid, $subject, $message);
+    }
+
+    private function run_reminders() {
         $lastrun = $this->kvs->get_value('lastrun', 0);
         $nextrun = $this->now
                         ->setTimestamp($lastrun)
@@ -99,11 +182,6 @@ class Cron {
         }
         $strings = $strings[$type];
 
-        $msg = $strings['single'];
-        if(count($list) > 1) {
-            $msg = $strings['multi'];
-        }
-
         $lines = array();
         foreach($list as $loan) {
             $product = $loan->get_product();
@@ -115,6 +193,15 @@ class Cron {
             $lines[] = "$brand $name, ".$strings['serial']
                       ." $serial, ".$strings['expiry']." $endtime";
         }
+
+        if($type === 'new') {
+            return implode("\n", $lines);
+        }
+
+        $msg = $strings['single'];
+        if(count($list) > 1) {
+            $msg = $strings['multi'];
+        }
         return $msg."\n\n".implode("\n", $lines);
     }
 
@@ -195,6 +282,10 @@ helpdesk@dsv.su.se
 08 - 16 16 48
 
 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,
@@ -203,8 +294,8 @@ EOF;
         } catch(Exception $e) {
             error_log($e->message);
             mb_send_mail($this->error,
-                         "Kunde inte skicka påminnelse",
-                         "Påminnelse kunde inte skickas till ".$uid);
+                         "Kunde inte skicka mail",
+                         "Mail kunde inte skickas till ".$uid);
         }
     }
 }
diff --git a/include/Loan.php b/include/Loan.php
index fe8346b..cb7f0ae 100644
--- a/include/Loan.php
+++ b/include/Loan.php
@@ -12,8 +12,22 @@ class Loan extends Event {
         $endtime .= '13:00';
         bind($insert, 'iii', $event_id, $user->get_id(), strtotime($endtime));
         execute($insert);
+        $loan = new Loan($event_id);
+
+        $now = time();
+        $pending = prepare('select * from `pending_receipt` where `user` = ?
+                                and `since_time` < ? and `send_time` > ?');
+        bind($pending, 'iii', $user->get_id(), $now, $now);
+        execute($pending);
+        if(result_single($pending) === null) {
+            $add = prepare('insert into `pending_receipt`
+                                (`user`, `since_time`, `send_time`)
+                                values(?, ?, ?)');
+            bind($add, 'iii', $user->get_id(), $now, $now + 3600);
+            execute($add);
+        }
         commit_trans();
-        return new Loan($event_id);
+        return $loan;
     }
 
     public function __construct($id) {
diff --git a/pending_receipts.sql b/pending_receipts.sql
new file mode 100644
index 0000000..550da5b
--- /dev/null
+++ b/pending_receipts.sql
@@ -0,0 +1,9 @@
+create table `pending_receipt` (
+  `user` bigint(20) not null,
+  primary key (`user`),
+  constraint `pr_f_user`
+    foreign key(`user`) references `user`(`id`),
+  `send_time` bigint(20) not null,
+  `since_time` bigint(20) not null
+) character set utf8mb4,
+  collate utf8mb4_unicode_ci;

From cd627f811d8d894843df06702d397bfd73fa91d2 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Wed, 20 Jul 2022 10:41:35 +0200
Subject: [PATCH 08/18] Made it work

---
 include/Cron.php | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/include/Cron.php b/include/Cron.php
index 66e65d2..3303ff5 100644
--- a/include/Cron.php
+++ b/include/Cron.php
@@ -77,9 +77,9 @@ class Cron {
                     $notify_loans[] = $loan;
                 }
             }
-            $this->send_receipt($user, $loans);
+            $this->send_receipt($user, $notify_loans);
             $delete = prepare('delete from `pending_receipt`
-                               where `user = ? and `send_time` < ?');
+                               where `user` = ? and `send_time` < ?');
             bind($delete, 'ii', $user->get_id(), $this->now->getTimestamp());
             execute($delete);
         }
@@ -87,6 +87,9 @@ class Cron {
     }
 
     private function send_receipt($user, $loans) {
+        $uid = $user->get_name();
+        $name = $this->ldap->get_firstname($uid);
+
         $count = count($loans);
         if($count > 1) {
             $new_sv = "nya lån";
@@ -292,7 +295,7 @@ EOF;
                          $message,
                          'From: '.$this->sender);
         } catch(Exception $e) {
-            error_log($e->message);
+            error_log($e->getMessage());
             mb_send_mail($this->error,
                          "Kunde inte skicka mail",
                          "Mail kunde inte skickas till ".$uid);

From 0af33c5dd660c510e992b2274de9f781bf1fbb2a Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Tue, 26 Jul 2022 10:23:09 +0200
Subject: [PATCH 09/18] Added buttons to quickly change the runtime of a loan

---
 html/fragments.html |  5 +++++
 script.js           | 19 ++++++++++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/html/fragments.html b/html/fragments.html
index 1e0e633..9dbb066 100644
--- a/html/fragments.html
+++ b/html/fragments.html
@@ -610,6 +610,11 @@
       Låna ut
     </button>
     <br/>
+    <label>Löptid:</label>
+    <button onClick="JavaScript:loanLength(event, 7)">1 vecka</button>
+    <button onClick="JavaScript:loanLength(event, 365)">1 år</button>
+    <button onClick="JavaScript:loanLength(event, 1095)">3 år</button>
+    <br/>
     <label for="end">Slutdatum:</label>
     <input type="text"
            id="end"
diff --git a/script.js b/script.js
index a708d23..d79d1d6 100644
--- a/script.js
+++ b/script.js
@@ -97,7 +97,7 @@ function getFragment(name, callback) {
             console.log(result);
         }
     }
-    
+
     var data = new FormData()
     data.append('fragment', name)
     ajaxRequest('getfragment', data, unpack)
@@ -573,3 +573,20 @@ function showFile(event) {
     var filefield = event.currentTarget.parentNode.filename
     filefield.value = event.currentTarget.files[0].name
 }
+
+function loanLength(event, days) {
+    event.preventDefault()
+    var end = document.getElementById('end')
+    var enddate = new Date()
+    enddate.setDate(enddate.getDate() + days)
+    console.log(enddate)
+    var month = enddate.getMonth()
+    if(month < 10) {
+        month = '0' + month
+    }
+    var day = enddate.getDay()
+    if(day < 10) {
+        day = '0' + day
+    }
+    end.value = enddate.getFullYear() + '-' + month + '-' + day
+}

From dbe2a0ee9070583a2591818e19faa3a6a91870b4 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Tue, 26 Jul 2022 10:24:16 +0200
Subject: [PATCH 10/18] Removed debug line

---
 script.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/script.js b/script.js
index d79d1d6..2dc75e4 100644
--- a/script.js
+++ b/script.js
@@ -579,7 +579,6 @@ function loanLength(event, days) {
     var end = document.getElementById('end')
     var enddate = new Date()
     enddate.setDate(enddate.getDate() + days)
-    console.log(enddate)
     var month = enddate.getMonth()
     if(month < 10) {
         month = '0' + month

From 1d0caf95134f9149428d3f41eb012a34b7a6bf54 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Tue, 26 Jul 2022 10:56:59 +0200
Subject: [PATCH 11/18] Sorted the bugs when picking an interval via button

---
 html/fragments.html |  6 +++---
 script.js           | 17 +++++++++++++----
 2 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/html/fragments.html b/html/fragments.html
index 9dbb066..761a059 100644
--- a/html/fragments.html
+++ b/html/fragments.html
@@ -611,9 +611,9 @@
     </button>
     <br/>
     <label>Löptid:</label>
-    <button onClick="JavaScript:loanLength(event, 7)">1 vecka</button>
-    <button onClick="JavaScript:loanLength(event, 365)">1 år</button>
-    <button onClick="JavaScript:loanLength(event, 1095)">3 år</button>
+    <button onClick="JavaScript:loanLength(event, 7, 'day')">1 vecka</button>
+    <button onClick="JavaScript:loanLength(event, 1, 'year')">1 år</button>
+    <button onClick="JavaScript:loanLength(event, 3, 'year')">3 år</button>
     <br/>
     <label for="end">Slutdatum:</label>
     <input type="text"
diff --git a/script.js b/script.js
index 2dc75e4..3478b9a 100644
--- a/script.js
+++ b/script.js
@@ -483,6 +483,7 @@ function calendar(event) {
     if(!input.cal) {
         var cal = new dhtmlXCalendarObject(input.id)
         cal.hideTime()
+        cal.setDate(input.value)
         input.cal = cal
         cal.show()
     }
@@ -574,16 +575,24 @@ function showFile(event) {
     filefield.value = event.currentTarget.files[0].name
 }
 
-function loanLength(event, days) {
+function loanLength(event, length, unit) {
     event.preventDefault()
     var end = document.getElementById('end')
     var enddate = new Date()
-    enddate.setDate(enddate.getDate() + days)
-    var month = enddate.getMonth()
+    switch(unit) {
+    case 'day':
+        enddate.setDate(enddate.getDate() + length)
+        break
+    case 'year':
+        enddate.setFullYear(enddate.getFullYear() + length)
+        break;
+    }
+    // javascript zero-indexes months because of course
+    var month = enddate.getMonth() + 1
     if(month < 10) {
         month = '0' + month
     }
-    var day = enddate.getDay()
+    var day = enddate.getDate()
     if(day < 10) {
         day = '0' + day
     }

From 22d760a0af2828933f14e24be121f225a126d20f Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Wed, 27 Jul 2022 10:48:53 +0200
Subject: [PATCH 12/18] Whitespace cleanup

---
 include/Page.php | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/include/Page.php b/include/Page.php
index 1d2607e..4d97158 100644
--- a/include/Page.php
+++ b/include/Page.php
@@ -1,7 +1,7 @@
 <?php
 abstract class Page extends Responder {
     protected abstract function render_body();
-    
+
     protected $page = 'checkout';
     protected $title = "DSV Utlåning";
     protected $subtitle = '';
@@ -15,11 +15,11 @@ abstract class Page extends Responder {
                                  'history' => 'Historik',
                                  'search' => 'Sök');
     private $template_parts = array();
-    
+
     public function __construct() {
         parent::__construct();
         $this->template_parts = get_fragments('./html/base.html');
-        
+
         if(isset($_GET['page'])) {
             $this->page = $_GET['page'];
         }
@@ -27,7 +27,7 @@ abstract class Page extends Responder {
             $this->subtitle = $this->menuitems[$this->page];
         }
     }
-    
+
     public function render() {
         $this->render_head();
         $this->render_body();
@@ -36,7 +36,7 @@ abstract class Page extends Responder {
         }
         $this->render_foot();
     }
-    
+
     final private function render_head() {
         $headtitle = $this->title;
         $pagetitle = $this->title;
@@ -83,7 +83,7 @@ abstract class Page extends Responder {
                             'message' => $this->error),
                       $this->fragments['message']));
     }
-    
+
     final private function render_foot() {
         print($this->template_parts['foot']);
     }

From b3434dbb6a66813d0d6281618c562353994abd5d Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Wed, 27 Jul 2022 10:52:52 +0200
Subject: [PATCH 13/18] Changed loan extension form to have a saner default end
 time.

---
 include/Page.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/Page.php b/include/Page.php
index 4d97158..7c4060a 100644
--- a/include/Page.php
+++ b/include/Page.php
@@ -211,7 +211,7 @@ abstract class Page extends Responder {
             $status = $loan->get_status();
             $note = '';
             if($status !== 'inactive_loan') {
-                $extend = format_date(default_loan_end(time()));
+                $extend = format_date($loan->get_endtime());
                 $note = replace(array('id' => $product->get_id(),
                                       'end_new' => $extend),
                                 $this->fragments['loan_extend_form']);

From b967c7dde296c8638a5c4fb5377c42e789dbbfee Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Wed, 27 Jul 2022 10:58:11 +0200
Subject: [PATCH 14/18] Added the new tables pending_receipt and loan_extension
 to the schema

---
 database.sql | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/database.sql b/database.sql
index 840e57b..7f459e7 100644
--- a/database.sql
+++ b/database.sql
@@ -147,3 +147,24 @@ create table `kvs` (
   `value` varchar(64) not null default ''
 ) character set utf8mb4,
   collate utf8mb4_unicode_ci;
+
+create table `pending_receipt` (
+  `user` bigint(20) not null,
+  primary key (`user`),
+  constraint `pr_f_user`
+    foreign key(`user`) references `user`(`id`),
+  `send_time` bigint(20) not null,
+  `since_time` bigint(20) not null
+) character set utf8mb4,
+  collate utf8mb4_unicode_ci;
+
+create table `loan_extension` (
+  `loan` bigint(20) not null,
+  constraint `le_f_loan`
+    foreign key(`loan`) references `loan`(`event`),
+  `extend_time` bigint(20) not null,
+  primary key (`loan`, `extend_time`),
+  `old_end` bigint(20) not null,
+  `new_end` bigint(20) not null
+) character set utf8mb4,
+  collate utf8mb4_unicode_ci;

From 4e9e5b93af0bac7343e37854402112dfef8c0fe4 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Wed, 27 Jul 2022 11:04:09 +0200
Subject: [PATCH 15/18] Added a function to get the latest loan extension.
 Broke receipt queueing into a function. Changed $this->id to $this->get_id()
 for the sake of consistency.

---
 include/Loan.php | 61 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 43 insertions(+), 18 deletions(-)

diff --git a/include/Loan.php b/include/Loan.php
index cb7f0ae..2640d73 100644
--- a/include/Loan.php
+++ b/include/Loan.php
@@ -13,19 +13,7 @@ class Loan extends Event {
         bind($insert, 'iii', $event_id, $user->get_id(), strtotime($endtime));
         execute($insert);
         $loan = new Loan($event_id);
-
-        $now = time();
-        $pending = prepare('select * from `pending_receipt` where `user` = ?
-                                and `since_time` < ? and `send_time` > ?');
-        bind($pending, 'iii', $user->get_id(), $now, $now);
-        execute($pending);
-        if(result_single($pending) === null) {
-            $add = prepare('insert into `pending_receipt`
-                                (`user`, `since_time`, `send_time`)
-                                values(?, ?, ?)');
-            bind($add, 'iii', $user->get_id(), $now, $now + 3600);
-            execute($add);
-        }
+        $loan->queue_receipt($user);
         commit_trans();
         return $loan;
     }
@@ -45,13 +33,28 @@ class Loan extends Event {
     protected function update_fields() {
         parent::update_fields();
         $get = prepare('select * from `loan` where `event`=?');
-        bind($get, 'i', $this->id);
+        bind($get, 'i', $this->get_id());
         execute($get);
         $loan = result_single($get);
         $this->user = $loan['user'];
         $this->endtime = $loan['endtime'];
     }
 
+    protected function queue_receipt($user) {
+        $now = time();
+        $pending = prepare('select * from `pending_receipt` where `user` = ?
+                                and `since_time` < ? and `send_time` > ?');
+        bind($pending, 'iii', $user->get_id(), $now, $now);
+        execute($pending);
+        if(result_single($pending) === null) {
+            $add = prepare('insert into `pending_receipt`
+                                (`user`, `since_time`, `send_time`)
+                                values(?, ?, ?)');
+            bind($add, 'iii', $user->get_id(), $now, $now + 3600);
+            execute($add);
+        }
+    }
+
     public function get_user() {
         return new User($this->user);
     }
@@ -61,18 +64,40 @@ class Loan extends Event {
     }
 
     public function extend($time) {
+        $oldend = $this->get_endtime();
+        $now = time();
         $ts = strtotime($time . ' 13:00');
-        $query = prepare('update `loan` set `endtime`=? where `event`=?');
-        bind($query, 'ii', $ts, $this->id);
-        execute($query);
+
+        begin_trans();
+        $extend = prepare('update `loan` set `endtime`=? where `event`=?');
+        bind($extend, 'ii', $ts, $this->get_id());
+        execute($extend);
+
+        $log = prepare('insert into `loan_extension`
+                            (`loan`, `extend_time`, `old_end`, `new_end`)
+                            values (?, ?, ?, ?)');
+        bind($log, 'iiii', $this->get_id(), $now, $oldend, $ts);
+        execute($log);
+
+        $this->queue_receipt($this->get_user());
+
         $this->endtime = $ts;
+        commit_trans();
         return true;
     }
 
+    public function get_last_extension() {
+        $select = prepare('select max(`extend_time`) as `extend_time`
+                           from `loan_extension` where `loan`=?');
+        bind($select, 'i', $this->get_id());
+        execute($select);
+        return result_single($select)['max(`extend_time`)'];
+    }
+
     public function end() {
         $now = time();
         $query = prepare('update `event` set `returntime`=? where `id`=?');
-        bind($query, 'ii', $now, $this->id);
+        bind($query, 'ii', $now, $this->get_id());
         execute($query);
         $this->returntime = $now;
         return true;

From ae8b73cb88fecd4afb470c14270794a5bab117f4 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Wed, 27 Jul 2022 13:15:06 +0200
Subject: [PATCH 16/18] Trim whitespace from serials when checking out and
 returning products

---
 include/Ajax.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/Ajax.php b/include/Ajax.php
index 1fa756d..99abc22 100644
--- a/include/Ajax.php
+++ b/include/Ajax.php
@@ -87,7 +87,7 @@ class Ajax extends Responder {
         }
         $product = null;
         try {
-            $product = new Product($_POST['product'], 'serial');
+            $product = new Product(trim($_POST['product']), 'serial');
         } catch(Exception $e) {
             return new Failure('Ogiltigt serienummer.');
         }
@@ -102,7 +102,7 @@ class Ajax extends Responder {
     private function return_product() {
         $product = null;
         try {
-            $product = new Product($_POST['serial'], 'serial');
+            $product = new Product(trim($_POST['serial']), 'serial');
         } catch(Exception $e) {
             return new Failure('Ogiltigt serienummer.');
         }

From 1b8c2e1e181d7b5070f62e670053be3a5bc91b78 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Wed, 27 Jul 2022 13:52:54 +0200
Subject: [PATCH 17/18] Corrected a database field name

---
 include/Loan.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/Loan.php b/include/Loan.php
index 2640d73..89b8465 100644
--- a/include/Loan.php
+++ b/include/Loan.php
@@ -91,7 +91,7 @@ class Loan extends Event {
                            from `loan_extension` where `loan`=?');
         bind($select, 'i', $this->get_id());
         execute($select);
-        return result_single($select)['max(`extend_time`)'];
+        return result_single($select)['extend_time'];
     }
 
     public function end() {

From 9307604aa08b4c793cdd44b53fb8bb0ae00e4243 Mon Sep 17 00:00:00 2001
From: Erik Thuning <boooink@gmail.com>
Date: Wed, 27 Jul 2022 13:53:18 +0200
Subject: [PATCH 18/18] Implemented receipts for loan extensions

---
 include/Cron.php | 108 ++++++++++++++++++++++++++++++++---------------
 1 file changed, 75 insertions(+), 33 deletions(-)

diff --git a/include/Cron.php b/include/Cron.php
index 3303ff5..80bc67a 100644
--- a/include/Cron.php
+++ b/include/Cron.php
@@ -19,9 +19,17 @@ class Cron {
         $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:",
@@ -37,9 +45,17 @@ class Cron {
             ),
             '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:",
@@ -71,13 +87,16 @@ class Cron {
             $user = new User($row['user']);
             $since_time = $row['since_time'];
 
-            $notify_loans = array();
+            $new_loans = array();
+            $extended_loans = array();
             foreach($user->get_loans('active') as $loan) {
                 if($loan->get_starttime() >= $since_time) {
-                    $notify_loans[] = $loan;
+                    $new_loans[] = $loan;
+                } else if($loan->get_last_extension() >= $since_time) {
+                    $extended_loans[] = $loan;
                 }
             }
-            $this->send_receipt($user, $notify_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());
@@ -86,44 +105,70 @@ class Cron {
         commit_trans();
     }
 
-    private function send_receipt($user, $loans) {
+    private function make_receipt_subject($num_new, $num_extended) {
+        $subject = "DSV Helpdesk: ";
+        $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);
 
-        $count = count($loans);
-        if($count > 1) {
-            $new_sv = "nya lån";
-            $new_en = "new loans";
-            $have = "have";
-        } else {
-            $new_sv = "nytt lån";
-            $new_en = "new loan";
-            $have = "has";
+        $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);
         }
-        $subject = "DSV Helpdesk: $count $new_sv";
+        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);
 
-        $intro_sv = "";
-        $intro_en = "$count $new_en $have been registered to your user:";
-
-        $list_sv = $this->make_notice('sv', 'new', $loans);
-        $list_en = $this->make_notice('en', 'new', $loans);
+        $info_sv = array();
+        $info_en = array();
+        if($new_count > 0) {
+            $info_sv[] = "Eventuella artiklar du inte hämtat ut redan kan hämtas från Helpdesk.";
+            $info_en[] = "Any products you haven't already picked up can be collected from Helpdesk.";
+        }
+        $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);
 
         $message = <<<EOF
 Hej $name!
 
-Det här är ett automatiskt meddelande om att $count $new_sv har registrerats på din användare:
+Det här är ett automatiskt meddelande från Helpdesk.
 
 $list_sv
 
-Eventuella artiklar du inte hämtat ut redan kan hämtas från Helpdesk. Svara på det här mailet om du har några frågor.
+$info_sv
 
 ----
 
-This is an automated message to inform you that $count $new_en $have been registered in your name:
+This is an automated message from Helpdesk.
 
 $list_en
 
-Any products you haven't already picked up can be collected from Helpdesk. Please reply to this email if you have any questions.
+$info_en
 
 Mvh
 DSV Helpdesk
@@ -158,18 +203,18 @@ EOF;
         }
     }
 
-    private function make_subject($num_expiring, $num_expired) {
+    private function make_reminder_subject($num_expiring, $num_expired) {
         $subject = "DSV Helpdesk: ";
         $messages = array();
         if($num_expiring > 0) {
-            $messages[] = $num_expiring." utgående lån";
+            $messages[] = $num_expiring." utgående";
         }
         if($num_expired > 1) {
-            $messages[] = $num_expired." försenade lån";
+            $messages[] = $num_expired." försenade";
         } elseif($num_expired > 0) {
-            $messages[] = $num_expired." försenat lån";
+            $messages[] = $num_expired." försenat";
         }
-        return $subject.implode(" och ", $messages);
+        return $subject.implode(" och ", $messages)." lån";
     }
 
     private function make_notice($lang, $type, $list) {
@@ -197,10 +242,6 @@ EOF;
                       ." $serial, ".$strings['expiry']." $endtime";
         }
 
-        if($type === 'new') {
-            return implode("\n", $lines);
-        }
-
         $msg = $strings['single'];
         if(count($list) > 1) {
             $msg = $strings['multi'];
@@ -244,7 +285,8 @@ EOF;
         $overdue_count = count($overdue);
         $total = $expiring_count + $overdue_count;
 
-        $subject = $this->make_subject($expiring_count, $overdue_count);
+        $subject = $this->make_reminder_subject($expiring_count,
+                                                $overdue_count);
 
         $info_sv = array();
         $info_en = array();