Bug 32734: (QA follow-up) Make use of $c->objects->to_api
[koha.git] / Koha / REST / V1 / Biblios.pm
1 package Koha::REST::V1::Biblios;
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::Controller';
21
22 use Koha::Biblios;
23 use Koha::Ratings;
24 use Koha::RecordProcessor;
25 use C4::Biblio qw( DelBiblio AddBiblio ModBiblio );
26 use C4::Search qw( FindDuplicate );
27
28 use List::MoreUtils qw( any );
29 use MARC::Record::MiJ;
30
31 use Try::Tiny qw( catch try );
32
33 =head1 API
34
35 =head2 Methods
36
37 =head3 get
38
39 Controller function that handles retrieving a single biblio object
40
41 =cut
42
43 sub get {
44     my $c = shift->openapi->valid_input or return;
45
46     my $attributes;
47     $attributes = { prefetch => [ 'metadata' ] } # don't prefetch metadata if not needed
48         unless $c->req->headers->accept =~ m/application\/json/;
49
50     my $biblio = Koha::Biblios->find( { biblionumber => $c->validation->param('biblio_id') }, $attributes );
51
52     unless ( $biblio ) {
53         return $c->render(
54             status  => 404,
55             openapi => {
56                 error => "Object not found."
57             }
58         );
59     }
60
61     return try {
62
63         if ( $c->req->headers->accept =~ m/application\/json/ ) {
64             return $c->render(
65                 status => 200,
66                 json   => $biblio->to_api
67             );
68         }
69         else {
70             my $record = $biblio->metadata->record;
71
72             $c->respond_to(
73                 marcxml => {
74                     status => 200,
75                     format => 'marcxml',
76                     text   => $record->as_xml_record
77                 },
78                 mij => {
79                     status => 200,
80                     format => 'mij',
81                     data   => $record->to_mij
82                 },
83                 marc => {
84                     status => 200,
85                     format => 'marc',
86                     text   => $record->as_usmarc
87                 },
88                 txt => {
89                     status => 200,
90                     format => 'text/plain',
91                     text   => $record->as_formatted
92                 },
93                 any => {
94                     status  => 406,
95                     openapi => [
96                         "application/json",
97                         "application/marcxml+xml",
98                         "application/marc-in-json",
99                         "application/marc",
100                         "text/plain"
101                     ]
102                 }
103             );
104         }
105     }
106     catch {
107         $c->unhandled_exception($_);
108     };
109 }
110
111 =head3 delete
112
113 Controller function that handles deleting a biblio object
114
115 =cut
116
117 sub delete {
118     my $c = shift->openapi->valid_input or return;
119
120     my $biblio = Koha::Biblios->find( $c->validation->param('biblio_id') );
121
122     if ( not defined $biblio ) {
123         return $c->render(
124             status  => 404,
125             openapi => { error => "Object not found" }
126         );
127     }
128
129     return try {
130         my $error = DelBiblio( $biblio->id );
131
132         if ($error) {
133             return $c->render(
134                 status  => 409,
135                 openapi => { error => $error }
136             );
137         }
138         else {
139             return $c->render( status => 204, openapi => "" );
140         }
141     }
142     catch {
143         $c->unhandled_exception($_);
144     };
145 }
146
147 =head3 get_public
148
149 Controller function that handles retrieving a single biblio object
150
151 =cut
152
153 sub get_public {
154     my $c = shift->openapi->valid_input or return;
155
156     my $biblio = Koha::Biblios->find(
157         { biblionumber => $c->validation->param('biblio_id') },
158         { prefetch     => ['metadata'] } );
159
160     unless ($biblio) {
161         return $c->render(
162             status  => 404,
163             openapi => {
164                 error => "Object not found."
165             }
166         );
167     }
168
169     return try {
170
171         my $record = $biblio->metadata->record;
172
173         my $opachiddenitems_rules = C4::Context->yaml_preference('OpacHiddenItems');
174         my $patron = $c->stash('koha.user');
175
176         # Check if the biblio should be hidden for unprivileged access
177         # unless there's a logged in user, and there's an exception for it's
178         # category
179         unless ( $patron and $patron->category->override_hidden_items ) {
180             if ( $biblio->hidden_in_opac({ rules => $opachiddenitems_rules }) )
181             {
182                 return $c->render(
183                     status  => 404,
184                     openapi => {
185                         error => "Object not found."
186                     }
187                 );
188             }
189         }
190
191         my $marcflavour = C4::Context->preference("marcflavour");
192
193         my $record_processor = Koha::RecordProcessor->new({
194             filters => 'ViewPolicy',
195             options => {
196                 interface => 'opac',
197                 frameworkcode => $biblio->frameworkcode
198             }
199         });
200         # Apply framework's filtering to MARC::Record object
201         $record_processor->process($record);
202
203         $c->respond_to(
204             marcxml => {
205                 status => 200,
206                 format => 'marcxml',
207                 text   => $record->as_xml_record
208             },
209             mij => {
210                 status => 200,
211                 format => 'mij',
212                 data   => $record->to_mij
213             },
214             marc => {
215                 status => 200,
216                 format => 'marc',
217                 text   => $record->as_usmarc
218             },
219             txt => {
220                 status => 200,
221                 format => 'text/plain',
222                 text   => $record->as_formatted
223             },
224             any => {
225                 status  => 406,
226                 openapi => [
227                     "application/marcxml+xml",
228                     "application/marc-in-json",
229                     "application/marc",
230                     "text/plain"
231                 ]
232             }
233         );
234     }
235     catch {
236         $c->unhandled_exception($_);
237     };
238 }
239
240 =head3 get_items
241
242 Controller function that handles retrieving biblio's items
243
244 =cut
245
246 sub get_items {
247     my $c = shift->openapi->valid_input or return;
248
249     my $biblio = Koha::Biblios->find( { biblionumber => $c->validation->param('biblio_id') }, { prefetch => ['items'] } );
250
251     unless ( $biblio ) {
252         return $c->render(
253             status  => 404,
254             openapi => {
255                 error => "Object not found."
256             }
257         );
258     }
259
260     return try {
261
262         my $items_rs = $biblio->items;
263         my $items    = $c->objects->search( $items_rs );
264         return $c->render(
265             status  => 200,
266             openapi => $items
267         );
268     }
269     catch {
270         $c->unhandled_exception($_);
271     };
272 }
273
274 =head3 get_checkouts
275
276 List Koha::Checkout objects
277
278 =cut
279
280 sub get_checkouts {
281     my $c = shift->openapi->valid_input or return;
282
283     my $checked_in = delete $c->validation->output->{checked_in};
284
285     try {
286         my $biblio = Koha::Biblios->find( $c->validation->param('biblio_id') );
287
288         unless ($biblio) {
289             return $c->render(
290                 status  => 404,
291                 openapi => { error => 'Object not found' }
292             );
293         }
294
295         my $checkouts =
296           ($checked_in)
297           ? $c->objects->search( $biblio->old_checkouts )
298           : $c->objects->search( $biblio->current_checkouts );
299
300         return $c->render(
301             status  => 200,
302             openapi => $checkouts
303         );
304     }
305     catch {
306         $c->unhandled_exception($_);
307     };
308 }
309
310 =head3 pickup_locations
311
312 Method that returns the possible pickup_locations for a given biblio
313 used for building the dropdown selector
314
315 =cut
316
317 sub pickup_locations {
318     my $c = shift->openapi->valid_input or return;
319
320     my $biblio_id = $c->validation->param('biblio_id');
321     my $biblio = Koha::Biblios->find( $biblio_id );
322
323     unless ($biblio) {
324         return $c->render(
325             status  => 404,
326             openapi => { error => "Biblio not found" }
327         );
328     }
329
330     my $patron_id = delete $c->validation->output->{patron_id};
331     my $patron    = Koha::Patrons->find( $patron_id );
332
333     unless ($patron) {
334         return $c->render(
335             status  => 400,
336             openapi => { error => "Patron not found" }
337         );
338     }
339
340     return try {
341
342         my $pl_set = $biblio->pickup_locations( { patron => $patron } );
343
344         my @response = ();
345         if ( C4::Context->preference('AllowHoldPolicyOverride') ) {
346
347             my $libraries_rs = Koha::Libraries->search( { pickup_location => 1 } );
348             my $libraries    = $c->objects->search($libraries_rs);
349
350             @response = map {
351                 my $library = $_;
352                 $library->{needs_override} = (
353                     any { $_->branchcode eq $library->{library_id} }
354                     @{$pl_set->as_list}
355                   )
356                   ? Mojo::JSON->false
357                   : Mojo::JSON->true;
358                 $library;
359             } @{$libraries};
360         }
361         else {
362
363             my $pickup_locations = $c->objects->search($pl_set);
364             @response = map { $_->{needs_override} = Mojo::JSON->false; $_; } @{$pickup_locations};
365         }
366
367         return $c->render(
368             status  => 200,
369             openapi => \@response
370         );
371     }
372     catch {
373         $c->unhandled_exception($_);
374     };
375 }
376
377 =head3 get_items_public
378
379 Controller function that handles retrieving biblio's items, for unprivileged
380 access.
381
382 =cut
383
384 sub get_items_public {
385     my $c = shift->openapi->valid_input or return;
386
387     my $biblio = Koha::Biblios->find( { biblionumber => $c->validation->param('biblio_id') }, { prefetch => ['items'] } );
388
389     unless ( $biblio ) {
390         return $c->render(
391             status  => 404,
392             openapi => {
393                 error => "Object not found."
394             }
395         );
396     }
397
398     return try {
399
400         my $patron = $c->stash('koha.user');
401
402         my $items_rs = $biblio->items->filter_by_visible_in_opac({ patron => $patron });
403         my $items    = $c->objects->search( $items_rs );
404         return $c->render(
405             status  => 200,
406             openapi => $items
407         );
408     }
409     catch {
410         $c->unhandled_exception($_);
411     };
412 }
413
414 =head3 set_rating
415
416 Set rating for the logged in user
417
418 =cut
419
420
421 sub set_rating {
422     my $c = shift->openapi->valid_input or return;
423
424     my $biblio = Koha::Biblios->find( $c->validation->param('biblio_id') );
425
426     unless ($biblio) {
427         return $c->render(
428             status  => 404,
429             openapi => {
430                 error => "Object not found."
431             }
432         );
433     }
434
435     my $patron = $c->stash('koha.user');
436     unless ($patron) {
437         return $c->render(
438             status => 403,
439             openapi =>
440                 { error => "Cannot rate. Reason: must be logged-in" }
441         );
442     }
443
444     my $body   = $c->validation->param('body');
445     my $rating_value = $body->{rating};
446
447     return try {
448
449         my $rating = Koha::Ratings->find(
450             {
451                 biblionumber   => $biblio->biblionumber,
452                 borrowernumber => $patron->borrowernumber,
453             }
454         );
455         $rating->delete if $rating;
456
457         if ( $rating_value ) { # Cannot set to 0 from the UI
458             $rating = Koha::Rating->new(
459                 {
460                     biblionumber   => $biblio->biblionumber,
461                     borrowernumber => $patron->borrowernumber,
462                     rating_value   => $rating_value,
463                 }
464             )->store;
465         };
466         my $ratings =
467           Koha::Ratings->search( { biblionumber => $biblio->biblionumber } );
468         my $average = $ratings->get_avg_rating;
469
470         return $c->render(
471             status  => 200,
472             openapi => {
473                 rating  => $rating && $rating->in_storage ? $rating->rating_value : undef,
474                 average => $average,
475                 count   => $ratings->count
476             },
477         );
478     }
479     catch {
480         $c->unhandled_exception($_);
481     };
482 }
483
484 =head3 add
485
486 Controller function that handles creating a biblio object
487
488 =cut
489
490 sub add {
491     my $c = shift->openapi->valid_input or return;
492
493     try {
494         my $body = $c->validation->param('Body');
495
496         my $flavour = $c->validation->param('x-marc-schema');
497         $flavour = C4::Context->preference('marcflavour') unless $flavour;
498
499         my $record;
500
501         my $frameworkcode = $c->validation->param('x-framework-id');
502         if ( $c->req->headers->content_type =~ m/application\/marcxml\+xml/ ) {
503             $record = MARC::Record->new_from_xml( $body, 'UTF-8', $flavour );
504         } elsif ( $c->req->headers->content_type =~ m/application\/marc-in-json/ ) {
505             $record = MARC::Record->new_from_mij_structure( $body );
506         } elsif ( $c->req->headers->content_type =~ m/application\/marc/ ) {
507             $record = MARC::Record->new_from_usmarc( $body );
508         } else {
509             return $c->render(
510                 status  => 406,
511                 openapi => [
512                     "application/marcxml+xml",
513                     "application/marc-in-json",
514                     "application/marc"
515                 ]
516             );
517         }
518
519         my ( $duplicatebiblionumber, $duplicatetitle );
520             ( $duplicatebiblionumber, $duplicatetitle ) = FindDuplicate($record);
521
522         my $confirm_not_duplicate = $c->validation->param('x-confirm-not-duplicate');
523
524         return $c->render(
525             status  => 400,
526             openapi => {
527                 error => "Duplicate biblio $duplicatebiblionumber"
528             }
529         ) unless !$duplicatebiblionumber || $confirm_not_duplicate;
530
531         my ( $biblionumber, $oldbibitemnum );
532             ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
533
534         $c->render(
535             status  => 200,
536             openapi => { id => $biblionumber }
537         );
538     }
539     catch {
540         $c->unhandled_exception($_);
541     };
542 }
543
544 =head3 update
545
546 Controller function that handles modifying an biblio object
547
548 =cut
549
550 sub update {
551     my $c = shift->openapi->valid_input or return;
552
553     my $biblionumber = $c->validation->param('biblio_id');
554     my $biblio = Koha::Biblios->find( $biblionumber );
555
556     if ( not defined $biblio ) {
557         return $c->render(
558             status  => 404,
559             openapi => { error => "Object not found" }
560         );
561     }
562
563     try {
564         my $body = $c->validation->param('Body');
565
566         my $flavour = $c->validation->param('x-marc-schema');
567         $flavour = C4::Context->preference('marcflavour') unless $flavour;
568
569         my $record;
570         my $frameworkcode = $c->validation->param('x-framework-id') || $biblio->frameworkcode;
571         if ( $c->req->headers->content_type =~ m/application\/marcxml\+xml/ ) {
572             $record = MARC::Record->new_from_xml( $body, 'UTF-8', $flavour );
573         } elsif ( $c->req->headers->content_type =~ m/application\/marc-in-json/ ) {
574             $record = MARC::Record->new_from_mij_structure( $body );
575         } elsif ( $c->req->headers->content_type =~ m/application\/marc/ ) {
576             $record = MARC::Record->new_from_usmarc( $body );
577         } else {
578             return $c->render(
579                 status  => 406,
580                 openapi => [
581                     "application/json",
582                     "application/marcxml+xml",
583                     "application/marc-in-json",
584                     "application/marc"
585                 ]
586             );
587         }
588
589         ModBiblio( $record, $biblionumber, $frameworkcode );
590
591         $c->render(
592             status  => 200,
593             openapi => { id => $biblionumber }
594         );
595     }
596     catch {
597         $c->unhandled_exception($_);
598     };
599 }
600
601 =head3 list
602
603 Controller function that handles retrieving a single biblio object
604
605 =cut
606
607 sub list {
608     my $c = shift->openapi->valid_input or return;
609
610     my $attributes;
611     $attributes =
612       { prefetch => ['metadata'] }    # don't prefetch metadata if not needed
613       unless $c->req->headers->accept =~ m/application\/json/;
614
615     my $biblios = $c->objects->search_rs( Koha::Biblios->new );
616
617     return try {
618
619         if ( $c->req->headers->accept =~ m/application\/json(;.*)?$/ ) {
620             return $c->render(
621                 status => 200,
622                 json   => $c->objects->to_api( $biblios ),
623             );
624         }
625         elsif (
626             $c->req->headers->accept =~ m/application\/marcxml\+xml(;.*)?$/ )
627         {
628             $c->res->headers->add( 'Content-Type', 'application/marcxml+xml' );
629             return $c->render(
630                 status => 200,
631                 text   => $biblios->print_collection('marcxml')
632             );
633         }
634         elsif (
635             $c->req->headers->accept =~ m/application\/marc-in-json(;.*)?$/ )
636         {
637             $c->res->headers->add( 'Content-Type', 'application/marc-in-json' );
638             return $c->render(
639                 status => 200,
640                 data   => $biblios->print_collection('mij')
641             );
642         }
643         elsif ( $c->req->headers->accept =~ m/application\/marc(;.*)?$/ ) {
644             $c->res->headers->add( 'Content-Type', 'application/marc' );
645             return $c->render(
646                 status => 200,
647                 text   => $biblios->print_collection('marc')
648             );
649         }
650         elsif ( $c->req->headers->accept =~ m/text\/plain(;.*)?$/ ) {
651             return $c->render(
652                 status => 200,
653                 text   => $biblios->print_collection('txt')
654             );
655         }
656         else {
657             return $c->render(
658                 status  => 406,
659                 openapi => [
660                     "application/json",         "application/marcxml+xml",
661                     "application/marc-in-json", "application/marc",
662                     "text/plain"
663                 ]
664             );
665         }
666     }
667     catch {
668         $c->unhandled_exception($_);
669     };
670 }
671
672 1;