Surgically removing custom fields using an RT::Action

Hi all,

This is just a quick “brain dump” of a hack I’ve done today in case other folk might find it useful in the future.

The background is that we’re using RT in a system that sometimes has user photos included as custom fields in tickets. We might need to remove all trace of these photos whilst leaving the actual ticket behind (for auditing purposes). All trace means not only nuke the current value from the UI but also all transactions involving that CF removed and the disabled RT::ObjectCustomFieldValue entries wiped from the database as well.

So here’s a local RT::Action::RemoveCF that should do this for you:

#!/usr/bin/perl
#
# RT::Action::RemoveCF - remove a custom field from a ticket and all
#                        transaction history, etc.
#
# Mandatory argument: the name of the custom field to surgically remove from
# the ticket this action is being run against.
#

package RT::Action::RemoveCF;

use strict;
use warnings;
use base 'RT::Action';
use DateTime;
use JSON;

sub Prepare {
    my $self = shift;

    my $argument = $self->Argument;

    unless ( $argument ) {
        $RT::Logger->error("Argument of CF name is mandatory for RemoveCF action");
        return 0;
    }
    
    # Stuff the CF name into this object instance so that the Commit code
    # can access it.
    $self->{'remove_CF_name'} = $argument;

    return 1;
}

sub Commit {
    my $self = shift;
    my $ticketObj = $self->TicketObj;

    # Grab the custom field name we were given as an argument.
    my $CFName = $self->{'remove_CF_name'};

    my $ocfvs = $ticketObj->CustomFieldValues($CFName);
    while(my $thisOCFV = $ocfvs->Next) {
	my($status, $msg) = $thisOCFV->Delete();
    } 

    # Remove any transactions that reference this named CF
    my $txns = $ticketObj->Transactions();
    while (my $thistxn = $txns->Next) {
	if($thistxn->ReferenceType &&
	   $thistxn->ReferenceType eq 'RT::ObjectCustomFieldValue' &&
	   $thistxn->NewReference
	    ) {
	    my $thisOCFV = RT::ObjectCustomFieldValue->new( RT::SystemUser );
	    $thisOCFV->Load($thistxn->NewReference);
	    my $cfObj = $thisOCFV->CustomFieldObj;
	    if($cfObj->Name eq $CFName) {
		$thistxn->Delete();
	    }
	}
    }

    # Find any previous versions of this custom field and wipe them from history
    my $cfObj = RT::CustomField->new( RT::SystemUser );
    $cfObj->LoadByName(
	Name => $CFName,
	ObjectType => $ticketObj->RecordType,
	);
    $ocfvs = RT::ObjectCustomFieldValues->new( RT::SystemUser );
    $ocfvs->ClearOCFVCache();
    $ocfvs->LimitToCustomField($cfObj->id);
    $ocfvs->LimitToObject( $ticketObj );
    # Sneaky hack to find those naughty disabled ObjectCustomFieldValues
    $ocfvs->{'find_disabled_rows'} = 1;
    while(my $thisOCFV = $ocfvs->Next) {
	$thisOCFV->RT::Record::Delete();
    }
    return 1;
}

1;

As this is an RT::Action it can be run from rt-crontool with a command such as:

/opt/rt5/bin/rt-crontool --search RT::Search::FromSQL --search-arg "id = '165930'" --action RT::Action::RemoveCF --action-arg Location --verbose

This will wipe any mention of the custom field “Location” from the ticket ID 165930.

Hope that might help someone. :slight_smile:

3 Likes