Bug 33227: Remove invalid spec and adjust the code accordingly
[srvgit] / 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 $headers = $c->req->headers;
495
496         my $flavour = $headers->header('x-marc-schema');
497         $flavour //= C4::Context->preference('marcflavour');
498
499         my $record;
500
501         my $frameworkcode = $headers->header('x-framework-id');
502         my $content_type  = $headers->content_type;
503
504         if ( $content_type =~ m/application\/marcxml\+xml/ ) {
505             $record = MARC::Record->new_from_xml( $c->req->body, 'UTF-8', $flavour );
506         }
507         elsif ( $content_type =~ m/application\/marc-in-json/ ) {
508             $record = MARC::Record->new_from_mij_structure( $c->req->json );
509         }
510         elsif ( $content_type =~ m/application\/marc/ ) {
511             $record = MARC::Record->new_from_usmarc( $c->req->body );
512         }
513         else {
514             return $c->render(
515                 status  => 406,
516                 openapi => [
517                     "application/marcxml+xml",
518                     "application/marc-in-json",
519                     "application/marc"
520                 ]
521             );
522         }
523
524         my ( $duplicatebiblionumber, $duplicatetitle );
525             ( $duplicatebiblionumber, $duplicatetitle ) = FindDuplicate($record);
526
527         my $confirm_not_duplicate = $headers->header('x-confirm-not-duplicate');
528
529         return $c->render(
530             status  => 400,
531             openapi => {
532                 error => "Duplicate biblio $duplicatebiblionumber",
533             }
534         ) unless !$duplicatebiblionumber || $confirm_not_duplicate;
535
536         my ( $biblionumber, $oldbibitemnum );
537             ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
538
539         $c->render(
540             status  => 200,
541             openapi => { id => $biblionumber }
542         );
543     }
544     catch {
545         $c->unhandled_exception($_);
546     };
547 }
548
549 =head3 update
550
551 Controller function that handles modifying an biblio object
552
553 =cut
554
555 sub update {
556     my $c = shift->openapi->valid_input or return;
557
558     my $biblio_id = $c->param('biblio_id');
559     my $biblio    = Koha::Biblios->find($biblio_id);
560
561     if ( ! defined $biblio ) {
562         return $c->render(
563             status  => 404,
564             openapi => { error => "Object not found" }
565         );
566     }
567
568     try {
569         my $headers = $c->req->headers;
570
571         my $flavour = $headers->header('x-marc-schema');
572         $flavour //= C4::Context->preference('marcflavour');
573
574         my $frameworkcode = $headers->header('x-framework-id') || $biblio->frameworkcode;
575
576         my $content_type = $headers->content_type;
577
578         my $record;
579
580         if ( $content_type =~ m/application\/marcxml\+xml/ ) {
581             $record = MARC::Record->new_from_xml( $c->req->body, 'UTF-8', $flavour );
582         }
583         elsif ( $content_type =~ m/application\/marc-in-json/ ) {
584             $record = MARC::Record->new_from_mij_structure( $c->req->json );
585         }
586         elsif ( $content_type =~ m/application\/marc/ ) {
587             $record = MARC::Record->new_from_usmarc( $c->req->body );
588         }
589         else {
590             return $c->render(
591                 status  => 406,
592                 openapi => [
593                     "application/json",
594                     "application/marcxml+xml",
595                     "application/marc-in-json",
596                     "application/marc"
597                 ]
598             );
599         }
600
601         ModBiblio( $record, $biblio_id, $frameworkcode );
602
603         $c->render(
604             status  => 200,
605             openapi => { id => $biblio_id }
606         );
607     }
608     catch {
609         $c->unhandled_exception($_);
610     };
611 }
612
613 =head3 list
614
615 Controller function that handles retrieving a single biblio object
616
617 =cut
618
619 sub list {
620     my $c = shift->openapi->valid_input or return;
621
622     my $attributes;
623     $attributes =
624       { prefetch => ['metadata'] }    # don't prefetch metadata if not needed
625       unless $c->req->headers->accept =~ m/application\/json/;
626
627     my $biblios = $c->objects->search_rs( Koha::Biblios->new );
628
629     return try {
630
631         if ( $c->req->headers->accept =~ m/application\/json(;.*)?$/ ) {
632             return $c->render(
633                 status => 200,
634                 json   => $c->objects->to_api( $biblios ),
635             );
636         }
637         elsif (
638             $c->req->headers->accept =~ m/application\/marcxml\+xml(;.*)?$/ )
639         {
640             $c->res->headers->add( 'Content-Type', 'application/marcxml+xml' );
641             return $c->render(
642                 status => 200,
643                 text   => $biblios->print_collection('marcxml')
644             );
645         }
646         elsif (
647             $c->req->headers->accept =~ m/application\/marc-in-json(;.*)?$/ )
648         {
649             $c->res->headers->add( 'Content-Type', 'application/marc-in-json' );
650             return $c->render(
651                 status => 200,
652                 data   => $biblios->print_collection('mij')
653             );
654         }
655         elsif ( $c->req->headers->accept =~ m/application\/marc(;.*)?$/ ) {
656             $c->res->headers->add( 'Content-Type', 'application/marc' );
657             return $c->render(
658                 status => 200,
659                 text   => $biblios->print_collection('marc')
660             );
661         }
662         elsif ( $c->req->headers->accept =~ m/text\/plain(;.*)?$/ ) {
663             return $c->render(
664                 status => 200,
665                 text   => $biblios->print_collection('txt')
666             );
667         }
668         else {
669             return $c->render(
670                 status  => 406,
671                 openapi => [
672                     "application/json",         "application/marcxml+xml",
673                     "application/marc-in-json", "application/marc",
674                     "text/plain"
675                 ]
676             );
677         }
678     }
679     catch {
680         $c->unhandled_exception($_);
681     };
682 }
683
684 1;