X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FLink.pm;h=27e9f0efd28855edf0595fb02d5f4876855e28c8;hp=bd8ad615eed522c28e94cf43039984efaff9b808;hb=187086c479a09629b7d180eec513fb7657f4e291;hpb=919e930aa9279b3c5cd12b593889cd6de79d67bf diff --git a/rt/lib/RT/Link.pm b/rt/lib/RT/Link.pm index bd8ad615e..27e9f0efd 100644 --- a/rt/lib/RT/Link.pm +++ b/rt/lib/RT/Link.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2018 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -59,10 +59,6 @@ This module should never be called directly by client code. it's an internal module which should only be accessed through exported APIs in Ticket other similar objects. -=head1 METHODS - - - =cut @@ -78,8 +74,67 @@ use base 'RT::Record'; sub Table {'Links'} use Carp; use RT::URI; +use List::Util 'first'; +use List::MoreUtils 'uniq'; + +# Helper tables for links mapping to make it easier +# to build and parse links between objects. +our %TYPEMAP = ( + MemberOf => { Type => 'MemberOf', Mode => 'Target', Display => 0 }, + Parents => { Type => 'MemberOf', Mode => 'Target', Display => 1 }, + Parent => { Type => 'MemberOf', Mode => 'Target', Display => 0 }, + Members => { Type => 'MemberOf', Mode => 'Base', Display => 0 }, + Member => { Type => 'MemberOf', Mode => 'Base', Display => 0 }, + Children => { Type => 'MemberOf', Mode => 'Base', Display => 1 }, + Child => { Type => 'MemberOf', Mode => 'Base', Display => 0 }, + HasMember => { Type => 'MemberOf', Mode => 'Base', Display => 0 }, + RefersTo => { Type => 'RefersTo', Mode => 'Target', Display => 1 }, + ReferredToBy => { Type => 'RefersTo', Mode => 'Base', Display => 1 }, + DependsOn => { Type => 'DependsOn', Mode => 'Target', Display => 1 }, + DependedOnBy => { Type => 'DependsOn', Mode => 'Base', Display => 1 }, + MergedInto => { Type => 'MergedInto', Mode => 'Target', Display => 1 }, +); +our %DIRMAP = ( + MemberOf => { Base => 'MemberOf', Target => 'HasMember' }, + RefersTo => { Base => 'RefersTo', Target => 'ReferredToBy' }, + DependsOn => { Base => 'DependsOn', Target => 'DependedOnBy' }, + MergedInto => { Base => 'MergedInto', Target => 'MergedInto' }, +); + +__PACKAGE__->_BuildDisplayAs; + +my %DISPLAY_AS; +sub _BuildDisplayAs { + %DISPLAY_AS = (); + foreach my $in_db ( uniq map { $_->{Type} } values %TYPEMAP ) { + foreach my $mode (qw(Base Target)) { + $DISPLAY_AS{$in_db}{$mode} = first { + $TYPEMAP{$_}{Display} + && $TYPEMAP{$_}{Type} eq $in_db + && $TYPEMAP{$_}{Mode} eq $mode + } keys %TYPEMAP; + } + } +} + +=head1 CLASS METHODS + +=head2 DisplayTypes + +Returns a list of the standard link Types for display, including directional +variants but not aliases. + +=cut +sub DisplayTypes { + sort { $a cmp $b } + uniq + grep { defined } + map { values %$_ } + values %DISPLAY_AS +} +=head1 METHODS =head2 Create PARAMHASH @@ -171,20 +226,20 @@ sub LoadByParams { my $base = RT::URI->new($self->CurrentUser); $base->FromURI( $args{'Base'} ) - or return (0, $self->loc("Couldn't parse Base URI: [_1]", $args{Base})); + or return wantarray ? (0, $self->loc("Couldn't parse Base URI: [_1]", $args{Base})) : 0; my $target = RT::URI->new($self->CurrentUser); $target->FromURI( $args{'Target'} ) - or return (0, $self->loc("Couldn't parse Target URI: [_1]", $args{Target})); + or return wantarray ? (0, $self->loc("Couldn't parse Target URI: [_1]", $args{Target})) : 0; my ( $id, $msg ) = $self->LoadByCols( Base => $base->URI, Type => $args{'Type'}, Target => $target->URI ); unless ($id) { - return ( 0, $self->loc("Couldn't load link: [_1]", $msg) ); + return wantarray ? ( 0, $self->loc("Couldn't load link: [_1]", $msg) ) : 0; } else { - return ($id, $msg); + return wantarray ? ($id, $msg) : $id; } } @@ -204,14 +259,14 @@ sub Load { if ( $identifier !~ /^\d+$/ ) { - return ( 0, $self->loc("That's not a numerical id") ); + return wantarray ? ( 0, $self->loc("That's not a numerical id") ) : 0; } else { my ( $id, $msg ) = $self->LoadById($identifier); unless ( $self->Id ) { - return ( 0, $self->loc("Couldn't load link") ); + return wantarray ? ( 0, $self->loc("Couldn't load link") ) : 0; } - return ( $id, $msg ); + return wantarray ? ( $id, $msg ) : $id; } } @@ -265,7 +320,6 @@ sub BaseObj { return $self->BaseURI->Object; } - =head2 id Returns the current value of id. @@ -406,29 +460,157 @@ sub _CoreAccessible { { id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Base => - {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''}, Target => - {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''}, Type => - {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''}, LocalTarget => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LocalBase => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdatedBy => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Creator => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; +sub FindDependencies { + my $self = shift; + my ($walker, $deps) = @_; + + $self->SUPER::FindDependencies($walker, $deps); + + $deps->Add( out => $self->BaseObj ) if $self->BaseObj and $self->BaseObj->id; + $deps->Add( out => $self->TargetObj ) if $self->TargetObj and $self->TargetObj->id; +} + +sub __DependsOn { + my $self = shift; + my %args = ( + Shredder => undef, + Dependencies => undef, + @_, + ); + my $deps = $args{'Dependencies'}; + my $list = []; + +# AddLink transactions + my $map = { %RT::Link::TYPEMAP }; + my $link_meta = $map->{ $self->Type }; + unless ( $link_meta && $link_meta->{'Mode'} && $link_meta->{'Type'} ) { + RT::Shredder::Exception->throw( 'Wrong link link_meta, no record for '. $self->Type ); + } + if ( $self->BaseURI->IsLocal ) { + my $objs = $self->BaseObj->Transactions; + $objs->Limit( + FIELD => 'Type', + OPERATOR => '=', + VALUE => 'AddLink', + ); + $objs->Limit( FIELD => 'NewValue', VALUE => $self->Target ); + while ( my ($k, $v) = each %$map ) { + next unless $v->{'Type'} eq $link_meta->{'Type'}; + next unless $v->{'Mode'} eq $link_meta->{'Mode'}; + $objs->Limit( FIELD => 'Field', VALUE => $k ); + } + push( @$list, $objs ); + } + + my %reverse = ( Base => 'Target', Target => 'Base' ); + if ( $self->TargetURI->IsLocal ) { + my $objs = $self->TargetObj->Transactions; + $objs->Limit( + FIELD => 'Type', + OPERATOR => '=', + VALUE => 'AddLink', + ); + $objs->Limit( FIELD => 'NewValue', VALUE => $self->Base ); + while ( my ($k, $v) = each %$map ) { + next unless $v->{'Type'} eq $link_meta->{'Type'}; + next unless $v->{'Mode'} eq $reverse{ $link_meta->{'Mode'} }; + $objs->Limit( FIELD => 'Field', VALUE => $k ); + } + push( @$list, $objs ); + } + + $deps->_PushDependencies( + BaseObject => $self, + Flags => RT::Shredder::Constants::DEPENDS_ON|RT::Shredder::Constants::WIPE_AFTER, + TargetObjects => $list, + Shredder => $args{'Shredder'} + ); + return $self->SUPER::__DependsOn( %args ); +} + +sub Serialize { + my $self = shift; + my %args = (@_); + my %store = $self->SUPER::Serialize(@_); + + delete $store{LocalBase} if $store{Base}; + delete $store{LocalTarget} if $store{Target}; + + for my $dir (qw/Base Target/) { + my $uri = $self->${\($dir.'URI')}; + my $object = $self->${\($dir.'Obj')}; + + if ($uri->IsLocal) { + if ($args{serializer}->Observe(object => $object)) { + # no action needed; the object is being migrated + } + elsif ($args{serializer}{HyperlinkUnmigrated}) { + # object is not being migrated; hyperlinkify + $store{$dir} = $uri->AsHREF; + } + else { + # object is not being migrated and hyperlinks not desired, + # so drop this RT::Link altogether + return; + } + } + } + + return %store; +} + + +sub PreInflate { + my $class = shift; + my ($importer, $uid, $data) = @_; + + for my $dir (qw/Base Target/) { + my $uid_ref = $data->{$dir}; + next unless $uid_ref and ref $uid_ref; + + my $to_uid = ${ $uid_ref }; + my $obj = $importer->LookupObj( $to_uid ); + if ($obj) { + $data->{$dir} = $obj->URI; + $data->{"Local$dir"} = $obj->Id if $obj->isa("RT::Ticket"); + } else { + $data->{$dir} = ""; + $importer->Postpone( + for => $to_uid, + uid => $uid, + uri => $dir, + column => ($to_uid =~ /RT::Ticket/ ? "Local$dir" : undef), + ); + } + + } + + return $class->SUPER::PreInflate( $importer, $uid, $data ); +} + RT::Base->_ImportOverlays(); 1;