Bug 31281: Use correct reply-to email when sending overdue mails
[koha.git] / C4 / Output.pm
1 package C4::Output;
2
3 #package to deal with marking up output
4 #You will need to edit parts of this pm
5 #set the value of path to be where your html lives
6
7 # Copyright 2000-2002 Katipo Communications
8 #
9 # This file is part of Koha.
10 #
11 # Koha is free software; you can redistribute it and/or modify it
12 # under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # Koha is distributed in the hope that it will be useful, but
17 # WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23
24
25 # NOTE: I'm pretty sure this module is deprecated in favor of
26 # templates.
27
28 use Modern::Perl;
29
30 use HTML::Entities;
31 use Scalar::Util qw( looks_like_number );
32 use URI::Escape;
33
34 use C4::Auth qw( get_template_and_user );
35 use C4::Context;
36 use C4::Templates;
37
38 our (@ISA, @EXPORT_OK);
39
40 BEGIN {
41     require Exporter;
42
43     @ISA    = qw(Exporter);
44     @EXPORT_OK = qw(
45         is_ajax
46         ajax_fail
47         setlanguagecookie getlanguagecookie pagination_bar parametrized_url
48         output_html_with_http_headers output_ajax_with_http_headers output_with_http_headers
49         output_and_exit_if_error output_and_exit output_error
50     );
51 }
52
53 =head1 NAME
54
55 C4::Output - Functions for managing output, is slowly being deprecated
56
57 =head1 FUNCTIONS
58
59 =over 2
60
61 =item pagination_bar
62
63    pagination_bar($base_url, $nb_pages, $current_page, $startfrom_name)
64
65 Build an HTML pagination bar based on the number of page to display, the
66 current page and the url to give to each page link.
67
68 C<$base_url> is the URL for each page link. The
69 C<$startfrom_name>=page_number is added at the end of the each URL.
70
71 C<$nb_pages> is the total number of pages available.
72
73 C<$current_page> is the current page number. This page number won't become a
74 link.
75
76 This function returns HTML, without any language dependency.
77
78 =cut
79
80 sub pagination_bar {
81     my $base_url = (@_ ? shift : return);
82     my $nb_pages       = (@_) ? shift : 1;
83     my $current_page   = (@_) ? shift : undef;  # delay default until later
84     my $startfrom_name = (@_) ? shift : 'page';
85     my $additional_parameters = shift || {};
86
87     $base_url = HTML::Entities::encode($base_url);
88
89     $current_page = looks_like_number($current_page) ? $current_page : undef;
90     $nb_pages     = looks_like_number($nb_pages)     ? $nb_pages     : undef;
91
92     # how many pages to show before and after the current page?
93     my $pages_around = 2;
94
95         my $delim = qr/\&(?:amp;)?|;/;          # "non memory" cluster: no backreference
96         $base_url =~ s/$delim*\b$startfrom_name=(\d+)//g; # remove previous pagination var
97     unless (defined $current_page and $current_page > 0 and $current_page <= $nb_pages) {
98         $current_page = ($1) ? $1 : 1;  # pull current page from param in URL, else default to 1
99     }
100         $base_url =~ s/($delim)+/$1/g;  # compress duplicate delims
101         $base_url =~ s/$delim;//g;              # remove empties
102         $base_url =~ s/$delim$//;               # remove trailing delim
103
104     my $url = $base_url . (($base_url =~ m/$delim/ or $base_url =~ m/\?/) ? '&amp;' : '?' ) . $startfrom_name . '=';
105     my $url_suffix = '';
106     while ( my ( $k, $v ) = each %$additional_parameters ) {
107         $url_suffix .= '&amp;' . URI::Escape::uri_escape_utf8($k) . '=' . URI::Escape::uri_escape_utf8($v);
108     }
109     my $pagination_bar = '';
110
111     # navigation bar useful only if more than one page to display !
112     if ( $nb_pages > 1 ) {
113
114         # link to first page?
115         if ( $current_page > 1 ) {
116             $pagination_bar .=
117                 "\n" . '&nbsp;'
118               . '<a href="'
119               . $url
120               . '1'
121               . $url_suffix
122               . '"rel="start">'
123               . '&lt;&lt;' . '</a>';
124         }
125         else {
126             $pagination_bar .=
127               "\n" . '&nbsp;<span class="inactive">&lt;&lt;</span>';
128         }
129
130         # link on previous page ?
131         if ( $current_page > 1 ) {
132             my $previous = $current_page - 1;
133
134             $pagination_bar .=
135                 "\n" . '&nbsp;'
136               . '<a href="'
137               . $url
138               . $previous
139               . $url_suffix
140               . '" rel="prev">' . '&lt;' . '</a>';
141         }
142         else {
143             $pagination_bar .=
144               "\n" . '&nbsp;<span class="inactive">&lt;</span>';
145         }
146
147         my $min_to_display      = $current_page - $pages_around;
148         my $max_to_display      = $current_page + $pages_around;
149         my $last_displayed_page = undef;
150
151         for my $page_number ( 1 .. $nb_pages ) {
152             if (
153                    $page_number == 1
154                 or $page_number == $nb_pages
155                 or (    $page_number >= $min_to_display
156                     and $page_number <= $max_to_display )
157               )
158             {
159                 if ( defined $last_displayed_page
160                     and $last_displayed_page != $page_number - 1 )
161                 {
162                     $pagination_bar .=
163                       "\n" . '&nbsp;<span class="inactive">...</span>';
164                 }
165
166                 if ( $page_number == $current_page ) {
167                     $pagination_bar .=
168                         "\n" . '&nbsp;'
169                       . '<span class="currentPage">'
170                       . $page_number
171                       . '</span>';
172                 }
173                 else {
174                     $pagination_bar .=
175                         "\n" . '&nbsp;'
176                       . '<a href="'
177                       . $url
178                       . $page_number
179                       . $url_suffix
180                       . '">'
181                       . $page_number . '</a>';
182                 }
183                 $last_displayed_page = $page_number;
184             }
185         }
186
187         # link on next page?
188         if ( $current_page < $nb_pages ) {
189             my $next = $current_page + 1;
190
191             $pagination_bar .= "\n"
192               . '&nbsp;<a href="'
193               . $url
194               . $next
195               . $url_suffix
196               . '" rel="next">' . '&gt;' . '</a>';
197         }
198         else {
199             $pagination_bar .=
200               "\n" . '&nbsp;<span class="inactive">&gt;</span>';
201         }
202
203         # link to last page?
204         if ( $current_page != $nb_pages ) {
205             $pagination_bar .= "\n"
206               . '&nbsp;<a href="'
207               . $url
208               . $nb_pages
209               . $url_suffix
210               . '" rel="last">'
211               . '&gt;&gt;' . '</a>';
212         }
213         else {
214             $pagination_bar .=
215               "\n" . '&nbsp;<span class="inactive">&gt;&gt;</span>';
216         }
217     }
218
219     return $pagination_bar;
220 }
221
222 =item output_with_http_headers
223
224    &output_with_http_headers($query, $cookie, $data, $content_type[, $status[, $extra_options]])
225
226 Outputs $data with the appropriate HTTP headers,
227 the authentication cookie $cookie and a Content-Type specified in
228 $content_type.
229
230 If applicable, $cookie can be undef, and it will not be sent.
231
232 $content_type is one of the following: 'html', 'js', 'json', 'opensearchdescription', 'xml', 'rss', or 'atom'.
233
234 $status is an HTTP status message, like '403 Authentication Required'. It defaults to '200 OK'.
235
236 $extra_options is hashref.  If the key 'force_no_caching' is present and has
237 a true value, the HTTP headers include directives to force there to be no
238 caching whatsoever.
239
240 =cut
241
242 sub output_with_http_headers {
243     my ( $query, $cookie, $data, $content_type, $status, $extra_options ) = @_;
244     $status ||= '200 OK';
245
246     $extra_options //= {};
247
248     my %content_type_map = (
249         'html' => 'text/html',
250         'js'   => 'text/javascript',
251         'json' => 'application/json',
252         'xml'  => 'text/xml',
253         # NOTE: not using application/atom+xml or application/rss+xml because of
254         # Internet Explorer 6; see bug 2078.
255         'rss'  => 'text/xml',
256         'atom' => 'text/xml',
257         'opensearchdescription' => 'application/opensearchdescription+xml',
258     );
259
260     die "Unknown content type '$content_type'" if ( !defined( $content_type_map{$content_type} ) );
261     my $cache_policy = 'no-cache';
262     $cache_policy .= ', no-store, max-age=0' if $extra_options->{force_no_caching};
263     my $options = {
264         type              => $content_type_map{$content_type},
265         status            => $status,
266         charset           => 'UTF-8',
267         Pragma            => 'no-cache',
268         'Cache-Control'   => $cache_policy,
269         'X-Frame-Options' => 'SAMEORIGIN',
270     };
271     $options->{expires} = 'now' if $extra_options->{force_no_caching};
272     $options->{'Access-Control-Allow-Origin'} = C4::Context->preference('AccessControlAllowOrigin')
273         if C4::Context->preference('AccessControlAllowOrigin');
274
275     $options->{cookie} = $cookie if $cookie;
276     if ($content_type eq 'html') {  # guaranteed to be one of the content_type_map keys, else we'd have died
277         $options->{'Content-Style-Type' } = 'text/css';
278         $options->{'Content-Script-Type'} = 'text/javascript';
279     }
280
281 # We can't encode here, that will double encode our templates, and xslt
282 # We need to fix the encoding as it comes out of the database, or when we pass the variables to templates
283
284     $data =~ s/\&amp\;amp\; /\&amp\; /g;
285     print $query->header($options), $data;
286 }
287
288 sub output_html_with_http_headers {
289     my ( $query, $cookie, $data, $status, $extra_options ) = @_;
290     output_with_http_headers( $query, $cookie, $data, 'html', $status, $extra_options );
291 }
292
293
294 sub output_ajax_with_http_headers {
295     my ( $query, $js ) = @_;
296     print $query->header(
297         -type            => 'text/javascript',
298         -charset         => 'UTF-8',
299         -Pragma          => 'no-cache',
300         -'Cache-Control' => 'no-cache',
301         -expires         => '-1d',
302     ), $js;
303 }
304
305 sub is_ajax {
306     my $x_req = $ENV{HTTP_X_REQUESTED_WITH};
307     return ( $x_req and $x_req =~ /XMLHttpRequest/i ) ? 1 : 0;
308 }
309
310 =item output_and_exit_if_error
311
312     output_and_exit_if_error( $query, $cookie, $template, $params );
313
314 To executed at the beginning of scripts to stop the script at this point if
315 some errors are found.
316
317 Tests for module 'members':
318 * patron is not defined (we are looking for a patron that does no longer exist/never existed)
319 * The logged in user cannot see patron's infos (feature 'cannot_see_patron_infos')
320
321 Others will be added here depending on the needs (for instance biblio does not exist will be useful).
322
323 =cut
324
325 sub output_and_exit_if_error {
326     my ( $query, $cookie, $template, $params ) = @_;
327     my $error;
328     if ( $params and exists $params->{module} ) {
329         if ( $params->{module} eq 'members' ) {
330             my $logged_in_user = $params->{logged_in_user};
331             my $current_patron = $params->{current_patron};
332             if ( not $current_patron ) {
333                 $error = 'unknown_patron';
334             }
335             elsif( not $logged_in_user->can_see_patron_infos( $current_patron ) ) {
336                 $error = 'cannot_see_patron_infos';
337             }
338         } elsif ( $params->{module} eq 'cataloguing' ) {
339             # We are testing the record to avoid additem to fetch the Koha::Biblio
340             # But in the long term we will want to get a biblio in parameter
341             $error = 'unknown_biblio' unless $params->{record};
342         }
343     }
344
345     output_and_exit( $query, $cookie, $template, $error ) if $error;
346     return;
347 }
348
349 =item output_and_exit
350
351     output_and_exit( $query, $cookie, $template, $error );
352
353     $error is a blocking error like biblionumber not found or so.
354     We should output the error and exit.
355
356 =cut
357
358 sub output_and_exit {
359     my ( $query, $cookie, $template, $error ) = @_;
360     $template->param( blocking_error => $error );
361     output_html_with_http_headers ( $query, $cookie, $template->output );
362     exit;
363 }
364
365 sub output_error {
366     my ( $query, $error ) = @_;
367     my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
368         {
369             template_name   => 'errors/errorpage.tt',
370             query           => $query,
371             type            => 'intranet',
372             authnotrequired => 1,
373         }
374     );
375     my $admin = C4::Context->preference('KohaAdminEmailAddress');
376     $template->param (
377         admin => $admin,
378         errno => $error,
379     );
380     output_with_http_headers $query, $cookie, $template->output, 'html', '404 Not Found';
381 }
382
383 sub parametrized_url {
384     my $url = shift || ''; # ie page.pl?ln={LANG}
385     my $vars = shift || {}; # ie { LANG => en }
386     my $ret = $url;
387     while ( my ($key,$val) = each %$vars) {
388         my $val_url = URI::Escape::uri_escape_utf8( $val // q{} );
389         $ret =~ s/\{$key\}/$val_url/g;
390     }
391     $ret =~ s/\{[^\{]*\}//g; # remove remaining vars
392     return $ret;
393 }
394
395 END { }    # module clean-up code here (global destructor)
396
397 1;
398 __END__
399
400 =back
401
402 =head1 AUTHOR
403
404 Koha Development Team <http://koha-community.org/>
405
406 =cut