LP#1750894 Workstation & Cascade settings
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Actor / Settings.pm
1 package OpenILS::Application::Actor::Settings;
2 use strict; use warnings;
3 use base 'OpenILS::Application';
4 use OpenSRF::AppSession;
5 use OpenSRF::Utils::Logger q/$logger/;
6 use OpenILS::Application::AppUtils;
7 use OpenILS::Utils::CStoreEditor q/:funcs/;
8 use OpenILS::Utils::Fieldmapper;
9 use OpenSRF::Utils::JSON;
10 use OpenILS::Event;
11 my $U = "OpenILS::Application::AppUtils";
12
13 # Setting names may only contains letters, numbers, unders, and dots.
14 my $name_regex = qr/[^a-zA-Z0-9_\.]/;
15
16 __PACKAGE__->register_method (
17     method      => 'retrieve_settings',
18     api_name    => 'open-ils.actor.settings.retrieve',
19     stream      => 1,
20     signature => {
21         desc => q/
22             Returns org unit, user, and workstation setting values
23             for the requested setting types.
24
25             The API makes a best effort to find the correct setting
26             value based on the available context data.
27
28             If no auth token is provided, only publicly visible org
29             unit settings may be returned.
30
31             If no workstation is linked to the provided auth token, only
32             user settings and perm-visible org unit settings may be
33             returned.
34
35             If no org unit is provided, but a workstation is linked to the
36             auth token, the owning lib of the workstation is used as the
37             context org unit.
38         /,
39         params => [
40             {desc => 'settings. List of setting names', type => 'array'},
41             {desc => 'authtoken. Optional', type => 'string'},
42             {desc => 'org_id. Optional', type => 'number'}
43         ],
44         return => {
45             desc => q/
46                 Stream of setting name=>value pairs in the same order
47                 as the provided list of setting names.  No key-value
48                 pair is returned for settings that have no value defined./,
49             type => 'string'
50         }
51     }
52 );
53
54 sub retrieve_settings {
55     my ($self, $client, $settings, $auth, $org_id) = @_;
56
57     my ($aou_id, $user_id, $ws_id, $evt) = get_context($auth, $org_id);
58     return $evt if $evt; # bogus auth token
59
60     return OpenILS::Event->new('BAD_PARAMS',
61         desc => 'Cannot retrieve settings without a user or org unit')
62         unless ($user_id || $aou_id);
63
64     # Setting names may only contains letters, numbers, unders, and dots.
65     s/$name_regex//g foreach @$settings;
66
67     # Encode as a db-friendly array.
68     my $settings_str = '{' . join(',', @$settings) . '}';
69
70     # Some settings could be bulky, so fetch them as a stream from
71     # cstore, relaying values back to the caller as they arrive.
72     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
73     my $req = $ses->request('open-ils.cstore.json_query', {
74         from => [
75             'actor.get_cascade_setting_batch',
76             $settings_str, $aou_id, $user_id, $ws_id
77         ]
78     });
79
80     while (my $resp = $req->recv) {
81         my $summary = $resp->content;
82         $summary->{value} = OpenSRF::Utils::JSON->JSON2perl($summary->{value});
83         $client->respond($summary);
84     }
85
86     $ses->kill_me;
87     return undef;
88 }
89
90 # Returns ($org_id, $user_id, $ws_id, $evt);
91 # Any value may be undef.
92 sub get_context {
93     my ($auth, $org_id) = @_;
94
95     return ($org_id) unless $auth;
96
97     my $e = new_editor(authtoken => $auth);
98     return (undef, undef, undef, $e->event) unless $e->checkauth;
99
100     my $user_id = $e->requestor->id;
101     my $ws_id = $e->requestor->wsid;
102
103     # default to the workstation org if needed.
104     $org_id = $e->requestor->ws_ou if $ws_id && !$org_id;
105
106     return ($org_id, $user_id, $ws_id);
107 }
108
109 __PACKAGE__->register_method (
110     method      => 'apply_user_or_ws_setting',
111     api_name    => 'open-ils.actor.settings.apply.user_or_ws',
112     stream      => 1,
113     signature => {
114         desc => q/
115             Apply values to user or workstation settings, depending
116             on which is supported via local configuration.
117
118             The API ignores nonexistent settings and only returns error
119             events when an auth, permission, or internal error occurs.
120         /,
121         params => [
122             {desc => 'authtoken', type => 'string'},
123             {desc => 'settings. Hash of key/value pairs', type => 'object'},
124         ],
125         return => {
126             desc => 'Returns the number of applied settings on succes, Event on error.',
127             type => 'number or event'
128         }
129     }
130 );
131
132 sub apply_user_or_ws_setting {
133     my ($self, $client, $auth, $settings) = @_;
134
135     my $e = new_editor(authtoken => $auth, xact => 1);
136     return $e->die_event unless $e->checkauth;
137
138     my $applied = 0;
139     my $ws_allowed = 0;
140
141     for my $name (keys %$settings) {
142         $name =~ s/$name_regex//g;
143         my $val = $$settings{$name};
144         my $stype = $e->retrieve_config_usr_setting_type($name);
145
146         if ($stype) {
147             my $evt = apply_user_setting($e, $name, $val);
148             return $evt if $evt;
149             $applied++;
150
151         } elsif ($e->requestor->wsid) {
152             $stype = $e->retrieve_config_workstation_setting_type($name);
153             next unless $stype; # no such workstation setting, skip.
154
155             if (!$ws_allowed) {
156                 # Confirm the caller has permission to apply workstation
157                 # settings at the logged-in workstation before applying.
158                 # Do the perm check here so it's only needed once per batch.
159                 return $e->die_event unless
160                     $ws_allowed = $e->allowed('APPLY_WORKSTATION_SETTING');
161             }
162
163             my $evt = apply_workstation_setting($e, $name, $val);
164             return $evt if $evt;
165             $applied++;
166         }
167     }
168
169     $e->commit if $applied > 0;
170     $e->rollback if $applied == 0;
171
172     return $applied;
173 }
174
175 # CUD for user settings.
176 # Returns undef on success, Event on error.
177 # NOTE: This code was copied as-is from
178 # open-ils.actor.patron.settings.update, because it lets us
179 # manage the batch of updates within a single transaction.  Also
180 # worth noting the APIs in this mod could eventually replace
181 # open-ils.actor.patron.settings.update.  Maybe.
182 sub apply_user_setting {
183     my ($e, $name, $val) = @_;
184     my $user_id = $e->requestor->id;
185
186     my $set = $e->search_actor_user_setting(
187         {usr => $user_id, name => $name})->[0];
188
189     if (defined $val) {
190         $val = OpenSRF::Utils::JSON->perl2JSON($val);
191         if ($set) {
192             $set->value($val);
193             $e->update_actor_user_setting($set) or return $e->die_event;
194         } else {
195             $set = Fieldmapper::actor::user_setting->new;
196             $set->usr($user_id);
197             $set->name($name);
198             $set->value($val);
199             $e->create_actor_user_setting($set) or return $e->die_event;
200         }
201     } elsif ($set) {
202         $e->delete_actor_user_setting($set) or return $e->die_event;
203     }
204
205     return undef;
206 }
207
208 # CUD for workstation settings.
209 # Assumes ->wsid contains a value and permissions have been checked.
210 # Returns undef on success, Event on error.
211 sub apply_workstation_setting {
212     my ($e, $name, $val) = @_;
213     my $ws_id = $e->requestor->wsid;
214
215     my $set = $e->search_actor_workstation_setting(
216         {workstation => $ws_id, name => $name})->[0];
217
218     if (defined $val) {
219         $val = OpenSRF::Utils::JSON->perl2JSON($val);
220
221         if ($set) {
222             $set->value($val);
223             $e->update_actor_workstation_setting($set) or return $e->die_event;
224         } else {
225             $set = Fieldmapper::actor::workstation_setting->new;
226             $set->workstation($ws_id);
227             $set->name($name);
228             $set->value($val);
229             $e->create_actor_workstation_setting($set) or return $e->die_event;
230         }
231     } elsif ($set) {
232         $e->delete_actor_workstation_setting($set) or return $e->die_event;
233     }
234
235     return undef;
236 }
237
238 __PACKAGE__->register_method (
239     method      => 'applied_settings',
240     api_name    => 'open-ils.actor.settings.staff.applied.names',
241     stream      => 1,
242     authoritative => 1,
243     signature => {
244         desc => q/
245             Returns a list of setting names where a value is applied to
246             the current user or workstation.
247
248             This is a staff-only API created primarily to support the
249             getKeys() functionality used in the browser client for
250             server-managed settings.
251         /,
252         params => [
253             {desc => 'authtoken', type => 'string'},
254             {desc =>
255                 'prefix.  Limit keys to those starting with $prefix',
256              type => 'string'
257             },
258         ],
259         return => {
260             desc => 'List of strings, Event on error',
261             type => 'array'
262         }
263     }
264 );
265
266 sub applied_settings {
267     my ($self, $client, $auth, $prefix) = @_;
268
269     my $e = new_editor(authtoken => $auth);
270     return $e->event unless $e->checkauth;
271     return $e->event unless $e->allowed('STAFF_LOGIN');
272
273     my $query = {
274         select => {awss => ['name']},
275         from => 'awss',
276         where => {
277             workstation => $e->requestor->wsid
278         }
279     };
280
281     $query->{where}->{name} = {like => "$prefix%"} if $prefix;
282
283     for my $key (@{$e->json_query($query)}) {
284         $client->respond($key->{name});
285     }
286
287     $query = {
288         select => {aus => ['name']},
289         from => 'aus',
290         where => {
291             usr => $e->requestor->id
292         }
293     };
294
295     $query->{where}->{name} = {like => "$prefix%"} if $prefix;
296
297     for my $key (@{$e->json_query($query)}) {
298         $client->respond($key->{name});
299     }
300
301     return undef;
302 }
303
304
305
306 1;