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 }}}
49 package RT::Attribute;
54 use base 'RT::Record';
56 sub Table {'Attributes'}
58 use Storable qw/nfreeze thaw/;
70 # the acl map is a map of "name of attribute" and "what right the user must have on the associated object to see/edit it
73 SavedSearch => { create => 'EditSavedSearches',
74 update => 'EditSavedSearches',
75 delete => 'EditSavedSearches',
76 display => 'ShowSavedSearches' },
80 # There are a number of attributes that users should be able to modify for themselves, such as saved searches
81 # we could do this with a different set of "update" rights, but that gets very hacky very fast. this is even faster and even
82 # hackier. we're hardcoding that a different set of rights are needed for attributes on oneself
83 our $PERSONAL_ACL_MAP = {
84 SavedSearch => { create => 'ModifySelf',
85 update => 'ModifySelf',
86 delete => 'ModifySelf',
91 =head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } }
93 Returns the right that the user needs to have on this attribute's object to perform the related attribute operation. Returns "allow" if the right is otherwise unspecified.
97 sub LookupObjectRight {
99 my %args = ( ObjectType => undef,
105 # if it's an attribute on oneself, check the personal acl map
106 if (($args{'ObjectType'} eq 'RT::User') && ($args{'ObjectId'} eq $self->CurrentUser->Id)) {
107 return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}});
108 return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
109 return($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
112 # otherwise check the main ACL map
114 return('allow') unless ($ACL_MAP->{$args{'Name'}});
115 return('allow') unless ($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
116 return($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
123 =head2 Create PARAMHASH
125 Create takes a hash of values and creates a row in the database:
128 varchar(255) 'Content'.
129 varchar(16) 'ContentType',
130 varchar(64) 'ObjectType'.
133 You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>.
150 if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) {
151 $args{ObjectType} = $args{Object}->isa("RT::CurrentUser") ? "RT::User" : ref($args{Object});
152 $args{ObjectId} = $args{Object}->Id;
154 return(0, $self->loc("Required parameter '[_1]' not specified", 'Object'));
158 # object_right is the right that the user has to have on the object for them to have $right on this attribute
159 my $object_right = $self->LookupObjectRight(
161 ObjectId => $args{'ObjectId'},
162 ObjectType => $args{'ObjectType'},
163 Name => $args{'Name'}
165 if ($object_right eq 'deny') {
166 return (0, $self->loc('Permission Denied'));
168 elsif ($object_right eq 'allow') {
169 # do nothing, we're ok
171 elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) {
172 return (0, $self->loc('Permission Denied'));
176 if (ref ($args{'Content'}) ) {
177 eval {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
181 $args{'ContentType'} = 'storable';
184 $self->SUPER::Create(
185 Name => $args{'Name'},
186 Content => $args{'Content'},
187 ContentType => $args{'ContentType'},
188 Description => $args{'Description'},
189 ObjectType => $args{'ObjectType'},
190 ObjectId => $args{'ObjectId'},
197 =head2 LoadByNameAndObject (Object => OBJECT, Name => NAME)
199 Loads the Attribute named NAME for Object OBJECT.
203 sub LoadByNameAndObject {
213 Name => $args{'Name'},
214 ObjectType => ref($args{'Object'}),
215 ObjectId => $args{'Object'}->Id,
223 =head2 _DeserializeContent
225 DeserializeContent returns this Attribute's "Content" as a hashref.
230 sub _DeserializeContent {
235 eval {$hashref = thaw(decode_base64($content))} ;
237 $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
247 Returns this attribute's content. If it's a scalar, returns a scalar
248 If it's data structure returns a ref to that data structure.
254 # Here we call _Value to get the ACL check.
255 my $content = $self->_Value('Content');
256 if ( ($self->__Value('ContentType') || '') eq 'storable') {
257 eval {$content = $self->_DeserializeContent($content); };
259 $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
267 sub _SerializeContent {
270 return( encode_base64(nfreeze($content)));
278 # Call __Value to avoid ACL check.
279 if ( ($self->__Value('ContentType')||'') eq 'storable' ) {
280 # We eval the serialization because it will lose on a coderef.
281 $content = eval { $self->_SerializeContent($content) };
283 $RT::Logger->error("Content couldn't be frozen: $@");
284 return(0, "Content couldn't be frozen");
287 my ($ok, $msg) = $self->_Set( Field => 'Content', Value => $content );
288 return ($ok, $self->loc("Attribute updated")) if $ok;
294 Returns the subvalue for $key.
302 my $values = $self->Content();
303 return undef unless ref($values);
304 return($values->{$key});
307 =head2 DeleteSubValue NAME
309 Deletes the subvalue with the key NAME
316 my $values = $self->Content();
317 delete $values->{$key};
318 $self->SetContent($values);
322 =head2 DeleteAllSubValues
324 Deletes all subvalues for this attribute
329 sub DeleteAllSubValues {
331 $self->SetContent({});
334 =head2 SetSubValues { }
336 Takes a hash of keys and values and stores them in the content of this attribute.
338 Each key B<replaces> the existing key with the same name
340 Returns a tuple of (status, message)
348 my $values = ($self->Content() || {} );
349 foreach my $key (keys %args) {
350 $values->{$key} = $args{$key};
353 $self->SetContent($values);
360 my $object_type = $self->__Value('ObjectType');
362 eval { $object = $object_type->new($self->CurrentUser) };
363 unless(UNIVERSAL::isa($object, $object_type)) {
364 $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
367 $object->Load($self->__Value('ObjectId'));
376 unless ($self->CurrentUserHasRight('delete')) {
377 return (0,$self->loc('Permission Denied'));
380 return($self->SUPER::Delete(@_));
386 unless ($self->CurrentUserHasRight('display')) {
387 return (0,$self->loc('Permission Denied'));
390 return($self->SUPER::_Value(@_));
398 unless ($self->CurrentUserHasRight('update')) {
400 return (0,$self->loc('Permission Denied'));
402 return($self->SUPER::_Set(@_));
407 =head2 CurrentUserHasRight
409 One of "display" "update" "delete" or "create" and returns 1 if the user has that right for attributes of this name for this object.Returns undef otherwise.
413 sub CurrentUserHasRight {
417 # object_right is the right that the user has to have on the object for them to have $right on this attribute
418 my $object_right = $self->LookupObjectRight(
420 ObjectId => $self->__Value('ObjectId'),
421 ObjectType => $self->__Value('ObjectType'),
422 Name => $self->__Value('Name')
425 return (1) if ($object_right eq 'allow');
426 return (0) if ($object_right eq 'deny');
427 return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
435 We should be deserializing the content on load and then enver again, rather than at every access
448 Returns the current value of id.
449 (In the database, id is stored as int(11).)
457 Returns the current value of Name.
458 (In the database, Name is stored as varchar(255).)
466 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
467 (In the database, Name will be stored as a varchar(255).)
475 Returns the current value of Description.
476 (In the database, Description is stored as varchar(255).)
480 =head2 SetDescription VALUE
483 Set Description to VALUE.
484 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
485 (In the database, Description will be stored as a varchar(255).)
493 Returns the current value of Content.
494 (In the database, Content is stored as blob.)
498 =head2 SetContent VALUE
501 Set Content to VALUE.
502 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
503 (In the database, Content will be stored as a blob.)
511 Returns the current value of ContentType.
512 (In the database, ContentType is stored as varchar(16).)
516 =head2 SetContentType VALUE
519 Set ContentType to VALUE.
520 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
521 (In the database, ContentType will be stored as a varchar(16).)
529 Returns the current value of ObjectType.
530 (In the database, ObjectType is stored as varchar(64).)
534 =head2 SetObjectType VALUE
537 Set ObjectType to VALUE.
538 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
539 (In the database, ObjectType will be stored as a varchar(64).)
547 Returns the current value of ObjectId.
548 (In the database, ObjectId is stored as int(11).)
552 =head2 SetObjectId VALUE
555 Set ObjectId to VALUE.
556 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
557 (In the database, ObjectId will be stored as a int(11).)
565 Returns the current value of Creator.
566 (In the database, Creator is stored as int(11).)
574 Returns the current value of Created.
575 (In the database, Created is stored as datetime.)
583 Returns the current value of LastUpdatedBy.
584 (In the database, LastUpdatedBy is stored as int(11).)
592 Returns the current value of LastUpdated.
593 (In the database, LastUpdated is stored as datetime.)
600 sub _CoreAccessible {
604 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
606 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
608 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
610 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''},
612 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
614 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
616 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
618 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
620 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
622 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
624 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
629 sub FindDependencies {
631 my ($walker, $deps) = @_;
633 $self->SUPER::FindDependencies($walker, $deps);
634 $deps->Add( out => $self->Object );
639 my ($importer, $uid, $data) = @_;
641 if ($data->{Object} and ref $data->{Object}) {
642 my $on_uid = ${ $data->{Object} };
643 return if $importer->ShouldSkipTransaction($on_uid);
645 return $class->SUPER::PreInflate( $importer, $uid, $data );
648 RT::Base->_ImportOverlays();