Bug 25898: Prohibit indirect object notation
[koha.git] / circ / pendingreserves.pl
blob01cf877b55284dc8f4c909b98b64e77f509e8797
1 #!/usr/bin/perl
3 # Copyright 2000-2002 Katipo Communications
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 use Modern::Perl;
22 use constant PULL_INTERVAL => 2;
24 use C4::Context;
25 use C4::Output;
26 use CGI qw ( -utf8 );
27 use C4::Auth;
28 use C4::Debug;
29 use C4::Items qw( ModItemTransfer );
30 use C4::Reserves qw( ModReserveCancelAll );
31 use Koha::Biblios;
32 use Koha::DateUtils;
33 use Koha::Holds;
34 use DateTime::Duration;
36 my $input = CGI->new;
37 my $startdate = $input->param('from');
38 my $enddate = $input->param('to');
39 my $theme = $input->param('theme'); # only used if allowthemeoverride is set
40 my $op = $input->param('op') || '';
41 my $borrowernumber = $input->param('borrowernumber');
42 my $reserve_id = $input->param('reserve_id');
44 my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
46 template_name => "circ/pendingreserves.tt",
47 query => $input,
48 type => "intranet",
49 flagsrequired => { circulate => "circulate_remaining_permissions" },
50 debug => 1,
54 my @messages;
55 if ( $op eq 'cancel_reserve' and $reserve_id ) {
56 my $hold = Koha::Holds->find( $reserve_id );
57 if ( $hold ) {
58 my $cancellation_reason = $input->param('cancellation-reason');
59 $hold->cancel({ cancellation_reason => $cancellation_reason });
60 push @messages, { type => 'message', code => 'hold_cancelled' };
62 } elsif ( $op =~ m|^mark_as_lost| ) {
63 my $hold = Koha::Holds->find( $reserve_id );
64 die "wrong reserve_id" unless $hold; # This is a bit rude, but we are not supposed to get a wrong reserve_id
65 my $item = $hold->item;
66 if ( $item and C4::Context->preference('CanMarkHoldsToPullAsLost') =~ m|^allow| ) {
67 my $patron = $hold->borrower;
68 C4::Circulation::LostItem( $item->itemnumber, "pendingreserves" );
69 if ( $op eq 'mark_as_lost_and_notify' and C4::Context->preference('CanMarkHoldsToPullAsLost') eq 'allow_and_notify' ) {
70 my $library = $hold->branch;
71 my $letter = C4::Letters::GetPreparedLetter(
72 module => 'reserves',
73 letter_code => 'CANCEL_HOLD_ON_LOST',
74 branchcode => $patron->branchcode,
75 lang => $patron->lang,
76 tables => {
77 branches => $library->branchcode,
78 borrowers => $patron->borrowernumber,
79 items => $item->itemnumber,
80 biblio => $hold->biblionumber,
81 biblioitems => $hold->biblionumber,
82 reserves => $hold->unblessed,
85 if ( $letter ) {
86 my $admin_email_address = $library->branchemail || C4::Context->preference('KohaAdminEmailAddress');
88 C4::Letters::EnqueueLetter(
89 { letter => $letter,
90 borrowernumber => $patron->borrowernumber,
91 message_transport_type => 'email',
92 from_address => $admin_email_address,
95 unless ( $patron->notice_email_address ) {
96 push @messages, {type => 'alert', code => 'no_email_address', };
98 push @messages, { type => 'message', code => 'letter_enqueued' };
99 } else {
100 push @messages, { type => 'error', code => 'no_template_notice' };
103 $hold->cancel;
104 if ( $item->homebranch ne $item->holdingbranch ) {
105 C4::Items::ModItemTransfer( $item->itemnumber, $item->holdingbranch, $item->homebranch, 'LostReserve' );
108 if ( my $yaml = C4::Context->preference('UpdateItemWhenLostFromHoldList') ) {
109 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
110 my $assignments;
111 eval { $assignments = YAML::Load($yaml); };
112 if ($@) {
113 warn "Unable to parse UpdateItemWhenLostFromHoldList syspref : $@" if $@;
115 else {
116 eval {
117 while ( my ( $f, $v ) = each( %$assignments ) ) {
118 $item->$f($v);
120 $item->store;
122 warn "Unable to modify item itemnumber=" . $item->itemnumber . ": $@" if $@;
126 } elsif ( not $item ) {
127 push @messages, { type => 'alert', code => 'hold_placed_at_biblio_level'};
128 } # else the url parameters have been modified and the user is not allowed to continue
132 my $today = dt_from_string;
134 if ( $startdate ) {
135 $startdate =~ s/^\s+//;
136 $startdate =~ s/\s+$//;
137 $startdate = eval{dt_from_string( $startdate )};
139 unless ( $startdate ){
140 # changed from delivered range of 10 years-yesterday to 2 days ago-today
141 # Find two days ago for the default shelf pull start date, unless HoldsToPullStartDate sys pref is set.
142 $startdate = $today - DateTime::Duration->new( days => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL );
145 if ( $enddate ) {
146 $enddate =~ s/^\s+//;
147 $enddate =~ s/\s+$//;
148 $enddate = eval{dt_from_string( $enddate )};
150 unless ( $enddate ) {
151 #similarly: calculate end date with ConfirmFutureHolds (days)
152 $enddate = $today + DateTime::Duration->new( days => C4::Context->preference('ConfirmFutureHolds') || 0 );
155 my @reservedata;
156 my $dbh = C4::Context->dbh;
157 my $sqldatewhere = "";
158 my $startdate_iso = output_pref({ dt => $startdate, dateformat => 'iso', dateonly => 1 });
159 my $enddate_iso = output_pref({ dt => $enddate, dateformat => 'iso', dateonly => 1 });
161 $debug and warn $startdate_iso. "\n" . $enddate_iso;
163 my @query_params = ();
165 if ($startdate_iso) {
166 $sqldatewhere .= " AND reservedate >= ?";
167 push @query_params, $startdate_iso;
169 if ($enddate_iso) {
170 $sqldatewhere .= " AND reservedate <= ?";
171 push @query_params, $enddate_iso;
174 my $item_type = C4::Context->preference('item-level_itypes') ? "items.itype" : "biblioitems.itemtype";
176 # Bug 21320
177 if ( ! C4::Context->preference('AllowHoldsOnDamagedItems') ) {
178 $sqldatewhere .= " AND damaged = 0";
181 my $strsth =
182 "SELECT min(reservedate) as l_reservedate,
183 reserves.reserve_id,
184 reserves.borrowernumber as borrowernumber,
186 GROUP_CONCAT(DISTINCT items.holdingbranch
187 ORDER BY items.itemnumber SEPARATOR '|') l_holdingbranch,
188 reserves.biblionumber,
189 reserves.branchcode as l_branch,
190 reserves.itemnumber,
191 items.holdingbranch,
192 items.homebranch,
193 GROUP_CONCAT(DISTINCT $item_type
194 ORDER BY items.itemnumber SEPARATOR '|') l_item_type,
195 GROUP_CONCAT(DISTINCT items.location
196 ORDER BY items.itemnumber SEPARATOR '|') l_location,
197 GROUP_CONCAT(DISTINCT items.itemcallnumber
198 ORDER BY items.itemnumber SEPARATOR '|') l_itemcallnumber,
199 GROUP_CONCAT(DISTINCT items.enumchron
200 ORDER BY items.itemnumber SEPARATOR '|') l_enumchron,
201 GROUP_CONCAT(DISTINCT items.copynumber
202 ORDER BY items.itemnumber SEPARATOR '|') l_copynumber,
203 GROUP_CONCAT(DISTINCT items.barcode
204 ORDER BY items.itemnumber SEPARATOR '|') l_barcode,
205 biblio.title,
206 biblio.copyrightdate,
207 biblioitems.publicationyear,
208 biblio.subtitle,
209 biblio.medium,
210 biblio.part_number,
211 biblio.part_name,
212 biblio.author,
213 biblioitems.editionstatement,
214 count(DISTINCT items.itemnumber) as icount,
215 count(DISTINCT reserves.borrowernumber) as rcount,
216 borrowers.firstname,
217 borrowers.surname
218 FROM reserves
219 LEFT JOIN items ON items.biblionumber=reserves.biblionumber
220 LEFT JOIN biblio ON reserves.biblionumber=biblio.biblionumber
221 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
222 LEFT JOIN branchtransfers ON items.itemnumber=branchtransfers.itemnumber
223 LEFT JOIN issues ON items.itemnumber=issues.itemnumber
224 LEFT JOIN borrowers ON reserves.borrowernumber=borrowers.borrowernumber
225 LEFT JOIN circulation_rules ON ( items.itype=circulation_rules.itemtype AND rule_name = 'holdallowed' AND circulation_rules.branchcode IS NULL AND circulation_rules.categorycode IS NULL )
226 WHERE
227 reserves.found IS NULL
228 $sqldatewhere
229 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = items.itemnumber)
230 AND items.itemnumber NOT IN (SELECT itemnumber FROM branchtransfers where datearrived IS NULL)
231 AND items.itemnumber NOT IN (SELECT itemnumber FROM reserves WHERE found IS NOT NULL AND itemnumber IS NOT NULL)
232 AND issues.itemnumber IS NULL
233 AND reserves.priority <> 0
234 AND reserves.suspend = 0
235 AND notforloan = 0 AND itemlost = 0 AND withdrawn = 0
236 AND ( circulation_rules.rule_value IS NULL OR circulation_rules.rule_value != 0 )
238 # GROUP BY reserves.biblionumber allows only items that are not checked out, else multiples occur when
239 # multiple patrons have a hold on an item
240 #FIXME "found IS NOT NULL AND itemnumber IS NOT NULL" is just a workaround: see BZ 25726
242 if (C4::Context->preference('IndependentBranches')){
243 $strsth .= " AND items.holdingbranch=? ";
244 push @query_params, C4::Context->userenv->{'branch'};
246 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
248 my $sth = $dbh->prepare($strsth);
249 $sth->execute(@query_params);
251 while ( my $data = $sth->fetchrow_hashref ) {
252 push(
253 @reservedata, {
254 reservedate => $data->{l_reservedate},
255 firstname => $data->{firstname} || '',
256 surname => $data->{surname},
257 title => $data->{title},
258 editionstatement => $data->{editionstatement},
259 subtitle => $data->{subtitle},
260 medium => $data->{medium},
261 part_number => $data->{part_number},
262 part_name => $data->{part_name},
263 author => $data->{author},
264 borrowernumber => $data->{borrowernumber},
265 biblionumber => $data->{biblionumber},
266 holdingbranches => [split('\|', $data->{l_holdingbranch})],
267 branch => $data->{l_branch},
268 itemcallnumber => [split('\|', $data->{l_itemcallnumber})],
269 enumchron => [split('\|', $data->{l_enumchron})],
270 copyno => [split('\|', $data->{l_copynumber})],
271 barcode => [split('\|', $data->{l_barcode})],
272 count => $data->{icount},
273 rcount => $data->{rcount},
274 pullcount => $data->{icount} <= $data->{rcount} ? $data->{icount} : $data->{rcount},
275 itemTypes => [split('\|', $data->{l_item_type})],
276 locations => [split('\|', $data->{l_location})],
277 reserve_id => $data->{reserve_id},
278 holdingbranch => $data->{holdingbranch},
279 homebranch => $data->{homebranch},
280 itemnumber => $data->{itemnumber},
281 publicationyear => C4::Context->preference('marcflavour') eq "MARC21" ? $data->{copyrightdate} : $data->{publicationyear},
285 $sth->finish;
287 $template->param(
288 todaysdate => $today,
289 from => $startdate,
290 to => $enddate,
291 reserveloop => \@reservedata,
292 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
293 HoldsToPullStartDate => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL,
294 HoldsToPullEndDate => C4::Context->preference('ConfirmFutureHolds') || 0,
295 messages => \@messages,
298 output_html_with_http_headers $input, $cookie, $template->output;