[Bps-public-commit] r17717 - in RT-Extension-MergeUsersHistory: lib lib/RT sbin

ruz at bestpractical.com ruz at bestpractical.com
Mon Jan 12 16:31:16 EST 2009


Author: ruz
Date: Mon Jan 12 16:31:16 2009
New Revision: 17717

Added:
   RT-Extension-MergeUsersHistory/lib/
   RT-Extension-MergeUsersHistory/lib/RT/
   RT-Extension-MergeUsersHistory/lib/RT/Extension/
   RT-Extension-MergeUsersHistory/lib/RT/Extension/MergeUsersHistory.pm
   RT-Extension-MergeUsersHistory/sbin/
   RT-Extension-MergeUsersHistory/sbin/rt-merge-users-history

Log:
* initial commit

Added: RT-Extension-MergeUsersHistory/lib/RT/Extension/MergeUsersHistory.pm
==============================================================================
--- (empty file)
+++ RT-Extension-MergeUsersHistory/lib/RT/Extension/MergeUsersHistory.pm	Mon Jan 12 16:31:16 2009
@@ -0,0 +1,28 @@
+use 5.008003;
+use strict;
+use warnings;
+
+package RT::Extension::MergeUsersHistory;
+
+our $VERSION = '0.01';
+
+=head1 NAME
+
+RT::Extension::MergeUsersHistory - merge historical activity of RT users.
+
+=head1 DESCRIPTION
+
+This distribution is shipped with a command line script F<sbin/rt-merge-users-history>,
+run it without arguments to get usage details.
+
+=head1 AUTHOR
+
+Ruslan Zakirov E<lt>ruz at bestpractical.comE<gt>
+
+=head1 LICENSE
+
+Under the same terms as Perl itself.
+
+=cut
+
+1;

