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::Link - an RT Link object
59 This module should never be called directly by client code. it's an internal module which
60 should only be accessed through exported APIs in Ticket other similar objects.
72 use base 'RT::Record';
77 use List::Util 'first';
78 use List::MoreUtils 'uniq';
80 # Helper tables for links mapping to make it easier
81 # to build and parse links between objects.
83 MemberOf => { Type => 'MemberOf', Mode => 'Target', Display => 0 },
84 Parents => { Type => 'MemberOf', Mode => 'Target', Display => 1 },
85 Parent => { Type => 'MemberOf', Mode => 'Target', Display => 0 },
86 Members => { Type => 'MemberOf', Mode => 'Base', Display => 0 },
87 Member => { Type => 'MemberOf', Mode => 'Base', Display => 0 },
88 Children => { Type => 'MemberOf', Mode => 'Base', Display => 1 },
89 Child => { Type => 'MemberOf', Mode => 'Base', Display => 0 },
90 HasMember => { Type => 'MemberOf', Mode => 'Base', Display => 0 },
91 RefersTo => { Type => 'RefersTo', Mode => 'Target', Display => 1 },
92 ReferredToBy => { Type => 'RefersTo', Mode => 'Base', Display => 1 },
93 DependsOn => { Type => 'DependsOn', Mode => 'Target', Display => 1 },
94 DependedOnBy => { Type => 'DependsOn', Mode => 'Base', Display => 1 },
95 MergedInto => { Type => 'MergedInto', Mode => 'Target', Display => 1 },
98 MemberOf => { Base => 'MemberOf', Target => 'HasMember' },
99 RefersTo => { Base => 'RefersTo', Target => 'ReferredToBy' },
100 DependsOn => { Base => 'DependsOn', Target => 'DependedOnBy' },
101 MergedInto => { Base => 'MergedInto', Target => 'MergedInto' },
104 __PACKAGE__->_BuildDisplayAs;
107 sub _BuildDisplayAs {
109 foreach my $in_db ( uniq map { $_->{Type} } values %TYPEMAP ) {
110 foreach my $mode (qw(Base Target)) {
111 $DISPLAY_AS{$in_db}{$mode} = first {
112 $TYPEMAP{$_}{Display}
113 && $TYPEMAP{$_}{Type} eq $in_db
114 && $TYPEMAP{$_}{Mode} eq $mode
124 Returns a list of the standard link Types for display, including directional
125 variants but not aliases.
139 =head2 Create PARAMHASH
141 Create a new link object. Takes 'Base', 'Target' and 'Type'.
142 Returns undef on failure or a Link Id on success.
148 my %args = ( Base => undef,
153 my $base = RT::URI->new( $self->CurrentUser );
154 unless ($base->FromURI( $args{'Base'} )) {
155 my $msg = $self->loc("Couldn't resolve base '[_1]' into a URI.", $args{'Base'});
156 $RT::Logger->warning( "$self $msg" );
157 return wantarray ? (undef, $msg) : undef;
160 my $target = RT::URI->new( $self->CurrentUser );
161 unless ($target->FromURI( $args{'Target'} )) {
162 my $msg = $self->loc("Couldn't resolve target '[_1]' into a URI.", $args{'Target'});
163 $RT::Logger->warning( "$self $msg" );
164 return wantarray ? (undef, $msg) : undef;
173 if ( $base->IsLocal ) {
174 my $object = $base->Object;
175 unless (UNIVERSAL::can($object, 'Id')) {
176 return (undef, $self->loc("[_1] appears to be a local object, but can't be found in the database", $args{'Base'}));
179 $base_id = $object->Id if UNIVERSAL::isa($object, 'RT::Ticket');
181 if ( $target->IsLocal ) {
182 my $object = $target->Object;
183 unless (UNIVERSAL::can($object, 'Id')) {
184 return (undef, $self->loc("[_1] appears to be a local object, but can't be found in the database", $args{'Target'}));
187 $target_id = $object->Id if UNIVERSAL::isa($object, 'RT::Ticket');
190 # We don't want references to ourself
191 if ( $base->URI eq $target->URI ) {
192 return ( 0, $self->loc("Can't link a ticket to itself") );
197 my ( $id, $msg ) = $self->SUPER::Create( Base => $base->URI,
198 Target => $target->URI,
199 LocalBase => $base_id,
200 LocalTarget => $target_id,
201 Type => $args{'Type'} );
202 return ( $id, $msg );
209 Load an RT::Link object from the database. Takes three parameters
215 Base and Target are expected to be integers which refer to Tickets or URIs
216 Type is the link type
222 my %args = ( Base => undef,
227 my $base = RT::URI->new($self->CurrentUser);
228 $base->FromURI( $args{'Base'} )
229 or return wantarray ? (0, $self->loc("Couldn't parse Base URI: [_1]", $args{Base})) : 0;
231 my $target = RT::URI->new($self->CurrentUser);
232 $target->FromURI( $args{'Target'} )
233 or return wantarray ? (0, $self->loc("Couldn't parse Target URI: [_1]", $args{Target})) : 0;
235 my ( $id, $msg ) = $self->LoadByCols( Base => $base->URI,
236 Type => $args{'Type'},
237 Target => $target->URI );
240 return wantarray ? ( 0, $self->loc("Couldn't load link: [_1]", $msg) ) : 0;
242 return wantarray ? ($id, $msg) : $id;
249 Load an RT::Link object from the database. Takes one parameter, the id of an entry in the links table.
256 my $identifier = shift;
261 if ( $identifier !~ /^\d+$/ ) {
262 return wantarray ? ( 0, $self->loc("That's not a numerical id") ) : 0;
265 my ( $id, $msg ) = $self->LoadById($identifier);
266 unless ( $self->Id ) {
267 return wantarray ? ( 0, $self->loc("Couldn't load link") ) : 0;
269 return wantarray ? ( $id, $msg ) : $id;
278 returns an RT::URI object for the "Target" of this link.
284 my $URI = RT::URI->new($self->CurrentUser);
285 $URI->FromURI($self->Target);
296 return $self->TargetURI->Object;
302 returns an RT::URI object for the "Base" of this link.
308 my $URI = RT::URI->new($self->CurrentUser);
309 $URI->FromURI($self->Base);
320 return $self->BaseURI->Object;
325 Returns the current value of id.
326 (In the database, id is stored as int(11).)
334 Returns the current value of Base.
335 (In the database, Base is stored as varchar(240).)
343 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
344 (In the database, Base will be stored as a varchar(240).)
352 Returns the current value of Target.
353 (In the database, Target is stored as varchar(240).)
357 =head2 SetTarget VALUE
361 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
362 (In the database, Target will be stored as a varchar(240).)
370 Returns the current value of Type.
371 (In the database, Type is stored as varchar(20).)
379 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
380 (In the database, Type will be stored as a varchar(20).)
388 Returns the current value of LocalTarget.
389 (In the database, LocalTarget is stored as int(11).)
393 =head2 SetLocalTarget VALUE
396 Set LocalTarget to VALUE.
397 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
398 (In the database, LocalTarget will be stored as a int(11).)
406 Returns the current value of LocalBase.
407 (In the database, LocalBase is stored as int(11).)
411 =head2 SetLocalBase VALUE
414 Set LocalBase to VALUE.
415 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
416 (In the database, LocalBase will be stored as a int(11).)
424 Returns the current value of LastUpdatedBy.
425 (In the database, LastUpdatedBy is stored as int(11).)
433 Returns the current value of LastUpdated.
434 (In the database, LastUpdated is stored as datetime.)
442 Returns the current value of Creator.
443 (In the database, Creator is stored as int(11).)
451 Returns the current value of Created.
452 (In the database, Created is stored as datetime.)
459 sub _CoreAccessible {
463 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
465 {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''},
467 {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''},
469 {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''},
471 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
473 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
475 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
477 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
479 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
481 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
486 sub FindDependencies {
488 my ($walker, $deps) = @_;
490 $self->SUPER::FindDependencies($walker, $deps);
492 $deps->Add( out => $self->BaseObj ) if $self->BaseObj and $self->BaseObj->id;
493 $deps->Add( out => $self->TargetObj ) if $self->TargetObj and $self->TargetObj->id;
500 Dependencies => undef,
503 my $deps = $args{'Dependencies'};
506 # AddLink transactions
507 my $map = { %RT::Link::TYPEMAP };
508 my $link_meta = $map->{ $self->Type };
509 unless ( $link_meta && $link_meta->{'Mode'} && $link_meta->{'Type'} ) {
510 RT::Shredder::Exception->throw( 'Wrong link link_meta, no record for '. $self->Type );
512 if ( $self->BaseURI->IsLocal ) {
513 my $objs = $self->BaseObj->Transactions;
519 $objs->Limit( FIELD => 'NewValue', VALUE => $self->Target );
520 while ( my ($k, $v) = each %$map ) {
521 next unless $v->{'Type'} eq $link_meta->{'Type'};
522 next unless $v->{'Mode'} eq $link_meta->{'Mode'};
523 $objs->Limit( FIELD => 'Field', VALUE => $k );
525 push( @$list, $objs );
528 my %reverse = ( Base => 'Target', Target => 'Base' );
529 if ( $self->TargetURI->IsLocal ) {
530 my $objs = $self->TargetObj->Transactions;
536 $objs->Limit( FIELD => 'NewValue', VALUE => $self->Base );
537 while ( my ($k, $v) = each %$map ) {
538 next unless $v->{'Type'} eq $link_meta->{'Type'};
539 next unless $v->{'Mode'} eq $reverse{ $link_meta->{'Mode'} };
540 $objs->Limit( FIELD => 'Field', VALUE => $k );
542 push( @$list, $objs );
545 $deps->_PushDependencies(
547 Flags => RT::Shredder::Constants::DEPENDS_ON|RT::Shredder::Constants::WIPE_AFTER,
548 TargetObjects => $list,
549 Shredder => $args{'Shredder'}
551 return $self->SUPER::__DependsOn( %args );
557 my %store = $self->SUPER::Serialize(@_);
559 delete $store{LocalBase} if $store{Base};
560 delete $store{LocalTarget} if $store{Target};
567 my ($importer, $uid, $data) = @_;
569 for my $dir (qw/Base Target/) {
570 my $uid_ref = $data->{$dir};
571 next unless $uid_ref and ref $uid_ref;
573 my $to_uid = ${ $uid_ref };
574 my $obj = $importer->LookupObj( $to_uid );
576 $data->{$dir} = $obj->URI;
577 $data->{"Local$dir"} = $obj->Id if $obj->isa("RT::Ticket");
584 column => ($to_uid =~ /RT::Ticket/ ? "Local$dir" : undef),
590 return $class->SUPER::PreInflate( $importer, $uid, $data );
593 RT::Base->_ImportOverlays();