Cleanup Members.pm - minor
[koha.git] / C4 / Members.pm
index 7a9da97..5d9cc1f 100644 (file)
@@ -27,6 +27,7 @@ use C4::Log; # logaction
 use C4::Overdues;
 use C4::Reserves;
 use C4::Accounts;
+use C4::Biblio;
 
 our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
 
@@ -57,13 +58,17 @@ BEGIN {
                &GetRoadTypeDetails 
                &GetSortDetails
                &GetTitles
-                &GetPatronImage
+
+    &GetPatronImage
+    &PutPatronImage
+    &RmPatronImage
 
                &GetMemberAccountRecords
                &GetBorNotifyAcctRecord
 
                &GetborCatFromCatType 
                &GetBorrowercategory
+    &GetBorrowercategoryList
 
                &GetBorrowersWhoHaveNotBorrowedSince
                &GetBorrowersWhoHaveNeverBorrowed
@@ -92,15 +97,16 @@ BEGIN {
        );
 
        #Check data
-       push @EXPORT, qw(
-               &checkuniquemember 
-               &checkuserpassword
-               &Check_Userid
-               &fixEthnicity
-               &ethnicitycategories 
-               &fixup_cardnumber
-               &checkcardnumber
-       );
+    push @EXPORT, qw(
+        &checkuniquemember
+        &checkuserpassword
+        &Check_Userid
+        &Generate_Userid
+        &fixEthnicity
+        &ethnicitycategories
+        &fixup_cardnumber
+        &checkcardnumber
+    );
 }
 
 =head1 NAME
@@ -123,6 +129,8 @@ This module contains routines for adding, modifying and deleting members/patrons
 
   ($count, $borrowers) = &SearchMember($searchstring, $type,$category_type,$filter,$showallbranches);
 
+=back
+
 Looks up patrons (borrowers) by name.
 
 BUGFIX 499: C<$type> is now used to determine type of search.
