+ return $self->loc( $self->Object->RecordType );
+}
+
+=head2 UpdateCustomFields
+
+Takes a hash of:
+
+ CustomField-C<Id> => Value
+
+or:
+
+ Object-RT::Transaction-CustomField-C<Id> => Value
+
+parameters to update this transaction's custom fields.
+
+=cut
+
+sub UpdateCustomFields {
+ my $self = shift;
+ my %args = (@_);
+
+ # This method used to have an API that took a hash of a single
+ # value "ARGSRef", which was a reference to a hash of arguments.
+ # This was insane. The next few lines of code preserve that API
+ # while giving us something saner.
+ my $args;
+ if ($args{'ARGSRef'}) {
+ RT->Deprecated( Arguments => "ARGSRef", Remove => "4.4" );
+ $args = $args{ARGSRef};
+ } else {
+ $args = \%args;
+ }
+
+ foreach my $arg ( keys %$args ) {
+ next
+ unless ( $arg =~
+ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ );
+ next if $arg =~ /-Magic$/;
+ next if $arg =~ /-TimeUnits$/;
+ my $cfid = $1;
+ my $values = $args->{$arg};
+ my $cf = $self->LoadCustomFieldByIdentifier($cfid);
+ next unless $cf->ObjectTypeFromLookupType($cf->__Value('LookupType'))->isa(ref $self);
+ foreach
+ my $value ( UNIVERSAL::isa( $values, 'ARRAY' ) ? @$values : $values )
+ {
+ next unless (defined($value) && length($value));
+ $self->_AddCustomFieldValue(
+ Field => $cfid,
+ Value => $value,
+ RecordTransaction => 0,
+ );
+ }
+ }
+}
+
+=head2 LoadCustomFieldByIdentifier
+
+Finds and returns the custom field of the given name for the
+transaction, overriding L<RT::Record/LoadCustomFieldByIdentifier> to
+look for queue-specific CFs before global ones.
+
+=cut
+
+sub LoadCustomFieldByIdentifier {
+ my $self = shift;
+ my $field = shift;
+
+ return $self->SUPER::LoadCustomFieldByIdentifier($field)
+ if ref $field or $field =~ /^\d+$/;
+
+ return $self->SUPER::LoadCustomFieldByIdentifier($field)
+ unless UNIVERSAL::can( $self->Object, 'QueueObj' );
+
+ my $CFs = RT::CustomFields->new( $self->CurrentUser );
+ $CFs->SetContextObject( $self->Object );
+ $CFs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 );
+ $CFs->LimitToLookupType($self->CustomFieldLookupType);
+ $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
+ return $CFs->First || RT::CustomField->new( $self->CurrentUser );
+}
+
+=head2 CustomFieldLookupType
+
+Returns the RT::Transaction lookup type, which can
+be passed to RT::CustomField->Create() via the 'LookupType' hash key.
+
+=cut
+
+
+sub CustomFieldLookupType {
+ "RT::Queue-RT::Ticket-RT::Transaction";
+}
+
+
+=head2 SquelchMailTo
+
+Similar to Ticket class SquelchMailTo method - returns a list of
+transaction's squelched addresses. As transactions are immutable, the
+list of squelched recipients cannot be modified after creation.
+
+=cut
+
+sub SquelchMailTo {
+ my $self = shift;
+ return () unless $self->CurrentUserCanSee;
+ return $self->Attributes->Named('SquelchMailTo');
+}
+
+=head2 Recipients
+
+Returns the list of email addresses (as L<Email::Address> objects)
+that this transaction would send mail to. There may be duplicates.
+
+=cut
+
+sub Recipients {
+ my $self = shift;
+ my @recipients;
+ foreach my $scrip ( @{ $self->Scrips->Prepared } ) {
+ my $action = $scrip->ActionObj->Action;
+ next unless $action->isa('RT::Action::SendEmail');
+
+ foreach my $type (qw(To Cc Bcc)) {
+ push @recipients, $action->$type();
+ }
+ }
+
+ if ( $self->Rules ) {
+ for my $rule (@{$self->Rules}) {
+ next unless $rule->{hints} && $rule->{hints}{class} eq 'SendEmail';
+ my $data = $rule->{hints}{recipients};
+ foreach my $type (qw(To Cc Bcc)) {
+ push @recipients, map {Email::Address->new($_)} @{$data->{$type}};
+ }
+ }
+ }
+ return @recipients;
+}
+
+=head2 DeferredRecipients($freq, $include_sent )
+
+Takes the following arguments:
+
+=over
+
+=item * a string to indicate the frequency of digest delivery. Valid values are "daily", "weekly", or "susp".
+
+=item * an optional argument which, if true, will return addresses even if this notification has been marked as 'sent' for this transaction.
+
+=back
+
+Returns an array of users who should now receive the notification that
+was recorded in this transaction. Returns an empty array if there were
+no deferred users, or if $include_sent was not specified and the deferred
+notifications have been sent.
+
+=cut
+
+sub DeferredRecipients {
+ my $self = shift;
+ my $freq = shift;
+ my $include_sent = @_? shift : 0;
+
+ my $attr = $self->FirstAttribute('DeferredRecipients');
+
+ return () unless ($attr);
+
+ my $deferred = $attr->Content;
+
+ return () unless ( ref($deferred) eq 'HASH' && exists $deferred->{$freq} );
+
+ # Skip it.
+
+ for my $user (keys %{$deferred->{$freq}}) {
+ if ($deferred->{$freq}->{$user}->{_sent} && !$include_sent) {
+ delete $deferred->{$freq}->{$user}
+ }
+ }
+ # Now get our users. Easy.
+
+ return keys %{ $deferred->{$freq} };
+}
+
+
+
+# Transactions don't change. by adding this cache config directive, we don't lose pathalogically on long tickets.
+sub _CacheConfig {
+ {
+ 'cache_for_sec' => 6000,
+ }
+}
+
+
+=head2 ACLEquivalenceObjects
+
+This method returns a list of objects for which a user's rights also apply
+to this Transaction.
+
+This currently only applies to Transaction Custom Fields on Tickets, so we return
+the Ticket's Queue and the Ticket.
+
+This method is called from L<RT::Principal/HasRight>.
+
+=cut
+
+sub ACLEquivalenceObjects {
+ my $self = shift;
+
+ return unless $self->ObjectType eq 'RT::Ticket';
+ my $object = $self->Object;
+ return $object,$object->QueueObj;
+
+}
+
+
+
+
+
+=head2 id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=head2 ObjectType
+
+Returns the current value of ObjectType.
+(In the database, ObjectType is stored as varchar(64).)
+
+
+
+=head2 SetObjectType VALUE
+
+
+Set ObjectType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectType will be stored as a varchar(64).)
+
+
+=cut
+
+
+=head2 ObjectId
+
+Returns the current value of ObjectId.
+(In the database, ObjectId is stored as int(11).)
+
+
+
+=head2 SetObjectId VALUE
+
+
+Set ObjectId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectId will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 TimeTaken
+
+Returns the current value of TimeTaken.
+(In the database, TimeTaken is stored as int(11).)
+
+
+
+=head2 SetTimeTaken VALUE
+
+
+Set TimeTaken to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, TimeTaken will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 Type
+
+Returns the current value of Type.
+(In the database, Type is stored as varchar(20).)
+
+
+
+=head2 SetType VALUE
+
+
+Set Type to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Type will be stored as a varchar(20).)
+
+
+=cut
+
+
+=head2 Field
+
+Returns the current value of Field.
+(In the database, Field is stored as varchar(40).)
+
+
+
+=head2 SetField VALUE
+
+
+Set Field to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Field will be stored as a varchar(40).)
+
+
+=cut
+
+
+=head2 OldValue
+
+Returns the current value of OldValue.
+(In the database, OldValue is stored as varchar(255).)
+
+
+
+=head2 SetOldValue VALUE
+
+
+Set OldValue to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, OldValue will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 NewValue
+
+Returns the current value of NewValue.
+(In the database, NewValue is stored as varchar(255).)
+
+
+
+=head2 SetNewValue VALUE
+
+
+Set NewValue to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, NewValue will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 ReferenceType
+
+Returns the current value of ReferenceType.
+(In the database, ReferenceType is stored as varchar(255).)
+
+
+
+=head2 SetReferenceType VALUE
+
+
+Set ReferenceType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ReferenceType will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 OldReference
+
+Returns the current value of OldReference.
+(In the database, OldReference is stored as int(11).)
+
+
+
+=head2 SetOldReference VALUE
+
+
+Set OldReference to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, OldReference will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 NewReference
+
+Returns the current value of NewReference.
+(In the database, NewReference is stored as int(11).)
+
+
+
+=head2 SetNewReference VALUE
+
+
+Set NewReference to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, NewReference will be stored as a int(11).)
+
+
+=cut
+
+
+=head2 Data
+
+Returns the current value of Data.
+(In the database, Data is stored as varchar(255).)
+
+
+
+=head2 SetData VALUE
+
+
+Set Data to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Data will be stored as a varchar(255).)
+
+
+=cut
+
+
+=head2 Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=head2 Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+
+sub _CoreAccessible {
+ {
+
+ id =>
+ {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
+ ObjectType =>
+ {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
+ ObjectId =>
+ {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
+ TimeTaken =>
+ {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
+ Type =>
+ {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''},
+ Field =>
+ {read => 1, write => 1, sql_type => 12, length => 40, is_blob => 0, is_numeric => 0, type => 'varchar(40)', default => ''},
+ OldValue =>
+ {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
+ NewValue =>
+ {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
+ ReferenceType =>
+ {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
+ OldReference =>
+ {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
+ NewReference =>
+ {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
+ Data =>
+ {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
+ Creator =>
+ {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
+
+ }
+};
+
+sub FindDependencies {
+ my $self = shift;
+ my ($walker, $deps) = @_;
+
+ $self->SUPER::FindDependencies($walker, $deps);
+
+ $deps->Add( out => $self->Object );
+ $deps->Add( in => $self->Attachments );
+
+ my $type = $self->Type;
+ if ($type eq "CustomField") {
+ my $cf = RT::CustomField->new( RT->SystemUser );
+ $cf->Load( $self->Field );
+ $deps->Add( out => $cf );
+ } elsif ($type =~ /^(Take|Untake|Force|Steal|Give)$/) {
+ for my $field (qw/OldValue NewValue/) {
+ my $user = RT::User->new( RT->SystemUser );
+ $user->Load( $self->$field );
+ $deps->Add( out => $user );
+ }
+ } elsif ($type eq "DelWatcher") {
+ my $principal = RT::Principal->new( RT->SystemUser );
+ $principal->Load( $self->OldValue );
+ $deps->Add( out => $principal->Object );
+ } elsif ($type eq "AddWatcher") {
+ my $principal = RT::Principal->new( RT->SystemUser );
+ $principal->Load( $self->NewValue );
+ $deps->Add( out => $principal->Object );
+ } elsif ($type eq "DeleteLink") {
+ if ($self->OldValue) {
+ my $base = RT::URI->new( $self->CurrentUser );
+ $base->FromURI( $self->OldValue );
+ $deps->Add( out => $base->Object ) if $base->Resolver and $base->Object;
+ }
+ } elsif ($type eq "AddLink") {
+ if ($self->NewValue) {
+ my $base = RT::URI->new( $self->CurrentUser );
+ $base->FromURI( $self->NewValue );
+ $deps->Add( out => $base->Object ) if $base->Resolver and $base->Object;
+ }
+ } elsif ($type eq "Set" and $self->Field eq "Queue") {
+ for my $field (qw/OldValue NewValue/) {
+ my $queue = RT::Queue->new( RT->SystemUser );
+ $queue->Load( $self->$field );
+ $deps->Add( out => $queue );
+ }
+ } elsif ($type =~ /^(Add|Open|Resolve)Reminder$/) {
+ my $ticket = RT::Ticket->new( RT->SystemUser );
+ $ticket->Load( $self->NewValue );
+ $deps->Add( out => $ticket );
+ }
+}
+
+sub __DependsOn {
+ my $self = shift;
+ my %args = (
+ Shredder => undef,
+ Dependencies => undef,
+ @_,
+ );
+ my $deps = $args{'Dependencies'};
+
+ $deps->_PushDependencies(
+ BaseObject => $self,
+ Flags => RT::Shredder::Constants::DEPENDS_ON,
+ TargetObjects => $self->Attachments,
+ Shredder => $args{'Shredder'}
+ );
+
+ return $self->SUPER::__DependsOn( %args );
+}
+
+sub Serialize {
+ my $self = shift;
+ my %args = (@_);
+ my %store = $self->SUPER::Serialize(@_);
+
+ my $type = $store{Type};
+ if ($type eq "CustomField") {
+ my $cf = RT::CustomField->new( RT->SystemUser );
+ $cf->Load( $store{Field} );
+ $store{Field} = \($cf->UID);
+
+ $store{OldReference} = \($self->OldReferenceObject->UID) if $self->OldReference;
+ $store{NewReference} = \($self->NewReferenceObject->UID) if $self->NewReference;
+ } elsif ($type =~ /^(Take|Untake|Force|Steal|Give)$/) {
+ for my $field (qw/OldValue NewValue/) {
+ my $user = RT::User->new( RT->SystemUser );
+ $user->Load( $store{$field} );
+ $store{$field} = \($user->UID);
+ }
+ } elsif ($type eq "DelWatcher") {
+ my $principal = RT::Principal->new( RT->SystemUser );
+ $principal->Load( $store{OldValue} );
+ $store{OldValue} = \($principal->UID);
+ } elsif ($type eq "AddWatcher") {
+ my $principal = RT::Principal->new( RT->SystemUser );
+ $principal->Load( $store{NewValue} );
+ $store{NewValue} = \($principal->UID);
+ } elsif ($type eq "DeleteLink") {
+ if ($store{OldValue}) {
+ my $base = RT::URI->new( $self->CurrentUser );
+ $base->FromURI( $store{OldValue} );
+ if ($base->Resolver && (my $object = $base->Object)) {
+ if ($args{serializer}->Observe(object => $object)) {
+ $store{OldValue} = \($object->UID);
+ }
+ elsif ($args{serializer}{HyperlinkUnmigrated}) {
+ $store{OldValue} = $base->AsHREF;
+ }
+ else {
+ $store{OldValue} = "(not migrated)";
+ }
+ }
+ }
+ } elsif ($type eq "AddLink") {
+ if ($store{NewValue}) {
+ my $base = RT::URI->new( $self->CurrentUser );
+ $base->FromURI( $store{NewValue} );
+ if ($base->Resolver && (my $object = $base->Object)) {
+ if ($args{serializer}->Observe(object => $object)) {
+ $store{NewValue} = \($object->UID);
+ }
+ elsif ($args{serializer}{HyperlinkUnmigrated}) {
+ $store{NewValue} = $base->AsHREF;
+ }
+ else {
+ $store{NewValue} = "(not migrated)";
+ }
+ }
+ }
+ } elsif ($type eq "Set" and $store{Field} eq "Queue") {
+ for my $field (qw/OldValue NewValue/) {
+ my $queue = RT::Queue->new( RT->SystemUser );
+ $queue->Load( $store{$field} );
+ if ($args{serializer}->Observe(object => $queue)) {
+ $store{$field} = \($queue->UID);
+ }
+ else {
+ $store{$field} = "$RT::Organization: " . $queue->Name . " (not migrated)";
+
+ }
+ }
+ } elsif ($type =~ /^(Add|Open|Resolve)Reminder$/) {
+ my $ticket = RT::Ticket->new( RT->SystemUser );
+ $ticket->Load( $store{NewValue} );
+ $store{NewValue} = \($ticket->UID);
+ }
+
+ return %store;
+}
+
+sub PreInflate {
+ my $class = shift;
+ my ($importer, $uid, $data) = @_;
+
+ if ($data->{Object} and ref $data->{Object}) {
+ my $on_uid = ${ $data->{Object} };
+ return if $importer->ShouldSkipTransaction($on_uid);
+ }
+
+ if ($data->{Type} eq "DeleteLink" and ref $data->{OldValue}) {
+ my $uid = ${ $data->{OldValue} };
+ my $obj = $importer->LookupObj( $uid );
+ $data->{OldValue} = $obj->URI;
+ } elsif ($data->{Type} eq "AddLink" and ref $data->{NewValue}) {
+ my $uid = ${ $data->{NewValue} };
+ my $obj = $importer->LookupObj( $uid );
+ $data->{NewValue} = $obj->URI;
+ }
+
+ return $class->SUPER::PreInflate( $importer, $uid, $data );