1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2013 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 delete $RT::User::PREFERENCES_CACHE{ $args{'ObjectId'} }{ $args{'Name'} }
185 if $args{'ObjectType'} eq 'RT::User';
187 $self->SUPER::Create(
188 Name => $args{'Name'},
189 Content => $args{'Content'},
190 ContentType => $args{'ContentType'},
191 Description => $args{'Description'},
192 ObjectType => $args{'ObjectType'},
193 ObjectId => $args{'ObjectId'},
200 =head2 LoadByNameAndObject (Object => OBJECT, Name => NAME)
202 Loads the Attribute named NAME for Object OBJECT.
206 sub LoadByNameAndObject {
216 Name => $args{'Name'},
217 ObjectType => ref($args{'Object'}),
218 ObjectId => $args{'Object'}->Id,
226 =head2 _DeserializeContent
228 DeserializeContent returns this Attribute's "Content" as a hashref.
233 sub _DeserializeContent {
238 eval {$hashref = thaw(decode_base64($content))} ;
240 $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
250 Returns this attribute's content. If it's a scalar, returns a scalar
251 If it's data structure returns a ref to that data structure.
257 # Here we call _Value to get the ACL check.
258 my $content = $self->_Value('Content');
259 if ( ($self->__Value('ContentType') || '') eq 'storable') {
260 eval {$content = $self->_DeserializeContent($content); };
262 $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
270 sub _SerializeContent {
273 return( encode_base64(nfreeze($content)));
281 if ( $self->__Value('ObjectType') eq 'RT::User' ) {
282 delete $RT::User::PREFERENCES_CACHE
283 { $self->__Value('ObjectId') }{ $self->__Value('Name') };
286 # Call __Value to avoid ACL check.
287 if ( ($self->__Value('ContentType')||'') eq 'storable' ) {
288 # We eval the serialization because it will lose on a coderef.
289 $content = eval { $self->_SerializeContent($content) };
291 $RT::Logger->error("Content couldn't be frozen: $@");
292 return(0, "Content couldn't be frozen");
295 return $self->_Set( Field => 'Content', Value => $content );
300 Returns the subvalue for $key.
308 my $values = $self->Content();
309 return undef unless ref($values);
310 return($values->{$key});
313 =head2 DeleteSubValue NAME
315 Deletes the subvalue with the key NAME
322 my %values = $self->Content();
323 delete $values{$key};
324 $self->SetContent(%values);
331 =head2 DeleteAllSubValues
333 Deletes all subvalues for this attribute
338 sub DeleteAllSubValues {
340 $self->SetContent({});
343 =head2 SetSubValues { }
345 Takes a hash of keys and values and stores them in the content of this attribute.
347 Each key B<replaces> the existing key with the same name
349 Returns a tuple of (status, message)
357 my $values = ($self->Content() || {} );
358 foreach my $key (keys %args) {
359 $values->{$key} = $args{$key};
362 $self->SetContent($values);
369 my $object_type = $self->__Value('ObjectType');
371 eval { $object = $object_type->new($self->CurrentUser) };
372 unless(UNIVERSAL::isa($object, $object_type)) {
373 $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
376 $object->Load($self->__Value('ObjectId'));
385 unless ($self->CurrentUserHasRight('delete')) {
386 return (0,$self->loc('Permission Denied'));
388 return($self->SUPER::Delete(@_));
394 unless ($self->CurrentUserHasRight('display')) {
395 return (0,$self->loc('Permission Denied'));
398 return($self->SUPER::_Value(@_));
406 unless ($self->CurrentUserHasRight('update')) {
408 return (0,$self->loc('Permission Denied'));
410 return($self->SUPER::_Set(@_));
415 =head2 CurrentUserHasRight
417 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.
421 sub CurrentUserHasRight {
425 # object_right is the right that the user has to have on the object for them to have $right on this attribute
426 my $object_right = $self->LookupObjectRight(
428 ObjectId => $self->__Value('ObjectId'),
429 ObjectType => $self->__Value('ObjectType'),
430 Name => $self->__Value('Name')
433 return (1) if ($object_right eq 'allow');
434 return (0) if ($object_right eq 'deny');
435 return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
443 We should be deserializing the content on load and then enver again, rather than at every access
456 Returns the current value of id.
457 (In the database, id is stored as int(11).)
465 Returns the current value of Name.
466 (In the database, Name is stored as varchar(255).)
474 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
475 (In the database, Name will be stored as a varchar(255).)
483 Returns the current value of Description.
484 (In the database, Description is stored as varchar(255).)
488 =head2 SetDescription VALUE
491 Set Description to VALUE.
492 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
493 (In the database, Description will be stored as a varchar(255).)
501 Returns the current value of Content.
502 (In the database, Content is stored as blob.)
506 =head2 SetContent VALUE
509 Set Content to VALUE.
510 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
511 (In the database, Content will be stored as a blob.)
519 Returns the current value of ContentType.
520 (In the database, ContentType is stored as varchar(16).)
524 =head2 SetContentType VALUE
527 Set ContentType to VALUE.
528 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
529 (In the database, ContentType will be stored as a varchar(16).)
537 Returns the current value of ObjectType.
538 (In the database, ObjectType is stored as varchar(64).)
542 =head2 SetObjectType VALUE
545 Set ObjectType to VALUE.
546 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
547 (In the database, ObjectType will be stored as a varchar(64).)
555 Returns the current value of ObjectId.
556 (In the database, ObjectId is stored as int(11).)
560 =head2 SetObjectId VALUE
563 Set ObjectId to VALUE.
564 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
565 (In the database, ObjectId will be stored as a int(11).)
573 Returns the current value of Creator.
574 (In the database, Creator is stored as int(11).)
582 Returns the current value of Created.
583 (In the database, Created is stored as datetime.)
591 Returns the current value of LastUpdatedBy.
592 (In the database, LastUpdatedBy is stored as int(11).)
600 Returns the current value of LastUpdated.
601 (In the database, LastUpdated is stored as datetime.)
608 sub _CoreAccessible {
612 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
614 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
616 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
618 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''},
620 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
622 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
624 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
626 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
628 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
630 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
632 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
637 sub FindDependencies {
639 my ($walker, $deps) = @_;
641 $self->SUPER::FindDependencies($walker, $deps);
642 $deps->Add( out => $self->Object );
647 my ($importer, $uid, $data) = @_;
649 if ($data->{Object} and ref $data->{Object}) {
650 my $on_uid = ${ $data->{Object} };
651 return if $importer->ShouldSkipTransaction($on_uid);
653 return $class->SUPER::PreInflate( $importer, $uid, $data );
656 RT::Base->_ImportOverlays();