1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
6 $Data::Dumper::Indent = 0;
9 use Digest::MD5 qw(md5_hex);
11 use OpenSRF::EX qw(:try);
14 use OpenILS::Application::AppUtils;
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
22 use OpenSRF::Utils::Cache;
24 use OpenSRF::Utils::JSON;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31 use OpenILS::Application::Actor::UserGroups;
32 use OpenILS::Application::Actor::Friends;
33 use OpenILS::Application::Actor::Stage;
34 use OpenILS::Application::Actor::Settings;
36 use OpenILS::Utils::CStoreEditor qw/:funcs/;
37 use OpenILS::Utils::Penalty;
38 use OpenILS::Utils::BadContact;
39 use List::Util qw/max reduce/;
41 use UUID::Tiny qw/:std/;
44 OpenILS::Application::Actor::Container->initialize();
45 OpenILS::Application::Actor::UserGroups->initialize();
46 OpenILS::Application::Actor::ClosedDates->initialize();
49 my $apputils = "OpenILS::Application::AppUtils";
52 sub _d { warn "Patron:\n" . Dumper(shift()); }
55 my $set_user_settings;
59 #__PACKAGE__->register_method(
60 # method => "allowed_test",
61 # api_name => "open-ils.actor.allowed_test",
64 # my($self, $conn, $auth, $orgid, $permcode) = @_;
65 # my $e = new_editor(authtoken => $auth);
66 # return $e->die_event unless $e->checkauth;
70 # permcode => $permcode,
71 # result => $e->allowed($permcode, $orgid)
75 __PACKAGE__->register_method(
76 method => "update_user_setting",
77 api_name => "open-ils.actor.patron.settings.update",
79 sub update_user_setting {
80 my($self, $conn, $auth, $user_id, $settings) = @_;
81 my $e = new_editor(xact => 1, authtoken => $auth);
82 return $e->die_event unless $e->checkauth;
84 $user_id = $e->requestor->id unless defined $user_id;
86 unless($e->requestor->id == $user_id) {
87 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
88 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
91 for my $name (keys %$settings) {
92 my $val = $$settings{$name};
93 my $set = $e->search_actor_user_setting({usr => $user_id, name => $name})->[0];
96 $val = OpenSRF::Utils::JSON->perl2JSON($val);
99 $e->update_actor_user_setting($set) or return $e->die_event;
101 $set = Fieldmapper::actor::user_setting->new;
105 $e->create_actor_user_setting($set) or return $e->die_event;
108 $e->delete_actor_user_setting($set) or return $e->die_event;
117 __PACKAGE__->register_method(
118 method => "set_ou_settings",
119 api_name => "open-ils.actor.org_unit.settings.update",
121 desc => "Updates the value for a given org unit setting. The permission to update " .
122 "an org unit setting is either the UPDATE_ORG_UNIT_SETTING_ALL, or a specific " .
123 "permission specified in the update_perm column of the config.org_unit_setting_type " .
124 "table's row corresponding to the setting being changed." ,
126 {desc => 'Authentication token', type => 'string'},
127 {desc => 'Org unit ID', type => 'number'},
128 {desc => 'Hash of setting name-value pairs', type => 'object'}
130 return => {desc => '1 on success, Event on error'}
134 sub set_ou_settings {
135 my( $self, $client, $auth, $org_id, $settings ) = @_;
137 my $e = new_editor(authtoken => $auth, xact => 1);
138 return $e->die_event unless $e->checkauth;
140 my $all_allowed = $e->allowed("UPDATE_ORG_UNIT_SETTING_ALL", $org_id);
142 for my $name (keys %$settings) {
143 my $val = $$settings{$name};
145 my $type = $e->retrieve_config_org_unit_setting_type([
147 {flesh => 1, flesh_fields => {'coust' => ['update_perm']}}
148 ]) or return $e->die_event;
149 my $set = $e->search_actor_org_unit_setting({org_unit => $org_id, name => $name})->[0];
151 # If there is no relevant permission, the default assumption will
152 # be, "no, the caller cannot change that value."
153 return $e->die_event unless ($all_allowed ||
154 ($type->update_perm && $e->allowed($type->update_perm->code, $org_id)));
157 $val = OpenSRF::Utils::JSON->perl2JSON($val);
160 $e->update_actor_org_unit_setting($set) or return $e->die_event;
162 $set = Fieldmapper::actor::org_unit_setting->new;
163 $set->org_unit($org_id);
166 $e->create_actor_org_unit_setting($set) or return $e->die_event;
169 $e->delete_actor_org_unit_setting($set) or return $e->die_event;
177 __PACKAGE__->register_method(
178 method => "user_settings",
180 api_name => "open-ils.actor.patron.settings.retrieve",
183 my( $self, $client, $auth, $user_id, $setting ) = @_;
185 my $e = new_editor(authtoken => $auth);
186 return $e->event unless $e->checkauth;
187 $user_id = $e->requestor->id unless defined $user_id;
189 my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
190 if($e->requestor->id != $user_id) {
191 return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
195 my($e, $user_id, $setting) = @_;
196 my $val = $e->search_actor_user_setting({usr => $user_id, name => $setting})->[0];
197 return undef unless $val; # XXX this should really return undef, but needs testing
198 return OpenSRF::Utils::JSON->JSON2perl($val->value);
202 if(ref $setting eq 'ARRAY') {
204 $settings{$_} = get_setting($e, $user_id, $_) for @$setting;
207 return get_setting($e, $user_id, $setting);
210 my $s = $e->search_actor_user_setting({usr => $user_id});
211 return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
216 __PACKAGE__->register_method(
217 method => "ranged_ou_settings",
218 api_name => "open-ils.actor.org_unit_setting.values.ranged.retrieve",
220 desc => "Retrieves all org unit settings for the given org_id, up to whatever limit " .
221 "is implied for retrieving OU settings by the authenticated users' permissions.",
223 {desc => 'Authentication token', type => 'string'},
224 {desc => 'Org unit ID', type => 'number'},
226 return => {desc => 'A hashref of "ranged" settings, event on error'}
229 sub ranged_ou_settings {
230 my( $self, $client, $auth, $org_id ) = @_;
232 my $e = new_editor(authtoken => $auth);
233 return $e->event unless $e->checkauth;
236 my $org_list = $U->get_org_ancestors($org_id);
237 my $settings = $e->search_actor_org_unit_setting({org_unit => $org_list});
238 $org_list = [ reverse @$org_list ];
240 # start at the context org and capture the setting value
241 # without clobbering settings we've already captured
242 for my $this_org_id (@$org_list) {
244 my @sets = grep { $_->org_unit == $this_org_id } @$settings;
246 for my $set (@sets) {
247 my $type = $e->retrieve_config_org_unit_setting_type([
249 {flesh => 1, flesh_fields => {coust => ['view_perm']}}
252 # If there is no relevant permission, the default assumption will
253 # be, "yes, the caller can have that value."
254 if ($type && $type->view_perm) {
255 next if not $e->allowed($type->view_perm->code, $org_id);
258 $ranged_settings{$set->name} = OpenSRF::Utils::JSON->JSON2perl($set->value)
259 unless defined $ranged_settings{$set->name};
263 return \%ranged_settings;
268 __PACKAGE__->register_method(
269 api_name => 'open-ils.actor.ou_setting.ancestor_default',
270 method => 'ou_ancestor_setting',
272 desc => 'Get the org unit setting value associated with the setting name as seen from the specified org unit. ' .
273 'This method will make sure that the given user has permission to view that setting, if there is a ' .
274 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
275 'the user lacks the permisssion, undef will be returned.' ,
277 { desc => 'Org unit ID', type => 'number' },
278 { desc => 'setting name', type => 'string' },
279 { desc => 'authtoken (optional)', type => 'string' }
281 return => {desc => 'A value for the org unit setting, or undef'}
285 # ------------------------------------------------------------------
286 # Attempts to find the org setting value for a given org. if not
287 # found at the requested org, searches up the org tree until it
288 # finds a parent that has the requested setting.
289 # when found, returns { org => $id, value => $value }
290 # otherwise, returns NULL
291 # ------------------------------------------------------------------
292 sub ou_ancestor_setting {
293 my( $self, $client, $orgid, $name, $auth ) = @_;
294 # Make sure $auth is set to something if not given.
296 return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
299 __PACKAGE__->register_method(
300 api_name => 'open-ils.actor.ou_setting.ancestor_default.batch',
301 method => 'ou_ancestor_setting_batch',
303 desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit. ' .
304 'This method will make sure that the given user has permission to view that setting, if there is a ' .
305 'permission associated with the setting. If a permission is required and no authtoken is given, or ' .
306 'the user lacks the permisssion, undef will be returned.' ,
308 { desc => 'Org unit ID', type => 'number' },
309 { desc => 'setting name list', type => 'array' },
310 { desc => 'authtoken (optional)', type => 'string' }
312 return => {desc => 'A hash with name => value pairs for the org unit settings'}
315 sub ou_ancestor_setting_batch {
316 my( $self, $client, $orgid, $name_list, $auth ) = @_;
318 # splitting the list of settings to fetch values
319 # so that ones that *don't* require view_perm checks
320 # can be fetched in one fell swoop, which is
321 # significantly faster in cases where a large
322 # number of settings need to be fetched.
323 my %perm_check_required = ();
324 my @perm_check_not_required = ();
326 # Note that ->ou_ancestor_setting also can check
327 # to see if the setting has a view_perm, but testing
328 # suggests that the redundant checks do not significantly
329 # increase the time it takes to fetch the values of
330 # permission-controlled settings.
331 my $e = new_editor();
332 my $res = $e->search_config_org_unit_setting_type({
334 view_perm => { "!=" => undef },
336 %perm_check_required = map { $_->name() => 1 } @$res;
337 foreach my $setting (@$name_list) {
338 push @perm_check_not_required, $setting
339 unless exists($perm_check_required{$setting});
343 if (@perm_check_not_required) {
344 %values = $U->ou_ancestor_setting_batch_insecure($orgid, \@perm_check_not_required);
346 $values{$_} = $U->ou_ancestor_setting(
349 ) for keys(%perm_check_required);
355 __PACKAGE__->register_method(
356 method => "update_patron",
357 api_name => "open-ils.actor.patron.update",
360 Update an existing user, or create a new one. Related objects,
361 like cards, addresses, survey responses, and stat cats,
362 can be updated by attaching them to the user object in their
363 respective fields. For examples, the billing address object
364 may be inserted into the 'billing_address' field, etc. For each
365 attached object, indicate if the object should be created,
366 updated, or deleted using the built-in 'isnew', 'ischanged',
367 and 'isdeleted' fields on the object.
370 { desc => 'Authentication token', type => 'string' },
371 { desc => 'Patron data object', type => 'object' }
373 return => {desc => 'A fleshed user object, event on error'}
378 my( $self, $client, $auth, $patron ) = @_;
380 my $e = new_editor(xact => 1, authtoken => $auth);
381 return $e->event unless $e->checkauth;
383 $logger->info($patron->isnew ? "Creating new patron..." :
384 "Updating Patron: " . $patron->id);
386 my $evt = check_group_perm($e, $e->requestor, $patron);
389 # $new_patron is the patron in progress. $patron is the original patron
390 # passed in with the method. new_patron will change as the components
391 # of patron are added/updated.
395 # unflesh the real items on the patron
396 $patron->card( $patron->card->id ) if(ref($patron->card));
397 $patron->billing_address( $patron->billing_address->id )
398 if(ref($patron->billing_address));
399 $patron->mailing_address( $patron->mailing_address->id )
400 if(ref($patron->mailing_address));
402 # create/update the patron first so we can use his id
404 # $patron is the obj from the client (new data) and $new_patron is the
405 # patron object properly built for db insertion, so we need a third variable
406 # if we want to represent the old patron.
409 my $barred_hook = '';
411 if($patron->isnew()) {
412 ( $new_patron, $evt ) = _add_patron($e, _clone_patron($patron));
414 if($U->is_true($patron->barred)) {
415 return $e->die_event unless
416 $e->allowed('BAR_PATRON', $patron->home_ou);
419 $new_patron = $patron;
421 # Did auth checking above already.
422 $old_patron = $e->retrieve_actor_user($patron->id) or
423 return $e->die_event;
425 if($U->is_true($old_patron->barred) != $U->is_true($new_patron->barred)) {
426 my $perm = $U->is_true($old_patron->barred) ? 'UNBAR_PATRON' : 'BAR_PATRON';
427 return $e->die_event unless $e->allowed($perm, $patron->home_ou);
429 $barred_hook = $U->is_true($new_patron->barred) ?
430 'au.barred' : 'au.unbarred';
433 # update the password by itself to avoid the password protection magic
434 if ($patron->passwd && $patron->passwd ne $old_patron->passwd) {
435 modify_migrated_user_password($e, $patron->id, $patron->passwd);
436 $new_patron->passwd(''); # subsequent update will set
437 # actor.usr.passwd to MD5('')
441 ( $new_patron, $evt ) = _add_update_addresses($e, $patron, $new_patron);
444 ( $new_patron, $evt ) = _add_update_cards($e, $patron, $new_patron);
447 ( $new_patron, $evt ) = _add_survey_responses($e, $patron, $new_patron);
450 # re-update the patron if anything has happened to him during this process
451 if($new_patron->ischanged()) {
452 ( $new_patron, $evt ) = _update_patron($e, $new_patron);
456 ( $new_patron, $evt ) = _clear_badcontact_penalties($e, $old_patron, $new_patron);
459 ($new_patron, $evt) = _create_stat_maps($e, $patron, $new_patron);
462 ($new_patron, $evt) = _create_perm_maps($e, $patron, $new_patron);
465 $evt = apply_invalid_addr_penalty($e, $patron);
470 my $tses = OpenSRF::AppSession->create('open-ils.trigger');
472 $tses->request('open-ils.trigger.event.autocreate',
473 'au.create', $new_patron, $new_patron->home_ou);
475 $tses->request('open-ils.trigger.event.autocreate',
476 'au.update', $new_patron, $new_patron->home_ou);
478 $tses->request('open-ils.trigger.event.autocreate', $barred_hook,
479 $new_patron, $new_patron->home_ou) if $barred_hook;
482 $e->xact_begin; # $e->rollback is called in new_flesh_user
483 return flesh_user($new_patron->id(), $e);
486 sub apply_invalid_addr_penalty {
490 # grab the invalid address penalty if set
491 my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
493 my ($addr_penalty) = grep
494 { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
496 # do we enforce invalid address penalty
497 my $enforce = $U->ou_ancestor_setting_value(
498 $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
500 my $addrs = $e->search_actor_user_address(
501 {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
502 my $addr_count = scalar(@$addrs);
504 if($addr_count == 0 and $addr_penalty) {
506 # regardless of any settings, remove the penalty when the user has no invalid addresses
507 $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
510 } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
512 my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
513 my $depth = $ptype->org_depth;
514 my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
515 $ctx_org = $patron->home_ou unless defined $ctx_org;
517 my $penalty = Fieldmapper::actor::user_standing_penalty->new;
518 $penalty->usr($patron->id);
519 $penalty->org_unit($ctx_org);
520 $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
522 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
537 "standing_penalties",
546 push @$fields, "home_ou" if $home_ou;
547 return new_flesh_user($id, $fields, $e );
555 # clone and clear stuff that would break the database
559 my $new_patron = $patron->clone;
561 $new_patron->clear_billing_address();
562 $new_patron->clear_mailing_address();
563 $new_patron->clear_addresses();
564 $new_patron->clear_card();
565 $new_patron->clear_cards();
566 $new_patron->clear_id();
567 $new_patron->clear_isnew();
568 $new_patron->clear_ischanged();
569 $new_patron->clear_isdeleted();
570 $new_patron->clear_stat_cat_entries();
571 $new_patron->clear_permissions();
572 $new_patron->clear_standing_penalties();
583 return (undef, $e->die_event) unless
584 $e->allowed('CREATE_USER', $patron->home_ou);
586 my $ex = $e->search_actor_user(
587 {usrname => $patron->usrname}, {idlist => 1});
588 return (undef, OpenILS::Event->new('USERNAME_EXISTS')) if @$ex;
590 $logger->info("Creating new user in the DB with username: ".$patron->usrname());
592 # do a dance to get the password hashed securely
593 my $saved_password = $patron->passwd;
595 $e->create_actor_user($patron) or return (undef, $e->die_event);
596 modify_migrated_user_password($e, $patron->id, $saved_password);
598 my $id = $patron->id; # added by CStoreEditor
600 $logger->info("Successfully created new user [$id] in DB");
601 return ($e->retrieve_actor_user($id), undef);
605 sub check_group_perm {
606 my( $e, $requestor, $patron ) = @_;
609 # first let's see if the requestor has
610 # priveleges to update this user in any way
611 if( ! $patron->isnew ) {
612 my $p = $e->retrieve_actor_user($patron->id);
614 # If we are the requestor (trying to update our own account)
615 # and we are not trying to change our profile, we're good
616 if( $p->id == $requestor->id and
617 $p->profile == $patron->profile ) {
622 $evt = group_perm_failed($e, $requestor, $p);
626 # They are allowed to edit this patron.. can they put the
627 # patron into the group requested?
628 $evt = group_perm_failed($e, $requestor, $patron);
634 sub group_perm_failed {
635 my( $e, $requestor, $patron ) = @_;
639 my $grpid = $patron->profile;
643 $logger->debug("user update looking for group perm for group $grpid");
644 $grp = $e->retrieve_permission_grp_tree($grpid);
646 } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
648 $logger->info("user update checking perm $perm on user ".
649 $requestor->id." for update/create on user username=".$patron->usrname);
651 return $e->allowed($perm, $patron->home_ou) ? undef : $e->die_event;
657 my( $e, $patron, $noperm) = @_;
659 $logger->info("Updating patron ".$patron->id." in DB");
664 return (undef, $e->die_event)
665 unless $e->allowed('UPDATE_USER', $patron->home_ou);
668 if(!$patron->ident_type) {
669 $patron->clear_ident_type;
670 $patron->clear_ident_value;
673 $evt = verify_last_xact($e, $patron);
674 return (undef, $evt) if $evt;
676 $e->update_actor_user($patron) or return (undef, $e->die_event);
678 # re-fetch the user to pick up the latest last_xact_id value
679 # to avoid collisions.
680 $patron = $e->retrieve_actor_user($patron->id);
685 sub verify_last_xact {
686 my( $e, $patron ) = @_;
687 return undef unless $patron->id and $patron->id > 0;
688 my $p = $e->retrieve_actor_user($patron->id);
689 my $xact = $p->last_xact_id;
690 return undef unless $xact;
691 $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
692 return OpenILS::Event->new('XACT_COLLISION')
693 if $xact ne $patron->last_xact_id;
698 sub _check_dup_ident {
699 my( $session, $patron ) = @_;
701 return undef unless $patron->ident_value;
704 ident_type => $patron->ident_type,
705 ident_value => $patron->ident_value,
708 $logger->debug("patron update searching for dup ident values: " .
709 $patron->ident_type . ':' . $patron->ident_value);
711 $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
713 my $dups = $session->request(
714 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
717 return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
724 sub _add_update_addresses {
728 my $new_patron = shift;
732 my $current_id; # id of the address before creation
734 my $addresses = $patron->addresses();
736 for my $address (@$addresses) {
738 next unless ref $address;
739 $current_id = $address->id();
741 if( $patron->billing_address() and
742 $patron->billing_address() == $current_id ) {
743 $logger->info("setting billing addr to $current_id");
744 $new_patron->billing_address($address->id());
745 $new_patron->ischanged(1);
748 if( $patron->mailing_address() and
749 $patron->mailing_address() == $current_id ) {
750 $new_patron->mailing_address($address->id());
751 $logger->info("setting mailing addr to $current_id");
752 $new_patron->ischanged(1);
756 if($address->isnew()) {
758 $address->usr($new_patron->id());
760 ($address, $evt) = _add_address($e,$address);
761 return (undef, $evt) if $evt;
763 # we need to get the new id
764 if( $patron->billing_address() and
765 $patron->billing_address() == $current_id ) {
766 $new_patron->billing_address($address->id());
767 $logger->info("setting billing addr to $current_id");
768 $new_patron->ischanged(1);
771 if( $patron->mailing_address() and
772 $patron->mailing_address() == $current_id ) {
773 $new_patron->mailing_address($address->id());
774 $logger->info("setting mailing addr to $current_id");
775 $new_patron->ischanged(1);
778 } elsif($address->ischanged() ) {
780 ($address, $evt) = _update_address($e, $address);
781 return (undef, $evt) if $evt;
783 } elsif($address->isdeleted() ) {
785 if( $address->id() == $new_patron->mailing_address() ) {
786 $new_patron->clear_mailing_address();
787 ($new_patron, $evt) = _update_patron($e, $new_patron);
788 return (undef, $evt) if $evt;
791 if( $address->id() == $new_patron->billing_address() ) {
792 $new_patron->clear_billing_address();
793 ($new_patron, $evt) = _update_patron($e, $new_patron);
794 return (undef, $evt) if $evt;
797 $evt = _delete_address($e, $address);
798 return (undef, $evt) if $evt;
802 return ( $new_patron, undef );
806 # adds an address to the db and returns the address with new id
808 my($e, $address) = @_;
809 $address->clear_id();
811 $logger->info("Creating new address at street ".$address->street1);
813 # put the address into the database
814 $e->create_actor_user_address($address) or return (undef, $e->die_event);
815 return ($address, undef);
819 sub _update_address {
820 my( $e, $address ) = @_;
822 $logger->info("Updating address ".$address->id." in the DB");
824 $e->update_actor_user_address($address) or return (undef, $e->die_event);
826 return ($address, undef);
831 sub _add_update_cards {
835 my $new_patron = shift;
839 my $virtual_id; #id of the card before creation
841 my $cards = $patron->cards();
842 for my $card (@$cards) {
844 $card->usr($new_patron->id());
846 if(ref($card) and $card->isnew()) {
848 $virtual_id = $card->id();
849 ( $card, $evt ) = _add_card($e, $card);
850 return (undef, $evt) if $evt;
852 #if(ref($patron->card)) { $patron->card($patron->card->id); }
853 if($patron->card() == $virtual_id) {
854 $new_patron->card($card->id());
855 $new_patron->ischanged(1);
858 } elsif( ref($card) and $card->ischanged() ) {
859 $evt = _update_card($e, $card);
860 return (undef, $evt) if $evt;
864 return ( $new_patron, undef );
868 # adds an card to the db and returns the card with new id
870 my( $e, $card ) = @_;
873 $logger->info("Adding new patron card ".$card->barcode);
875 $e->create_actor_card($card) or return (undef, $e->die_event);
877 return ( $card, undef );
881 # returns event on error. returns undef otherwise
883 my( $e, $card ) = @_;
884 $logger->info("Updating patron card ".$card->id);
886 $e->update_actor_card($card) or return $e->die_event;
893 # returns event on error. returns undef otherwise
894 sub _delete_address {
895 my( $e, $address ) = @_;
897 $logger->info("Deleting address ".$address->id." from DB");
899 $e->delete_actor_user_address($address) or return $e->die_event;
905 sub _add_survey_responses {
906 my ($e, $patron, $new_patron) = @_;
908 $logger->info( "Updating survey responses for patron ".$new_patron->id );
910 my $responses = $patron->survey_responses;
914 $_->usr($new_patron->id) for (@$responses);
916 my $evt = $U->simplereq( "open-ils.circ",
917 "open-ils.circ.survey.submit.user_id", $responses );
919 return (undef, $evt) if defined($U->event_code($evt));
923 return ( $new_patron, undef );
926 sub _clear_badcontact_penalties {
927 my ($e, $old_patron, $new_patron) = @_;
929 return ($new_patron, undef) unless $old_patron;
931 my $PNM = $OpenILS::Utils::BadContact::PENALTY_NAME_MAP;
933 # This ignores whether the caller of update_patron has any permission
934 # to remove penalties, but these penalties no longer make sense
935 # if an email address field (for example) is changed (and the caller must
936 # have perms to do *that*) so there's no reason not to clear the penalties.
938 my $bad_contact_penalties = $e->search_actor_user_standing_penalty([
940 "+csp" => {"name" => [values(%$PNM)]},
941 "+ausp" => {"stop_date" => undef, "usr" => $new_patron->id}
943 "join" => {"csp" => {}},
945 "flesh_fields" => {"ausp" => ["standing_penalty"]}
947 ]) or return (undef, $e->die_event);
949 return ($new_patron, undef) unless @$bad_contact_penalties;
951 my @penalties_to_clear;
952 my ($field, $penalty_name);
954 # For each field that might have an associated bad contact penalty,
955 # check for such penalties and add them to the to-clear list if that
957 while (($field, $penalty_name) = each(%$PNM)) {
958 if ($old_patron->$field ne $new_patron->$field) {
959 push @penalties_to_clear, grep {
960 $_->standing_penalty->name eq $penalty_name
961 } @$bad_contact_penalties;
965 foreach (@penalties_to_clear) {
966 # Note that this "archives" penalties, in the terminology of the staff
967 # client, instead of just deleting them. This may assist reporting,
968 # or preserving old contact information when it is still potentially
970 $_->standing_penalty($_->standing_penalty->id); # deflesh
971 $_->stop_date('now');
972 $e->update_actor_user_standing_penalty($_) or return (undef, $e->die_event);
975 return ($new_patron, undef);
979 sub _create_stat_maps {
981 my($e, $patron, $new_patron) = @_;
983 my $maps = $patron->stat_cat_entries();
985 for my $map (@$maps) {
987 my $method = "update_actor_stat_cat_entry_user_map";
989 if ($map->isdeleted()) {
990 $method = "delete_actor_stat_cat_entry_user_map";
992 } elsif ($map->isnew()) {
993 $method = "create_actor_stat_cat_entry_user_map";
998 $map->target_usr($new_patron->id);
1000 $logger->info("Updating stat entry with method $method and map $map");
1002 $e->$method($map) or return (undef, $e->die_event);
1005 return ($new_patron, undef);
1008 sub _create_perm_maps {
1010 my($e, $patron, $new_patron) = @_;
1012 my $maps = $patron->permissions;
1014 for my $map (@$maps) {
1016 my $method = "update_permission_usr_perm_map";
1017 if ($map->isdeleted()) {
1018 $method = "delete_permission_usr_perm_map";
1019 } elsif ($map->isnew()) {
1020 $method = "create_permission_usr_perm_map";
1024 $map->usr($new_patron->id);
1026 $logger->info( "Updating permissions with method $method and map $map" );
1028 $e->$method($map) or return (undef, $e->die_event);
1031 return ($new_patron, undef);
1035 __PACKAGE__->register_method(
1036 method => "set_user_work_ous",
1037 api_name => "open-ils.actor.user.work_ous.update",
1040 sub set_user_work_ous {
1046 my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
1047 return $evt if $evt;
1049 my $session = $apputils->start_db_session();
1050 $apputils->set_audit_info($session, $ses, $requestor->id, $requestor->wsid);
1052 for my $map (@$maps) {
1054 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
1055 if ($map->isdeleted()) {
1056 $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
1057 } elsif ($map->isnew()) {
1058 $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
1062 #warn( "Updating permissions with method $method and session $ses and map $map" );
1063 $logger->info( "Updating work_ou map with method $method and map $map" );
1065 my $stat = $session->request($method, $map)->gather(1);
1066 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1070 $apputils->commit_db_session($session);
1072 return scalar(@$maps);
1076 __PACKAGE__->register_method(
1077 method => "set_user_perms",
1078 api_name => "open-ils.actor.user.permissions.update",
1081 sub set_user_perms {
1087 my $session = $apputils->start_db_session();
1089 my( $user_obj, $evt ) = $U->checkses($ses);
1090 return $evt if $evt;
1091 $apputils->set_audit_info($session, $ses, $user_obj->id, $user_obj->wsid);
1093 my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
1096 $all = 1 if ($U->is_true($user_obj->super_user()));
1097 $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
1099 for my $map (@$maps) {
1101 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
1102 if ($map->isdeleted()) {
1103 $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
1104 } elsif ($map->isnew()) {
1105 $method = "open-ils.storage.direct.permission.usr_perm_map.create";
1109 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
1110 #warn( "Updating permissions with method $method and session $ses and map $map" );
1111 $logger->info( "Updating permissions with method $method and map $map" );
1113 my $stat = $session->request($method, $map)->gather(1);
1114 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
1118 $apputils->commit_db_session($session);
1120 return scalar(@$maps);
1124 __PACKAGE__->register_method(
1125 method => "user_retrieve_by_barcode",
1127 api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1129 sub user_retrieve_by_barcode {
1130 my($self, $client, $auth, $barcode, $flesh_home_ou) = @_;
1132 my $e = new_editor(authtoken => $auth);
1133 return $e->event unless $e->checkauth;
1135 my $card = $e->search_actor_card({barcode => $barcode})->[0]
1136 or return $e->event;
1138 my $user = flesh_user($card->usr, $e, $flesh_home_ou);
1139 return $e->event unless $e->allowed(
1140 "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou
1147 __PACKAGE__->register_method(
1148 method => "get_user_by_id",
1150 api_name => "open-ils.actor.user.retrieve",
1153 sub get_user_by_id {
1154 my ($self, $client, $auth, $id) = @_;
1155 my $e = new_editor(authtoken=>$auth);
1156 return $e->event unless $e->checkauth;
1157 my $user = $e->retrieve_actor_user($id) or return $e->event;
1158 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
1163 __PACKAGE__->register_method(
1164 method => "get_org_types",
1165 api_name => "open-ils.actor.org_types.retrieve",
1168 return $U->get_org_types();
1172 __PACKAGE__->register_method(
1173 method => "get_user_ident_types",
1174 api_name => "open-ils.actor.user.ident_types.retrieve",
1177 sub get_user_ident_types {
1178 return $ident_types if $ident_types;
1179 return $ident_types =
1180 new_editor()->retrieve_all_config_identification_type();
1184 __PACKAGE__->register_method(
1185 method => "get_org_unit",
1186 api_name => "open-ils.actor.org_unit.retrieve",
1190 my( $self, $client, $user_session, $org_id ) = @_;
1191 my $e = new_editor(authtoken => $user_session);
1193 return $e->event unless $e->checkauth;
1194 $org_id = $e->requestor->ws_ou;
1196 my $o = $e->retrieve_actor_org_unit($org_id)
1197 or return $e->event;
1201 __PACKAGE__->register_method(
1202 method => "search_org_unit",
1203 api_name => "open-ils.actor.org_unit_list.search",
1206 sub search_org_unit {
1208 my( $self, $client, $field, $value ) = @_;
1210 my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1212 "open-ils.cstore.direct.actor.org_unit.search.atomic",
1213 { $field => $value } );
1219 # build the org tree
1221 __PACKAGE__->register_method(
1222 method => "get_org_tree",
1223 api_name => "open-ils.actor.org_tree.retrieve",
1225 note => "Returns the entire org tree structure",
1231 return $U->get_org_tree($client->session->session_locale);
1235 __PACKAGE__->register_method(
1236 method => "get_org_descendants",
1237 api_name => "open-ils.actor.org_tree.descendants.retrieve"
1240 # depth is optional. org_unit is the id
1241 sub get_org_descendants {
1242 my( $self, $client, $org_unit, $depth ) = @_;
1244 if(ref $org_unit eq 'ARRAY') {
1247 for my $i (0..scalar(@$org_unit)-1) {
1248 my $list = $U->simple_scalar_request(
1250 "open-ils.storage.actor.org_unit.descendants.atomic",
1251 $org_unit->[$i], $depth->[$i] );
1252 push(@trees, $U->build_org_tree($list));
1257 my $orglist = $apputils->simple_scalar_request(
1259 "open-ils.storage.actor.org_unit.descendants.atomic",
1260 $org_unit, $depth );
1261 return $U->build_org_tree($orglist);
1266 __PACKAGE__->register_method(
1267 method => "get_org_ancestors",
1268 api_name => "open-ils.actor.org_tree.ancestors.retrieve"
1271 # depth is optional. org_unit is the id
1272 sub get_org_ancestors {
1273 my( $self, $client, $org_unit, $depth ) = @_;
1274 my $orglist = $apputils->simple_scalar_request(
1276 "open-ils.storage.actor.org_unit.ancestors.atomic",
1277 $org_unit, $depth );
1278 return $U->build_org_tree($orglist);
1282 __PACKAGE__->register_method(
1283 method => "get_standings",
1284 api_name => "open-ils.actor.standings.retrieve"
1289 return $user_standings if $user_standings;
1290 return $user_standings =
1291 $apputils->simple_scalar_request(
1293 "open-ils.cstore.direct.config.standing.search.atomic",
1294 { id => { "!=" => undef } }
1299 __PACKAGE__->register_method(
1300 method => "get_my_org_path",
1301 api_name => "open-ils.actor.org_unit.full_path.retrieve"
1304 sub get_my_org_path {
1305 my( $self, $client, $auth, $org_id ) = @_;
1306 my $e = new_editor(authtoken=>$auth);
1307 return $e->event unless $e->checkauth;
1308 $org_id = $e->requestor->ws_ou unless defined $org_id;
1310 return $apputils->simple_scalar_request(
1312 "open-ils.storage.actor.org_unit.full_path.atomic",
1317 __PACKAGE__->register_method(
1318 method => "patron_adv_search",
1319 api_name => "open-ils.actor.patron.search.advanced"
1322 __PACKAGE__->register_method(
1323 method => "patron_adv_search",
1324 api_name => "open-ils.actor.patron.search.advanced.fleshed",
1326 # TODO: change when opensrf 'bundling' is merged.
1327 # set a relatively small bundle size so the caller can start
1328 # seeing results fairly quickly
1329 max_chunk_size => 4096, # bundling
1332 # pending opensrf work -- also, not sure if needed since we're not
1333 # actaully creating an alternate vesrion, only offering to return a
1337 desc => q/Returns a stream of fleshed user objects instead of
1338 a pile of identifiers/
1342 sub patron_adv_search {
1343 my( $self, $client, $auth, $search_hash, $search_limit,
1344 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1346 # API params sanity checks.
1347 # Exit early with empty result if no filter exists.
1348 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1349 my $fleshed = ($self->api_name =~ /fleshed/);
1350 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1352 for my $key (keys %$search_hash) {
1353 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1357 return ($fleshed ? undef : []) unless $search_ok;
1359 my $e = new_editor(authtoken=>$auth);
1360 return $e->event unless $e->checkauth;
1361 return $e->event unless $e->allowed('VIEW_USER');
1363 # depth boundary outside of which patrons must opt-in, default to 0
1364 my $opt_boundary = 0;
1365 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1367 if (not defined $search_ou) {
1368 my $depth = $U->ou_ancestor_setting_value(
1369 $e->requestor->ws_ou,
1370 'circ.patron_edit.duplicate_patron_check_depth'
1373 if (defined $depth) {
1374 $search_ou = $U->org_unit_ancestor_at_depth(
1375 $e->requestor->ws_ou, $depth
1380 my $ids = $U->storagereq(
1381 "open-ils.storage.actor.user.crazy_search", $search_hash,
1382 $search_limit, $search_sort, $include_inactive,
1383 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1385 return $ids unless $self->api_name =~ /fleshed/;
1387 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1393 # A migrated (main) password has the form:
1394 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1395 sub modify_migrated_user_password {
1396 my ($e, $user_id, $passwd) = @_;
1398 # new password gets a new salt
1399 my $new_salt = $e->json_query({
1400 from => ['actor.create_salt', 'main']})->[0];
1401 $new_salt = $new_salt->{'actor.create_salt'};
1408 md5_hex($new_salt . md5_hex($passwd)),
1416 __PACKAGE__->register_method(
1417 method => "update_passwd",
1418 api_name => "open-ils.actor.user.password.update",
1420 desc => "Update the operator's password",
1422 { desc => 'Authentication token', type => 'string' },
1423 { desc => 'New password', type => 'string' },
1424 { desc => 'Current password', type => 'string' }
1426 return => {desc => '1 on success, Event on error or incorrect current password'}
1430 __PACKAGE__->register_method(
1431 method => "update_passwd",
1432 api_name => "open-ils.actor.user.username.update",
1434 desc => "Update the operator's username",
1436 { desc => 'Authentication token', type => 'string' },
1437 { desc => 'New username', type => 'string' },
1438 { desc => 'Current password', type => 'string' }
1440 return => {desc => '1 on success, Event on error or incorrect current password'}
1444 __PACKAGE__->register_method(
1445 method => "update_passwd",
1446 api_name => "open-ils.actor.user.email.update",
1448 desc => "Update the operator's email address",
1450 { desc => 'Authentication token', type => 'string' },
1451 { desc => 'New email address', type => 'string' },
1452 { desc => 'Current password', type => 'string' }
1454 return => {desc => '1 on success, Event on error or incorrect current password'}
1459 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1460 my $e = new_editor(xact=>1, authtoken=>$auth);
1461 return $e->die_event unless $e->checkauth;
1463 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1464 or return $e->die_event;
1465 my $api = $self->api_name;
1467 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1469 return new OpenILS::Event('INCORRECT_PASSWORD');
1472 if( $api =~ /password/o ) {
1473 # NOTE: with access to the plain text password we could crypt
1474 # the password without the extra MD5 pre-hashing. Other changes
1475 # would be required. Noting here for future reference.
1476 modify_migrated_user_password($e, $db_user->id, $new_val);
1477 $db_user->passwd('');
1481 # if we don't clear the password, the user will be updated with
1482 # a hashed version of the hashed version of their password
1483 $db_user->clear_passwd;
1485 if( $api =~ /username/o ) {
1487 # make sure no one else has this username
1488 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1491 return new OpenILS::Event('USERNAME_EXISTS');
1493 $db_user->usrname($new_val);
1495 } elsif( $api =~ /email/o ) {
1496 $db_user->email($new_val);
1500 $e->update_actor_user($db_user) or return $e->die_event;
1503 # update the cached user to pick up these changes
1504 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1510 __PACKAGE__->register_method(
1511 method => "check_user_perms",
1512 api_name => "open-ils.actor.user.perm.check",
1513 notes => <<" NOTES");
1514 Takes a login session, user id, an org id, and an array of perm type strings. For each
1515 perm type, if the user does *not* have the given permission it is added
1516 to a list which is returned from the method. If all permissions
1517 are allowed, an empty list is returned
1518 if the logged in user does not match 'user_id', then the logged in user must
1519 have VIEW_PERMISSION priveleges.
1522 sub check_user_perms {
1523 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1525 my( $staff, $evt ) = $apputils->checkses($login_session);
1526 return $evt if $evt;
1528 if($staff->id ne $user_id) {
1529 if( $evt = $apputils->check_perms(
1530 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1536 for my $perm (@$perm_types) {
1537 if($apputils->check_perms($user_id, $org_id, $perm)) {
1538 push @not_allowed, $perm;
1542 return \@not_allowed
1545 __PACKAGE__->register_method(
1546 method => "check_user_perms2",
1547 api_name => "open-ils.actor.user.perm.check.multi_org",
1549 Checks the permissions on a list of perms and orgs for a user
1550 @param authtoken The login session key
1551 @param user_id The id of the user to check
1552 @param orgs The array of org ids
1553 @param perms The array of permission names
1554 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1555 if the logged in user does not match 'user_id', then the logged in user must
1556 have VIEW_PERMISSION priveleges.
1559 sub check_user_perms2 {
1560 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1562 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1563 $authtoken, $user_id, 'VIEW_PERMISSION' );
1564 return $evt if $evt;
1567 for my $org (@$orgs) {
1568 for my $perm (@$perms) {
1569 if($apputils->check_perms($user_id, $org, $perm)) {
1570 push @not_allowed, [ $org, $perm ];
1575 return \@not_allowed
1579 __PACKAGE__->register_method(
1580 method => 'check_user_perms3',
1581 api_name => 'open-ils.actor.user.perm.highest_org',
1583 Returns the highest org unit id at which a user has a given permission
1584 If the requestor does not match the target user, the requestor must have
1585 'VIEW_PERMISSION' rights at the home org unit of the target user
1586 @param authtoken The login session key
1587 @param userid The id of the user in question
1588 @param perm The permission to check
1589 @return The org unit highest in the org tree within which the user has
1590 the requested permission
1593 sub check_user_perms3 {
1594 my($self, $client, $authtoken, $user_id, $perm) = @_;
1595 my $e = new_editor(authtoken=>$authtoken);
1596 return $e->event unless $e->checkauth;
1598 my $tree = $U->get_org_tree();
1600 unless($e->requestor->id == $user_id) {
1601 my $user = $e->retrieve_actor_user($user_id)
1602 or return $e->event;
1603 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1604 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1607 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1610 __PACKAGE__->register_method(
1611 method => 'user_has_work_perm_at',
1612 api_name => 'open-ils.actor.user.has_work_perm_at',
1616 Returns a set of org unit IDs which represent the highest orgs in
1617 the org tree where the user has the requested permission. The
1618 purpose of this method is to return the smallest set of org units
1619 which represent the full expanse of the user's ability to perform
1620 the requested action. The user whose perms this method should
1621 check is implied by the authtoken. /,
1623 {desc => 'authtoken', type => 'string'},
1624 {desc => 'permission name', type => 'string'},
1625 {desc => q/user id, optional. If present, check perms for
1626 this user instead of the logged in user/, type => 'number'},
1628 return => {desc => 'An array of org IDs'}
1632 sub user_has_work_perm_at {
1633 my($self, $conn, $auth, $perm, $user_id) = @_;
1634 my $e = new_editor(authtoken=>$auth);
1635 return $e->event unless $e->checkauth;
1636 if(defined $user_id) {
1637 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1638 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1640 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1643 __PACKAGE__->register_method(
1644 method => 'user_has_work_perm_at_batch',
1645 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1649 sub user_has_work_perm_at_batch {
1650 my($self, $conn, $auth, $perms, $user_id) = @_;
1651 my $e = new_editor(authtoken=>$auth);
1652 return $e->event unless $e->checkauth;
1653 if(defined $user_id) {
1654 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1655 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1658 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1664 __PACKAGE__->register_method(
1665 method => 'check_user_perms4',
1666 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1668 Returns the highest org unit id at which a user has a given permission
1669 If the requestor does not match the target user, the requestor must have
1670 'VIEW_PERMISSION' rights at the home org unit of the target user
1671 @param authtoken The login session key
1672 @param userid The id of the user in question
1673 @param perms An array of perm names to check
1674 @return An array of orgId's representing the org unit
1675 highest in the org tree within which the user has the requested permission
1676 The arrah of orgId's has matches the order of the perms array
1679 sub check_user_perms4 {
1680 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1682 my( $staff, $target, $org, $evt );
1684 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1685 $authtoken, $userid, 'VIEW_PERMISSION' );
1686 return $evt if $evt;
1689 return [] unless ref($perms);
1690 my $tree = $U->get_org_tree();
1692 for my $p (@$perms) {
1693 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1699 __PACKAGE__->register_method(
1700 method => "user_fines_summary",
1701 api_name => "open-ils.actor.user.fines.summary",
1704 desc => 'Returns a short summary of the users total open fines, ' .
1705 'excluding voided fines Params are login_session, user_id' ,
1707 {desc => 'Authentication token', type => 'string'},
1708 {desc => 'User ID', type => 'string'} # number?
1711 desc => "a 'mous' object, event on error",
1716 sub user_fines_summary {
1717 my( $self, $client, $auth, $user_id ) = @_;
1719 my $e = new_editor(authtoken=>$auth);
1720 return $e->event unless $e->checkauth;
1722 if( $user_id ne $e->requestor->id ) {
1723 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1724 return $e->event unless
1725 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1728 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1732 __PACKAGE__->register_method(
1733 method => "user_opac_vitals",
1734 api_name => "open-ils.actor.user.opac.vital_stats",
1738 desc => 'Returns a short summary of the users vital stats, including ' .
1739 'identification information, accumulated balance, number of holds, ' .
1740 'and current open circulation stats' ,
1742 {desc => 'Authentication token', type => 'string'},
1743 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1746 desc => "An object with four properties: user, fines, checkouts and holds."
1751 sub user_opac_vitals {
1752 my( $self, $client, $auth, $user_id ) = @_;
1754 my $e = new_editor(authtoken=>$auth);
1755 return $e->event unless $e->checkauth;
1757 $user_id ||= $e->requestor->id;
1759 my $user = $e->retrieve_actor_user( $user_id );
1762 ->method_lookup('open-ils.actor.user.fines.summary')
1763 ->run($auth => $user_id);
1764 return $fines if (defined($U->event_code($fines)));
1767 $fines = new Fieldmapper::money::open_user_summary ();
1768 $fines->balance_owed(0.00);
1769 $fines->total_owed(0.00);
1770 $fines->total_paid(0.00);
1771 $fines->usr($user_id);
1775 ->method_lookup('open-ils.actor.user.hold_requests.count')
1776 ->run($auth => $user_id);
1777 return $holds if (defined($U->event_code($holds)));
1780 ->method_lookup('open-ils.actor.user.checked_out.count')
1781 ->run($auth => $user_id);
1782 return $out if (defined($U->event_code($out)));
1784 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1786 my $unread_msgs = $e->search_actor_usr_message([
1787 {usr => $user_id, read_date => undef, deleted => 'f'},
1793 first_given_name => $user->first_given_name,
1794 second_given_name => $user->second_given_name,
1795 family_name => $user->family_name,
1796 alias => $user->alias,
1797 usrname => $user->usrname
1799 fines => $fines->to_bare_hash,
1802 messages => { unread => scalar(@$unread_msgs) }
1807 ##### a small consolidation of related method registrations
1808 my $common_params = [
1809 { desc => 'Authentication token', type => 'string' },
1810 { desc => 'User ID', type => 'string' },
1811 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1812 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1815 'open-ils.actor.user.transactions' => '',
1816 'open-ils.actor.user.transactions.fleshed' => '',
1817 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1818 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1819 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1820 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1823 foreach (keys %methods) {
1825 method => "user_transactions",
1828 desc => 'For a given user, retrieve a list of '
1829 . (/\.fleshed/ ? 'fleshed ' : '')
1830 . 'transactions' . $methods{$_}
1831 . ' optionally limited to transactions of a given type.',
1832 params => $common_params,
1834 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1835 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1839 $args{authoritative} = 1;
1840 __PACKAGE__->register_method(%args);
1843 # Now for the counts
1845 'open-ils.actor.user.transactions.count' => '',
1846 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1847 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1850 foreach (keys %methods) {
1852 method => "user_transactions",
1855 desc => 'For a given user, retrieve a count of open '
1856 . 'transactions' . $methods{$_}
1857 . ' optionally limited to transactions of a given type.',
1858 params => $common_params,
1859 return => { desc => "Integer count of transactions, or event on error" }
1862 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1863 __PACKAGE__->register_method(%args);
1866 __PACKAGE__->register_method(
1867 method => "user_transactions",
1868 api_name => "open-ils.actor.user.transactions.have_balance.total",
1871 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1872 . ' optionally limited to transactions of a given type.',
1873 params => $common_params,
1874 return => { desc => "Decimal balance value, or event on error" }
1879 sub user_transactions {
1880 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1883 my $e = new_editor(authtoken => $auth);
1884 return $e->event unless $e->checkauth;
1886 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1888 return $e->event unless
1889 $e->requestor->id == $user_id or
1890 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1892 my $api = $self->api_name();
1894 my $filter = ($api =~ /have_balance/o) ?
1895 { 'balance_owed' => { '<>' => 0 } }:
1896 { 'total_owed' => { '>' => 0 } };
1898 my $method = 'open-ils.actor.user.transactions.history.still_open';
1899 $method = "$method.authoritative" if $api =~ /authoritative/;
1900 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1902 if($api =~ /total/o) {
1904 $total += $_->balance_owed for @$trans;
1908 ($api =~ /count/o ) and return scalar @$trans;
1909 ($api !~ /fleshed/o) and return $trans;
1912 for my $t (@$trans) {
1914 if( $t->xact_type ne 'circulation' ) {
1915 push @resp, {transaction => $t};
1919 my $circ_data = flesh_circ($e, $t->id);
1920 push @resp, {transaction => $t, %$circ_data};
1927 __PACKAGE__->register_method(
1928 method => "user_transaction_retrieve",
1929 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1932 notes => "Returns a fleshed transaction record"
1935 __PACKAGE__->register_method(
1936 method => "user_transaction_retrieve",
1937 api_name => "open-ils.actor.user.transaction.retrieve",
1940 notes => "Returns a transaction record"
1943 sub user_transaction_retrieve {
1944 my($self, $client, $auth, $bill_id) = @_;
1946 my $e = new_editor(authtoken => $auth);
1947 return $e->event unless $e->checkauth;
1949 my $trans = $e->retrieve_money_billable_transaction_summary(
1950 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1952 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1954 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1956 return $trans unless $self->api_name =~ /flesh/;
1957 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1959 my $circ_data = flesh_circ($e, $trans->id, 1);
1961 return {transaction => $trans, %$circ_data};
1966 my $circ_id = shift;
1967 my $flesh_copy = shift;
1969 my $circ = $e->retrieve_action_circulation([
1973 circ => ['target_copy'],
1974 acp => ['call_number'],
1981 my $copy = $circ->target_copy;
1983 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1984 $mods = new Fieldmapper::metabib::virtual_record;
1985 $mods->doc_id(OILS_PRECAT_RECORD);
1986 $mods->title($copy->dummy_title);
1987 $mods->author($copy->dummy_author);
1990 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1994 $circ->target_copy($circ->target_copy->id);
1995 $copy->call_number($copy->call_number->id);
1997 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
2001 __PACKAGE__->register_method(
2002 method => "hold_request_count",
2003 api_name => "open-ils.actor.user.hold_requests.count",
2007 Returns hold ready vs. total counts.
2008 If a context org unit is provided, a third value
2009 is returned with key 'behind_desk', which reports
2010 how many holds are ready at the pickup library
2011 with the behind_desk flag set to true.
2015 sub hold_request_count {
2016 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2017 my $e = new_editor(authtoken => $authtoken);
2018 return $e->event unless $e->checkauth;
2020 $user_id = $e->requestor->id unless defined $user_id;
2022 if($e->requestor->id ne $user_id) {
2023 my $user = $e->retrieve_actor_user($user_id);
2024 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2027 my $holds = $e->json_query({
2028 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2032 fulfillment_time => {"=" => undef },
2033 cancel_time => undef,
2038 $_->{current_shelf_lib} and # avoid undef warnings
2039 $_->{pickup_lib} eq $_->{current_shelf_lib}
2043 total => scalar(@$holds),
2044 ready => scalar(@ready)
2048 # count of holds ready at pickup lib with behind_desk true.
2049 $resp->{behind_desk} = scalar(
2051 $_->{pickup_lib} == $ctx_org and
2052 $U->is_true($_->{behind_desk})
2060 __PACKAGE__->register_method(
2061 method => "checked_out",
2062 api_name => "open-ils.actor.user.checked_out",
2066 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2067 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2068 . "(i.e., outstanding balance or some other pending action on the circ). "
2069 . "The .count method also includes a 'total' field which sums all open circs.",
2071 { desc => 'Authentication Token', type => 'string'},
2072 { desc => 'User ID', type => 'string'},
2075 desc => 'Returns event on error, or an object with ID lists, like: '
2076 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2081 __PACKAGE__->register_method(
2082 method => "checked_out",
2083 api_name => "open-ils.actor.user.checked_out.count",
2086 signature => q/@see open-ils.actor.user.checked_out/
2090 my( $self, $conn, $auth, $userid ) = @_;
2092 my $e = new_editor(authtoken=>$auth);
2093 return $e->event unless $e->checkauth;
2095 if( $userid ne $e->requestor->id ) {
2096 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2097 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2099 # see if there is a friend link allowing circ.view perms
2100 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2101 $e, $userid, $e->requestor->id, 'circ.view');
2102 return $e->event unless $allowed;
2106 my $count = $self->api_name =~ /count/;
2107 return _checked_out( $count, $e, $userid );
2111 my( $iscount, $e, $userid ) = @_;
2117 claims_returned => [],
2120 my $meth = 'retrieve_action_open_circ_';
2128 claims_returned => 0,
2135 my $data = $e->$meth($userid);
2139 $result{$_} += $data->$_() for (keys %result);
2140 $result{total} += $data->$_() for (keys %result);
2142 for my $k (keys %result) {
2143 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2153 __PACKAGE__->register_method(
2154 method => "checked_in_with_fines",
2155 api_name => "open-ils.actor.user.checked_in_with_fines",
2158 signature => q/@see open-ils.actor.user.checked_out/
2161 sub checked_in_with_fines {
2162 my( $self, $conn, $auth, $userid ) = @_;
2164 my $e = new_editor(authtoken=>$auth);
2165 return $e->event unless $e->checkauth;
2167 if( $userid ne $e->requestor->id ) {
2168 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2171 # money is owed on these items and they are checked in
2172 my $open = $e->search_action_circulation(
2175 xact_finish => undef,
2176 checkin_time => { "!=" => undef },
2181 my( @lost, @cr, @lo );
2182 for my $c (@$open) {
2183 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2184 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2185 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2190 claims_returned => \@cr,
2191 long_overdue => \@lo
2197 my ($api, $desc, $auth) = @_;
2198 $desc = $desc ? (" " . $desc) : '';
2199 my $ids = ($api =~ /ids$/) ? 1 : 0;
2202 method => "user_transaction_history",
2203 api_name => "open-ils.actor.user.transactions.$api",
2205 desc => "For a given User ID, returns a list of billable transaction" .
2206 ($ids ? " id" : '') .
2207 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2208 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2210 {desc => 'Authentication token', type => 'string'},
2211 {desc => 'User ID', type => 'number'},
2212 {desc => 'Transaction type (optional)', type => 'number'},
2213 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2216 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2220 $auth and push @sig, (authoritative => 1);
2224 my %auth_hist_methods = (
2226 'history.have_charge' => 'that have an initial charge',
2227 'history.still_open' => 'that are not finished',
2228 'history.have_balance' => 'that have a balance',
2229 'history.have_bill' => 'that have billings',
2230 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2231 'history.have_payment' => 'that have at least 1 payment',
2234 foreach (keys %auth_hist_methods) {
2235 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2236 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2237 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2240 sub user_transaction_history {
2241 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2245 my $e = new_editor(authtoken=>$auth);
2246 return $e->die_event unless $e->checkauth;
2248 if ($e->requestor->id ne $userid) {
2249 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2252 my $api = $self->api_name;
2253 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2255 if(defined($type)) {
2256 $filter->{'xact_type'} = $type;
2259 if($api =~ /have_bill_or_payment/o) {
2261 # transactions that have a non-zero sum across all billings or at least 1 payment
2262 $filter->{'-or'} = {
2263 'balance_owed' => { '<>' => 0 },
2264 'last_payment_ts' => { '<>' => undef }
2267 } elsif($api =~ /have_payment/) {
2269 $filter->{last_payment_ts} ||= {'<>' => undef};
2271 } elsif( $api =~ /have_balance/o) {
2273 # transactions that have a non-zero overall balance
2274 $filter->{'balance_owed'} = { '<>' => 0 };
2276 } elsif( $api =~ /have_charge/o) {
2278 # transactions that have at least 1 billing, regardless of whether it was voided
2279 $filter->{'last_billing_ts'} = { '<>' => undef };
2281 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2283 # transactions that have non-zero sum across all billings. This will exclude
2284 # xacts where all billings have been voided
2285 $filter->{'total_owed'} = { '<>' => 0 };
2288 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2289 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2290 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2292 my $mbts = $e->search_money_billable_transaction_summary(
2293 [ { usr => $userid, @xact_finish, %$filter },
2298 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2299 return $mbts unless $api =~ /fleshed/;
2302 for my $t (@$mbts) {
2304 if( $t->xact_type ne 'circulation' ) {
2305 push @resp, {transaction => $t};
2309 my $circ_data = flesh_circ($e, $t->id);
2310 push @resp, {transaction => $t, %$circ_data};
2318 __PACKAGE__->register_method(
2319 method => "user_perms",
2320 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2322 notes => "Returns a list of permissions"
2326 my( $self, $client, $authtoken, $user ) = @_;
2328 my( $staff, $evt ) = $apputils->checkses($authtoken);
2329 return $evt if $evt;
2331 $user ||= $staff->id;
2333 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2337 return $apputils->simple_scalar_request(
2339 "open-ils.storage.permission.user_perms.atomic",
2343 __PACKAGE__->register_method(
2344 method => "retrieve_perms",
2345 api_name => "open-ils.actor.permissions.retrieve",
2346 notes => "Returns a list of permissions"
2348 sub retrieve_perms {
2349 my( $self, $client ) = @_;
2350 return $apputils->simple_scalar_request(
2352 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2353 { id => { '!=' => undef } }
2357 __PACKAGE__->register_method(
2358 method => "retrieve_groups",
2359 api_name => "open-ils.actor.groups.retrieve",
2360 notes => "Returns a list of user groups"
2362 sub retrieve_groups {
2363 my( $self, $client ) = @_;
2364 return new_editor()->retrieve_all_permission_grp_tree();
2367 __PACKAGE__->register_method(
2368 method => "retrieve_org_address",
2369 api_name => "open-ils.actor.org_unit.address.retrieve",
2370 notes => <<' NOTES');
2371 Returns an org_unit address by ID
2372 @param An org_address ID
2374 sub retrieve_org_address {
2375 my( $self, $client, $id ) = @_;
2376 return $apputils->simple_scalar_request(
2378 "open-ils.cstore.direct.actor.org_address.retrieve",
2383 __PACKAGE__->register_method(
2384 method => "retrieve_groups_tree",
2385 api_name => "open-ils.actor.groups.tree.retrieve",
2386 notes => "Returns a list of user groups"
2389 sub retrieve_groups_tree {
2390 my( $self, $client ) = @_;
2391 return new_editor()->search_permission_grp_tree(
2396 flesh_fields => { pgt => ["children"] },
2397 order_by => { pgt => 'name'}
2404 __PACKAGE__->register_method(
2405 method => "add_user_to_groups",
2406 api_name => "open-ils.actor.user.set_groups",
2407 notes => "Adds a user to one or more permission groups"
2410 sub add_user_to_groups {
2411 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2413 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2414 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2415 return $evt if $evt;
2417 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2418 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2419 return $evt if $evt;
2421 $apputils->simplereq(
2423 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2425 for my $group (@$groups) {
2426 my $link = Fieldmapper::permission::usr_grp_map->new;
2428 $link->usr($userid);
2430 my $id = $apputils->simplereq(
2432 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2438 __PACKAGE__->register_method(
2439 method => "get_user_perm_groups",
2440 api_name => "open-ils.actor.user.get_groups",
2441 notes => "Retrieve a user's permission groups."
2445 sub get_user_perm_groups {
2446 my( $self, $client, $authtoken, $userid ) = @_;
2448 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2449 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2450 return $evt if $evt;
2452 return $apputils->simplereq(
2454 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2458 __PACKAGE__->register_method(
2459 method => "get_user_work_ous",
2460 api_name => "open-ils.actor.user.get_work_ous",
2461 notes => "Retrieve a user's work org units."
2464 __PACKAGE__->register_method(
2465 method => "get_user_work_ous",
2466 api_name => "open-ils.actor.user.get_work_ous.ids",
2467 notes => "Retrieve a user's work org units."
2470 sub get_user_work_ous {
2471 my( $self, $client, $auth, $userid ) = @_;
2472 my $e = new_editor(authtoken=>$auth);
2473 return $e->event unless $e->checkauth;
2474 $userid ||= $e->requestor->id;
2476 if($e->requestor->id != $userid) {
2477 my $user = $e->retrieve_actor_user($userid)
2478 or return $e->event;
2479 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2482 return $e->search_permission_usr_work_ou_map({usr => $userid})
2483 unless $self->api_name =~ /.ids$/;
2485 # client just wants a list of org IDs
2486 return $U->get_user_work_ou_ids($e, $userid);
2491 __PACKAGE__->register_method(
2492 method => 'register_workstation',
2493 api_name => 'open-ils.actor.workstation.register.override',
2494 signature => q/@see open-ils.actor.workstation.register/
2497 __PACKAGE__->register_method(
2498 method => 'register_workstation',
2499 api_name => 'open-ils.actor.workstation.register',
2501 Registers a new workstion in the system
2502 @param authtoken The login session key
2503 @param name The name of the workstation id
2504 @param owner The org unit that owns this workstation
2505 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2506 if the name is already in use.
2510 sub register_workstation {
2511 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2513 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2514 return $e->die_event unless $e->checkauth;
2515 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2516 my $existing = $e->search_actor_workstation({name => $name})->[0];
2517 $oargs = { all => 1 } unless defined $oargs;
2521 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2522 # workstation with the given name exists.
2524 if($owner ne $existing->owning_lib) {
2525 # if necessary, update the owning_lib of the workstation
2527 $logger->info("changing owning lib of workstation ".$existing->id.
2528 " from ".$existing->owning_lib." to $owner");
2529 return $e->die_event unless
2530 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2532 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2534 $existing->owning_lib($owner);
2535 return $e->die_event unless $e->update_actor_workstation($existing);
2541 "attempt to register an existing workstation. returning existing ID");
2544 return $existing->id;
2547 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2551 my $ws = Fieldmapper::actor::workstation->new;
2552 $ws->owning_lib($owner);
2554 $e->create_actor_workstation($ws) or return $e->die_event;
2556 return $ws->id; # note: editor sets the id on the new object for us
2559 __PACKAGE__->register_method(
2560 method => 'workstation_list',
2561 api_name => 'open-ils.actor.workstation.list',
2563 Returns a list of workstations registered at the given location
2564 @param authtoken The login session key
2565 @param ids A list of org_unit.id's for the workstation owners
2569 sub workstation_list {
2570 my( $self, $conn, $authtoken, @orgs ) = @_;
2572 my $e = new_editor(authtoken=>$authtoken);
2573 return $e->event unless $e->checkauth;
2578 unless $e->allowed('REGISTER_WORKSTATION', $o);
2579 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2585 __PACKAGE__->register_method(
2586 method => 'fetch_patron_note',
2587 api_name => 'open-ils.actor.note.retrieve.all',
2590 Returns a list of notes for a given user
2591 Requestor must have VIEW_USER permission if pub==false and
2592 @param authtoken The login session key
2593 @param args Hash of params including
2594 patronid : the patron's id
2595 pub : true if retrieving only public notes
2599 sub fetch_patron_note {
2600 my( $self, $conn, $authtoken, $args ) = @_;
2601 my $patronid = $$args{patronid};
2603 my($reqr, $evt) = $U->checkses($authtoken);
2604 return $evt if $evt;
2607 ($patron, $evt) = $U->fetch_user($patronid);
2608 return $evt if $evt;
2611 if( $patronid ne $reqr->id ) {
2612 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2613 return $evt if $evt;
2615 return $U->cstorereq(
2616 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2617 { usr => $patronid, pub => 't' } );
2620 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2621 return $evt if $evt;
2623 return $U->cstorereq(
2624 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2627 __PACKAGE__->register_method(
2628 method => 'create_user_note',
2629 api_name => 'open-ils.actor.note.create',
2631 Creates a new note for the given user
2632 @param authtoken The login session key
2633 @param note The note object
2636 sub create_user_note {
2637 my( $self, $conn, $authtoken, $note ) = @_;
2638 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2639 return $e->die_event unless $e->checkauth;
2641 my $user = $e->retrieve_actor_user($note->usr)
2642 or return $e->die_event;
2644 return $e->die_event unless
2645 $e->allowed('UPDATE_USER',$user->home_ou);
2647 $note->creator($e->requestor->id);
2648 $e->create_actor_usr_note($note) or return $e->die_event;
2654 __PACKAGE__->register_method(
2655 method => 'delete_user_note',
2656 api_name => 'open-ils.actor.note.delete',
2658 Deletes a note for the given user
2659 @param authtoken The login session key
2660 @param noteid The note id
2663 sub delete_user_note {
2664 my( $self, $conn, $authtoken, $noteid ) = @_;
2666 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2667 return $e->die_event unless $e->checkauth;
2668 my $note = $e->retrieve_actor_usr_note($noteid)
2669 or return $e->die_event;
2670 my $user = $e->retrieve_actor_user($note->usr)
2671 or return $e->die_event;
2672 return $e->die_event unless
2673 $e->allowed('UPDATE_USER', $user->home_ou);
2675 $e->delete_actor_usr_note($note) or return $e->die_event;
2681 __PACKAGE__->register_method(
2682 method => 'update_user_note',
2683 api_name => 'open-ils.actor.note.update',
2685 @param authtoken The login session key
2686 @param note The note
2690 sub update_user_note {
2691 my( $self, $conn, $auth, $note ) = @_;
2692 my $e = new_editor(authtoken=>$auth, xact=>1);
2693 return $e->die_event unless $e->checkauth;
2694 my $patron = $e->retrieve_actor_user($note->usr)
2695 or return $e->die_event;
2696 return $e->die_event unless
2697 $e->allowed('UPDATE_USER', $patron->home_ou);
2698 $e->update_actor_user_note($note)
2699 or return $e->die_event;
2704 __PACKAGE__->register_method(
2705 method => 'fetch_patron_messages',
2706 api_name => 'open-ils.actor.message.retrieve',
2709 Returns a list of notes for a given user, not
2710 including ones marked deleted
2711 @param authtoken The login session key
2712 @param patronid patron ID
2713 @param options hash containing optional limit and offset
2717 sub fetch_patron_messages {
2718 my( $self, $conn, $auth, $patronid, $options ) = @_;
2722 my $e = new_editor(authtoken => $auth);
2723 return $e->die_event unless $e->checkauth;
2725 if ($e->requestor->id ne $patronid) {
2726 return $e->die_event unless $e->allowed('VIEW_USER');
2729 my $select_clause = { usr => $patronid };
2730 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2731 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2732 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2734 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2739 __PACKAGE__->register_method(
2740 method => 'usrname_exists',
2741 api_name => 'open-ils.actor.username.exists',
2743 desc => 'Check if a username is already taken (by an undeleted patron)',
2745 {desc => 'Authentication token', type => 'string'},
2746 {desc => 'Username', type => 'string'}
2749 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2754 sub usrname_exists {
2755 my( $self, $conn, $auth, $usrname ) = @_;
2756 my $e = new_editor(authtoken=>$auth);
2757 return $e->event unless $e->checkauth;
2758 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2759 return $$a[0] if $a and @$a;
2763 __PACKAGE__->register_method(
2764 method => 'barcode_exists',
2765 api_name => 'open-ils.actor.barcode.exists',
2767 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2770 sub barcode_exists {
2771 my( $self, $conn, $auth, $barcode ) = @_;
2772 my $e = new_editor(authtoken=>$auth);
2773 return $e->event unless $e->checkauth;
2774 my $card = $e->search_actor_card({barcode => $barcode});
2780 #return undef unless @$card;
2781 #return $card->[0]->usr;
2785 __PACKAGE__->register_method(
2786 method => 'retrieve_net_levels',
2787 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2790 sub retrieve_net_levels {
2791 my( $self, $conn, $auth ) = @_;
2792 my $e = new_editor(authtoken=>$auth);
2793 return $e->event unless $e->checkauth;
2794 return $e->retrieve_all_config_net_access_level();
2797 # Retain the old typo API name just in case
2798 __PACKAGE__->register_method(
2799 method => 'fetch_org_by_shortname',
2800 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2802 __PACKAGE__->register_method(
2803 method => 'fetch_org_by_shortname',
2804 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2806 sub fetch_org_by_shortname {
2807 my( $self, $conn, $sname ) = @_;
2808 my $e = new_editor();
2809 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2810 return $e->event unless $org;
2815 __PACKAGE__->register_method(
2816 method => 'session_home_lib',
2817 api_name => 'open-ils.actor.session.home_lib',
2820 sub session_home_lib {
2821 my( $self, $conn, $auth ) = @_;
2822 my $e = new_editor(authtoken=>$auth);
2823 return undef unless $e->checkauth;
2824 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2825 return $org->shortname;
2828 __PACKAGE__->register_method(
2829 method => 'session_safe_token',
2830 api_name => 'open-ils.actor.session.safe_token',
2832 Returns a hashed session ID that is safe for export to the world.
2833 This safe token will expire after 1 hour of non-use.
2834 @param auth Active authentication token
2838 sub session_safe_token {
2839 my( $self, $conn, $auth ) = @_;
2840 my $e = new_editor(authtoken=>$auth);
2841 return undef unless $e->checkauth;
2843 my $safe_token = md5_hex($auth);
2845 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2847 # add more user fields as needed
2849 "safe-token-user-$safe_token", {
2850 id => $e->requestor->id,
2851 home_ou_shortname => $e->retrieve_actor_org_unit(
2852 $e->requestor->home_ou)->shortname,
2861 __PACKAGE__->register_method(
2862 method => 'safe_token_home_lib',
2863 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2865 Returns the home library shortname from the session
2866 asscociated with a safe token from generated by
2867 open-ils.actor.session.safe_token.
2868 @param safe_token Active safe token
2869 @param who Optional user activity "ewho" value
2873 sub safe_token_home_lib {
2874 my( $self, $conn, $safe_token, $who ) = @_;
2875 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2877 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2878 return unless $blob;
2880 $U->log_user_activity($blob->{id}, $who, 'verify');
2881 return $blob->{home_ou_shortname};
2885 __PACKAGE__->register_method(
2886 method => "update_penalties",
2887 api_name => "open-ils.actor.user.penalties.update"
2890 sub update_penalties {
2891 my($self, $conn, $auth, $user_id) = @_;
2892 my $e = new_editor(authtoken=>$auth, xact => 1);
2893 return $e->die_event unless $e->checkauth;
2894 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2895 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2896 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2897 return $evt if $evt;
2903 __PACKAGE__->register_method(
2904 method => "apply_penalty",
2905 api_name => "open-ils.actor.user.penalty.apply"
2909 my($self, $conn, $auth, $penalty) = @_;
2911 my $e = new_editor(authtoken=>$auth, xact => 1);
2912 return $e->die_event unless $e->checkauth;
2914 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2915 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2917 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2920 (defined $ptype->org_depth) ?
2921 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2924 $penalty->org_unit($ctx_org);
2925 $penalty->staff($e->requestor->id);
2926 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2929 return $penalty->id;
2932 __PACKAGE__->register_method(
2933 method => "remove_penalty",
2934 api_name => "open-ils.actor.user.penalty.remove"
2937 sub remove_penalty {
2938 my($self, $conn, $auth, $penalty) = @_;
2939 my $e = new_editor(authtoken=>$auth, xact => 1);
2940 return $e->die_event unless $e->checkauth;
2941 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2942 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2944 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2949 __PACKAGE__->register_method(
2950 method => "update_penalty_note",
2951 api_name => "open-ils.actor.user.penalty.note.update"
2954 sub update_penalty_note {
2955 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2956 my $e = new_editor(authtoken=>$auth, xact => 1);
2957 return $e->die_event unless $e->checkauth;
2958 for my $penalty_id (@$penalty_ids) {
2959 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2960 if (! $penalty ) { return $e->die_event; }
2961 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2962 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2964 $penalty->note( $note ); $penalty->ischanged( 1 );
2966 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2972 __PACKAGE__->register_method(
2973 method => "ranged_penalty_thresholds",
2974 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2978 sub ranged_penalty_thresholds {
2979 my($self, $conn, $auth, $context_org) = @_;
2980 my $e = new_editor(authtoken=>$auth);
2981 return $e->event unless $e->checkauth;
2982 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2983 my $list = $e->search_permission_grp_penalty_threshold([
2984 {org_unit => $U->get_org_ancestors($context_org)},
2985 {order_by => {pgpt => 'id'}}
2987 $conn->respond($_) for @$list;
2993 __PACKAGE__->register_method(
2994 method => "user_retrieve_fleshed_by_id",
2996 api_name => "open-ils.actor.user.fleshed.retrieve",
2999 sub user_retrieve_fleshed_by_id {
3000 my( $self, $client, $auth, $user_id, $fields ) = @_;
3001 my $e = new_editor(authtoken => $auth);
3002 return $e->event unless $e->checkauth;
3004 if( $e->requestor->id != $user_id ) {
3005 return $e->event unless $e->allowed('VIEW_USER');
3012 "standing_penalties",
3019 return new_flesh_user($user_id, $fields, $e);
3023 sub new_flesh_user {
3026 my $fields = shift || [];
3029 my $fetch_penalties = 0;
3030 if(grep {$_ eq 'standing_penalties'} @$fields) {
3031 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3032 $fetch_penalties = 1;
3035 my $fetch_usr_act = 0;
3036 if(grep {$_ eq 'usr_activity'} @$fields) {
3037 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3041 my $user = $e->retrieve_actor_user(
3046 "flesh_fields" => { "au" => $fields }
3049 ) or return $e->die_event;
3052 if( grep { $_ eq 'addresses' } @$fields ) {
3054 $user->addresses([]) unless @{$user->addresses};
3055 # don't expose "replaced" addresses by default
3056 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3058 if( ref $user->billing_address ) {
3059 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3060 push( @{$user->addresses}, $user->billing_address );
3064 if( ref $user->mailing_address ) {
3065 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3066 push( @{$user->addresses}, $user->mailing_address );
3071 if($fetch_penalties) {
3072 # grab the user penalties ranged for this location
3073 $user->standing_penalties(
3074 $e->search_actor_user_standing_penalty([
3077 {stop_date => undef},
3078 {stop_date => {'>' => 'now'}}
3080 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3083 flesh_fields => {ausp => ['standing_penalty']}
3089 # retrieve the most recent usr_activity entry
3090 if ($fetch_usr_act) {
3092 # max number to return for simple patron fleshing
3093 my $limit = $U->ou_ancestor_setting_value(
3094 $e->requestor->ws_ou,
3095 'circ.patron.usr_activity_retrieve.max');
3099 flesh_fields => {auact => ['etype']},
3100 order_by => {auact => 'event_time DESC'},
3103 # 0 == none, <0 == return all
3104 $limit = 1 unless defined $limit;
3105 $opts->{limit} = $limit if $limit > 0;
3107 $user->usr_activity(
3109 [] : # skip the DB call
3110 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3115 $user->clear_passwd();
3122 __PACKAGE__->register_method(
3123 method => "user_retrieve_parts",
3124 api_name => "open-ils.actor.user.retrieve.parts",
3127 sub user_retrieve_parts {
3128 my( $self, $client, $auth, $user_id, $fields ) = @_;
3129 my $e = new_editor(authtoken => $auth);
3130 return $e->event unless $e->checkauth;
3131 $user_id ||= $e->requestor->id;
3132 if( $e->requestor->id != $user_id ) {
3133 return $e->event unless $e->allowed('VIEW_USER');
3136 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3137 push(@resp, $user->$_()) for(@$fields);
3143 __PACKAGE__->register_method(
3144 method => 'user_opt_in_enabled',
3145 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3146 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3149 sub user_opt_in_enabled {
3150 my($self, $conn) = @_;
3151 my $sc = OpenSRF::Utils::SettingsClient->new;
3152 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3157 __PACKAGE__->register_method(
3158 method => 'user_opt_in_at_org',
3159 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3161 @param $auth The auth token
3162 @param user_id The ID of the user to test
3163 @return 1 if the user has opted in at the specified org,
3164 2 if opt-in is disallowed for the user's home org,
3165 event on error, and 0 otherwise. /
3167 sub user_opt_in_at_org {
3168 my($self, $conn, $auth, $user_id) = @_;
3170 # see if we even need to enforce the opt-in value
3171 return 1 unless user_opt_in_enabled($self);
3173 my $e = new_editor(authtoken => $auth);
3174 return $e->event unless $e->checkauth;
3176 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3177 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3179 my $ws_org = $e->requestor->ws_ou;
3180 # user is automatically opted-in if they are from the local org
3181 return 1 if $user->home_ou eq $ws_org;
3183 # get the boundary setting
3184 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3186 # auto opt in if user falls within the opt boundary
3187 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3189 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3191 # check whether opt-in is restricted at the user's home library
3192 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3193 if ($opt_restrict_depth) {
3194 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3195 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3197 # opt-in is disallowed unless the workstation org is within the home
3198 # library's opt-in scope
3199 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3202 my $vals = $e->search_actor_usr_org_unit_opt_in(
3203 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3209 __PACKAGE__->register_method(
3210 method => 'create_user_opt_in_at_org',
3211 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3213 @param $auth The auth token
3214 @param user_id The ID of the user to test
3215 @return The ID of the newly created object, event on error./
3218 sub create_user_opt_in_at_org {
3219 my($self, $conn, $auth, $user_id, $org_id) = @_;
3221 my $e = new_editor(authtoken => $auth, xact=>1);
3222 return $e->die_event unless $e->checkauth;
3224 # if a specific org unit wasn't passed in, get one based on the defaults;
3226 my $wsou = $e->requestor->ws_ou;
3227 # get the default opt depth
3228 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3229 # get the org unit at that depth
3230 my $org = $e->json_query({
3231 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3232 $org_id = $org->{id};
3235 # fall back to the workstation OU, the pre-opt-in-boundary way
3236 $org_id = $e->requestor->ws_ou;
3239 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3240 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3242 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3244 $opt_in->org_unit($org_id);
3245 $opt_in->usr($user_id);
3246 $opt_in->staff($e->requestor->id);
3247 $opt_in->opt_in_ts('now');
3248 $opt_in->opt_in_ws($e->requestor->wsid);
3250 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3251 or return $e->die_event;
3259 __PACKAGE__->register_method (
3260 method => 'retrieve_org_hours',
3261 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3263 Returns the hours of operation for a specified org unit
3264 @param authtoken The login session key
3265 @param org_id The org_unit ID
3269 sub retrieve_org_hours {
3270 my($self, $conn, $auth, $org_id) = @_;
3271 my $e = new_editor(authtoken => $auth);
3272 return $e->die_event unless $e->checkauth;
3273 $org_id ||= $e->requestor->ws_ou;
3274 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3278 __PACKAGE__->register_method (
3279 method => 'verify_user_password',
3280 api_name => 'open-ils.actor.verify_user_password',
3282 Given a barcode or username and the MD5 encoded password,
3283 returns 1 if the password is correct. Returns 0 otherwise.
3287 sub verify_user_password {
3288 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3289 my $e = new_editor(authtoken => $auth);
3290 return $e->die_event unless $e->checkauth;
3292 my $user_by_barcode;
3293 my $user_by_username;
3295 my $card = $e->search_actor_card([
3296 {barcode => $barcode},
3297 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3298 $user_by_barcode = $card->usr;
3299 $user = $user_by_barcode;
3302 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3303 $user = $user_by_username;
3305 return 0 if (!$user || $U->is_true($user->deleted));
3306 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3307 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3308 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3311 __PACKAGE__->register_method (
3312 method => 'retrieve_usr_id_via_barcode_or_usrname',
3313 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3315 Given a barcode or username returns the id for the user or
3320 sub retrieve_usr_id_via_barcode_or_usrname {
3321 my($self, $conn, $auth, $barcode, $username) = @_;
3322 my $e = new_editor(authtoken => $auth);
3323 return $e->die_event unless $e->checkauth;
3324 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3326 my $user_by_barcode;
3327 my $user_by_username;
3328 $logger->info("$id_as_barcode is the ID as BARCODE");
3330 my $card = $e->search_actor_card([
3331 {barcode => $barcode},
3332 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3333 if ($id_as_barcode =~ /^t/i) {
3335 $user = $e->retrieve_actor_user($barcode);
3336 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3338 $user_by_barcode = $card->usr;
3339 $user = $user_by_barcode;
3342 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3343 $user_by_barcode = $card->usr;
3344 $user = $user_by_barcode;
3349 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3351 $user = $user_by_username;
3353 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3354 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3355 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3360 __PACKAGE__->register_method (
3361 method => 'merge_users',
3362 api_name => 'open-ils.actor.user.merge',
3365 Given a list of source users and destination user, transfer all data from the source
3366 to the dest user and delete the source user. All user related data is
3367 transferred, including circulations, holds, bookbags, etc.
3373 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3374 my $e = new_editor(xact => 1, authtoken => $auth);
3375 return $e->die_event unless $e->checkauth;
3377 # disallow the merge if any subordinate accounts are in collections
3378 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3379 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3381 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3382 if $master_id == $e->requestor->id;
3384 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3385 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3386 return $evt if $evt;
3388 my $del_addrs = ($U->ou_ancestor_setting_value(
3389 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3390 my $del_cards = ($U->ou_ancestor_setting_value(
3391 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3392 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3393 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3395 for my $src_id (@$user_ids) {
3397 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3398 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3399 return $evt if $evt;
3401 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3402 if $src_id == $e->requestor->id;
3404 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3405 if($src_user->home_ou ne $master_user->home_ou) {
3406 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3409 return $e->die_event unless
3410 $e->json_query({from => [
3425 __PACKAGE__->register_method (
3426 method => 'approve_user_address',
3427 api_name => 'open-ils.actor.user.pending_address.approve',
3434 sub approve_user_address {
3435 my($self, $conn, $auth, $addr) = @_;
3436 my $e = new_editor(xact => 1, authtoken => $auth);
3437 return $e->die_event unless $e->checkauth;
3439 # if the caller passes an address object, assume they want to
3440 # update it first before approving it
3441 $e->update_actor_user_address($addr) or return $e->die_event;
3443 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3445 my $user = $e->retrieve_actor_user($addr->usr);
3446 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3447 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3448 or return $e->die_event;
3450 return [values %$result]->[0];
3454 __PACKAGE__->register_method (
3455 method => 'retrieve_friends',
3456 api_name => 'open-ils.actor.friends.retrieve',
3459 returns { confirmed: [], pending_out: [], pending_in: []}
3460 pending_out are users I'm requesting friendship with
3461 pending_in are users requesting friendship with me
3466 sub retrieve_friends {
3467 my($self, $conn, $auth, $user_id, $options) = @_;
3468 my $e = new_editor(authtoken => $auth);
3469 return $e->event unless $e->checkauth;
3470 $user_id ||= $e->requestor->id;
3472 if($user_id != $e->requestor->id) {
3473 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3474 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3477 return OpenILS::Application::Actor::Friends->retrieve_friends(
3478 $e, $user_id, $options);
3483 __PACKAGE__->register_method (
3484 method => 'apply_friend_perms',
3485 api_name => 'open-ils.actor.friends.perms.apply',
3491 sub apply_friend_perms {
3492 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3493 my $e = new_editor(authtoken => $auth, xact => 1);
3494 return $e->die_event unless $e->checkauth;
3496 if($user_id != $e->requestor->id) {
3497 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3498 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3501 for my $perm (@perms) {
3503 OpenILS::Application::Actor::Friends->apply_friend_perm(
3504 $e, $user_id, $delegate_id, $perm);
3505 return $evt if $evt;
3513 __PACKAGE__->register_method (
3514 method => 'update_user_pending_address',
3515 api_name => 'open-ils.actor.user.address.pending.cud'
3518 sub update_user_pending_address {
3519 my($self, $conn, $auth, $addr) = @_;
3520 my $e = new_editor(authtoken => $auth, xact => 1);
3521 return $e->die_event unless $e->checkauth;
3523 if($addr->usr != $e->requestor->id) {
3524 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3525 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3529 $e->create_actor_user_address($addr) or return $e->die_event;
3530 } elsif($addr->isdeleted) {
3531 $e->delete_actor_user_address($addr) or return $e->die_event;
3533 $e->update_actor_user_address($addr) or return $e->die_event;
3541 __PACKAGE__->register_method (
3542 method => 'user_events',
3543 api_name => 'open-ils.actor.user.events.circ',
3546 __PACKAGE__->register_method (
3547 method => 'user_events',
3548 api_name => 'open-ils.actor.user.events.ahr',
3553 my($self, $conn, $auth, $user_id, $filters) = @_;
3554 my $e = new_editor(authtoken => $auth);
3555 return $e->event unless $e->checkauth;
3557 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3558 my $user_field = 'usr';
3561 $filters->{target} = {
3562 select => { $obj_type => ['id'] },
3564 where => {usr => $user_id}
3567 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3568 if($e->requestor->id != $user_id) {
3569 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3572 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3573 my $req = $ses->request('open-ils.trigger.events_by_target',
3574 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3576 while(my $resp = $req->recv) {
3577 my $val = $resp->content;
3578 my $tgt = $val->target;
3580 if($obj_type eq 'circ') {
3581 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3583 } elsif($obj_type eq 'ahr') {
3584 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3585 if $tgt->current_copy;
3588 $conn->respond($val) if $val;
3594 __PACKAGE__->register_method (
3595 method => 'copy_events',
3596 api_name => 'open-ils.actor.copy.events.circ',
3599 __PACKAGE__->register_method (
3600 method => 'copy_events',
3601 api_name => 'open-ils.actor.copy.events.ahr',
3606 my($self, $conn, $auth, $copy_id, $filters) = @_;
3607 my $e = new_editor(authtoken => $auth);
3608 return $e->event unless $e->checkauth;
3610 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3612 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3614 my $copy_field = 'target_copy';
3615 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3618 $filters->{target} = {
3619 select => { $obj_type => ['id'] },
3621 where => {$copy_field => $copy_id}
3625 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3626 my $req = $ses->request('open-ils.trigger.events_by_target',
3627 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3629 while(my $resp = $req->recv) {
3630 my $val = $resp->content;
3631 my $tgt = $val->target;
3633 my $user = $e->retrieve_actor_user($tgt->usr);
3634 if($e->requestor->id != $user->id) {
3635 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3638 $tgt->$copy_field($copy);
3641 $conn->respond($val) if $val;
3648 __PACKAGE__->register_method (
3649 method => 'get_itemsout_notices',
3650 api_name => 'open-ils.actor.user.itemsout.notices',
3655 sub get_itemsout_notices{
3656 my( $self, $conn, $auth, $circId, $patronId) = @_;
3658 my $e = new_editor(authtoken => $auth);
3659 return $e->event unless $e->checkauth;
3661 my $requestorId = $e->requestor->id;
3663 if( $patronId ne $requestorId ){
3664 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3665 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3668 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3669 #my $req = $ses->request('open-ils.trigger.events_by_target',
3670 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3671 # ^ Above removed in favor of faster json_query.
3674 # select complete_time
3675 # from action_trigger.event atev
3676 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3677 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3678 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3681 my $ctx_loc = $e->requestor->ws_ou;
3682 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value($ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3684 select => { atev => ["complete_time"] },
3687 atevdef => { field => "id",fkey => "event_def", join => { ath => { field => "key", fkey => "hook" }} }
3690 where => {"+ath" => { key => "checkout.due" },"+atevdef" => { active => 't' },"+atev" => { target => $circId, state => 'complete' }}
3693 if ($exclude_courtesy_notices){
3694 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3697 my %resblob = ( numNotices => 0, lastDt => undef );
3699 my $res = $e->json_query($query);
3700 for my $ndate (@$res) {
3701 $resblob{numNotices}++;
3702 if( !defined $resblob{lastDt}){
3703 $resblob{lastDt} = $$ndate{complete_time};
3706 if ($resblob{lastDt} lt $$ndate{complete_time}){
3707 $resblob{lastDt} = $$ndate{complete_time};
3711 $conn->respond(\%resblob);
3715 __PACKAGE__->register_method (
3716 method => 'update_events',
3717 api_name => 'open-ils.actor.user.event.cancel.batch',
3720 __PACKAGE__->register_method (
3721 method => 'update_events',
3722 api_name => 'open-ils.actor.user.event.reset.batch',
3727 my($self, $conn, $auth, $event_ids) = @_;
3728 my $e = new_editor(xact => 1, authtoken => $auth);
3729 return $e->die_event unless $e->checkauth;
3732 for my $id (@$event_ids) {
3734 # do a little dance to determine what user we are ultimately affecting
3735 my $event = $e->retrieve_action_trigger_event([
3738 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3740 ]) or return $e->die_event;
3743 if($event->event_def->hook->core_type eq 'circ') {
3744 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3745 } elsif($event->event_def->hook->core_type eq 'ahr') {
3746 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3751 my $user = $e->retrieve_actor_user($user_id);
3752 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3754 if($self->api_name =~ /cancel/) {
3755 $event->state('invalid');
3756 } elsif($self->api_name =~ /reset/) {
3757 $event->clear_start_time;
3758 $event->clear_update_time;
3759 $event->state('pending');
3762 $e->update_action_trigger_event($event) or return $e->die_event;
3763 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3767 return {complete => 1};
3771 __PACKAGE__->register_method (
3772 method => 'really_delete_user',
3773 api_name => 'open-ils.actor.user.delete.override',
3774 signature => q/@see open-ils.actor.user.delete/
3777 __PACKAGE__->register_method (
3778 method => 'really_delete_user',
3779 api_name => 'open-ils.actor.user.delete',
3781 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3782 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3783 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3784 dest_usr_id is only required when deleting a user that performs staff functions.
3788 sub really_delete_user {
3789 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3790 my $e = new_editor(authtoken => $auth, xact => 1);
3791 return $e->die_event unless $e->checkauth;
3792 $oargs = { all => 1 } unless defined $oargs;
3794 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3795 my $open_bills = $e->json_query({
3796 select => { mbts => ['id'] },
3799 xact_finish => { '=' => undef },
3800 usr => { '=' => $user_id },
3802 }) or return $e->die_event;
3804 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3806 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3808 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3809 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3810 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3812 # No deleting yourself - UI is supposed to stop you first, though.
3813 return $e->die_event unless $e->requestor->id != $user->id;
3814 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3815 # Check if you are allowed to mess with this patron permission group at all
3816 my $evt = group_perm_failed($e, $e->requestor, $user);
3817 return $e->die_event($evt) if $evt;
3818 my $stat = $e->json_query(
3819 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3820 or return $e->die_event;
3826 __PACKAGE__->register_method (
3827 method => 'user_payments',
3828 api_name => 'open-ils.actor.user.payments.retrieve',
3831 Returns all payments for a given user. Default order is newest payments first.
3832 @param auth Authentication token
3833 @param user_id The user ID
3834 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3839 my($self, $conn, $auth, $user_id, $filters) = @_;
3842 my $e = new_editor(authtoken => $auth);
3843 return $e->die_event unless $e->checkauth;
3845 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3846 return $e->event unless
3847 $e->requestor->id == $user_id or
3848 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3850 # Find all payments for all transactions for user $user_id
3852 select => {mp => ['id']},
3857 select => {mbt => ['id']},
3859 where => {usr => $user_id}
3864 { # by default, order newest payments first
3866 field => 'payment_ts',
3869 # secondary sort in ID as a tie-breaker, since payments created
3870 # within the same transaction will have identical payment_ts's
3877 for (qw/order_by limit offset/) {
3878 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3881 if(defined $filters->{where}) {
3882 foreach (keys %{$filters->{where}}) {
3883 # don't allow the caller to expand the result set to other users
3884 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3888 my $payment_ids = $e->json_query($query);
3889 for my $pid (@$payment_ids) {
3890 my $pay = $e->retrieve_money_payment([
3895 mbt => ['summary', 'circulation', 'grocery'],
3896 circ => ['target_copy'],
3897 acp => ['call_number'],
3905 xact_type => $pay->xact->summary->xact_type,
3906 last_billing_type => $pay->xact->summary->last_billing_type,
3909 if($pay->xact->summary->xact_type eq 'circulation') {
3910 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3911 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3914 $pay->xact($pay->xact->id); # de-flesh
3915 $conn->respond($resp);
3923 __PACKAGE__->register_method (
3924 method => 'negative_balance_users',
3925 api_name => 'open-ils.actor.users.negative_balance',
3928 Returns all users that have an overall negative balance
3929 @param auth Authentication token
3930 @param org_id The context org unit as an ID or list of IDs. This will be the home
3931 library of the user. If no org_unit is specified, no org unit filter is applied
3935 sub negative_balance_users {
3936 my($self, $conn, $auth, $org_id) = @_;
3938 my $e = new_editor(authtoken => $auth);
3939 return $e->die_event unless $e->checkauth;
3940 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3944 mous => ['usr', 'balance_owed'],
3947 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3948 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3965 where => {'+mous' => {balance_owed => {'<' => 0}}}
3968 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3970 my $list = $e->json_query($query, {timeout => 600});
3972 for my $data (@$list) {
3974 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3975 balance_owed => $data->{balance_owed},
3976 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3983 __PACKAGE__->register_method(
3984 method => "request_password_reset",
3985 api_name => "open-ils.actor.patron.password_reset.request",
3987 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3988 "method for changing a user's password. The UUID token is distributed via A/T " .
3989 "templates (i.e. email to the user).",
3991 { desc => 'user_id_type', type => 'string' },
3992 { desc => 'user_id', type => 'string' },
3993 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3995 return => {desc => '1 on success, Event on error'}
3998 sub request_password_reset {
3999 my($self, $conn, $user_id_type, $user_id, $email) = @_;
4001 # Check to see if password reset requests are already being throttled:
4002 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4004 my $e = new_editor(xact => 1);
4007 # Get the user, if any, depending on the input value
4008 if ($user_id_type eq 'username') {
4009 $user = $e->search_actor_user({usrname => $user_id})->[0];
4012 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4014 } elsif ($user_id_type eq 'barcode') {
4015 my $card = $e->search_actor_card([
4016 {barcode => $user_id},
4017 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4020 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4025 # If the user doesn't have an email address, we can't help them
4026 if (!$user->email) {
4028 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4031 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4032 if ($email_must_match) {
4033 if (lc($user->email) ne lc($email)) {
4034 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4038 _reset_password_request($conn, $e, $user);
4041 # Once we have the user, we can issue the password reset request
4042 # XXX Add a wrapper method that accepts barcode + email input
4043 sub _reset_password_request {
4044 my ($conn, $e, $user) = @_;
4046 # 1. Get throttle threshold and time-to-live from OU_settings
4047 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4048 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4050 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4052 # 2. Get time of last request and number of active requests (num_active)
4053 my $active_requests = $e->json_query({
4059 transform => 'COUNT'
4062 column => 'request_time',
4068 has_been_reset => { '=' => 'f' },
4069 request_time => { '>' => $threshold_time }
4073 # Guard against no active requests
4074 if ($active_requests->[0]->{'request_time'}) {
4075 my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
4076 my $now = DateTime::Format::ISO8601->new();
4078 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4079 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4080 ($last_request->add_duration('1 minute') > $now)) {
4081 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4083 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4087 # TODO Check to see if the user is in a password-reset-restricted group
4089 # Otherwise, go ahead and try to get the user.
4091 # Check the number of active requests for this user
4092 $active_requests = $e->json_query({
4098 transform => 'COUNT'
4103 usr => { '=' => $user->id },
4104 has_been_reset => { '=' => 'f' },
4105 request_time => { '>' => $threshold_time }
4109 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4111 # if less than or equal to per-user threshold, proceed; otherwise, return event
4112 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4113 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4115 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4118 # Create the aupr object and insert into the database
4119 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4120 my $uuid = create_uuid_as_string(UUID_V4);
4121 $reset_request->uuid($uuid);
4122 $reset_request->usr($user->id);
4124 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4127 # Create an event to notify user of the URL to reset their password
4129 # Can we stuff this in the user_data param for trigger autocreate?
4130 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4132 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4133 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4136 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4141 __PACKAGE__->register_method(
4142 method => "commit_password_reset",
4143 api_name => "open-ils.actor.patron.password_reset.commit",
4145 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4146 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4147 "with the supplied password.",
4149 { desc => 'uuid', type => 'string' },
4150 { desc => 'password', type => 'string' },
4152 return => {desc => '1 on success, Event on error'}
4155 sub commit_password_reset {
4156 my($self, $conn, $uuid, $password) = @_;
4158 # Check to see if password reset requests are already being throttled:
4159 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4160 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4161 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4163 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4166 my $e = new_editor(xact => 1);
4168 my $aupr = $e->search_actor_usr_password_reset({
4175 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4177 my $user_id = $aupr->[0]->usr;
4178 my $user = $e->retrieve_actor_user($user_id);
4180 # Ensure we're still within the TTL for the request
4181 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4182 my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4183 if ($threshold < DateTime->now(time_zone => 'local')) {
4185 $logger->info("Password reset request needed to be submitted before $threshold");
4186 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4189 # Check complexity of password against OU-defined regex
4190 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4194 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4195 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4196 $is_strong = check_password_strength_custom($password, $pw_regex);
4198 $is_strong = check_password_strength_default($password);
4203 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4206 # All is well; update the password
4207 modify_migrated_user_password($e, $user->id, $password);
4209 # And flag that this password reset request has been honoured
4210 $aupr->[0]->has_been_reset('t');
4211 $e->update_actor_usr_password_reset($aupr->[0]);
4217 sub check_password_strength_default {
4218 my $password = shift;
4219 # Use the default set of checks
4220 if ( (length($password) < 7) or
4221 ($password !~ m/.*\d+.*/) or
4222 ($password !~ m/.*[A-Za-z]+.*/)
4229 sub check_password_strength_custom {
4230 my ($password, $pw_regex) = @_;
4232 $pw_regex = qr/$pw_regex/;
4233 if ($password !~ /$pw_regex/) {
4241 __PACKAGE__->register_method(
4242 method => "event_def_opt_in_settings",
4243 api_name => "open-ils.actor.event_def.opt_in.settings",
4246 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4248 { desc => 'Authentication token', type => 'string'},
4250 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4255 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4262 sub event_def_opt_in_settings {
4263 my($self, $conn, $auth, $org_id) = @_;
4264 my $e = new_editor(authtoken => $auth);
4265 return $e->event unless $e->checkauth;
4267 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4268 return $e->event unless
4269 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4271 $org_id = $e->requestor->home_ou;
4274 # find all config.user_setting_type's related to event_defs for the requested org unit
4275 my $types = $e->json_query({
4276 select => {cust => ['name']},
4277 from => {atevdef => 'cust'},
4280 owner => $U->get_org_ancestors($org_id), # context org plus parents
4287 $conn->respond($_) for
4288 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4295 __PACKAGE__->register_method(
4296 method => "user_circ_history",
4297 api_name => "open-ils.actor.history.circ",
4301 desc => 'Returns user circ history objects for the calling user',
4303 { desc => 'Authentication token', type => 'string'},
4304 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4307 desc => q/Stream of 'auch' circ history objects/,
4313 __PACKAGE__->register_method(
4314 method => "user_circ_history",
4315 api_name => "open-ils.actor.history.circ.clear",
4318 desc => 'Delete all user circ history entries for the calling user',
4320 { desc => 'Authentication token', type => 'string'},
4321 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4324 desc => q/1 on success, event on error/,
4330 __PACKAGE__->register_method(
4331 method => "user_circ_history",
4332 api_name => "open-ils.actor.history.circ.print",
4335 desc => q/Returns printable output for the caller's circ history objects/,
4337 { desc => 'Authentication token', type => 'string'},
4338 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4341 desc => q/An action_trigger.event object or error event./,
4347 __PACKAGE__->register_method(
4348 method => "user_circ_history",
4349 api_name => "open-ils.actor.history.circ.email",
4352 desc => q/Emails the caller's circ history/,
4354 { desc => 'Authentication token', type => 'string'},
4355 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4356 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4359 desc => q/undef, or event on error/
4364 sub user_circ_history {
4365 my ($self, $conn, $auth, $options) = @_;
4368 my $for_print = ($self->api_name =~ /print/);
4369 my $for_email = ($self->api_name =~ /email/);
4370 my $for_clear = ($self->api_name =~ /clear/);
4372 # No perm check is performed. Caller may only access his/her own
4373 # circ history entries.
4374 my $e = new_editor(authtoken => $auth);
4375 return $e->event unless $e->checkauth;
4378 if (!$for_clear) { # clear deletes all
4379 $limits{offset} = $options->{offset} if defined $options->{offset};
4380 $limits{limit} = $options->{limit} if defined $options->{limit};
4383 my %circ_id_filter = $options->{circ_ids} ?
4384 (id => $options->{circ_ids}) : ();
4386 my $circs = $e->search_action_user_circ_history([
4387 { usr => $e->requestor->id,
4390 { # order newest to oldest by default
4391 order_by => {auch => 'xact_start DESC'},
4394 {substream => 1} # could be a large list
4398 return $U->fire_object_event(undef,
4399 'circ.format.history.print', $circs, $e->requestor->home_ou);
4402 $e->xact_begin if $for_clear;
4403 $conn->respond_complete(1) if $for_email; # no sense in waiting
4405 for my $circ (@$circs) {
4408 # events will be fired from action_trigger_runner
4409 $U->create_events_for_hook('circ.format.history.email',
4410 $circ, $e->editor->home_ou, undef, undef, 1);
4412 } elsif ($for_clear) {
4414 $e->delete_action_user_circ_history($circ)
4415 or return $e->die_event;
4418 $conn->respond($circ);
4431 __PACKAGE__->register_method(
4432 method => "user_visible_holds",
4433 api_name => "open-ils.actor.history.hold.visible",
4436 desc => 'Returns the set of opt-in visible holds',
4438 { desc => 'Authentication token', type => 'string'},
4439 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4440 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4443 desc => q/An object with 1 field: "hold"/,
4449 __PACKAGE__->register_method(
4450 method => "user_visible_holds",
4451 api_name => "open-ils.actor.history.hold.visible.print",
4454 desc => 'Returns printable output for the set of opt-in visible holds',
4456 { desc => 'Authentication token', type => 'string'},
4457 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4458 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4461 desc => q/An action_trigger.event object or error event./,
4467 __PACKAGE__->register_method(
4468 method => "user_visible_holds",
4469 api_name => "open-ils.actor.history.hold.visible.email",
4472 desc => 'Emails the set of opt-in visible holds to the requestor',
4474 { desc => 'Authentication token', type => 'string'},
4475 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4476 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4479 desc => q/undef, or event on error/
4484 sub user_visible_holds {
4485 my($self, $conn, $auth, $user_id, $options) = @_;
4488 my $for_print = ($self->api_name =~ /print/);
4489 my $for_email = ($self->api_name =~ /email/);
4490 my $e = new_editor(authtoken => $auth);
4491 return $e->event unless $e->checkauth;
4493 $user_id ||= $e->requestor->id;
4495 $options->{limit} ||= 50;
4496 $options->{offset} ||= 0;
4498 if($user_id != $e->requestor->id) {
4499 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4500 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4501 return $e->event unless $e->allowed($perm, $user->home_ou);
4504 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4506 my $data = $e->json_query({
4507 from => [$db_func, $user_id],
4508 limit => $$options{limit},
4509 offset => $$options{offset}
4511 # TODO: I only want IDs. code below didn't get me there
4512 # {"select":{"au":[{"column":"id", "result_field":"id",
4513 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4518 return undef unless @$data;
4522 # collect the batch of objects
4526 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4527 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4531 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4532 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4535 } elsif ($for_email) {
4537 $conn->respond_complete(1) if $for_email; # no sense in waiting
4545 my $hold = $e->retrieve_action_hold_request($id);
4546 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4547 # events will be fired from action_trigger_runner
4551 my $circ = $e->retrieve_action_circulation($id);
4552 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4553 # events will be fired from action_trigger_runner
4557 } else { # just give me the data please
4565 my $hold = $e->retrieve_action_hold_request($id);
4566 $conn->respond({hold => $hold});
4570 my $circ = $e->retrieve_action_circulation($id);
4573 summary => $U->create_circ_chain_summary($e, $id)
4582 __PACKAGE__->register_method(
4583 method => "user_saved_search_cud",
4584 api_name => "open-ils.actor.user.saved_search.cud",
4587 desc => 'Create/Update/Delete Access to user saved searches',
4589 { desc => 'Authentication token', type => 'string' },
4590 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4593 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4599 __PACKAGE__->register_method(
4600 method => "user_saved_search_cud",
4601 api_name => "open-ils.actor.user.saved_search.retrieve",
4604 desc => 'Retrieve a saved search object',
4606 { desc => 'Authentication token', type => 'string' },
4607 { desc => 'Saved Search ID', type => 'number' }
4610 desc => q/The saved search object, Event on error/,
4616 sub user_saved_search_cud {
4617 my( $self, $client, $auth, $search ) = @_;
4618 my $e = new_editor( authtoken=>$auth );
4619 return $e->die_event unless $e->checkauth;
4621 my $o_search; # prior version of the object, if any
4622 my $res; # to be returned
4624 # branch on the operation type
4626 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4628 # Get the old version, to check ownership
4629 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4630 or return $e->die_event;
4632 # You can't read somebody else's search
4633 return OpenILS::Event->new('BAD_PARAMS')
4634 unless $o_search->owner == $e->requestor->id;
4640 $e->xact_begin; # start an editor transaction
4642 if( $search->isnew ) { # Create
4644 # You can't create a search for somebody else
4645 return OpenILS::Event->new('BAD_PARAMS')
4646 unless $search->owner == $e->requestor->id;
4648 $e->create_actor_usr_saved_search( $search )
4649 or return $e->die_event;
4653 } elsif( $search->ischanged ) { # Update
4655 # You can't change ownership of a search
4656 return OpenILS::Event->new('BAD_PARAMS')
4657 unless $search->owner == $e->requestor->id;
4659 # Get the old version, to check ownership
4660 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4661 or return $e->die_event;
4663 # You can't update somebody else's search
4664 return OpenILS::Event->new('BAD_PARAMS')
4665 unless $o_search->owner == $e->requestor->id;
4668 $e->update_actor_usr_saved_search( $search )
4669 or return $e->die_event;
4673 } elsif( $search->isdeleted ) { # Delete
4675 # Get the old version, to check ownership
4676 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4677 or return $e->die_event;
4679 # You can't delete somebody else's search
4680 return OpenILS::Event->new('BAD_PARAMS')
4681 unless $o_search->owner == $e->requestor->id;
4684 $e->delete_actor_usr_saved_search( $o_search )
4685 or return $e->die_event;
4696 __PACKAGE__->register_method(
4697 method => "get_barcodes",
4698 api_name => "open-ils.actor.get_barcodes"
4702 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4703 my $e = new_editor(authtoken => $auth);
4704 return $e->event unless $e->checkauth;
4705 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4707 my $db_result = $e->json_query(
4709 'evergreen.get_barcodes',
4710 $org_id, $context, $barcode,
4714 if($context =~ /actor/) {
4715 my $filter_result = ();
4717 foreach my $result (@$db_result) {
4718 if($result->{type} eq 'actor') {
4719 if($e->requestor->id != $result->{id}) {
4720 $patron = $e->retrieve_actor_user($result->{id});
4722 push(@$filter_result, $e->event);
4725 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4726 push(@$filter_result, $result);
4729 push(@$filter_result, $e->event);
4733 push(@$filter_result, $result);
4737 push(@$filter_result, $result);
4740 return $filter_result;
4746 __PACKAGE__->register_method(
4747 method => 'address_alert_test',
4748 api_name => 'open-ils.actor.address_alert.test',
4750 desc => "Tests a set of address fields to determine if they match with an address_alert",
4752 {desc => 'Authentication token', type => 'string'},
4753 {desc => 'Org Unit', type => 'number'},
4754 {desc => 'Fields', type => 'hash'},
4756 return => {desc => 'List of matching address_alerts'}
4760 sub address_alert_test {
4761 my ($self, $client, $auth, $org_unit, $fields) = @_;
4762 return [] unless $fields and grep {$_} values %$fields;
4764 my $e = new_editor(authtoken => $auth);
4765 return $e->event unless $e->checkauth;
4766 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4767 $org_unit ||= $e->requestor->ws_ou;
4769 my $alerts = $e->json_query({
4771 'actor.address_alert_matches',
4779 $$fields{post_code},
4780 $$fields{mailing_address},
4781 $$fields{billing_address}
4785 # map the json_query hashes to real objects
4787 map {$e->retrieve_actor_address_alert($_)}
4788 (map {$_->{id}} @$alerts)
4792 __PACKAGE__->register_method(
4793 method => "mark_users_contact_invalid",
4794 api_name => "open-ils.actor.invalidate.email",
4796 desc => "Given a patron or email address, clear the email field for one patron or all patrons with that email address and put the old email address into a note and/or create a standing penalty, depending on OU settings",
4798 {desc => "Authentication token", type => "string"},
4799 {desc => "Patron ID (optional if Email address specified)", type => "number"},
4800 {desc => "Additional note text (optional)", type => "string"},
4801 {desc => "penalty org unit ID (optional)", type => "number"},
4802 {desc => "Email address (optional)", type => "string"}
4804 return => {desc => "Event describing success or failure", type => "object"}
4808 __PACKAGE__->register_method(
4809 method => "mark_users_contact_invalid",
4810 api_name => "open-ils.actor.invalidate.day_phone",
4812 desc => "Given a patron or phone number, clear the day_phone field for one patron or all patrons with that day_phone number and put the old day_phone into a note and/or create a standing penalty, depending on OU settings",
4814 {desc => "Authentication token", type => "string"},
4815 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4816 {desc => "Additional note text (optional)", type => "string"},
4817 {desc => "penalty org unit ID (optional)", type => "number"},
4818 {desc => "Phone Number (optional)", type => "string"}
4820 return => {desc => "Event describing success or failure", type => "object"}
4824 __PACKAGE__->register_method(
4825 method => "mark_users_contact_invalid",
4826 api_name => "open-ils.actor.invalidate.evening_phone",
4828 desc => "Given a patron or phone number, clear the evening_phone field for one patron or all patrons with that evening_phone number and put the old evening_phone into a note and/or create a standing penalty, depending on OU settings",
4830 {desc => "Authentication token", type => "string"},
4831 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4832 {desc => "Additional note text (optional)", type => "string"},
4833 {desc => "penalty org unit ID (optional)", type => "number"},
4834 {desc => "Phone Number (optional)", type => "string"}
4836 return => {desc => "Event describing success or failure", type => "object"}
4840 __PACKAGE__->register_method(
4841 method => "mark_users_contact_invalid",
4842 api_name => "open-ils.actor.invalidate.other_phone",
4844 desc => "Given a patron or phone number, clear the other_phone field for one patron or all patrons with that other_phone number and put the old other_phone into a note and/or create a standing penalty, depending on OU settings",
4846 {desc => "Authentication token", type => "string"},
4847 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4848 {desc => "Additional note text (optional)", type => "string"},
4849 {desc => "penalty org unit ID (optional, default to top of org tree)",
4851 {desc => "Phone Number (optional)", type => "string"}
4853 return => {desc => "Event describing success or failure", type => "object"}
4857 sub mark_users_contact_invalid {
4858 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
4860 # This method invalidates an email address or a phone_number which
4861 # removes the bad email address or phone number, copying its contents
4862 # to a patron note, and institutes a standing penalty for "bad email"
4863 # or "bad phone number" which is cleared when the user is saved or
4864 # optionally only when the user is saved with an email address or
4865 # phone number (or staff manually delete the penalty).
4867 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4869 my $e = new_editor(authtoken => $auth, xact => 1);
4870 return $e->die_event unless $e->checkauth;
4873 if (defined $patron_id && $patron_id ne "") {
4874 $howfind = {usr => $patron_id};
4875 } elsif (defined $contact && $contact ne "") {
4876 $howfind = {$contact_type => $contact};
4878 # Error out if no patron id set or no contact is set.
4879 return OpenILS::Event->new('BAD_PARAMS');
4882 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4883 $e, $contact_type, $howfind,
4884 $addl_note, $penalty_ou, $e->requestor->id
4888 # Putting the following method in open-ils.actor is a bad fit, except in that
4889 # it serves an interface that lives under 'actor' in the templates directory,
4890 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4892 __PACKAGE__->register_method(
4893 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4894 method => "get_all_at_reactors_in_use",
4899 { name => 'authtoken', type => 'string' }
4902 desc => 'list of reactor names', type => 'array'
4907 sub get_all_at_reactors_in_use {
4908 my ($self, $conn, $auth) = @_;
4910 my $e = new_editor(authtoken => $auth);
4911 $e->checkauth or return $e->die_event;
4912 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4914 my $reactors = $e->json_query({
4916 atevdef => [{column => "reactor", transform => "distinct"}]
4918 from => {atevdef => {}}
4921 return $e->die_event unless ref $reactors eq "ARRAY";
4924 return [ map { $_->{reactor} } @$reactors ];
4927 __PACKAGE__->register_method(
4928 method => "filter_group_entry_crud",
4929 api_name => "open-ils.actor.filter_group_entry.crud",
4932 Provides CRUD access to filter group entry objects. These are not full accessible
4933 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4934 are not accessible via PCRUD (because they have no fields against which to link perms)
4937 {desc => "Authentication token", type => "string"},
4938 {desc => "Entry ID / Entry Object", type => "number"},
4939 {desc => "Additional note text (optional)", type => "string"},
4940 {desc => "penalty org unit ID (optional, default to top of org tree)",
4944 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4950 sub filter_group_entry_crud {
4951 my ($self, $conn, $auth, $arg) = @_;
4953 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4954 my $e = new_editor(authtoken => $auth, xact => 1);
4955 return $e->die_event unless $e->checkauth;
4961 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4962 or return $e->die_event;
4964 return $e->die_event unless $e->allowed(
4965 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4967 my $query = $arg->query;
4968 $query = $e->create_actor_search_query($query) or return $e->die_event;
4969 $arg->query($query->id);
4970 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4971 $entry->query($query);
4976 } elsif ($arg->ischanged) {
4978 my $entry = $e->retrieve_actor_search_filter_group_entry([
4981 flesh_fields => {asfge => ['grp']}
4983 ]) or return $e->die_event;
4985 return $e->die_event unless $e->allowed(
4986 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4988 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4989 $arg->query($arg->query->id);
4990 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4991 $arg->query($query);
4996 } elsif ($arg->isdeleted) {
4998 my $entry = $e->retrieve_actor_search_filter_group_entry([
5001 flesh_fields => {asfge => ['grp', 'query']}
5003 ]) or return $e->die_event;
5005 return $e->die_event unless $e->allowed(
5006 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5008 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5009 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5022 my $entry = $e->retrieve_actor_search_filter_group_entry([
5025 flesh_fields => {asfge => ['grp', 'query']}
5027 ]) or return $e->die_event;
5029 return $e->die_event unless $e->allowed(
5030 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5031 $entry->grp->owner);
5034 $entry->grp($entry->grp->id); # for consistency