1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2016 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;
67 use base qw/RT::Base/;
70 use Scalar::Util 'blessed';
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'} = RT::Attribute->new($self->CurrentUser);
107 $self->{'Attribute'}->Load( $id );
108 if ($self->{'Attribute'}->Id) {
109 $self->{'Id'} = $self->{'Attribute'}->Id;
110 $self->{'Privacy'} = $privacy;
113 return wantarray ? (0, $self->loc("Permission Denied")) : 0
114 unless $self->CurrentUserCanSee;
116 my ($ok, $msg) = $self->PostLoadValidate;
117 return wantarray ? ($ok, $msg) : $ok if !$ok;
119 return wantarray ? (1, $self->loc("Loaded [_1] [_2]", $self->ObjectName, $self->Name)) : 1;
121 $RT::Logger->error("Could not load attribute " . $id
122 . " for object " . $privacy);
123 return wantarray ? (0, $self->loc("Failed to load [_1] [_2]", $self->ObjectName, $id)) : 0;
126 $RT::Logger->warning("Could not load object $privacy when loading " . $self->ObjectName);
127 return wantarray ? (0, $self->loc("Could not load object for [_1]", $privacy)) : 0;
133 First loads up the L<RT::Attribute> for this shared setting by ID, then calls
134 L</Load> with the correct parameters. Returns a tuple of status and message,
135 where status is true on success.
143 my $attr = RT::Attribute->new($self->CurrentUser);
144 my ($ok, $msg) = $attr->LoadById($id);
147 return wantarray ? (0, $self->loc("Failed to load [_1] [_2]: [_3]", $self->ObjectName, $id, $msg)) : 0;
150 my $privacy = $self->_build_privacy($attr->ObjectType, $attr->ObjectId);
151 return wantarray ? (0, $self->loc("Bad privacy for attribute [_1]", $id)) : 0
154 return $self->Load($privacy, $id);
159 Called after a successful L</Load>.
165 =head2 PostLoadValidate
167 Called just before returning success from L</Load>; may be used to validate
168 that the record is correct. This method is expected to return a (ok, msg)
173 sub PostLoadValidate {
179 Creates a new shared setting. Takes a privacy, a name, and any other arguments.
180 Saves the given parameters to the appropriate user/group object, and loads the
181 resulting object. Arguments are passed to the L</SaveAttribute> method, which
182 does the actual update. Returns a tuple of status and message, where status is
183 true on success. Defaults are:
185 Privacy: CurrentUser only
186 Name: "new (ObjectName)"
193 'Privacy' => 'RT::User-' . $self->CurrentUser->Id,
194 'Name' => "new " . $self->ObjectName,
198 my $privacy = $args{'Privacy'};
199 my $name = $args{'Name'},
200 my $object = $self->_GetObject($privacy);
202 return (0, $self->loc("Failed to load object for [_1]", $privacy))
205 return (0, $self->loc("Permission Denied"))
206 unless $self->CurrentUserCanCreate($privacy);
208 my ($att_id, $att_msg) = $self->SaveAttribute($object, \%args);
211 $self->{'Attribute'} = RT::Attribute->new($self->CurrentUser);
212 $self->{'Attribute'}->Load( $att_id );
213 $self->{'Id'} = $att_id;
214 $self->{'Privacy'} = $privacy;
215 return ( 1, $self->loc( "Saved [_1] [_2]", $self->loc( $self->ObjectName ), $name ) );
218 $RT::Logger->error($self->ObjectName . " save failure: $att_msg");
219 return ( 0, $self->loc("Failed to create [_1] attribute", $self->loc( $self->ObjectName ) ) );
225 An empty method for subclassing. Called from L</Save> method.
229 sub SaveAttribute { }
233 Updates the parameters of an existing shared setting. Any arguments are passed
234 to the L</UpdateAttribute> method. Returns a tuple of status and message, where
235 status is true on success.
243 return(0, $self->loc("No [_1] loaded", $self->ObjectName)) unless $self->Id;
244 return(0, $self->loc("Could not load [_1] attribute", $self->ObjectName))
245 unless $self->{'Attribute'}->Id;
247 return (0, $self->loc("Permission Denied"))
248 unless $self->CurrentUserCanModify;
250 my ($status, $msg) = $self->UpdateAttribute(\%args);
252 return (1, $self->loc("[_1] update: Nothing changed", ucfirst($self->ObjectName)))
255 # prevent useless warnings
256 return (1, $self->loc("[_1] updated"), ucfirst($self->ObjectName))
257 if $msg =~ /That is already the current value/;
259 return ($status, $self->loc("[_1] update: [_2]", ucfirst($self->ObjectName), $msg));
262 =head2 UpdateAttribute
264 An empty method for subclassing. Called from L</Update> method.
268 sub UpdateAttribute { }
272 Deletes the existing shared setting. Returns a tuple of status and message,
273 where status is true upon success.
279 return (0, $self->loc("Permission Denied"))
280 unless $self->CurrentUserCanDelete;
282 my ($status, $msg) = $self->{'Attribute'}->Delete;
283 $self->CurrentUser->ClearAttributes; # force the current user's attribute cache to be cleaned up
285 return (1, $self->loc("Deleted [_1]", $self->ObjectName));
287 return (0, $self->loc("Delete failed: [_1]", $msg));
295 Returns the name of this shared setting.
301 return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
302 return $self->{'Attribute'}->Description();
307 Returns the numerical ID of this shared setting.
313 return $self->{'Id'};
321 Returns the principal object to whom this shared setting belongs, in a string
322 "<class>-<id>", e.g. "RT::Group-16".
328 return $self->{'Privacy'};
333 Returns the given named parameter of the setting.
340 return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
341 return $self->{'Attribute'}->SubValue($param);
344 =head2 IsVisibleTo Privacy
346 Returns true if the setting is visible to all principals of the given privacy.
347 This does not deal with ACLs, this only looks at membership.
354 my $privacy = $self->Privacy || '';
356 # if the privacies are the same, then they can be seen. this handles
357 # a personal setting being visible to that user.
358 return 1 if $privacy eq $to;
360 # If the setting is systemwide, then any user can see it.
361 return 1 if $privacy =~ /^RT::System/;
363 # Only privacies that are RT::System can be seen by everyone.
364 return 0 if $to =~ /^RT::System/;
366 # If the setting is group-wide...
367 if ($privacy =~ /^RT::Group-(\d+)$/) {
368 my $setting_group = RT::Group->new($self->CurrentUser);
369 $setting_group->Load($1);
371 if ($to =~ /-(\d+)$/) {
374 # then any principal that is a member of the setting's group can see
376 return $setting_group->HasMemberRecursively($to_id);
383 sub CurrentUserCanSee { 1 }
384 sub CurrentUserCanCreate { 1 }
385 sub CurrentUserCanModify { 1 }
386 sub CurrentUserCanDelete { 1 }
390 # _GetObject: helper routine to load the correct object whose parameters
397 # short circuit: if they pass the object we want anyway, just return it
398 if (blessed($privacy) && $privacy->isa('RT::Record')) {
402 my ($obj_type, $obj_id) = split(/\-/, ($privacy || ''));
404 unless ($obj_type && $obj_id) {
405 $privacy = '(undef)' if !defined($privacy);
406 $RT::Logger->debug("Invalid privacy string '$privacy'");
410 my $object = $self->_load_privacy_object($obj_type, $obj_id);
412 unless (ref($object) eq $obj_type) {
413 $RT::Logger->error("Could not load object of type $obj_type with ID $obj_id, got object of type " . (ref($object) || 'undef'));
417 # Do not allow the loading of a user object other than the current
418 # user, or of a group object of which the current user is not a member.
420 if ($obj_type eq 'RT::User' && $object->Id != $self->CurrentUser->UserObj->Id) {
421 $RT::Logger->debug("Permission denied for user other than self");
425 if ( $obj_type eq 'RT::Group'
426 && !$object->HasMemberRecursively($self->CurrentUser->PrincipalObj)
427 && !$self->CurrentUser->HasRight( Object => $RT::System, Right => 'SuperUser' ) ) {
428 $RT::Logger->debug("Permission denied, ".$self->CurrentUser->Name.
429 " is not a member of group");
436 sub _load_privacy_object {
437 my ($self, $obj_type, $obj_id) = @_;
438 if ( $obj_type eq 'RT::User' ) {
439 if ( $obj_id == $self->CurrentUser->Id ) {
440 return $self->CurrentUser->UserObj;
442 $RT::Logger->warning("User #". $self->CurrentUser->Id ." tried to load container user #". $obj_id);
446 elsif ($obj_type eq 'RT::Group') {
447 my $group = RT::Group->new($self->CurrentUser);
448 $group->Load($obj_id);
451 elsif ($obj_type eq 'RT::System') {
452 return RT::System->new($self->CurrentUser);
456 "Tried to load a ". $self->ObjectName
457 ." belonging to an $obj_type, which is neither a user nor a group"
464 my ($self, $obj_type, $obj_id) = @_;
466 # allow passing in just an object to find its privacy string
467 if (ref($obj_type)) {
468 my $Object = $obj_type;
469 return $Object->isa('RT::User') ? 'RT::User-' . $Object->Id
470 : $Object->isa('RT::Group') ? 'RT::Group-' . $Object->Id
471 : $Object->isa('RT::System') ? 'RT::System-' . $Object->Id
475 return undef unless ($obj_type); # undef workaround
476 return $obj_type eq 'RT::User' ? "$obj_type-$obj_id"
477 : $obj_type eq 'RT::Group' ? "$obj_type-$obj_id"
478 : $obj_type eq 'RT::System' ? "$obj_type-$obj_id"
482 =head2 ObjectsForLoading
484 Returns a list of objects that can be used to load this shared setting. It
489 sub ObjectsForLoading {
491 return grep { $self->CurrentUserCanSee($_) } $self->_PrivacyObjects;
494 =head2 ObjectsForCreating
496 Returns a list of objects that can be used to create this shared setting. It
501 sub ObjectsForCreating {
503 return grep { $self->CurrentUserCanCreate($_) } $self->_PrivacyObjects;
506 =head2 ObjectsForModifying
508 Returns a list of objects that can be used to modify this shared setting. It
513 sub ObjectsForModifying {
515 return grep { $self->CurrentUserCanModify($_) } $self->_PrivacyObjects;
518 RT::Base->_ImportOverlays();