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 OpenILS::Utils::DateTime 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 # Flush the response stream at most 5 patrons in for UI responsiveness.
1327 max_bundle_count => 5,
1329 desc => q/Returns a stream of fleshed user objects instead of
1330 a pile of identifiers/
1334 sub patron_adv_search {
1335 my( $self, $client, $auth, $search_hash, $search_limit,
1336 $search_sort, $include_inactive, $search_ou, $flesh_fields, $offset) = @_;
1338 # API params sanity checks.
1339 # Exit early with empty result if no filter exists.
1340 # .fleshed call is streaming. Non-fleshed is effectively atomic.
1341 my $fleshed = ($self->api_name =~ /fleshed/);
1342 return ($fleshed ? undef : []) unless (ref $search_hash ||'') eq 'HASH';
1344 for my $key (keys %$search_hash) {
1345 next if $search_hash->{$key}{value} =~ /^\s*$/; # empty filter
1349 return ($fleshed ? undef : []) unless $search_ok;
1351 my $e = new_editor(authtoken=>$auth);
1352 return $e->event unless $e->checkauth;
1353 return $e->event unless $e->allowed('VIEW_USER');
1355 # depth boundary outside of which patrons must opt-in, default to 0
1356 my $opt_boundary = 0;
1357 $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary') if user_opt_in_enabled($self);
1359 if (not defined $search_ou) {
1360 my $depth = $U->ou_ancestor_setting_value(
1361 $e->requestor->ws_ou,
1362 'circ.patron_edit.duplicate_patron_check_depth'
1365 if (defined $depth) {
1366 $search_ou = $U->org_unit_ancestor_at_depth(
1367 $e->requestor->ws_ou, $depth
1372 my $ids = $U->storagereq(
1373 "open-ils.storage.actor.user.crazy_search", $search_hash,
1374 $search_limit, $search_sort, $include_inactive,
1375 $e->requestor->ws_ou, $search_ou, $opt_boundary, $offset);
1377 return $ids unless $self->api_name =~ /fleshed/;
1379 $client->respond(new_flesh_user($_, $flesh_fields, $e)) for @$ids;
1385 # A migrated (main) password has the form:
1386 # CRYPT( MD5( pw_salt || MD5(real_password) ), pw_salt )
1387 sub modify_migrated_user_password {
1388 my ($e, $user_id, $passwd) = @_;
1390 # new password gets a new salt
1391 my $new_salt = $e->json_query({
1392 from => ['actor.create_salt', 'main']})->[0];
1393 $new_salt = $new_salt->{'actor.create_salt'};
1400 md5_hex($new_salt . md5_hex($passwd)),
1408 __PACKAGE__->register_method(
1409 method => "update_passwd",
1410 api_name => "open-ils.actor.user.password.update",
1412 desc => "Update the operator's password",
1414 { desc => 'Authentication token', type => 'string' },
1415 { desc => 'New password', type => 'string' },
1416 { desc => 'Current password', type => 'string' }
1418 return => {desc => '1 on success, Event on error or incorrect current password'}
1422 __PACKAGE__->register_method(
1423 method => "update_passwd",
1424 api_name => "open-ils.actor.user.username.update",
1426 desc => "Update the operator's username",
1428 { desc => 'Authentication token', type => 'string' },
1429 { desc => 'New username', type => 'string' },
1430 { desc => 'Current password', type => 'string' }
1432 return => {desc => '1 on success, Event on error or incorrect current password'}
1436 __PACKAGE__->register_method(
1437 method => "update_passwd",
1438 api_name => "open-ils.actor.user.email.update",
1440 desc => "Update the operator's email address",
1442 { desc => 'Authentication token', type => 'string' },
1443 { desc => 'New email address', type => 'string' },
1444 { desc => 'Current password', type => 'string' }
1446 return => {desc => '1 on success, Event on error or incorrect current password'}
1451 my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1452 my $e = new_editor(xact=>1, authtoken=>$auth);
1453 return $e->die_event unless $e->checkauth;
1455 my $db_user = $e->retrieve_actor_user($e->requestor->id)
1456 or return $e->die_event;
1457 my $api = $self->api_name;
1459 if (!$U->verify_migrated_user_password($e, $db_user->id, $orig_pw)) {
1461 return new OpenILS::Event('INCORRECT_PASSWORD');
1464 if( $api =~ /password/o ) {
1465 # NOTE: with access to the plain text password we could crypt
1466 # the password without the extra MD5 pre-hashing. Other changes
1467 # would be required. Noting here for future reference.
1468 modify_migrated_user_password($e, $db_user->id, $new_val);
1469 $db_user->passwd('');
1473 # if we don't clear the password, the user will be updated with
1474 # a hashed version of the hashed version of their password
1475 $db_user->clear_passwd;
1477 if( $api =~ /username/o ) {
1479 # make sure no one else has this username
1480 my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1});
1483 return new OpenILS::Event('USERNAME_EXISTS');
1485 $db_user->usrname($new_val);
1487 } elsif( $api =~ /email/o ) {
1488 $db_user->email($new_val);
1492 $e->update_actor_user($db_user) or return $e->die_event;
1495 # update the cached user to pick up these changes
1496 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1);
1502 __PACKAGE__->register_method(
1503 method => "check_user_perms",
1504 api_name => "open-ils.actor.user.perm.check",
1505 notes => <<" NOTES");
1506 Takes a login session, user id, an org id, and an array of perm type strings. For each
1507 perm type, if the user does *not* have the given permission it is added
1508 to a list which is returned from the method. If all permissions
1509 are allowed, an empty list is returned
1510 if the logged in user does not match 'user_id', then the logged in user must
1511 have VIEW_PERMISSION priveleges.
1514 sub check_user_perms {
1515 my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1517 my( $staff, $evt ) = $apputils->checkses($login_session);
1518 return $evt if $evt;
1520 if($staff->id ne $user_id) {
1521 if( $evt = $apputils->check_perms(
1522 $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1528 for my $perm (@$perm_types) {
1529 if($apputils->check_perms($user_id, $org_id, $perm)) {
1530 push @not_allowed, $perm;
1534 return \@not_allowed
1537 __PACKAGE__->register_method(
1538 method => "check_user_perms2",
1539 api_name => "open-ils.actor.user.perm.check.multi_org",
1541 Checks the permissions on a list of perms and orgs for a user
1542 @param authtoken The login session key
1543 @param user_id The id of the user to check
1544 @param orgs The array of org ids
1545 @param perms The array of permission names
1546 @return An array of [ orgId, permissionName ] arrays that FAILED the check
1547 if the logged in user does not match 'user_id', then the logged in user must
1548 have VIEW_PERMISSION priveleges.
1551 sub check_user_perms2 {
1552 my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1554 my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1555 $authtoken, $user_id, 'VIEW_PERMISSION' );
1556 return $evt if $evt;
1559 for my $org (@$orgs) {
1560 for my $perm (@$perms) {
1561 if($apputils->check_perms($user_id, $org, $perm)) {
1562 push @not_allowed, [ $org, $perm ];
1567 return \@not_allowed
1571 __PACKAGE__->register_method(
1572 method => 'check_user_perms3',
1573 api_name => 'open-ils.actor.user.perm.highest_org',
1575 Returns the highest org unit id at which a user has a given permission
1576 If the requestor does not match the target user, the requestor must have
1577 'VIEW_PERMISSION' rights at the home org unit of the target user
1578 @param authtoken The login session key
1579 @param userid The id of the user in question
1580 @param perm The permission to check
1581 @return The org unit highest in the org tree within which the user has
1582 the requested permission
1585 sub check_user_perms3 {
1586 my($self, $client, $authtoken, $user_id, $perm) = @_;
1587 my $e = new_editor(authtoken=>$authtoken);
1588 return $e->event unless $e->checkauth;
1590 my $tree = $U->get_org_tree();
1592 unless($e->requestor->id == $user_id) {
1593 my $user = $e->retrieve_actor_user($user_id)
1594 or return $e->event;
1595 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1596 return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1599 return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1602 __PACKAGE__->register_method(
1603 method => 'user_has_work_perm_at',
1604 api_name => 'open-ils.actor.user.has_work_perm_at',
1608 Returns a set of org unit IDs which represent the highest orgs in
1609 the org tree where the user has the requested permission. The
1610 purpose of this method is to return the smallest set of org units
1611 which represent the full expanse of the user's ability to perform
1612 the requested action. The user whose perms this method should
1613 check is implied by the authtoken. /,
1615 {desc => 'authtoken', type => 'string'},
1616 {desc => 'permission name', type => 'string'},
1617 {desc => q/user id, optional. If present, check perms for
1618 this user instead of the logged in user/, type => 'number'},
1620 return => {desc => 'An array of org IDs'}
1624 sub user_has_work_perm_at {
1625 my($self, $conn, $auth, $perm, $user_id) = @_;
1626 my $e = new_editor(authtoken=>$auth);
1627 return $e->event unless $e->checkauth;
1628 if(defined $user_id) {
1629 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1630 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1632 return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1635 __PACKAGE__->register_method(
1636 method => 'user_has_work_perm_at_batch',
1637 api_name => 'open-ils.actor.user.has_work_perm_at.batch',
1641 sub user_has_work_perm_at_batch {
1642 my($self, $conn, $auth, $perms, $user_id) = @_;
1643 my $e = new_editor(authtoken=>$auth);
1644 return $e->event unless $e->checkauth;
1645 if(defined $user_id) {
1646 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1647 return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1650 $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1656 __PACKAGE__->register_method(
1657 method => 'check_user_perms4',
1658 api_name => 'open-ils.actor.user.perm.highest_org.batch',
1660 Returns the highest org unit id at which a user has a given permission
1661 If the requestor does not match the target user, the requestor must have
1662 'VIEW_PERMISSION' rights at the home org unit of the target user
1663 @param authtoken The login session key
1664 @param userid The id of the user in question
1665 @param perms An array of perm names to check
1666 @return An array of orgId's representing the org unit
1667 highest in the org tree within which the user has the requested permission
1668 The arrah of orgId's has matches the order of the perms array
1671 sub check_user_perms4 {
1672 my( $self, $client, $authtoken, $userid, $perms ) = @_;
1674 my( $staff, $target, $org, $evt );
1676 ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1677 $authtoken, $userid, 'VIEW_PERMISSION' );
1678 return $evt if $evt;
1681 return [] unless ref($perms);
1682 my $tree = $U->get_org_tree();
1684 for my $p (@$perms) {
1685 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1691 __PACKAGE__->register_method(
1692 method => "user_fines_summary",
1693 api_name => "open-ils.actor.user.fines.summary",
1696 desc => 'Returns a short summary of the users total open fines, ' .
1697 'excluding voided fines Params are login_session, user_id' ,
1699 {desc => 'Authentication token', type => 'string'},
1700 {desc => 'User ID', type => 'string'} # number?
1703 desc => "a 'mous' object, event on error",
1708 sub user_fines_summary {
1709 my( $self, $client, $auth, $user_id ) = @_;
1711 my $e = new_editor(authtoken=>$auth);
1712 return $e->event unless $e->checkauth;
1714 if( $user_id ne $e->requestor->id ) {
1715 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1716 return $e->event unless
1717 $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1720 return $e->search_money_open_user_summary({usr => $user_id})->[0];
1724 __PACKAGE__->register_method(
1725 method => "user_opac_vitals",
1726 api_name => "open-ils.actor.user.opac.vital_stats",
1730 desc => 'Returns a short summary of the users vital stats, including ' .
1731 'identification information, accumulated balance, number of holds, ' .
1732 'and current open circulation stats' ,
1734 {desc => 'Authentication token', type => 'string'},
1735 {desc => 'Optional User ID, for use in the staff client', type => 'number'} # number?
1738 desc => "An object with four properties: user, fines, checkouts and holds."
1743 sub user_opac_vitals {
1744 my( $self, $client, $auth, $user_id ) = @_;
1746 my $e = new_editor(authtoken=>$auth);
1747 return $e->event unless $e->checkauth;
1749 $user_id ||= $e->requestor->id;
1751 my $user = $e->retrieve_actor_user( $user_id );
1754 ->method_lookup('open-ils.actor.user.fines.summary')
1755 ->run($auth => $user_id);
1756 return $fines if (defined($U->event_code($fines)));
1759 $fines = new Fieldmapper::money::open_user_summary ();
1760 $fines->balance_owed(0.00);
1761 $fines->total_owed(0.00);
1762 $fines->total_paid(0.00);
1763 $fines->usr($user_id);
1767 ->method_lookup('open-ils.actor.user.hold_requests.count')
1768 ->run($auth => $user_id);
1769 return $holds if (defined($U->event_code($holds)));
1772 ->method_lookup('open-ils.actor.user.checked_out.count')
1773 ->run($auth => $user_id);
1774 return $out if (defined($U->event_code($out)));
1776 $out->{"total_out"} = reduce { $a + $out->{$b} } 0, qw/out overdue/;
1778 my $unread_msgs = $e->search_actor_usr_message([
1779 {usr => $user_id, read_date => undef, deleted => 'f'},
1785 first_given_name => $user->first_given_name,
1786 second_given_name => $user->second_given_name,
1787 family_name => $user->family_name,
1788 alias => $user->alias,
1789 usrname => $user->usrname
1791 fines => $fines->to_bare_hash,
1794 messages => { unread => scalar(@$unread_msgs) }
1799 ##### a small consolidation of related method registrations
1800 my $common_params = [
1801 { desc => 'Authentication token', type => 'string' },
1802 { desc => 'User ID', type => 'string' },
1803 { desc => 'Transactions type (optional, defaults to all)', type => 'string' },
1804 { desc => 'Options hash. May contain limit and offset for paged results.', type => 'object' },
1807 'open-ils.actor.user.transactions' => '',
1808 'open-ils.actor.user.transactions.fleshed' => '',
1809 'open-ils.actor.user.transactions.have_charge' => ' that have an initial charge',
1810 'open-ils.actor.user.transactions.have_charge.fleshed' => ' that have an initial charge',
1811 'open-ils.actor.user.transactions.have_balance' => ' that have an outstanding balance',
1812 'open-ils.actor.user.transactions.have_balance.fleshed' => ' that have an outstanding balance',
1815 foreach (keys %methods) {
1817 method => "user_transactions",
1820 desc => 'For a given user, retrieve a list of '
1821 . (/\.fleshed/ ? 'fleshed ' : '')
1822 . 'transactions' . $methods{$_}
1823 . ' optionally limited to transactions of a given type.',
1824 params => $common_params,
1826 desc => "List of objects, or event on error. Each object is a hash containing: transaction, circ, record. "
1827 . 'These represent the relevant (mbts) transaction, attached circulation and title pointed to in the circ, respectively.',
1831 $args{authoritative} = 1;
1832 __PACKAGE__->register_method(%args);
1835 # Now for the counts
1837 'open-ils.actor.user.transactions.count' => '',
1838 'open-ils.actor.user.transactions.have_charge.count' => ' that have an initial charge',
1839 'open-ils.actor.user.transactions.have_balance.count' => ' that have an outstanding balance',
1842 foreach (keys %methods) {
1844 method => "user_transactions",
1847 desc => 'For a given user, retrieve a count of open '
1848 . 'transactions' . $methods{$_}
1849 . ' optionally limited to transactions of a given type.',
1850 params => $common_params,
1851 return => { desc => "Integer count of transactions, or event on error" }
1854 /\.have_balance/ and $args{authoritative} = 1; # FIXME: I don't know why have_charge isn't authoritative
1855 __PACKAGE__->register_method(%args);
1858 __PACKAGE__->register_method(
1859 method => "user_transactions",
1860 api_name => "open-ils.actor.user.transactions.have_balance.total",
1863 desc => 'For a given user, retrieve the total balance owed for open transactions,'
1864 . ' optionally limited to transactions of a given type.',
1865 params => $common_params,
1866 return => { desc => "Decimal balance value, or event on error" }
1871 sub user_transactions {
1872 my( $self, $client, $auth, $user_id, $type, $options ) = @_;
1875 my $e = new_editor(authtoken => $auth);
1876 return $e->event unless $e->checkauth;
1878 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1880 return $e->event unless
1881 $e->requestor->id == $user_id or
1882 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
1884 my $api = $self->api_name();
1886 my $filter = ($api =~ /have_balance/o) ?
1887 { 'balance_owed' => { '<>' => 0 } }:
1888 { 'total_owed' => { '>' => 0 } };
1890 my $method = 'open-ils.actor.user.transactions.history.still_open';
1891 $method = "$method.authoritative" if $api =~ /authoritative/;
1892 my ($trans) = $self->method_lookup($method)->run($auth, $user_id, $type, $filter, $options);
1894 if($api =~ /total/o) {
1896 $total += $_->balance_owed for @$trans;
1900 ($api =~ /count/o ) and return scalar @$trans;
1901 ($api !~ /fleshed/o) and return $trans;
1904 for my $t (@$trans) {
1906 if( $t->xact_type ne 'circulation' ) {
1907 push @resp, {transaction => $t};
1911 my $circ_data = flesh_circ($e, $t->id);
1912 push @resp, {transaction => $t, %$circ_data};
1919 __PACKAGE__->register_method(
1920 method => "user_transaction_retrieve",
1921 api_name => "open-ils.actor.user.transaction.fleshed.retrieve",
1924 notes => "Returns a fleshed transaction record"
1927 __PACKAGE__->register_method(
1928 method => "user_transaction_retrieve",
1929 api_name => "open-ils.actor.user.transaction.retrieve",
1932 notes => "Returns a transaction record"
1935 sub user_transaction_retrieve {
1936 my($self, $client, $auth, $bill_id) = @_;
1938 my $e = new_editor(authtoken => $auth);
1939 return $e->event unless $e->checkauth;
1941 my $trans = $e->retrieve_money_billable_transaction_summary(
1942 [$bill_id, {flesh => 1, flesh_fields => {mbts => ['usr']}}]) or return $e->event;
1944 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $trans->usr->home_ou);
1946 $trans->usr($trans->usr->id); # de-flesh for backwards compat
1948 return $trans unless $self->api_name =~ /flesh/;
1949 return {transaction => $trans} if $trans->xact_type ne 'circulation';
1951 my $circ_data = flesh_circ($e, $trans->id, 1);
1953 return {transaction => $trans, %$circ_data};
1958 my $circ_id = shift;
1959 my $flesh_copy = shift;
1961 my $circ = $e->retrieve_action_circulation([
1965 circ => ['target_copy'],
1966 acp => ['call_number'],
1973 my $copy = $circ->target_copy;
1975 if($circ->target_copy->call_number->id == OILS_PRECAT_CALL_NUMBER) {
1976 $mods = new Fieldmapper::metabib::virtual_record;
1977 $mods->doc_id(OILS_PRECAT_RECORD);
1978 $mods->title($copy->dummy_title);
1979 $mods->author($copy->dummy_author);
1982 $mods = $U->record_to_mvr($circ->target_copy->call_number->record);
1986 $circ->target_copy($circ->target_copy->id);
1987 $copy->call_number($copy->call_number->id);
1989 return {circ => $circ, record => $mods, copy => ($flesh_copy) ? $copy : undef };
1993 __PACKAGE__->register_method(
1994 method => "hold_request_count",
1995 api_name => "open-ils.actor.user.hold_requests.count",
1999 Returns hold ready vs. total counts.
2000 If a context org unit is provided, a third value
2001 is returned with key 'behind_desk', which reports
2002 how many holds are ready at the pickup library
2003 with the behind_desk flag set to true.
2007 sub hold_request_count {
2008 my( $self, $client, $authtoken, $user_id, $ctx_org ) = @_;
2009 my $e = new_editor(authtoken => $authtoken);
2010 return $e->event unless $e->checkauth;
2012 $user_id = $e->requestor->id unless defined $user_id;
2014 if($e->requestor->id ne $user_id) {
2015 my $user = $e->retrieve_actor_user($user_id);
2016 return $e->event unless $e->allowed('VIEW_HOLD', $user->home_ou);
2019 my $holds = $e->json_query({
2020 select => {ahr => ['pickup_lib', 'current_shelf_lib', 'behind_desk']},
2024 fulfillment_time => {"=" => undef },
2025 cancel_time => undef,
2030 $_->{current_shelf_lib} and # avoid undef warnings
2031 $_->{pickup_lib} eq $_->{current_shelf_lib}
2035 total => scalar(@$holds),
2036 ready => scalar(@ready)
2040 # count of holds ready at pickup lib with behind_desk true.
2041 $resp->{behind_desk} = scalar(
2043 $_->{pickup_lib} == $ctx_org and
2044 $U->is_true($_->{behind_desk})
2052 __PACKAGE__->register_method(
2053 method => "checked_out",
2054 api_name => "open-ils.actor.user.checked_out",
2058 desc => "For a given user, returns a structure of circulations objects sorted by out, overdue, lost, claims_returned, long_overdue. "
2059 . "A list of IDs are returned of each type. Circs marked lost, long_overdue, and claims_returned will not be 'finished' "
2060 . "(i.e., outstanding balance or some other pending action on the circ). "
2061 . "The .count method also includes a 'total' field which sums all open circs.",
2063 { desc => 'Authentication Token', type => 'string'},
2064 { desc => 'User ID', type => 'string'},
2067 desc => 'Returns event on error, or an object with ID lists, like: '
2068 . '{"out":[12552,451232], "claims_returned":[], "long_overdue":[23421] "overdue":[], "lost":[]}'
2073 __PACKAGE__->register_method(
2074 method => "checked_out",
2075 api_name => "open-ils.actor.user.checked_out.count",
2078 signature => q/@see open-ils.actor.user.checked_out/
2082 my( $self, $conn, $auth, $userid ) = @_;
2084 my $e = new_editor(authtoken=>$auth);
2085 return $e->event unless $e->checkauth;
2087 if( $userid ne $e->requestor->id ) {
2088 my $user = $e->retrieve_actor_user($userid) or return $e->event;
2089 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
2091 # see if there is a friend link allowing circ.view perms
2092 my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
2093 $e, $userid, $e->requestor->id, 'circ.view');
2094 return $e->event unless $allowed;
2098 my $count = $self->api_name =~ /count/;
2099 return _checked_out( $count, $e, $userid );
2103 my( $iscount, $e, $userid ) = @_;
2109 claims_returned => [],
2112 my $meth = 'retrieve_action_open_circ_';
2120 claims_returned => 0,
2127 my $data = $e->$meth($userid);
2131 $result{$_} += $data->$_() for (keys %result);
2132 $result{total} += $data->$_() for (keys %result);
2134 for my $k (keys %result) {
2135 $result{$k} = [ grep { $_ > 0 } split( ',', $data->$k()) ];
2145 __PACKAGE__->register_method(
2146 method => "checked_in_with_fines",
2147 api_name => "open-ils.actor.user.checked_in_with_fines",
2150 signature => q/@see open-ils.actor.user.checked_out/
2153 sub checked_in_with_fines {
2154 my( $self, $conn, $auth, $userid ) = @_;
2156 my $e = new_editor(authtoken=>$auth);
2157 return $e->event unless $e->checkauth;
2159 if( $userid ne $e->requestor->id ) {
2160 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2163 # money is owed on these items and they are checked in
2164 my $open = $e->search_action_circulation(
2167 xact_finish => undef,
2168 checkin_time => { "!=" => undef },
2173 my( @lost, @cr, @lo );
2174 for my $c (@$open) {
2175 push( @lost, $c->id ) if ($c->stop_fines eq 'LOST');
2176 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2177 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2182 claims_returned => \@cr,
2183 long_overdue => \@lo
2189 my ($api, $desc, $auth) = @_;
2190 $desc = $desc ? (" " . $desc) : '';
2191 my $ids = ($api =~ /ids$/) ? 1 : 0;
2194 method => "user_transaction_history",
2195 api_name => "open-ils.actor.user.transactions.$api",
2197 desc => "For a given User ID, returns a list of billable transaction" .
2198 ($ids ? " id" : '') .
2199 "s$desc, optionally filtered by type and/or fields in money.billable_xact_summary. " .
2200 "The VIEW_USER_TRANSACTIONS permission is required to view another user's transactions",
2202 {desc => 'Authentication token', type => 'string'},
2203 {desc => 'User ID', type => 'number'},
2204 {desc => 'Transaction type (optional)', type => 'number'},
2205 {desc => 'Hash of Billable Transaction Summary filters (optional)', type => 'object'}
2208 desc => 'List of transaction' . ($ids ? " id" : '') . 's, Event on error'
2212 $auth and push @sig, (authoritative => 1);
2216 my %auth_hist_methods = (
2218 'history.have_charge' => 'that have an initial charge',
2219 'history.still_open' => 'that are not finished',
2220 'history.have_balance' => 'that have a balance',
2221 'history.have_bill' => 'that have billings',
2222 'history.have_bill_or_payment' => 'that have non-zero-sum billings or at least 1 payment',
2223 'history.have_payment' => 'that have at least 1 payment',
2226 foreach (keys %auth_hist_methods) {
2227 __PACKAGE__->register_method(_sigmaker($_, $auth_hist_methods{$_}, 1));
2228 __PACKAGE__->register_method(_sigmaker("$_.ids", $auth_hist_methods{$_}, 1));
2229 __PACKAGE__->register_method(_sigmaker("$_.fleshed", $auth_hist_methods{$_}, 1));
2232 sub user_transaction_history {
2233 my( $self, $conn, $auth, $userid, $type, $filter, $options ) = @_;
2237 my $e = new_editor(authtoken=>$auth);
2238 return $e->die_event unless $e->checkauth;
2240 if ($e->requestor->id ne $userid) {
2241 return $e->die_event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2244 my $api = $self->api_name;
2245 my @xact_finish = (xact_finish => undef ) if ($api =~ /history\.still_open$/); # What about history.still_open.ids?
2247 if(defined($type)) {
2248 $filter->{'xact_type'} = $type;
2251 if($api =~ /have_bill_or_payment/o) {
2253 # transactions that have a non-zero sum across all billings or at least 1 payment
2254 $filter->{'-or'} = {
2255 'balance_owed' => { '<>' => 0 },
2256 'last_payment_ts' => { '<>' => undef }
2259 } elsif($api =~ /have_payment/) {
2261 $filter->{last_payment_ts} ||= {'<>' => undef};
2263 } elsif( $api =~ /have_balance/o) {
2265 # transactions that have a non-zero overall balance
2266 $filter->{'balance_owed'} = { '<>' => 0 };
2268 } elsif( $api =~ /have_charge/o) {
2270 # transactions that have at least 1 billing, regardless of whether it was voided
2271 $filter->{'last_billing_ts'} = { '<>' => undef };
2273 } elsif( $api =~ /have_bill/o) { # needs to be an elsif, or we double-match have_bill_or_payment!
2275 # transactions that have non-zero sum across all billings. This will exclude
2276 # xacts where all billings have been voided
2277 $filter->{'total_owed'} = { '<>' => 0 };
2280 my $options_clause = { order_by => { mbt => 'xact_start DESC' } };
2281 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2282 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2284 my $mbts = $e->search_money_billable_transaction_summary(
2285 [ { usr => $userid, @xact_finish, %$filter },
2290 return [map {$_->id} @$mbts] if $api =~ /\.ids/;
2291 return $mbts unless $api =~ /fleshed/;
2294 for my $t (@$mbts) {
2296 if( $t->xact_type ne 'circulation' ) {
2297 push @resp, {transaction => $t};
2301 my $circ_data = flesh_circ($e, $t->id);
2302 push @resp, {transaction => $t, %$circ_data};
2310 __PACKAGE__->register_method(
2311 method => "user_perms",
2312 api_name => "open-ils.actor.permissions.user_perms.retrieve",
2314 notes => "Returns a list of permissions"
2318 my( $self, $client, $authtoken, $user ) = @_;
2320 my( $staff, $evt ) = $apputils->checkses($authtoken);
2321 return $evt if $evt;
2323 $user ||= $staff->id;
2325 if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2329 return $apputils->simple_scalar_request(
2331 "open-ils.storage.permission.user_perms.atomic",
2335 __PACKAGE__->register_method(
2336 method => "retrieve_perms",
2337 api_name => "open-ils.actor.permissions.retrieve",
2338 notes => "Returns a list of permissions"
2340 sub retrieve_perms {
2341 my( $self, $client ) = @_;
2342 return $apputils->simple_scalar_request(
2344 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2345 { id => { '!=' => undef } }
2349 __PACKAGE__->register_method(
2350 method => "retrieve_groups",
2351 api_name => "open-ils.actor.groups.retrieve",
2352 notes => "Returns a list of user groups"
2354 sub retrieve_groups {
2355 my( $self, $client ) = @_;
2356 return new_editor()->retrieve_all_permission_grp_tree();
2359 __PACKAGE__->register_method(
2360 method => "retrieve_org_address",
2361 api_name => "open-ils.actor.org_unit.address.retrieve",
2362 notes => <<' NOTES');
2363 Returns an org_unit address by ID
2364 @param An org_address ID
2366 sub retrieve_org_address {
2367 my( $self, $client, $id ) = @_;
2368 return $apputils->simple_scalar_request(
2370 "open-ils.cstore.direct.actor.org_address.retrieve",
2375 __PACKAGE__->register_method(
2376 method => "retrieve_groups_tree",
2377 api_name => "open-ils.actor.groups.tree.retrieve",
2378 notes => "Returns a list of user groups"
2381 sub retrieve_groups_tree {
2382 my( $self, $client ) = @_;
2383 return new_editor()->search_permission_grp_tree(
2388 flesh_fields => { pgt => ["children"] },
2389 order_by => { pgt => 'name'}
2396 __PACKAGE__->register_method(
2397 method => "add_user_to_groups",
2398 api_name => "open-ils.actor.user.set_groups",
2399 notes => "Adds a user to one or more permission groups"
2402 sub add_user_to_groups {
2403 my( $self, $client, $authtoken, $userid, $groups ) = @_;
2405 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2406 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2407 return $evt if $evt;
2409 ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2410 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2411 return $evt if $evt;
2413 $apputils->simplereq(
2415 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2417 for my $group (@$groups) {
2418 my $link = Fieldmapper::permission::usr_grp_map->new;
2420 $link->usr($userid);
2422 my $id = $apputils->simplereq(
2424 'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2430 __PACKAGE__->register_method(
2431 method => "get_user_perm_groups",
2432 api_name => "open-ils.actor.user.get_groups",
2433 notes => "Retrieve a user's permission groups."
2437 sub get_user_perm_groups {
2438 my( $self, $client, $authtoken, $userid ) = @_;
2440 my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2441 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2442 return $evt if $evt;
2444 return $apputils->simplereq(
2446 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2450 __PACKAGE__->register_method(
2451 method => "get_user_work_ous",
2452 api_name => "open-ils.actor.user.get_work_ous",
2453 notes => "Retrieve a user's work org units."
2456 __PACKAGE__->register_method(
2457 method => "get_user_work_ous",
2458 api_name => "open-ils.actor.user.get_work_ous.ids",
2459 notes => "Retrieve a user's work org units."
2462 sub get_user_work_ous {
2463 my( $self, $client, $auth, $userid ) = @_;
2464 my $e = new_editor(authtoken=>$auth);
2465 return $e->event unless $e->checkauth;
2466 $userid ||= $e->requestor->id;
2468 if($e->requestor->id != $userid) {
2469 my $user = $e->retrieve_actor_user($userid)
2470 or return $e->event;
2471 return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2474 return $e->search_permission_usr_work_ou_map({usr => $userid})
2475 unless $self->api_name =~ /.ids$/;
2477 # client just wants a list of org IDs
2478 return $U->get_user_work_ou_ids($e, $userid);
2483 __PACKAGE__->register_method(
2484 method => 'register_workstation',
2485 api_name => 'open-ils.actor.workstation.register.override',
2486 signature => q/@see open-ils.actor.workstation.register/
2489 __PACKAGE__->register_method(
2490 method => 'register_workstation',
2491 api_name => 'open-ils.actor.workstation.register',
2493 Registers a new workstion in the system
2494 @param authtoken The login session key
2495 @param name The name of the workstation id
2496 @param owner The org unit that owns this workstation
2497 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2498 if the name is already in use.
2502 sub register_workstation {
2503 my( $self, $conn, $authtoken, $name, $owner, $oargs ) = @_;
2505 my $e = new_editor(authtoken=>$authtoken, xact=>1);
2506 return $e->die_event unless $e->checkauth;
2507 return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2508 my $existing = $e->search_actor_workstation({name => $name})->[0];
2509 $oargs = { all => 1 } unless defined $oargs;
2513 if( $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'WORKSTATION_NAME_EXISTS' } @{$oargs->{events}}) ) {
2514 # workstation with the given name exists.
2516 if($owner ne $existing->owning_lib) {
2517 # if necessary, update the owning_lib of the workstation
2519 $logger->info("changing owning lib of workstation ".$existing->id.
2520 " from ".$existing->owning_lib." to $owner");
2521 return $e->die_event unless
2522 $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib);
2524 return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner);
2526 $existing->owning_lib($owner);
2527 return $e->die_event unless $e->update_actor_workstation($existing);
2533 "attempt to register an existing workstation. returning existing ID");
2536 return $existing->id;
2539 return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2543 my $ws = Fieldmapper::actor::workstation->new;
2544 $ws->owning_lib($owner);
2546 $e->create_actor_workstation($ws) or return $e->die_event;
2548 return $ws->id; # note: editor sets the id on the new object for us
2551 __PACKAGE__->register_method(
2552 method => 'workstation_list',
2553 api_name => 'open-ils.actor.workstation.list',
2555 Returns a list of workstations registered at the given location
2556 @param authtoken The login session key
2557 @param ids A list of org_unit.id's for the workstation owners
2561 sub workstation_list {
2562 my( $self, $conn, $authtoken, @orgs ) = @_;
2564 my $e = new_editor(authtoken=>$authtoken);
2565 return $e->event unless $e->checkauth;
2570 unless $e->allowed('REGISTER_WORKSTATION', $o);
2571 $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2577 __PACKAGE__->register_method(
2578 method => 'fetch_patron_note',
2579 api_name => 'open-ils.actor.note.retrieve.all',
2582 Returns a list of notes for a given user
2583 Requestor must have VIEW_USER permission if pub==false and
2584 @param authtoken The login session key
2585 @param args Hash of params including
2586 patronid : the patron's id
2587 pub : true if retrieving only public notes
2591 sub fetch_patron_note {
2592 my( $self, $conn, $authtoken, $args ) = @_;
2593 my $patronid = $$args{patronid};
2595 my($reqr, $evt) = $U->checkses($authtoken);
2596 return $evt if $evt;
2599 ($patron, $evt) = $U->fetch_user($patronid);
2600 return $evt if $evt;
2603 if( $patronid ne $reqr->id ) {
2604 $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2605 return $evt if $evt;
2607 return $U->cstorereq(
2608 'open-ils.cstore.direct.actor.usr_note.search.atomic',
2609 { usr => $patronid, pub => 't' } );
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', { usr => $patronid } );
2619 __PACKAGE__->register_method(
2620 method => 'create_user_note',
2621 api_name => 'open-ils.actor.note.create',
2623 Creates a new note for the given user
2624 @param authtoken The login session key
2625 @param note The note object
2628 sub create_user_note {
2629 my( $self, $conn, $authtoken, $note ) = @_;
2630 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2631 return $e->die_event unless $e->checkauth;
2633 my $user = $e->retrieve_actor_user($note->usr)
2634 or return $e->die_event;
2636 return $e->die_event unless
2637 $e->allowed('UPDATE_USER',$user->home_ou);
2639 $note->creator($e->requestor->id);
2640 $e->create_actor_usr_note($note) or return $e->die_event;
2646 __PACKAGE__->register_method(
2647 method => 'delete_user_note',
2648 api_name => 'open-ils.actor.note.delete',
2650 Deletes a note for the given user
2651 @param authtoken The login session key
2652 @param noteid The note id
2655 sub delete_user_note {
2656 my( $self, $conn, $authtoken, $noteid ) = @_;
2658 my $e = new_editor(xact=>1, authtoken=>$authtoken);
2659 return $e->die_event unless $e->checkauth;
2660 my $note = $e->retrieve_actor_usr_note($noteid)
2661 or return $e->die_event;
2662 my $user = $e->retrieve_actor_user($note->usr)
2663 or return $e->die_event;
2664 return $e->die_event unless
2665 $e->allowed('UPDATE_USER', $user->home_ou);
2667 $e->delete_actor_usr_note($note) or return $e->die_event;
2673 __PACKAGE__->register_method(
2674 method => 'update_user_note',
2675 api_name => 'open-ils.actor.note.update',
2677 @param authtoken The login session key
2678 @param note The note
2682 sub update_user_note {
2683 my( $self, $conn, $auth, $note ) = @_;
2684 my $e = new_editor(authtoken=>$auth, xact=>1);
2685 return $e->die_event unless $e->checkauth;
2686 my $patron = $e->retrieve_actor_user($note->usr)
2687 or return $e->die_event;
2688 return $e->die_event unless
2689 $e->allowed('UPDATE_USER', $patron->home_ou);
2690 $e->update_actor_user_note($note)
2691 or return $e->die_event;
2696 __PACKAGE__->register_method(
2697 method => 'fetch_patron_messages',
2698 api_name => 'open-ils.actor.message.retrieve',
2701 Returns a list of notes for a given user, not
2702 including ones marked deleted
2703 @param authtoken The login session key
2704 @param patronid patron ID
2705 @param options hash containing optional limit and offset
2709 sub fetch_patron_messages {
2710 my( $self, $conn, $auth, $patronid, $options ) = @_;
2714 my $e = new_editor(authtoken => $auth);
2715 return $e->die_event unless $e->checkauth;
2717 if ($e->requestor->id ne $patronid) {
2718 return $e->die_event unless $e->allowed('VIEW_USER');
2721 my $select_clause = { usr => $patronid };
2722 my $options_clause = { order_by => { aum => 'create_date DESC' } };
2723 $options_clause->{'limit'} = $options->{'limit'} if $options->{'limit'};
2724 $options_clause->{'offset'} = $options->{'offset'} if $options->{'offset'};
2726 my $aum = $e->search_actor_usr_message([ $select_clause, $options_clause ]);
2731 __PACKAGE__->register_method(
2732 method => 'usrname_exists',
2733 api_name => 'open-ils.actor.username.exists',
2735 desc => 'Check if a username is already taken (by an undeleted patron)',
2737 {desc => 'Authentication token', type => 'string'},
2738 {desc => 'Username', type => 'string'}
2741 desc => 'id of existing user if username exists, undef otherwise. Event on error'
2746 sub usrname_exists {
2747 my( $self, $conn, $auth, $usrname ) = @_;
2748 my $e = new_editor(authtoken=>$auth);
2749 return $e->event unless $e->checkauth;
2750 my $a = $e->search_actor_user({usrname => $usrname}, {idlist=>1});
2751 return $$a[0] if $a and @$a;
2755 __PACKAGE__->register_method(
2756 method => 'barcode_exists',
2757 api_name => 'open-ils.actor.barcode.exists',
2759 signature => 'Returns 1 if the requested barcode exists, returns 0 otherwise'
2762 sub barcode_exists {
2763 my( $self, $conn, $auth, $barcode ) = @_;
2764 my $e = new_editor(authtoken=>$auth);
2765 return $e->event unless $e->checkauth;
2766 my $card = $e->search_actor_card({barcode => $barcode});
2772 #return undef unless @$card;
2773 #return $card->[0]->usr;
2777 __PACKAGE__->register_method(
2778 method => 'retrieve_net_levels',
2779 api_name => 'open-ils.actor.net_access_level.retrieve.all',
2782 sub retrieve_net_levels {
2783 my( $self, $conn, $auth ) = @_;
2784 my $e = new_editor(authtoken=>$auth);
2785 return $e->event unless $e->checkauth;
2786 return $e->retrieve_all_config_net_access_level();
2789 # Retain the old typo API name just in case
2790 __PACKAGE__->register_method(
2791 method => 'fetch_org_by_shortname',
2792 api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2794 __PACKAGE__->register_method(
2795 method => 'fetch_org_by_shortname',
2796 api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
2798 sub fetch_org_by_shortname {
2799 my( $self, $conn, $sname ) = @_;
2800 my $e = new_editor();
2801 my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2802 return $e->event unless $org;
2807 __PACKAGE__->register_method(
2808 method => 'session_home_lib',
2809 api_name => 'open-ils.actor.session.home_lib',
2812 sub session_home_lib {
2813 my( $self, $conn, $auth ) = @_;
2814 my $e = new_editor(authtoken=>$auth);
2815 return undef unless $e->checkauth;
2816 my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2817 return $org->shortname;
2820 __PACKAGE__->register_method(
2821 method => 'session_safe_token',
2822 api_name => 'open-ils.actor.session.safe_token',
2824 Returns a hashed session ID that is safe for export to the world.
2825 This safe token will expire after 1 hour of non-use.
2826 @param auth Active authentication token
2830 sub session_safe_token {
2831 my( $self, $conn, $auth ) = @_;
2832 my $e = new_editor(authtoken=>$auth);
2833 return undef unless $e->checkauth;
2835 my $safe_token = md5_hex($auth);
2837 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2839 # add more user fields as needed
2841 "safe-token-user-$safe_token", {
2842 id => $e->requestor->id,
2843 home_ou_shortname => $e->retrieve_actor_org_unit(
2844 $e->requestor->home_ou)->shortname,
2853 __PACKAGE__->register_method(
2854 method => 'safe_token_home_lib',
2855 api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2857 Returns the home library shortname from the session
2858 asscociated with a safe token from generated by
2859 open-ils.actor.session.safe_token.
2860 @param safe_token Active safe token
2861 @param who Optional user activity "ewho" value
2865 sub safe_token_home_lib {
2866 my( $self, $conn, $safe_token, $who ) = @_;
2867 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2869 my $blob = $cache->get_cache("safe-token-user-$safe_token");
2870 return unless $blob;
2872 $U->log_user_activity($blob->{id}, $who, 'verify');
2873 return $blob->{home_ou_shortname};
2877 __PACKAGE__->register_method(
2878 method => "update_penalties",
2879 api_name => "open-ils.actor.user.penalties.update"
2882 sub update_penalties {
2883 my($self, $conn, $auth, $user_id) = @_;
2884 my $e = new_editor(authtoken=>$auth, xact => 1);
2885 return $e->die_event unless $e->checkauth;
2886 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2887 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2888 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $e->requestor->ws_ou);
2889 return $evt if $evt;
2895 __PACKAGE__->register_method(
2896 method => "apply_penalty",
2897 api_name => "open-ils.actor.user.penalty.apply"
2901 my($self, $conn, $auth, $penalty) = @_;
2903 my $e = new_editor(authtoken=>$auth, xact => 1);
2904 return $e->die_event unless $e->checkauth;
2906 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2907 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2909 my $ptype = $e->retrieve_config_standing_penalty($penalty->standing_penalty) or return $e->die_event;
2912 (defined $ptype->org_depth) ?
2913 $U->org_unit_ancestor_at_depth($penalty->org_unit, $ptype->org_depth) :
2916 $penalty->org_unit($ctx_org);
2917 $penalty->staff($e->requestor->id);
2918 $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
2921 return $penalty->id;
2924 __PACKAGE__->register_method(
2925 method => "remove_penalty",
2926 api_name => "open-ils.actor.user.penalty.remove"
2929 sub remove_penalty {
2930 my($self, $conn, $auth, $penalty) = @_;
2931 my $e = new_editor(authtoken=>$auth, xact => 1);
2932 return $e->die_event unless $e->checkauth;
2933 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2934 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2936 $e->delete_actor_user_standing_penalty($penalty) or return $e->die_event;
2941 __PACKAGE__->register_method(
2942 method => "update_penalty_note",
2943 api_name => "open-ils.actor.user.penalty.note.update"
2946 sub update_penalty_note {
2947 my($self, $conn, $auth, $penalty_ids, $note) = @_;
2948 my $e = new_editor(authtoken=>$auth, xact => 1);
2949 return $e->die_event unless $e->checkauth;
2950 for my $penalty_id (@$penalty_ids) {
2951 my $penalty = $e->search_actor_user_standing_penalty( { id => $penalty_id } )->[0];
2952 if (! $penalty ) { return $e->die_event; }
2953 my $user = $e->retrieve_actor_user($penalty->usr) or return $e->die_event;
2954 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2956 $penalty->note( $note ); $penalty->ischanged( 1 );
2958 $e->update_actor_user_standing_penalty($penalty) or return $e->die_event;
2964 __PACKAGE__->register_method(
2965 method => "ranged_penalty_thresholds",
2966 api_name => "open-ils.actor.grp_penalty_threshold.ranged.retrieve",
2970 sub ranged_penalty_thresholds {
2971 my($self, $conn, $auth, $context_org) = @_;
2972 my $e = new_editor(authtoken=>$auth);
2973 return $e->event unless $e->checkauth;
2974 return $e->event unless $e->allowed('VIEW_GROUP_PENALTY_THRESHOLD', $context_org);
2975 my $list = $e->search_permission_grp_penalty_threshold([
2976 {org_unit => $U->get_org_ancestors($context_org)},
2977 {order_by => {pgpt => 'id'}}
2979 $conn->respond($_) for @$list;
2985 __PACKAGE__->register_method(
2986 method => "user_retrieve_fleshed_by_id",
2988 api_name => "open-ils.actor.user.fleshed.retrieve",
2991 sub user_retrieve_fleshed_by_id {
2992 my( $self, $client, $auth, $user_id, $fields ) = @_;
2993 my $e = new_editor(authtoken => $auth);
2994 return $e->event unless $e->checkauth;
2996 if( $e->requestor->id != $user_id ) {
2997 return $e->event unless $e->allowed('VIEW_USER');
3004 "standing_penalties",
3011 return new_flesh_user($user_id, $fields, $e);
3015 sub new_flesh_user {
3018 my $fields = shift || [];
3021 my $fetch_penalties = 0;
3022 if(grep {$_ eq 'standing_penalties'} @$fields) {
3023 $fields = [grep {$_ ne 'standing_penalties'} @$fields];
3024 $fetch_penalties = 1;
3027 my $fetch_usr_act = 0;
3028 if(grep {$_ eq 'usr_activity'} @$fields) {
3029 $fields = [grep {$_ ne 'usr_activity'} @$fields];
3033 my $user = $e->retrieve_actor_user(
3038 "flesh_fields" => { "au" => $fields }
3041 ) or return $e->die_event;
3044 if( grep { $_ eq 'addresses' } @$fields ) {
3046 $user->addresses([]) unless @{$user->addresses};
3047 # don't expose "replaced" addresses by default
3048 $user->addresses([grep {$_->id >= 0} @{$user->addresses}]);
3050 if( ref $user->billing_address ) {
3051 unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
3052 push( @{$user->addresses}, $user->billing_address );
3056 if( ref $user->mailing_address ) {
3057 unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
3058 push( @{$user->addresses}, $user->mailing_address );
3063 if($fetch_penalties) {
3064 # grab the user penalties ranged for this location
3065 $user->standing_penalties(
3066 $e->search_actor_user_standing_penalty([
3069 {stop_date => undef},
3070 {stop_date => {'>' => 'now'}}
3072 org_unit => $U->get_org_full_path($e->requestor->ws_ou)
3075 flesh_fields => {ausp => ['standing_penalty']}
3081 # retrieve the most recent usr_activity entry
3082 if ($fetch_usr_act) {
3084 # max number to return for simple patron fleshing
3085 my $limit = $U->ou_ancestor_setting_value(
3086 $e->requestor->ws_ou,
3087 'circ.patron.usr_activity_retrieve.max');
3091 flesh_fields => {auact => ['etype']},
3092 order_by => {auact => 'event_time DESC'},
3095 # 0 == none, <0 == return all
3096 $limit = 1 unless defined $limit;
3097 $opts->{limit} = $limit if $limit > 0;
3099 $user->usr_activity(
3101 [] : # skip the DB call
3102 $e->search_actor_usr_activity([{usr => $user->id}, $opts])
3107 $user->clear_passwd();
3114 __PACKAGE__->register_method(
3115 method => "user_retrieve_parts",
3116 api_name => "open-ils.actor.user.retrieve.parts",
3119 sub user_retrieve_parts {
3120 my( $self, $client, $auth, $user_id, $fields ) = @_;
3121 my $e = new_editor(authtoken => $auth);
3122 return $e->event unless $e->checkauth;
3123 $user_id ||= $e->requestor->id;
3124 if( $e->requestor->id != $user_id ) {
3125 return $e->event unless $e->allowed('VIEW_USER');
3128 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3129 push(@resp, $user->$_()) for(@$fields);
3135 __PACKAGE__->register_method(
3136 method => 'user_opt_in_enabled',
3137 api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
3138 signature => '@return 1 if user opt-in is globally enabled, 0 otherwise.'
3141 sub user_opt_in_enabled {
3142 my($self, $conn) = @_;
3143 my $sc = OpenSRF::Utils::SettingsClient->new;
3144 return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true';
3149 __PACKAGE__->register_method(
3150 method => 'user_opt_in_at_org',
3151 api_name => 'open-ils.actor.user.org_unit_opt_in.check',
3153 @param $auth The auth token
3154 @param user_id The ID of the user to test
3155 @return 1 if the user has opted in at the specified org,
3156 2 if opt-in is disallowed for the user's home org,
3157 event on error, and 0 otherwise. /
3159 sub user_opt_in_at_org {
3160 my($self, $conn, $auth, $user_id) = @_;
3162 # see if we even need to enforce the opt-in value
3163 return 1 unless user_opt_in_enabled($self);
3165 my $e = new_editor(authtoken => $auth);
3166 return $e->event unless $e->checkauth;
3168 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3169 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3171 my $ws_org = $e->requestor->ws_ou;
3172 # user is automatically opted-in if they are from the local org
3173 return 1 if $user->home_ou eq $ws_org;
3175 # get the boundary setting
3176 my $opt_boundary = $U->ou_ancestor_setting_value($e->requestor->ws_ou,'org.patron_opt_boundary');
3178 # auto opt in if user falls within the opt boundary
3179 my $opt_orgs = $U->get_org_descendants($ws_org, $opt_boundary);
3181 return 1 if grep $_ eq $user->home_ou, @$opt_orgs;
3183 # check whether opt-in is restricted at the user's home library
3184 my $opt_restrict_depth = $U->ou_ancestor_setting_value($user->home_ou, 'org.restrict_opt_to_depth');
3185 if ($opt_restrict_depth) {
3186 my $restrict_ancestor = $U->org_unit_ancestor_at_depth($user->home_ou, $opt_restrict_depth);
3187 my $unrestricted_orgs = $U->get_org_descendants($restrict_ancestor);
3189 # opt-in is disallowed unless the workstation org is within the home
3190 # library's opt-in scope
3191 return 2 unless grep $_ eq $e->requestor->ws_ou, @$unrestricted_orgs;
3194 my $vals = $e->search_actor_usr_org_unit_opt_in(
3195 {org_unit=>$opt_orgs, usr=>$user_id},{idlist=>1});
3201 __PACKAGE__->register_method(
3202 method => 'create_user_opt_in_at_org',
3203 api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3205 @param $auth The auth token
3206 @param user_id The ID of the user to test
3207 @return The ID of the newly created object, event on error./
3210 sub create_user_opt_in_at_org {
3211 my($self, $conn, $auth, $user_id, $org_id) = @_;
3213 my $e = new_editor(authtoken => $auth, xact=>1);
3214 return $e->die_event unless $e->checkauth;
3216 # if a specific org unit wasn't passed in, get one based on the defaults;
3218 my $wsou = $e->requestor->ws_ou;
3219 # get the default opt depth
3220 my $opt_depth = $U->ou_ancestor_setting_value($wsou,'org.patron_opt_default');
3221 # get the org unit at that depth
3222 my $org = $e->json_query({
3223 from => [ 'actor.org_unit_ancestor_at_depth', $wsou, $opt_depth ]})->[0];
3224 $org_id = $org->{id};
3227 # fall back to the workstation OU, the pre-opt-in-boundary way
3228 $org_id = $e->requestor->ws_ou;
3231 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3232 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3234 my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3236 $opt_in->org_unit($org_id);
3237 $opt_in->usr($user_id);
3238 $opt_in->staff($e->requestor->id);
3239 $opt_in->opt_in_ts('now');
3240 $opt_in->opt_in_ws($e->requestor->wsid);
3242 $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3243 or return $e->die_event;
3251 __PACKAGE__->register_method (
3252 method => 'retrieve_org_hours',
3253 api_name => 'open-ils.actor.org_unit.hours_of_operation.retrieve',
3255 Returns the hours of operation for a specified org unit
3256 @param authtoken The login session key
3257 @param org_id The org_unit ID
3261 sub retrieve_org_hours {
3262 my($self, $conn, $auth, $org_id) = @_;
3263 my $e = new_editor(authtoken => $auth);
3264 return $e->die_event unless $e->checkauth;
3265 $org_id ||= $e->requestor->ws_ou;
3266 return $e->retrieve_actor_org_unit_hours_of_operation($org_id);
3270 __PACKAGE__->register_method (
3271 method => 'verify_user_password',
3272 api_name => 'open-ils.actor.verify_user_password',
3274 Given a barcode or username and the MD5 encoded password,
3275 returns 1 if the password is correct. Returns 0 otherwise.
3279 sub verify_user_password {
3280 my($self, $conn, $auth, $barcode, $username, $password) = @_;
3281 my $e = new_editor(authtoken => $auth);
3282 return $e->die_event unless $e->checkauth;
3284 my $user_by_barcode;
3285 my $user_by_username;
3287 my $card = $e->search_actor_card([
3288 {barcode => $barcode},
3289 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0] or return 0;
3290 $user_by_barcode = $card->usr;
3291 $user = $user_by_barcode;
3294 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return 0;
3295 $user = $user_by_username;
3297 return 0 if (!$user || $U->is_true($user->deleted));
3298 return 0 if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3299 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3300 return $U->verify_migrated_user_password($e, $user->id, $password, 1);
3303 __PACKAGE__->register_method (
3304 method => 'retrieve_usr_id_via_barcode_or_usrname',
3305 api_name => "open-ils.actor.user.retrieve_id_by_barcode_or_username",
3307 Given a barcode or username returns the id for the user or
3312 sub retrieve_usr_id_via_barcode_or_usrname {
3313 my($self, $conn, $auth, $barcode, $username) = @_;
3314 my $e = new_editor(authtoken => $auth);
3315 return $e->die_event unless $e->checkauth;
3316 my $id_as_barcode= OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.actor' => app_settings => 'id_as_barcode');
3318 my $user_by_barcode;
3319 my $user_by_username;
3320 $logger->info("$id_as_barcode is the ID as BARCODE");
3322 my $card = $e->search_actor_card([
3323 {barcode => $barcode},
3324 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
3325 if ($id_as_barcode =~ /^t/i) {
3327 $user = $e->retrieve_actor_user($barcode);
3328 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$user);
3330 $user_by_barcode = $card->usr;
3331 $user = $user_by_barcode;
3334 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if(!$card);
3335 $user_by_barcode = $card->usr;
3336 $user = $user_by_barcode;
3341 $user_by_username = $e->search_actor_user({usrname => $username})->[0] or return OpenILS::Event->new( 'ACTOR_USR_NOT_FOUND' );
3343 $user = $user_by_username;
3345 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if (!$user);
3346 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ) if ($user_by_username && $user_by_barcode && $user_by_username->id != $user_by_barcode->id);
3347 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3352 __PACKAGE__->register_method (
3353 method => 'merge_users',
3354 api_name => 'open-ils.actor.user.merge',
3357 Given a list of source users and destination user, transfer all data from the source
3358 to the dest user and delete the source user. All user related data is
3359 transferred, including circulations, holds, bookbags, etc.
3365 my($self, $conn, $auth, $master_id, $user_ids, $options) = @_;
3366 my $e = new_editor(xact => 1, authtoken => $auth);
3367 return $e->die_event unless $e->checkauth;
3369 # disallow the merge if any subordinate accounts are in collections
3370 my $colls = $e->search_money_collections_tracker({usr => $user_ids}, {idlist => 1});
3371 return OpenILS::Event->new('MERGED_USER_IN_COLLECTIONS', payload => $user_ids) if @$colls;
3373 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3374 if $master_id == $e->requestor->id;
3376 my $master_user = $e->retrieve_actor_user($master_id) or return $e->die_event;
3377 my $evt = group_perm_failed($e, $e->requestor, $master_user);
3378 return $evt if $evt;
3380 my $del_addrs = ($U->ou_ancestor_setting_value(
3381 $master_user->home_ou, 'circ.user_merge.delete_addresses', $e)) ? 't' : 'f';
3382 my $del_cards = ($U->ou_ancestor_setting_value(
3383 $master_user->home_ou, 'circ.user_merge.delete_cards', $e)) ? 't' : 'f';
3384 my $deactivate_cards = ($U->ou_ancestor_setting_value(
3385 $master_user->home_ou, 'circ.user_merge.deactivate_cards', $e)) ? 't' : 'f';
3387 for my $src_id (@$user_ids) {
3389 my $src_user = $e->retrieve_actor_user($src_id) or return $e->die_event;
3390 my $evt = group_perm_failed($e, $e->requestor, $src_user);
3391 return $evt if $evt;
3393 return OpenILS::Event->new('MERGE_SELF_NOT_ALLOWED')
3394 if $src_id == $e->requestor->id;
3396 return $e->die_event unless $e->allowed('MERGE_USERS', $src_user->home_ou);
3397 if($src_user->home_ou ne $master_user->home_ou) {
3398 return $e->die_event unless $e->allowed('MERGE_USERS', $master_user->home_ou);
3401 return $e->die_event unless
3402 $e->json_query({from => [
3417 __PACKAGE__->register_method (
3418 method => 'approve_user_address',
3419 api_name => 'open-ils.actor.user.pending_address.approve',
3426 sub approve_user_address {
3427 my($self, $conn, $auth, $addr) = @_;
3428 my $e = new_editor(xact => 1, authtoken => $auth);
3429 return $e->die_event unless $e->checkauth;
3431 # if the caller passes an address object, assume they want to
3432 # update it first before approving it
3433 $e->update_actor_user_address($addr) or return $e->die_event;
3435 $addr = $e->retrieve_actor_user_address($addr) or return $e->die_event;
3437 my $user = $e->retrieve_actor_user($addr->usr);
3438 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3439 my $result = $e->json_query({from => ['actor.approve_pending_address', $addr->id]})->[0]
3440 or return $e->die_event;
3442 return [values %$result]->[0];
3446 __PACKAGE__->register_method (
3447 method => 'retrieve_friends',
3448 api_name => 'open-ils.actor.friends.retrieve',
3451 returns { confirmed: [], pending_out: [], pending_in: []}
3452 pending_out are users I'm requesting friendship with
3453 pending_in are users requesting friendship with me
3458 sub retrieve_friends {
3459 my($self, $conn, $auth, $user_id, $options) = @_;
3460 my $e = new_editor(authtoken => $auth);
3461 return $e->event unless $e->checkauth;
3462 $user_id ||= $e->requestor->id;
3464 if($user_id != $e->requestor->id) {
3465 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3466 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3469 return OpenILS::Application::Actor::Friends->retrieve_friends(
3470 $e, $user_id, $options);
3475 __PACKAGE__->register_method (
3476 method => 'apply_friend_perms',
3477 api_name => 'open-ils.actor.friends.perms.apply',
3483 sub apply_friend_perms {
3484 my($self, $conn, $auth, $user_id, $delegate_id, @perms) = @_;
3485 my $e = new_editor(authtoken => $auth, xact => 1);
3486 return $e->die_event unless $e->checkauth;
3488 if($user_id != $e->requestor->id) {
3489 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3490 return $e->die_event unless $e->allowed('VIEW_USER', $user->home_ou);
3493 for my $perm (@perms) {
3495 OpenILS::Application::Actor::Friends->apply_friend_perm(
3496 $e, $user_id, $delegate_id, $perm);
3497 return $evt if $evt;
3505 __PACKAGE__->register_method (
3506 method => 'update_user_pending_address',
3507 api_name => 'open-ils.actor.user.address.pending.cud'
3510 sub update_user_pending_address {
3511 my($self, $conn, $auth, $addr) = @_;
3512 my $e = new_editor(authtoken => $auth, xact => 1);
3513 return $e->die_event unless $e->checkauth;
3515 if($addr->usr != $e->requestor->id) {
3516 my $user = $e->retrieve_actor_user($addr->usr) or return $e->die_event;
3517 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3521 $e->create_actor_user_address($addr) or return $e->die_event;
3522 } elsif($addr->isdeleted) {
3523 $e->delete_actor_user_address($addr) or return $e->die_event;
3525 $e->update_actor_user_address($addr) or return $e->die_event;
3533 __PACKAGE__->register_method (
3534 method => 'user_events',
3535 api_name => 'open-ils.actor.user.events.circ',
3538 __PACKAGE__->register_method (
3539 method => 'user_events',
3540 api_name => 'open-ils.actor.user.events.ahr',
3545 my($self, $conn, $auth, $user_id, $filters) = @_;
3546 my $e = new_editor(authtoken => $auth);
3547 return $e->event unless $e->checkauth;
3549 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3550 my $user_field = 'usr';
3553 $filters->{target} = {
3554 select => { $obj_type => ['id'] },
3556 where => {usr => $user_id}
3559 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3560 if($e->requestor->id != $user_id) {
3561 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3564 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3565 my $req = $ses->request('open-ils.trigger.events_by_target',
3566 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3568 while(my $resp = $req->recv) {
3569 my $val = $resp->content;
3570 my $tgt = $val->target;
3572 if($obj_type eq 'circ') {
3573 $tgt->target_copy($e->retrieve_asset_copy($tgt->target_copy));
3575 } elsif($obj_type eq 'ahr') {
3576 $tgt->current_copy($e->retrieve_asset_copy($tgt->current_copy))
3577 if $tgt->current_copy;
3580 $conn->respond($val) if $val;
3586 __PACKAGE__->register_method (
3587 method => 'copy_events',
3588 api_name => 'open-ils.actor.copy.events.circ',
3591 __PACKAGE__->register_method (
3592 method => 'copy_events',
3593 api_name => 'open-ils.actor.copy.events.ahr',
3598 my($self, $conn, $auth, $copy_id, $filters) = @_;
3599 my $e = new_editor(authtoken => $auth);
3600 return $e->event unless $e->checkauth;
3602 (my $obj_type = $self->api_name) =~ s/.*\.([a-z]+)$/$1/;
3604 my $copy = $e->retrieve_asset_copy($copy_id) or return $e->event;
3606 my $copy_field = 'target_copy';
3607 $copy_field = 'current_copy' if $obj_type eq 'ahr';
3610 $filters->{target} = {
3611 select => { $obj_type => ['id'] },
3613 where => {$copy_field => $copy_id}
3617 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3618 my $req = $ses->request('open-ils.trigger.events_by_target',
3619 $obj_type, $filters, {atevdef => ['reactor', 'validator']}, 2);
3621 while(my $resp = $req->recv) {
3622 my $val = $resp->content;
3623 my $tgt = $val->target;
3625 my $user = $e->retrieve_actor_user($tgt->usr);
3626 if($e->requestor->id != $user->id) {
3627 return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3630 $tgt->$copy_field($copy);
3633 $conn->respond($val) if $val;
3640 __PACKAGE__->register_method (
3641 method => 'get_itemsout_notices',
3642 api_name => 'open-ils.actor.user.itemsout.notices',
3647 sub get_itemsout_notices{
3648 my( $self, $conn, $auth, $circId, $patronId) = @_;
3650 my $e = new_editor(authtoken => $auth);
3651 return $e->event unless $e->checkauth;
3653 my $requestorId = $e->requestor->id;
3655 if( $patronId ne $requestorId ){
3656 my $user = $e->retrieve_actor_user($requestorId) or return $e->event;
3657 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
3660 #my $ses = OpenSRF::AppSession->create('open-ils.trigger');
3661 #my $req = $ses->request('open-ils.trigger.events_by_target',
3662 # 'circ', {target => [$circId], event=> {state=>'complete'}});
3663 # ^ Above removed in favor of faster json_query.
3666 # select complete_time
3667 # from action_trigger.event atev
3668 # JOIN action_trigger.event_definition def ON (def.id = atev.event_def)
3669 # JOIN action_trigger.hook athook ON (athook.key = def.hook)
3670 # where hook = 'checkout.due' AND state = 'complete' and target = <circId>;
3673 my $ctx_loc = $e->requestor->ws_ou;
3674 my $exclude_courtesy_notices = $U->ou_ancestor_setting_value($ctx_loc, 'webstaff.circ.itemsout_notice_count_excludes_courtesies');
3676 select => { atev => ["complete_time"] },
3679 atevdef => { field => "id",fkey => "event_def", join => { ath => { field => "key", fkey => "hook" }} }
3682 where => {"+ath" => { key => "checkout.due" },"+atevdef" => { active => 't' },"+atev" => { target => $circId, state => 'complete' }}
3685 if ($exclude_courtesy_notices){
3686 $query->{"where"}->{"+atevdef"}->{validator} = { "<>" => "CircIsOpen"};
3689 my %resblob = ( numNotices => 0, lastDt => undef );
3691 my $res = $e->json_query($query);
3692 for my $ndate (@$res) {
3693 $resblob{numNotices}++;
3694 if( !defined $resblob{lastDt}){
3695 $resblob{lastDt} = $$ndate{complete_time};
3698 if ($resblob{lastDt} lt $$ndate{complete_time}){
3699 $resblob{lastDt} = $$ndate{complete_time};
3703 $conn->respond(\%resblob);
3707 __PACKAGE__->register_method (
3708 method => 'update_events',
3709 api_name => 'open-ils.actor.user.event.cancel.batch',
3712 __PACKAGE__->register_method (
3713 method => 'update_events',
3714 api_name => 'open-ils.actor.user.event.reset.batch',
3719 my($self, $conn, $auth, $event_ids) = @_;
3720 my $e = new_editor(xact => 1, authtoken => $auth);
3721 return $e->die_event unless $e->checkauth;
3724 for my $id (@$event_ids) {
3726 # do a little dance to determine what user we are ultimately affecting
3727 my $event = $e->retrieve_action_trigger_event([
3730 flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
3732 ]) or return $e->die_event;
3735 if($event->event_def->hook->core_type eq 'circ') {
3736 $user_id = $e->retrieve_action_circulation($event->target)->usr;
3737 } elsif($event->event_def->hook->core_type eq 'ahr') {
3738 $user_id = $e->retrieve_action_hold_request($event->target)->usr;
3743 my $user = $e->retrieve_actor_user($user_id);
3744 return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3746 if($self->api_name =~ /cancel/) {
3747 $event->state('invalid');
3748 } elsif($self->api_name =~ /reset/) {
3749 $event->clear_start_time;
3750 $event->clear_update_time;
3751 $event->state('pending');
3754 $e->update_action_trigger_event($event) or return $e->die_event;
3755 $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
3759 return {complete => 1};
3763 __PACKAGE__->register_method (
3764 method => 'really_delete_user',
3765 api_name => 'open-ils.actor.user.delete.override',
3766 signature => q/@see open-ils.actor.user.delete/
3769 __PACKAGE__->register_method (
3770 method => 'really_delete_user',
3771 api_name => 'open-ils.actor.user.delete',
3773 It anonymizes all personally identifiable information in actor.usr. By calling actor.usr_purge_data()
3774 it also purges related data from other tables, sometimes by transferring it to a designated destination user.
3775 The usrname field (along with first_given_name and family_name) is updated to id '-PURGED-' now().
3776 dest_usr_id is only required when deleting a user that performs staff functions.
3780 sub really_delete_user {
3781 my($self, $conn, $auth, $user_id, $dest_user_id, $oargs) = @_;
3782 my $e = new_editor(authtoken => $auth, xact => 1);
3783 return $e->die_event unless $e->checkauth;
3784 $oargs = { all => 1 } unless defined $oargs;
3786 # Find all unclosed billings for for user $user_id, thereby, also checking for open circs
3787 my $open_bills = $e->json_query({
3788 select => { mbts => ['id'] },
3791 xact_finish => { '=' => undef },
3792 usr => { '=' => $user_id },
3794 }) or return $e->die_event;
3796 my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3798 # No deleting patrons with open billings or checked out copies, unless perm-enabled override
3800 return $e->die_event(OpenILS::Event->new('ACTOR_USER_DELETE_OPEN_XACTS'))
3801 unless $self->api_name =~ /override/o && ($oargs->{all} || grep { $_ eq 'ACTOR_USER_DELETE_OPEN_XACTS' } @{$oargs->{events}})
3802 && $e->allowed('ACTOR_USER_DELETE_OPEN_XACTS.override', $user->home_ou);
3804 # No deleting yourself - UI is supposed to stop you first, though.
3805 return $e->die_event unless $e->requestor->id != $user->id;
3806 return $e->die_event unless $e->allowed('DELETE_USER', $user->home_ou);
3807 # Check if you are allowed to mess with this patron permission group at all
3808 my $evt = group_perm_failed($e, $e->requestor, $user);
3809 return $e->die_event($evt) if $evt;
3810 my $stat = $e->json_query(
3811 {from => ['actor.usr_delete', $user_id, $dest_user_id]})->[0]
3812 or return $e->die_event;
3818 __PACKAGE__->register_method (
3819 method => 'user_payments',
3820 api_name => 'open-ils.actor.user.payments.retrieve',
3823 Returns all payments for a given user. Default order is newest payments first.
3824 @param auth Authentication token
3825 @param user_id The user ID
3826 @param filters An optional hash of filters, including limit, offset, and order_by definitions
3831 my($self, $conn, $auth, $user_id, $filters) = @_;
3834 my $e = new_editor(authtoken => $auth);
3835 return $e->die_event unless $e->checkauth;
3837 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3838 return $e->event unless
3839 $e->requestor->id == $user_id or
3840 $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou);
3842 # Find all payments for all transactions for user $user_id
3844 select => {mp => ['id']},
3849 select => {mbt => ['id']},
3851 where => {usr => $user_id}
3856 { # by default, order newest payments first
3858 field => 'payment_ts',
3861 # secondary sort in ID as a tie-breaker, since payments created
3862 # within the same transaction will have identical payment_ts's
3869 for (qw/order_by limit offset/) {
3870 $query->{$_} = $filters->{$_} if defined $filters->{$_};
3873 if(defined $filters->{where}) {
3874 foreach (keys %{$filters->{where}}) {
3875 # don't allow the caller to expand the result set to other users
3876 $query->{where}->{$_} = $filters->{where}->{$_} unless $_ eq 'xact';
3880 my $payment_ids = $e->json_query($query);
3881 for my $pid (@$payment_ids) {
3882 my $pay = $e->retrieve_money_payment([
3887 mbt => ['summary', 'circulation', 'grocery'],
3888 circ => ['target_copy'],
3889 acp => ['call_number'],
3897 xact_type => $pay->xact->summary->xact_type,
3898 last_billing_type => $pay->xact->summary->last_billing_type,
3901 if($pay->xact->summary->xact_type eq 'circulation') {
3902 $resp->{barcode} = $pay->xact->circulation->target_copy->barcode;
3903 $resp->{title} = $U->record_to_mvr($pay->xact->circulation->target_copy->call_number->record)->title;
3906 $pay->xact($pay->xact->id); # de-flesh
3907 $conn->respond($resp);
3915 __PACKAGE__->register_method (
3916 method => 'negative_balance_users',
3917 api_name => 'open-ils.actor.users.negative_balance',
3920 Returns all users that have an overall negative balance
3921 @param auth Authentication token
3922 @param org_id The context org unit as an ID or list of IDs. This will be the home
3923 library of the user. If no org_unit is specified, no org unit filter is applied
3927 sub negative_balance_users {
3928 my($self, $conn, $auth, $org_id) = @_;
3930 my $e = new_editor(authtoken => $auth);
3931 return $e->die_event unless $e->checkauth;
3932 return $e->die_event unless $e->allowed('VIEW_USER', $org_id);
3936 mous => ['usr', 'balance_owed'],
3939 {column => 'last_billing_ts', transform => 'max', aggregate => 1},
3940 {column => 'last_payment_ts', transform => 'max', aggregate => 1},
3957 where => {'+mous' => {balance_owed => {'<' => 0}}}
3960 $query->{from}->{mous}->{au}->{filter}->{home_ou} = $org_id if $org_id;
3962 my $list = $e->json_query($query, {timeout => 600});
3964 for my $data (@$list) {
3966 usr => $e->retrieve_actor_user([$data->{usr}, {flesh => 1, flesh_fields => {au => ['card']}}]),
3967 balance_owed => $data->{balance_owed},
3968 last_billing_activity => max($data->{last_billing_ts}, $data->{last_payment_ts})
3975 __PACKAGE__->register_method(
3976 method => "request_password_reset",
3977 api_name => "open-ils.actor.patron.password_reset.request",
3979 desc => "Generates a UUID token usable with the open-ils.actor.patron.password_reset.commit " .
3980 "method for changing a user's password. The UUID token is distributed via A/T " .
3981 "templates (i.e. email to the user).",
3983 { desc => 'user_id_type', type => 'string' },
3984 { desc => 'user_id', type => 'string' },
3985 { desc => 'optional (based on library setting) matching email address for authorizing request', type => 'string' },
3987 return => {desc => '1 on success, Event on error'}
3990 sub request_password_reset {
3991 my($self, $conn, $user_id_type, $user_id, $email) = @_;
3993 # Check to see if password reset requests are already being throttled:
3994 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
3996 my $e = new_editor(xact => 1);
3999 # Get the user, if any, depending on the input value
4000 if ($user_id_type eq 'username') {
4001 $user = $e->search_actor_user({usrname => $user_id})->[0];
4004 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
4006 } elsif ($user_id_type eq 'barcode') {
4007 my $card = $e->search_actor_card([
4008 {barcode => $user_id},
4009 {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
4012 return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
4017 # If the user doesn't have an email address, we can't help them
4018 if (!$user->email) {
4020 return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
4023 my $email_must_match = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_requires_matching_email');
4024 if ($email_must_match) {
4025 if (lc($user->email) ne lc($email)) {
4026 return OpenILS::Event->new('EMAIL_VERIFICATION_FAILED');
4030 _reset_password_request($conn, $e, $user);
4033 # Once we have the user, we can issue the password reset request
4034 # XXX Add a wrapper method that accepts barcode + email input
4035 sub _reset_password_request {
4036 my ($conn, $e, $user) = @_;
4038 # 1. Get throttle threshold and time-to-live from OU_settings
4039 my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
4040 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4042 my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
4044 # 2. Get time of last request and number of active requests (num_active)
4045 my $active_requests = $e->json_query({
4051 transform => 'COUNT'
4054 column => 'request_time',
4060 has_been_reset => { '=' => 'f' },
4061 request_time => { '>' => $threshold_time }
4065 # Guard against no active requests
4066 if ($active_requests->[0]->{'request_time'}) {
4067 my $last_request = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($active_requests->[0]->{'request_time'}));
4068 my $now = DateTime::Format::ISO8601->new();
4070 # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
4071 if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
4072 ($last_request->add_duration('1 minute') > $now)) {
4073 $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
4075 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4079 # TODO Check to see if the user is in a password-reset-restricted group
4081 # Otherwise, go ahead and try to get the user.
4083 # Check the number of active requests for this user
4084 $active_requests = $e->json_query({
4090 transform => 'COUNT'
4095 usr => { '=' => $user->id },
4096 has_been_reset => { '=' => 'f' },
4097 request_time => { '>' => $threshold_time }
4101 $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
4103 # if less than or equal to per-user threshold, proceed; otherwise, return event
4104 my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
4105 if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
4107 return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
4110 # Create the aupr object and insert into the database
4111 my $reset_request = Fieldmapper::actor::usr_password_reset->new;
4112 my $uuid = create_uuid_as_string(UUID_V4);
4113 $reset_request->uuid($uuid);
4114 $reset_request->usr($user->id);
4116 my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
4119 # Create an event to notify user of the URL to reset their password
4121 # Can we stuff this in the user_data param for trigger autocreate?
4122 my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
4124 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
4125 $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
4128 # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
4133 __PACKAGE__->register_method(
4134 method => "commit_password_reset",
4135 api_name => "open-ils.actor.patron.password_reset.commit",
4137 desc => "Checks a UUID token generated by the open-ils.actor.patron.password_reset.request method for " .
4138 "validity, and if valid, uses it as authorization for changing the associated user's password " .
4139 "with the supplied password.",
4141 { desc => 'uuid', type => 'string' },
4142 { desc => 'password', type => 'string' },
4144 return => {desc => '1 on success, Event on error'}
4147 sub commit_password_reset {
4148 my($self, $conn, $uuid, $password) = @_;
4150 # Check to see if password reset requests are already being throttled:
4151 # 0. Check cache to see if we're in throttle mode (avoid hitting database)
4152 $cache ||= OpenSRF::Utils::Cache->new("global", 0);
4153 my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
4155 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4158 my $e = new_editor(xact => 1);
4160 my $aupr = $e->search_actor_usr_password_reset({
4167 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4169 my $user_id = $aupr->[0]->usr;
4170 my $user = $e->retrieve_actor_user($user_id);
4172 # Ensure we're still within the TTL for the request
4173 my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
4174 my $threshold = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
4175 if ($threshold < DateTime->now(time_zone => 'local')) {
4177 $logger->info("Password reset request needed to be submitted before $threshold");
4178 return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
4181 # Check complexity of password against OU-defined regex
4182 my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
4186 # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
4187 # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
4188 $is_strong = check_password_strength_custom($password, $pw_regex);
4190 $is_strong = check_password_strength_default($password);
4195 return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
4198 # All is well; update the password
4199 modify_migrated_user_password($e, $user->id, $password);
4201 # And flag that this password reset request has been honoured
4202 $aupr->[0]->has_been_reset('t');
4203 $e->update_actor_usr_password_reset($aupr->[0]);
4209 sub check_password_strength_default {
4210 my $password = shift;
4211 # Use the default set of checks
4212 if ( (length($password) < 7) or
4213 ($password !~ m/.*\d+.*/) or
4214 ($password !~ m/.*[A-Za-z]+.*/)
4221 sub check_password_strength_custom {
4222 my ($password, $pw_regex) = @_;
4224 $pw_regex = qr/$pw_regex/;
4225 if ($password !~ /$pw_regex/) {
4233 __PACKAGE__->register_method(
4234 method => "event_def_opt_in_settings",
4235 api_name => "open-ils.actor.event_def.opt_in.settings",
4238 desc => 'Streams the set of "cust" objects that are used as opt-in settings for event definitions',
4240 { desc => 'Authentication token', type => 'string'},
4242 desc => 'Org Unit ID. (optional). If no org ID is present, the home_ou of the requesting user is used',
4247 desc => q/set of "cust" objects that are used as opt-in settings for event definitions at the specified org unit/,
4254 sub event_def_opt_in_settings {
4255 my($self, $conn, $auth, $org_id) = @_;
4256 my $e = new_editor(authtoken => $auth);
4257 return $e->event unless $e->checkauth;
4259 if(defined $org_id and $org_id != $e->requestor->home_ou) {
4260 return $e->event unless
4261 $e->allowed(['VIEW_USER_SETTING_TYPE', 'ADMIN_USER_SETTING_TYPE'], $org_id);
4263 $org_id = $e->requestor->home_ou;
4266 # find all config.user_setting_type's related to event_defs for the requested org unit
4267 my $types = $e->json_query({
4268 select => {cust => ['name']},
4269 from => {atevdef => 'cust'},
4272 owner => $U->get_org_ancestors($org_id), # context org plus parents
4279 $conn->respond($_) for
4280 @{$e->search_config_usr_setting_type({name => [map {$_->{name}} @$types]})};
4287 __PACKAGE__->register_method(
4288 method => "user_circ_history",
4289 api_name => "open-ils.actor.history.circ",
4293 desc => 'Returns user circ history objects for the calling user',
4295 { desc => 'Authentication token', type => 'string'},
4296 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4299 desc => q/Stream of 'auch' circ history objects/,
4305 __PACKAGE__->register_method(
4306 method => "user_circ_history",
4307 api_name => "open-ils.actor.history.circ.clear",
4310 desc => 'Delete all user circ history entries for the calling user',
4312 { desc => 'Authentication token', type => 'string'},
4313 { desc => "Options hash. 'circ_ids' is an arrayref of circulation IDs to delete", type => 'object' },
4316 desc => q/1 on success, event on error/,
4322 __PACKAGE__->register_method(
4323 method => "user_circ_history",
4324 api_name => "open-ils.actor.history.circ.print",
4327 desc => q/Returns printable output for the caller's circ history objects/,
4329 { desc => 'Authentication token', type => 'string'},
4330 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4333 desc => q/An action_trigger.event object or error event./,
4339 __PACKAGE__->register_method(
4340 method => "user_circ_history",
4341 api_name => "open-ils.actor.history.circ.email",
4344 desc => q/Emails the caller's circ history/,
4346 { desc => 'Authentication token', type => 'string'},
4347 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4348 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4351 desc => q/undef, or event on error/
4356 sub user_circ_history {
4357 my ($self, $conn, $auth, $options) = @_;
4360 my $for_print = ($self->api_name =~ /print/);
4361 my $for_email = ($self->api_name =~ /email/);
4362 my $for_clear = ($self->api_name =~ /clear/);
4364 # No perm check is performed. Caller may only access his/her own
4365 # circ history entries.
4366 my $e = new_editor(authtoken => $auth);
4367 return $e->event unless $e->checkauth;
4370 if (!$for_clear) { # clear deletes all
4371 $limits{offset} = $options->{offset} if defined $options->{offset};
4372 $limits{limit} = $options->{limit} if defined $options->{limit};
4375 my %circ_id_filter = $options->{circ_ids} ?
4376 (id => $options->{circ_ids}) : ();
4378 my $circs = $e->search_action_user_circ_history([
4379 { usr => $e->requestor->id,
4382 { # order newest to oldest by default
4383 order_by => {auch => 'xact_start DESC'},
4386 {substream => 1} # could be a large list
4390 return $U->fire_object_event(undef,
4391 'circ.format.history.print', $circs, $e->requestor->home_ou);
4394 $e->xact_begin if $for_clear;
4395 $conn->respond_complete(1) if $for_email; # no sense in waiting
4397 for my $circ (@$circs) {
4400 # events will be fired from action_trigger_runner
4401 $U->create_events_for_hook('circ.format.history.email',
4402 $circ, $e->editor->home_ou, undef, undef, 1);
4404 } elsif ($for_clear) {
4406 $e->delete_action_user_circ_history($circ)
4407 or return $e->die_event;
4410 $conn->respond($circ);
4423 __PACKAGE__->register_method(
4424 method => "user_visible_holds",
4425 api_name => "open-ils.actor.history.hold.visible",
4428 desc => 'Returns the set of opt-in visible holds',
4430 { desc => 'Authentication token', type => 'string'},
4431 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4432 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4435 desc => q/An object with 1 field: "hold"/,
4441 __PACKAGE__->register_method(
4442 method => "user_visible_holds",
4443 api_name => "open-ils.actor.history.hold.visible.print",
4446 desc => 'Returns printable output for the set of opt-in visible holds',
4448 { desc => 'Authentication token', type => 'string'},
4449 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4450 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4453 desc => q/An action_trigger.event object or error event./,
4459 __PACKAGE__->register_method(
4460 method => "user_visible_holds",
4461 api_name => "open-ils.actor.history.hold.visible.email",
4464 desc => 'Emails the set of opt-in visible holds to the requestor',
4466 { desc => 'Authentication token', type => 'string'},
4467 { desc => 'User ID. If no user id is present, the authenticated user is assumed', type => 'number' },
4468 { desc => 'Options hash. Supported fields are "limit" and "offset"', type => 'object' },
4471 desc => q/undef, or event on error/
4476 sub user_visible_holds {
4477 my($self, $conn, $auth, $user_id, $options) = @_;
4480 my $for_print = ($self->api_name =~ /print/);
4481 my $for_email = ($self->api_name =~ /email/);
4482 my $e = new_editor(authtoken => $auth);
4483 return $e->event unless $e->checkauth;
4485 $user_id ||= $e->requestor->id;
4487 $options->{limit} ||= 50;
4488 $options->{offset} ||= 0;
4490 if($user_id != $e->requestor->id) {
4491 my $perm = ($is_hold) ? 'VIEW_HOLD' : 'VIEW_CIRCULATIONS';
4492 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
4493 return $e->event unless $e->allowed($perm, $user->home_ou);
4496 my $db_func = ($is_hold) ? 'action.usr_visible_holds' : 'action.usr_visible_circs';
4498 my $data = $e->json_query({
4499 from => [$db_func, $user_id],
4500 limit => $$options{limit},
4501 offset => $$options{offset}
4503 # TODO: I only want IDs. code below didn't get me there
4504 # {"select":{"au":[{"column":"id", "result_field":"id",
4505 # "transform":"action.usr_visible_circs"}]}, "where":{"id":10}, "from":"au"}
4510 return undef unless @$data;
4514 # collect the batch of objects
4518 my $hold_list = $e->search_action_hold_request({id => [map { $_->{id} } @$data]});
4519 return $U->fire_object_event(undef, 'ahr.format.history.print', $hold_list, $$hold_list[0]->request_lib);
4523 my $circ_list = $e->search_action_circulation({id => [map { $_->{id} } @$data]});
4524 return $U->fire_object_event(undef, 'circ.format.history.print', $circ_list, $$circ_list[0]->circ_lib);
4527 } elsif ($for_email) {
4529 $conn->respond_complete(1) if $for_email; # no sense in waiting
4537 my $hold = $e->retrieve_action_hold_request($id);
4538 $U->create_events_for_hook('ahr.format.history.email', $hold, $hold->request_lib, undef, undef, 1);
4539 # events will be fired from action_trigger_runner
4543 my $circ = $e->retrieve_action_circulation($id);
4544 $U->create_events_for_hook('circ.format.history.email', $circ, $circ->circ_lib, undef, undef, 1);
4545 # events will be fired from action_trigger_runner
4549 } else { # just give me the data please
4557 my $hold = $e->retrieve_action_hold_request($id);
4558 $conn->respond({hold => $hold});
4562 my $circ = $e->retrieve_action_circulation($id);
4565 summary => $U->create_circ_chain_summary($e, $id)
4574 __PACKAGE__->register_method(
4575 method => "user_saved_search_cud",
4576 api_name => "open-ils.actor.user.saved_search.cud",
4579 desc => 'Create/Update/Delete Access to user saved searches',
4581 { desc => 'Authentication token', type => 'string' },
4582 { desc => 'Saved Search Object', type => 'object', class => 'auss' }
4585 desc => q/The retrieved or updated saved search object, or id of a deleted object; Event on error/,
4591 __PACKAGE__->register_method(
4592 method => "user_saved_search_cud",
4593 api_name => "open-ils.actor.user.saved_search.retrieve",
4596 desc => 'Retrieve a saved search object',
4598 { desc => 'Authentication token', type => 'string' },
4599 { desc => 'Saved Search ID', type => 'number' }
4602 desc => q/The saved search object, Event on error/,
4608 sub user_saved_search_cud {
4609 my( $self, $client, $auth, $search ) = @_;
4610 my $e = new_editor( authtoken=>$auth );
4611 return $e->die_event unless $e->checkauth;
4613 my $o_search; # prior version of the object, if any
4614 my $res; # to be returned
4616 # branch on the operation type
4618 if( $self->api_name =~ /retrieve/ ) { # Retrieve
4620 # Get the old version, to check ownership
4621 $o_search = $e->retrieve_actor_usr_saved_search( $search )
4622 or return $e->die_event;
4624 # You can't read somebody else's search
4625 return OpenILS::Event->new('BAD_PARAMS')
4626 unless $o_search->owner == $e->requestor->id;
4632 $e->xact_begin; # start an editor transaction
4634 if( $search->isnew ) { # Create
4636 # You can't create a search for somebody else
4637 return OpenILS::Event->new('BAD_PARAMS')
4638 unless $search->owner == $e->requestor->id;
4640 $e->create_actor_usr_saved_search( $search )
4641 or return $e->die_event;
4645 } elsif( $search->ischanged ) { # Update
4647 # You can't change ownership of a search
4648 return OpenILS::Event->new('BAD_PARAMS')
4649 unless $search->owner == $e->requestor->id;
4651 # Get the old version, to check ownership
4652 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4653 or return $e->die_event;
4655 # You can't update somebody else's search
4656 return OpenILS::Event->new('BAD_PARAMS')
4657 unless $o_search->owner == $e->requestor->id;
4660 $e->update_actor_usr_saved_search( $search )
4661 or return $e->die_event;
4665 } elsif( $search->isdeleted ) { # Delete
4667 # Get the old version, to check ownership
4668 $o_search = $e->retrieve_actor_usr_saved_search( $search->id )
4669 or return $e->die_event;
4671 # You can't delete somebody else's search
4672 return OpenILS::Event->new('BAD_PARAMS')
4673 unless $o_search->owner == $e->requestor->id;
4676 $e->delete_actor_usr_saved_search( $o_search )
4677 or return $e->die_event;
4688 __PACKAGE__->register_method(
4689 method => "get_barcodes",
4690 api_name => "open-ils.actor.get_barcodes"
4694 my( $self, $client, $auth, $org_id, $context, $barcode ) = @_;
4695 my $e = new_editor(authtoken => $auth);
4696 return $e->event unless $e->checkauth;
4697 return $e->event unless $e->allowed('STAFF_LOGIN', $org_id);
4699 my $db_result = $e->json_query(
4701 'evergreen.get_barcodes',
4702 $org_id, $context, $barcode,
4706 if($context =~ /actor/) {
4707 my $filter_result = ();
4709 foreach my $result (@$db_result) {
4710 if($result->{type} eq 'actor') {
4711 if($e->requestor->id != $result->{id}) {
4712 $patron = $e->retrieve_actor_user($result->{id});
4714 push(@$filter_result, $e->event);
4717 if($e->allowed('VIEW_USER', $patron->home_ou)) {
4718 push(@$filter_result, $result);
4721 push(@$filter_result, $e->event);
4725 push(@$filter_result, $result);
4729 push(@$filter_result, $result);
4732 return $filter_result;
4738 __PACKAGE__->register_method(
4739 method => 'address_alert_test',
4740 api_name => 'open-ils.actor.address_alert.test',
4742 desc => "Tests a set of address fields to determine if they match with an address_alert",
4744 {desc => 'Authentication token', type => 'string'},
4745 {desc => 'Org Unit', type => 'number'},
4746 {desc => 'Fields', type => 'hash'},
4748 return => {desc => 'List of matching address_alerts'}
4752 sub address_alert_test {
4753 my ($self, $client, $auth, $org_unit, $fields) = @_;
4754 return [] unless $fields and grep {$_} values %$fields;
4756 my $e = new_editor(authtoken => $auth);
4757 return $e->event unless $e->checkauth;
4758 return $e->event unless $e->allowed('CREATE_USER', $org_unit);
4759 $org_unit ||= $e->requestor->ws_ou;
4761 my $alerts = $e->json_query({
4763 'actor.address_alert_matches',
4771 $$fields{post_code},
4772 $$fields{mailing_address},
4773 $$fields{billing_address}
4777 # map the json_query hashes to real objects
4779 map {$e->retrieve_actor_address_alert($_)}
4780 (map {$_->{id}} @$alerts)
4784 __PACKAGE__->register_method(
4785 method => "mark_users_contact_invalid",
4786 api_name => "open-ils.actor.invalidate.email",
4788 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",
4790 {desc => "Authentication token", type => "string"},
4791 {desc => "Patron ID (optional if Email address specified)", type => "number"},
4792 {desc => "Additional note text (optional)", type => "string"},
4793 {desc => "penalty org unit ID (optional)", type => "number"},
4794 {desc => "Email address (optional)", type => "string"}
4796 return => {desc => "Event describing success or failure", type => "object"}
4800 __PACKAGE__->register_method(
4801 method => "mark_users_contact_invalid",
4802 api_name => "open-ils.actor.invalidate.day_phone",
4804 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",
4806 {desc => "Authentication token", type => "string"},
4807 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4808 {desc => "Additional note text (optional)", type => "string"},
4809 {desc => "penalty org unit ID (optional)", type => "number"},
4810 {desc => "Phone Number (optional)", type => "string"}
4812 return => {desc => "Event describing success or failure", type => "object"}
4816 __PACKAGE__->register_method(
4817 method => "mark_users_contact_invalid",
4818 api_name => "open-ils.actor.invalidate.evening_phone",
4820 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",
4822 {desc => "Authentication token", type => "string"},
4823 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4824 {desc => "Additional note text (optional)", type => "string"},
4825 {desc => "penalty org unit ID (optional)", type => "number"},
4826 {desc => "Phone Number (optional)", type => "string"}
4828 return => {desc => "Event describing success or failure", type => "object"}
4832 __PACKAGE__->register_method(
4833 method => "mark_users_contact_invalid",
4834 api_name => "open-ils.actor.invalidate.other_phone",
4836 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",
4838 {desc => "Authentication token", type => "string"},
4839 {desc => "Patron ID (optional if Phone Number specified)", type => "number"},
4840 {desc => "Additional note text (optional)", type => "string"},
4841 {desc => "penalty org unit ID (optional, default to top of org tree)",
4843 {desc => "Phone Number (optional)", type => "string"}
4845 return => {desc => "Event describing success or failure", type => "object"}
4849 sub mark_users_contact_invalid {
4850 my ($self, $conn, $auth, $patron_id, $addl_note, $penalty_ou, $contact) = @_;
4852 # This method invalidates an email address or a phone_number which
4853 # removes the bad email address or phone number, copying its contents
4854 # to a patron note, and institutes a standing penalty for "bad email"
4855 # or "bad phone number" which is cleared when the user is saved or
4856 # optionally only when the user is saved with an email address or
4857 # phone number (or staff manually delete the penalty).
4859 my $contact_type = ($self->api_name =~ /invalidate.(\w+)(\.|$)/)[0];
4861 my $e = new_editor(authtoken => $auth, xact => 1);
4862 return $e->die_event unless $e->checkauth;
4865 if (defined $patron_id && $patron_id ne "") {
4866 $howfind = {usr => $patron_id};
4867 } elsif (defined $contact && $contact ne "") {
4868 $howfind = {$contact_type => $contact};
4870 # Error out if no patron id set or no contact is set.
4871 return OpenILS::Event->new('BAD_PARAMS');
4874 return OpenILS::Utils::BadContact->mark_users_contact_invalid(
4875 $e, $contact_type, $howfind,
4876 $addl_note, $penalty_ou, $e->requestor->id
4880 # Putting the following method in open-ils.actor is a bad fit, except in that
4881 # it serves an interface that lives under 'actor' in the templates directory,
4882 # and in that there's nowhere else obvious to put it (open-ils.trigger is
4884 __PACKAGE__->register_method(
4885 api_name => "open-ils.actor.action_trigger.reactors.all_in_use",
4886 method => "get_all_at_reactors_in_use",
4891 { name => 'authtoken', type => 'string' }
4894 desc => 'list of reactor names', type => 'array'
4899 sub get_all_at_reactors_in_use {
4900 my ($self, $conn, $auth) = @_;
4902 my $e = new_editor(authtoken => $auth);
4903 $e->checkauth or return $e->die_event;
4904 return $e->die_event unless $e->allowed('VIEW_TRIGGER_EVENT_DEF');
4906 my $reactors = $e->json_query({
4908 atevdef => [{column => "reactor", transform => "distinct"}]
4910 from => {atevdef => {}}
4913 return $e->die_event unless ref $reactors eq "ARRAY";
4916 return [ map { $_->{reactor} } @$reactors ];
4919 __PACKAGE__->register_method(
4920 method => "filter_group_entry_crud",
4921 api_name => "open-ils.actor.filter_group_entry.crud",
4924 Provides CRUD access to filter group entry objects. These are not full accessible
4925 via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
4926 are not accessible via PCRUD (because they have no fields against which to link perms)
4929 {desc => "Authentication token", type => "string"},
4930 {desc => "Entry ID / Entry Object", type => "number"},
4931 {desc => "Additional note text (optional)", type => "string"},
4932 {desc => "penalty org unit ID (optional, default to top of org tree)",
4936 desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
4942 sub filter_group_entry_crud {
4943 my ($self, $conn, $auth, $arg) = @_;
4945 return OpenILS::Event->new('BAD_PARAMS') unless $arg;
4946 my $e = new_editor(authtoken => $auth, xact => 1);
4947 return $e->die_event unless $e->checkauth;
4953 my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
4954 or return $e->die_event;
4956 return $e->die_event unless $e->allowed(
4957 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
4959 my $query = $arg->query;
4960 $query = $e->create_actor_search_query($query) or return $e->die_event;
4961 $arg->query($query->id);
4962 my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
4963 $entry->query($query);
4968 } elsif ($arg->ischanged) {
4970 my $entry = $e->retrieve_actor_search_filter_group_entry([
4973 flesh_fields => {asfge => ['grp']}
4975 ]) or return $e->die_event;
4977 return $e->die_event unless $e->allowed(
4978 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
4980 my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
4981 $arg->query($arg->query->id);
4982 $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
4983 $arg->query($query);
4988 } elsif ($arg->isdeleted) {
4990 my $entry = $e->retrieve_actor_search_filter_group_entry([
4993 flesh_fields => {asfge => ['grp', 'query']}
4995 ]) or return $e->die_event;
4997 return $e->die_event unless $e->allowed(
4998 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
5000 $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
5001 $e->delete_actor_search_query($entry->query) or return $e->die_event;
5014 my $entry = $e->retrieve_actor_search_filter_group_entry([
5017 flesh_fields => {asfge => ['grp', 'query']}
5019 ]) or return $e->die_event;
5021 return $e->die_event unless $e->allowed(
5022 ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
5023 $entry->grp->owner);
5026 $entry->grp($entry->grp->id); # for consistency