1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
51 RT::SharedSetting - an API for settings that belong to an RT::User or RT::Group
55 use RT::SharedSetting;
59 A RT::SharedSetting is an object that can belong to an L<RT::User> or an <RT::Group>.
60 It consists of an ID, a name, and some arbitrary data.
64 package RT::SharedSetting;
69 use Scalar::Util 'blessed';
70 use base qw/RT::Base/;
76 Returns a new L<RT::SharedSetting> object.
77 Takes the current user, see also L<RT::Base>.
83 my $class = ref($proto) || $proto;
86 bless ($self, $class);
87 $self->CurrentUser(@_);
93 Takes a privacy specification and a shared-setting ID. Loads the given object
94 ID if it belongs to the stated user or group. Calls the L</PostLoad> method on
95 success for any further initialization. Returns a tuple of status and message,
96 where status is true on success.
102 my ($privacy, $id) = @_;
103 my $object = $self->_GetObject($privacy);
106 $self->{'Attribute'} = $object->Attributes->WithId($id);
107 if ($self->{'Attribute'}->Id) {
108 $self->{'Id'} = $self->{'Attribute'}->Id;
109 $self->{'Privacy'} = $privacy;
112 return (0, $self->loc("Permission denied"))
113 unless $self->CurrentUserCanSee;
115 my ($ok, $msg) = $self->PostLoadValidate;
116 return ($ok, $msg) if !$ok;
118 return (1, $self->loc("Loaded [_1] [_2]", $self->ObjectName, $self->Name));
120 $RT::Logger->error("Could not load attribute " . $id
121 . " for object " . $privacy);
122 return (0, $self->loc("Failed to load [_1] [_2]", $self->ObjectName, $id))
125 $RT::Logger->warning("Could not load object $privacy when loading " . $self->ObjectName);
126 return (0, $self->loc("Could not load object for [_1]", $privacy));
132 First loads up the L<RT::Attribute> for this shared setting by ID, then calls
133 L</Load> with the correct parameters. Returns a tuple of status and message,
134 where status is true on success.
142 my $attr = RT::Attribute->new($self->CurrentUser);
143 my ($ok, $msg) = $attr->LoadById($id);
146 return (0, $self->loc("Failed to load [_1] [_2]: [_3]", $self->ObjectName, $id, $msg))
149 my $privacy = $self->_build_privacy($attr->ObjectType, $attr->ObjectId);
150 return (0, $self->loc("Bad privacy for attribute [_1]", $id))
153 return $self->Load($privacy, $id);
158 Called after a successful L</Load>.
164 =head2 PostLoadValidate
166 Called just before returning success from L</Load>; may be used to validate
167 that the record is correct. This method is expected to return a (ok, msg)
172 sub PostLoadValidate {
178 Creates a new shared setting. Takes a privacy, a name, and any other arguments.
179 Saves the given parameters to the appropriate user/group object, and loads the
180 resulting object. Arguments are passed to the L</SaveAttribute> method, which
181 does the actual update. Returns a tuple of status and message, where status is
182 true on success. Defaults are:
184 Privacy: CurrentUser only
185 Name: "new (ObjectName)"
192 'Privacy' => 'RT::User-' . $self->CurrentUser->Id,
193 'Name' => "new " . $self->ObjectName,
197 my $privacy = $args{'Privacy'};
198 my $name = $args{'Name'},
199 my $object = $self->_GetObject($privacy);
201 return (0, $self->loc("Failed to load object for [_1]", $privacy))
204 return (0, $self->loc("Permission denied"))
205 unless $self->CurrentUserCanCreate($privacy);
207 my ($att_id, $att_msg) = $self->SaveAttribute($object, \%args);
210 $self->{'Attribute'} = $object->Attributes->WithId($att_id);
211 $self->{'Id'} = $att_id;
212 $self->{'Privacy'} = $privacy;
213 return ( 1, $self->loc( "Saved [_1] [_2]", $self->loc( $self->ObjectName ), $name ) );
216 $RT::Logger->error($self->ObjectName . " save failure: $att_msg");
217 return ( 0, $self->loc("Failed to create [_1] attribute", $self->loc( $self->ObjectName ) ) );
223 An empty method for subclassing. Called from L</Save> method.
227 sub SaveAttribute { }
231 Updates the parameters of an existing shared setting. Any arguments are passed
232 to the L</UpdateAttribute> method. Returns a tuple of status and message, where
233 status is true on success.
241 return(0, $self->loc("No [_1] loaded", $self->ObjectName)) unless $self->Id;
242 return(0, $self->loc("Could not load [_1] attribute", $self->ObjectName))
243 unless $self->{'Attribute'}->Id;
245 return (0, $self->loc("Permission denied"))
246 unless $self->CurrentUserCanModify;
248 my ($status, $msg) = $self->UpdateAttribute(\%args);
250 return (1, $self->loc("[_1] update: Nothing changed", ucfirst($self->ObjectName)))
253 # prevent useless warnings
254 return (1, $self->loc("[_1] updated"), ucfirst($self->ObjectName))
255 if $msg =~ /That is already the current value/;
257 return ($status, $self->loc("[_1] update: [_2]", ucfirst($self->ObjectName), $msg));
260 =head2 UpdateAttribute
262 An empty method for subclassing. Called from L</Update> method.
266 sub UpdateAttribute { }
270 Deletes the existing shared setting. Returns a tuple of status and message,
271 where status is true upon success.
277 return (0, $self->loc("Permission denied"))
278 unless $self->CurrentUserCanDelete;
280 my ($status, $msg) = $self->{'Attribute'}->Delete;
281 $self->CurrentUser->ClearAttributes; # force the current user's attribute cache to be cleaned up
283 return (1, $self->loc("Deleted [_1]", $self->ObjectName));
285 return (0, $self->loc("Delete failed: [_1]", $msg));
293 Returns the name of this shared setting.
299 return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
300 return $self->{'Attribute'}->Description();
305 Returns the numerical ID of this shared setting.
311 return $self->{'Id'};
319 Returns the principal object to whom this shared setting belongs, in a string
320 "<class>-<id>", e.g. "RT::Group-16".
326 return $self->{'Privacy'};
331 Returns the given named parameter of the setting.
338 return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
339 return $self->{'Attribute'}->SubValue($param);
342 =head2 IsVisibleTo Privacy
344 Returns true if the setting is visible to all principals of the given privacy.
345 This does not deal with ACLs, this only looks at membership.
352 my $privacy = $self->Privacy || '';
354 # if the privacies are the same, then they can be seen. this handles
355 # a personal setting being visible to that user.
356 return 1 if $privacy eq $to;
358 # If the setting is systemwide, then any user can see it.
359 return 1 if $privacy =~ /^RT::System/;
361 # Only privacies that are RT::System can be seen by everyone.
362 return 0 if $to =~ /^RT::System/;
364 # If the setting is group-wide...
365 if ($privacy =~ /^RT::Group-(\d+)$/) {
366 my $setting_group = RT::Group->new($self->CurrentUser);
367 $setting_group->Load($1);
369 if ($to =~ /-(\d+)$/) {
372 # then any principal that is a member of the setting's group can see
374 return $setting_group->HasMemberRecursively($to_id);
381 sub CurrentUserCanSee { 1 }
382 sub CurrentUserCanCreate { 1 }
383 sub CurrentUserCanModify { 1 }
384 sub CurrentUserCanDelete { 1 }
388 # _GetObject: helper routine to load the correct object whose parameters
395 # short circuit: if they pass the object we want anyway, just return it
396 if (blessed($privacy) && $privacy->isa('RT::Record')) {
400 my ($obj_type, $obj_id) = split(/\-/, ($privacy || ''));
402 unless ($obj_type && $obj_id) {
403 $privacy = '(undef)' if !defined($privacy);
404 $RT::Logger->debug("Invalid privacy string '$privacy'");
408 my $object = $self->_load_privacy_object($obj_type, $obj_id);
410 unless (ref($object) eq $obj_type) {
411 $RT::Logger->error("Could not load object of type $obj_type with ID $obj_id, got object of type " . (ref($object) || 'undef'));
415 # Do not allow the loading of a user object other than the current
416 # user, or of a group object of which the current user is not a member.
418 if ($obj_type eq 'RT::User' && $object->Id != $self->CurrentUser->UserObj->Id) {
419 $RT::Logger->debug("Permission denied for user other than self");
423 if ( $obj_type eq 'RT::Group'
424 && !$object->HasMemberRecursively($self->CurrentUser->PrincipalObj)
425 && !$self->CurrentUser->HasRight( Object => $RT::System, Right => 'SuperUser' ) ) {
426 $RT::Logger->debug("Permission denied, ".$self->CurrentUser->Name.
427 " is not a member of group");
434 sub _load_privacy_object {
435 my ($self, $obj_type, $obj_id) = @_;
436 if ( $obj_type eq 'RT::User' ) {
437 if ( $obj_id == $self->CurrentUser->Id ) {
438 return $self->CurrentUser->UserObj;
440 $RT::Logger->warning("User #". $self->CurrentUser->Id ." tried to load container user #". $obj_id);
444 elsif ($obj_type eq 'RT::Group') {
445 my $group = RT::Group->new($self->CurrentUser);
446 $group->Load($obj_id);
449 elsif ($obj_type eq 'RT::System') {
450 return RT::System->new($self->CurrentUser);
454 "Tried to load a ". $self->ObjectName
455 ." belonging to an $obj_type, which is neither a user nor a group"
462 my ($self, $obj_type, $obj_id) = @_;
464 # allow passing in just an object to find its privacy string
465 if (ref($obj_type)) {
466 my $Object = $obj_type;
467 return $Object->isa('RT::User') ? 'RT::User-' . $Object->Id
468 : $Object->isa('RT::Group') ? 'RT::Group-' . $Object->Id
469 : $Object->isa('RT::System') ? 'RT::System-' . $Object->Id
473 return undef unless ($obj_type); # undef workaround
474 return $obj_type eq 'RT::User' ? "$obj_type-$obj_id"
475 : $obj_type eq 'RT::Group' ? "$obj_type-$obj_id"
476 : $obj_type eq 'RT::System' ? "$obj_type-$obj_id"
480 =head2 ObjectsForLoading
482 Returns a list of objects that can be used to load this shared setting. It
487 sub ObjectsForLoading {
489 return grep { $self->CurrentUserCanSee($_) } $self->_PrivacyObjects;
492 =head2 ObjectsForCreating
494 Returns a list of objects that can be used to create this shared setting. It
499 sub ObjectsForCreating {
501 return grep { $self->CurrentUserCanCreate($_) } $self->_PrivacyObjects;
504 =head2 ObjectsForModifying
506 Returns a list of objects that can be used to modify this shared setting. It
511 sub ObjectsForModifying {
513 return grep { $self->CurrentUserCanModify($_) } $self->_PrivacyObjects;
516 RT::Base->_ImportOverlays();