]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Container.pm
537f35fba02bd49cb6d6a75e4309269caadbca97
[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
135     return new OpenILS::Event("BAD_PARAMS") unless
136         $note->class_name =~ /bucket_item_note$/;
137
138     my $e = new_editor(authtoken => $auth, xact => 1);
139     return $e->die_event unless $e->checkauth;
140
141     my $meat = $ctypes{$class} . "_item_note";
142     my $meth = "retrieve_$meat";
143
144     my $item_meat = $ctypes{$class} . "_item";
145     my $item_meth = "retrieve_$item_meat";
146
147     my $nhint = $Fieldmapper::fieldmap->{$note->class_name}->{hint};
148     (my $ihint = $nhint) =~ s/n$//og;
149
150     my ($db_note, $item);
151
152     if ($note->isnew) {
153         $db_note = $note;
154
155         $item = $e->$item_meth([
156             $note->item, {
157                 flesh => 1, flesh_fields => {$ihint => ["bucket"]}
158             }
159         ]) or return $e->die_event;
160     } else {
161         $db_note = $e->$meth([
162             $note->id, {
163                 flesh => 2,
164                 flesh_fields => {
165                     $nhint => ['item'],
166                     $ihint => ['bucket']
167                 }
168             }
169         ]) or return $e->die_event;
170
171         $item = $db_note->item;
172     }
173
174     if($item->bucket->owner ne $e->requestor->id) {
175         return $e->die_event unless $e->allowed("UPDATE_CONTAINER");
176     }
177
178     $meth = 'create_' . $meat if $note->isnew;
179     $meth = 'update_' . $meat if $note->ischanged;
180     $meth = 'delete_' . $meat if $note->isdeleted;
181     return $e->die_event unless $e->$meth($note);
182     $e->commit;
183 }
184
185
186 __PACKAGE__->register_method(
187         method  => "bucket_retrieve_class",
188         api_name        => "open-ils.actor.container.retrieve_by_class",
189         argc            => 3, 
190         authoritative   => 1, 
191         notes           => <<"  NOTES");
192                 Retrieves all un-fleshed buckets by class assigned to given user 
193                 PARAMS(authtoken, bucketOwnerId, class [, type])
194                 class can be one of "biblio", "callnumber", "copy", "user"
195                 The optional "type" parameter allows you to limit the search by 
196                 bucket type.  
197                 If bucketOwnerId is not defined, the authtoken is used as the
198                 bucket owner.
199                 If requestor ID is different than bucketOwnerId, requestor must have
200                 VIEW_CONTAINER permissions.
201         NOTES
202
203 sub bucket_retrieve_class {
204         my( $self, $client, $authtoken, $userid, $class, $type ) = @_;
205
206         my( $staff, $user, $evt ) = 
207                 $apputils->checkses_requestor( $authtoken, $userid, 'VIEW_CONTAINER' );
208         return $evt if $evt;
209
210         $logger->debug("User " . $staff->id . 
211                 " retrieving buckets for user $userid [class=$class, type=$type]");
212
213         my $meth = $types{$class} . ".search.atomic";
214         my $buckets;
215
216         if( $type ) {
217                 $buckets = $apputils->simplereq( $svc, 
218                         $meth, { owner => $userid, btype => $type } );
219         } else {
220                 $logger->debug("Grabbing buckets by class $class: $svc : $meth :  {owner => $userid}");
221                 $buckets = $apputils->simplereq( $svc, $meth, { owner => $userid } );
222         }
223
224         return _sort_buckets($buckets);
225 }
226
227 __PACKAGE__->register_method(
228         method  => "bucket_create",
229         api_name        => "open-ils.actor.container.create",
230         notes           => <<"  NOTES");
231                 Creates a new bucket object.  If requestor is different from
232                 bucketOwner, requestor needs CREATE_CONTAINER permissions
233                 PARAMS(authtoken, bucketObject);
234                 Returns the new bucket object
235         NOTES
236
237 sub bucket_create {
238         my( $self, $client, $authtoken, $class, $bucket ) = @_;
239
240         my $e = new_editor(xact=>1, authtoken=>$authtoken);
241         return $e->event unless $e->checkauth;
242
243         if( $bucket->owner ne $e->requestor->id ) {
244                 return $e->event unless
245                         $e->allowed('CREATE_CONTAINER');
246
247         } else {
248                 return $e->event unless
249                         $e->allowed('CREATE_MY_CONTAINER');
250         }
251                 
252         $bucket->clear_id;
253
254     my $evt = OpenILS::Event->new('CONTAINER_EXISTS', 
255         payload => [$class, $bucket->owner, $bucket->btype, $bucket->name]);
256     my $search = {name => $bucket->name, owner => $bucket->owner, btype => $bucket->btype};
257
258         my $obj;
259         if( $class eq 'copy' ) {
260         return $evt if $e->search_container_copy_bucket($search)->[0];
261                 return $e->event unless
262                         $obj = $e->create_container_copy_bucket($bucket);
263         }
264
265         if( $class eq 'callnumber' ) {
266         return $evt if $e->search_container_call_number_bucket($search)->[0];
267                 return $e->event unless
268                         $obj = $e->create_container_call_number_bucket($bucket);
269         }
270
271         if( $class eq 'biblio' ) {
272         return $evt if $e->search_container_biblio_record_entry_bucket($search)->[0];
273                 return $e->event unless
274                         $obj = $e->create_container_biblio_record_entry_bucket($bucket);
275         }
276
277         if( $class eq 'user') {
278         return $evt if $e->search_container_user_bucket($search)->[0];
279                 return $e->event unless
280                         $obj = $e->create_container_user_bucket($bucket);
281         }
282
283         $e->commit;
284         return $obj->id;
285 }
286
287
288 __PACKAGE__->register_method(
289         method  => "item_create",
290         api_name        => "open-ils.actor.container.item.create",
291     signature => {
292         desc => q/
293             Adds one or more items to an existing container
294         /,
295         params => [
296                     {desc => 'Authentication token', type => 'string'},
297                     {desc => 'Container class.  Can be "copy", "callnumber", "biblio", or "user"', type => 'string'},
298                     {desc => 'Item or items.  Can either be a single container item object, or an array of them', type => 'object'},
299         ],
300         return => {
301             desc => 'The ID of the newly created item(s).  In batch context, an array of IDs is returned'
302         }
303     }
304 );
305
306
307 sub item_create {
308         my( $self, $client, $authtoken, $class, $item ) = @_;
309
310         my $e = new_editor(xact=>1, authtoken=>$authtoken);
311         return $e->die_event unless $e->checkauth;
312     my $items = (ref $item eq 'ARRAY') ? $item : [$item];
313
314         my ( $bucket, $evt ) = $apputils->fetch_container_e($e, $item->bucket, $class);
315         return $evt if $evt;
316
317         if( $bucket->owner ne $e->requestor->id ) {
318                 return $e->die_event unless
319                         $e->allowed('CREATE_CONTAINER_ITEM');
320
321         } else {
322 #               return $e->event unless
323 #                       $e->allowed('CREATE_CONTAINER_ITEM'); # new perm here?
324         }
325                 
326     for my $one_item (@$items) {
327
328         $one_item->clear_id;
329
330         my $stat;
331         if( $class eq 'copy' ) {
332             return $e->die_event unless
333                 $stat = $e->create_container_copy_bucket_item($one_item);
334         }
335
336         if( $class eq 'callnumber' ) {
337             return $e->die_event unless
338                 $stat = $e->create_container_call_number_bucket_item($one_item);
339         }
340
341         if( $class eq 'biblio' ) {
342             return $e->die_event unless
343                 $stat = $e->create_container_biblio_record_entry_bucket_item($one_item);
344         }
345
346         if( $class eq 'user') {
347             return $e->die_event unless
348                 $stat = $e->create_container_user_bucket_item($one_item);
349         }
350     }
351
352         $e->commit;
353
354     # CStoreEeditor inserts the id (pkey) on newly created objects
355     return [ map { $_->id } @$items ] if ref $item eq 'ARRAY';
356         return $item->id; 
357 }
358
359
360
361 __PACKAGE__->register_method(
362         method  => "item_delete",
363         api_name        => "open-ils.actor.container.item.delete",
364         notes           => <<"  NOTES");
365                 PARAMS(authtoken, class, itemId)
366         NOTES
367
368 sub item_delete {
369         my( $self, $client, $authtoken, $class, $itemid ) = @_;
370
371         my $e = new_editor(xact=>1, authtoken=>$authtoken);
372         return $e->event unless $e->checkauth;
373
374         my $ret = __item_delete($e, $class, $itemid);
375         $e->commit unless $U->event_code($ret);
376         return $ret;
377 }
378
379 sub __item_delete {
380         my( $e, $class, $itemid ) = @_;
381         my( $bucket, $item, $evt);
382
383         ( $item, $evt ) = $U->fetch_container_item_e( $e, $itemid, $class );
384         return $evt if $evt;
385
386         ( $bucket, $evt ) = $U->fetch_container_e($e, $item->bucket, $class);
387         return $evt if $evt;
388
389         if( $bucket->owner ne $e->requestor->id ) {
390       my $owner = $e->retrieve_actor_user($bucket->owner)
391          or return $e->die_event;
392                 return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM', $owner->home_ou);
393         }
394
395         my $stat;
396         if( $class eq 'copy' ) {
397         for my $note (@{$e->search_container_copy_bucket_item_note({item => $item->id})}) {
398             return $e->event unless 
399                 $e->delete_container_copy_bucket_item_note($note);
400         }
401                 return $e->event unless
402                         $stat = $e->delete_container_copy_bucket_item($item);
403         }
404
405         if( $class eq 'callnumber' ) {
406         for my $note (@{$e->search_container_call_number_bucket_item_note({item => $item->id})}) {
407             return $e->event unless 
408                 $e->delete_container_call_number_bucket_item_note($note);
409         }
410                 return $e->event unless
411                         $stat = $e->delete_container_call_number_bucket_item($item);
412         }
413
414         if( $class eq 'biblio' ) {
415         for my $note (@{$e->search_container_biblio_record_entry_bucket_item_note({item => $item->id})}) {
416             return $e->event unless 
417                 $e->delete_container_biblio_record_entry_bucket_item_note($note);
418         }
419                 return $e->event unless
420                         $stat = $e->delete_container_biblio_record_entry_bucket_item($item);
421         }
422
423         if( $class eq 'user') {
424         for my $note (@{$e->search_container_user_bucket_item_note({item => $item->id})}) {
425             return $e->event unless 
426                 $e->delete_container_user_bucket_item_note($note);
427         }
428                 return $e->event unless
429                         $stat = $e->delete_container_user_bucket_item($item);
430         }
431
432         return $stat;
433 }
434
435
436 __PACKAGE__->register_method(
437         method  => 'full_delete',
438         api_name        => 'open-ils.actor.container.full_delete',
439         notes           => "Complety removes a container including all attached items",
440 );      
441
442 sub full_delete {
443         my( $self, $client, $authtoken, $class, $containerId ) = @_;
444         my( $container, $evt);
445
446         my $e = new_editor(xact=>1, authtoken=>$authtoken);
447         return $e->event unless $e->checkauth;
448
449         ( $container, $evt ) = $apputils->fetch_container_e($e, $containerId, $class);
450         return $evt if $evt;
451
452         if( $container->owner ne $e->requestor->id ) {
453       my $owner = $e->retrieve_actor_user($container->owner)
454          or return $e->die_event;
455                 return $e->event unless $e->allowed('DELETE_CONTAINER', $owner->home_ou);
456         }
457
458         my $items; 
459
460         my @s = ({bucket => $containerId}, {idlist=>1});
461
462         if( $class eq 'copy' ) {
463                 $items = $e->search_container_copy_bucket_item(@s);
464         }
465
466         if( $class eq 'callnumber' ) {
467                 $items = $e->search_container_call_number_bucket_item(@s);
468         }
469
470         if( $class eq 'biblio' ) {
471                 $items = $e->search_container_biblio_record_entry_bucket_item(@s);
472         }
473
474         if( $class eq 'user') {
475                 $items = $e->search_container_user_bucket_item(@s);
476         }
477
478         __item_delete($e, $class, $_) for @$items;
479
480         my $stat;
481         if( $class eq 'copy' ) {
482                 return $e->event unless
483                         $stat = $e->delete_container_copy_bucket($container);
484         }
485
486         if( $class eq 'callnumber' ) {
487                 return $e->event unless
488                         $stat = $e->delete_container_call_number_bucket($container);
489         }
490
491         if( $class eq 'biblio' ) {
492                 return $e->event unless
493                         $stat = $e->delete_container_biblio_record_entry_bucket($container);
494         }
495
496         if( $class eq 'user') {
497                 return $e->event unless
498                         $stat = $e->delete_container_user_bucket($container);
499         }
500
501         $e->commit;
502         return $stat;
503 }
504
505 __PACKAGE__->register_method(
506         method          => 'container_update',
507         api_name                => 'open-ils.actor.container.update',
508         signature       => q/
509                 Updates the given container item.
510                 @param authtoken The login session key
511                 @param class The container class
512                 @param container The container item
513                 @return true on success, 0 on no update, Event on error
514                 /
515 );
516
517 sub container_update {
518         my( $self, $conn, $authtoken, $class, $container )  = @_;
519
520         my $e = new_editor(xact=>1, authtoken=>$authtoken);
521         return $e->event unless $e->checkauth;
522
523         my ( $dbcontainer, $evt ) = $U->fetch_container_e($e, $container->id, $class);
524         return $evt if $evt;
525
526         if( $e->requestor->id ne $container->owner ) {
527                 return $e->event unless $e->allowed('UPDATE_CONTAINER');
528         }
529
530         my $stat;
531         if( $class eq 'copy' ) {
532                 return $e->event unless
533                         $stat = $e->update_container_copy_bucket($container);
534         }
535
536         if( $class eq 'callnumber' ) {
537                 return $e->event unless
538                         $stat = $e->update_container_call_number_bucket($container);
539         }
540
541         if( $class eq 'biblio' ) {
542                 return $e->event unless
543                         $stat = $e->update_container_biblio_record_entry_bucket($container);
544         }
545
546         if( $class eq 'user') {
547                 return $e->event unless
548                         $stat = $e->update_container_user_bucket($container);
549         }
550
551         $e->commit;
552         return $stat;
553 }
554
555
556
557 __PACKAGE__->register_method(
558         method  => "anon_cache",
559         api_name        => "open-ils.actor.anon_cache.set_value",
560     signature => {
561         desc => q/
562             Sets a value in the anon web cache.  If the session key is
563             undefined, one will be automatically generated.
564         /,
565         params => [
566                     {desc => 'Session key', type => 'string'},
567             {
568                 desc => q/Field name.  The name of the field in this cache session whose value to set/, 
569                 type => 'string'
570             },
571             {
572                 desc => q/The cached value.  This can be any type of object (hash, array, string, etc.)/,
573                 type => 'any'
574             },
575         ],
576         return => {
577             desc => 'session key on success, undef on error',
578             type => 'string'
579         }
580     }
581 );
582
583 __PACKAGE__->register_method(
584         method  => "anon_cache",
585         api_name        => "open-ils.actor.anon_cache.get_value",
586     signature => {
587         desc => q/
588             Returns the cached data at the specified field within the specified cache session.
589         /,
590         params => [
591                     {desc => 'Session key', type => 'string'},
592             {
593                 desc => q/Field name.  The name of the field in this cache session whose value to set/, 
594                 type => 'string'
595             },
596         ],
597         return => {
598             desc => 'cached value on success, undef on error',
599             type => 'any'
600         }
601     }
602 );
603
604 __PACKAGE__->register_method(
605         method  => "anon_cache",
606         api_name        => "open-ils.actor.anon_cache.delete_session",
607     signature => {
608         desc => q/
609             Deletes a cache session.
610         /,
611         params => [
612                     {desc => 'Session key', type => 'string'},
613         ],
614         return => {
615             desc => 'Session key',
616             type => 'string'
617         }
618     }
619 );
620
621 sub anon_cache {
622     my($self, $conn, $ses_key, $field_key, $value) = @_;
623
624     my $sc = OpenSRF::Utils::SettingsClient->new;
625         my $cache = OpenSRF::Utils::Cache->new('anon');
626     my $cache_timeout = $sc->config_value(cache => anon => 'max_cache_time') || 1800; # 30 minutes
627     my $cache_size = $sc->config_value(cache => anon => 'max_cache_size') || 102400; # 100k
628
629     if($self->api_name =~ /delete_session/) {
630
631        return $cache->delete_cache($ses_key); 
632
633     }  elsif( $self->api_name =~ /set_value/ ) {
634
635         $ses_key = md5_hex(time . rand($$)) unless $ses_key;
636         my $blob = $cache->get_cache($ses_key) || {};
637         $blob->{$field_key} = $value;
638         return undef if 
639             length(OpenSRF::Utils::JSON->perl2JSON($blob)) > $cache_size; # bytes, characters, whatever ;)
640         $cache->put_cache($ses_key, $blob, $cache_timeout);
641         return $ses_key;
642
643     } else {
644
645         my $blob = $cache->get_cache($ses_key) or return undef;
646         return $blob if (!defined($field_key));
647         return $blob->{$field_key};
648     }
649 }
650
651
652
653 1;
654
655