Added: RT-Extension-MergeUsersHistory/sbin/rt-merge-users-history
==============================================================================
--- (empty file)
+++ RT-Extension-MergeUsersHistory/sbin/rt-merge-users-history	Mon Jan 12 16:31:16 2009
@@ -0,0 +1,246 @@
+#!/usr/bin/perl
+
+use 5.008003;
+use strict;
+use warnings;
+
+my @models = qw(
+    ACE
+    Attachment
+    Attribute
+    CachedGroupMember
+    CustomField
+    CustomFieldValue
+    GroupMember
+    Group
+    Link
+    ObjectCustomField
+    ObjectCustomFieldValue
+    Principal
+    Queue
+    ScripAction
+    ScripCondition
+    Scrip
+    Template
+    Ticket
+    Transaction
+    User
+);
+
+my $usage = <<END;
+
+This script turns historical activity of two users into activity of one.
+Then you can either delete one of them or use MergeUsers extension to make
+one account an alias to another.
+
+    usage: $0 <merge_user> <into_user>
+
+END
+
+
+my ($from, $into) = @ARGV;
+die "$usage" unless $from && $into;
+
+{
+    require File::Spec;
+### after:     my @libs = ("@LOCAL_LIB_PATH@", "@RT_LIB_PATH@");
+    my @libs = ("/opt/rt3/local/lib", "/opt/rt3/lib");
+    my $bin_path;
+
+    for my $lib (@libs) {
+        unless ( File::Spec->file_name_is_absolute($lib) ) {
+            unless ($bin_path) {
+                if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+                    $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+                }
+                else {
+                    require FindBin;
+                    no warnings "once";
+                    $bin_path = $FindBin::Bin;
+                }
+            }
+            $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+        }
+        unshift @INC, $lib;
+    }
+}
+
+require RT;
+print $usage;
+
+RT::LoadConfig();
+RT->Config->Set( LogToScreen => 'debug' );
+RT->Config->Set( StatementLog => 'info' );
+RT::Init();
+
+my $merge = RT::User->new( $RT::SystemUser );
+$merge->Load( $from );
+die "Couldn't load user using '$from'" unless $merge->id;
+
+my $user = RT::User->new( $RT::SystemUser );
+$user->Load( $into );
+die "Couldn't load user using '$into'" unless $user->id;
+
+my ($from_id, $into_id) = ($merge->id, $user->id);
+
+print <<END;
+
+Going to merge user #$from_id into user #$into_id.
+
+User #$into_id will be added to all groups where user #$from_id
+is member. User #$from_id will be deleted from all groups
+except system internal.
+
+All other references to user #$from_id will be replaced with
+references to user you're merging into.
+
+END
+exit 0 unless prompt_yN("Are you sure you want to do that?");
+
+
+my $dbh = $RT::Handle->dbh;
+
+$RT::Handle->BeginTransaction or die "couldn't start a transaction";
+
+foreach my $column ( qw(OldValue NewValue) ) {
+    my $query = "UPDATE Transactions SET $column = ?"
+        ." WHERE $column LIKE ? AND ObjectType = ? AND Type = ?";
+
+    # Txn.OldValue, Txn.NewValue for type = DelWatcher, AddWatcher
+    foreach my $t ( qw(DelWatcher AddWatcher) ) {
+        $RT::Logger->debug( "Updating '$column' column of $t transactions");
+        execute($query, $into_id, $from_id, 'RT::Ticket', $t);
+    }
+    # Txn.OldValue, Txn.NewValue for type = Set, Take, Untake, Force, Steal or Give
+    foreach my $t ( qw(Set Take Untake Force Steal Give) ) {
+        $RT::Logger->debug( "Updating '$column' column of $t transactions");
+        execute($query ." AND Field = ?", $into_id, $from_id, 'RT::Ticket', $t, 'Owner');
+    }
+}
+
+# *.LastUpdatedBy and *.Creator
+# XXX: do something about Creator == ACL.equiv.group.id
+foreach my $model ( @models ) {
+    my $class = "RT::$model";
+    my $object = $class->new( $RT::SystemUser );
+    my $table = $object->Table;
+    foreach my $column ( qw(LastUpdatedBy Creator) ) {
+        next unless $object->_Accessible( $column => 'auto' );
+
+        my $query = "UPDATE $table SET $column = ? WHERE $column = ?";
+        $RT::Logger->debug( "Updating '$column' column in $table");
+        execute($query, $into_id, $from_id);
+    }
+}
+
+# add user to all groups...
+{
+    my $user_is_owner = 0;
+
+    # select all GMs where user we're merging is member, but target user is not
+    my $query = "SELECT s.GroupId FROM GroupMembers s LEFT JOIN GroupMembers t"
+        ." ON t.GroupId = s.GroupId AND t.MemberId = ?"
+        ." WHERE s.MemberId = ? AND t.id IS NULL";
+    my $sth = execute( $query, $into_id, $from_id );
+    while ( my ($gid) = $sth->fetchrow_array ) {
+        my $group = RT::Group->new( $RT::SystemUser );
+        $group->LoadById( $gid );
+        die "couldn't load group #$gid" unless $group->id;
+
+        my $domain = $group->Domain;
+        next if $domain eq 'SystemInternal' || $domain eq 'ACLEquivalence';
+
+        # special case
+        $user_is_owner = 1 if $domain eq 'RT::Ticket-Role' && $group->Type eq 'Owner';
+
+        $RT::Logger->debug( "Adding user #$into_id as member to ". describe( $group ) );
+        my ($status, $msg) = $group->_AddMember(
+            PrincipalId => $into_id,
+            InsideTransaction => 1,
+        );
+        unless ( $status ) {
+            die "couldn't add member to a group";
+        }
+    }
+    if ( $user_is_owner ) {
+        my $query = "UPDATE Tickets SET Owner = ? WHERE Owner = ?";
+        $RT::Logger->debug( "Replacing owner of tickets");
+        execute($query, $into_id, $from_id);
+    }
+}
+
+# delete user from all groups...
+{
+    # select all GMs where user we're merging is member
+    my $query = "SELECT gm.GroupId FROM GroupMembers gm"
+        ." WHERE gm.MemberId = ?";
+    my $sth = execute( $query, $from_id );
+    while ( my ($gid) = $sth->fetchrow_array ) {
+        my $group = RT::Group->new( $RT::SystemUser );
+        $group->LoadById( $gid );
+        die "couldn't load group #$gid" unless $group->id;
+
+        my $domain = $group->Domain;
+        next if $domain eq 'SystemInternal' || $domain eq 'ACLEquivalence';
+
+        $RT::Logger->debug( "Deleting user #$from_id from ". describe( $group ) );
+        my ($status, $msg) = $group->_DeleteMember( $from_id );
+        unless ( $status ) {
+            die "couldn't delete member of a group: $msg";
+        }
+    }
+}
+
+# update attachments' headers
+if ( $merge->EmailAddress && $user->EmailAddress ) {
+    $RT::Logger->debug( "Replacing email addresses in attachments' headers" );
+    my $query = "UPDATE Attachments a, Transactions t SET a.Headers = REPLACE(a.Headers, ?, ?)"
+        ." WHERE a.TransactionId = t.id AND t.Type NOT LIKE ?";
+    my $sth = execute( $query, $merge->EmailAddress, $user->EmailAddress, '%EmailRecord%' );
+    $sth->finish;
+}
+
+exit 0 unless prompt_yN("We're ready to commit changes, continue?");
+$RT::Handle->Commit or die "couldn't commit the transaction";
+exit 0;
+
+sub execute {
+    my ($query, @binds) = (@_);
+    my $sth = $dbh->prepare( $query ) or die "couldn't prepare $query\n\tError: ". $dbh->errstr;
+    $sth->execute( @binds ) or die "couldn't execute $query\n\tError: ". $sth->errstr;
+    return $sth;
+}
+
+sub prompt_yN {
+    my $msg = shift;
+    print $msg ." [N]: ";
+    my $a = <STDIN>;
+    return 1 if $a =~ /^(y|yes)$/i;
+    return 0;
+}
+
+sub describe {
+    my $obj = shift;
+    return describe_group( $obj ) if $obj->isa('RT::Group');
+    die "No describe method for $obj";
+}
+
+sub describe_group {
+    my $group = shift;
+    my $res = '';
+    my $domain = $group->Domain;
+    if ( $domain =~ /^RT::(.*)-Role$/i ) {
+        if ( lc($1) eq 'system' ) {
+            $res = $group->Type ." system role group";
+        } else {
+            $res = $group->Type ." role group of $1 #". $group->Instance;
+        }
+    }
+    elsif ( lc($domain) eq 'userdefined' ) {
+        $res = 'group #'. $group->id .' ('. $group->Name .')';
+    } else {
+        $res = 'group #'. $group->id;
+    }
+    return $res;
+}
+



More information about the Bps-public-commit mailing list