]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Container.pm
Merge branch 'master' of git://git.evergreen-ils.org/Evergreen into ttopac
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Actor / Container.pm
1 package OpenILS::Application::Actor::Container;
2 use base 'OpenILS::Application';
3 use strict; use warnings;
4 use OpenILS::Application::AppUtils;
5 use OpenILS::Perm;
6 use Data::Dumper;
7 use OpenSRF::EX qw(:try);
8 use OpenILS::Utils::Fieldmapper;
9 use OpenILS::Utils::CStoreEditor qw/:funcs/;
10 use OpenSRF::Utils::SettingsClient;
11 use OpenSRF::Utils::Cache;
12 use Digest::MD5 qw(md5_hex);
13 use OpenSRF::Utils::JSON;
14
15 my $apputils = "OpenILS::Application::AppUtils";
16 my $U = $apputils;
17 my $logger = "OpenSRF::Utils::Logger";
18
19 sub initialize { return 1; }
20
21 my $svc = 'open-ils.cstore';
22 my $meth = 'open-ils.cstore.direct.container';
23 my %types;
24 my %ctypes;
25 $types{'biblio'} = "$meth.biblio_record_entry_bucket";
26 $types{'callnumber'} = "$meth.call_number_bucket";
27 $types{'copy'} = "$meth.copy_bucket";
28 $types{'user'} = "$meth.user_bucket";
29 $ctypes{'biblio'} = "container_biblio_record_entry_bucket";
30 $ctypes{'callnumber'} = "container_call_number_bucket";
31 $ctypes{'copy'} = "container_copy_bucket";
32 $ctypes{'user'} = "container_user_bucket";
33 my $event;
34
35 sub _sort_buckets {
36         my $buckets = shift;
37         return $buckets unless ($buckets && $buckets->[0]);
38         return [ sort { $a->name cmp $b->name } @$buckets ];
39 }
40
41 __PACKAGE__->register_method(
42         method  => "bucket_retrieve_all",
43         api_name        => "open-ils.actor.container.all.retrieve_by_user",
44     authoritative => 1,
45         notes           => <<"  NOTES");
46                 Retrieves all un-fleshed buckets assigned to given user 
47                 PARAMS(authtoken, bucketOwnerId)
48                 If requestor ID is different than bucketOwnerId, requestor must have
49                 VIEW_CONTAINER permissions.
50         NOTES
51
52 sub bucket_retrieve_all {
53         my($self, $client, $auth, $user_id) = @_;
54     my $e = new_editor(authtoken => $auth);
55     return $e->event unless $e->checkauth;
56
57     if($e->requestor->id ne $user_id) {
58         return $e->event unless $e->allowed('VIEW_CONTAINER');
59     }
60     
61         my %buckets;
62     for my $type (keys %ctypes) {
63         my $meth = "search_" . $ctypes{$type};
64             $buckets{$type} = $e->$meth({owner => $user_id});
65     }
66
67         return \%buckets;
68 }
69
70 __PACKAGE__->register_method(
71         method  => "bucket_flesh",
72         api_name        => "open-ils.actor.container.flesh",
73     authoritative => 1,
74         argc            => 3, 
75 );
76
77 __PACKAGE__->register_method(
78         method  => "bucket_flesh_pub",
79         api_name        => "open-ils.actor.container.public.flesh",
80         argc            => 3, 
81 );
82
83 sub bucket_flesh {
84         my($self, $conn, $auth, $class, $bucket_id) = @_;
85     my $e = new_editor(authtoken => $auth);
86     return $e->event unless $e->checkauth;
87     return _bucket_flesh($self, $conn, $e, $class, $bucket_id);
88 }
89
90 sub bucket_flesh_pub {
91     my($self, $conn, $class, $bucket_id) = @_;
92     my $e = new_editor();
93     return _bucket_flesh($self, $conn, $e, $class, $bucket_id);
94 }
95
96 sub _bucket_flesh {
97         my($self, $conn, $e, $class, $bucket_id) = @_;
98         my $meth = 'retrieve_' . $ctypes{$class};
99     my $bkt = $e->$meth($bucket_id) or return $e->event;
100
101         unless($U->is_true($bkt->pub)) {
102         return undef if $self->api_name =~ /public/;
103         unless($bkt->owner eq $e->requestor->id) {
104             my $owner = $e->retrieve_actor_user($bkt->owner)
105                 or return $e->die_event;
106             return $e->event unless $e->allowed('VIEW_CONTAINER', $owner->home_ou);
107         }
108         }
109
110     my $fmclass = $bkt->class_name . "i";
111     $meth = 'search_' . $ctypes{$class} . '_item';
112         $bkt->items(
113         $e->$meth(
114             {bucket => $bucket_id}, 
115             {   order_by => {$fmclass => "pos"},
116                 flesh => 1, 
117                 flesh_fields => {$fmclass => ['notes']}
118             }
119         )
120     );
121
122         return $bkt;
123 }
124
125
126 __PACKAGE__->register_method(
127         method  => "item_note_cud",
128         api_name        => "open-ils.actor.container.item_note.cud",
129 );
130
131
132 sub item_note_cud {
133     my($self, $conn, $auth, $class, $note) = @_;
134     my $e = new_editor(authtoken => $auth, xact => 1);
135     return $e->die_event unless $e->checkauth;
136
137     my $meth = 'retrieve_' . $ctypes{$class};
138     my $nclass = $note->class_name;
139     (my $iclass = $nclass) =~ s/n$//og;
140
141     my $db_note = $e->$meth($note->id, {
142         flesh => 2,
143         flesh_fields => {
144             $nclass => ['item'],
145             $iclass => ['bucket']
146         }
147     });
148
149     if($db_note->item->bucket->owner ne $e->requestor->id) {
150         return $e->die_event unless 
151             $e->allowed('UPDATE_CONTAINER', $db_note->item->bucket);
152     }
153
154     $meth = 'create_' . $ctypes{$class} if $note->isnew;
155     $meth = 'update_' . $ctypes{$class} if $note->ischanged;
156     $meth = 'delete_' . $ctypes{$class} if $note->isdeleted;
157     return $e->die_event unless $e->$meth($note);
158     $e->commit;
159 }
160
161
162 __PACKAGE__->register_method(
163         method  => "bucket_retrieve_class",
164         api_name        => "open-ils.actor.container.retrieve_by_class",
165         argc            => 3, 
166         authoritative   => 1, 
167         notes           => <<"  NOTES");
168                 Retrieves all un-fleshed buckets by class assigned to given user 
169                 PARAMS(authtoken, bucketOwnerId, class [, type])
170                 class can be one of "biblio", "callnumber", "copy", "user"
171                 The optional "type" parameter allows you to limit the search by 
172                 bucket type.  
173                 If bucketOwnerId is not defined, the authtoken is used as the
174                 bucket owner.
175                 If requestor ID is different than bucketOwnerId, requestor must have
176                 VIEW_CONTAINER permissions.
177         NOTES
178
179 sub bucket_retrieve_class {
180         my( $self, $client, $authtoken, $userid, $class, $type ) = @_;
181
182         my( $staff, $user, $evt ) = 
183                 $apputils->checkses_requestor( $authtoken, $userid, 'VIEW_CONTAINER' );
184         return $evt if $evt;
185
186         $logger->debug("User " . $staff->id . 
187                 " retrieving buckets for user $userid [class=$class, type=$type]");
188
189         my $meth = $types{$class} . ".search.atomic";
190         my $buckets;
191
192         if( $type ) {
193                 $buckets = $apputils->simplereq( $svc, 
194                         $meth, { owner => $userid, btype => $type } );
195         } else {
196                 $logger->debug("Grabbing buckets by class $class: $svc : $meth :  {owner => $userid}");
197                 $buckets = $apputils->simplereq( $svc, $meth, { owner => $userid } );
198         }
199
200         return _sort_buckets($buckets);
201 }
202
203 __PACKAGE__->register_method(
204         method  => "bucket_create",
205         api_name        => "open-ils.actor.container.create",
206         notes           => <<"  NOTES");
207                 Creates a new bucket object.  If requestor is different from
208                 bucketOwner, requestor needs CREATE_CONTAINER permissions
209                 PARAMS(authtoken, bucketObject);
210                 Returns the new bucket object
211         NOTES
212
213 sub bucket_create {
214         my( $self, $client, $authtoken, $class, $bucket ) = @_;
215
216         my $e = new_editor(xact=>1, authtoken=>$authtoken);
217         return $e->event unless $e->checkauth;
218
219         if( $bucket->owner ne $e->requestor->id ) {
220                 return $e->event unless
221                         $e->allowed('CREATE_CONTAINER');
222
223         } else {
224                 return $e->event unless
225                         $e->allowed('CREATE_MY_CONTAINER');
226         }
227                 
228         $bucket->clear_id;
229
230     my $evt = OpenILS::Event->new('CONTAINER_EXISTS', 
231         payload => [$class, $bucket->owner, $bucket->btype, $bucket->name]);
232     my $search = {name => $bucket->name, owner => $bucket->owner, btype => $bucket->btype};
233
234         my $obj;
235         if( $class eq 'copy' ) {
236         return $evt if $e->search_container_copy_bucket($search)->[0];
237                 return $e->event unless
238                         $obj = $e->create_container_copy_bucket($bucket);
239         }
240
241         if( $class eq 'callnumber' ) {
242         return $evt if $e->search_container_call_number_bucket($search)->[0];
243                 return $e->event unless
244                         $obj = $e->create_container_call_number_bucket($bucket);
245         }
246
247         if( $class eq 'biblio' ) {
248         return $evt if $e->search_container_biblio_record_entry_bucket($search)->[0];
249                 return $e->event unless
250                         $obj = $e->create_container_biblio_record_entry_bucket($bucket);
251         }
252
253         if( $class eq 'user') {
254         return $evt if $e->search_container_user_bucket($search)->[0];
255                 return $e->event unless
256                         $obj = $e->create_container_user_bucket($bucket);
257         }
258
259         $e->commit;
260         return $obj->id;
261 }
262
263
264 __PACKAGE__->register_method(
265         method  => "item_create",
266         api_name        => "open-ils.actor.container.item.create",
267     signature => {
268         desc => q/
269             Adds one or more items to an existing container
270         /,
271         params => [
272                     {desc => 'Authentication token', type => 'string'},
273                     {desc => 'Container class.  Can be "copy", "callnumber", "biblio", or "user"', type => 'string'},
274                     {desc => 'Item or items.  Can either be a single container item object, or an array of them', type => 'object'},
275         ],
276         return => {
277             desc => 'The ID of the newly created item(s).  In batch context, an array of IDs is returned'
278         }
279     }
280 );
281
282
283 sub item_create {
284         my( $self, $client, $authtoken, $class, $item ) = @_;
285
286         my $e = new_editor(xact=>1, authtoken=>$authtoken);
287         return $e->die_event unless $e->checkauth;
288     my $items = (ref $item eq 'ARRAY') ? $item : [$item];
289
290         my ( $bucket, $evt ) = $apputils->fetch_container_e($e, $item->bucket, $class);
291         return $evt if $evt;
292
293         if( $bucket->owner ne $e->requestor->id ) {
294                 return $e->die_event unless
295                         $e->allowed('CREATE_CONTAINER_ITEM');
296
297         } else {
298 #               return $e->event unless
299 #                       $e->allowed('CREATE_CONTAINER_ITEM'); # new perm here?
300         }
301                 
302     for my $one_item (@$items) {
303
304         $one_item->clear_id;
305
306         my $stat;
307         if( $class eq 'copy' ) {
308             return $e->die_event unless
309                 $stat = $e->create_container_copy_bucket_item($one_item);
310         }
311
312         if( $class eq 'callnumber' ) {
313             return $e->die_event unless
314                 $stat = $e->create_container_call_number_bucket_item($one_item);
315         }
316
317         if( $class eq 'biblio' ) {
318             return $e->die_event unless
319                 $stat = $e->create_container_biblio_record_entry_bucket_item($one_item);
320         }
321
322         if( $class eq 'user') {
323             return $e->die_event unless
324                 $stat = $e->create_container_user_bucket_item($one_item);
325         }
326     }
327
328         $e->commit;
329
330     # CStoreEeditor inserts the id (pkey) on newly created objects
331     return [ map { $_->id } @$items ] if ref $item eq 'ARRAY';
332         return $item->id; 
333 }
334
335
336
337 __PACKAGE__->register_method(
338         method  => "item_delete",
339         api_name        => "open-ils.actor.container.item.delete",
340         notes           => <<"  NOTES");
341                 PARAMS(authtoken, class, itemId)
342         NOTES
343
344 sub item_delete {
345         my( $self, $client, $authtoken, $class, $itemid ) = @_;
346
347         my $e = new_editor(xact=>1, authtoken=>$authtoken);
348         return $e->event unless $e->checkauth;
349
350         my $ret = __item_delete($e, $class, $itemid);
351         $e->commit unless $U->event_code($ret);
352         return $ret;
353 }
354
355 sub __item_delete {
356         my( $e, $class, $itemid ) = @_;
357         my( $bucket, $item, $evt);
358
359         ( $item, $evt ) = $U->fetch_container_item_e( $e, $itemid, $class );
360         return $evt if $evt;
361
362         ( $bucket, $evt ) = $U->fetch_container_e($e, $item->bucket, $class);
363         return $evt if $evt;
364
365         if( $bucket->owner ne $e->requestor->id ) {
366       my $owner = $e->retrieve_actor_user($bucket->owner)
367          or return $e->die_event;
368                 return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM', $owner->home_ou);
369         }
370
371         my $stat;
372         if( $class eq 'copy' ) {
373         for my $note (@{$e->search_container_copy_bucket_item_note({item => $item->id})}) {
374             return $e->event unless 
375                 $e->delete_container_copy_bucket_item_note($note);
376         }
377                 return $e->event unless
378                         $stat = $e->delete_container_copy_bucket_item($item);
379         }
380
381         if( $class eq 'callnumber' ) {
382         for my $note (@{$e->search_container_call_number_bucket_item_note({item => $item->id})}) {
383             return $e->event unless 
384                 $e->delete_container_call_number_bucket_item_note($note);
385         }
386                 return $e->event unless
387                         $stat = $e->delete_container_call_number_bucket_item($item);
388         }
389
390         if( $class eq 'biblio' ) {
391         for my $note (@{$e->search_container_biblio_record_entry_bucket_item_note({item => $item->id})}) {
392             return $e->event unless 
393                 $e->delete_container_biblio_record_entry_bucket_item_note($note);
394         }
395                 return $e->event unless
396                         $stat = $e->delete_container_biblio_record_entry_bucket_item($item);
397         }
398
399         if( $class eq 'user') {
400         for my $note (@{$e->search_container_user_bucket_item_note({item => $item->id})}) {
401             return $e->event unless 
402                 $e->delete_container_user_bucket_item_note($note);
403         }
404                 return $e->event unless
405                         $stat = $e->delete_container_user_bucket_item($item);
406         }
407
408         return $stat;
409 }
410
411
412 __PACKAGE__->register_method(
413         method  => 'full_delete',
414         api_name        => 'open-ils.actor.container.full_delete',
415         notes           => "Complety removes a container including all attached items",
416 );      
417
418 sub full_delete {
419         my( $self, $client, $authtoken, $class, $containerId ) = @_;
420         my( $container, $evt);
421
422         my $e = new_editor(xact=>1, authtoken=>$authtoken);
423         return $e->event unless $e->checkauth;
424
425         ( $container, $evt ) = $apputils->fetch_container_e($e, $containerId, $class);
426         return $evt if $evt;
427
428         if( $container->owner ne $e->requestor->id ) {
429       my $owner = $e->retrieve_actor_user($container->owner)
430          or return $e->die_event;
431                 return $e->event unless $e->allowed('DELETE_CONTAINER', $owner->home_ou);
432         }
433
434         my $items; 
435
436         my @s = ({bucket => $containerId}, {idlist=>1});
437
438         if( $class eq 'copy' ) {
439                 $items = $e->search_container_copy_bucket_item(@s);
440         }
441
442         if( $class eq 'callnumber' ) {
443                 $items = $e->search_container_call_number_bucket_item(@s);
444         }
445
446         if( $class eq 'biblio' ) {
447                 $items = $e->search_container_biblio_record_entry_bucket_item(@s);
448         }
449
450         if( $class eq 'user') {
451                 $items = $e->search_container_user_bucket_item(@s);
452         }
453
454         __item_delete($e, $class, $_) for @$items;
455
456         my $stat;
457         if( $class eq 'copy' ) {
458                 return $e->event unless
459                         $stat = $e->delete_container_copy_bucket($container);
460         }
461
462         if( $class eq 'callnumber' ) {
463                 return $e->event unless
464                         $stat = $e->delete_container_call_number_bucket($container);
465         }
466
467         if( $class eq 'biblio' ) {
468                 return $e->event unless
469                         $stat = $e->delete_container_biblio_record_entry_bucket($container);
470         }
471
472         if( $class eq 'user') {
473                 return $e->event unless
474                         $stat = $e->delete_container_user_bucket($container);
475         }
476
477         $e->commit;
478         return $stat;
479 }
480
481 __PACKAGE__->register_method(
482         method          => 'container_update',
483         api_name                => 'open-ils.actor.container.update',
484         signature       => q/
485                 Updates the given container item.
486                 @param authtoken The login session key
487                 @param class The container class
488                 @param container The container item
489                 @return true on success, 0 on no update, Event on error
490                 /
491 );
492
493 sub container_update {
494         my( $self, $conn, $authtoken, $class, $container )  = @_;
495
496         my $e = new_editor(xact=>1, authtoken=>$authtoken);
497         return $e->event unless $e->checkauth;
498
499         my ( $dbcontainer, $evt ) = $U->fetch_container_e($e, $container->id, $class);
500         return $evt if $evt;
501
502         if( $e->requestor->id ne $container->owner ) {
503                 return $e->event unless $e->allowed('UPDATE_CONTAINER');
504         }
505
506         my $stat;
507         if( $class eq 'copy' ) {
508                 return $e->event unless
509                         $stat = $e->update_container_copy_bucket($container);
510         }
511
512         if( $class eq 'callnumber' ) {
513                 return $e->event unless
514                         $stat = $e->update_container_call_number_bucket($container);
515         }
516
517         if( $class eq 'biblio' ) {
518                 return $e->event unless
519                         $stat = $e->update_container_biblio_record_entry_bucket($container);
520         }
521
522         if( $class eq 'user') {
523                 return $e->event unless
524                         $stat = $e->update_container_user_bucket($container);
525         }
526
527         $e->commit;
528         return $stat;
529 }
530
531
532
533 __PACKAGE__->register_method(
534         method  => "anon_cache",
535         api_name        => "open-ils.actor.anon_cache.set_value",
536     signature => {
537         desc => q/
538             Sets a value in the anon web cache.  If the session key is
539             undefined, one will be automatically generated.
540         /,
541         params => [
542                     {desc => 'Session key', type => 'string'},
543             {
544                 desc => q/Field name.  The name of the field in this cache session whose value to set/, 
545                 type => 'string'
546             },
547             {
548                 desc => q/The cached value.  This can be any type of object (hash, array, string, etc.)/,
549                 type => 'any'
550             },
551         ],
552         return => {
553             desc => 'session key on success, undef on error',
554             type => 'string'
555         }
556     }
557 );
558
559 __PACKAGE__->register_method(
560         method  => "anon_cache",
561         api_name        => "open-ils.actor.anon_cache.get_value",
562     signature => {
563         desc => q/
564             Returns the cached data at the specified field within the specified cache session.
565         /,
566         params => [
567                     {desc => 'Session key', type => 'string'},
568             {
569                 desc => q/Field name.  The name of the field in this cache session whose value to set/, 
570                 type => 'string'
571             },
572         ],
573         return => {
574             desc => 'cached value on success, undef on error',
575             type => 'any'
576         }
577     }
578 );
579
580 __PACKAGE__->register_method(
581         method  => "anon_cache",
582         api_name        => "open-ils.actor.anon_cache.delete_session",
583     signature => {
584         desc => q/
585             Deletes a cache session.
586         /,
587         params => [
588                     {desc => 'Session key', type => 'string'},
589         ],
590         return => {
591             desc => 'Session key',
592             type => 'string'
593         }
594     }
595 );
596
597 sub anon_cache {
598     my($self, $conn, $ses_key, $field_key, $value) = @_;
599
600     my $sc = OpenSRF::Utils::SettingsClient->new;
601         my $cache = OpenSRF::Utils::Cache->new('anon');
602     my $cache_timeout = $sc->config_value(cache => anon => 'max_cache_time') || 1800; # 30 minutes
603     my $cache_size = $sc->config_value(cache => anon => 'max_cache_size') || 102400; # 100k
604
605     if($self->api_name =~ /delete_session/) {
606
607        return $cache->delete_cache($ses_key); 
608
609     }  elsif( $self->api_name =~ /set_value/ ) {
610
611         $ses_key = md5_hex(time . rand($$)) unless $ses_key;
612         my $blob = $cache->get_cache($ses_key) || {};
613         $blob->{$field_key} = $value;
614         return undef if 
615             length(OpenSRF::Utils::JSON->perl2JSON($blob)) > $cache_size; # bytes, characters, whatever ;)
616         $cache->put_cache($ses_key, $blob, $cache_timeout);
617         return $ses_key;
618
619     } else {
620
621         my $blob = $cache->get_cache($ses_key) or return undef;
622         return $blob if (!defined($field_key));
623         return $blob->{$field_key};
624     }
625 }
626
627
628
629 1;
630
631