#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
=head1 NAME
- RT::Transaction - RT\'s transaction object
+ RT::Transaction - RT's transaction object
=head1 SYNOPSIS
return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify an object type and id"));
}
-
- # Set up any custom fields passed at creation. Has to happen
- # before scrips.
-
- $self->UpdateCustomFields(%{ $args{'CustomFields'} });
-
#lets create our transaction
my %params = (
Type => $args{'Type'},
}
}
+ # Set up any custom fields passed at creation. Has to happen
+ # before scrips.
+
+ $self->UpdateCustomFields(%{ $args{'CustomFields'} });
+
$self->AddAttribute(
Name => 'SquelchMailTo',
Content => RT::User->CanonicalizeEmailAddress($_)
}
if ( $args{'Quote'} ) {
+ $content = $self->ApplyQuoteWrap(content => $content,
+ cols => $args{'Wrap'} );
- # What's the longest line like?
- my $max = 0;
- foreach ( split ( /\n/, $content ) ) {
- $max = length if length > $max;
- }
+ $content = $self->QuoteHeader . "\n$content\n\n";
+ }
- if ( $max > $args{'Wrap'}+6 ) { # 76 ) {
- require Text::Wrapper;
- my $wrapper = Text::Wrapper->new(
- columns => $args{'Wrap'},
- body_start => ( $max > 70 * 3 ? ' ' : '' ),
- par_start => ''
- );
- $content = $wrapper->wrap($content);
- }
+ return ($content);
+}
+
+=head2 QuoteHeader
- $content =~ s/^/> /gm;
- $content = $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString, $self->CreatorObj->Name)
- . "\n$content\n\n";
+Returns text prepended to content when transaction is quoted
+(see C<Quote> argument in L</Content>). By default returns
+localized "On <date> <user name> wrote:\n".
+
+=cut
+
+sub QuoteHeader {
+ my $self = shift;
+ return $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString, $self->CreatorObj->Name);
+}
+
+=head2 ApplyQuoteWrap PARAMHASH
+
+Wrapper to calculate wrap criteria and apply quote wrapping if needed.
+
+=cut
+
+sub ApplyQuoteWrap {
+ my $self = shift;
+ my %args = @_;
+ my $content = $args{content};
+
+ # What's the longest line like?
+ my $max = 0;
+ foreach ( split ( /\n/, $args{content} ) ) {
+ $max = length if length > $max;
}
- return ($content);
+ if ( $max > 76 ) {
+ require Text::Quoted;
+ require Text::Wrapper;
+
+ my $structure = Text::Quoted::extract($args{content});
+ $content = $self->QuoteWrap(content_ref => $structure,
+ cols => $args{cols},
+ max => $max );
+ }
+
+ $content =~ s/^/> /gm; # use regex since string might be multi-line
+ return $content;
}
+=head2 QuoteWrap PARAMHASH
+
+Wrap the contents of transactions based on Wrap settings, maintaining
+the quote character from the original.
+
+=cut
+
+sub QuoteWrap {
+ my $self = shift;
+ my %args = @_;
+ my $ref = $args{content_ref};
+ my $final_string;
+
+ if ( ref $ref eq 'ARRAY' ){
+ foreach my $array (@$ref){
+ $final_string .= $self->QuoteWrap(content_ref => $array,
+ cols => $args{cols},
+ max => $args{max} );
+ }
+ }
+ elsif ( ref $ref eq 'HASH' ){
+ return $ref->{quoter} . "\n" if $ref->{empty}; # Blank line
+
+ my $col = $args{cols} - (length $ref->{quoter});
+ my $wrapper = Text::Wrapper->new( columns => $col );
+
+ # Wrap on individual lines to honor incoming line breaks
+ # Otherwise deliberate separate lines (like a list or a sig)
+ # all get combined incorrectly into single paragraphs.
+
+ my @lines = split /\n/, $ref->{text};
+ my $wrap = join '', map { $wrapper->wrap($_) } @lines;
+ my $quoter = $ref->{quoter};
+
+ # Only add the space if actually quoting
+ $quoter .= ' ' if length $quoter;
+ $wrap =~ s/^/$quoter/mg; # use regex since string might be multi-line
+
+ return $wrap;
+ }
+ else{
+ $RT::Logger->warning("Can't apply quoting with $ref");
+ return;
+ }
+ return $final_string;
+}
=head2 Addresses
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( $self->OldValue ) . "'",
- "'" . $self->loc( $self->NewValue ) . "'"
+ "'" . $self->loc( $canon->($self->OldValue) ) . "'",
+ "'" . $self->loc( $canon->($self->NewValue) ) . "'"
)
);
my $self = shift;
my $field = $self->loc('CustomField');
+ my $cf;
if ( $self->Field ) {
- my $cf = RT::CustomField->new( $self->CurrentUser );
+ $cf = RT::CustomField->new( $self->CurrentUser );
$cf->SetContextObject( $self->Object );
$cf->Load( $self->Field );
$field = $cf->Name();
my $new = $self->NewValue;
my $old = $self->OldValue;
+ if ( $cf ) {
+
+ if ( $cf->Type eq 'DateTime' ) {
+ if ($old) {
+ my $date = RT::Date->new( $self->CurrentUser );
+ $date->Set( Format => 'ISO', Value => $old );
+ $old = $date->AsString;
+ }
+
+ if ($new) {
+ my $date = RT::Date->new( $self->CurrentUser );
+ $date->Set( Format => 'ISO', Value => $new );
+ $new = $date->AsString;
+ }
+ }
+ elsif ( $cf->Type eq 'Date' ) {
+ if ($old) {
+ my $date = RT::Date->new( $self->CurrentUser );
+ $date->Set(
+ Format => 'unknown',
+ Value => $old,
+ Timezone => 'UTC',
+ );
+ $old = $date->AsString( Time => 0, Timezone => 'UTC' );
+ }
+
+ if ($new) {
+ my $date = RT::Date->new( $self->CurrentUser );
+ $date->Set(
+ Format => 'unknown',
+ Value => $new,
+ Timezone => 'UTC',
+ );
+ $new = $date->AsString( Time => 0, Timezone => 'UTC' );
+ }
+ }
+ }
+
if ( !defined($old) || $old eq '' ) {
return $self->loc("[_1] [_2] added", $field, $new);
}
my $value;
if ( $self->NewValue ) {
my $URI = RT::URI->new( $self->CurrentUser );
- $URI->FromURI( $self->NewValue );
- if ( $URI->Resolver ) {
+ if ( $URI->FromURI( $self->NewValue ) ) {
$value = $URI->Resolver->AsString;
}
else {
my $value;
if ( $self->OldValue ) {
my $URI = RT::URI->new( $self->CurrentUser );
- $URI->FromURI( $self->OldValue );
- if ( $URI->Resolver ) {
+ if ( $URI->FromURI( $self->OldValue ) ){
$value = $URI->Resolver->AsString;
}
else {
else {
return $self->loc( "[_1] changed from [_2] to [_3]",
$self->loc($self->Field),
- ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" );
+ ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")),
+ ($self->NewValue? "'".$self->NewValue ."'" : $self->loc("(no value)")));
}
},
PurgeTransaction => sub {
$cf->Load( $cf_id );
return 0 unless $cf->CurrentUserHasRight('SeeCustomField');
}
+
+ # Transactions that might have changed the ->Object's visibility to
+ # the current user are marked readable
+ return 1 if $self->{ _object_is_readable };
+
# Defer to the object in question
return $self->Object->CurrentUserCanSee("Transaction");
}
}
}
+=head2 LoadCustomFieldByIdentifier
-
-=head2 CustomFieldValues
-
- Do name => id mapping (if needed) before falling back to RT::Record's CustomFieldValues
-
- See L<RT::Record>
+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 CustomFieldValues {
+sub LoadCustomFieldByIdentifier {
my $self = shift;
my $field = shift;
- if ( UNIVERSAL::can( $self->Object, 'QueueObj' ) ) {
-
- # XXX: $field could be undef when we want fetch values for all CFs
- # do we want to cover this situation somehow here?
- unless ( defined $field && $field =~ /^\d+$/o ) {
- my $CFs = RT::CustomFields->new( $self->CurrentUser );
- $CFs->SetContextObject( $self->Object );
- $CFs->Limit( FIELD => 'Name', VALUE => $field );
- $CFs->LimitToLookupType($self->CustomFieldLookupType);
- $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
- $field = $CFs->First->id if $CFs->First;
- }
- }
- return $self->SUPER::CustomFieldValues($field);
-}
+ 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 );
+ $CFs->LimitToLookupType($self->CustomFieldLookupType);
+ $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
+ return $CFs->First || RT::CustomField->new( $self->CurrentUser );
+}
=head2 CustomFieldLookupType