+ my %args = ( Type => $PreferredContentType, Attachment => undef, @_ );
+
+ # If we don't have any content, return undef now.
+ # Get the set of toplevel attachments to this transaction.
+
+ my $Attachment = $args{'Attachment'};
+
+ $Attachment ||= $self->Attachments->First;
+
+ return undef unless ($Attachment);
+
+ # If it's a textual part, just return the body.
+ if ( RT::I18N::IsTextualContentType($Attachment->ContentType) ) {
+ return ($Attachment);
+ }
+
+ # If it's a multipart object, first try returning the first part with preferred
+ # MIME type ('text/plain' by default).
+
+ elsif ( $Attachment->ContentType =~ m|^multipart/mixed|i ) {
+ my $kids = $Attachment->Children;
+ while (my $child = $kids->Next) {
+ my $ret = $self->ContentObj(%args, Attachment => $child);
+ return $ret if ($ret);
+ }
+ }
+ elsif ( $Attachment->ContentType =~ m|^multipart/|i ) {
+ if ( $args{Type} ) {
+ my $plain_parts = $Attachment->Children;
+ $plain_parts->ContentType( VALUE => $args{Type} );
+ $plain_parts->LimitNotEmpty;
+
+ # If we actully found a part, return its content
+ if ( my $first = $plain_parts->First ) {
+ return $first;
+ }
+ }
+
+ # If that fails, return the first textual part which has some content.
+ my $all_parts = $self->Attachments;
+ while ( my $part = $all_parts->Next ) {
+ next unless RT::I18N::IsTextualContentType($part->ContentType)
+ && $part->Content;
+ return $part;
+ }
+ }
+
+ # We found no content. suck
+ return (undef);
+}
+
+
+
+=head2 Subject
+
+If this transaction has attached mime objects, returns the first one's subject
+Otherwise, returns null
+
+=cut
+
+sub Subject {
+ my $self = shift;
+ return undef unless my $first = $self->Attachments->First;
+ return $first->Subject;
+}
+
+
+
+=head2 Attachments
+
+Returns all the RT::Attachment objects which are attached
+to this transaction. Takes an optional parameter, which is
+a ContentType that Attachments should be restricted to.
+
+=cut
+
+sub Attachments {
+ my $self = shift;
+
+ if ( $self->{'attachments'} ) {
+ $self->{'attachments'}->GotoFirstItem;
+ return $self->{'attachments'};
+ }
+
+ $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser );
+
+ unless ( $self->CurrentUserCanSee ) {
+ $self->{'attachments'}->Limit(FIELD => 'id', VALUE => '0', SUBCLAUSE => 'acl');
+ return $self->{'attachments'};
+ }
+
+ $self->{'attachments'}->Limit( FIELD => 'TransactionId', VALUE => $self->Id );
+
+ # Get the self->{'attachments'} in the order they're put into
+ # the database. Arguably, we should be returning a tree
+ # of self->{'attachments'}, not a set...but no current app seems to need
+ # it.
+
+ $self->{'attachments'}->OrderBy( FIELD => 'id', ORDER => 'ASC' );
+
+ return $self->{'attachments'};
+}
+
+
+
+=head2 _Attach
+
+A private method used to attach a mime object to this transaction.
+
+=cut
+
+sub _Attach {
+ my $self = shift;
+ my $MIMEObject = shift;
+
+ unless ( defined $MIMEObject ) {
+ $RT::Logger->error("We can't attach a mime object if you don't give us one.");
+ return ( 0, $self->loc("[_1]: no attachment specified", $self) );
+ }
+
+ my $Attachment = RT::Attachment->new( $self->CurrentUser );
+ my ($id, $msg) = $Attachment->Create(
+ TransactionId => $self->Id,
+ Attachment => $MIMEObject
+ );
+ return ( $Attachment, $msg || $self->loc("Attachment created") );
+}
+
+
+
+sub ContentAsMIME {
+ my $self = shift;
+
+ # RT::Attachments doesn't limit ACLs as strictly as RT::Transaction does
+ # since it has less information available without looking to it's parent
+ # transaction. Check ACLs here before we go any further.
+ return unless $self->CurrentUserCanSee;
+
+ my $attachments = RT::Attachments->new( $self->CurrentUser );
+ $attachments->OrderBy( FIELD => 'id', ORDER => 'ASC' );
+ $attachments->Limit( FIELD => 'TransactionId', VALUE => $self->id );
+ $attachments->Limit( FIELD => 'Parent', VALUE => 0 );
+ $attachments->RowsPerPage(1);
+
+ my $top = $attachments->First;
+ return unless $top;
+
+ my $entity = MIME::Entity->build(
+ Type => 'message/rfc822',
+ Description => 'transaction ' . $self->id,
+ Data => $top->ContentAsMIME(Children => 1)->as_string,
+ );
+
+ return $entity;
+}
+
+
+
+=head2 Description
+
+Returns a text string which describes this transaction
+
+=cut
+
+sub Description {
+ my $self = shift;
+
+ unless ( $self->CurrentUserCanSee ) {
+ return ( $self->loc("Permission Denied") );
+ }
+
+ unless ( defined $self->Type ) {
+ return ( $self->loc("No transaction type specified"));
+ }
+
+ return $self->loc("[_1] by [_2]", $self->BriefDescription , $self->CreatorObj->Name );
+}
+
+
+
+=head2 BriefDescription
+
+Returns a text string which briefly describes this transaction
+
+=cut
+
+sub BriefDescription {
+ my $self = shift;
+
+ unless ( $self->CurrentUserCanSee ) {
+ return ( $self->loc("Permission Denied") );
+ }
+
+ my $type = $self->Type; #cache this, rather than calling it 30 times
+
+ unless ( defined $type ) {
+ return $self->loc("No transaction type specified");
+ }
+
+ my $obj_type = $self->FriendlyObjectType;
+
+ if ( $type eq 'Create' ) {
+ return ( $self->loc( "[_1] created", $obj_type ) );
+ }
+ elsif ( $type eq 'Enabled' ) {
+ return ( $self->loc( "[_1] enabled", $obj_type ) );
+ }
+ elsif ( $type eq 'Disabled' ) {
+ return ( $self->loc( "[_1] disabled", $obj_type ) );
+ }
+ elsif ( $type =~ /Status/ ) {
+ if ( $self->Field eq 'Status' ) {
+ if ( $self->NewValue eq 'deleted' ) {
+ return ( $self->loc( "[_1] deleted", $obj_type ) );
+ }
+ else {
+ my $canon = $self->Object->can("QueueObj")
+ ? sub { $self->Object->QueueObj->Lifecycle->CanonicalCase(@_) }
+ : sub { return $_[0] };
+ return (
+ $self->loc(
+ "Status changed from [_1] to [_2]",
+ "'" . $self->loc( $canon->($self->OldValue) ) . "'",
+ "'" . $self->loc( $canon->($self->NewValue) ) . "'"
+ )
+ );
+
+ }
+ }
+
+ # Generic:
+ my $no_value = $self->loc("(no value)");
+ return (
+ $self->loc(
+ "[_1] changed from [_2] to [_3]",
+ $self->Field,
+ ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ),
+ "'" . $self->NewValue . "'"
+ )
+ );
+ }
+ elsif ( $type =~ /SystemError/ ) {
+ return $self->loc("System error");
+ }
+ elsif ( $type =~ /Forward Transaction/ ) {
+ return $self->loc( "Forwarded Transaction #[_1] to [_2]",
+ $self->Field, $self->Data );
+ }
+ elsif ( $type =~ /Forward Ticket/ ) {
+ return $self->loc( "Forwarded Ticket to [_1]", $self->Data );
+ }
+
+ if ( my $code = $_BriefDescriptions{$type} ) {
+ return $code->($self);
+ }
+
+ return $self->loc(
+ "Default: [_1]/[_2] changed from [_3] to [_4]",
+ $type,
+ $self->Field,
+ (
+ $self->OldValue
+ ? "'" . $self->OldValue . "'"
+ : $self->loc("(no value)")
+ ),
+ "'" . $self->NewValue . "'"
+ );
+}
+
+%_BriefDescriptions = (
+ CommentEmailRecord => sub {
+ my $self = shift;
+ return $self->loc("Outgoing email about a comment recorded");
+ },
+ EmailRecord => sub {
+ my $self = shift;
+ return $self->loc("Outgoing email recorded");
+ },
+ Correspond => sub {
+ my $self = shift;
+ return $self->loc("Correspondence added");
+ },
+ Comment => sub {
+ my $self = shift;
+ return $self->loc("Comments added");
+ },
+ CustomField => sub {
+ my $self = shift;
+ my $field = $self->loc('CustomField');
+
+ if ( $self->Field ) {
+ my $cf = RT::CustomField->new( $self->CurrentUser );
+ $cf->SetContextObject( $self->Object );
+ $cf->Load( $self->Field );
+ $field = $cf->Name();
+ $field = $self->loc('a custom field') if !defined($field);
+ }
+
+ my $new = $self->NewValue;
+ my $old = $self->OldValue;
+
+ if ( !defined($old) || $old eq '' ) {
+ return $self->loc("[_1] [_2] added", $field, $new);
+ }
+ elsif ( !defined($new) || $new eq '' ) {
+ return $self->loc("[_1] [_2] deleted", $field, $old);
+ }
+ else {
+ return $self->loc("[_1] [_2] changed to [_3]", $field, $old, $new);
+ }
+ },
+ Untake => sub {
+ my $self = shift;
+ return $self->loc("Untaken");
+ },
+ Take => sub {
+ my $self = shift;
+ return $self->loc("Taken");
+ },
+ Force => sub {
+ my $self = shift;
+ my $Old = RT::User->new( $self->CurrentUser );
+ $Old->Load( $self->OldValue );
+ my $New = RT::User->new( $self->CurrentUser );
+ $New->Load( $self->NewValue );
+
+ return $self->loc("Owner forcibly changed from [_1] to [_2]" , $Old->Name , $New->Name);
+ },
+ Steal => sub {
+ my $self = shift;
+ my $Old = RT::User->new( $self->CurrentUser );
+ $Old->Load( $self->OldValue );
+ return $self->loc("Stolen from [_1]", $Old->Name);
+ },
+ Give => sub {
+ my $self = shift;
+ my $New = RT::User->new( $self->CurrentUser );
+ $New->Load( $self->NewValue );
+ return $self->loc( "Given to [_1]", $New->Name );
+ },
+ AddWatcher => sub {
+ my $self = shift;
+ my $principal = RT::Principal->new($self->CurrentUser);
+ $principal->Load($self->NewValue);
+ return $self->loc( "[_1] [_2] added", $self->Field, $principal->Object->Name);
+ },
+ DelWatcher => sub {
+ my $self = shift;
+ my $principal = RT::Principal->new($self->CurrentUser);
+ $principal->Load($self->OldValue);
+ return $self->loc( "[_1] [_2] deleted", $self->Field, $principal->Object->Name);
+ },
+ Subject => sub {
+ my $self = shift;
+ return $self->loc( "Subject changed to [_1]", $self->Data );
+ },
+ AddLink => sub {
+ my $self = shift;
+ my $value;
+ if ( $self->NewValue ) {
+ my $URI = RT::URI->new( $self->CurrentUser );
+ if ( $URI->FromURI( $self->NewValue ) ) {
+ $value = $URI->Resolver->AsString;
+ }
+ else {
+ $value = $self->NewValue;
+ }
+ if ( $self->Field eq 'DependsOn' ) {
+ return $self->loc( "Dependency on [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'DependedOnBy' ) {
+ return $self->loc( "Dependency by [_1] added", $value );
+
+ }
+ elsif ( $self->Field eq 'RefersTo' ) {
+ return $self->loc( "Reference to [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'ReferredToBy' ) {
+ return $self->loc( "Reference by [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'MemberOf' ) {
+ return $self->loc( "Membership in [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'HasMember' ) {
+ return $self->loc( "Member [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'MergedInto' ) {
+ return $self->loc( "Merged into [_1]", $value );
+ }
+ }
+ else {
+ return ( $self->Data );
+ }
+ },
+ DeleteLink => sub {
+ my $self = shift;
+ my $value;
+ if ( $self->OldValue ) {
+ my $URI = RT::URI->new( $self->CurrentUser );
+ if ( $URI->FromURI( $self->OldValue ) ){
+ $value = $URI->Resolver->AsString;
+ }
+ else {
+ $value = $self->OldValue;
+ }
+
+ if ( $self->Field eq 'DependsOn' ) {
+ return $self->loc( "Dependency on [_1] deleted", $value );
+ }
+ elsif ( $self->Field eq 'DependedOnBy' ) {
+ return $self->loc( "Dependency by [_1] deleted", $value );
+
+ }
+ elsif ( $self->Field eq 'RefersTo' ) {
+ return $self->loc( "Reference to [_1] deleted", $value );
+ }
+ elsif ( $self->Field eq 'ReferredToBy' ) {
+ return $self->loc( "Reference by [_1] deleted", $value );
+ }
+ elsif ( $self->Field eq 'MemberOf' ) {
+ return $self->loc( "Membership in [_1] deleted", $value );
+ }
+ elsif ( $self->Field eq 'HasMember' ) {
+ return $self->loc( "Member [_1] deleted", $value );
+ }
+ }
+ else {
+ return ( $self->Data );
+ }
+ },
+ Told => sub {
+ my $self = shift;
+ if ( $self->Field eq 'Told' ) {
+ my $t1 = RT::Date->new($self->CurrentUser);
+ $t1->Set(Format => 'ISO', Value => $self->NewValue);
+ my $t2 = RT::Date->new($self->CurrentUser);
+ $t2->Set(Format => 'ISO', Value => $self->OldValue);
+ return $self->loc( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString );
+ }
+ else {
+ return $self->loc( "[_1] changed from [_2] to [_3]",
+ $self->loc($self->Field),
+ ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" );
+ }
+ },
+ Set => sub {
+ my $self = shift;
+ if ( $self->Field eq 'Password' ) {
+ return $self->loc('Password changed');
+ }
+ elsif ( $self->Field eq 'Queue' ) {
+ my $q1 = RT::Queue->new( $self->CurrentUser );
+ $q1->Load( $self->OldValue );
+ my $q2 = RT::Queue->new( $self->CurrentUser );
+ $q2->Load( $self->NewValue );
+ return $self->loc("[_1] changed from [_2] to [_3]",
+ $self->loc($self->Field) , $q1->Name , $q2->Name);
+ }
+
+ # Write the date/time change at local time:
+ elsif ($self->Field =~ /Due|Starts|Started|Told|WillResolve/) {
+ my $t1 = RT::Date->new($self->CurrentUser);
+ $t1->Set(Format => 'ISO', Value => $self->NewValue);
+ my $t2 = RT::Date->new($self->CurrentUser);
+ $t2->Set(Format => 'ISO', Value => $self->OldValue);
+ return $self->loc( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString );
+ }
+ elsif ( $self->Field eq 'Owner' ) {
+ my $Old = RT::User->new( $self->CurrentUser );
+ $Old->Load( $self->OldValue );
+ my $New = RT::User->new( $self->CurrentUser );
+ $New->Load( $self->NewValue );
+
+ if ( $Old->id == RT->Nobody->id ) {
+ if ( $New->id == $self->Creator ) {
+ return $self->loc("Taken");
+ }
+ else {
+ return $self->loc( "Given to [_1]", $New->Name );
+ }
+ }
+ else {
+ if ( $New->id == $self->Creator ) {
+ return $self->loc("Stolen from [_1]", $Old->Name);
+ }
+ elsif ( $Old->id == $self->Creator ) {
+ if ( $New->id == RT->Nobody->id ) {
+ return $self->loc("Untaken");
+ }
+ else {
+ return $self->loc( "Given to [_1]", $New->Name );
+ }
+ }
+ else {
+ return $self->loc(
+ "Owner forcibly changed from [_1] to [_2]",
+ $Old->Name, $New->Name );
+ }
+ }
+ }
+ else {
+ return $self->loc( "[_1] changed from [_2] to [_3]",
+ $self->loc($self->Field),
+ ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" );
+ }
+ },
+ PurgeTransaction => sub {
+ my $self = shift;
+ return $self->loc("Transaction [_1] purged", $self->Data);
+ },
+ AddReminder => sub {
+ my $self = shift;
+ my $ticket = RT::Ticket->new($self->CurrentUser);
+ $ticket->Load($self->NewValue);
+ return $self->loc("Reminder '[_1]' added", $ticket->Subject);
+ },
+ OpenReminder => sub {
+ my $self = shift;
+ my $ticket = RT::Ticket->new($self->CurrentUser);
+ $ticket->Load($self->NewValue);
+ return $self->loc("Reminder '[_1]' reopened", $ticket->Subject);
+
+ },
+ ResolveReminder => sub {
+ my $self = shift;
+ my $ticket = RT::Ticket->new($self->CurrentUser);
+ $ticket->Load($self->NewValue);
+ return $self->loc("Reminder '[_1]' completed", $ticket->Subject);
+
+
+ }