File Coverage

blib/lib/RT/Extension/Converter/RT3.pm
Criterion Covered Total %
statement 24 383 6.2
branch 0 180 0.0
condition 0 29 0.0
subroutine 8 22 36.3
pod 8 8 100.0
total 40 622 6.4


line stmt bran cond sub pod time code
1             package RT::Extension::Converter::RT3;
2              
3 1     1   1702 use warnings;
  1         2  
  1         26  
4 1     1   4 use strict;
  1         2  
  1         26  
5 1     1   4 use base qw(Class::Accessor::Fast);
  1         2  
  1         87  
6             __PACKAGE__->mk_accessors(qw(config _merge_list));
7              
8 1     1   709 use RT::Extension::Converter::RT3::Config;
  1         3  
  1         9  
9 1     1   1121 use Encode;
  1         18145  
  1         126  
10 1     1   783 use Date::Format;
  1         7603  
  1         84  
11 1     1   1426 use MIME::Parser;
  1         136967  
  1         42  
12 1     1   11 use Carp;
  1         2  
  1         8009  
13              
14             =head1 NAME
15              
16             RT::Extension::Converter::RT3 - Handle the RT3 side of a conversion
17              
18              
19             =head1 SYNOPSIS
20              
21             use RT::Extension::Converter::RT3;
22             my $converter = RT::Extension::Converter::RT3->new;
23              
24             =head1 DESCRIPTION
25              
26             Object that should be used by converter scripts to
27              
28             =head1 METHODS
29              
30             =head2 new
31              
32             Returns a converter object after setting up things such as the config
33              
34             =cut
35              
36             sub new {
37 0     0 1   my $class = shift;
38              
39 0           my $self = $class->SUPER::new(@_);
40 0           $self->config(RT::Extension::Converter::RT3::Config->new);
41 0           return $self;
42             }
43              
44             =head2 config
45              
46             Returns a config object
47              
48             =head2 create_user
49              
50             Creates a new user, expects a hash of valid values for RT3's
51             User::Create method plus one special SuperUser argument
52             that will cause SuperUser rights to be granted after creation
53              
54             returns an RT::User object, or undef on failure
55              
56             =cut
57              
58             sub create_user {
59 0     0 1   my $self = shift;
60 0           my %args = ( Privileged => 1, @_ );
61              
62             # this is very RT1'y, because we kept super user rights
63             # in the users table
64 0           my $is_superuser = delete $args{SuperUser};
65 0 0         if ($args{Name} eq 'root') {
66 0           $is_superuser = 1;
67             }
68              
69 0           my $user = RT::User->new($RT::SystemUser);
70              
71 0           %args = %{$self->_encode_data(\%args)};
  0            
72 0           $user->Load( $args{Name} );
73              
74 0 0         if ($user->Id) {
75 0 0         print "\nLoaded ".$user->Name." from the database" if $self->config->debug;
76 0           return $user;
77             }
78            
79 0           local $RT::MinimumPasswordLength = 1; # some people from RT1 have short passwords
80 0           my ($val, $msg) = $user->Create( %args );
81              
82 0 0         if ($val) {
83 0 0         print "\nAdded user ".$user->Name if $self->config->debug;
84 0 0         if ($is_superuser) {
85 0           $user->PrincipalObj->GrantRight( Right => 'SuperUser', Object => $RT::System );
86 0 0         print " as superuser" if $self->config->debug;
87             }
88 0           return $user;
89             } else {
90 0           print "\nfailed to create user $args{Name}: $msg";
91 0           return;
92             }
93              
94             }
95              
96             =head2 create_queue
97              
98             Creates a new queue, expects a hash of valid values for RT3's
99             Queue::Create method
100              
101             returns an RT::Queue object, or undef on failure
102              
103             =cut
104              
105             sub create_queue {
106 0     0 1   my $self = shift;
107 0           my %args = @_;
108              
109             # RT3 really doesn't like undef arguments
110 0           %args = map { $_ => $args{$_} } grep { defined $args{$_} } keys %args;
  0            
  0            
111              
112 0           my $queue = RT::Queue->new($RT::SystemUser);
113              
114 0           %args = %{$self->_encode_data(\%args)};
  0            
115             # Try to load up the current queue by name. avoids duplication.
116 0           $queue->Load($args{Name});
117            
118             #if the queue isn't there, create one.
119 0 0         if ($queue->id) {
120 0 0         print "\nLoaded queue ".$queue->Name." from the database" if $self->config->debug;
121 0           return $queue;
122             }
123              
124 0           my ($val, $msg) = $queue->Create(%args);
125              
126 0 0         if ($val) {
127 0 0         print "\nAdded queue ".$queue->Name if $self->config->debug;
128 0           return $queue;
129             } else {
130 0           print "\nfailed to create queue [$args{Name}]: $msg";
131 0           return;
132             }
133              
134             }
135              
136             =head3 create_queue_area
137              
138             Takes
139             Queue => RT::Queue, Area => Area's name
140              
141             Returns an error message if making the appropriate custom fields fails.
142             Otherwise returns the empty string
143              
144             This is rather RT1 specific. RT2 has a more hierarchical Keyword
145             option that translates into CFs. Areas are the RT1 "custom field"
146             but there was only one of them, so we just make an RT3 Custom Field
147             called Area and whack a simple select list into it
148              
149             =cut
150              
151             sub create_queue_area {
152 0     0 1   my $self = shift;
153 0           my %args = @_;
154 0           my $queue = delete $args{Queue};
155              
156 0           %args = %{$self->_encode_data(\%args)};
  0            
157              
158 0           my $cf = $self->_create_queue_area_cf($queue);
159              
160 0 0         if ($self->config->debug) {
161 0           print "\nAdding $args{Area} to the area for ".$queue->Name;
162             }
163              
164 0           my ($val,$msg) = $cf->AddValue( Name => $args{Area} );
165 0 0         return $val ? '' : $msg ;
166             }
167              
168             =head3 _create_queue_area_cf
169              
170             Wraps up the nasty logic of loading/creating a CF for the area
171              
172             =cut
173              
174             sub _create_queue_area_cf {
175 0     0     my $self = shift;
176 0           my $queue = shift;
177              
178             # load up the custom field
179 0           my $cf = RT::CustomField->new($RT::SystemUser);
180 0           $cf->LoadByName(
181             Name => 'Area',
182             Queue => $queue->Id
183             );
184              
185             # look for an existing cf not assigned to this queue yet
186 0 0         unless ($cf->Id) {
187 0           $cf->LoadByName( Name => 'Area' );
188 0 0         if ($cf->Id) {
189 0           $cf->AddToObject( $queue );
190             }
191             }
192              
193 0 0         unless ($cf->Id) {
194 0           $cf->Create(
195             Name => 'Area',
196             Type => 'SelectSingle',
197             Queue => $queue->Id
198             );
199             }
200 0 0         unless ( $cf->Id ) {
201 0           print "\nCouldn't create custom field Area for queue" . $queue->Name;
202             }
203              
204 0           return $cf;
205              
206             }
207              
208             =head2 create_queue_acl
209              
210             Takes
211             Queue => RT::Queue
212             Acl => acl data from RT1
213              
214             Sets a number of new rights based on the old display/manipulate/admin
215             categories. This should probably be reworked manually to use groups
216             once RT3 is being tested. But, if you have a lot of users, this will
217             at least get you converted.
218              
219             XXX Possibly create 3 groups, granting rights on the queues and
220             adding users to the groups, rather than doing individual rights
221              
222             =cut
223              
224             sub create_queue_acl {
225 0     0 1   my $self = shift;
226 0           my %args = @_;
227              
228 0           my $queue = $args{Queue};
229 0           my $acl = $args{Acl};
230 0           my $username = delete $acl->{user_id};
231              
232              
233 0           my %rightlist = (
234             display => [qw(SeeQueue ShowTemplate ShowScrips
235             ShowTicket ShowTicketComments)],
236             manipulate => [qw(CreateTicket ReplyToTicket CommentOnTicket
237             OwnTicket ModifyTicket DeleteTicket)],
238             admin => [qw(ModifyACL ModifyQueueWatchers AdminCustomField
239             ModifyTemplate ModifyScrips)]
240             );
241              
242 0 0         my @rights = map { @{$rightlist{$_}||[]} } keys %$acl;
  0            
  0            
243              
244 0 0         return unless @rights;
245            
246 0           my $user = RT::User->new($RT::SystemUser);
247 0           $user->Load($username);
248            
249 0 0         unless ($user->id) {
250 0           return "\nCouldn't find user $username Not granting rights\n";
251             }
252              
253 0           my $principal = $user->PrincipalObj;
254            
255 0 0         print "\nAdding rights for $username to ".$queue->Name if $self->config->debug;
256 0           foreach my $right (@rights) {
257 0 0         print "...$right" if $self->config->debug;
258 0           my ($val,$msg) = $principal->GrantRight( Right => $right,
259             Object => $queue);
260 0 0         unless ($val) {
261 0           return "\nFailed to grant $right to $username: $msg\n";
262             }
263             }
264            
265 0 0         print "...adding as AdminCc." if $self->config->debug;
266 0           my ($val,$msg) = $queue->AddWatcher( Type => 'AdminCC',
267             PrincipalId => $principal->Id );
268 0 0         unless ($val) {
269 0           return "\nFailed to make $username an AdminCc: $msg\n";
270             }
271              
272 0           return;
273             }
274              
275             =head3 create_ticket
276              
277             Takes arguments similar to RT3::Ticket's Create.
278             Will take a Requestors argument and try to chop it up into
279             individual Requestor values.
280              
281             =cut
282              
283             sub create_ticket {
284 0     0 1   my $self = shift;
285 0           my %args = @_;
286              
287             # track what merges need to be done later, after all
288             # the tickets are created (Rather than playing games
289             # to see if the ticket we're merging into has been
290             # created yet)
291 0 0 0       if ($args{EffectiveId} && $args{EffectiveId} != $args{id}) {
292 0           print "merging into $args{EffectiveId}";
293 0           $self->_merges( ticket => $args{id},
294             into => $args{EffectiveId} );
295 0           $args{Status} = 'resolved';
296             }
297              
298 0 0         if ($args{Status} eq 'dead') {
299 0           $args{Status} = 'resolved';
300             }
301              
302 0           my @requestors = split(',',$args{Requestors});
303            
304             # if they had an old queue, stuff the new one into general
305 0           my $queue = new RT::Queue($RT::SystemUser);
306 0           $queue->Load($args{Queue});
307 0 0         unless ($queue->id) {
308 0           print "...can't find queue id for $args{id} queue $args{Queue} - using default";
309 0           $queue->Load($self->config->default_queue);
310             }
311 0           $args{Queue} = $queue;
312            
313             # RT1 stored dates in "Seconds from the epoch" so we
314             # need to convert that to ISO so RT3 can grok it
315 0           foreach my $type (qw(Due Told Created Updated)) {
316 0 0 0       if (defined $args{$type} && $args{$type} =~ /^\d+$/) {
317 0           my $date = new RT::Date($RT::SystemUser);
318 0           $date->Set( Format => 'unix', Value => $args{$type} );
319 0           $args{$type} = $date->ISO;
320             }
321             }
322              
323 0 0 0       if ($args{Area} && (my $area = delete $args{Area})) {
324 0 0         print "setting Area $area" if $self->config->debug;
325 0           my $cf_obj = $queue->CustomField('Area');
326 0           $args{'CustomField-'.$cf_obj->Id} = $area;;
327             }
328              
329 0           my $ticket = new RT::Ticket($RT::SystemUser);
330 0           my ($val, $msg) = $ticket->Import(Requestor => \@requestors, %args);
331 0 0         die $msg unless $val;
332              
333 0 0         if ($args{Told}) {
334             # Create/Import doesn't bubble Told up properly in some RT3.6.3 and earlier
335 0           $ticket->__Set( Field => 'Told', Value => $args{Told} );
336             }
337              
338 0           return $ticket;
339             }
340              
341             =head3 create_transactions
342              
343             takes Path => /path/to/transaction/file, Ticket => RT::Ticket,
344             Transactions => [arrayref of transaction data]
345              
346             =cut
347              
348             sub create_transactions {
349 0     0 1   my $self = shift;
350 0           my %args = @_;
351 0           my $ticket = $args{Ticket};
352 0           my $path = $args{Path};
353              
354 0           my $Status = "open";
355 0           my $Queue = "(unknown)";
356 0           my $Area = '';
357 0           my $Subject = '';
358 0           my $Owner = $RT::Nobody->Id;
359 0           my $Requestor = $RT::Nobody->Id;
360 0           my $Priority = $ticket->InitialPriority();
361 0           my $FinalPriority = $ticket->Priority();
362              
363 0           foreach my $txn (@{$args{Transactions}}) {
  0            
364 0           my (%trans_args, $MIMEObj);
365            
366 0           print "t";
367            
368 0           my $load_content = 0;
369 0           $trans_args{'Type'} = '';
370 0           $trans_args{'Field'} = '';
371            
372 0 0 0       if ( ( $txn->{type} eq 'create' ) or ($txn->{type} eq 'import') ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
373 0           $load_content = 1;
374 0           $trans_args{'Type'} = "Create";
375             }
376             elsif ( $txn->{type} eq 'status' ) {
377 0           $trans_args{'Type'} = "Status";
378 0           $trans_args{'Field'} = "Status";
379 0           $trans_args{'OldValue'} = $Status;
380 0           $trans_args{'NewValue'} = $txn->{trans_data};
381 0           $Status = $txn->{trans_data};
382             }
383             elsif ( $txn->{type} eq 'correspond' ) {
384 0           $load_content = 1;
385 0           $trans_args{'Type'} = "Correspond";
386             }
387             elsif ( $txn->{type} eq 'comments' ) {
388 0           $load_content = 1;
389 0           $trans_args{'Type'} = "Comment";
390             }
391             elsif ( $txn->{type} eq 'queue_id' ) {
392 0           $trans_args{'Type'} = "Set";
393 0           $trans_args{'Field'} = "Queue";
394 0           $trans_args{'OldValue'} = $Queue;
395 0           $trans_args{'NewValue'} = $txn->{trans_data};
396 0           $Queue = $txn->{trans_data};
397             }
398             elsif ( $txn->{type} eq 'owner' ) {
399              
400 0           $trans_args{'Type'} = "Owner";
401 0           $trans_args{'Field'} ="Owner";
402 0           $trans_args{'OldValue'} = $Owner;
403 0   0       $txn->{trans_data} ||= 'Nobody';
404            
405 0           my $new_user = RT::User->new($RT::SystemUser);
406 0           $new_user->Load($txn->{'trans_data'});
407 0           $trans_args{'NewValue'} = $new_user->Id;
408            
409 0           my $actor = new RT::User($RT::SystemUser);
410 0 0         $txn->{actor} = 'RT_System' if ($txn->{actor} eq '_rt_system');
411 0           $actor->Load($txn->{actor});
412            
413             #take/give
414            
415 0 0         $Owner = $RT::Nobody->Id unless ($Owner);
416              
417 0 0 0       if ($Owner == $RT::Nobody->Id &&
    0 0        
    0          
418             $txn->{trans_data} eq $txn->{actor} ) {
419 0           $trans_args{'Type'} = 'Take';
420             } elsif ( $Owner == $actor->Id &&
421             $new_user->Id == $RT::Nobody->Id ) {
422 0           $trans_args{'Type'} = 'Untake';
423             } elsif ( $Owner != $RT::Nobody->Id) {
424 0           $trans_args{'Type'} = 'Steal';
425             } else {
426 0           $trans_args{'Type'} = 'Give';
427             }
428            
429 0           $Owner = $new_user->Id;
430            
431             }
432             elsif ( $txn->{type} eq 'effective_sn' ) {
433 0           $trans_args{'Type'} = "AddLink";
434 0           $trans_args{'Field'} ="MemberOf";
435 0           $trans_args{'Data'} = "Ticket ". $ticket->Id.
436             " MergedInto ticket ". $txn->{trans_data};
437            
438             }
439             elsif ( $txn->{type} eq 'area' ) {
440 0           $trans_args{'Type'} = "CustomField";
441 0           $trans_args{'OldValue'} = $Area;
442 0           $trans_args{'NewValue'} = $txn->{trans_data};
443 0           $Area = $txn->{trans_data};
444             }
445             elsif ( $txn->{type} eq 'requestors' ) {
446             # RT1 removed requestors by recording a transaction with
447             # '' for trans_data. For RT3 we need to say "DelWatcher"
448             # AND tell RT which requestor we're nuking.
449 0           $trans_args{'Field'} ="Requestor";
450              
451 0 0         if ($txn->{trans_data}) {
452 0           $trans_args{'Type'} = "AddWatcher";
453             # earlier RTs stored email addresses in the Transaction
454             # RT3 calls Load on that address and goes splody
455             # since Load only works on id/username
456 0           my $user = $self->_load_or_create_user(EmailAddress => $txn->{trans_data});
457 0           $trans_args{NewValue} = $user->Id;
458 0           $Requestor = $user->Id;
459             } else {
460 0           $trans_args{Type} = "DelWatcher";
461 0           $trans_args{OldValue} = $Requestor;
462             }
463             }
464             elsif ( $txn->{type} eq 'date_due' ) {
465 0           $trans_args{'Type'} = "Set";
466 0           $trans_args{'Field'} ="Due";
467 0           my $date = new RT::Date($RT::SystemUser);
468 0           $date->Set( Format=>'unix', Value=>$txn->{trans_data} );
469 0           $trans_args{'NewValue'} = $date->ISO();
470             }
471             elsif ( $txn->{type} eq 'subject' ) {
472 0           $trans_args{'Type'} = "Set";
473 0           $trans_args{'Field'} ="Subject";
474 0           $trans_args{'OldValue'} = $Subject;
475 0           $trans_args{'NewValue'} = $txn->{trans_data};
476 0           $Subject = $txn->{trans_data};
477            
478             }
479             elsif ( $txn->{type} eq 'priority' ) {
480 0           $trans_args{'Type'} = "Set";
481 0           $trans_args{'Field'} ="Priority";
482 0           $trans_args{'OldValue'} = $Priority;
483 0           $trans_args{'NewValue'} = $txn->{'trans_data'};
484 0           $Priority = $txn->{'trans_data'};
485            
486             }
487             elsif ( $txn->{type} eq 'final_priority' ) {
488 0           $trans_args{'Type'} = "Set";
489 0           $trans_args{'Field'} ="FinalPriority";
490 0           $trans_args{'OldValue'} = $FinalPriority;
491 0           $trans_args{'NewValue'} = $txn->{'trans_data'};
492 0           $FinalPriority = $txn->{'trans_data'};
493            
494             }
495             elsif ( $txn->{type} eq 'date_told' ) {
496 0           $trans_args{'Type'} = "Set";
497 0           $trans_args{'Field'} = "Told";
498            
499 0           my $date = new RT::Date($RT::SystemUser);
500 0           $date->Set( Format=>'unix', Value=>$txn->{trans_data} );
501 0           $trans_args{'NewValue'} = $date->ISO();
502            
503             } else {
504 0           die "unrecognized transaction type: $txn->{type}";
505             }
506              
507 0           my $filename = $txn->{serial_num}.".".$txn->{id};
508            
509 0 0         if ( $load_content ) {
510 0 0         if (my $trans_file = $self->_find_transaction_file(Path => $args{Path},
511             Date => $txn->{trans_date},
512             Filename => $filename ) ) {
513 0           $MIMEObj = $self->_process_transaction_file(File => $trans_file);
514             }
515             }
516            
517            
518 0 0         if ( $trans_args{'Type'} ) {
519            
520 0           my $User;
521 0 0         if ($txn->{actor}) {
522 0           $User = $self->_load_or_create_user(EmailAddress => $txn->{actor});
523             } else {
524 0           $User = RT::User->new($RT::System);
525 0           $User->Load($RT::Nobody->Id);
526             }
527 0           my $created = new RT::Date($RT::SystemUser);
528 0           $created->Set( Format=>'unix', Value=>$txn->{'trans_date'});
529            
530 0           my $trans = new RT::Transaction($User);
531            
532             # Allow us to set the 'Created' attribute.
533 0           $trans->{'_AccessibleCache'}{Created} = { 'read'=>1, 'write'=>1 };
534 0           $trans->{'_AccessibleCache'}{Creator} = { 'read'=>1, 'auto'=>1 };
535            
536 0           my ($transaction, $msg) =
537             $trans->Create( Ticket => $ticket->Id,
538             Type => $trans_args{'Type'},
539             Data => $trans_args{'Data'},
540             Field => $trans_args{'Field'},
541             NewValue => $trans_args{'NewValue'},
542             OldValue => $trans_args{'OldValue'},
543             MIMEObj => $MIMEObj,
544             Created => $created->ISO,
545             ActivateScrips => 0
546             );
547            
548 0 0         unless ($transaction) {
549 0           die("Couldn't create transaction for $txn->{id} $msg\n")
550             }
551             } else {
552 0           die "Couldn't parse ". $txn->{id};
553             }
554             }
555 0           return $ticket;
556             }
557              
558             =head3 _find_transaction_file
559              
560             RT1 would sometimes get confused about timezones and store
561             a file in tomorrow or yesterday's path. Go find the file.
562              
563             =cut
564              
565             sub _find_transaction_file {
566 0     0     my $self = shift;
567 0           my %args = @_;
568              
569 0           my @files;
570 0           foreach my $date ($args{Date},$args{Date}+43200,$args{Date}-43200) {
571              
572 0           my $file = time2str("$args{Path}/%Y/%b/%e/",$date,'PST');
573 0           $file .= $args{Filename};
574 0           $file =~ s/ //;
575              
576 0 0         print "\nTesting $file" if $self->config->debug;
577 0 0         if (-e $file) {
578 0           return $file
579             } else {
580 0           push @files,$file;
581             }
582             }
583 0           warn "none of @files exist\n";
584 0           return;
585             }
586              
587             =head3 _process_transaction_file
588              
589             We need to turn the RT1 files back into MIME objects
590             This means converting the old Headers Follow line and
591             the broken MIME headers into something MIME::Parser
592             won't choke on.
593              
594             =cut
595              
596             sub _process_transaction_file {
597 0     0     my $self = shift;
598 0           my %args = @_;
599 0           my $trans_file = $args{File};
600              
601 0 0         print "\nprocessing file $trans_file" if $self->config->debug;
602            
603 0 0         open (FILE,"<$trans_file") or die "can't open [$trans_file] $!";
604            
605            
606 0           my(@headers, @body);
607 0           my $headers = 0;
608 0           while () {
609 0 0         if ( /^--- Headers Follow ---$/ ) {
    0          
610 0           $headers = 1;
611 0           next;
612             } elsif ( $headers ) {
613 0 0         next if /^\s*$/;
614 0 0         next if /^>From /;
615 0           push @headers, $_;
616             } else {
617 0           push @body, $_;
618             }
619             }
620            
621             #clean up files with false multipart Content-type
622 0           my @n_headers;
623 0           while ( my $header = shift @headers ) {
624 0 0         if ( $header =~ /^content-type:\s*multipart\/(alternative|mixed|report|signed|digest|related)\s*;/i ) {
625 0           my $two = 0;
626 0           my $boundary;
627 0 0 0       if ( $header =~ /;\s*boundary=\s*"?([\-\w\.\=\/\+\%\#]+)"?/i ) {
    0          
    0          
    0          
    0          
    0          
628 0           $boundary = $1;
629             } elsif (( $header =~ /;\s*boundary=\s*$/i ) and ($headers[0] =~ /\s*"?([\-\w\.\=\/\+\%\#]+)"?/i)) {
630             #special case for actual boundary on next line
631 0           $boundary = $1;
632 0           $two = 1;
633             } elsif ( $headers[0] =~ /(^|;)\s*boundary=\s*"([ \-\w\.\=\/\+\%\#]+)"/i ) { #embedded space, quotes not optional
634 0           $boundary = $2;
635 0           $two = 1;
636             } elsif ( $headers[0] =~ /(^|;)\s*boundary=\s*"?([\-\w\.\=\/\+\%\#]+)"?/i ) {
637 0           $boundary = $2;
638 0           $two = 1;
639             } elsif ( $headers[1] =~ /(^|;)\s*boundary=\s*"?([\-\w\.\=\/\+\%\#]+)"?/i ) {
640 0           $boundary = $2;
641 0           $two = 2;
642             } elsif ( $headers[2] =~ /(^|;)\s*boundary=\s*"?([\-\w\.\=\/\+\%\#]+)"?/i ) {
643             #terrible false laziness.
644 0           $boundary = $2;
645 0           $two = 3;
646             } else {
647 0           warn "can\'t parse $header for boundry";
648             }
649 0 0         print "looking for $boundary in body\n" if $self->config->debug;
650 0 0         unless ( grep /^(\-\-)?\Q$boundary\E(\-\-)?$/, @body ) {
651 0           splice(@headers, 0, $two);
652 0   0       until ( !scalar(@headers) || $headers[0] =~ /^\S/ ) {
653 0           warn "**WARNING throwing away header fragment: ". shift @headers;
654             }
655 0           warn "false Content-type: header removed\n";
656 0           push @n_headers, "Content-Type: text/plain\n";
657 0           push @n_headers, "X-Content-Type-Munged-By: RT import tool\n";
658              
659 0           next; #This is here so we don't push into n_headers
660             }
661             }
662 0           push @n_headers, $header;
663             }
664            
665 0 0         print "..parsing.." if $self->config->debug;
666 0           my $parser = new MIME::Parser;
667 0           $parser->output_to_core(1);
668 0           $parser->extract_nested_messages(0);
669 0           my $MIMEObj = $parser->parse_data( [ @n_headers, "\n", "\n", @body ] );
670 0 0         print "parsed.." if $self->config->debug;
671 0           return $MIMEObj;
672             }
673              
674             =head3 _load_or_create_user
675              
676             Given an EmailAddress, Name (username)
677             will try to load the user by username first and
678             then by EmailAddress. If that fails, a new unprivileged
679             user will be created with Name => Name|EmailAddress
680              
681             Will carp if loading AND creating fail
682             Otherwise returns a valid user object
683              
684             =cut
685              
686             sub _load_or_create_user {
687 0     0     my $self = shift;
688 0           my %args = @_;
689 0   0       $args{Name} ||= $args{EmailAddress};
690              
691 0           my $user_obj = RT::User->new($RT::SystemUser);
692              
693 0           $user_obj->Load( $args{Name} );
694 0 0         unless ($user_obj->Id) {
695 0           $user_obj->LoadByEmail($args{EmailAddress});
696             }
697 0 0         unless ($user_obj->Id) {
698 0           my ($val, $msg) = $user_obj->Create(%args,
699             Password => undef,
700             Privileged => 0,
701             Comments => undef
702             );
703              
704 0 0         unless ($val) {
705 0           die "couldn't create user_obj for %args{Name}: $msg\n";
706             }
707             }
708              
709 0 0         unless ($user_obj->Id) {
710 0           carp "We couldn't find or create $args{Name}. This should never happen"
711             }
712 0           return $user_obj;
713             }
714            
715             =head3 create_links
716              
717             creates all accumulated links.
718             We do this at the end so that all the tickets will exist, rather
719             than doing it during ticket creation and having to work around
720             future tickets not being imported yet.
721              
722             =cut
723              
724             sub create_links {
725 0     0 1   my $self = shift;
726              
727 0           my $merges = $self->_merges;
728            
729 0           foreach my $ticket (keys %$merges) {
730 0           my $into = $merges->{$ticket};
731 0 0         if ($self->config->debug) {
732 0           print "\nMerging $ticket into $into"
733             } else {
734 0           print ".";
735             }
736              
737 0           my $mergeinto = RT::Ticket->new($RT::SystemUser);
738 0           $mergeinto->Load($into);
739              
740 0 0         unless ($mergeinto->Id) {
741 0           print "Skipping $ticket => $into because $into doesn't exist";
742 0           next;
743             }
744              
745             # Store the link in the DB.
746 0           my $link = RT::Link->new($RT::SystemUser);
747 0           my ($linkid) = $link->Create(Target => $into,
748             Base => $ticket,
749             Type => 'MergedInto');
750            
751 0           my $ticket_obj = RT::Ticket->new($RT::SystemUser);
752 0           $ticket_obj->Load($ticket);
753            
754 0 0         if ($ticket_obj->id != $ticket) {
755 0           die "Ticket mismatch ".$ticket_obj->id ." and $ticket\n";
756             }
757 0           my ($val, $msg) = $ticket_obj->__Set( Field => 'EffectiveId', Value => $into );
758            
759 0 0         print " couldn't set EffectiveId: $msg\n" unless ($val);
760             }
761              
762             }
763              
764             =head3 _merge_list
765              
766             private data storage routine to hold what tickets are merged where
767              
768             =head3 _merges
769              
770             takes ticket => id, into => otherid
771             tracks what merges need doing after we're done
772             creating all the tickets.
773              
774             When called without arguments, returns a hashref
775             containing ticketid => ticket to merge into
776              
777             =cut
778              
779             sub _merges {
780 0     0     my $self = shift;
781              
782 0 0         unless (@_) {
783 0           return $self->_merge_list;
784             }
785              
786 0           my %args = @_;
787 0           my $list = $self->_merge_list;
788 0           $list->{$args{ticket}} = $args{into};
789 0           $self->_merge_list($list);
790 0           return;
791             }
792              
793             =head3 _encode_data
794              
795             Used to make sure data gets properly unicode'd for RT3.6.
796             Failure to use this in places will make non-americans unhappy
797              
798             Takes a hashref of arguments, returns an encoded hashref.
799              
800             =cut
801              
802             sub _encode_data {
803 0     0     my $self = shift;
804 0 0         my %args = %{shift||{}};
  0            
805              
806 0           foreach my $key ( keys %args ) {
807 0 0         if ( !ref( $args{$key} ) ) {
    0          
808 0           $args{$key} = decode( $self->config->encoding, $args{$key} );
809             }
810             elsif ( ref( $args{$key} ) eq 'ARRAY' ) {
811 0           my @temp = @{ $args{$key} };
  0            
812 0           undef $args{$key};
813 0           foreach my $var (@temp) {
814 0 0         if ( ref($var) ) {
815              
816 0           push( @{ $args{$key} }, $var );
  0            
817             }
818             else {
819 0           push( @{ $args{$key} }, decode( $self->config->encoding, $var ) );
  0            
820             }
821             }
822             }
823             else {
824 0           die "What do I do with $key for %args. It is a "
825             . ref( { $args{$key} } );
826             }
827             }
828              
829 0           return \%args;
830             }
831              
832             =head1 AUTHOR
833              
834             Kevin Falcone C<< >>
835              
836              
837             =head1 LICENCE AND COPYRIGHT
838              
839             Copyright (c) 2007, Best Practical Solutions, LLC. All rights reserved.
840              
841             This module is free software; you can redistribute it and/or
842             modify it under the same terms as Perl itself. See L.
843              
844              
845             =head1 DISCLAIMER OF WARRANTY
846              
847             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
848             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
849             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
850             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
851             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
852             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
853             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
854             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
855             NECESSARY SERVICING, REPAIR, OR CORRECTION.
856              
857             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
858             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
859             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
860             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
861             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
862             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
863             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
864             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
865             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
866             SUCH DAMAGES.
867              
868             =cut
869              
870             1;