Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / Migrate / Incremental.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2013 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
414
415     '4.1.0' => {
416         'RT::Attribute' => sub {
417             my ($ref) = @_;
418             return unless $ref->{Name} eq "HomepageSettings";
419
420             my $v = eval {
421                 Storable::thaw(MIME::Base64::decode_base64($ref->{Content}))
422               };
423             return if not $v or $v->{sidebar};
424             $v->{sidebar} = delete $v->{summary};
425             $ref->{Content} = MIME::Base64::encode_base64(
426                 Storable::nfreeze($v) );
427         },
428     },
429
430     '4.1.1' => {
431         '+RT::Scrip' => sub {
432             my ($ref) = @_;
433             my $new = [
434                 "RT::ObjectScrip" => rand(1),
435                 {
436                     id            => undef,
437                     Scrip         => $ref->{id},
438                     Stage         => delete $ref->{Stage},
439                     ObjectId      => delete $ref->{Queue},
440                     Creator       => $ref->{Creator},
441                     Created       => $ref->{Created},
442                     LastUpdatedBy => $ref->{LastUpdatedBy},
443                     LastUpdated   => $ref->{LastUpdated},
444                 }
445             ];
446             if ( $new->[2]{Stage} eq "Disabled" ) {
447                 $ref->{Disabled} = 1;
448                 $new->[2]{Stage} = "TransactionCreate";
449             } else {
450                 $ref->{Disabled} = 0;
451             }
452             # XXX SortOrder
453             return $new;
454         },
455     },
456
457     '4.1.4' => {
458         'RT::Group' => sub {
459             my ($ref) = @_;
460             $ref->{Instance} = 1
461                 if $ref->{Domain} eq "RT::System-Role"
462                     and $ref->{Instance} = 0;
463         },
464         # XXX Invalid rights
465     },
466
467     '4.1.5' => {
468         'RT::Scrip' => sub {
469             my ($ref) = @_;
470             my $template = RT::Template->new( $RT::SystemUser );
471             $template->Load( $ref->{Template} );
472             $ref->{Template} = $template->id ? $template->Name : 'Blank';
473         },
474     },
475
476     '4.1.6' => {
477         'RT::Attribute' => sub {
478             my ($ref) = @_;
479             return unless $ref->{Name} eq RT::User::_PrefName( RT->System )
480                 and $ref->{ObjectType} eq "RT::User";
481             my $v = eval {
482                 Storable::thaw(MIME::Base64::decode_base64($ref->{Content}))
483               };
484             return if not $v or $v->{ShowHistory};
485             $v->{ShowHistory} = delete $v->{DeferTransactionLoading}
486                 ? "click" : "delay";
487             $ref->{Content} = MIME::Base64::encode_base64(
488                 Storable::nfreeze($v) );
489         },
490     },
491
492     '4.1.7' => {
493         'RT::Transaction' => sub {
494             my ($ref) = @_;
495             return unless $ref->{ObjectType} eq 'RT::Ticket'
496                       and $ref->{Type} eq 'Set'
497                       and $ref->{Field} eq 'TimeWorked';
498             $ref->{TimeTaken} = $ref->{NewValue} - $ref->{OldValue};
499         },
500     },
501
502     '4.1.8' => {
503         'RT::Ticket' => sub {
504             my ($ref) = @_;
505             $ref->{IsMerged} = 1 if $ref->{id} != $ref->{EffectiveId};
506         },
507     },
508
509     '4.1.10' => {
510         'RT::ObjectcustomFieldValue' => sub {
511             my ($ref) = @_;
512             $ref->{Content} = undef if defined $ref->{LargeContent}
513                 and defined $ref->{Content} and $ref->{Content} eq '';
514         },
515     },
516
517     '4.1.11' => {
518         'RT::CustomField' => sub {
519             my ($ref) = @_;
520             delete $ref->{Repeated};
521         },
522     },
523
524     '4.1.13' => {
525         'RT::Group' => sub {
526             my ($ref) = @_;
527             $ref->{Name} = $ref->{Type}
528                 if $ref->Domain =~ /^(ACLEquivalence|SystemInternal|.*-Role)$/;
529         },
530     },
531
532     '4.1.14' => {
533         'RT::Scrip' => sub {
534             my ($ref) = @_;
535             delete $ref->{ConditionRules};
536             delete $ref->{ActionRules};
537         },
538     },
539
540     '4.1.17' => {
541         'RT::Attribute' => sub {
542             my ($ref) = @_;
543             return unless $ref->{Name} eq 'SavedSearch';
544             my $v = eval {
545                 Storable::thaw(MIME::Base64::decode_base64($ref->{Content}))
546               };
547             return unless $v and ref $v and ($v->{SearchType}||'') eq 'Chart';
548
549             # Switch from PrimaryGroupBy to GroupBy name
550             # Switch from "CreatedMonthly" to "Created.Monthly"
551             $v->{GroupBy} ||= [delete $v->{PrimaryGroupBy}];
552             for (@{$v->{GroupBy}}) {
553                 next if /\./;
554                 s/(?<=[a-z])(?=[A-Z])/./;
555             }
556             $ref->{Content} = MIME::Base64::encode_base64(
557                 Storable::nfreeze($v) );
558         },
559     },
560
561     '4.1.19' => {
562         'RT::Template' => sub {
563             my ($ref) = @_;
564             delete $ref->{Language};
565             delete $ref->{TranslationOf};
566         },
567     },
568
569     '4.1.20' => {
570         'RT::Template' => sub {
571             my ($ref) = @_;
572             if ($ref->{Name} eq 'Forward') {
573                 $ref->{Description} = 'Forwarded message';
574                 if ( $ref->{Content} =~
575                      m/^\n*This is (a )?forward of transaction #\{\s*\$Transaction->id\s*\} of (a )?ticket #\{\s*\$Ticket->id\s*\}\n*$/
576                    ) {
577                     $ref->{Content} = q{
578 { $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of transaction #".$Transaction->id." of ticket #". $Ticket->id }
579 };
580                 } else {
581                     RT->Logger->error('Current "Forward" template is not the default version, please check docs/4.2-UPGRADING');
582                 }
583             } elsif ($ref->{Name} eq 'Forward Ticket') {
584                 $ref->{Description} = 'Forwarded ticket message';
585                 if ( $ref->{Content} eq q{
586
587 This is a forward of ticket #{ $Ticket->id }
588 } ) {
589                     $ref->{Content} = q{
590 { $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of ticket #". $Ticket->id }
591 };
592                 } else {
593                     RT->Logger->error('Current "Forward Ticket" template is not the default version, please check docs/4.2-UPGRADING');
594                 }
595             }
596         },
597     },
598
599     '4.1.21' => {
600         # XXX User dashboards
601     },
602
603     '4.1.22' => {
604         'RT::Template' => sub {
605             my ($ref) = @_;
606             return unless $ref->{Name} eq 'Error: bad GnuPG data';
607             $ref->{Name} = 'Error: bad encrypted data';
608             $ref->{Description} =
609                 'Inform user that a message he sent has invalid encryption data';
610             $ref->{Content} =~ s/GnuPG signature/signature/g;
611         },
612         # XXX SMIME keys
613         'RT::Attribute' => sub {
614             my ($ref, $classref) = @_;
615             if ($ref->{ObjectType} eq "RT::User" and $ref->{Name} eq "SMIMEKeyNotAfter") {
616                 $$classref = undef;
617             }
618         },
619     },
620 );
621
622 1;