Follow-up fix for Bug 4473 - Recent comments view for the OPAC
[koha.git] / C4 / Auth.pm
1 package C4::Auth;
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 use strict;
21 #use warnings; FIXME - Bug 2505
22 use Digest::MD5 qw(md5_base64);
23 use Storable qw(thaw freeze);
24 use URI::Escape;
25 use CGI::Session;
26
27 require Exporter;
28 use C4::Context;
29 use C4::Templates;    # to get the template
30 use C4::Members;
31 use C4::Koha;
32 use C4::Branch; # GetBranches
33 use C4::VirtualShelves;
34 use POSIX qw/strftime/;
35 use List::MoreUtils qw/ any /;
36
37 # use utf8;
38 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $servers $memcached);
39
40 BEGIN {
41     sub psgi_env { any { /^psgi\./ } keys %ENV }
42     sub safe_exit {
43         if ( psgi_env ) { die 'psgi:exit' }
44         else { exit }
45     }
46
47     $VERSION     = 3.02;                                                                                                            # set version for version checking
48     $debug       = $ENV{DEBUG};
49     @ISA         = qw(Exporter);
50     @EXPORT      = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
51     @EXPORT_OK   = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions);
52     %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] );
53     $ldap        = C4::Context->config('useldapserver') || 0;
54     $cas         = C4::Context->preference('casAuthentication');
55     $caslogout   = C4::Context->preference('casLogout');
56     require C4::Auth_with_cas;             # no import
57     if ($ldap) {
58         require C4::Auth_with_ldap;
59         import C4::Auth_with_ldap qw(checkpw_ldap);
60     }
61     if ($cas) {
62         import  C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url);
63     }
64     $servers = C4::Context->config('memcached_servers');
65     if ($servers) {
66         require Cache::Memcached;
67         $memcached = Cache::Memcached->new({
68                                                servers => [ $servers ],
69                                                debug   => 0,
70                                                compress_threshold => 10_000,
71                                                namespace => C4::Context->config('memcached_namespace') || 'koha',
72                                            });
73     }
74 }
75
76 =head1 NAME
77
78 C4::Auth - Authenticates Koha users
79
80 =head1 SYNOPSIS
81
82   use CGI;
83   use C4::Auth;
84   use C4::Output;
85
86   my $query = new CGI;
87
88   my ($template, $borrowernumber, $cookie)
89     = get_template_and_user(
90         {
91             template_name   => "opac-main.tmpl",
92             query           => $query,
93       type            => "opac",
94       authnotrequired => 1,
95       flagsrequired   => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
96   }
97     );
98
99   output_html_with_http_headers $query, $cookie, $template->output;
100
101 =head1 DESCRIPTION
102
103 The main function of this module is to provide
104 authentification. However the get_template_and_user function has
105 been provided so that a users login information is passed along
106 automatically. This gets loaded into the template.
107
108 =head1 FUNCTIONS
109
110 =head2 get_template_and_user
111
112  my ($template, $borrowernumber, $cookie)
113      = get_template_and_user(
114        {
115          template_name   => "opac-main.tmpl",
116          query           => $query,
117          type            => "opac",
118          authnotrequired => 1,
119          flagsrequired   => {borrow => 1, catalogue => '*', tools => 'import_patrons' },
120        }
121      );
122
123 This call passes the C<query>, C<flagsrequired> and C<authnotrequired>
124 to C<&checkauth> (in this module) to perform authentification.
125 See C<&checkauth> for an explanation of these parameters.
126
127 The C<template_name> is then used to find the correct template for
128 the page. The authenticated users details are loaded onto the
129 template in the HTML::Template LOOP variable C<USER_INFO>. Also the
130 C<sessionID> is passed to the template. This can be used in templates
131 if cookies are disabled. It needs to be put as and input to every
132 authenticated page.
133
134 More information on the C<gettemplate> sub can be found in the
135 Output.pm module.
136
137 =cut
138
139 my $SEARCH_HISTORY_INSERT_SQL =<<EOQ;
140 INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, total, time            )
141 VALUES                    (     ?,         ?,          ?,         ?,          ?, FROM_UNIXTIME(?))
142 EOQ
143 sub get_template_and_user {
144     my $in       = shift;
145     my $template =
146       C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, $in->{'query'} );
147     my ( $user, $cookie, $sessionID, $flags );
148     if ( $in->{'template_name'} !~m/maintenance/ ) {
149         ( $user, $cookie, $sessionID, $flags ) = checkauth(
150             $in->{'query'},
151             $in->{'authnotrequired'},
152             $in->{'flagsrequired'},
153             $in->{'type'}
154         );
155     }
156
157     my $borrowernumber;
158     my $insecure = C4::Context->preference('insecure');
159     if ($user or $insecure) {
160
161         # load the template variables for stylesheets and JavaScript
162         $template->param( css_libs => $in->{'css_libs'} );
163         $template->param( css_module => $in->{'css_module'} );
164         $template->param( css_page => $in->{'css_page'} );
165         $template->param( css_widgets => $in->{'css_widgets'} );
166
167         $template->param( js_libs => $in->{'js_libs'} );
168         $template->param( js_module => $in->{'js_module'} );
169         $template->param( js_page => $in->{'js_page'} );
170         $template->param( js_widgets => $in->{'js_widgets'} );
171
172         # user info
173         $template->param( loggedinusername => $user );
174         $template->param( sessionID        => $sessionID );
175
176         my ($total, $pubshelves, $barshelves) = C4::Context->get_shelves_userenv();
177         if (defined($pubshelves)) {
178             $template->param( pubshelves     => scalar @{$pubshelves},
179                               pubshelvesloop => $pubshelves,
180             );
181             $template->param( pubtotal   => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
182         }
183         if (defined($barshelves)) {
184             $template->param( barshelves      => scalar @{$barshelves},
185                               barshelvesloop  => $barshelves,
186             );
187             $template->param( bartotal  => $total->{'bartotal'}, ) if ($total->{'bartotal'} > scalar @{$barshelves});
188         }
189
190         $borrowernumber = getborrowernumber($user) if defined($user);
191
192         my ( $borr ) = GetMemberDetails( $borrowernumber );
193         my @bordat;
194         $bordat[0] = $borr;
195         $template->param( "USER_INFO" => \@bordat );
196
197         my $all_perms = get_all_subpermissions();
198
199         my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow
200                             editcatalogue updatecharges management tools editauthorities serials reports acquisition);
201         # We are going to use the $flags returned by checkauth
202         # to create the template's parameters that will indicate
203         # which menus the user can access.
204         if (( $flags && $flags->{superlibrarian}==1) or $insecure==1) {
205             $template->param( CAN_user_circulate        => 1 );
206             $template->param( CAN_user_catalogue        => 1 );
207             $template->param( CAN_user_parameters       => 1 );
208             $template->param( CAN_user_borrowers        => 1 );
209             $template->param( CAN_user_permissions      => 1 );
210             $template->param( CAN_user_reserveforothers => 1 );
211             $template->param( CAN_user_borrow           => 1 );
212             $template->param( CAN_user_editcatalogue    => 1 );
213             $template->param( CAN_user_updatecharges     => 1 );
214             $template->param( CAN_user_acquisition      => 1 );
215             $template->param( CAN_user_management       => 1 );
216             $template->param( CAN_user_tools            => 1 );
217             $template->param( CAN_user_editauthorities  => 1 );
218             $template->param( CAN_user_serials          => 1 );
219             $template->param( CAN_user_reports          => 1 );
220             $template->param( CAN_user_staffaccess      => 1 );
221             foreach my $module (keys %$all_perms) {
222                 foreach my $subperm (keys %{ $all_perms->{$module} }) {
223                     $template->param( "CAN_user_${module}_${subperm}" => 1 );
224                 }
225             }
226         }
227
228         if ( $flags ) {
229             foreach my $module (keys %$all_perms) {
230                 if ( $flags->{$module} == 1) {
231                     foreach my $subperm (keys %{ $all_perms->{$module} }) {
232                         $template->param( "CAN_user_${module}_${subperm}" => 1 );
233                     }
234                 } elsif ( ref($flags->{$module}) ) {
235                     foreach my $subperm (keys %{ $flags->{$module} } ) {
236                         $template->param( "CAN_user_${module}_${subperm}" => 1 );
237                     }
238                 }
239             }
240         }
241
242         if ($flags) {
243             foreach my $module (keys %$flags) {
244                 if ( $flags->{$module} == 1 or ref($flags->{$module}) ) {
245                     $template->param( "CAN_user_$module" => 1 );
246                     if ($module eq "parameters") {
247                         $template->param( CAN_user_management => 1 );
248                     }
249                 }
250             }
251         }
252                 # Logged-in opac search history
253                 # If the requested template is an opac one and opac search history is enabled
254                 if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) {
255                         my $dbh = C4::Context->dbh;
256                         my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?";
257                         my $sth = $dbh->prepare($query);
258                         $sth->execute($borrowernumber);
259                         
260                         # If at least one search has already been performed
261                         if ($sth->fetchrow_array > 0) { 
262                         # We show the link in opac
263                         $template->param(ShowOpacRecentSearchLink => 1);
264                         }
265
266                         # And if there's a cookie with searches performed when the user was not logged in, 
267                         # we add them to the logged-in search history
268                         my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
269                         if ($searchcookie){
270                                 $searchcookie = uri_unescape($searchcookie);
271                                 my @recentSearches = @{thaw($searchcookie) || []};
272                                 if (@recentSearches) {
273                                         my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL);
274                                         $sth->execute( $borrowernumber,
275                                                        $in->{'query'}->cookie("CGISESSID"),
276                                                        $_->{'query_desc'},
277                                                        $_->{'query_cgi'},
278                                                        $_->{'total'},
279                                                        $_->{'time'},
280                             ) foreach @recentSearches;
281
282                                         # And then, delete the cookie's content
283                                         my $newsearchcookie = $in->{'query'}->cookie(
284                                                                                                 -name => 'KohaOpacRecentSearches',
285                                                                                                 -value => freeze([]),
286                                                                                                 -expires => ''
287                                                                                          );
288                                         $cookie = [$cookie, $newsearchcookie];
289                                 }
290                         }
291                 }
292     }
293         else {  # if this is an anonymous session, setup to display public lists...
294
295         # load the template variables for stylesheets and JavaScript
296         $template->param( css_libs => $in->{'css_libs'} );
297         $template->param( css_module => $in->{'css_module'} );
298         $template->param( css_page => $in->{'css_page'} );
299         $template->param( css_widgets => $in->{'css_widgets'} );
300
301         $template->param( js_libs => $in->{'js_libs'} );
302         $template->param( js_module => $in->{'js_module'} );
303         $template->param( js_page => $in->{'js_page'} );
304         $template->param( js_widgets => $in->{'js_widgets'} );
305
306         $template->param( sessionID        => $sessionID );
307         
308         my ($total, $pubshelves) = C4::Context->get_shelves_userenv();  # an anonymous user has no 'barshelves'...
309         if (defined $pubshelves) {
310             $template->param(   pubshelves      => scalar @{$pubshelves},
311                                 pubshelvesloop  => $pubshelves,
312                             );
313             $template->param(   pubtotal        => $total->{'pubtotal'}, ) if ($total->{'pubtotal'} > scalar @{$pubshelves});
314         }
315
316     }
317         # Anonymous opac search history
318         # If opac search history is enabled and at least one search has already been performed
319         if (C4::Context->preference('EnableOpacSearchHistory')) {
320                 my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches');
321                 if ($searchcookie){
322                         $searchcookie = uri_unescape($searchcookie);
323                         my @recentSearches = @{thaw($searchcookie) || []};
324             # We show the link in opac
325                         if (@recentSearches) {
326                                 $template->param(ShowOpacRecentSearchLink => 1);
327                         }
328             }
329         }
330
331     if(C4::Context->preference('dateformat')){
332         if(C4::Context->preference('dateformat') eq "metric"){
333             $template->param(dateformat_metric => 1);
334         } elsif(C4::Context->preference('dateformat') eq "us"){
335             $template->param(dateformat_us => 1);
336         } else {
337             $template->param(dateformat_iso => 1);
338         }
339     } else {
340         $template->param(dateformat_iso => 1);
341     }
342
343     # these template parameters are set the same regardless of $in->{'type'}
344     $template->param(
345             "BiblioDefaultView".C4::Context->preference("BiblioDefaultView")         => 1,
346             EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'),
347             GoogleJackets                => C4::Context->preference("GoogleJackets"),
348             OpenLibraryCovers            => C4::Context->preference("OpenLibraryCovers"),
349             KohaAdminEmailAddress        => "" . C4::Context->preference("KohaAdminEmailAddress"),
350             LoginBranchcode              => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"),
351             LoginFirstname               => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"),
352             LoginSurname                 => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu",
353             TagsEnabled                  => C4::Context->preference("TagsEnabled"),
354             hide_marc                    => C4::Context->preference("hide_marc"),
355             item_level_itypes            => C4::Context->preference('item-level_itypes'),
356             patronimages                 => C4::Context->preference("patronimages"),
357             singleBranchMode             => C4::Context->preference("singleBranchMode"),
358             XSLTDetailsDisplay           => C4::Context->preference("XSLTDetailsDisplay"),
359             XSLTResultsDisplay           => C4::Context->preference("XSLTResultsDisplay"),
360             using_https                  => $in->{'query'}->https() ? 1 : 0,
361             noItemTypeImages            => C4::Context->preference("noItemTypeImages"),
362     );
363
364     if ( $in->{'type'} eq "intranet" ) {
365         $template->param(
366             AmazonContent               => C4::Context->preference("AmazonContent"),
367             AmazonCoverImages           => C4::Context->preference("AmazonCoverImages"),
368             AmazonEnabled               => C4::Context->preference("AmazonEnabled"),
369             AmazonSimilarItems          => C4::Context->preference("AmazonSimilarItems"),
370             AutoLocation                => C4::Context->preference("AutoLocation"),
371             "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1,
372             CircAutocompl               => C4::Context->preference("CircAutocompl"),
373             FRBRizeEditions             => C4::Context->preference("FRBRizeEditions"),
374             IndependantBranches         => C4::Context->preference("IndependantBranches"),
375             IntranetNav                 => C4::Context->preference("IntranetNav"),
376             IntranetmainUserblock       => C4::Context->preference("IntranetmainUserblock"),
377             LibraryName                 => C4::Context->preference("LibraryName"),
378             LoginBranchname             => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:"insecure"),
379             advancedMARCEditor          => C4::Context->preference("advancedMARCEditor"),
380             canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'),
381             intranetcolorstylesheet     => C4::Context->preference("intranetcolorstylesheet"),
382             IntranetFavicon             => C4::Context->preference("IntranetFavicon"),
383             intranetreadinghistory      => C4::Context->preference("intranetreadinghistory"),
384             intranetstylesheet          => C4::Context->preference("intranetstylesheet"),
385             IntranetUserCSS             => C4::Context->preference("IntranetUserCSS"),
386             intranetuserjs              => C4::Context->preference("intranetuserjs"),
387             intranetbookbag             => C4::Context->preference("intranetbookbag"),
388             suggestion                  => C4::Context->preference("suggestion"),
389             virtualshelves              => C4::Context->preference("virtualshelves"),
390             StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"),
391             NoZebra                     => C4::Context->preference('NoZebra'),
392                 EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'),
393         );
394     }
395     else {
396         warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' );
397         #TODO : replace LibraryName syspref with 'system name', and remove this html processing
398         my $LibraryNameTitle = C4::Context->preference("LibraryName");
399         $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi;
400         $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg;
401         # variables passed from CGI: opac_css_override and opac_search_limits.
402         my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'};
403         my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'};
404         my $opac_name = '';
405         if (($opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || $in->{'query'}->param('limit') =~ /branch:(\w+)/){
406             $opac_name = $1;   # opac_search_limit is a branch, so we use it.
407         } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) {
408             $opac_name = C4::Context->userenv->{'branch'};
409         }
410         my $checkstyle = C4::Context->preference("opaccolorstylesheet");
411         if ($checkstyle =~ /http/)
412         {
413                 $template->param( opacexternalsheet => $checkstyle);
414         } else
415         {
416                 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");  
417             $template->param( opaccolorstylesheet => $opaccolorstylesheet);
418         }
419         $template->param(
420             AmazonContent             => "" . C4::Context->preference("AmazonContent"),
421             AnonSuggestions           => "" . C4::Context->preference("AnonSuggestions"),
422             AuthorisedValueImages     => C4::Context->preference("AuthorisedValueImages"),
423             BranchesLoop              => GetBranchesLoop($opac_name),
424             LibraryName               => "" . C4::Context->preference("LibraryName"),
425             LibraryNameTitle          => "" . $LibraryNameTitle,
426             LoginBranchname           => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"",
427             OPACAmazonEnabled         => C4::Context->preference("OPACAmazonEnabled"),
428             OPACAmazonSimilarItems    => C4::Context->preference("OPACAmazonSimilarItems"),
429             OPACAmazonCoverImages     => C4::Context->preference("OPACAmazonCoverImages"),
430             OPACAmazonReviews         => C4::Context->preference("OPACAmazonReviews"),
431             OPACFRBRizeEditions       => C4::Context->preference("OPACFRBRizeEditions"),
432             OpacHighlightedWords       => C4::Context->preference("OpacHighlightedWords"),
433             OPACItemHolds             => C4::Context->preference("OPACItemHolds"),
434             OPACShelfBrowser          => "". C4::Context->preference("OPACShelfBrowser"),
435             OpacShowRecentComments    => C4::Context->preference("OpacShowRecentComments"),
436             OPACURLOpenInNewWindow    => "" . C4::Context->preference("OPACURLOpenInNewWindow"),
437             OPACUserCSS               => "". C4::Context->preference("OPACUserCSS"),
438             OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"),
439             OpacAuthorities           => C4::Context->preference("OpacAuthorities"),
440             OPACBaseURL               => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} .
441                    ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"),
442             opac_css_override           => $ENV{'OPAC_CSS_OVERRIDE'},
443             opac_search_limit         => $opac_search_limit,
444             opac_limit_override       => $opac_limit_override,
445             OpacBrowser               => C4::Context->preference("OpacBrowser"),
446             OpacCloud                 => C4::Context->preference("OpacCloud"),
447             OpacKohaUrl               => C4::Context->preference("OpacKohaUrl"),
448             OpacMainUserBlock         => "" . C4::Context->preference("OpacMainUserBlock"),
449             OpacNav                   => "" . C4::Context->preference("OpacNav"),
450             OpacNavBottom             => "" . C4::Context->preference("OpacNavBottom"),
451             OpacPasswordChange        => C4::Context->preference("OpacPasswordChange"),
452             OPACPatronDetails        => C4::Context->preference("OPACPatronDetails"),
453             OPACPrivacy               => C4::Context->preference("OPACPrivacy"),
454             OPACFinesTab              => C4::Context->preference("OPACFinesTab"),
455             OpacTopissue              => C4::Context->preference("OpacTopissue"),
456             RequestOnOpac             => C4::Context->preference("RequestOnOpac"),
457             'Version'                 => C4::Context->preference('Version'),
458             hidelostitems             => C4::Context->preference("hidelostitems"),
459             mylibraryfirst            => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '',
460             opaclayoutstylesheet      => "" . C4::Context->preference("opaclayoutstylesheet"),
461             opacstylesheet            => "" . C4::Context->preference("opacstylesheet"),
462             opacbookbag               => "" . C4::Context->preference("opacbookbag"),
463             opaccredits               => "" . C4::Context->preference("opaccredits"),
464             OpacFavicon               => C4::Context->preference("OpacFavicon"),
465             opacheader                => "" . C4::Context->preference("opacheader"),
466             opaclanguagesdisplay      => "" . C4::Context->preference("opaclanguagesdisplay"),
467             opacreadinghistory        => C4::Context->preference("opacreadinghistory"),
468             opacsmallimage            => "" . C4::Context->preference("opacsmallimage"),
469             opacuserjs                => C4::Context->preference("opacuserjs"),
470             opacuserlogin             => "" . C4::Context->preference("opacuserlogin"),
471             reviewson                 => C4::Context->preference("reviewson"),
472             ShowReviewer              => C4::Context->preference("ShowReviewer"),
473             ShowReviewerPhoto         => C4::Context->preference("ShowReviewerPhoto"),
474             suggestion                => "" . C4::Context->preference("suggestion"),
475             virtualshelves            => "" . C4::Context->preference("virtualshelves"),
476             OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"),
477             OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"),
478             OPACXSLTDetailsDisplay           => C4::Context->preference("OPACXSLTDetailsDisplay"),
479             OPACXSLTResultsDisplay           => C4::Context->preference("OPACXSLTResultsDisplay"),
480             SyndeticsClientCode          => C4::Context->preference("SyndeticsClientCode"),
481             SyndeticsEnabled             => C4::Context->preference("SyndeticsEnabled"),
482             SyndeticsCoverImages         => C4::Context->preference("SyndeticsCoverImages"),
483             SyndeticsTOC                 => C4::Context->preference("SyndeticsTOC"),
484             SyndeticsSummary             => C4::Context->preference("SyndeticsSummary"),
485             SyndeticsEditions            => C4::Context->preference("SyndeticsEditions"),
486             SyndeticsExcerpt             => C4::Context->preference("SyndeticsExcerpt"),
487             SyndeticsReviews             => C4::Context->preference("SyndeticsReviews"),
488             SyndeticsAuthorNotes         => C4::Context->preference("SyndeticsAuthorNotes"),
489             SyndeticsAwards              => C4::Context->preference("SyndeticsAwards"),
490             SyndeticsSeries              => C4::Context->preference("SyndeticsSeries"),
491             SyndeticsCoverImageSize      => C4::Context->preference("SyndeticsCoverImageSize"),
492         );
493
494         $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic"));
495     }
496         $template->param(listloop=>[{shelfname=>"Freelist", shelfnumber=>110}]);
497     return ( $template, $borrowernumber, $cookie, $flags);
498 }
499
500 =head2 checkauth
501
502   ($userid, $cookie, $sessionID) = &checkauth($query, $noauth, $flagsrequired, $type);
503
504 Verifies that the user is authorized to run this script.  If
505 the user is authorized, a (userid, cookie, session-id, flags)
506 quadruple is returned.  If the user is not authorized but does
507 not have the required privilege (see $flagsrequired below), it
508 displays an error page and exits.  Otherwise, it displays the
509 login page and exits.
510
511 Note that C<&checkauth> will return if and only if the user
512 is authorized, so it should be called early on, before any
513 unfinished operations (e.g., if you've opened a file, then
514 C<&checkauth> won't close it for you).
515
516 C<$query> is the CGI object for the script calling C<&checkauth>.
517
518 The C<$noauth> argument is optional. If it is set, then no
519 authorization is required for the script.
520
521 C<&checkauth> fetches user and session information from C<$query> and
522 ensures that the user is authorized to run scripts that require
523 authorization.
524
525 The C<$flagsrequired> argument specifies the required privileges
526 the user must have if the username and password are correct.
527 It should be specified as a reference-to-hash; keys in the hash
528 should be the "flags" for the user, as specified in the Members
529 intranet module. Any key specified must correspond to a "flag"
530 in the userflags table. E.g., { circulate => 1 } would specify
531 that the user must have the "circulate" privilege in order to
532 proceed. To make sure that access control is correct, the
533 C<$flagsrequired> parameter must be specified correctly.
534
535 Koha also has a concept of sub-permissions, also known as
536 granular permissions.  This makes the value of each key
537 in the C<flagsrequired> hash take on an additional
538 meaning, i.e.,
539
540  1
541
542 The user must have access to all subfunctions of the module
543 specified by the hash key.
544
545  *
546
547 The user must have access to at least one subfunction of the module
548 specified by the hash key.
549
550  specific permission, e.g., 'export_catalog'
551
552 The user must have access to the specific subfunction list, which
553 must correspond to a row in the permissions table.
554
555 The C<$type> argument specifies whether the template should be
556 retrieved from the opac or intranet directory tree.  "opac" is
557 assumed if it is not specified; however, if C<$type> is specified,
558 "intranet" is assumed if it is not "opac".
559
560 If C<$query> does not have a valid session ID associated with it
561 (i.e., the user has not logged in) or if the session has expired,
562 C<&checkauth> presents the user with a login page (from the point of
563 view of the original script, C<&checkauth> does not return). Once the
564 user has authenticated, C<&checkauth> restarts the original script
565 (this time, C<&checkauth> returns).
566
567 The login page is provided using a HTML::Template, which is set in the
568 systempreferences table or at the top of this file. The variable C<$type>
569 selects which template to use, either the opac or the intranet
570 authentification template.
571
572 C<&checkauth> returns a user ID, a cookie, and a session ID. The
573 cookie should be sent back to the browser; it verifies that the user
574 has authenticated.
575
576 =cut
577
578 sub _version_check ($$) {
579     my $type = shift;
580     my $query = shift;
581     my $version;
582     # If Version syspref is unavailable, it means Koha is beeing installed,
583     # and so we must redirect to OPAC maintenance page or to the WebInstaller
584         # also, if OpacMaintenance is ON, OPAC should redirect to maintenance
585         if (C4::Context->preference('OpacMaintenance') && $type eq 'opac') {
586         warn "OPAC Install required, redirecting to maintenance";
587         print $query->redirect("/cgi-bin/koha/maintenance.pl");
588     }
589     unless ( $version = C4::Context->preference('Version') ) {    # assignment, not comparison
590         if ( $type ne 'opac' ) {
591             warn "Install required, redirecting to Installer";
592             print $query->redirect("/cgi-bin/koha/installer/install.pl");
593         } else {
594             warn "OPAC Install required, redirecting to maintenance";
595             print $query->redirect("/cgi-bin/koha/maintenance.pl");
596         }
597         safe_exit;
598     }
599
600     # check that database and koha version are the same
601     # there is no DB version, it's a fresh install,
602     # go to web installer
603     # there is a DB version, compare it to the code version
604     my $kohaversion=C4::Context::KOHAVERSION;
605     # remove the 3 last . to have a Perl number
606     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
607     $debug and print STDERR "kohaversion : $kohaversion\n";
608     if ($version < $kohaversion){
609         my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion";
610         if ($type ne 'opac'){
611             warn sprintf($warning, 'Installer');
612             print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3");
613         } else {
614             warn sprintf("OPAC: " . $warning, 'maintenance');
615             print $query->redirect("/cgi-bin/koha/maintenance.pl");
616         }
617         safe_exit;
618     }
619 }
620
621 sub _session_log {
622     (@_) or return 0;
623     open L, ">>/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog";
624     printf L join("\n",@_);
625     close L;
626 }
627
628 sub checkauth {
629     my $query = shift;
630         $debug and warn "Checking Auth";
631     # $authnotrequired will be set for scripts which will run without authentication
632     my $authnotrequired = shift;
633     my $flagsrequired   = shift;
634     my $type            = shift;
635     $type = 'opac' unless $type;
636
637     my $dbh     = C4::Context->dbh;
638     my $timeout = C4::Context->preference('timeout');
639     # days
640     if ($timeout =~ /(\d+)[dD]/) {
641         $timeout = $1 * 86400;
642     };
643     $timeout = 600 unless $timeout;
644
645     _version_check($type,$query);
646     # state variables
647     my $loggedin = 0;
648     my %info;
649     my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves );
650     my $logout = $query->param('logout.x');
651
652     # This parameter is the name of the CAS server we want to authenticate against,
653     # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml
654     my $casparam = $query->param('cas');
655
656     if ( $userid = $ENV{'REMOTE_USER'} ) {
657         # Using Basic Authentication, no cookies required
658         $cookie = $query->cookie(
659             -name    => 'CGISESSID',
660             -value   => '',
661             -expires => ''
662         );
663         $loggedin = 1;
664     }
665     elsif ( $sessionID = $query->cookie("CGISESSID")) {     # assignment, not comparison
666         my $session = get_session($sessionID);
667         C4::Context->_new_userenv($sessionID);
668         my ($ip, $lasttime, $sessiontype);
669         if ($session){
670             C4::Context::set_userenv(
671                 $session->param('number'),       $session->param('id'),
672                 $session->param('cardnumber'),   $session->param('firstname'),
673                 $session->param('surname'),      $session->param('branch'),
674                 $session->param('branchname'),   $session->param('flags'),
675                 $session->param('emailaddress'), $session->param('branchprinter')
676             );
677             C4::Context::set_shelves_userenv('bar',$session->param('barshelves'));
678             C4::Context::set_shelves_userenv('pub',$session->param('pubshelves'));
679             C4::Context::set_shelves_userenv('tot',$session->param('totshelves'));
680             $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
681             $ip       = $session->param('ip');
682             $lasttime = $session->param('lasttime');
683             $userid   = $session->param('id');
684                         $sessiontype = $session->param('sessiontype');
685         }
686         if ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) {
687             #if a user enters an id ne to the id in the current session, we need to log them in...
688             #first we need to clear the anonymous session...
689             $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id');
690             $session->flush;      
691             $session->delete();
692             C4::Context->_unset_userenv($sessionID);
693                         $sessionID = undef;
694                         $userid = undef;
695                 }
696         elsif ($logout) {
697             # voluntary logout the user
698             $session->flush;
699             $session->delete();
700             C4::Context->_unset_userenv($sessionID);
701             _session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime));
702             $sessionID = undef;
703             $userid    = undef;
704
705             if ($cas and $caslogout) {
706                 logout_cas($query);
707             }
708         }
709         elsif ( $lasttime < time() - $timeout ) {
710             # timed logout
711             $info{'timed_out'} = 1;
712             $session->delete();
713             C4::Context->_unset_userenv($sessionID);
714             _session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime));
715             $userid    = undef;
716             $sessionID = undef;
717         }
718         elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
719             # Different ip than originally logged in from
720             $info{'oldip'}        = $ip;
721             $info{'newip'}        = $ENV{'REMOTE_ADDR'};
722             $info{'different_ip'} = 1;
723             $session->delete();
724             C4::Context->_unset_userenv($sessionID);
725             _session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'});
726             $sessionID = undef;
727             $userid    = undef;
728         }
729         else {
730             $cookie = $query->cookie( CGISESSID => $session->id );
731             $session->param('lasttime',time());
732             unless ( $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in...
733                 $flags = haspermission($userid, $flagsrequired);
734                 if ($flags) {
735                     $loggedin = 1;
736                 } else {
737                     $info{'nopermission'} = 1;
738                 }
739             }
740         }
741     }
742     unless ($userid || $sessionID) {
743         #we initiate a session prior to checking for a username to allow for anonymous sessions...
744                 my $session = get_session("") or die "Auth ERROR: Cannot get_session()";
745         my $sessionID = $session->id;
746         C4::Context->_new_userenv($sessionID);
747         $cookie = $query->cookie(CGISESSID => $sessionID);
748             $userid    = $query->param('userid');
749             if ($cas || $userid) {
750                 my $password = $query->param('password');
751                 my ($return, $cardnumber);
752                 if ($cas && $query->param('ticket')) {
753                     my $retuserid;
754                     ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
755                     $userid = $retuserid;
756                     $info{'invalidCasLogin'} = 1 unless ($return);
757                 } else {
758                     my $retuserid;
759                     ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query );
760                     $userid = $retuserid if ($retuserid ne '');
761                 }
762                 if ($return) {
763                _session_log(sprintf "%20s from %16s logged in  at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime));
764                 if ( $flags = haspermission(  $userid, $flagsrequired ) ) {
765                                         $loggedin = 1;
766                 }
767                         else {
768                         $info{'nopermission'} = 1;
769                         C4::Context->_unset_userenv($sessionID);
770                 }
771
772                                 my ($borrowernumber, $firstname, $surname, $userflags,
773                                         $branchcode, $branchname, $branchprinter, $emailaddress);
774
775                 if ( $return == 1 ) {
776                         my $select = "
777                         SELECT borrowernumber, firstname, surname, flags, borrowers.branchcode, 
778                             branches.branchname    as branchname, 
779                                 branches.branchprinter as branchprinter, 
780                                 email 
781                         FROM borrowers 
782                         LEFT JOIN branches on borrowers.branchcode=branches.branchcode
783                         ";
784                         my $sth = $dbh->prepare("$select where userid=?");
785                         $sth->execute($userid);
786                         unless ($sth->rows) {
787                             $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n";
788                             $sth = $dbh->prepare("$select where cardnumber=?");
789                             $sth->execute($cardnumber);
790
791                             unless ($sth->rows) {
792                                 $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n";
793                                 $sth->execute($userid);
794                                 unless ($sth->rows) {
795                                     $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n";
796                                 }
797                             }
798                         }
799                         if ($sth->rows) {
800                             ($borrowernumber, $firstname, $surname, $userflags,
801                                 $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow;
802                                                 $debug and print STDERR "AUTH_3 results: " .
803                                                         "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n";
804                                         } else {
805                                                 print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n";
806                                         }
807
808 # launch a sequence to check if we have a ip for the branch, i
809 # if we have one we replace the branchcode of the userenv by the branch bound in the ip.
810
811                                         my $ip       = $ENV{'REMOTE_ADDR'};
812                                         # if they specify at login, use that
813                                         if ($query->param('branch')) {
814                                                 $branchcode  = $query->param('branch');
815                                                 $branchname = GetBranchName($branchcode);
816                                         }
817                                         my $branches = GetBranches();
818                                         if (C4::Context->boolean_preference('IndependantBranches') && C4::Context->boolean_preference('Autolocation')){
819                                                 # we have to check they are coming from the right ip range
820                                                 my $domain = $branches->{$branchcode}->{'branchip'};
821                                                 if ($ip !~ /^$domain/){
822                                                         $loggedin=0;
823                                                         $info{'wrongip'} = 1;
824                                                 }
825                                         }
826
827                                         my @branchesloop;
828                                         foreach my $br ( keys %$branches ) {
829                                                 #     now we work with the treatment of ip
830                                                 my $domain = $branches->{$br}->{'branchip'};
831                                                 if ( $domain && $ip =~ /^$domain/ ) {
832                                                         $branchcode = $branches->{$br}->{'branchcode'};
833
834                                                         # new op dev : add the branchprinter and branchname in the cookie
835                                                         $branchprinter = $branches->{$br}->{'branchprinter'};
836                                                         $branchname    = $branches->{$br}->{'branchname'};
837                                                 }
838                                         }
839                                         $session->param('number',$borrowernumber);
840                                         $session->param('id',$userid);
841                                         $session->param('cardnumber',$cardnumber);
842                                         $session->param('firstname',$firstname);
843                                         $session->param('surname',$surname);
844                                         $session->param('branch',$branchcode);
845                                         $session->param('branchname',$branchname);
846                                         $session->param('flags',$userflags);
847                                         $session->param('emailaddress',$emailaddress);
848                                         $session->param('ip',$session->remote_addr());
849                                         $session->param('lasttime',time());
850                                         $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ;
851                                 }
852                                 elsif ( $return == 2 ) {
853                                         #We suppose the user is the superlibrarian
854                                         $borrowernumber = 0;
855                                         $session->param('number',0);
856                                         $session->param('id',C4::Context->config('user'));
857                                         $session->param('cardnumber',C4::Context->config('user'));
858                                         $session->param('firstname',C4::Context->config('user'));
859                                         $session->param('surname',C4::Context->config('user'));
860                                         $session->param('branch','NO_LIBRARY_SET');
861                                         $session->param('branchname','NO_LIBRARY_SET');
862                                         $session->param('flags',1);
863                                         $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
864                                         $session->param('ip',$session->remote_addr());
865                                         $session->param('lasttime',time());
866                                 }
867                                 C4::Context::set_userenv(
868                                         $session->param('number'),       $session->param('id'),
869                                         $session->param('cardnumber'),   $session->param('firstname'),
870                                         $session->param('surname'),      $session->param('branch'),
871                                         $session->param('branchname'),   $session->param('flags'),
872                                         $session->param('emailaddress'), $session->param('branchprinter')
873                                 );
874
875                                 # Grab borrower's shelves and public shelves and add them to the session
876                                 # $row_count determines how many records are returned from the db query
877                                 # and the number of lists to be displayed of each type in the 'Lists' button drop down
878                                 my $row_count = 10; # FIXME:This probably should be a syspref
879                                 my ($total, $totshelves, $barshelves, $pubshelves);
880                                 ($barshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(1, $row_count, $borrowernumber);
881                                 $total->{'bartotal'} = $totshelves;
882                                 ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
883                                 $total->{'pubtotal'} = $totshelves;
884                                 $session->param('barshelves', $barshelves);
885                                 $session->param('pubshelves', $pubshelves);
886                                 $session->param('totshelves', $total);
887
888                                 C4::Context::set_shelves_userenv('bar',$barshelves);
889                                 C4::Context::set_shelves_userenv('pub',$pubshelves);
890                                 C4::Context::set_shelves_userenv('tot',$total);
891                         }
892                 else {
893                 if ($userid) {
894                         $info{'invalid_username_or_password'} = 1;
895                         C4::Context->_unset_userenv($sessionID);
896                 }
897                         }
898         }       # END if ( $userid    = $query->param('userid') )
899                 elsif ($type eq "opac") {
900             # if we are here this is an anonymous session; add public lists to it and a few other items...
901             # anonymous sessions are created only for the OPAC
902                         $debug and warn "Initiating an anonymous session...";
903
904                         # Grab the public shelves and add to the session...
905                         my $row_count = 20; # FIXME:This probably should be a syspref
906                         my ($total, $totshelves, $pubshelves);
907                         ($pubshelves, $totshelves) = C4::VirtualShelves::GetRecentShelves(2, $row_count, undef);
908                         $total->{'pubtotal'} = $totshelves;
909                         $session->param('pubshelves', $pubshelves);
910                         $session->param('totshelves', $total);
911                         C4::Context::set_shelves_userenv('pub',$pubshelves);
912                         C4::Context::set_shelves_userenv('tot',$total);
913
914                         # setting a couple of other session vars...
915                         $session->param('ip',$session->remote_addr());
916                         $session->param('lasttime',time());
917                         $session->param('sessiontype','anon');
918                 }
919     }   # END unless ($userid)
920     my $insecure = C4::Context->boolean_preference('insecure');
921
922     # finished authentification, now respond
923     if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) )
924     {
925         # successful login
926         unless ($cookie) {
927             $cookie = $query->cookie( CGISESSID => '' );
928         }
929         return ( $userid, $cookie, $sessionID, $flags );
930     }
931
932 #
933 #
934 # AUTH rejected, show the login/password template, after checking the DB.
935 #
936 #
937
938     # get the inputs from the incoming query
939     my @inputs = ();
940     foreach my $name ( param $query) {
941         (next) if ( $name eq 'userid' || $name eq 'password' || $name eq 'ticket' );
942         my $value = $query->param($name);
943         push @inputs, { name => $name, value => $value };
944     }
945     # get the branchloop, which we need for authentication
946     my $branches = GetBranches();
947     my @branch_loop;
948     for my $branch_hash (sort keys %$branches) {
949                 push @branch_loop, {branchcode => "$branch_hash", branchname => $branches->{$branch_hash}->{'branchname'}, };
950     }
951
952     my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl';
953     my $template = C4::Templates::gettemplate( $template_name, $type, $query );
954     $template->param(branchloop => \@branch_loop,);
955     my $checkstyle = C4::Context->preference("opaccolorstylesheet");
956     if ($checkstyle =~ /\//)
957         {
958                 $template->param( opacexternalsheet => $checkstyle);
959         } else
960         {
961                 my $opaccolorstylesheet = C4::Context->preference("opaccolorstylesheet");  
962             $template->param( opaccolorstylesheet => $opaccolorstylesheet);
963         }
964     $template->param(
965     login        => 1,
966         INPUTS               => \@inputs,
967         casAuthentication    => C4::Context->preference("casAuthentication"),
968         suggestion           => C4::Context->preference("suggestion"),
969         virtualshelves       => C4::Context->preference("virtualshelves"),
970         LibraryName          => C4::Context->preference("LibraryName"),
971         opacuserlogin        => C4::Context->preference("opacuserlogin"),
972         OpacNav              => C4::Context->preference("OpacNav"),
973         OpacNavBottom        => C4::Context->preference("OpacNavBottom"),
974         opaccredits          => C4::Context->preference("opaccredits"),
975         OpacFavicon          => C4::Context->preference("OpacFavicon"),
976         opacreadinghistory   => C4::Context->preference("opacreadinghistory"),
977         opacsmallimage       => C4::Context->preference("opacsmallimage"),
978         opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"),
979         opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"),
980         opacuserjs           => C4::Context->preference("opacuserjs"),
981         opacbookbag          => "" . C4::Context->preference("opacbookbag"),
982         OpacCloud            => C4::Context->preference("OpacCloud"),
983         OpacTopissue         => C4::Context->preference("OpacTopissue"),
984         OpacAuthorities      => C4::Context->preference("OpacAuthorities"),
985         OpacBrowser          => C4::Context->preference("OpacBrowser"),
986         opacheader           => C4::Context->preference("opacheader"),
987         TagsEnabled                  => C4::Context->preference("TagsEnabled"),
988         OPACUserCSS           => C4::Context->preference("OPACUserCSS"),
989         opacstylesheet       => C4::Context->preference("opacstylesheet"),
990         intranetcolorstylesheet =>
991                                                                 C4::Context->preference("intranetcolorstylesheet"),
992         intranetstylesheet => C4::Context->preference("intranetstylesheet"),
993         intranetbookbag    => C4::Context->preference("intranetbookbag"),
994         IntranetNav        => C4::Context->preference("IntranetNav"),
995         intranetuserjs     => C4::Context->preference("intranetuserjs"),
996         IndependantBranches=> C4::Context->preference("IndependantBranches"),
997         AutoLocation       => C4::Context->preference("AutoLocation"),
998                 wrongip            => $info{'wrongip'},
999     );
1000
1001     $template->param( OpacPublic => C4::Context->preference("OpacPublic"));
1002     $template->param( loginprompt => 1 ) unless $info{'nopermission'};
1003
1004     if ($cas) {
1005
1006         # Is authentication against multiple CAS servers enabled?
1007         if (C4::Auth_with_cas::multipleAuth && !$casparam) {
1008             my $casservers = C4::Auth_with_cas::getMultipleAuth();                  
1009             my @tmplservers;
1010             foreach my $key (keys %$casservers) {
1011                 push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" };
1012             }
1013             #warn Data::Dumper::Dumper(\@tmplservers);
1014             $template->param(
1015                 casServersLoop => \@tmplservers
1016             );
1017         } else {
1018         $template->param(
1019             casServerUrl    => login_cas_url($query),
1020             );
1021         }
1022
1023         $template->param(
1024             invalidCasLogin => $info{'invalidCasLogin'}
1025         );
1026     }
1027
1028     my $self_url = $query->url( -absolute => 1 );
1029     $template->param(
1030         url         => $self_url,
1031         LibraryName => C4::Context->preference("LibraryName"),
1032     );
1033     $template->param( %info );
1034 #    $cookie = $query->cookie(CGISESSID => $session->id
1035 #   );
1036     print $query->header(
1037         -type   => 'text/html',
1038         -charset => 'utf-8',
1039         -cookie => $cookie
1040       ),
1041       $template->output;
1042     safe_exit;
1043 }
1044
1045 =head2 check_api_auth
1046
1047   ($status, $cookie, $sessionId) = check_api_auth($query, $userflags);
1048
1049 Given a CGI query containing the parameters 'userid' and 'password' and/or a session
1050 cookie, determine if the user has the privileges specified by C<$userflags>.
1051
1052 C<check_api_auth> is is meant for authenticating users of web services, and
1053 consequently will always return and will not attempt to redirect the user
1054 agent.
1055
1056 If a valid session cookie is already present, check_api_auth will return a status
1057 of "ok", the cookie, and the Koha session ID.
1058
1059 If no session cookie is present, check_api_auth will check the 'userid' and 'password
1060 parameters and create a session cookie and Koha session if the supplied credentials
1061 are OK.
1062
1063 Possible return values in C<$status> are:
1064
1065 =over
1066
1067 =item "ok" -- user authenticated; C<$cookie> and C<$sessionid> have valid values.
1068
1069 =item "failed" -- credentials are not correct; C<$cookie> and C<$sessionid> are undef
1070
1071 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1072
1073 =item "expired -- session cookie has expired; API user should resubmit userid and password
1074
1075 =back
1076
1077 =cut
1078
1079 sub check_api_auth {
1080     my $query = shift;
1081     my $flagsrequired = shift;
1082
1083     my $dbh     = C4::Context->dbh;
1084     my $timeout = C4::Context->preference('timeout');
1085     $timeout = 600 unless $timeout;
1086
1087     unless (C4::Context->preference('Version')) {
1088         # database has not been installed yet
1089         return ("maintenance", undef, undef);
1090     }
1091     my $kohaversion=C4::Context::KOHAVERSION;
1092     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1093     if (C4::Context->preference('Version') < $kohaversion) {
1094         # database in need of version update; assume that
1095         # no API should be called while databsae is in
1096         # this condition.
1097         return ("maintenance", undef, undef);
1098     }
1099
1100     # FIXME -- most of what follows is a copy-and-paste
1101     # of code from checkauth.  There is an obvious need
1102     # for refactoring to separate the various parts of
1103     # the authentication code, but as of 2007-11-19 this
1104     # is deferred so as to not introduce bugs into the
1105     # regular authentication code for Koha 3.0.
1106
1107     # see if we have a valid session cookie already
1108     # however, if a userid parameter is present (i.e., from
1109     # a form submission, assume that any current cookie
1110     # is to be ignored
1111     my $sessionID = undef;
1112     unless ($query->param('userid')) {
1113         $sessionID = $query->cookie("CGISESSID");
1114     }
1115     if ($sessionID && not $cas) {
1116         my $session = get_session($sessionID);
1117         C4::Context->_new_userenv($sessionID);
1118         if ($session) {
1119             C4::Context::set_userenv(
1120                 $session->param('number'),       $session->param('id'),
1121                 $session->param('cardnumber'),   $session->param('firstname'),
1122                 $session->param('surname'),      $session->param('branch'),
1123                 $session->param('branchname'),   $session->param('flags'),
1124                 $session->param('emailaddress'), $session->param('branchprinter')
1125             );
1126
1127             my $ip = $session->param('ip');
1128             my $lasttime = $session->param('lasttime');
1129             my $userid = $session->param('id');
1130             if ( $lasttime < time() - $timeout ) {
1131                 # time out
1132                 $session->delete();
1133                 C4::Context->_unset_userenv($sessionID);
1134                 $userid    = undef;
1135                 $sessionID = undef;
1136                 return ("expired", undef, undef);
1137             } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1138                 # IP address changed
1139                 $session->delete();
1140                 C4::Context->_unset_userenv($sessionID);
1141                 $userid    = undef;
1142                 $sessionID = undef;
1143                 return ("expired", undef, undef);
1144             } else {
1145                 my $cookie = $query->cookie( CGISESSID => $session->id );
1146                 $session->param('lasttime',time());
1147                 my $flags = haspermission($userid, $flagsrequired);
1148                 if ($flags) {
1149                     return ("ok", $cookie, $sessionID);
1150                 } else {
1151                     $session->delete();
1152                     C4::Context->_unset_userenv($sessionID);
1153                     $userid    = undef;
1154                     $sessionID = undef;
1155                     return ("failed", undef, undef);
1156                 }
1157             }
1158         } else {
1159             return ("expired", undef, undef);
1160         }
1161     } else {
1162         # new login
1163         my $userid = $query->param('userid');
1164         my $password = $query->param('password');
1165         my ($return, $cardnumber);
1166
1167         # Proxy CAS auth
1168         if ($cas && $query->param('PT')) {
1169             my $retuserid;
1170             $debug and print STDERR "## check_api_auth - checking CAS\n";
1171             # In case of a CAS authentication, we use the ticket instead of the password
1172             my $PT = $query->param('PT');
1173             ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query);    # EXTERNAL AUTH
1174         } else {
1175             # User / password auth
1176             unless ($userid and $password) {
1177                 # caller did something wrong, fail the authenticateion
1178                 return ("failed", undef, undef);
1179             }
1180             ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query );
1181         }
1182
1183         if ($return and haspermission(  $userid, $flagsrequired)) {
1184             my $session = get_session("");
1185             return ("failed", undef, undef) unless $session;
1186
1187             my $sessionID = $session->id;
1188             C4::Context->_new_userenv($sessionID);
1189             my $cookie = $query->cookie(CGISESSID => $sessionID);
1190             if ( $return == 1 ) {
1191                 my (
1192                     $borrowernumber, $firstname,  $surname,
1193                     $userflags,      $branchcode, $branchname,
1194                     $branchprinter,  $emailaddress
1195                 );
1196                 my $sth =
1197                   $dbh->prepare(
1198 "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname,branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where userid=?"
1199                   );
1200                 $sth->execute($userid);
1201                 (
1202                     $borrowernumber, $firstname,  $surname,
1203                     $userflags,      $branchcode, $branchname,
1204                     $branchprinter,  $emailaddress
1205                 ) = $sth->fetchrow if ( $sth->rows );
1206
1207                 unless ($sth->rows ) {
1208                     my $sth = $dbh->prepare(
1209 "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname, branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where cardnumber=?"
1210                       );
1211                     $sth->execute($cardnumber);
1212                     (
1213                         $borrowernumber, $firstname,  $surname,
1214                         $userflags,      $branchcode, $branchname,
1215                         $branchprinter,  $emailaddress
1216                     ) = $sth->fetchrow if ( $sth->rows );
1217
1218                     unless ( $sth->rows ) {
1219                         $sth->execute($userid);
1220                         (
1221                             $borrowernumber, $firstname, $surname, $userflags,
1222                             $branchcode, $branchname, $branchprinter, $emailaddress
1223                         ) = $sth->fetchrow if ( $sth->rows );
1224                     }
1225                 }
1226
1227                 my $ip       = $ENV{'REMOTE_ADDR'};
1228                 # if they specify at login, use that
1229                 if ($query->param('branch')) {
1230                     $branchcode  = $query->param('branch');
1231                     $branchname = GetBranchName($branchcode);
1232                 }
1233                 my $branches = GetBranches();
1234                 my @branchesloop;
1235                 foreach my $br ( keys %$branches ) {
1236                     #     now we work with the treatment of ip
1237                     my $domain = $branches->{$br}->{'branchip'};
1238                     if ( $domain && $ip =~ /^$domain/ ) {
1239                         $branchcode = $branches->{$br}->{'branchcode'};
1240
1241                         # new op dev : add the branchprinter and branchname in the cookie
1242                         $branchprinter = $branches->{$br}->{'branchprinter'};
1243                         $branchname    = $branches->{$br}->{'branchname'};
1244                     }
1245                 }
1246                 $session->param('number',$borrowernumber);
1247                 $session->param('id',$userid);
1248                 $session->param('cardnumber',$cardnumber);
1249                 $session->param('firstname',$firstname);
1250                 $session->param('surname',$surname);
1251                 $session->param('branch',$branchcode);
1252                 $session->param('branchname',$branchname);
1253                 $session->param('flags',$userflags);
1254                 $session->param('emailaddress',$emailaddress);
1255                 $session->param('ip',$session->remote_addr());
1256                 $session->param('lasttime',time());
1257             } elsif ( $return == 2 ) {
1258                 #We suppose the user is the superlibrarian
1259                 $session->param('number',0);
1260                 $session->param('id',C4::Context->config('user'));
1261                 $session->param('cardnumber',C4::Context->config('user'));
1262                 $session->param('firstname',C4::Context->config('user'));
1263                 $session->param('surname',C4::Context->config('user'));
1264                 $session->param('branch','NO_LIBRARY_SET');
1265                 $session->param('branchname','NO_LIBRARY_SET');
1266                 $session->param('flags',1);
1267                 $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress'));
1268                 $session->param('ip',$session->remote_addr());
1269                 $session->param('lasttime',time());
1270             }
1271             C4::Context::set_userenv(
1272                 $session->param('number'),       $session->param('id'),
1273                 $session->param('cardnumber'),   $session->param('firstname'),
1274                 $session->param('surname'),      $session->param('branch'),
1275                 $session->param('branchname'),   $session->param('flags'),
1276                 $session->param('emailaddress'), $session->param('branchprinter')
1277             );
1278             return ("ok", $cookie, $sessionID);
1279         } else {
1280             return ("failed", undef, undef);
1281         }
1282     }
1283 }
1284
1285 =head2 check_cookie_auth
1286
1287   ($status, $sessionId) = check_api_auth($cookie, $userflags);
1288
1289 Given a CGISESSID cookie set during a previous login to Koha, determine
1290 if the user has the privileges specified by C<$userflags>.
1291
1292 C<check_cookie_auth> is meant for authenticating special services
1293 such as tools/upload-file.pl that are invoked by other pages that
1294 have been authenticated in the usual way.
1295
1296 Possible return values in C<$status> are:
1297
1298 =over
1299
1300 =item "ok" -- user authenticated; C<$sessionID> have valid values.
1301
1302 =item "failed" -- credentials are not correct; C<$sessionid> are undef
1303
1304 =item "maintenance" -- DB is in maintenance mode; no login possible at the moment
1305
1306 =item "expired -- session cookie has expired; API user should resubmit userid and password
1307
1308 =back
1309
1310 =cut
1311
1312 sub check_cookie_auth {
1313     my $cookie = shift;
1314     my $flagsrequired = shift;
1315
1316     my $dbh     = C4::Context->dbh;
1317     my $timeout = C4::Context->preference('timeout');
1318     $timeout = 600 unless $timeout;
1319
1320     unless (C4::Context->preference('Version')) {
1321         # database has not been installed yet
1322         return ("maintenance", undef);
1323     }
1324     my $kohaversion=C4::Context::KOHAVERSION;
1325     $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
1326     if (C4::Context->preference('Version') < $kohaversion) {
1327         # database in need of version update; assume that
1328         # no API should be called while databsae is in
1329         # this condition.
1330         return ("maintenance", undef);
1331     }
1332
1333     # FIXME -- most of what follows is a copy-and-paste
1334     # of code from checkauth.  There is an obvious need
1335     # for refactoring to separate the various parts of
1336     # the authentication code, but as of 2007-11-23 this
1337     # is deferred so as to not introduce bugs into the
1338     # regular authentication code for Koha 3.0.
1339
1340     # see if we have a valid session cookie already
1341     # however, if a userid parameter is present (i.e., from
1342     # a form submission, assume that any current cookie
1343     # is to be ignored
1344     unless (defined $cookie and $cookie) {
1345         return ("failed", undef);
1346     }
1347     my $sessionID = $cookie;
1348     my $session = get_session($sessionID);
1349     C4::Context->_new_userenv($sessionID);
1350     if ($session) {
1351         C4::Context::set_userenv(
1352             $session->param('number'),       $session->param('id'),
1353             $session->param('cardnumber'),   $session->param('firstname'),
1354             $session->param('surname'),      $session->param('branch'),
1355             $session->param('branchname'),   $session->param('flags'),
1356             $session->param('emailaddress'), $session->param('branchprinter')
1357         );
1358
1359         my $ip = $session->param('ip');
1360         my $lasttime = $session->param('lasttime');
1361         my $userid = $session->param('id');
1362         if ( $lasttime < time() - $timeout ) {
1363             # time out
1364             $session->delete();
1365             C4::Context->_unset_userenv($sessionID);
1366             $userid    = undef;
1367             $sessionID = undef;
1368             return ("expired", undef);
1369         } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) {
1370             # IP address changed
1371             $session->delete();
1372             C4::Context->_unset_userenv($sessionID);
1373             $userid    = undef;
1374             $sessionID = undef;
1375             return ("expired", undef);
1376         } else {
1377             $session->param('lasttime',time());
1378             my $flags = haspermission($userid, $flagsrequired);
1379             if ($flags) {
1380                 return ("ok", $sessionID);
1381             } else {
1382                 $session->delete();
1383                 C4::Context->_unset_userenv($sessionID);
1384                 $userid    = undef;
1385                 $sessionID = undef;
1386                 return ("failed", undef);
1387             }
1388         }
1389     } else {
1390         return ("expired", undef);
1391     }
1392 }
1393
1394 =head2 get_session
1395
1396   use CGI::Session;
1397   my $session = get_session($sessionID);
1398
1399 Given a session ID, retrieve the CGI::Session object used to store
1400 the session's state.  The session object can be used to store
1401 data that needs to be accessed by different scripts during a
1402 user's session.
1403
1404 If the C<$sessionID> parameter is an empty string, a new session
1405 will be created.
1406
1407 =cut
1408
1409 sub get_session {
1410     my $sessionID = shift;
1411     my $storage_method = C4::Context->preference('SessionStorage');
1412     my $dbh = C4::Context->dbh;
1413     my $session;
1414     if ($storage_method eq 'mysql'){
1415         $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1416     }
1417     elsif ($storage_method eq 'Pg') {
1418         $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh});
1419     }
1420     elsif ($storage_method eq 'memcached' && $servers){
1421         $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => $memcached } );
1422     }
1423     else {
1424         # catch all defaults to tmp should work on all systems
1425         $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'});
1426     }
1427     return $session;
1428 }
1429
1430 sub checkpw {
1431
1432     my ( $dbh, $userid, $password, $query ) = @_;
1433     if ($ldap) {
1434         $debug and print "## checkpw - checking LDAP\n";
1435         my ($retval,$retcard,$retuserid) = checkpw_ldap(@_);    # EXTERNAL AUTH
1436         ($retval) and return ($retval,$retcard,$retuserid);
1437     }
1438
1439     if ($cas && $query && $query->param('ticket')) {
1440         $debug and print STDERR "## checkpw - checking CAS\n";
1441         # In case of a CAS authentication, we use the ticket instead of the password
1442         my $ticket = $query->param('ticket');
1443         my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query);    # EXTERNAL AUTH
1444         ($retval) and return ($retval,$retcard,$retuserid);
1445         return 0;
1446     }
1447
1448     # INTERNAL AUTH
1449     my $sth =
1450       $dbh->prepare(
1451 "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?"
1452       );
1453     $sth->execute($userid);
1454     if ( $sth->rows ) {
1455         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1456             $surname, $branchcode, $flags )
1457           = $sth->fetchrow;
1458         if ( md5_base64($password) eq $md5password and $md5password ne "!") {
1459
1460             C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber,
1461                 $firstname, $surname, $branchcode, $flags );
1462             return 1, $cardnumber, $userid;
1463         }
1464     }
1465     $sth =
1466       $dbh->prepare(
1467 "select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?"
1468       );
1469     $sth->execute($userid);
1470     if ( $sth->rows ) {
1471         my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname,
1472             $surname, $branchcode, $flags )
1473           = $sth->fetchrow;
1474         if ( md5_base64($password) eq $md5password ) {
1475
1476             C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber,
1477                 $firstname, $surname, $branchcode, $flags );
1478             return 1, $cardnumber, $userid;
1479         }
1480     }
1481     if (   $userid && $userid eq C4::Context->config('user')
1482         && "$password" eq C4::Context->config('pass') )
1483     {
1484
1485 # Koha superuser account
1486 #     C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1);
1487         return 2;
1488     }
1489     if (   $userid && $userid eq 'demo'
1490         && "$password" eq 'demo'
1491         && C4::Context->config('demo') )
1492     {
1493
1494 # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf
1495 # some features won't be effective : modify systempref, modify MARC structure,
1496         return 2;
1497     }
1498     return 0;
1499 }
1500
1501 =head2 getuserflags
1502
1503     my $authflags = getuserflags($flags, $userid, [$dbh]);
1504
1505 Translates integer flags into permissions strings hash.
1506
1507 C<$flags> is the integer userflags value ( borrowers.userflags )
1508 C<$userid> is the members.userid, used for building subpermissions
1509 C<$authflags> is a hashref of permissions
1510
1511 =cut
1512
1513 sub getuserflags {
1514     my $flags   = shift;
1515     my $userid  = shift;
1516     my $dbh     = @_ ? shift : C4::Context->dbh;
1517     my $userflags;
1518     $flags = 0 unless $flags;
1519     my $sth = $dbh->prepare("SELECT bit, flag, defaulton FROM userflags");
1520     $sth->execute;
1521
1522     while ( my ( $bit, $flag, $defaulton ) = $sth->fetchrow ) {
1523         if ( ( $flags & ( 2**$bit ) ) || $defaulton ) {
1524             $userflags->{$flag} = 1;
1525         }
1526         else {
1527             $userflags->{$flag} = 0;
1528         }
1529     }
1530
1531     # get subpermissions and merge with top-level permissions
1532     my $user_subperms = get_user_subpermissions($userid);
1533     foreach my $module (keys %$user_subperms) {
1534         next if $userflags->{$module} == 1; # user already has permission for everything in this module
1535         $userflags->{$module} = $user_subperms->{$module};
1536     }
1537
1538     return $userflags;
1539 }
1540
1541 =head2 get_user_subpermissions
1542
1543   $user_perm_hashref = get_user_subpermissions($userid);
1544
1545 Given the userid (note, not the borrowernumber) of a staff user,
1546 return a hashref of hashrefs of the specific subpermissions
1547 accorded to the user.  An example return is
1548
1549  {
1550     tools => {
1551         export_catalog => 1,
1552         import_patrons => 1,
1553     }
1554  }
1555
1556 The top-level hash-key is a module or function code from
1557 userflags.flag, while the second-level key is a code
1558 from permissions.
1559
1560 The results of this function do not give a complete picture
1561 of the functions that a staff user can access; it is also
1562 necessary to check borrowers.flags.
1563
1564 =cut
1565
1566 sub get_user_subpermissions {
1567     my $userid = shift;
1568
1569     my $dbh = C4::Context->dbh;
1570     my $sth = $dbh->prepare("SELECT flag, user_permissions.code
1571                              FROM user_permissions
1572                              JOIN permissions USING (module_bit, code)
1573                              JOIN userflags ON (module_bit = bit)
1574                              JOIN borrowers USING (borrowernumber)
1575                              WHERE userid = ?");
1576     $sth->execute($userid);
1577
1578     my $user_perms = {};
1579     while (my $perm = $sth->fetchrow_hashref) {
1580         $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1;
1581     }
1582     return $user_perms;
1583 }
1584
1585 =head2 get_all_subpermissions
1586
1587   my $perm_hashref = get_all_subpermissions();
1588
1589 Returns a hashref of hashrefs defining all specific
1590 permissions currently defined.  The return value
1591 has the same structure as that of C<get_user_subpermissions>,
1592 except that the innermost hash value is the description
1593 of the subpermission.
1594
1595 =cut
1596
1597 sub get_all_subpermissions {
1598     my $dbh = C4::Context->dbh;
1599     my $sth = $dbh->prepare("SELECT flag, code, description
1600                              FROM permissions
1601                              JOIN userflags ON (module_bit = bit)");
1602     $sth->execute();
1603
1604     my $all_perms = {};
1605     while (my $perm = $sth->fetchrow_hashref) {
1606         $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'};
1607     }
1608     return $all_perms;
1609 }
1610
1611 =head2 haspermission
1612
1613   $flags = ($userid, $flagsrequired);
1614
1615 C<$userid> the userid of the member
1616 C<$flags> is a hashref of required flags like C<$borrower-&lt;{authflags}> 
1617
1618 Returns member's flags or 0 if a permission is not met.
1619
1620 =cut
1621
1622 sub haspermission {
1623     my ($userid, $flagsrequired) = @_;
1624     my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?");
1625     $sth->execute($userid);
1626     my $flags = getuserflags($sth->fetchrow(), $userid);
1627     if ( $userid eq C4::Context->config('user') ) {
1628         # Super User Account from /etc/koha.conf
1629         $flags->{'superlibrarian'} = 1;
1630     }
1631     elsif ( $userid eq 'demo' && C4::Context->config('demo') ) {
1632         # Demo user that can do "anything" (demo=1 in /etc/koha.conf)
1633         $flags->{'superlibrarian'} = 1;
1634     }
1635
1636     return $flags if $flags->{superlibrarian};
1637
1638     foreach my $module ( keys %$flagsrequired ) {
1639         my $subperm = $flagsrequired->{$module};
1640         if ($subperm eq '*') {
1641             return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) );
1642         } else {
1643             return 0 unless ( $flags->{$module} == 1 or
1644                                 ( ref($flags->{$module}) and
1645                                   exists $flags->{$module}->{$subperm} and
1646                                   $flags->{$module}->{$subperm} == 1
1647                                 )
1648                             );
1649         }
1650     }
1651     return $flags;
1652     #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered.
1653 }
1654
1655
1656 sub getborrowernumber {
1657     my ($userid) = @_;
1658     my $userenv = C4::Context->userenv;
1659     if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) {
1660         return $userenv->{number};
1661     }
1662     my $dbh = C4::Context->dbh;
1663     for my $field ( 'userid', 'cardnumber' ) {
1664         my $sth =
1665           $dbh->prepare("select borrowernumber from borrowers where $field=?");
1666         $sth->execute($userid);
1667         if ( $sth->rows ) {
1668             my ($bnumber) = $sth->fetchrow;
1669             return $bnumber;
1670         }
1671     }
1672     return 0;
1673 }
1674
1675 END { }    # module clean-up code here (global destructor)
1676 1;
1677 __END__
1678
1679 =head1 SEE ALSO
1680
1681 CGI(3)
1682
1683 C4::Output(3)
1684
1685 Digest::MD5(3)
1686
1687 =cut