Bug 33573: Add public endpoint for cancelling holds
[koha.git] / Koha / REST / Plugin / Pagination.pm
1 package Koha::REST::Plugin::Pagination;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use Mojo::Base 'Mojolicious::Plugin';
21
22 =head1 NAME
23
24 Koha::REST::Plugin::Pagination
25
26 =head1 API
27
28 =head2 Mojolicious::Plugin methods
29
30 =head3 register
31
32 =cut
33
34 sub register {
35     my ( $self, $app ) = @_;
36
37 =head2 Helper methods
38
39 =head3 add_pagination_headers
40
41     $c->add_pagination_headers();
42     $c->add_pagination_headers(
43         {
44             base_total   => $base_total,
45             page         => $page,
46             per_page     => $per_page,
47             query_params => $query_params,
48             total        => $total,
49         }
50     );
51
52 Adds RFC5988 compliant I<Link> headers for pagination to the response message carried
53 by our controller.
54
55 Additionally, it also adds the customer I<X-Total-Count> header containing the total results
56 count, and I<X-Base-Total-Count> header containing the total of the non-filtered results count.
57
58 Optionally accepts the any of the following parameters to override the values stored in the
59 stash by the I<search_rs> helper.
60
61 =over
62
63 =item base_total
64
65 Total records for the search domain (e.g. all patron records, filtered only by visibility)
66
67 =item page
68
69 The requested page number, usually extracted from the request query.
70 See I<objects.search_rs> for more information.
71
72 =item per_page
73
74 The requested maximum results per page, usually extracted from the request query.
75 See I<objects.search_rs> for more information.
76
77 =item query_params
78
79 The request query, usually extracted from the request query and used to build the I<Link> headers.
80 See I<objects.search_rs> for more information.
81
82 =item total
83
84 Total records for the search with filters applied.
85
86 =back
87
88 =cut
89
90     $app->helper(
91         'add_pagination_headers' => sub {
92             my ( $c, $args ) = @_;
93
94             my $base_total = $args->{base_total} // $c->stash('koha.pagination.base_total');
95             my $req_page   = $args->{page}         // $c->stash('koha.pagination.page')     // 1;
96             my $per_page   = $args->{per_page}     // $c->stash('koha.pagination.per_page') // C4::Context->preference('RESTdefaultPageSize') // 20;
97             my $params     = $args->{query_params} // $c->stash('koha.pagination.query_params');
98             my $total      = $args->{total}        // $c->stash('koha.pagination.total');
99
100             my $pages;
101             if ( $per_page == -1 ) {
102                 $req_page = 1;
103                 $pages    = 1;
104             }
105             else {
106                 $pages = int $total / $per_page;
107                 $pages++
108                     if $total % $per_page > 0;
109             }
110
111             my @links;
112
113             if ( $per_page != -1 and $pages > 1 and $req_page > 1 ) {    # Previous exists?
114                 push @links,
115                     _build_link(
116                     $c,
117                     {   page     => $req_page - 1,
118                         per_page => $per_page,
119                         rel      => 'prev',
120                         params   => $params
121                     }
122                     );
123             }
124
125             if ( $per_page != -1 and $pages > 1 and $req_page < $pages ) {    # Next exists?
126                 push @links,
127                     _build_link(
128                     $c,
129                     {   page     => $req_page + 1,
130                         per_page => $per_page,
131                         rel      => 'next',
132                         params   => $params
133                     }
134                     );
135             }
136
137             push @links,
138                 _build_link( $c,
139                 { page => 1, per_page => $per_page, rel => 'first', params => $params } );
140             push @links,
141                 _build_link( $c,
142                 { page => $pages, per_page => $per_page, rel => 'last', params => $params } );
143
144             # Add Link header
145             foreach my $link (@links) {
146                 $c->res->headers->add( 'Link' => $link );
147             }
148
149             # Add X-Total-Count header
150             $c->res->headers->add( 'X-Total-Count'      => $total );
151             $c->res->headers->add( 'X-Base-Total-Count' => $base_total )
152               if defined $base_total;
153
154             return $c;
155         }
156     );
157
158 =head3 dbic_merge_pagination
159
160     $filter = $c->dbic_merge_pagination({
161         filter => $filter,
162         params => {
163             page     => $params->{_page},
164             per_page => $params->{_per_page}
165         }
166     });
167
168 Adds I<page> and I<rows> elements to the filter parameter.
169
170 =cut
171
172     $app->helper(
173         'dbic_merge_pagination' => sub {
174             my ( $c, $args ) = @_;
175             my $filter = $args->{filter};
176
177             $filter->{page} = $args->{params}->{_page};
178             $filter->{rows} = $args->{params}->{_per_page};
179
180             return $filter;
181         }
182     );
183 }
184
185 =head2 Internal methods
186
187 =head3 _build_link
188
189     my $link = _build_link( $c, { page => 1, per_page => 5, rel => 'prev' });
190
191 Returns a string, suitable for using in Link headers following RFC5988.
192
193 =cut
194
195 sub _build_link {
196     my ( $c, $args ) = @_;
197
198     my $params = $args->{params};
199
200     $params->{_page}     = $args->{page};
201     $params->{_per_page} = $args->{per_page};
202
203     my $link = '<'
204         . $c->req->url->clone->query(
205             $params
206         )->to_abs
207         . '>; rel="'
208         . $args->{rel} . '"';
209
210     # TODO: Find a better solution for this horrible (but needed) fix
211     $link =~ s|api/v1/app\.pl/||;
212
213     return $link;
214 }
215
216 1;