diff options
Diffstat (limited to 'rt/lib/RT/Migrate/Incremental.pm')
-rw-r--r-- | rt/lib/RT/Migrate/Incremental.pm | 657 |
1 files changed, 657 insertions, 0 deletions
diff --git a/rt/lib/RT/Migrate/Incremental.pm b/rt/lib/RT/Migrate/Incremental.pm new file mode 100644 index 000000000..61aea6cfb --- /dev/null +++ b/rt/lib/RT/Migrate/Incremental.pm @@ -0,0 +1,657 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC +# <sales@bestpractical.com> +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: +# +# This work is made available to you under the terms of Version 2 of +# the GNU General Public License. A copy of that license should have +# been provided with this software, but in any event can be snarfed +# from www.gnu.org. +# +# This work is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# +# CONTRIBUTION SUBMISSION POLICY: +# +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) +# +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. +# +# END BPS TAGGED BLOCK }}} + +package RT::Migrate::Incremental; + +use strict; +use warnings; +require Storable; +require MIME::Base64; + +our %UPGRADES = ( + '3.3.0' => { + 'RT::Transaction' => sub { + my ($ref) = @_; + $ref->{ObjectType} = 'RT::Ticket'; + $ref->{ObjectId} = delete $ref->{Ticket}; + delete $ref->{EffectiveTicket}; + }, + 'RT::TicketCustomFieldValue' => sub { + my ($ref, $classref) = @_; + $$classref = "RT::ObjectCustomFieldValue"; + $ref->{ObjectType} = 'RT::Ticket'; + $ref->{ObjectId} = delete $ref->{Ticket}; + }, + '-RT::TicketCustomFieldValue' => sub { + my ($ref, $classref) = @_; + $$classref = "RT::ObjectCustomFieldValue"; + }, + 'RT::CustomField' => sub { + my ($ref) = @_; + $ref->{MaxValues} = 0 if $ref->{Type} =~ /Multiple$/; + $ref->{MaxValues} = 1 if $ref->{Type} =~ /Single$/; + $ref->{Type} = 'Select' if $ref->{Type} =~ /^Select/; + $ref->{Type} = 'Freeform' if $ref->{Type} =~ /^Freeform/; + $ref->{LookupType} = 'RT::Queue-RT::Ticket'; + delete $ref->{Queue}; + }, + '+RT::CustomField' => sub { + my ($ref) = @_; + return [ + "RT::ObjectCustomField" => rand(1), + { + id => undef, + CustomField => $ref->{id}, + ObjectId => $ref->{Queue}, + SortOrder => $ref->{SortOrder}, + Creator => $ref->{Creator}, + LastUpdatedBy => $ref->{LastUpdatedBy}, + } + ]; + } + }, + + '3.3.11' => { + 'RT::ObjectCustomFieldValue' => sub { + my ($ref) = @_; + $ref->{Disabled} = not delete $ref->{Current}; + }, + }, + + '3.7.19' => { + 'RT::Scrip' => sub { + my ($ref) = @_; + return if defined $ref->{Description} and length $ref->{Description}; + + my $scrip = RT::Scrip->new( $RT::SystemUser ); + $scrip->Load( $ref->{id} ); + my $condition = $scrip->ConditionObj->Name + || $scrip->ConditionObj->Description + || ('On Condition #'. $scrip->Condition); + my $action = $scrip->ActionObj->Name + || $scrip->ActionObj->Description + || ('Run Action #'. $scrip->Action); + $ref->{Description} = join ' ', $condition, $action; + }, + }, + + # XXX BrandedQueues + # XXX iCal + + '3.8.2' => { + 'RT::Template' => sub { + my ($ref) = @_; + return unless $ref->{Queue}; + + my $queue = RT::Queue->new( $RT::SystemUser ); + $queue->Load( $ref->{Queue} ); + return unless $queue->Id and $queue->Name eq "___Approvals"; + + $ref->{Name} = "[OLD] ".$ref->{Name}; + }, + 'RT::Attribute' => sub { + my ($ref) = @_; + return unless $ref->{Name} eq "Dashboard"; + + my $v = eval { + Storable::thaw(MIME::Base64::decode_base64($ref->{Content})) + }; + return unless $v and exists $v->{Searches}; + $v->{Panes} = { + body => [ + map { + my ($privacy, $id, $desc) = @$_; + +{ + portlet_type => 'search', + privacy => $privacy, + id => $id, + description => $desc, + pane => 'body', + } + } @{ delete $v->{Searches} } + ], + }; + $ref->{Content} = MIME::Base64::encode_base64( + Storable::nfreeze($v) ); + }, + 'RT::Scrip' => sub { + my ($ref, $classref) = @_; + return unless $ref->{Queue}; + + my $queue = RT::Queue->new( $RT::SystemUser ); + $queue->Load( $ref->{Queue} ); + return unless $queue->Id and $queue->Name eq "___Approvals"; + + $$classref = undef; + }, + }, + + '3.8.3' => { + 'RT::ScripAction' => sub { + my ($ref) = @_; + return unless ($ref->{Argument}||"") eq "All"; + if ($ref->{ExecModule} eq "Notify") { + $ref->{Name} = 'Notify Owner, Requestors, Ccs and AdminCcs'; + $ref->{Description} = 'Send mail to owner and all watchers'; + } elsif ($ref->{ExecModule} eq "NotifyAsComment") { + $ref->{Name} = 'Notify Owner, Requestors, Ccs and AdminCcs as Comment'; + $ref->{Description} = 'Send mail to owner and all watchers as a "comment"'; + } + }, + }, + + '3.8.4' => { + 'RT::ScripAction' => sub { + my ($ref) = @_; + return unless $ref->{ExecModule} eq "NotifyGroup" + or $ref->{ExecModule} eq "NotifyGroupAsComment"; + + my $argument = $ref->{Argument}; + if ( my $struct = eval { Storable::thaw( $argument ) } ) { + my @res; + foreach my $r ( @{ $struct } ) { + my $obj; + next unless $r->{'Type'}; + if( lc $r->{'Type'} eq 'user' ) { + $obj = RT::User->new( $RT::SystemUser ); + } elsif ( lc $r->{'Type'} eq 'group' ) { + $obj = RT::Group->new( $RT::SystemUser ); + } else { + next; + } + $obj->Load( $r->{'Instance'} ); + next unless $obj->id ; + + push @res, $obj->id; + } + $ref->{Argument} = join ",", @res; + } else { + $ref->{Argument} = join ",", grep length, split /[^0-9]+/, $argument; + } + }, + }, + + '3.8.8' => { + 'RT::ObjectCustomField' => sub { + # XXX Removing OCFs applied both global and non-global + # XXX Fixing SortOrder on OCFs + }, + }, + + '3.8.9' => { + 'RT::Link' => sub { + my ($ref) = @_; + my $prefix = RT::URI::fsck_com_rt->LocalURIPrefix . '/ticket/'; + for my $dir (qw(Target Base)) { + next unless $ref->{$dir} =~ /^$prefix(.*)/; + next unless int($1) eq $1; + next if $ref->{'Local'.$dir}; + $ref->{'Local'.$dir} = $1; + } + }, + 'RT::Template' => sub { + my ($ref) = @_; + + return unless $ref->{Name} =~ + /^(All Approvals Passed|Approval Passed|Approval Rejected)$/; + + my $queue = RT::Queue->new( $RT::SystemUser ); + $queue->Load( $ref->{Queue} ); + return unless $queue->Id and $queue->Name eq "___Approvals"; + + $ref->{Content} =~ +s!(?<=Your ticket has been (?:approved|rejected) by \{ eval \{ )\$Approval->OwnerObj->Name!\$Approver->Name!; + }, + }, + + '3.9.1' => { + 'RT::Template' => sub { + my ($ref) = @_; + $ref->{Type} = 'Perl'; + }, + # XXX: Add ExecuteCode to principals that currently have ModifyTemplate or ModifyScrips + }, + + '3.9.2' => { + 'RT::ACE' => sub { + my ($ref, $classref) = @_; + $$classref = undef if $ref->{DelegatedBy} > 0 + or $ref->{DelegatedFrom} > 0; + }, + + 'RT::GroupMember' => sub { + my ($ref, $classref) = @_; + my $group = RT::Group->new( $RT::SystemUser ); + $group->Load( $ref->{GroupId} ); + $$classref = undef if $group->Domain eq "Personal"; + }, + 'RT::Group' => sub { + my ($ref, $classref) = @_; + $$classref = undef if $ref->{Domain} eq "Personal"; + }, + 'RT::Principal' => sub { + my ($ref, $classref) = @_; + return unless $ref->{PrincipalType} eq "Group"; + my $group = RT::Group->new( $RT::SystemUser ); + $group->Load( $ref->{ObjectId} ); + $$classref = undef if $group->Domain eq "Personal"; + }, + }, + + '3.9.3' => { + 'RT::ACE' => sub { + my ($ref) = @_; + delete $ref->{DelegatedBy}; + delete $ref->{DelegatedFrom}; + }, + }, + + '3.9.5' => { + 'RT::CustomFieldValue' => sub { + my ($ref) = @_; + my $attr = RT::Attribute->new( $RT::SystemUser ); + $attr->LoadByCols( + ObjectType => "RT::CustomFieldValue", + ObjectId => $ref->{Id}, + Name => "Category", + ); + $ref->{Category} = $attr->Content if $attr->id; + }, + 'RT::Attribute' => sub { + my ($ref, $classref) = @_; + $$classref = undef if $ref->{Name} eq "Category" + and $ref->{ObjectType} eq "RT::CustomFieldValue"; + }, + }, + + '3.9.7' => { + 'RT::User' => sub { + my ($ref) = @_; + my $attr = RT::Attribute->new( $RT::SystemUser ); + $attr->LoadByCols( + ObjectType => "RT::User", + ObjectId => $ref->{id}, + Name => "AuthToken", + ); + $ref->{AuthToken} = $attr->Content if $attr->id; + }, + 'RT::CustomField' => sub { + my ($ref) = @_; + for my $name (qw/RenderType BasedOn ValuesClass/) { + my $attr = RT::Attribute->new( $RT::SystemUser ); + $attr->LoadByCols( + ObjectType => "RT::CustomField", + ObjectId => $ref->{id}, + Name => $name, + ); + $ref->{$name} = $attr->Content if $attr->id; + } + }, + 'RT::Queue' => sub { + my ($ref) = @_; + my $attr = RT::Attribute->new( + ObjectType => "RT::System", + ObjectId => 1, + Name => "BrandedSubjectTag", + );; + return unless $attr->id; + my $map = $attr->Content || {}; + return unless $map->{$ref->{id}}; + $ref->{SubjectTag} = $map->{$ref->{id}}; + }, + 'RT::Attribute' => sub { + my ($ref, $classref) = @_; + if ($ref->{ObjectType} eq "RT::User" and $ref->{Name} eq "AuthToken") { + $$classref = undef; + } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "RenderType") { + $$classref = undef; + } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "BasedOn") { + $$classref = undef; + } elsif ($ref->{ObjectType} eq "RT::CustomField" and $ref->{Name} eq "ValuesClass") { + $$classref = undef; + } elsif ($ref->{ObjectType} eq "RT::System" and $ref->{Name} eq "BrandedSubjectTag") { + $$classref = undef; + } + }, + }, + + '3.9.8' => { + # XXX RTFM => Articles + }, + + '4.0.0rc7' => { + 'RT::Queue' => sub { + my ($ref) = @_; + return unless $ref->{Name} eq '___Approvals'; + $ref->{Lifecycle} = "approvals"; + }, + }, + + '4.0.1' => { + 'RT::ACE' => sub { + my ($ref, $classref) = @_; + my $group = RT::Group->new( $RT::SystemUser ); + $group->LoadByCols( + id => $ref->{PrincipalId}, + Domain => "Personal", + ); + $$classref = undef if $group->id; + $$classref = undef if $ref->{RightName} =~ + /^(AdminOwnPersonalGroups|AdminAllPersonalGroups|DelegateRights)$/; + $$classref = undef if $ref->{RightName} =~ + /^(RejectTicket|ModifyTicketStatus)$/; + }, + }, + + '4.0.4' => { + 'RT::Template' => sub { + my ($ref) = @_; + $ref->{Type} ||= 'Perl'; + }, + }, + + '4.0.6' => { + 'RT::Transaction' => sub { + my ($ref) = @_; + return unless $ref->{ObjectType} eq "RT::User" and $ref->{Field} eq "Password"; + $ref->{OldValue} = $ref->{NewValue} = '********'; + }, + }, + + '4.0.9' => { + 'RT::Queue' => sub { + my ($ref) = @_; + $ref->{Lifecycle} ||= 'default'; + }, + }, + + '4.0.19' => { + 'RT::CustomField' => sub { + my ($ref) = @_; + $ref->{LookupType} = 'RT::Class-RT::Article' + if $ref->{LookupType} eq 'RT::FM::Class-RT::FM::Article'; + }, + 'RT::ObjectCustomFieldValue' => sub { + my ($ref) = @_; + $ref->{ObjectType} = 'RT::Article' + if $ref->{ObjectType} eq 'RT::FM::Article'; + }, + }, + + + '4.1.0' => { + 'RT::Attribute' => sub { + my ($ref) = @_; + return unless $ref->{Name} eq "HomepageSettings"; + + my $v = eval { + Storable::thaw(MIME::Base64::decode_base64($ref->{Content})) + }; + return if not $v or $v->{sidebar}; + $v->{sidebar} = delete $v->{summary}; + $ref->{Content} = MIME::Base64::encode_base64( + Storable::nfreeze($v) ); + }, + }, + + '4.1.1' => { + '+RT::Scrip' => sub { + my ($ref) = @_; + my $new = [ + "RT::ObjectScrip" => rand(1), + { + id => undef, + Scrip => $ref->{id}, + Stage => delete $ref->{Stage}, + ObjectId => delete $ref->{Queue}, + Creator => $ref->{Creator}, + Created => $ref->{Created}, + LastUpdatedBy => $ref->{LastUpdatedBy}, + LastUpdated => $ref->{LastUpdated}, + } + ]; + if ( $new->[2]{Stage} eq "Disabled" ) { + $ref->{Disabled} = 1; + $new->[2]{Stage} = "TransactionCreate"; + } else { + $ref->{Disabled} = 0; + } + # XXX SortOrder + return $new; + }, + }, + + '4.1.4' => { + 'RT::Group' => sub { + my ($ref) = @_; + $ref->{Instance} = 1 + if $ref->{Domain} eq "RT::System-Role" + and $ref->{Instance} = 0; + }, + # XXX Invalid rights + }, + + '4.1.5' => { + 'RT::Scrip' => sub { + my ($ref) = @_; + my $template = RT::Template->new( $RT::SystemUser ); + $template->Load( $ref->{Template} ); + $ref->{Template} = $template->id ? $template->Name : 'Blank'; + }, + }, + + '4.1.6' => { + 'RT::Attribute' => sub { + my ($ref) = @_; + return unless $ref->{Name} eq RT::User::_PrefName( RT->System ) + and $ref->{ObjectType} eq "RT::User"; + my $v = eval { + Storable::thaw(MIME::Base64::decode_base64($ref->{Content})) + }; + return if not $v or $v->{ShowHistory}; + $v->{ShowHistory} = delete $v->{DeferTransactionLoading} + ? "click" : "delay"; + $ref->{Content} = MIME::Base64::encode_base64( + Storable::nfreeze($v) ); + }, + }, + + '4.1.7' => { + 'RT::Transaction' => sub { + my ($ref) = @_; + return unless $ref->{ObjectType} eq 'RT::Ticket' + and $ref->{Type} eq 'Set' + and $ref->{Field} eq 'TimeWorked'; + $ref->{TimeTaken} = $ref->{NewValue} - $ref->{OldValue}; + }, + }, + + '4.1.8' => { + 'RT::Ticket' => sub { + my ($ref) = @_; + $ref->{IsMerged} = 1 if $ref->{id} != $ref->{EffectiveId}; + }, + }, + + '4.1.10' => { + 'RT::ObjectcustomFieldValue' => sub { + my ($ref) = @_; + $ref->{Content} = undef if defined $ref->{LargeContent} + and defined $ref->{Content} and $ref->{Content} eq ''; + }, + }, + + '4.1.11' => { + 'RT::CustomField' => sub { + my ($ref) = @_; + delete $ref->{Repeated}; + }, + }, + + '4.1.13' => { + 'RT::Group' => sub { + my ($ref) = @_; + $ref->{Name} = $ref->{Type} + if $ref->{Domain} =~ /^(ACLEquivalence|SystemInternal|.*-Role)$/; + }, + }, + + '4.1.14' => { + 'RT::Scrip' => sub { + my ($ref) = @_; + delete $ref->{ConditionRules}; + delete $ref->{ActionRules}; + }, + }, + + '4.1.17' => { + 'RT::Attribute' => sub { + my ($ref) = @_; + return unless $ref->{Name} eq 'SavedSearch'; + my $v = eval { + Storable::thaw(MIME::Base64::decode_base64($ref->{Content})) + }; + return unless $v and ref $v and ($v->{SearchType}||'') eq 'Chart'; + + # Switch from PrimaryGroupBy to GroupBy name + # Switch from "CreatedMonthly" to "Created.Monthly" + $v->{GroupBy} ||= [delete $v->{PrimaryGroupBy}]; + for (@{$v->{GroupBy}}) { + next if /\./; + s/(?<=[a-z])(?=[A-Z])/./; + } + $ref->{Content} = MIME::Base64::encode_base64( + Storable::nfreeze($v) ); + }, + }, + + '4.1.19' => { + 'RT::Template' => sub { + my ($ref) = @_; + delete $ref->{Language}; + delete $ref->{TranslationOf}; + }, + }, + + '4.1.20' => { + 'RT::Template' => sub { + my ($ref) = @_; + if ($ref->{Name} eq 'Forward') { + $ref->{Description} = 'Forwarded message'; + if ( $ref->{Content} =~ + m/^\n*This is (a )?forward of transaction #\{\s*\$Transaction->id\s*\} of (a )?ticket #\{\s*\$Ticket->id\s*\}\n*$/ + ) { + $ref->{Content} = q{ +{ $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of transaction #".$Transaction->id." of ticket #". $Ticket->id } +}; + } else { + RT->Logger->error('Current "Forward" template is not the default version, please check docs/UPGRADING-4.2'); + } + } elsif ($ref->{Name} eq 'Forward Ticket') { + $ref->{Description} = 'Forwarded ticket message'; + if ( $ref->{Content} eq q{ + +This is a forward of ticket #{ $Ticket->id } +} ) { + $ref->{Content} = q{ +{ $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of ticket #". $Ticket->id } +}; + } else { + RT->Logger->error('Current "Forward Ticket" template is not the default version, please check docs/UPGRADING-4.2'); + } + } + }, + }, + + '4.1.21' => { + # XXX User dashboards + }, + + '4.1.22' => { + 'RT::Template' => sub { + my ($ref) = @_; + return unless $ref->{Name} eq 'Error: bad GnuPG data'; + $ref->{Name} = 'Error: bad encrypted data'; + $ref->{Description} = + 'Inform user that a message he sent has invalid encryption data'; + $ref->{Content} =~ s/GnuPG signature/signature/g; + }, + # XXX SMIME keys + 'RT::Attribute' => sub { + my ($ref, $classref) = @_; + if ($ref->{ObjectType} eq "RT::User" and $ref->{Name} eq "SMIMEKeyNotAfter") { + $$classref = undef; + } + }, + }, + + '4.2.1' => { + 'RT::Attribute' => sub { + my ($ref, $classref) = @_; + if ($ref->{ObjectType} eq "RT::System" and $ref->{Name} eq "BrandedSubjectTag") { + $$classref = undef; + } + }, + }, + + '4.2.2' => { + 'RT::CustomField' => sub { + my ($ref) = @_; + $ref->{LookupType} = 'RT::Class-RT::Article' + if $ref->{LookupType} eq 'RT::FM::Class-RT::FM::Article'; + }, + 'RT::ObjectCustomFieldValue' => sub { + my ($ref) = @_; + $ref->{ObjectType} = 'RT::Article' + if $ref->{ObjectType} eq 'RT::FM::Article'; + }, + }, + +); + +1; |