rt 4.2.16
[freeside.git] / rt / lib / RT / Migrate / Incremental.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
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 package RT::Migrate::Incremental;
50
51 use strict;
52 use warnings;
53 require Storable;
54 require MIME::Base64;
55
56 our %UPGRADES = (
57     '3.3.0' => {
58         'RT::Transaction' => sub {
59             my ($ref) = @_;
60             $ref->{ObjectType} = 'RT::Ticket';
61             $ref->{ObjectId} = delete $ref->{Ticket};
62             delete $ref->{EffectiveTicket};
63         },
64         'RT::TicketCustomFieldValue' => sub {
65             my ($ref, $classref) = @_;
66             $$classref = "RT::ObjectCustomFieldValue";
67             $ref->{ObjectType} = 'RT::Ticket';
68             $ref->{ObjectId} = delete $ref->{Ticket};
69         },
70         '-RT::TicketCustomFieldValue' => sub {
71             my ($ref, $classref) = @_;
72             $$classref = "RT::ObjectCustomFieldValue";
73         },
74         'RT::CustomField' => sub {
75             my ($ref) = @_;
76             $ref->{MaxValues} = 0 if $ref->{Type} =~ /Multiple$/;
77             $ref->{MaxValues} = 1 if $ref->{Type} =~ /Single$/;
78             $ref->{Type} = 'Select'   if $ref->{Type} =~ /^Select/;
79             $ref->{Type} = 'Freeform' if $ref->{Type} =~ /^Freeform/;
80             $ref->{LookupType} = 'RT::Queue-RT::Ticket';
81             delete $ref->{Queue};
82         },
83         '+RT::CustomField' => sub {
84             my ($ref) = @_;
85             return [
86                 "RT::ObjectCustomField" => rand(1),
87                 {
88                     id            => undef,
89                     CustomField   => $ref->{id},
90                     ObjectId      => $ref->{Queue},
91                     SortOrder     => $ref->{SortOrder},
92                     Creator       => $ref->{Creator},
93                     LastUpdatedBy => $ref->{LastUpdatedBy},
94                 }
95             ];
96         }
97     },
98
99     '3.3.11' => {
100         'RT::ObjectCustomFieldValue' => sub {
101             my ($ref) = @_;
102             $ref->{Disabled} = not delete $ref->{Current};
103         },
104     },
105
106     '3.7.19' => {
107         'RT::Scrip' => sub {
108             my ($ref) = @_;
109             return if defined $ref->{Description} and length $ref->{Description};
110
111             my $scrip = RT::Scrip->new( $RT::SystemUser );
112             $scrip->Load( $ref->{id} );
113             my $condition = $scrip->ConditionObj->Name
114                 || $scrip->ConditionObj->Description
115                 || ('On Condition #'. $scrip->Condition);
116             my $action = $scrip->ActionObj->Name
117                 || $scrip->ActionObj->Description
118                 || ('Run Action #'. $scrip->Action);
119             $ref->{Description} = join ' ', $condition, $action;
120         },
121     },
122
123     # XXX BrandedQueues
124     # XXX iCal
125
126     '3.8.2' => {
127         'RT::Template' => sub {
128             my ($ref) = @_;
129             return unless $ref->{Queue};
130
131             my $queue = RT::Queue->new( $RT::SystemUser );
132             $queue->Load( $ref->{Queue} );
133             return unless $queue->Id and $queue->Name eq "___Approvals";
134
135             $ref->{Name} = "[OLD] ".$ref->{Name};
136         },
137         'RT::Attribute' => sub {
138             my ($ref) = @_;
139             return unless $ref->{Name} eq "Dashboard";
140
141             my $v = eval {
142                 Storable::thaw(MIME::Base64::decode_base64($ref->{Content}))
143               };
144             return unless $v and exists $v->{Searches};
145             $v->{Panes} = {
146                 body => [
147                     map {
148                         my ($privacy, $id, $desc) = @$_;
149                         +{
150                             portlet_type => 'search',
151                             privacy      => $privacy,
152                             id           => $id,
153                             description  => $desc,
154                             pane         => 'body',
155                         }
156                     } @{ delete $v->{Searches} }
157                 ],
158             };
159             $ref->{Content} = MIME::Base64::encode_base64(
160                 Storable::nfreeze($v) );
161         },
162         'RT::Scrip' => sub {
163             my ($ref, $classref) = @_;
164             return unless $ref->{Queue};
165
166             my $queue = RT::Queue->new( $RT::SystemUser );
167             $queue->Load( $ref->{Queue} );
168             return unless $queue->Id and $queue->Name eq "___Approvals";
169
170             $$classref = undef;
171         },
172     },
173
174     '3.8.3' => {
175         'RT::ScripAction' => sub {
176             my ($ref) = @_;
177             return unless ($ref->{Argument}||"") eq "All";
178             if ($ref->{ExecModule} eq "Notify") {
179                 $ref->{Name} = 'Notify Owner, Requestors, Ccs and AdminCcs';
180                 $ref->{Description} = 'Send mail to owner and all watchers';
181             } elsif ($ref->{ExecModule} eq "NotifyAsComment") {
182                 $ref->{Name} = 'Notify Owner, Requestors, Ccs and AdminCcs as Comment';
183                 $ref->{Description} = 'Send mail to owner and all watchers as a "comment"';
184             }
185         },
186     },
187
188     '3.8.4' => {
189         'RT::ScripAction' => sub {
190             my ($ref) = @_;
191             return unless $ref->{ExecModule} eq "NotifyGroup"
192                 or $ref->{ExecModule} eq "NotifyGroupAsComment";
193
194             my $argument = $ref->{Argument};
195             if ( my $struct = eval { Storable::thaw( $argument ) } ) {
196                 my @res;
197                 foreach my $r ( @{ $struct } ) {
198                     my $obj;
199                     next unless $r->{'Type'};
200                     if( lc $r->{'Type'} eq 'user' ) {
201                         $obj = RT::User->new( $RT::SystemUser );
202                     } elsif ( lc $r->{'Type'} eq 'group' ) {
203                         $obj = RT::Group->new( $RT::SystemUser );
204                     } else {
205                         next;
206                     }
207                     $obj->Load( $r->{'Instance'} );
208                     next unless $obj->id ;
209
210                     push @res, $obj->id;
211                 }
212                 $ref->{Argument} = join ",", @res;
213             } else {
214                 $ref->{Argument} = join ",", grep length, split /[^0-9]+/, $argument;
215             }
216         },
217     },
218
219     '3.8.8' => {
220         'RT::ObjectCustomField' => sub {
221             # XXX Removing OCFs applied both global and non-global
222             # XXX Fixing SortOrder on OCFs
223         },
224     },
225
226     '3.8.9' => {
227         'RT::Link' => sub {
228             my ($ref) = @_;
229             my $prefix = RT::URI::fsck_com_rt->LocalURIPrefix . '/ticket/';
230             for my $dir (qw(Target Base)) {
231                 next unless $ref->{$dir} =~ /^$prefix(.*)/;
232                 next unless int($1) eq $1;
233                 next if $ref->{'Local'.$dir};
234                 $ref->{'Local'.$dir} = $1;
235             }
236         },
237         'RT::Template' => sub {
238             my ($ref) = @_;
239
240             return unless $ref->{Name} =~
241                 /^(All Approvals Passed|Approval Passed|Approval Rejected)$/;
242
243             my $queue = RT::Queue->new( $RT::SystemUser );
244             $queue->Load( $ref->{Queue} );
245             return unless $queue->Id and $queue->Name eq "___Approvals";
246
247             $ref->{Content} =~
248 s!(?<=Your ticket has been (?:approved|rejected) by \{ eval \{ )\$Approval->OwnerObj->Name!\$Approver->Name!;
249         },
250     },
251
252     '3.9.1' => {
253         'RT::Template' => sub {
254             my ($ref) = @_;
255             $ref->{Type} = 'Perl';
256         },
257         # XXX: Add ExecuteCode to principals that currently have ModifyTemplate or ModifyScrips
258     },
259
260     '3.9.2' => {
261         'RT::ACE' => sub {
262             my ($ref, $classref) = @_;
263             $$classref = undef if $ref->{DelegatedBy} > 0
264                                or $ref->{DelegatedFrom} > 0;
265         },
266
267         'RT::GroupMember' => sub {
268             my ($ref, $classref) = @_;
269             my $group = RT::Group->new( $RT::SystemUser );
270             $group->Load( $ref->{GroupId} );
271             $$classref = undef if $group->Domain eq "Personal";
272         },
273         'RT::Group' => sub {
274             my ($ref, $classref) = @_;
275             $$classref = undef if $ref->{Domain} eq "Personal";
276         },
277         'RT::Principal' => sub {
278             my ($ref, $classref) = @_;
279             return unless $ref->{PrincipalType} eq "Group";
280             my $group = RT::Group->new( $RT::SystemUser );
281             $group->Load( $ref->{ObjectId} );
282             $$classref = undef if $group->Domain eq "Personal";
283         },
284     },
285
286     '3.9.3' => {
287         'RT::ACE' => sub {
288             my ($ref) = @_;
289             delete $ref->{DelegatedBy};
290             delete $ref->{DelegatedFrom};
291         },
292     },
293
294     '3.9.5' => {
295         'RT::CustomFieldValue' => sub {
296             my ($ref) = @_;
297             my $attr = RT::Attribute->new( $RT::SystemUser );
298             $attr->LoadByCols(
299                 ObjectType => "RT::CustomFieldValue",
300                 ObjectId   => $ref->{Id},
301                 Name       => "Category",
302             );
303             $ref->{Category} = $attr->Content if $attr->id;
304         },
305         'RT::Attribute' => sub {
306             my ($ref, $classref) = @_;
307             $$classref = undef if $ref->{Name} eq "Category"
308                 and $ref->{ObjectType} eq "RT::CustomFieldValue";
309         },
310     },
311
312     '3.9.7' => {
313         'RT::User' => sub {
314             my ($ref) = @_;
315             my $attr = RT::Attribute->new( $RT::SystemUser );
316             $attr->LoadByCols(
317                 ObjectType => "RT::User",
318                 ObjectId   => $ref->{id},
319                 Name       => "AuthToken",
320             );
321             $ref->{AuthToken} = $attr->Content if $attr->id;
322         },
323         'RT::CustomField' => sub {
324             my ($ref) = @_;
325             for my $name (qw/RenderType BasedOn ValuesClass/) {
326                 my $attr = RT::Attribute->new( $RT::SystemUser );
327                 $attr->LoadByCols(
328                     ObjectType => "RT::CustomField",
329                     ObjectId   => $ref->{id},
330                     Name       => $name,
331                 );
332                 $ref->{$name} = $attr->Content if $attr->id;
333             }
334         },
335         'RT::Queue' => sub {
336             my ($ref) = @_;
337             my $attr = RT::Attribute->new(
338                 ObjectType => "RT::System",
339                 ObjectId   => 1,
340                 Name       => "BrandedSubjectTag",
341             );;
342             return unless $attr->id;
343             my $map = $attr->Content || {};
344             return unless $map->{$ref->{id}};
345             $ref->{SubjectTag} = $map->{$ref->{id}};
346         },
347         'RT::Attribute' => sub {
348             my ($ref, $classref) = @_;
349             if ($ref->{ObjectType} eq "RT::User" and $ref->{Name} eq "AuthToken") {
350                 $$classref = undef;
351             } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "RenderType") {
352                 $$classref = undef;
353             } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "BasedOn") {
354                 $$classref = undef;
355             } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "ValuesClass") {
356                 $$classref = undef;
357             } elsif ($ref->{ObjectType} eq "RT::System" and $ref->{Name} eq "BrandedSubjectTag") {
358                 $$classref = undef;
359             }
360         },
361     },
362
363     '3.9.8' => {
364         # XXX RTFM => Articles
365     },
366
367     '4.0.0rc7' => {
368         'RT::Queue' => sub {
369             my ($ref) = @_;
370             return unless $ref->{Name} eq '___Approvals';
371             $ref->{Lifecycle} = "approvals";
372         },
373     },
374
375     '4.0.1' => {
376         'RT::ACE' => sub {
377             my ($ref, $classref) = @_;
378             my $group = RT::Group->new( $RT::SystemUser );
379             $group->LoadByCols(
380                 id     => $ref->{PrincipalId},
381                 Domain => "Personal",
382             );
383             $$classref = undef if $group->id;
384             $$classref = undef if $ref->{RightName} =~
385                 /^(AdminOwnPersonalGroups|AdminAllPersonalGroups|DelegateRights)$/;
386             $$classref = undef if $ref->{RightName} =~
387                 /^(RejectTicket|ModifyTicketStatus)$/;
388         },
389     },
390
391     '4.0.4' => {
392         'RT::Template' => sub {
393             my ($ref) = @_;
394             $ref->{Type} ||= 'Perl';
395         },
396     },
397
398     '4.0.6' => {
399         'RT::Transaction' => sub {
400             my ($ref) = @_;
401             return unless $ref->{ObjectType} eq "RT::User" and $ref->{Field} eq "Password";
402             $ref->{OldValue} = $ref->{NewValue} = '********';
403         },
404     },
405
406     '4.0.9' => {
407         'RT::Queue' => sub {
408             my ($ref) = @_;
409             $ref->{Lifecycle} ||= 'default';
410         },
411     },
412
413     '4.0.19' => {
414         'RT::CustomField' => sub {
415             my ($ref) = @_;
416             $ref->{LookupType} = 'RT::Class-RT::Article'
417                 if $ref->{LookupType} eq 'RT::FM::Class-RT::FM::Article';
418         },
419         'RT::ObjectCustomFieldValue' => sub {
420             my ($ref) = @_;
421             $ref->{ObjectType} = 'RT::Article'
422                 if $ref->{ObjectType} eq 'RT::FM::Article';
423         },
424     },
425
426
427     '4.1.0' => {
428         'RT::Attribute' => sub {
429             my ($ref) = @_;
430             return unless $ref->{Name} eq "HomepageSettings";
431
432             my $v = eval {
433                 Storable::thaw(MIME::Base64::decode_base64($ref->{Content}))
434               };
435             return if not $v or $v->{sidebar};
436             $v->{sidebar} = delete $v->{summary};
437             $ref->{Content} = MIME::Base64::encode_base64(
438                 Storable::nfreeze($v) );
439         },
440     },
441
442     '4.1.1' => {
443         '+RT::Scrip' => sub {
444             my ($ref) = @_;
445             my $new = [
446                 "RT::ObjectScrip" => rand(1),
447                 {
448                     id            => undef,
449                     Scrip         => $ref->{id},
450                     Stage         => delete $ref->{Stage},
451                     ObjectId      => delete $ref->{Queue},
452                     Creator       => $ref->{Creator},
453                     Created       => $ref->{Created},
454                     LastUpdatedBy => $ref->{LastUpdatedBy},
455                     LastUpdated   => $ref->{LastUpdated},
456                 }
457             ];
458             if ( $new->[2]{Stage} eq "Disabled" ) {
459                 $ref->{Disabled} = 1;
460                 $new->[2]{Stage} = "TransactionCreate";
461             } else {
462                 $ref->{Disabled} = 0;
463             }
464             # XXX SortOrder
465             return $new;
466         },
467     },
468
469     '4.1.4' => {
470         'RT::Group' => sub {
471             my ($ref) = @_;
472             $ref->{Instance} = 1
473                 if $ref->{Domain} eq "RT::System-Role"
474                     and $ref->{Instance} = 0;
475         },
476         # XXX Invalid rights
477     },
478
479     '4.1.5' => {
480         'RT::Scrip' => sub {
481             my ($ref) = @_;
482             my $template = RT::Template->new( $RT::SystemUser );
483             $template->Load( $ref->{Template} );
484             $ref->{Template} = $template->id ? $template->Name : 'Blank';
485         },
486     },
487
488     '4.1.6' => {
489         'RT::Attribute' => sub {
490             my ($ref) = @_;
491             return unless $ref->{Name} eq RT::User::_PrefName( RT->System )
492                 and $ref->{ObjectType} eq "RT::User";
493             my $v = eval {
494                 Storable::thaw(MIME::Base64::decode_base64($ref->{Content}))
495               };
496             return if not $v or $v->{ShowHistory};
497             $v->{ShowHistory} = delete $v->{DeferTransactionLoading}
498                 ? "click" : "delay";
499             $ref->{Content} = MIME::Base64::encode_base64(
500                 Storable::nfreeze($v) );
501         },
502     },
503
504     '4.1.7' => {
505         'RT::Transaction' => sub {
506             my ($ref) = @_;
507             return unless $ref->{ObjectType} eq 'RT::Ticket'
508                       and $ref->{Type} eq 'Set'
509                       and $ref->{Field} eq 'TimeWorked';
510             $ref->{TimeTaken} = $ref->{NewValue} - $ref->{OldValue};
511         },
512     },
513
514     '4.1.8' => {
515         'RT::Ticket' => sub {
516             my ($ref) = @_;
517             $ref->{IsMerged} = 1 if $ref->{id} != $ref->{EffectiveId};
518         },
519     },
520
521     '4.1.10' => {
522         'RT::ObjectcustomFieldValue' => sub {
523             my ($ref) = @_;
524             $ref->{Content} = undef if defined $ref->{LargeContent}
525                 and defined $ref->{Content} and $ref->{Content} eq '';
526         },
527     },
528
529     '4.1.11' => {
530         'RT::CustomField' => sub {
531             my ($ref) = @_;
532             delete $ref->{Repeated};
533         },
534     },
535
536     '4.1.13' => {
537         'RT::Group' => sub {
538             my ($ref) = @_;
539             $ref->{Name} = $ref->{Type}
540                 if $ref->{Domain} =~ /^(ACLEquivalence|SystemInternal|.*-Role)$/;
541         },
542     },
543
544     '4.1.14' => {
545         'RT::Scrip' => sub {
546             my ($ref) = @_;
547             delete $ref->{ConditionRules};
548             delete $ref->{ActionRules};
549         },
550     },
551
552     '4.1.17' => {
553         'RT::Attribute' => sub {
554             my ($ref) = @_;
555             return unless $ref->{Name} eq 'SavedSearch';
556             my $v = eval {
557                 Storable::thaw(MIME::Base64::decode_base64($ref->{Content}))
558               };
559             return unless $v and ref $v and ($v->{SearchType}||'') eq 'Chart';
560
561             # Switch from PrimaryGroupBy to GroupBy name
562             # Switch from "CreatedMonthly" to "Created.Monthly"
563             $v->{GroupBy} ||= [delete $v->{PrimaryGroupBy}];
564             for (@{$v->{GroupBy}}) {
565                 next if /\./;
566                 s/(?<=[a-z])(?=[A-Z])/./;
567             }
568             $ref->{Content} = MIME::Base64::encode_base64(
569                 Storable::nfreeze($v) );
570         },
571     },
572
573     '4.1.19' => {
574         'RT::Template' => sub {
575             my ($ref) = @_;
576             delete $ref->{Language};
577             delete $ref->{TranslationOf};
578         },
579     },
580
581     '4.1.20' => {
582         'RT::Template' => sub {
583             my ($ref) = @_;
584             if ($ref->{Name} eq 'Forward') {
585                 $ref->{Description} = 'Forwarded message';
586                 if ( $ref->{Content} =~
587                      m/^\n*This is (a )?forward of transaction #\{\s*\$Transaction->id\s*\} of (a )?ticket #\{\s*\$Ticket->id\s*\}\n*$/
588                    ) {
589                     $ref->{Content} = q{
590 { $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of transaction #".$Transaction->id." of ticket #". $Ticket->id }
591 };
592                 } else {
593                     RT->Logger->error('Current "Forward" template is not the default version, please check docs/UPGRADING-4.2');
594                 }
595             } elsif ($ref->{Name} eq 'Forward Ticket') {
596                 $ref->{Description} = 'Forwarded ticket message';
597                 if ( $ref->{Content} eq q{
598
599 This is a forward of ticket #{ $Ticket->id }
600 } ) {
601                     $ref->{Content} = q{
602 { $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of ticket #". $Ticket->id }
603 };
604                 } else {
605                     RT->Logger->error('Current "Forward Ticket" template is not the default version, please check docs/UPGRADING-4.2');
606                 }
607             }
608         },
609     },
610
611     '4.1.21' => {
612         # XXX User dashboards
613     },
614
615     '4.1.22' => {
616         'RT::Template' => sub {
617             my ($ref) = @_;
618             return unless $ref->{Name} eq 'Error: bad GnuPG data';
619             $ref->{Name} = 'Error: bad encrypted data';
620             $ref->{Description} =
621                 'Inform user that a message he sent has invalid encryption data';
622             $ref->{Content} =~ s/GnuPG signature/signature/g;
623         },
624         # XXX SMIME keys
625         'RT::Attribute' => sub {
626             my ($ref, $classref) = @_;
627             if ($ref->{ObjectType} eq "RT::User" and $ref->{Name} eq "SMIMEKeyNotAfter") {
628                 $$classref = undef;
629             }
630         },
631     },
632
633     '4.2.1' => {
634         'RT::Attribute' => sub {
635             my ($ref, $classref) = @_;
636             if ($ref->{ObjectType} eq "RT::System" and $ref->{Name} eq "BrandedSubjectTag") {
637                 $$classref = undef;
638             }
639         },
640     },
641
642     '4.2.2' => {
643         'RT::CustomField' => sub {
644             my ($ref) = @_;
645             $ref->{LookupType} = 'RT::Class-RT::Article'
646                 if $ref->{LookupType} eq 'RT::FM::Class-RT::FM::Article';
647         },
648         'RT::ObjectCustomFieldValue' => sub {
649             my ($ref) = @_;
650             $ref->{ObjectType} = 'RT::Article'
651                 if $ref->{ObjectType} eq 'RT::FM::Article';
652         },
653     },
654
655 );
656
657 1;