@@ -176,7 +184,7 @@ sub SearchMember {
         $query .= ($category_type ? " AND category_type = ".$dbh->quote($category_type) : ""); 
         $query .= " WHERE (surname LIKE ? OR cardnumber like ?) ";
         if (C4::Context->preference("IndependantBranches") && !$showallbranches){
-          if (C4::Context->userenv && C4::Context->userenv->{flags}!=1 && C4::Context->userenv->{'branch'}){
+          if (C4::Context->userenv && C4::Context->userenv->{flags} % 2 !=1 && C4::Context->userenv->{'branch'}){
             $query.=" AND borrowers.branchcode =".$dbh->quote(C4::Context->userenv->{'branch'}) unless (C4::Context->userenv->{'branch'} eq "insecure");
           }
         }
@@ -189,7 +197,7 @@ sub SearchMember {
         $count = @data;
         $query .= " WHERE ";
         if (C4::Context->preference("IndependantBranches") && !$showallbranches){
-          if (C4::Context->userenv && C4::Context->userenv->{flags}!=1 && C4::Context->userenv->{'branch'}){
+          if (C4::Context->userenv && C4::Context->userenv->{flags} % 2 !=1 && C4::Context->userenv->{'branch'}){
             $query.=" borrowers.branchcode =".$dbh->quote(C4::Context->userenv->{'branch'})." AND " unless (C4::Context->userenv->{'branch'} eq "insecure");
           }      
         }     
@@ -212,9 +220,19 @@ sub SearchMember {
 
             # FIXME - .= <<EOT;
         }
-        $query = $query . ") OR cardnumber LIKE ?
-        order by $orderby";
+        $query = $query . ") OR cardnumber LIKE ? ";
         push( @bind, $searchstring );
+        if (C4::Context->preference('ExtendedPatronAttributes')) {
+            $query .= "OR borrowernumber IN (
+SELECT borrowernumber
+FROM borrower_attributes
+JOIN borrower_attribute_types USING (code)
+WHERE staff_searchable = 1
+AND attribute like ?
+)";
+            push (@bind, $searchstring);
+        }
+        $query .= "order by $orderby";
 
         # FIXME - .= <<EOT;
     }
@@ -232,7 +250,7 @@ sub SearchMember {
 
 =head2 GetMemberDetails
 
-($borrower, $flags) = &GetMemberDetails($borrowernumber, $cardnumber);
+($borrower) = &GetMemberDetails($borrowernumber, $cardnumber);
 
 Looks up a patron and returns information about him or her. If
 C<$borrowernumber> is true (nonzero), C<&GetMemberDetails> looks
@@ -324,6 +342,12 @@ fields from the reserves table of the Koha database.
 
 =back
 
+C<$borrower-E<gt>{authflags}> is a hash giving more detailed information
+about the top-level permissions flags set for the borrower.  For example,
+if a user has the "editcatalogue" permission,
+C<$borrower-E<gt>{authflags}-E<gt>{editcatalogue}> will exist and have
+the value "1".
+
 =cut
 
 sub GetMemberDetails {
@@ -332,11 +356,11 @@ sub GetMemberDetails {
     my $query;
     my $sth;
     if ($borrowernumber) {
-        $sth = $dbh->prepare("select borrowers.*,category_type from borrowers left join categories on borrowers.categorycode=categories.categorycode where  borrowernumber=?");
+        $sth = $dbh->prepare("select borrowers.*,category_type,categories.description from borrowers left join categories on borrowers.categorycode=categories.categorycode where  borrowernumber=?");
         $sth->execute($borrowernumber);
     }
     elsif ($cardnumber) {
-        $sth = $dbh->prepare("select borrowers.*,category_type from borrowers left join categories on borrowers.categorycode=categories.categorycode where cardnumber=?");
+        $sth = $dbh->prepare("select borrowers.*,category_type,categories.description from borrowers left join categories on borrowers.categorycode=categories.categorycode where cardnumber=?");
         $sth->execute($cardnumber);
     }
     else {
@@ -479,7 +503,7 @@ sub patronflags {
 }
 
 
-=item GetMember
+=head2 GetMember
 
   $borrower = &GetMember($information, $type);
 
@@ -504,7 +528,7 @@ SELECT borrowers.*, categories.category_type, categories.description
 FROM borrowers 
 LEFT JOIN categories on borrowers.categorycode=categories.categorycode 
 ";
-    if ($type eq 'cardnumber' || $type eq 'firstname'|| $type eq 'userid'|| $type eq 'borrowernumber'){
+    if (defined($type) and ( $type eq 'cardnumber' || $type eq 'firstname'|| $type eq 'userid'|| $type eq 'borrowernumber' ) ){
         $information = uc $information;
         $sth = $dbh->prepare("$select WHERE $type=?");
     } else {
@@ -512,29 +536,27 @@ LEFT JOIN categories on borrowers.categorycode=categories.categorycode
     }
     $sth->execute($information);
     my $data = $sth->fetchrow_hashref;
-    $sth->finish;
     ($data) and return ($data);
 
-    if ($type eq 'cardnumber' || $type eq 'firstname') {    # otherwise, try with firstname
+    if (defined($type) and ($type eq 'cardnumber' || $type eq 'firstname')) {    # otherwise, try with firstname
         $sth = $dbh->prepare("$select WHERE firstname like ?");
         $sth->execute($information);
         $data = $sth->fetchrow_hashref;
-        $sth->finish;
         ($data) and return ($data);
     }
     return undef;        
 }
 
-=item GetMemberIssuesAndFines
+=head2 GetMemberIssuesAndFines
 
-  ($borrowed, $due, $fine) = &GetMemberIssuesAndFines($borrowernumber);
+  ($overdue_count, $issue_count, $total_fines) = &GetMemberIssuesAndFines($borrowernumber);
 
 Returns aggregate data about items borrowed by the patron with the
 given borrowernumber.
 
-C<&GetMemberIssuesAndFines> returns a three-element array. C<$borrowed> is the
-number of books the patron currently has borrowed. C<$due> is the
-number of overdue items the patron currently has borrowed. C<$fine> is
+C<&GetMemberIssuesAndFines> returns a three-element array.  C<$overdue_count> is the
+number of overdue items the patron currently has borrowed. C<$issue_count> is the
+number of books the patron currently has borrowed.  C<$total_fines> is
 the total fine currently due by the borrower.
 
 =cut
@@ -543,45 +565,52 @@ the total fine currently due by the borrower.
 sub GetMemberIssuesAndFines {
     my ( $borrowernumber ) = @_;
     my $dbh   = C4::Context->dbh;
-    my $query =
-      "Select count(*) from issues where borrowernumber='$borrowernumber' and
-    returndate is NULL";
+    my $query = "SELECT COUNT(*) FROM issues WHERE borrowernumber = ?";
 
     $debug and warn $query."\n";
     my $sth = $dbh->prepare($query);
-    $sth->execute;
-    my $data = $sth->fetchrow_hashref;
+    $sth->execute($borrowernumber);
+    my $issue_count = $sth->fetchrow_arrayref->[0];
     $sth->finish;
+
     $sth = $dbh->prepare(
-        "Select count(*) from issues where
-    borrowernumber='$borrowernumber' and date_due < now() and returndate is NULL"
+        "SELECT COUNT(*) FROM issues 
+         WHERE borrowernumber = ? 
+         AND date_due < now()"
     );
-    $sth->execute;
-    my $data2 = $sth->fetchrow_hashref;
+    $sth->execute($borrowernumber);
+    my $overdue_count = $sth->fetchrow_arrayref->[0];
     $sth->finish;
-    $sth = $dbh->prepare(
-        "Select sum(amountoutstanding) from accountlines where
-    borrowernumber='$borrowernumber'"
-    );
-    $sth->execute;
-    my $data3 = $sth->fetchrow_hashref;
+
+    $sth = $dbh->prepare("SELECT SUM(amountoutstanding) FROM accountlines WHERE borrowernumber = ?");
+    $sth->execute($borrowernumber);
+    my $total_fines = $sth->fetchrow_arrayref->[0];
     $sth->finish;
 
-    return ( $data2->{'count(*)'}, $data->{'count(*)'},
-        $data3->{'sum(amountoutstanding)'} );
+    return ($overdue_count, $issue_count, $total_fines);
+}
+
+sub columns(;$) {
+    return @{C4::Context->dbh->selectcol_arrayref("SHOW columns from borrowers")};
 }
 
 =head2
 
-=item ModMember
+=head2 ModMember
+
+=over 4
 
-  &ModMember($borrowernumber);
+my $success = ModMember(borrowernumber => $borrowernumber, [ field => value ]... );
 
 Modify borrower's data.  All date fields should ALREADY be in ISO format.
 
+return :
+true on success, or false on failure
+
+=back
+
 =cut
 
-#'
 sub ModMember {
     my (%data) = @_;
     my $dbh = C4::Context->dbh;
@@ -590,57 +619,69 @@ sub ModMember {
         if (my $tempdate = $data{$_}) {                                 # assignment, not comparison
             ($tempdate =~ /$iso_re/) and next;                          # Congatulations, you sent a valid ISO date.
             warn "ModMember given $_ not in ISO format ($tempdate)";
-            if (my $tempdate2 = format_date_in_iso($tempdate)) {        # assignment, not comparison
-                $data{$_} = $tempdate2;
-            } else {
-                warn "ModMember cannot convert '$tempdate' (from syspref)";
+            my $tempdate2 = format_date_in_iso($tempdate);
+            if (!$tempdate2 or $tempdate2 eq '0000-00-00') {
+                warn "ModMember cannot convert '$tempdate' (from syspref to ISO)";
+                next;
             }
+            $data{$_} = $tempdate2;
         }
     }
     if (!$data{'dateofbirth'}){
-        undef $data{'dateofbirth'};
-    }
-    my $qborrower=$dbh->prepare("SHOW columns from borrowers");
-    $qborrower->execute;
-    my %hashborrowerfields;  
-    while (my ($field)=$qborrower->fetchrow){
-      $hashborrowerfields{$field}=1;
-    }  
+        delete $data{'dateofbirth'};
+    }
+    my @columns = &columns;
+    my %hashborrowerfields = (map {$_=>1} @columns);
     my $query = "UPDATE borrowers SET \n";
     my $sth;
     my @parameters;  
     
     # test to know if you must update or not the borrower password
-    if ( $data{'password'} eq '****' ) {
-        delete $data{'password'};
-    } else {
-        $data{'password'} = md5_base64( $data{'password'} )  if ($data{'password'} ne "");
-        delete $data{'password'} if ($data{password} eq "");
+    if (exists $data{password}) {
+        if ($data{password} eq '****' or $data{password} eq '') {
+            delete $data{password};
+        } else {
+            $data{password} = md5_base64($data{password});
+        }
     }
-    foreach (keys %data)
-    { push @parameters,"$_ = ".$dbh->quote($data{$_}) if ($_ ne 'borrowernumber' and $_ ne 'flags' and $hashborrowerfields{$_}); }
-    $query .= join (',',@parameters) . "\n WHERE borrowernumber=? \n";
+    my @badkeys;
+    foreach (keys %data) {  
+        next if ($_ eq 'borrowernumber' or $_ eq 'flags');
+        if ($hashborrowerfields{$_}){
+            $query .= " $_=?, "; 
+            push @parameters,$data{$_};
+        } else {
+            push @badkeys, $_;
+            delete $data{$_};
+        }
+    }
+    (@badkeys) and warn scalar(@badkeys) . " Illegal key(s) passed to ModMember: " . join(',',@badkeys);
+    $query =~ s/, $//;
+    $query .= " WHERE borrowernumber=?";
+    push @parameters, $data{'borrowernumber'};
     $debug and print STDERR "$query (executed w/ arg: $data{'borrowernumber'})";
     $sth = $dbh->prepare($query);
-    $sth->execute($data{'borrowernumber'});
+    my $execute_success = $sth->execute(@parameters);
     $sth->finish;
 
 # ok if its an adult (type) it may have borrowers that depend on it as a guarantor
 # so when we update information for an adult we should check for guarantees and update the relevant part
 # of their records, ie addresses and phone numbers
     my $borrowercategory= GetBorrowercategory( $data{'category_type'} );
-    if ( $borrowercategory->{'category_type'} eq ('A' || 'S') ) {
+    if ( exists  $borrowercategory->{'category_type'} && $borrowercategory->{'category_type'} eq ('A' || 'S') ) {
         # is adult check guarantees;
         UpdateGuarantees(%data);
     }
-    &logaction(C4::Context->userenv->{'number'},"MEMBERS","MODIFY",$data{'borrowernumber'},"") 
+    logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "$query (executed w/ arg: $data{'borrowernumber'})") 
         if C4::Context->preference("BorrowersLog");
+
+    return $execute_success;
 }
 
 
 =head2
 
-=item AddMember
+=head2 AddMember
 
   $borrowernumber = &AddMember(%borrower);
 
@@ -729,14 +770,13 @@ sub AddMember {
     $data{'borrowernumber'} = $dbh->{'mysql_insertid'};     # unneeded w/ autoincrement ?  
     # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
     
-    &logaction(C4::Context->userenv->{'number'},"MEMBERS","CREATE",$data{'borrowernumber'},"") 
-        if C4::Context->preference("BorrowersLog");
+    logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
     
     # check for enrollment fee & add it if needed
     $sth = $dbh->prepare("SELECT enrolmentfee FROM categories WHERE categorycode=?");
     $sth->execute($data{'categorycode'});
     my ($enrolmentfee) = $sth->fetchrow;
-    if ($enrolmentfee) {
+    if ($enrolmentfee && $enrolmentfee > 0) {
         # insert fee in patron debts
         manualinvoice($data{'borrowernumber'}, '', '', 'A', $enrolmentfee);
     }
@@ -760,6 +800,21 @@ sub Check_Userid {
     }
 }
 
+sub Generate_Userid {
+  my ($borrowernumber, $firstname, $surname) = @_;
+  my $newuid;
+  my $offset = 0;
+  do {
+    $firstname =~ s/[[:digit:][:space:][:blank:][:punct:][:cntrl:]]//g;
+    $surname =~ s/[[:digit:][:space:][:blank:][:punct:][:cntrl:]]//g;
+    $newuid = lc("$firstname.$surname");
+    $newuid .= $offset unless $offset == 0;
+    $offset++;
+
+   } while (!Check_Userid($newuid,$borrowernumber));
+
+   return $newuid;
+}
 
 sub changepassword {
     my ( $uid, $member, $digest ) = @_;
@@ -767,12 +822,13 @@ sub changepassword {
 
 #Make sure the userid chosen is unique and not theirs if non-empty. If it is not,
 #Then we need to tell the user and have them create a new one.
+    my $resultcode;
     my $sth =
       $dbh->prepare(
         "SELECT * FROM borrowers WHERE userid=? AND borrowernumber != ?");
     $sth->execute( $uid, $member );
     if ( ( $uid ne '' ) && ( my $row = $sth->fetchrow_hashref ) ) {
-        return 0;
+        $resultcode=0;
     }
     else {
         #Everything is good so we can update the information.
@@ -780,16 +836,16 @@ sub changepassword {
           $dbh->prepare(
             "update borrowers set userid=?, password=? where borrowernumber=?");
         $sth->execute( $uid, $digest, $member );
-        return 1;
+        $resultcode=1;
     }
     
-    &logaction(C4::Context->userenv->{'number'},"MEMBERS","CHANGE PASS",$member,"") 
-        if C4::Context->preference("BorrowersLog");
+    logaction("MEMBERS", "CHANGE PASS", $member, "") if C4::Context->preference("BorrowersLog");
+    return $resultcode;    
 }
 
 
 
-=item fixup_cardnumber
+=head2 fixup_cardnumber
 
 Warning: The caller is responsible for locking the members table in write
 mode, to avoid database corruption.
@@ -801,76 +857,65 @@ my @weightings = ( 8, 4, 6, 3, 5, 2, 1 );
 
 sub fixup_cardnumber ($) {
     my ($cardnumber) = @_;
-    my $autonumber_members = C4::Context->boolean_preference('autoMemberNum');
-    $autonumber_members = 0 unless defined $autonumber_members;
+    my $autonumber_members = C4::Context->boolean_preference('autoMemberNum') || 0;
 
     # Find out whether member numbers should be generated
     # automatically. Should be either "1" or something else.
     # Defaults to "0", which is interpreted as "no".
 
     #     if ($cardnumber !~ /\S/ && $autonumber_members) {
-    if ($autonumber_members) {
-        my $dbh = C4::Context->dbh;
-        if ( C4::Context->preference('checkdigit') eq 'katipo' ) {
-
-            # if checkdigit is selected, calculate katipo-style cardnumber.
-            # otherwise, just use the max()
-            # purpose: generate checksum'd member numbers.
-            # We'll assume we just got the max value of digits 2-8 of member #'s
-            # from the database and our job is to increment that by one,
-            # determine the 1st and 9th digits and return the full string.
-            my $sth =
-              $dbh->prepare(
-                "select max(substring(borrowers.cardnumber,2,7)) from borrowers"
-              );
-            $sth->execute;
-
-            my $data = $sth->fetchrow_hashref;
-            $cardnumber = $data->{'max(substring(borrowers.cardnumber,2,7))'};
-            $sth->finish;
-            if ( !$cardnumber ) {    # If DB has no values,
-                $cardnumber = 1000000;    # start at 1000000
-            }
-            else {
-                $cardnumber += 1;
-            }
-
-            my $sum = 0;
-            for ( my $i = 0 ; $i < 8 ; $i += 1 ) {
+    ($autonumber_members) or return $cardnumber;
+    my $checkdigit = C4::Context->preference('checkdigit');
+    my $dbh = C4::Context->dbh;
+    if ( $checkdigit and $checkdigit eq 'katipo' ) {
+
+        # if checkdigit is selected, calculate katipo-style cardnumber.
+        # otherwise, just use the max()
+        # purpose: generate checksum'd member numbers.
+        # We'll assume we just got the max value of digits 2-8 of member #'s
+        # from the database and our job is to increment that by one,
+        # determine the 1st and 9th digits and return the full string.
+        my $sth = $dbh->prepare(
+            "select max(substring(borrowers.cardnumber,2,7)) as new_num from borrowers"
+        );
+        $sth->execute;
+        my $data = $sth->fetchrow_hashref;
+        $cardnumber = $data->{new_num};
+        if ( !$cardnumber ) {    # If DB has no values,
+            $cardnumber = 1000000;    # start at 1000000
+        } else {
+            $cardnumber += 1;
+        }
 
-                # read weightings, left to right, 1 char at a time
-                my $temp1 = $weightings[$i];
+        my $sum = 0;
+        for ( my $i = 0 ; $i < 8 ; $i += 1 ) {
+            # read weightings, left to right, 1 char at a time
+            my $temp1 = $weightings[$i];
 
-                # sequence left to right, 1 char at a time
-                my $temp2 = substr( $cardnumber, $i, 1 );
+            # sequence left to right, 1 char at a time
+            my $temp2 = substr( $cardnumber, $i, 1 );
 
-                # mult each char 1-7 by its corresponding weighting
-                $sum += $temp1 * $temp2;
-            }
+            # mult each char 1-7 by its corresponding weighting
+            $sum += $temp1 * $temp2;
+        }
 
-            my $rem = ( $sum % 11 );
-            $rem = 'X' if $rem == 10;
+        my $rem = ( $sum % 11 );
+        $rem = 'X' if $rem == 10;
 
-            $cardnumber = "V$cardnumber$rem";
-        }
-        else {
+        return "V$cardnumber$rem";
+     } else {
 
      # MODIFIED BY JF: mysql4.1 allows casting as an integer, which is probably
      # better. I'll leave the original in in case it needs to be changed for you
-            my $sth =
-              $dbh->prepare(
-                "select max(cast(cardnumber as signed)) from borrowers");
-
-      #my $sth=$dbh->prepare("select max(borrowers.cardnumber) from borrowers");
-
-            $sth->execute;
-
-            my ($result) = $sth->fetchrow;
-            $sth->finish;
-            $cardnumber = $result + 1;
-        }
+     # my $sth=$dbh->prepare("select max(borrowers.cardnumber) from borrowers");
+        my $sth = $dbh->prepare(
+            "select max(cast(cardnumber as signed)) from borrowers"
+        );
+        $sth->execute;
+        my ($result) = $sth->fetchrow;
+        return $result + 1;
     }
-    return $cardnumber;
+    return $cardnumber;     # just here as a fallback/reminder 
 }
 
 =head2 GetGuarantees
@@ -910,7 +955,7 @@ sub GetGuarantees {
   &UpdateGuarantees($parent_borrno);
   
 
-C<&UpdateGuarantees> borrower data for an adulte and updates all the guarantees
+C<&UpdateGuarantees> borrower data for an adult and updates all the guarantees
 with the modified information
 
 =cut
@@ -938,45 +983,60 @@ sub UpdateGuarantees {
 }
 =head2 GetPendingIssues
 
-  ($count, $issues) = &GetPendingIssues($borrowernumber);
+  my $issues = &GetPendingIssues($borrowernumber);
 
 Looks up what the patron with the given borrowernumber has borrowed.
 
-C<&GetPendingIssues> returns a two-element array. C<$issues> is a
-reference-to-array, where each element is a reference-to-hash; the
-keys are the fields from the C<issues>, C<biblio>, and C<items> tables
-in the Koha database. C<$count> is the number of elements in
-C<$issues>.
+C<&GetPendingIssues> returns a
+reference-to-array where each element is a reference-to-hash; the
+keys are the fields from the C<issues>, C<biblio>, and C<items> tables.
+The keys include C<biblioitems> fields except marc and marcxml.
 
 =cut
 
 #'
 sub GetPendingIssues {
     my ($borrowernumber) = @_;
-    my $dbh              = C4::Context->dbh;
-
-    my $sth              = $dbh->prepare(
-   "SELECT * FROM issues 
-      LEFT JOIN items ON issues.itemnumber=items.itemnumber
-      LEFT JOIN biblio ON     items.biblionumber=biblio.biblionumber 
-      LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
+    # must avoid biblioitems.* to prevent large marc and marcxml fields from killing performance
+    # FIXME: namespace collision: each table has "timestamp" fields.  Which one is "timestamp" ?
+    # FIXME: circ/ciculation.pl tries to sort by timestamp!
+    # FIXME: C4::Print::printslip tries to sort by timestamp!
+    # FIXME: namespace collision: other collisions possible.
+    # FIXME: most of this data isn't really being used by callers.
+    my $sth = C4::Context->dbh->prepare(
+   "SELECT issues.*,
+            items.*,
+           biblio.*,
+           biblioitems.volume,
+           biblioitems.number,
+           biblioitems.itemtype,
+           biblioitems.isbn,
+           biblioitems.issn,
+           biblioitems.publicationyear,
+           biblioitems.publishercode,
+           biblioitems.volumedate,
+           biblioitems.volumedesc,
+           biblioitems.lccn,
+           biblioitems.url,
+           issues.timestamp AS timestamp,
+           issues.renewals  AS renewals,
+            items.renewals  AS totalrenewals
+    FROM   issues
+    LEFT JOIN items       ON items.itemnumber       =      issues.itemnumber
+    LEFT JOIN biblio      ON items.biblionumber     =      biblio.biblionumber
+    LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
     WHERE
-      borrowernumber=? 
-      AND returndate IS NULL
+      borrowernumber=?
     ORDER BY issues.issuedate"
     );
     $sth->execute($borrowernumber);
     my $data = $sth->fetchall_arrayref({});
-    my $today = POSIX::strftime("%Y%m%d", localtime);
-    foreach( @$data ) {
-        my $datedue = $_->{'date_due'};
-        $datedue =~ s/-//g;
-        if ( $datedue < $today ) {
-            $_->{'overdue'} = 1;
-        }
+    my $today = C4::Dates->new->output('iso');
+    foreach (@$data) {
+        $_->{date_due} or next;
+        ($_->{date_due} lt $today) and $_->{overdue} = 1;
     }
-    $sth->finish;
-    return ( scalar(@$data), $data );
+    return $data;
 }
 
 =head2 GetAllIssues
@@ -1008,12 +1068,19 @@ sub GetAllIssues {
     my $dbh   = C4::Context->dbh;
     my $count = 0;
     my $query =
-  "Select *,items.timestamp AS itemstimestamp from 
-  issues 
+  "SELECT *,issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp 
+  FROM issues 
   LEFT JOIN items on items.itemnumber=issues.itemnumber
   LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
   LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
-  where borrowernumber=? 
+  WHERE borrowernumber=? 
+  UNION ALL
+  SELECT *,old_issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp 
+  FROM old_issues 
+  LEFT JOIN items on items.itemnumber=old_issues.itemnumber
+  LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
+  LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
+  WHERE borrowernumber=? 
   order by $order";
     if ( $limit != 0 ) {
         $query .= " limit $limit";
@@ -1021,7 +1088,7 @@ sub GetAllIssues {
 
     #print $query;
     my $sth = $dbh->prepare($query);
-    $sth->execute($borrowernumber);
+    $sth->execute($borrowernumber, $borrowernumber);
     my @result;
     my $i = 0;
     while ( my $data = $sth->fetchrow_hashref ) {
@@ -1034,6 +1101,10 @@ sub GetAllIssues {
     # large chunk of older issues data put into table oldissues
     # to speed up db calls for issuing items
     if ( C4::Context->preference("ReadingHistory") ) {
+        # FIXME oldissues (not to be confused with old_issues) is
+        # apparently specific to HLT.  Not sure if the ReadingHistory
+        # syspref is still required, as old_issues by design
+        # is no longer checked with each loan.
         my $query2 = "SELECT * FROM oldissues
                       LEFT JOIN items ON items.itemnumber=oldissues.itemnumber
                       LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
@@ -1094,10 +1165,14 @@ sub GetMemberAccountRecords {
     $sth->execute( @bind );
     my $total = 0;
     while ( my $data = $sth->fetchrow_hashref ) {
+               my $biblio = GetBiblioFromItemNumber($data->{itemnumber}) if $data->{itemnumber};
+               $data->{biblionumber} = $biblio->{biblionumber};
+               $data->{title} = $biblio->{title};
         $acctlines[$numlines] = $data;
         $numlines++;
-        $total += $data->{'amountoutstanding'};
+        $total += int(100 * $data->{'amountoutstanding'}); # convert float to integer to avoid round-off errors
     }
+    $total /= 100;
     $sth->finish;
     return ( $total, \@acctlines,$numlines);
 }
@@ -1138,8 +1213,9 @@ sub GetBorNotifyAcctRecord {
     while ( my $data = $sth->fetchrow_hashref ) {
         $acctlines[$numlines] = $data;
         $numlines++;
-        $total += $data->{'amountoutstanding'};
+        $total += int(100 * $data->{'amountoutstanding'});
     }
+    $total /= 100;
     $sth->finish;
     return ( $total, \@acctlines, $numlines );
 }
@@ -1167,12 +1243,16 @@ sub checkuniquemember {
     my $dbh = C4::Context->dbh;
     my $request = ($collectivity) ?
         "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? " :
-        "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? and firstname=? and dateofbirth=? ";
+            ($dateofbirth) ?
+            "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? and firstname=?  and dateofbirth=?" :
+            "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? and firstname=?";
     my $sth = $dbh->prepare($request);
     if ($collectivity) {
         $sth->execute( uc($surname) );
-    } els{
+    } elsif($dateofbirth){
         $sth->execute( uc($surname), ucfirst($firstname), $dateofbirth );
+    }else{
+        $sth->execute( uc($surname), ucfirst($firstname));
     }
     my @data = $sth->fetchrow;
     $sth->finish;
@@ -1295,13 +1375,14 @@ to category descriptions.
 #'
 sub GetborCatFromCatType {
     my ( $category_type, $action ) = @_;
+       # FIXME - This API  seems both limited and dangerous. 
     my $dbh     = C4::Context->dbh;
     my $request = qq|   SELECT categorycode,description 
             FROM categories 
             $action
             ORDER BY categorycode|;
     my $sth = $dbh->prepare($request);
-    if ($action) {
+       if ($action) {
         $sth->execute($category_type);
     }
     else {
@@ -1325,21 +1406,49 @@ sub GetborCatFromCatType {
 
 Given the borrower's category code, the function returns the corresponding
 data hashref for a comprehensive information display.
+  
+  $arrayref_hashref = &GetBorrowercategory;
+If no category code provided, the function returns all the categories.
 
 =cut
 
 sub GetBorrowercategory {
     my ($catcode) = @_;
+    my $dbh       = C4::Context->dbh;
+    if ($catcode){
+        my $sth       =
+        $dbh->prepare(
+    "SELECT description,dateofbirthrequired,upperagelimit,category_type 
+    FROM categories 
+    WHERE categorycode = ?"
+        );
+        $sth->execute($catcode);
+        my $data =
+        $sth->fetchrow_hashref;
+        $sth->finish();
+        return $data;
+    } 
+    return;  
+}    # sub getborrowercategory
+
+=head2 GetBorrowercategoryList
+  $arrayref_hashref = &GetBorrowercategoryList;
+If no category code provided, the function returns all the categories.
+
+=cut
+
+sub GetBorrowercategoryList {
     my $dbh       = C4::Context->dbh;
     my $sth       =
-      $dbh->prepare(
-"SELECT description,dateofbirthrequired,upperagelimit,category_type 
- FROM categories 
WHERE categorycode = ?"
-      );
-    $sth->execute($catcode);
+    $dbh->prepare(
+    "SELECT * 
   FROM categories 
   ORDER BY description"
+        );
+    $sth->execute;
     my $data =
-      $sth->fetchrow_hashref;
+    $sth->fetchall_arrayref({});
     $sth->finish();
     return $data;
 }    # sub getborrowercategory
@@ -1533,35 +1642,36 @@ sub GetSortDetails {
     my $sth = $dbh->prepare($query);
     $sth->execute( $category, $sortvalue );
     my $lib = $sth->fetchrow;
-    return ($lib);
+    return ($lib) if ($lib);
+    return ($sortvalue) unless ($lib);
 }
 
-=head2 DeleteBorrower 
+=head2 MoveMemberToDeleted
 
-  () = &DeleteBorrower($member);
+  $result = &MoveMemberToDeleted($borrowernumber);
 
-delete all data fo borrowers and add record to deletedborrowers table
-C<&$member>this is the borrowernumber
+Copy the record from borrowers to deletedborrowers table.
 
 =cut
 
+# FIXME: should do it in one SQL statement w/ subquery
+# Otherwise, we should return the @data on success
+
 sub MoveMemberToDeleted {
-    my ($member) = @_;
+    my ($member) = shift or return;
     my $dbh = C4::Context->dbh;
-    my $query;
-    $query = qq|SELECT * 
+    my $query = qq|SELECT * 
           FROM borrowers 
           WHERE borrowernumber=?|;
     my $sth = $dbh->prepare($query);
     $sth->execute($member);
     my @data = $sth->fetchrow_array;
-    $sth->finish;
+    (@data) or return;  # if we got a bad borrowernumber, there's nothing to insert
     $sth =
       $dbh->prepare( "INSERT INTO deletedborrowers VALUES ("
           . ( "?," x ( scalar(@data) - 1 ) )
           . "?)" );
     $sth->execute(@data);
-    $sth->finish;
 }
 
 =head2 DelMember
@@ -1593,8 +1703,7 @@ sub DelMember {
     $sth = $dbh->prepare($query);
     $sth->execute($borrowernumber);
     $sth->finish;
-    &logaction(C4::Context->userenv->{'number'},"MEMBERS","DELETE",$borrowernumber,"") 
-        if C4::Context->preference("BorrowersLog");
+    logaction("MEMBERS", "DELETE", $borrowernumber, "") if C4::Context->preference("BorrowersLog");
     return $sth->rows;
 }
 
@@ -1625,7 +1734,7 @@ EOF
     $sth = $dbh->prepare("SELECT enrolmentfee FROM categories WHERE categorycode=?");
     $sth->execute($borrower->{'categorycode'});
     my ($enrolmentfee) = $sth->fetchrow;
-    if ($enrolmentfee) {
+    if ($enrolmentfee && $enrolmentfee > 0) {
         # insert fee in patron debts
         manualinvoice($borrower->{'borrowernumber'}, '', '', 'A', $enrolmentfee);
     }
@@ -1698,23 +1807,62 @@ sub GetTitles {
 
 =head2 GetPatronImage
 
-    $patronimage = &GetPatronImage('cardnumber');
+    my ($imagedata, $dberror) = GetPatronImage($cardnumber);
 
-Returns the path/filename.jpg of the image for the patron with the supplied cardnumber.
+Returns the mimetype and binary image data of the image for the patron with the supplied cardnumber.
 
 =cut
 
 sub GetPatronImage {
-    my $cardnumber = shift;
+    my ($cardnumber) = @_;
     warn "Cardnumber passed to GetPatronImage is $cardnumber" if $debug;
-    my $htdocs = C4::Context->config('intrahtdocs');
-    my $picture = "patronimages/" . $cardnumber . ".jpg";
-    if ( -e "$htdocs/$picture" ) {
-           return ( "/intranet-tmpl/$picture" );   # FIXME: This is a real hack and should be handled better, but I'm in a hurry... -fbcit
-    }
-    else {
-        return ();
-    }
+    my $dbh = C4::Context->dbh;
+    my $query = 'SELECT mimetype, imagefile FROM patronimage WHERE cardnumber = ?';
+    my $sth = $dbh->prepare($query);
+    $sth->execute($cardnumber);
+    my $imagedata = $sth->fetchrow_hashref;
+    warn "Database error!" if $sth->errstr;
+    return $imagedata, $sth->errstr;
+}
+
+=head2 PutPatronImage
+
+    PutPatronImage($cardnumber, $mimetype, $imgfile);
+
+Stores patron binary image data and mimetype in database.
+NOTE: This function is good for updating images as well as inserting new images in the database.
+
+=cut
+
+sub PutPatronImage {
+    my ($cardnumber, $mimetype, $imgfile) = @_;
+    warn "Parameters passed in: Cardnumber=$cardnumber, Mimetype=$mimetype, " . ($imgfile ? "Imagefile" : "No Imagefile") if $debug;
+    my $dbh = C4::Context->dbh;
+    my $query = "INSERT INTO patronimage (cardnumber, mimetype, imagefile) VALUES (?,?,?) ON DUPLICATE KEY UPDATE imagefile = ?;";
+    my $sth = $dbh->prepare($query);
+    $sth->execute($cardnumber,$mimetype,$imgfile,$imgfile);
+    warn "Error returned inserting $cardnumber.$mimetype." if $sth->errstr;
+    return $sth->errstr;
+}
+
+=head2 RmPatronImage
+
+    my ($dberror) = RmPatronImage($cardnumber);
+
+Removes the image for the patron with the supplied cardnumber.
+
+=cut
+
+sub RmPatronImage {
+    my ($cardnumber) = @_;
+    warn "Cardnumber passed to GetPatronImage is $cardnumber" if $debug;
+    my $dbh = C4::Context->dbh;
+    my $query = "DELETE FROM patronimage WHERE cardnumber = ?;";
+    my $sth = $dbh->prepare($query);
+    $sth->execute($cardnumber);
+    my $dberror = $sth->errstr;
+    warn "Database error!" if $sth->errstr;
+    return $dberror;
 }
 
 =head2 GetRoadTypeDetails (OUEST-PROVENCE)
@@ -1745,24 +1893,48 @@ WHERE roadtypeid=?|;
 &GetBorrowersWhoHaveNotBorrowedSince($date)
 
 this function get all borrowers who haven't borrowed since the date given on input arg.
-
+      
 =cut
 
 sub GetBorrowersWhoHaveNotBorrowedSince {
-    my $date = shift;
-    return unless $date;    # date is mandatory.
+### TODO : It could be dangerous to delete Borrowers who have just been entered and who have not yet borrowed any book. May be good to add a dateexpiry or dateenrolled filter.      
+       
+                my $filterdate = shift||POSIX::strftime("%Y-%m-%d",localtime());
+    my $filterbranch = shift || 
+                        ((C4::Context->preference('IndependantBranches') 
+                             && C4::Context->userenv 
+                             && C4::Context->userenv->{flags} % 2 !=1 
+                             && C4::Context->userenv->{branch})
+                         ? C4::Context->userenv->{branch}
+                         : "");  
     my $dbh   = C4::Context->dbh;
     my $query = "
-        SELECT borrowers.borrowernumber,max(timestamp)
+        SELECT borrowers.borrowernumber,max(issues.timestamp) as latestissue
         FROM   borrowers
-          LEFT JOIN issues ON borrowers.borrowernumber = issues.borrowernumber
-        WHERE issues.borrowernumber IS NOT NULL
-        GROUP BY borrowers.borrowernumber
+        JOIN   categories USING (categorycode)
+        LEFT JOIN issues ON borrowers.borrowernumber = issues.borrowernumber
+        WHERE  category_type <> 'S'
    ";
+    my @query_params;
+    if ($filterbranch && $filterbranch ne ""){ 
+        $query.=" AND borrowers.branchcode= ?";
+        push @query_params,$filterbranch;
+    }    
+    $query.=" GROUP BY borrowers.borrowernumber";
+    if ($filterdate){ 
+        $query.=" HAVING latestissue <? OR latestissue IS NULL";
+        push @query_params,$filterdate;
+    }
+    warn $query if $debug;
     my $sth = $dbh->prepare($query);
-    $sth->execute;
+    if (scalar(@query_params)>0){  
+        $sth->execute(@query_params);
+    } 
+    else {
+        $sth->execute;
+    }      
+    
     my @results;
-
     while ( my $data = $sth->fetchrow_hashref ) {
         push @results, $data;
     }
@@ -1780,15 +1952,35 @@ I<$result> is a ref to an array which all elements are a hasref.
 =cut
 
 sub GetBorrowersWhoHaveNeverBorrowed {
+    my $filterbranch = shift || 
+                        ((C4::Context->preference('IndependantBranches') 
+                             && C4::Context->userenv 
+                             && C4::Context->userenv->{flags} % 2 !=1 
+                             && C4::Context->userenv->{branch})
+                         ? C4::Context->userenv->{branch}
+                         : "");  
     my $dbh   = C4::Context->dbh;
     my $query = "
-        SELECT borrowers.borrowernumber,max(timestamp)
+        SELECT borrowers.borrowernumber,max(timestamp) as latestissue
         FROM   borrowers
           LEFT JOIN issues ON borrowers.borrowernumber = issues.borrowernumber
         WHERE issues.borrowernumber IS NULL
    ";
+    my @query_params;
+    if ($filterbranch && $filterbranch ne ""){ 
+        $query.=" AND borrowers.branchcode= ?";
+        push @query_params,$filterbranch;
+    }
+    warn $query if $debug;
+  
     my $sth = $dbh->prepare($query);
-    $sth->execute;
+    if (scalar(@query_params)>0){  
+        $sth->execute(@query_params);
+    } 
+    else {
+        $sth->execute;
+    }      
+    
     my @results;
     while ( my $data = $sth->fetchrow_hashref ) {
         push @results, $data;
@@ -1809,17 +2001,30 @@ This hashref is containt the number of time this borrowers has borrowed before I
 
 sub GetBorrowersWithIssuesHistoryOlderThan {
     my $dbh  = C4::Context->dbh;
-    my $date = shift;
-    return unless $date;    # date is mandatory.
+    my $date = shift ||POSIX::strftime("%Y-%m-%d",localtime());
+    my $filterbranch = shift || 
+                        ((C4::Context->preference('IndependantBranches') 
+                             && C4::Context->userenv 
+                             && C4::Context->userenv->{flags} % 2 !=1 
+                             && C4::Context->userenv->{branch})
+                         ? C4::Context->userenv->{branch}
+                         : "");  
     my $query = "
        SELECT count(borrowernumber) as n,borrowernumber
-       FROM issues
+       FROM old_issues
        WHERE returndate < ?
          AND borrowernumber IS NOT NULL 
-       GROUP BY borrowernumber
-   ";
+    "; 
+    my @query_params;
+    push @query_params, $date;
+    if ($filterbranch){
+        $query.="   AND branchcode = ?";
+        push @query_params, $filterbranch;
+    }    
+    $query.=" GROUP BY borrowernumber ";
+    warn $query if $debug;
     my $sth = $dbh->prepare($query);
-    $sth->execute($date);
+    $sth->execute(@query_params);
     my @results;
 
     while ( my $data = $sth->fetchrow_hashref ) {
@@ -1828,14 +2033,65 @@ sub GetBorrowersWithIssuesHistoryOlderThan {
     return \@results;
 }
 
+=head2 GetBorrowersNamesAndLatestIssue
+
+$results = &GetBorrowersNamesAndLatestIssueList(@borrowernumbers)
+
+this function get borrowers Names and surnames and Issue information.
+
+I<@borrowernumbers> is an array which all elements are borrowernumbers.
+This hashref is containt the number of time this borrowers has borrowed before I<$date> and the borrowernumber.
+
+=cut
+
+sub GetBorrowersNamesAndLatestIssue {
+    my $dbh  = C4::Context->dbh;
+    my @borrowernumbers=@_;  
+    my $query = "
+       SELECT surname,lastname, phone, email,max(timestamp)
+       FROM borrowers 
+         LEFT JOIN issues ON borrowers.borrowernumber=issues.borrowernumber
+       GROUP BY borrowernumber
+   ";
+    my $sth = $dbh->prepare($query);
+    $sth->execute;
+    my $results = $sth->fetchall_arrayref({});
+    return $results;
+}
+
+=head2 DebarMember
+
+=over 4
+
+my $success = DebarMember( $borrowernumber );
+
+marks a Member as debarred, and therefore unable to checkout any more
+items.
+
+return :
+true on success, false on failure
+
+=back
+
+=cut
+
+sub DebarMember {
+    my $borrowernumber = shift;
+
+    return unless defined $borrowernumber;
+    return unless $borrowernumber =~ /^\d+$/;
+
+    return ModMember( borrowernumber => $borrowernumber,
+                      debarred       => 1 );
+    
+}
+
 END { }    # module clean-up code here (global destructor)
 
 1;
 
 __END__
 
-=back
-
 =head1 AUTHOR
 
 Koha Team