Bug 33080: Allow pagination to be built from stashed values
[koha.git] / Koha / REST / Plugin / Objects.pm
1 package Koha::REST::Plugin::Objects;
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 use JSON;
23
24 =head1 NAME
25
26 Koha::REST::Plugin::Objects
27
28 =head1 API
29
30 =head2 Helper methods
31
32 =cut
33
34 sub register {
35     my ( $self, $app ) = @_;
36
37 =head3 objects.find
38
39     my $patrons_rs = Koha::Patrons->new;
40     my $patrons = $c->objects->find( $patrons_rs, $id );
41     . . .
42     return $c->render({ status => 200, openapi => $patron });
43
44 Performs a database search using given Koha::Objects object and the $id.
45
46 Returns I<undef> if no object is found or the I<API representation> of
47 the requested object including any embeds specified in the request.
48
49 =cut
50
51     $app->helper(
52         'objects.find' => sub {
53             my ( $c, $result_set, $id ) = @_;
54             my $object = $c->objects->find_rs( $result_set, $id );
55             return unless $object;
56             return $c->objects->to_api($object);
57         }
58     );
59
60
61 =head3 objects.find_rs
62
63     my $patrons_rs = Koha::Patrons->new;
64     my $patron_rs = $c->objects->find_rs( $patrons_rs, $id );
65     . . .
66     return $c->render({ status => 200, openapi => $patron_rs->to_api });
67
68 Returns the passed Koha::Object result filtered to the passed $id and
69 with any embeds requested by the api applied.
70
71 The result object can then be used for further processing.
72
73 =cut
74
75     $app->helper(
76         'objects.find_rs' => sub {
77             my ( $c, $result_set, $id ) = @_;
78
79             my $attributes = {};
80
81             # Generate prefetches for embedded stuff
82             $c->dbic_merge_prefetch(
83                 {
84                     attributes => $attributes,
85                     result_set => $result_set
86                 }
87             );
88
89             my $object = $result_set->find( $id, $attributes );
90
91             return $object;
92         }
93     );
94
95 =head3 objects.search
96
97     my $patrons_rs = Koha::Patrons->new;
98     my $patrons = $c->objects->search( $patrons_rs );
99     . . .
100     return $c->render({ status => 200, openapi => $patrons });
101
102 Performs a database search using given Koha::Objects object with any api
103 query parameters applied.
104
105 Returns an arrayref of I<API representations> of the requested objects
106 including any embeds specified in the request.
107
108 Warning: this helper adds pagination headers to the calling controller, and thus
109 shouldn't be called twice in it.
110
111 =cut
112
113     $app->helper(
114         'objects.search' => sub {
115             my ( $c, $result_set ) = @_;
116
117             # Generate the resultset using the HTTP request information
118             my $objects_rs = $c->objects->search_rs($result_set);
119
120             # Add pagination headers
121             $c->add_pagination_headers();
122
123             return $c->objects->to_api($objects_rs);
124         }
125     );
126
127 =head3 objects.search_rs
128
129     my $patrons_object = Koha::Patrons->new;
130     my $patrons_rs = $c->objects->search_rs( $patrons_object );
131     . . .
132     return $c->render({ status => 200, openapi => $patrons_rs->to_api });
133
134 Returns the passed Koha::Objects resultset filtered as requested by the api query
135 parameters and with requested embeds applied.
136
137 Warning: this helper stashes base values for the pagination headers to the calling
138 controller, and thus shouldn't be called twice in it.
139
140 =cut
141
142     $app->helper(
143         'objects.search_rs' => sub {
144             my ( $c, $result_set ) = @_;
145
146             my $args       = $c->validation->output;
147             my $attributes = {};
148
149             # Extract reserved params
150             my ( $filtered_params, $reserved_params, $path_params ) =
151               $c->extract_reserved_params($args);
152
153             # Merge sorting into query attributes
154             $c->dbic_merge_sorting(
155                 {
156                     attributes => $attributes,
157                     params     => $reserved_params,
158                     result_set => $result_set
159                 }
160             );
161
162             # If no pagination parameters are passed, default
163             $reserved_params->{_per_page} //=
164               C4::Context->preference('RESTdefaultPageSize') // 20;
165             $reserved_params->{_page} //= 1;
166
167             $c->stash('koha.pagination.page'     => $reserved_params->{_page});
168             $c->stash('koha.pagination.per_page' => $reserved_params->{_per_page});
169
170             unless ( $reserved_params->{_per_page} == -1 ) {
171
172                 # Merge pagination into query attributes
173                 $c->dbic_merge_pagination(
174                     {
175                         filter => $attributes,
176                         params => $reserved_params
177                     }
178                 );
179             }
180
181             # Generate prefetches for embedded stuff
182             $c->dbic_merge_prefetch(
183                 {
184                     attributes => $attributes,
185                     result_set => $result_set
186                 }
187             );
188
189             # Call the to_model function by reference, if defined
190             if ( defined $filtered_params ) {
191
192                 # Apply the mapping function to the passed params
193                 $filtered_params =
194                   $result_set->attributes_from_api($filtered_params);
195                 $filtered_params =
196                   $c->build_query_params( $filtered_params, $reserved_params );
197             }
198
199             if (   defined $reserved_params->{q}
200                 || defined $reserved_params->{query}
201                 || defined $reserved_params->{'x-koha-query'} )
202             {
203                 $filtered_params //= {};
204
205                 my @query_params_array;
206
207                 # query in request body, JSON::Validator already decoded it
208                 push @query_params_array, $reserved_params->{query}
209                   if defined $reserved_params->{query};
210
211                 my $json = JSON->new;
212
213                 if ( ref( $reserved_params->{q} ) eq 'ARRAY' ) {
214
215                    # q is defined as multi => JSON::Validator generates an array
216                     foreach my $q ( @{ $reserved_params->{q} } ) {
217                         push @query_params_array, $json->decode($q)
218                           if $q;    # skip if exists but is empty
219                     }
220                 }
221                 else {
222                     # objects.search called outside OpenAPI context
223                     # might be a hashref
224                     push @query_params_array,
225                       $json->decode( $reserved_params->{q} )
226                       if $reserved_params->{q};
227                 }
228
229                 push @query_params_array,
230                   $json->decode( $reserved_params->{'x-koha-query'} )
231                   if defined $reserved_params->{'x-koha-query'};
232
233                 my $query_params;
234
235                 if ( scalar(@query_params_array) > 1 ) {
236                     $query_params = { '-and' => \@query_params_array };
237                 }
238                 else {
239                     $query_params = $query_params_array[0];
240                 }
241
242                 $filtered_params =
243                   $c->merge_q_params( $filtered_params, $query_params,
244                     $result_set );
245             }
246
247             # request sequence id (i.e. 'draw' Datatables parameter)
248             $c->res->headers->add(
249                 'x-koha-request-id' => $reserved_params->{'x-koha-request-id'} )
250               if $reserved_params->{'x-koha-request-id'};
251
252             # If search_limited exists, use it
253             $result_set = $result_set->search_limited,
254               if $result_set->can('search_limited');
255
256             $c->stash('koha.pagination.base_total'   => $result_set->count);
257             $c->stash('koha.pagination.query_params' => $args);
258
259             # Generate the resultset
260             my $objects_rs = $result_set->search( $filtered_params, $attributes );
261             # Stash the page total if requires, total otherwise
262             $c->stash('koha.pagination.total' => $objects_rs->is_paged ? $objects_rs->pager->total_entries : $objects_rs->count);
263
264             return $objects_rs;
265         }
266     );
267
268 =head3 objects.to_api
269
270     my $patrons_rs = Koha::Patrons->new;
271     my $api_representation = $c->objects->to_api( $patrons_rs );
272
273 Returns the API representation of the passed resultset.
274
275 =cut
276
277     $app->helper(
278         'objects.to_api' => sub {
279             my ( $c, $object ) = @_;
280
281             # Privileged request?
282             my $public = $c->stash('is_public');
283
284             # Look for embeds
285             my $embed   = $c->stash('koha.embed');
286             my $strings = $c->stash('koha.strings');
287
288             return $object->to_api(
289                 {
290                     embed   => $embed,
291                     public  => $public,
292                     strings => $strings
293                 }
294             );
295         }
296     );
297 }
298
299 1;