Filename | /mnt/catalyst/koha/C4/Letters.pm |
Statements | Executed 38 statements in 10.5ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 14.8ms | 17.9ms | BEGIN@23 | C4::Letters::
1 | 1 | 1 | 3.37ms | 17.5ms | BEGIN@28 | C4::Letters::
1 | 1 | 1 | 3.33ms | 10.4ms | BEGIN@24 | C4::Letters::
1 | 1 | 1 | 2.28ms | 2.81ms | BEGIN@31 | C4::Letters::
1 | 1 | 1 | 420µs | 432µs | BEGIN@20 | C4::Letters::
1 | 1 | 1 | 12µs | 40µs | BEGIN@33 | C4::Letters::
1 | 1 | 1 | 11µs | 96µs | BEGIN@32 | C4::Letters::
1 | 1 | 1 | 10µs | 123µs | BEGIN@26 | C4::Letters::
1 | 1 | 1 | 10µs | 88µs | BEGIN@29 | C4::Letters::
1 | 1 | 1 | 10µs | 10µs | BEGIN@40 | C4::Letters::
1 | 1 | 1 | 10µs | 39µs | BEGIN@30 | C4::Letters::
1 | 1 | 1 | 9µs | 27µs | BEGIN@34 | C4::Letters::
1 | 1 | 1 | 9µs | 50µs | BEGIN@35 | C4::Letters::
1 | 1 | 1 | 8µs | 58µs | BEGIN@38 | C4::Letters::
1 | 1 | 1 | 8µs | 31µs | BEGIN@36 | C4::Letters::
1 | 1 | 1 | 7µs | 12µs | BEGIN@21 | C4::Letters::
1 | 1 | 1 | 5µs | 5µs | BEGIN@27 | C4::Letters::
0 | 0 | 0 | 0s | 0s | EnqueueLetter | C4::Letters::
0 | 0 | 0 | 0s | 0s | GetLetters | C4::Letters::
0 | 0 | 0 | 0s | 0s | GetPreparedLetter | C4::Letters::
0 | 0 | 0 | 0s | 0s | GetPrintMessages | C4::Letters::
0 | 0 | 0 | 0s | 0s | GetQueuedMessages | C4::Letters::
0 | 0 | 0 | 0s | 0s | GetRSSMessages | C4::Letters::
0 | 0 | 0 | 0s | 0s | SendAlerts | C4::Letters::
0 | 0 | 0 | 0s | 0s | SendQueuedMessages | C4::Letters::
0 | 0 | 0 | 0s | 0s | _add_attachments | C4::Letters::
0 | 0 | 0 | 0s | 0s | _get_unsent_messages | C4::Letters::
0 | 0 | 0 | 0s | 0s | _parseletter | C4::Letters::
0 | 0 | 0 | 0s | 0s | _parseletter_sth | C4::Letters::
0 | 0 | 0 | 0s | 0s | _send_message_by_email | C4::Letters::
0 | 0 | 0 | 0s | 0s | _send_message_by_sms | C4::Letters::
0 | 0 | 0 | 0s | 0s | _set_message_status | C4::Letters::
0 | 0 | 0 | 0s | 0s | _substitute_tables | C4::Letters::
0 | 0 | 0 | 0s | 0s | _update_message_to_address | C4::Letters::
0 | 0 | 0 | 0s | 0s | _wrap_html | C4::Letters::
0 | 0 | 0 | 0s | 0s | addalert | C4::Letters::
0 | 0 | 0 | 0s | 0s | delalert | C4::Letters::
0 | 0 | 0 | 0s | 0s | findrelatedto | C4::Letters::
0 | 0 | 0 | 0s | 0s | getalert | C4::Letters::
0 | 0 | 0 | 0s | 0s | getletter | C4::Letters::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package C4::Letters; | ||||
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 | 2 | 26µs | 2 | 444µs | # spent 432µs (420+12) within C4::Letters::BEGIN@20 which was called:
# once (420µs+12µs) by C4::Reserves::BEGIN@36 at line 20 # spent 432µs making 1 call to C4::Letters::BEGIN@20
# spent 12µs making 1 call to strict::import |
21 | 2 | 25µs | 2 | 17µs | # spent 12µs (7+5) within C4::Letters::BEGIN@21 which was called:
# once (7µs+5µs) by C4::Reserves::BEGIN@36 at line 21 # spent 12µs making 1 call to C4::Letters::BEGIN@21
# spent 5µs making 1 call to warnings::import |
22 | |||||
23 | 2 | 819µs | 1 | 17.9ms | # spent 17.9ms (14.8+3.06) within C4::Letters::BEGIN@23 which was called:
# once (14.8ms+3.06ms) by C4::Reserves::BEGIN@36 at line 23 # spent 17.9ms making 1 call to C4::Letters::BEGIN@23 |
24 | 2 | 732µs | 2 | 10.5ms | # spent 10.4ms (3.33+7.08) within C4::Letters::BEGIN@24 which was called:
# once (3.33ms+7.08ms) by C4::Reserves::BEGIN@36 at line 24 # spent 10.4ms making 1 call to C4::Letters::BEGIN@24
# spent 75µs making 1 call to Exporter::import |
25 | |||||
26 | 2 | 25µs | 2 | 236µs | # spent 123µs (10+113) within C4::Letters::BEGIN@26 which was called:
# once (10µs+113µs) by C4::Reserves::BEGIN@36 at line 26 # spent 123µs making 1 call to C4::Letters::BEGIN@26
# spent 113µs making 1 call to Exporter::import |
27 | 2 | 23µs | 1 | 5µs | # spent 5µs within C4::Letters::BEGIN@27 which was called:
# once (5µs+0s) by C4::Reserves::BEGIN@36 at line 27 # spent 5µs making 1 call to C4::Letters::BEGIN@27 |
28 | 2 | 2.78ms | 2 | 17.5ms | # spent 17.5ms (3.37+14.1) within C4::Letters::BEGIN@28 which was called:
# once (3.37ms+14.1ms) by C4::Reserves::BEGIN@36 at line 28 # spent 17.5ms making 1 call to C4::Letters::BEGIN@28
# spent 48µs making 1 call to Exporter::import |
29 | 2 | 26µs | 2 | 165µs | # spent 88µs (10+77) within C4::Letters::BEGIN@29 which was called:
# once (10µs+77µs) by C4::Reserves::BEGIN@36 at line 29 # spent 88µs making 1 call to C4::Letters::BEGIN@29
# spent 77µs making 1 call to Exporter::import |
30 | 2 | 23µs | 2 | 69µs | # spent 39µs (10+29) within C4::Letters::BEGIN@30 which was called:
# once (10µs+29µs) by C4::Reserves::BEGIN@36 at line 30 # spent 39µs making 1 call to C4::Letters::BEGIN@30
# spent 29µs making 1 call to Exporter::import |
31 | 2 | 2.43ms | 1 | 2.81ms | # spent 2.81ms (2.28+523µs) within C4::Letters::BEGIN@31 which was called:
# once (2.28ms+523µs) by C4::Reserves::BEGIN@36 at line 31 # spent 2.81ms making 1 call to C4::Letters::BEGIN@31 |
32 | 2 | 28µs | 2 | 180µs | # spent 96µs (11+85) within C4::Letters::BEGIN@32 which was called:
# once (11µs+85µs) by C4::Reserves::BEGIN@36 at line 32 # spent 96µs making 1 call to C4::Letters::BEGIN@32
# spent 85µs making 1 call to Exporter::import |
33 | 2 | 26µs | 2 | 68µs | # spent 40µs (12+28) within C4::Letters::BEGIN@33 which was called:
# once (12µs+28µs) by C4::Reserves::BEGIN@36 at line 33 # spent 40µs making 1 call to C4::Letters::BEGIN@33
# spent 28µs making 1 call to Exporter::import |
34 | 2 | 22µs | 2 | 44µs | # spent 27µs (9+17) within C4::Letters::BEGIN@34 which was called:
# once (9µs+17µs) by C4::Reserves::BEGIN@36 at line 34 # spent 27µs making 1 call to C4::Letters::BEGIN@34
# spent 17µs making 1 call to Exporter::import |
35 | 2 | 21µs | 2 | 91µs | # spent 50µs (9+41) within C4::Letters::BEGIN@35 which was called:
# once (9µs+41µs) by C4::Reserves::BEGIN@36 at line 35 # spent 50µs making 1 call to C4::Letters::BEGIN@35
# spent 41µs making 1 call to Exporter::import |
36 | 2 | 25µs | 2 | 55µs | # spent 31µs (8+23) within C4::Letters::BEGIN@36 which was called:
# once (8µs+23µs) by C4::Reserves::BEGIN@36 at line 36 # spent 31µs making 1 call to C4::Letters::BEGIN@36
# spent 24µs making 1 call to Exporter::import |
37 | |||||
38 | 2 | 75µs | 2 | 109µs | # spent 58µs (8+51) within C4::Letters::BEGIN@38 which was called:
# once (8µs+51µs) by C4::Reserves::BEGIN@36 at line 38 # spent 58µs making 1 call to C4::Letters::BEGIN@38
# spent 51µs making 1 call to vars::import |
39 | |||||
40 | # spent 10µs within C4::Letters::BEGIN@40 which was called:
# once (10µs+0s) by C4::Reserves::BEGIN@36 at line 48 | ||||
41 | 1 | 600ns | require Exporter; | ||
42 | # set the version for version checking | ||||
43 | 1 | 900ns | $VERSION = 3.07.00.049; | ||
44 | 1 | 5µs | @ISA = qw(Exporter); | ||
45 | 1 | 5µs | @EXPORT = qw( | ||
46 | &GetLetters &GetPreparedLetter &GetWrappedLetter &addalert &getalert &delalert &findrelatedto &SendAlerts &GetPrintMessages | ||||
47 | ); | ||||
48 | 1 | 1.80ms | 1 | 10µs | } # spent 10µs making 1 call to C4::Letters::BEGIN@40 |
49 | |||||
50 | =head1 NAME | ||||
51 | |||||
52 | C4::Letters - Give functions for Letters management | ||||
53 | |||||
54 | =head1 SYNOPSIS | ||||
55 | |||||
56 | use C4::Letters; | ||||
57 | |||||
58 | =head1 DESCRIPTION | ||||
59 | |||||
60 | "Letters" is the tool used in Koha to manage informations sent to the patrons and/or the library. This include some cron jobs like | ||||
61 | late issues, as well as other tasks like sending a mail to users that have subscribed to a "serial issue alert" (= being warned every time a new issue has arrived at the library) | ||||
62 | |||||
63 | Letters are managed through "alerts" sent by Koha on some events. All "alert" related functions are in this module too. | ||||
64 | |||||
65 | =head2 GetLetters([$category]) | ||||
66 | |||||
67 | $letters = &GetLetters($category); | ||||
68 | returns informations about letters. | ||||
69 | if needed, $category filters for letters given category | ||||
70 | Create a letter selector with the following code | ||||
71 | |||||
72 | =head3 in PERL SCRIPT | ||||
73 | |||||
74 | my $letters = GetLetters($cat); | ||||
75 | my @letterloop; | ||||
76 | foreach my $thisletter (keys %$letters) { | ||||
77 | my $selected = 1 if $thisletter eq $letter; | ||||
78 | my %row =( | ||||
79 | value => $thisletter, | ||||
80 | selected => $selected, | ||||
81 | lettername => $letters->{$thisletter}, | ||||
82 | ); | ||||
83 | push @letterloop, \%row; | ||||
84 | } | ||||
85 | $template->param(LETTERLOOP => \@letterloop); | ||||
86 | |||||
87 | =head3 in TEMPLATE | ||||
88 | |||||
89 | <select name="letter"> | ||||
90 | <option value="">Default</option> | ||||
91 | <!-- TMPL_LOOP name="LETTERLOOP" --> | ||||
92 | <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="lettername" --></option> | ||||
93 | <!-- /TMPL_LOOP --> | ||||
94 | </select> | ||||
95 | |||||
96 | =cut | ||||
97 | |||||
98 | sub GetLetters { | ||||
99 | |||||
100 | # returns a reference to a hash of references to ALL letters... | ||||
101 | my $cat = shift; | ||||
102 | my %letters; | ||||
103 | my $dbh = C4::Context->dbh; | ||||
104 | my $sth; | ||||
105 | if (defined $cat) { | ||||
106 | my $query = "SELECT * FROM letter WHERE module = ? ORDER BY name"; | ||||
107 | $sth = $dbh->prepare($query); | ||||
108 | $sth->execute($cat); | ||||
109 | } | ||||
110 | else { | ||||
111 | my $query = "SELECT * FROM letter ORDER BY name"; | ||||
112 | $sth = $dbh->prepare($query); | ||||
113 | $sth->execute; | ||||
114 | } | ||||
115 | while ( my $letter = $sth->fetchrow_hashref ) { | ||||
116 | $letters{ $letter->{'code'} } = $letter->{'name'}; | ||||
117 | } | ||||
118 | return \%letters; | ||||
119 | } | ||||
120 | |||||
121 | # FIXME: using our here means that a Plack server will need to be | ||||
122 | # restarted fairly regularly when working with this routine. | ||||
123 | # A better option would be to use Koha::Cache and use a cache | ||||
124 | # that actually works in a persistent environment, but as a | ||||
125 | # short-term fix, our will work. | ||||
126 | 1 | 300ns | our %letter; | ||
127 | sub getletter { | ||||
128 | my ( $module, $code, $branchcode ) = @_; | ||||
129 | |||||
130 | |||||
131 | if ( C4::Context->preference('IndependentBranches') | ||||
132 | and $branchcode | ||||
133 | and C4::Context->userenv ) { | ||||
134 | |||||
135 | $branchcode = C4::Context->userenv->{'branch'}; | ||||
136 | } | ||||
137 | $branchcode //= ''; | ||||
138 | |||||
139 | if ( my $l = $letter{$module}{$code}{$branchcode} ) { | ||||
140 | return { %$l }; # deep copy | ||||
141 | } | ||||
142 | |||||
143 | my $dbh = C4::Context->dbh; | ||||
144 | my $sth = $dbh->prepare("select * from letter where module=? and code=? and (branchcode = ? or branchcode = '') order by branchcode desc limit 1"); | ||||
145 | $sth->execute( $module, $code, $branchcode ); | ||||
146 | my $line = $sth->fetchrow_hashref | ||||
147 | or return; | ||||
148 | $line->{'content-type'} = 'text/html; charset="UTF-8"' if $line->{is_html}; | ||||
149 | $letter{$module}{$code}{$branchcode} = $line; | ||||
150 | return { %$line }; | ||||
151 | } | ||||
152 | |||||
153 | =head2 addalert ($borrowernumber, $type, $externalid) | ||||
154 | |||||
155 | parameters : | ||||
156 | - $borrowernumber : the number of the borrower subscribing to the alert | ||||
157 | - $type : the type of alert. | ||||
158 | - $externalid : the primary key of the object to put alert on. For issues, the alert is made on subscriptionid. | ||||
159 | |||||
160 | create an alert and return the alertid (primary key) | ||||
161 | |||||
162 | =cut | ||||
163 | |||||
164 | sub addalert { | ||||
165 | my ( $borrowernumber, $type, $externalid ) = @_; | ||||
166 | my $dbh = C4::Context->dbh; | ||||
167 | my $sth = | ||||
168 | $dbh->prepare( | ||||
169 | "insert into alert (borrowernumber, type, externalid) values (?,?,?)"); | ||||
170 | $sth->execute( $borrowernumber, $type, $externalid ); | ||||
171 | |||||
172 | # get the alert number newly created and return it | ||||
173 | my $alertid = $dbh->{'mysql_insertid'}; | ||||
174 | return $alertid; | ||||
175 | } | ||||
176 | |||||
177 | =head2 delalert ($alertid) | ||||
178 | |||||
179 | parameters : | ||||
180 | - alertid : the alert id | ||||
181 | deletes the alert | ||||
182 | |||||
183 | =cut | ||||
184 | |||||
185 | sub delalert { | ||||
186 | my $alertid = shift or die "delalert() called without valid argument (alertid)"; # it's gonna die anyway. | ||||
187 | $debug and warn "delalert: deleting alertid $alertid"; | ||||
188 | my $sth = C4::Context->dbh->prepare("delete from alert where alertid=?"); | ||||
189 | $sth->execute($alertid); | ||||
190 | } | ||||
191 | |||||
192 | =head2 getalert ([$borrowernumber], [$type], [$externalid]) | ||||
193 | |||||
194 | parameters : | ||||
195 | - $borrowernumber : the number of the borrower subscribing to the alert | ||||
196 | - $type : the type of alert. | ||||
197 | - $externalid : the primary key of the object to put alert on. For issues, the alert is made on subscriptionid. | ||||
198 | all parameters NON mandatory. If a parameter is omitted, the query is done without the corresponding parameter. For example, without $externalid, returns all alerts for a borrower on a topic. | ||||
199 | |||||
200 | =cut | ||||
201 | |||||
202 | sub getalert { | ||||
203 | my ( $borrowernumber, $type, $externalid ) = @_; | ||||
204 | my $dbh = C4::Context->dbh; | ||||
205 | my $query = "SELECT a.*, b.branchcode FROM alert a JOIN borrowers b USING(borrowernumber) WHERE"; | ||||
206 | my @bind; | ||||
207 | if ($borrowernumber and $borrowernumber =~ /^\d+$/) { | ||||
208 | $query .= " borrowernumber=? AND "; | ||||
209 | push @bind, $borrowernumber; | ||||
210 | } | ||||
211 | if ($type) { | ||||
212 | $query .= " type=? AND "; | ||||
213 | push @bind, $type; | ||||
214 | } | ||||
215 | if ($externalid) { | ||||
216 | $query .= " externalid=? AND "; | ||||
217 | push @bind, $externalid; | ||||
218 | } | ||||
219 | $query =~ s/ AND $//; | ||||
220 | my $sth = $dbh->prepare($query); | ||||
221 | $sth->execute(@bind); | ||||
222 | return $sth->fetchall_arrayref({}); | ||||
223 | } | ||||
224 | |||||
225 | =head2 findrelatedto($type, $externalid) | ||||
226 | |||||
227 | parameters : | ||||
228 | - $type : the type of alert | ||||
229 | - $externalid : the id of the "object" to query | ||||
230 | |||||
231 | In the table alert, a "id" is stored in the externalid field. This "id" is related to another table, depending on the type of the alert. | ||||
232 | When type=issue, the id is related to a subscriptionid and this sub returns the name of the biblio. | ||||
233 | |||||
234 | =cut | ||||
235 | |||||
236 | # outmoded POD: | ||||
237 | # When type=virtual, the id is related to a virtual shelf and this sub returns the name of the sub | ||||
238 | |||||
239 | sub findrelatedto { | ||||
240 | my $type = shift or return; | ||||
241 | my $externalid = shift or return; | ||||
242 | my $q = ($type eq 'issue' ) ? | ||||
243 | "select title as result from subscription left join biblio on subscription.biblionumber=biblio.biblionumber where subscriptionid=?" : | ||||
244 | ($type eq 'borrower') ? | ||||
245 | "select concat(firstname,' ',surname) from borrowers where borrowernumber=?" : undef; | ||||
246 | unless ($q) { | ||||
247 | warn "findrelatedto(): Illegal type '$type'"; | ||||
248 | return; | ||||
249 | } | ||||
250 | my $sth = C4::Context->dbh->prepare($q); | ||||
251 | $sth->execute($externalid); | ||||
252 | my ($result) = $sth->fetchrow; | ||||
253 | return $result; | ||||
254 | } | ||||
255 | |||||
256 | =head2 SendAlerts | ||||
257 | |||||
258 | parameters : | ||||
259 | - $type : the type of alert | ||||
260 | - $externalid : the id of the "object" to query | ||||
261 | - $letter_code : the letter to send. | ||||
262 | |||||
263 | send an alert to all borrowers having put an alert on a given subject. | ||||
264 | |||||
265 | =cut | ||||
266 | |||||
267 | sub SendAlerts { | ||||
268 | my ( $type, $externalid, $letter_code ) = @_; | ||||
269 | my $dbh = C4::Context->dbh; | ||||
270 | if ( $type eq 'issue' ) { | ||||
271 | |||||
272 | # prepare the letter... | ||||
273 | # search the biblionumber | ||||
274 | my $sth = | ||||
275 | $dbh->prepare( | ||||
276 | "SELECT biblionumber FROM subscription WHERE subscriptionid=?"); | ||||
277 | $sth->execute($externalid); | ||||
278 | my ($biblionumber) = $sth->fetchrow | ||||
279 | or warn( "No subscription for '$externalid'" ), | ||||
280 | return; | ||||
281 | |||||
282 | my %letter; | ||||
283 | # find the list of borrowers to alert | ||||
284 | my $alerts = getalert( '', 'issue', $externalid ); | ||||
285 | foreach (@$alerts) { | ||||
286 | |||||
287 | my $borinfo = C4::Members::GetMember('borrowernumber' => $_->{'borrowernumber'}); | ||||
288 | my $email = $borinfo->{email} or next; | ||||
289 | |||||
290 | # warn "sending issues..."; | ||||
291 | my $userenv = C4::Context->userenv; | ||||
292 | my $branchdetails = GetBranchDetail($_->{'branchcode'}); | ||||
293 | my $letter = GetPreparedLetter ( | ||||
294 | module => 'serial', | ||||
295 | letter_code => $letter_code, | ||||
296 | branchcode => $userenv->{branch}, | ||||
297 | tables => { | ||||
298 | 'branches' => $_->{branchcode}, | ||||
299 | 'biblio' => $biblionumber, | ||||
300 | 'biblioitems' => $biblionumber, | ||||
301 | 'borrowers' => $borinfo, | ||||
302 | }, | ||||
303 | want_librarian => 1, | ||||
304 | ) or return; | ||||
305 | |||||
306 | # ... then send mail | ||||
307 | my %mail = ( | ||||
308 | To => $email, | ||||
309 | From => $branchdetails->{'branchemail'} || C4::Context->preference("KohaAdminEmailAddress"), | ||||
310 | Subject => Encode::encode( "utf8", "" . $letter->{title} ), | ||||
311 | Message => Encode::encode( "utf8", "" . $letter->{content} ), | ||||
312 | 'Content-Type' => 'text/plain; charset="utf8"', | ||||
313 | ); | ||||
314 | sendmail(%mail) or carp $Mail::Sendmail::error; | ||||
315 | } | ||||
316 | } | ||||
317 | elsif ( $type eq 'claimacquisition' or $type eq 'claimissues' ) { | ||||
318 | |||||
319 | # prepare the letter... | ||||
320 | # search the biblionumber | ||||
321 | my $strsth = $type eq 'claimacquisition' | ||||
322 | ? qq{ | ||||
323 | SELECT aqorders.*,aqbasket.*,biblio.*,biblioitems.*,aqbooksellers.*, | ||||
324 | aqbooksellers.id AS booksellerid | ||||
325 | FROM aqorders | ||||
326 | LEFT JOIN aqbasket ON aqbasket.basketno=aqorders.basketno | ||||
327 | LEFT JOIN biblio ON aqorders.biblionumber=biblio.biblionumber | ||||
328 | LEFT JOIN biblioitems ON aqorders.biblionumber=biblioitems.biblionumber | ||||
329 | LEFT JOIN aqbooksellers ON aqbasket.booksellerid=aqbooksellers.id | ||||
330 | WHERE aqorders.ordernumber IN ( | ||||
331 | } | ||||
332 | : qq{ | ||||
333 | SELECT serial.*,subscription.*, biblio.*, aqbooksellers.*, | ||||
334 | aqbooksellers.id AS booksellerid | ||||
335 | FROM serial | ||||
336 | LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid | ||||
337 | LEFT JOIN biblio ON serial.biblionumber=biblio.biblionumber | ||||
338 | LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id | ||||
339 | WHERE serial.serialid IN ( | ||||
340 | }; | ||||
341 | $strsth .= join( ",", @$externalid ) . ")"; | ||||
342 | my $sthorders = $dbh->prepare($strsth); | ||||
343 | $sthorders->execute; | ||||
344 | my $dataorders = $sthorders->fetchall_arrayref( {} ); | ||||
345 | |||||
346 | my $sthbookseller = | ||||
347 | $dbh->prepare("select * from aqbooksellers where id=?"); | ||||
348 | $sthbookseller->execute( $dataorders->[0]->{booksellerid} ); | ||||
349 | my $databookseller = $sthbookseller->fetchrow_hashref; | ||||
350 | |||||
351 | my @email; | ||||
352 | push @email, $databookseller->{bookselleremail} if $databookseller->{bookselleremail}; | ||||
353 | push @email, $databookseller->{contemail} if $databookseller->{contemail}; | ||||
354 | unless (@email) { | ||||
355 | warn "Bookseller $dataorders->[0]->{booksellerid} without emails"; | ||||
356 | return { error => "no_email" }; | ||||
357 | } | ||||
358 | |||||
359 | my $userenv = C4::Context->userenv; | ||||
360 | my $letter = GetPreparedLetter ( | ||||
361 | module => $type, | ||||
362 | letter_code => $letter_code, | ||||
363 | branchcode => $userenv->{branch}, | ||||
364 | tables => { | ||||
365 | 'branches' => $userenv->{branch}, | ||||
366 | 'aqbooksellers' => $databookseller, | ||||
367 | }, | ||||
368 | repeat => $dataorders, | ||||
369 | want_librarian => 1, | ||||
370 | ) or return; | ||||
371 | |||||
372 | # ... then send mail | ||||
373 | my %mail = ( | ||||
374 | To => join( ',', @email), | ||||
375 | From => $userenv->{emailaddress}, | ||||
376 | Subject => Encode::encode( "utf8", "" . $letter->{title} ), | ||||
377 | Message => Encode::encode( "utf8", "" . $letter->{content} ), | ||||
378 | 'Content-Type' => 'text/plain; charset="utf8"', | ||||
379 | ); | ||||
380 | sendmail(%mail) or carp $Mail::Sendmail::error; | ||||
381 | |||||
382 | logaction( | ||||
383 | "ACQUISITION", | ||||
384 | $type eq 'claimissues' ? "CLAIM ISSUE" : "ACQUISITION CLAIM", | ||||
385 | undef, | ||||
386 | "To=" | ||||
387 | . $databookseller->{contemail} | ||||
388 | . " Title=" | ||||
389 | . $letter->{title} | ||||
390 | . " Content=" | ||||
391 | . $letter->{content} | ||||
392 | ) if C4::Context->preference("LetterLog"); | ||||
393 | } | ||||
394 | # send an "account details" notice to a newly created user | ||||
395 | elsif ( $type eq 'members' ) { | ||||
396 | my $branchdetails = GetBranchDetail($externalid->{'branchcode'}); | ||||
397 | my $letter = GetPreparedLetter ( | ||||
398 | module => 'members', | ||||
399 | letter_code => $letter_code, | ||||
400 | branchcode => $externalid->{'branchcode'}, | ||||
401 | tables => { | ||||
402 | 'branches' => $branchdetails, | ||||
403 | 'borrowers' => $externalid->{'borrowernumber'}, | ||||
404 | }, | ||||
405 | substitute => { 'borrowers.password' => $externalid->{'password'} }, | ||||
406 | want_librarian => 1, | ||||
407 | ) or return; | ||||
408 | |||||
409 | return { error => "no_email" } unless $externalid->{'emailaddr'}; | ||||
410 | my %mail = ( | ||||
411 | To => $externalid->{'emailaddr'}, | ||||
412 | From => $branchdetails->{'branchemail'} || C4::Context->preference("KohaAdminEmailAddress"), | ||||
413 | Subject => Encode::encode( "utf8", $letter->{'title'} ), | ||||
414 | Message => Encode::encode( "utf8", $letter->{'content'} ), | ||||
415 | 'Content-Type' => 'text/plain; charset="utf8"', | ||||
416 | ); | ||||
417 | sendmail(%mail) or carp $Mail::Sendmail::error; | ||||
418 | } | ||||
419 | } | ||||
420 | |||||
421 | =head2 GetPreparedLetter( %params ) | ||||
422 | |||||
423 | %params hash: | ||||
424 | module => letter module, mandatory | ||||
425 | letter_code => letter code, mandatory | ||||
426 | branchcode => for letter selection, if missing default system letter taken | ||||
427 | tables => a hashref with table names as keys. Values are either: | ||||
428 | - a scalar - primary key value | ||||
429 | - an arrayref - primary key values | ||||
430 | - a hashref - full record | ||||
431 | substitute => custom substitution key/value pairs | ||||
432 | repeat => records to be substituted on consecutive lines: | ||||
433 | - an arrayref - tries to guess what needs substituting by | ||||
434 | taking remaining << >> tokensr; not recommended | ||||
435 | - a hashref token => @tables - replaces <token> << >> << >> </token> | ||||
436 | subtemplate for each @tables row; table is a hashref as above | ||||
437 | want_librarian => boolean, if set to true triggers librarian details | ||||
438 | substitution from the userenv | ||||
439 | Return value: | ||||
440 | letter fields hashref (title & content useful) | ||||
441 | |||||
442 | =cut | ||||
443 | |||||
444 | sub GetPreparedLetter { | ||||
445 | my %params = @_; | ||||
446 | |||||
447 | my $module = $params{module} or croak "No module"; | ||||
448 | my $letter_code = $params{letter_code} or croak "No letter_code"; | ||||
449 | my $branchcode = $params{branchcode} || ''; | ||||
450 | |||||
451 | my $letter = getletter( $module, $letter_code, $branchcode ) | ||||
452 | or warn( "No $module $letter_code letter"), | ||||
453 | return; | ||||
454 | |||||
455 | my $tables = $params{tables}; | ||||
456 | my $substitute = $params{substitute}; | ||||
457 | my $repeat = $params{repeat}; | ||||
458 | $tables || $substitute || $repeat | ||||
459 | or carp( "ERROR: nothing to substitute - both 'tables' and 'substitute' are empty" ), | ||||
460 | return; | ||||
461 | my $want_librarian = $params{want_librarian}; | ||||
462 | |||||
463 | if ($substitute) { | ||||
464 | while ( my ($token, $val) = each %$substitute ) { | ||||
465 | $letter->{title} =~ s/<<$token>>/$val/g; | ||||
466 | $letter->{content} =~ s/<<$token>>/$val/g; | ||||
467 | } | ||||
468 | } | ||||
469 | |||||
470 | my $OPACBaseURL = C4::Context->preference('OPACBaseURL'); | ||||
471 | $letter->{content} =~ s/<<OPACBaseURL>>/$OPACBaseURL/go; | ||||
472 | |||||
473 | if ($want_librarian) { | ||||
474 | # parsing librarian name | ||||
475 | my $userenv = C4::Context->userenv; | ||||
476 | $letter->{content} =~ s/<<LibrarianFirstname>>/$userenv->{firstname}/go; | ||||
477 | $letter->{content} =~ s/<<LibrarianSurname>>/$userenv->{surname}/go; | ||||
478 | $letter->{content} =~ s/<<LibrarianEmailaddress>>/$userenv->{emailaddress}/go; | ||||
479 | } | ||||
480 | |||||
481 | my ($repeat_no_enclosing_tags, $repeat_enclosing_tags); | ||||
482 | |||||
483 | if ($repeat) { | ||||
484 | if (ref ($repeat) eq 'ARRAY' ) { | ||||
485 | $repeat_no_enclosing_tags = $repeat; | ||||
486 | } else { | ||||
487 | $repeat_enclosing_tags = $repeat; | ||||
488 | } | ||||
489 | } | ||||
490 | |||||
491 | if ($repeat_enclosing_tags) { | ||||
492 | while ( my ($tag, $tag_tables) = each %$repeat_enclosing_tags ) { | ||||
493 | if ( $letter->{content} =~ m!<$tag>(.*)</$tag>!s ) { | ||||
494 | my $subcontent = $1; | ||||
495 | my @lines = map { | ||||
496 | my %subletter = ( title => '', content => $subcontent ); | ||||
497 | _substitute_tables( \%subletter, $_ ); | ||||
498 | $subletter{content}; | ||||
499 | } @$tag_tables; | ||||
500 | $letter->{content} =~ s!<$tag>.*</$tag>!join( "\n", @lines )!se; | ||||
501 | } | ||||
502 | } | ||||
503 | } | ||||
504 | |||||
505 | if ($tables) { | ||||
506 | _substitute_tables( $letter, $tables ); | ||||
507 | } | ||||
508 | |||||
509 | if ($repeat_no_enclosing_tags) { | ||||
510 | if ( $letter->{content} =~ m/[^\n]*<<.*>>[^\n]*/so ) { | ||||
511 | my $line = $&; | ||||
512 | my $i = 1; | ||||
513 | my @lines = map { | ||||
514 | my $c = $line; | ||||
515 | $c =~ s/<<count>>/$i/go; | ||||
516 | foreach my $field ( keys %{$_} ) { | ||||
517 | $c =~ s/(<<[^\.]+.$field>>)/$_->{$field}/; | ||||
518 | } | ||||
519 | $i++; | ||||
520 | $c; | ||||
521 | } @$repeat_no_enclosing_tags; | ||||
522 | |||||
523 | my $replaceby = join( "\n", @lines ); | ||||
524 | $letter->{content} =~ s/\Q$line\E/$replaceby/s; | ||||
525 | } | ||||
526 | } | ||||
527 | |||||
528 | $letter->{content} =~ s/<<\S*>>//go; #remove any stragglers | ||||
529 | # $letter->{content} =~ s/<<[^>]*>>//go; | ||||
530 | |||||
531 | return $letter; | ||||
532 | } | ||||
533 | |||||
534 | sub _substitute_tables { | ||||
535 | my ( $letter, $tables ) = @_; | ||||
536 | while ( my ($table, $param) = each %$tables ) { | ||||
537 | next unless $param; | ||||
538 | |||||
539 | my $ref = ref $param; | ||||
540 | |||||
541 | my $values; | ||||
542 | if ($ref && $ref eq 'HASH') { | ||||
543 | $values = $param; | ||||
544 | } | ||||
545 | else { | ||||
546 | my @pk; | ||||
547 | my $sth = _parseletter_sth($table); | ||||
548 | unless ($sth) { | ||||
549 | warn "_parseletter_sth('$table') failed to return a valid sth. No substitution will be done for that table."; | ||||
550 | return; | ||||
551 | } | ||||
552 | $sth->execute( $ref ? @$param : $param ); | ||||
553 | |||||
554 | $values = $sth->fetchrow_hashref; | ||||
555 | $sth->finish(); | ||||
556 | } | ||||
557 | |||||
558 | _parseletter ( $letter, $table, $values ); | ||||
559 | } | ||||
560 | } | ||||
561 | |||||
562 | sub _parseletter_sth { | ||||
563 | my $table = shift; | ||||
564 | my $sth; | ||||
565 | unless ($table) { | ||||
566 | carp "ERROR: _parseletter_sth() called without argument (table)"; | ||||
567 | return; | ||||
568 | } | ||||
569 | # NOTE: we used to check whether we had a statement handle cached in | ||||
570 | # a %handles module-level variable. This was a dumb move and | ||||
571 | # broke things for the rest of us. prepare_cached is a better | ||||
572 | # way to cache statement handles anyway. | ||||
573 | my $query = | ||||
574 | ($table eq 'biblio' ) ? "SELECT * FROM $table WHERE biblionumber = ?" : | ||||
575 | ($table eq 'biblioitems' ) ? "SELECT * FROM $table WHERE biblionumber = ?" : | ||||
576 | ($table eq 'items' ) ? "SELECT * FROM $table WHERE itemnumber = ?" : | ||||
577 | ($table eq 'issues' ) ? "SELECT * FROM $table WHERE itemnumber = ?" : | ||||
578 | ($table eq 'old_issues' ) ? "SELECT * FROM $table WHERE itemnumber = ? ORDER BY timestamp DESC LIMIT 1" : | ||||
579 | ($table eq 'reserves' ) ? "SELECT * FROM $table WHERE borrowernumber = ? and biblionumber = ?" : | ||||
580 | ($table eq 'borrowers' ) ? "SELECT * FROM $table WHERE borrowernumber = ?" : | ||||
581 | ($table eq 'branches' ) ? "SELECT * FROM $table WHERE branchcode = ?" : | ||||
582 | ($table eq 'suggestions' ) ? "SELECT * FROM $table WHERE suggestionid = ?" : | ||||
583 | ($table eq 'aqbooksellers') ? "SELECT * FROM $table WHERE id = ?" : | ||||
584 | ($table eq 'aqorders' ) ? "SELECT * FROM $table WHERE ordernumber = ?" : | ||||
585 | ($table eq 'opac_news' ) ? "SELECT * FROM $table WHERE idnew = ?" : | ||||
586 | ($table eq 'borrower_modifications') ? "SELECT * FROM $table WHERE borrowernumber = ? OR verification_token =?": | ||||
587 | undef ; | ||||
588 | unless ($query) { | ||||
589 | warn "ERROR: No _parseletter_sth query for table '$table'"; | ||||
590 | return; # nothing to get | ||||
591 | } | ||||
592 | unless ($sth = C4::Context->dbh->prepare_cached($query)) { | ||||
593 | warn "ERROR: Failed to prepare query: '$query'"; | ||||
594 | return; | ||||
595 | } | ||||
596 | return $sth; # now cache is populated for that $table | ||||
597 | } | ||||
598 | |||||
599 | =head2 _parseletter($letter, $table, $values) | ||||
600 | |||||
601 | parameters : | ||||
602 | - $letter : a hash to letter fields (title & content useful) | ||||
603 | - $table : the Koha table to parse. | ||||
604 | - $values : table record hashref | ||||
605 | parse all fields from a table, and replace values in title & content with the appropriate value | ||||
606 | (not exported sub, used only internally) | ||||
607 | |||||
608 | =cut | ||||
609 | |||||
610 | sub _parseletter { | ||||
611 | my ( $letter, $table, $values ) = @_; | ||||
612 | |||||
613 | if ( $table eq 'reserves' && $values->{'waitingdate'} ) { | ||||
614 | my @waitingdate = split /-/, $values->{'waitingdate'}; | ||||
615 | |||||
616 | $values->{'expirationdate'} = ''; | ||||
617 | if( C4::Context->preference('ExpireReservesMaxPickUpDelay') && | ||||
618 | C4::Context->preference('ReservesMaxPickUpDelay') ) { | ||||
619 | my $dt = dt_from_string(); | ||||
620 | $dt->add( days => C4::Context->preference('ReservesMaxPickUpDelay') ); | ||||
621 | $values->{'expirationdate'} = output_pref({ dt => $dt, dateonly => 1 }); | ||||
622 | } | ||||
623 | |||||
624 | $values->{'waitingdate'} = output_pref({ dt => dt_from_string( $values->{'waitingdate'} ), dateonly => 1 }); | ||||
625 | |||||
626 | } | ||||
627 | |||||
628 | if ($letter->{content} && $letter->{content} =~ /<<today>>/) { | ||||
629 | my $todaysdate = output_pref( DateTime->now() ); | ||||
630 | $letter->{content} =~ s/<<today>>/$todaysdate/go; | ||||
631 | } | ||||
632 | |||||
633 | while ( my ($field, $val) = each %$values ) { | ||||
634 | my $replacetablefield = "<<$table.$field>>"; | ||||
635 | my $replacefield = "<<$field>>"; | ||||
636 | 1 | 164µs | 1 | 855µs | $val =~ s/\p{P}$// if $val && $table=~/biblio/; # spent 855µs making 1 call to utf8::SWASHNEW |
637 | #BZ 9886: Assuming that we want to eliminate ISBD punctuation here | ||||
638 | #Therefore adding the test on biblio. This includes biblioitems, | ||||
639 | #but excludes items. Removed unneeded global and lookahead. | ||||
640 | |||||
641 | $val = GetAuthorisedValueByCode ('ROADTYPE', $val, 0) if $table=~/^borrowers$/ && $field=~/^streettype$/; | ||||
642 | my $replacedby = defined ($val) ? $val : ''; | ||||
643 | ($letter->{title} ) and do { | ||||
644 | $letter->{title} =~ s/$replacetablefield/$replacedby/g; | ||||
645 | $letter->{title} =~ s/$replacefield/$replacedby/g; | ||||
646 | }; | ||||
647 | ($letter->{content}) and do { | ||||
648 | $letter->{content} =~ s/$replacetablefield/$replacedby/g; | ||||
649 | $letter->{content} =~ s/$replacefield/$replacedby/g; | ||||
650 | }; | ||||
651 | } | ||||
652 | |||||
653 | if ($table eq 'borrowers' && $letter->{content}) { | ||||
654 | if ( my $attributes = GetBorrowerAttributes($values->{borrowernumber}) ) { | ||||
655 | my %attr; | ||||
656 | foreach (@$attributes) { | ||||
657 | my $code = $_->{code}; | ||||
658 | my $val = $_->{value_description} || $_->{value}; | ||||
659 | 1 | 1.38ms | 1 | 43µs | $val =~ s/\p{P}(?=$)//g if $val; # spent 43µs making 1 call to utf8::SWASHNEW |
660 | next unless $val gt ''; | ||||
661 | $attr{$code} ||= []; | ||||
662 | push @{ $attr{$code} }, $val; | ||||
663 | } | ||||
664 | while ( my ($code, $val_ar) = each %attr ) { | ||||
665 | my $replacefield = "<<borrower-attribute:$code>>"; | ||||
666 | my $replacedby = join ',', @$val_ar; | ||||
667 | $letter->{content} =~ s/$replacefield/$replacedby/g; | ||||
668 | } | ||||
669 | } | ||||
670 | } | ||||
671 | return $letter; | ||||
672 | } | ||||
673 | |||||
674 | =head2 EnqueueLetter | ||||
675 | |||||
676 | my $success = EnqueueLetter( { letter => $letter, | ||||
677 | borrowernumber => '12', message_transport_type => 'email' } ) | ||||
678 | |||||
679 | places a letter in the message_queue database table, which will | ||||
680 | eventually get processed (sent) by the process_message_queue.pl | ||||
681 | cronjob when it calls SendQueuedMessages. | ||||
682 | |||||
683 | return message_id on success | ||||
684 | |||||
685 | =cut | ||||
686 | |||||
687 | sub EnqueueLetter { | ||||
688 | my $params = shift or return; | ||||
689 | |||||
690 | return unless exists $params->{'letter'}; | ||||
691 | # return unless exists $params->{'borrowernumber'}; | ||||
692 | return unless exists $params->{'message_transport_type'}; | ||||
693 | |||||
694 | my $content = $params->{letter}->{content}; | ||||
695 | $content =~ s/\s+//g if(defined $content); | ||||
696 | if ( not defined $content or $content eq '' ) { | ||||
697 | warn "Trying to add an empty message to the message queue" if $debug; | ||||
698 | return; | ||||
699 | } | ||||
700 | |||||
701 | # If we have any attachments we should encode then into the body. | ||||
702 | if ( $params->{'attachments'} ) { | ||||
703 | $params->{'letter'} = _add_attachments( | ||||
704 | { letter => $params->{'letter'}, | ||||
705 | attachments => $params->{'attachments'}, | ||||
706 | message => MIME::Lite->new( Type => 'multipart/mixed' ), | ||||
707 | } | ||||
708 | ); | ||||
709 | } | ||||
710 | |||||
711 | my $dbh = C4::Context->dbh(); | ||||
712 | my $statement = << 'ENDSQL'; | ||||
713 | INSERT INTO message_queue | ||||
714 | ( borrowernumber, subject, content, metadata, letter_code, message_transport_type, status, time_queued, to_address, from_address, content_type ) | ||||
715 | VALUES | ||||
716 | ( ?, ?, ?, ?, ?, ?, ?, NOW(), ?, ?, ? ) | ||||
717 | ENDSQL | ||||
718 | |||||
719 | my $sth = $dbh->prepare($statement); | ||||
720 | my $result = $sth->execute( | ||||
721 | $params->{'borrowernumber'}, # borrowernumber | ||||
722 | $params->{'letter'}->{'title'}, # subject | ||||
723 | $params->{'letter'}->{'content'}, # content | ||||
724 | $params->{'letter'}->{'metadata'} || '', # metadata | ||||
725 | $params->{'letter'}->{'code'} || '', # letter_code | ||||
726 | $params->{'message_transport_type'}, # message_transport_type | ||||
727 | 'pending', # status | ||||
728 | $params->{'to_address'}, # to_address | ||||
729 | $params->{'from_address'}, # from_address | ||||
730 | $params->{'letter'}->{'content-type'}, # content_type | ||||
731 | ); | ||||
732 | return $dbh->last_insert_id(undef,undef,'message_queue', undef); | ||||
733 | } | ||||
734 | |||||
735 | =head2 SendQueuedMessages ([$hashref]) | ||||
736 | |||||
737 | my $sent = SendQueuedMessages( { verbose => 1 } ); | ||||
738 | |||||
739 | sends all of the 'pending' items in the message queue. | ||||
740 | |||||
741 | returns number of messages sent. | ||||
742 | |||||
743 | =cut | ||||
744 | |||||
745 | sub SendQueuedMessages { | ||||
746 | my $params = shift; | ||||
747 | |||||
748 | my $unsent_messages = _get_unsent_messages(); | ||||
749 | MESSAGE: foreach my $message ( @$unsent_messages ) { | ||||
750 | # warn Data::Dumper->Dump( [ $message ], [ 'message' ] ); | ||||
751 | warn sprintf( 'sending %s message to patron: %s', | ||||
752 | $message->{'message_transport_type'}, | ||||
753 | $message->{'borrowernumber'} || 'Admin' ) | ||||
754 | if $params->{'verbose'} or $debug; | ||||
755 | # This is just begging for subclassing | ||||
756 | next MESSAGE if ( lc($message->{'message_transport_type'}) eq 'rss' ); | ||||
757 | if ( lc( $message->{'message_transport_type'} ) eq 'email' ) { | ||||
758 | _send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} ); | ||||
759 | } | ||||
760 | elsif ( lc( $message->{'message_transport_type'} ) eq 'sms' ) { | ||||
761 | _send_message_by_sms( $message ); | ||||
762 | } | ||||
763 | } | ||||
764 | return scalar( @$unsent_messages ); | ||||
765 | } | ||||
766 | |||||
767 | =head2 GetRSSMessages | ||||
768 | |||||
769 | my $message_list = GetRSSMessages( { limit => 10, borrowernumber => '14' } ) | ||||
770 | |||||
771 | returns a listref of all queued RSS messages for a particular person. | ||||
772 | |||||
773 | =cut | ||||
774 | |||||
775 | sub GetRSSMessages { | ||||
776 | my $params = shift; | ||||
777 | |||||
778 | return unless $params; | ||||
779 | return unless ref $params; | ||||
780 | return unless $params->{'borrowernumber'}; | ||||
781 | |||||
782 | return _get_unsent_messages( { message_transport_type => 'rss', | ||||
783 | limit => $params->{'limit'}, | ||||
784 | borrowernumber => $params->{'borrowernumber'}, } ); | ||||
785 | } | ||||
786 | |||||
787 | =head2 GetPrintMessages | ||||
788 | |||||
789 | my $message_list = GetPrintMessages( { borrowernumber => $borrowernumber } ) | ||||
790 | |||||
791 | Returns a arrayref of all queued print messages (optionally, for a particular | ||||
792 | person). | ||||
793 | |||||
794 | =cut | ||||
795 | |||||
796 | sub GetPrintMessages { | ||||
797 | my $params = shift || {}; | ||||
798 | |||||
799 | return _get_unsent_messages( { message_transport_type => 'print', | ||||
800 | borrowernumber => $params->{'borrowernumber'}, | ||||
801 | } ); | ||||
802 | } | ||||
803 | |||||
804 | =head2 GetQueuedMessages ([$hashref]) | ||||
805 | |||||
806 | my $messages = GetQueuedMessage( { borrowernumber => '123', limit => 20 } ); | ||||
807 | |||||
808 | fetches messages out of the message queue. | ||||
809 | |||||
810 | returns: | ||||
811 | list of hashes, each has represents a message in the message queue. | ||||
812 | |||||
813 | =cut | ||||
814 | |||||
815 | sub GetQueuedMessages { | ||||
816 | my $params = shift; | ||||
817 | |||||
818 | my $dbh = C4::Context->dbh(); | ||||
819 | my $statement = << 'ENDSQL'; | ||||
820 | SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued | ||||
821 | FROM message_queue | ||||
822 | ENDSQL | ||||
823 | |||||
824 | my @query_params; | ||||
825 | my @whereclauses; | ||||
826 | if ( exists $params->{'borrowernumber'} ) { | ||||
827 | push @whereclauses, ' borrowernumber = ? '; | ||||
828 | push @query_params, $params->{'borrowernumber'}; | ||||
829 | } | ||||
830 | |||||
831 | if ( @whereclauses ) { | ||||
832 | $statement .= ' WHERE ' . join( 'AND', @whereclauses ); | ||||
833 | } | ||||
834 | |||||
835 | if ( defined $params->{'limit'} ) { | ||||
836 | $statement .= ' LIMIT ? '; | ||||
837 | push @query_params, $params->{'limit'}; | ||||
838 | } | ||||
839 | |||||
840 | my $sth = $dbh->prepare( $statement ); | ||||
841 | my $result = $sth->execute( @query_params ); | ||||
842 | return $sth->fetchall_arrayref({}); | ||||
843 | } | ||||
844 | |||||
845 | =head2 _add_attachements | ||||
846 | |||||
847 | named parameters: | ||||
848 | letter - the standard letter hashref | ||||
849 | attachments - listref of attachments. each attachment is a hashref of: | ||||
850 | type - the mime type, like 'text/plain' | ||||
851 | content - the actual attachment | ||||
852 | filename - the name of the attachment. | ||||
853 | message - a MIME::Lite object to attach these to. | ||||
854 | |||||
855 | returns your letter object, with the content updated. | ||||
856 | |||||
857 | =cut | ||||
858 | |||||
859 | sub _add_attachments { | ||||
860 | my $params = shift; | ||||
861 | |||||
862 | my $letter = $params->{'letter'}; | ||||
863 | my $attachments = $params->{'attachments'}; | ||||
864 | return $letter unless @$attachments; | ||||
865 | my $message = $params->{'message'}; | ||||
866 | |||||
867 | # First, we have to put the body in as the first attachment | ||||
868 | $message->attach( | ||||
869 | Type => $letter->{'content-type'} || 'TEXT', | ||||
870 | Data => $letter->{'is_html'} | ||||
871 | ? _wrap_html($letter->{'content'}, $letter->{'title'}) | ||||
872 | : $letter->{'content'}, | ||||
873 | ); | ||||
874 | |||||
875 | foreach my $attachment ( @$attachments ) { | ||||
876 | $message->attach( | ||||
877 | Type => $attachment->{'type'}, | ||||
878 | Data => $attachment->{'content'}, | ||||
879 | Filename => $attachment->{'filename'}, | ||||
880 | ); | ||||
881 | } | ||||
882 | # we're forcing list context here to get the header, not the count back from grep. | ||||
883 | ( $letter->{'content-type'} ) = grep( /^Content-Type:/, split( /\n/, $params->{'message'}->header_as_string ) ); | ||||
884 | $letter->{'content-type'} =~ s/^Content-Type:\s+//; | ||||
885 | $letter->{'content'} = $message->body_as_string; | ||||
886 | |||||
887 | return $letter; | ||||
888 | |||||
889 | } | ||||
890 | |||||
891 | sub _get_unsent_messages { | ||||
892 | my $params = shift; | ||||
893 | |||||
894 | my $dbh = C4::Context->dbh(); | ||||
895 | my $statement = << 'ENDSQL'; | ||||
896 | SELECT mq.message_id, mq.borrowernumber, mq.subject, mq.content, mq.message_transport_type, mq.status, mq.time_queued, mq.from_address, mq.to_address, mq.content_type, b.branchcode | ||||
897 | FROM message_queue mq | ||||
898 | LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber | ||||
899 | WHERE status = ? | ||||
900 | ENDSQL | ||||
901 | |||||
902 | my @query_params = ('pending'); | ||||
903 | if ( ref $params ) { | ||||
904 | if ( $params->{'message_transport_type'} ) { | ||||
905 | $statement .= ' AND message_transport_type = ? '; | ||||
906 | push @query_params, $params->{'message_transport_type'}; | ||||
907 | } | ||||
908 | if ( $params->{'borrowernumber'} ) { | ||||
909 | $statement .= ' AND borrowernumber = ? '; | ||||
910 | push @query_params, $params->{'borrowernumber'}; | ||||
911 | } | ||||
912 | if ( $params->{'limit'} ) { | ||||
913 | $statement .= ' limit ? '; | ||||
914 | push @query_params, $params->{'limit'}; | ||||
915 | } | ||||
916 | } | ||||
917 | |||||
918 | $debug and warn "_get_unsent_messages SQL: $statement"; | ||||
919 | $debug and warn "_get_unsent_messages params: " . join(',',@query_params); | ||||
920 | my $sth = $dbh->prepare( $statement ); | ||||
921 | my $result = $sth->execute( @query_params ); | ||||
922 | return $sth->fetchall_arrayref({}); | ||||
923 | } | ||||
924 | |||||
925 | sub _send_message_by_email { | ||||
926 | my $message = shift or return; | ||||
927 | my ($username, $password, $method) = @_; | ||||
928 | |||||
929 | my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} ); | ||||
930 | my $to_address = $message->{'to_address'}; | ||||
931 | unless ($to_address) { | ||||
932 | unless ($member) { | ||||
933 | warn "FAIL: No 'to_address' and INVALID borrowernumber ($message->{borrowernumber})"; | ||||
934 | _set_message_status( { message_id => $message->{'message_id'}, | ||||
935 | status => 'failed' } ); | ||||
936 | return; | ||||
937 | } | ||||
938 | $to_address = C4::Members::GetNoticeEmailAddress( $message->{'borrowernumber'} ); | ||||
939 | unless ($to_address) { | ||||
940 | # warn "FAIL: No 'to_address' and no email for " . ($member->{surname} ||'') . ", borrowernumber ($message->{borrowernumber})"; | ||||
941 | # warning too verbose for this more common case? | ||||
942 | _set_message_status( { message_id => $message->{'message_id'}, | ||||
943 | status => 'failed' } ); | ||||
944 | return; | ||||
945 | } | ||||
946 | } | ||||
947 | |||||
948 | my $utf8 = decode('MIME-Header', $message->{'subject'} ); | ||||
949 | $message->{subject}= encode('MIME-Header', $utf8); | ||||
950 | my $subject = encode('utf8', $message->{'subject'}); | ||||
951 | my $content = encode('utf8', $message->{'content'}); | ||||
952 | my $content_type = $message->{'content_type'} || 'text/plain; charset="UTF-8"'; | ||||
953 | my $is_html = $content_type =~ m/html/io; | ||||
954 | |||||
955 | my $branch_email = ( $member ) ? GetBranchDetail( $member->{'branchcode'} )->{'branchemail'} : undef; | ||||
956 | |||||
957 | my %sendmail_params = ( | ||||
958 | To => $to_address, | ||||
959 | From => $message->{'from_address'} || $branch_email || C4::Context->preference('KohaAdminEmailAddress'), | ||||
960 | Subject => $subject, | ||||
961 | charset => 'utf8', | ||||
962 | Message => $is_html ? _wrap_html($content, $subject) : $content, | ||||
963 | 'content-type' => $content_type, | ||||
964 | ); | ||||
965 | $sendmail_params{'Auth'} = {user => $username, pass => $password, method => $method} if $username; | ||||
966 | if ( my $bcc = C4::Context->preference('OverdueNoticeBcc') ) { | ||||
967 | $sendmail_params{ Bcc } = $bcc; | ||||
968 | } | ||||
969 | |||||
970 | _update_message_to_address($message->{'message_id'},$to_address) unless $message->{to_address}; #if initial message address was empty, coming here means that a to address was found and queue should be updated | ||||
971 | if ( sendmail( %sendmail_params ) ) { | ||||
972 | _set_message_status( { message_id => $message->{'message_id'}, | ||||
973 | status => 'sent' } ); | ||||
974 | return 1; | ||||
975 | } else { | ||||
976 | _set_message_status( { message_id => $message->{'message_id'}, | ||||
977 | status => 'failed' } ); | ||||
978 | carp $Mail::Sendmail::error; | ||||
979 | return; | ||||
980 | } | ||||
981 | } | ||||
982 | |||||
983 | sub _wrap_html { | ||||
984 | my ($content, $title) = @_; | ||||
985 | |||||
986 | my $css = C4::Context->preference("NoticeCSS") || ''; | ||||
987 | $css = qq{<link rel="stylesheet" type="text/css" href="$css">} if $css; | ||||
988 | return <<EOS; | ||||
989 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | ||||
990 | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
991 | <html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"> | ||||
992 | <head> | ||||
993 | <title>$title</title> | ||||
994 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||||
995 | $css | ||||
996 | </head> | ||||
997 | <body> | ||||
998 | $content | ||||
999 | </body> | ||||
1000 | </html> | ||||
1001 | EOS | ||||
1002 | } | ||||
1003 | |||||
1004 | sub _send_message_by_sms { | ||||
1005 | my $message = shift or return; | ||||
1006 | my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} ); | ||||
1007 | return unless $member->{'smsalertnumber'}; | ||||
1008 | |||||
1009 | my $success = C4::SMS->send_sms( { destination => $member->{'smsalertnumber'}, | ||||
1010 | message => $message->{'content'}, | ||||
1011 | } ); | ||||
1012 | _set_message_status( { message_id => $message->{'message_id'}, | ||||
1013 | status => ($success ? 'sent' : 'failed') } ); | ||||
1014 | return $success; | ||||
1015 | } | ||||
1016 | |||||
1017 | sub _update_message_to_address { | ||||
1018 | my ($id, $to)= @_; | ||||
1019 | my $dbh = C4::Context->dbh(); | ||||
1020 | $dbh->do('UPDATE message_queue SET to_address=? WHERE message_id=?',undef,($to,$id)); | ||||
1021 | } | ||||
1022 | |||||
1023 | sub _set_message_status { | ||||
1024 | my $params = shift or return; | ||||
1025 | |||||
1026 | foreach my $required_parameter ( qw( message_id status ) ) { | ||||
1027 | return unless exists $params->{ $required_parameter }; | ||||
1028 | } | ||||
1029 | |||||
1030 | my $dbh = C4::Context->dbh(); | ||||
1031 | my $statement = 'UPDATE message_queue SET status= ? WHERE message_id = ?'; | ||||
1032 | my $sth = $dbh->prepare( $statement ); | ||||
1033 | my $result = $sth->execute( $params->{'status'}, | ||||
1034 | $params->{'message_id'} ); | ||||
1035 | return $result; | ||||
1036 | } | ||||
1037 | |||||
1038 | |||||
1039 | 1 | 3µs | 1; | ||
1040 | __END__ |