Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / Link.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
403d7b0b 5# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
84fb5b46
MKG
6# <sales@bestpractical.com>
7#
8# (Except where explicitly superseded by other copyright notices)
9#
10#
11# LICENSE:
12#
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
16# from www.gnu.org.
17#
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.
22#
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.
28#
29#
30# CONTRIBUTION SUBMISSION POLICY:
31#
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.)
37#
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.
46#
47# END BPS TAGGED BLOCK }}}
48
49=head1 NAME
50
51 RT::Link - an RT Link object
52
53=head1 SYNOPSIS
54
55 use RT::Link;
56
57=head1 DESCRIPTION
58
59This module should never be called directly by client code. it's an internal module which
60should only be accessed through exported APIs in Ticket other similar objects.
61
84fb5b46
MKG
62=cut
63
64
65package RT::Link;
66
67use strict;
68use warnings;
69
70
71
72use base 'RT::Record';
73
74sub Table {'Links'}
75use Carp;
76use RT::URI;
af59614d
MKG
77use List::Util 'first';
78use List::MoreUtils 'uniq';
79
80# Helper tables for links mapping to make it easier
81# to build and parse links between objects.
82our %TYPEMAP = (
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 },
96);
97our %DIRMAP = (
98 MemberOf => { Base => 'MemberOf', Target => 'HasMember' },
99 RefersTo => { Base => 'RefersTo', Target => 'ReferredToBy' },
100 DependsOn => { Base => 'DependsOn', Target => 'DependedOnBy' },
101 MergedInto => { Base => 'MergedInto', Target => 'MergedInto' },
102);
103
104__PACKAGE__->_BuildDisplayAs;
105
106my %DISPLAY_AS;
107sub _BuildDisplayAs {
108 %DISPLAY_AS = ();
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
115 } keys %TYPEMAP;
116 }
117 }
118}
119
120=head1 CLASS METHODS
84fb5b46 121
af59614d 122=head2 DisplayTypes
84fb5b46 123
af59614d
MKG
124Returns a list of the standard link Types for display, including directional
125variants but not aliases.
126
127=cut
128
129sub DisplayTypes {
130 sort { $a cmp $b }
131 uniq
132 grep { defined }
133 map { values %$_ }
134 values %DISPLAY_AS
135}
136
137=head1 METHODS
84fb5b46
MKG
138
139=head2 Create PARAMHASH
140
141Create a new link object. Takes 'Base', 'Target' and 'Type'.
142Returns undef on failure or a Link Id on success.
143
144=cut
145
146sub Create {
147 my $self = shift;
148 my %args = ( Base => undef,
149 Target => undef,
150 Type => undef,
151 @_ );
152
153 my $base = RT::URI->new( $self->CurrentUser );
c36a7e1d
MKG
154 unless ($base->FromURI( $args{'Base'} )) {
155 my $msg = $self->loc("Couldn't resolve base '[_1]' into a URI.", $args{'Base'});
84fb5b46 156 $RT::Logger->warning( "$self $msg" );
c36a7e1d 157 return wantarray ? (undef, $msg) : undef;
84fb5b46
MKG
158 }
159
160 my $target = RT::URI->new( $self->CurrentUser );
c36a7e1d
MKG
161 unless ($target->FromURI( $args{'Target'} )) {
162 my $msg = $self->loc("Couldn't resolve target '[_1]' into a URI.", $args{'Target'});
84fb5b46 163 $RT::Logger->warning( "$self $msg" );
c36a7e1d 164 return wantarray ? (undef, $msg) : undef;
84fb5b46
MKG
165 }
166
167 my $base_id = 0;
168 my $target_id = 0;
169
170
171
172
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'}));
177
178 }
179 $base_id = $object->Id if UNIVERSAL::isa($object, 'RT::Ticket');
180 }
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'}));
185
186 }
187 $target_id = $object->Id if UNIVERSAL::isa($object, 'RT::Ticket');
188 }
189
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") );
193 }
194
195 # }}}
196
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 );
203}
204
205 # sub LoadByParams
206
207=head2 LoadByParams
208
209 Load an RT::Link object from the database. Takes three parameters
210
211 Base => undef,
212 Target => undef,
213 Type =>undef
214
215 Base and Target are expected to be integers which refer to Tickets or URIs
216 Type is the link type
217
218=cut
219
220sub LoadByParams {
221 my $self = shift;
222 my %args = ( Base => undef,
223 Target => undef,
224 Type => undef,
225 @_ );
226
227 my $base = RT::URI->new($self->CurrentUser);
c36a7e1d 228 $base->FromURI( $args{'Base'} )
af59614d 229 or return wantarray ? (0, $self->loc("Couldn't parse Base URI: [_1]", $args{Base})) : 0;
84fb5b46
MKG
230
231 my $target = RT::URI->new($self->CurrentUser);
c36a7e1d 232 $target->FromURI( $args{'Target'} )
af59614d 233 or return wantarray ? (0, $self->loc("Couldn't parse Target URI: [_1]", $args{Target})) : 0;
84fb5b46
MKG
234
235 my ( $id, $msg ) = $self->LoadByCols( Base => $base->URI,
236 Type => $args{'Type'},
237 Target => $target->URI );
238
239 unless ($id) {
af59614d 240 return wantarray ? ( 0, $self->loc("Couldn't load link: [_1]", $msg) ) : 0;
c36a7e1d 241 } else {
af59614d 242 return wantarray ? ($id, $msg) : $id;
84fb5b46
MKG
243 }
244}
245
246
247=head2 Load
248
249 Load an RT::Link object from the database. Takes one parameter, the id of an entry in the links table.
250
251
252=cut
253
254sub Load {
255 my $self = shift;
256 my $identifier = shift;
257
258
259
260
261 if ( $identifier !~ /^\d+$/ ) {
af59614d 262 return wantarray ? ( 0, $self->loc("That's not a numerical id") ) : 0;
84fb5b46
MKG
263 }
264 else {
265 my ( $id, $msg ) = $self->LoadById($identifier);
266 unless ( $self->Id ) {
af59614d 267 return wantarray ? ( 0, $self->loc("Couldn't load link") ) : 0;
84fb5b46 268 }
af59614d 269 return wantarray ? ( $id, $msg ) : $id;
84fb5b46
MKG
270 }
271}
272
273
274
275
276=head2 TargetURI
277
278returns an RT::URI object for the "Target" of this link.
279
280=cut
281
282sub TargetURI {
283 my $self = shift;
284 my $URI = RT::URI->new($self->CurrentUser);
285 $URI->FromURI($self->Target);
286 return ($URI);
287}
288
289
290=head2 TargetObj
291
292=cut
293
294sub TargetObj {
295 my $self = shift;
296 return $self->TargetURI->Object;
297}
298
299
300=head2 BaseURI
301
302returns an RT::URI object for the "Base" of this link.
303
304=cut
305
306sub BaseURI {
307 my $self = shift;
308 my $URI = RT::URI->new($self->CurrentUser);
309 $URI->FromURI($self->Base);
310 return ($URI);
311}
312
313
314=head2 BaseObj
315
316=cut
317
318sub BaseObj {
319 my $self = shift;
320 return $self->BaseURI->Object;
321}
322
84fb5b46
MKG
323=head2 id
324
325Returns the current value of id.
326(In the database, id is stored as int(11).)
327
328
329=cut
330
331
332=head2 Base
333
334Returns the current value of Base.
335(In the database, Base is stored as varchar(240).)
336
337
338
339=head2 SetBase VALUE
340
341
342Set Base to VALUE.
343Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
344(In the database, Base will be stored as a varchar(240).)
345
346
347=cut
348
349
350=head2 Target
351
352Returns the current value of Target.
353(In the database, Target is stored as varchar(240).)
354
355
356
357=head2 SetTarget VALUE
358
359
360Set Target to VALUE.
361Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
362(In the database, Target will be stored as a varchar(240).)
363
364
365=cut
366
367
368=head2 Type
369
370Returns the current value of Type.
371(In the database, Type is stored as varchar(20).)
372
373
374
375=head2 SetType VALUE
376
377
378Set Type to VALUE.
379Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
380(In the database, Type will be stored as a varchar(20).)
381
382
383=cut
384
385
386=head2 LocalTarget
387
388Returns the current value of LocalTarget.
389(In the database, LocalTarget is stored as int(11).)
390
391
392
393=head2 SetLocalTarget VALUE
394
395
396Set LocalTarget to VALUE.
397Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
398(In the database, LocalTarget will be stored as a int(11).)
399
400
401=cut
402
403
404=head2 LocalBase
405
406Returns the current value of LocalBase.
407(In the database, LocalBase is stored as int(11).)
408
409
410
411=head2 SetLocalBase VALUE
412
413
414Set LocalBase to VALUE.
415Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
416(In the database, LocalBase will be stored as a int(11).)
417
418
419=cut
420
421
422=head2 LastUpdatedBy
423
424Returns the current value of LastUpdatedBy.
425(In the database, LastUpdatedBy is stored as int(11).)
426
427
428=cut
429
430
431=head2 LastUpdated
432
433Returns the current value of LastUpdated.
434(In the database, LastUpdated is stored as datetime.)
435
436
437=cut
438
439
440=head2 Creator
441
442Returns the current value of Creator.
443(In the database, Creator is stored as int(11).)
444
445
446=cut
447
448
449=head2 Created
450
451Returns the current value of Created.
452(In the database, Created is stored as datetime.)
453
454
455=cut
456
457
458
459sub _CoreAccessible {
460 {
461
462 id =>
af59614d 463 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
84fb5b46 464 Base =>
af59614d 465 {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''},
84fb5b46 466 Target =>
af59614d 467 {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''},
84fb5b46 468 Type =>
af59614d 469 {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''},
84fb5b46 470 LocalTarget =>
af59614d 471 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 472 LocalBase =>
af59614d 473 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 474 LastUpdatedBy =>
af59614d 475 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 476 LastUpdated =>
af59614d 477 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
84fb5b46 478 Creator =>
af59614d 479 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 480 Created =>
af59614d 481 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
84fb5b46
MKG
482
483 }
484};
485
af59614d
MKG
486sub FindDependencies {
487 my $self = shift;
488 my ($walker, $deps) = @_;
489
490 $self->SUPER::FindDependencies($walker, $deps);
491
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;
494}
495
496sub Serialize {
497 my $self = shift;
498 my %args = (@_);
499 my %store = $self->SUPER::Serialize(@_);
500
501 delete $store{LocalBase} if $store{Base};
502 delete $store{LocalTarget} if $store{Target};
503 return %store;
504}
505
506
507sub PreInflate {
508 my $class = shift;
509 my ($importer, $uid, $data) = @_;
510
511 for my $dir (qw/Base Target/) {
512 my $uid_ref = $data->{$dir};
513 next unless $uid_ref and ref $uid_ref;
514
515 my $to_uid = ${ $uid_ref };
516 my $obj = $importer->LookupObj( $to_uid );
517 if ($obj) {
518 $data->{$dir} = $obj->URI;
519 $data->{"Local$dir"} = $obj->Id if $obj->isa("RT::Ticket");
520 } else {
521 $data->{$dir} = "";
522 $importer->Postpone(
523 for => $to_uid,
524 uid => $uid,
525 uri => $dir,
526 column => ($to_uid =~ /RT::Ticket/ ? "Local$dir" : undef),
527 );
528 }
529
530 }
531
532 return $class->SUPER::PreInflate( $importer, $uid, $data );
533}
534
84fb5b46
MKG
535RT::Base->_ImportOverlays();
536
5371;