RT 4.0.19
[freeside.git] / rt / lib / RT / Transaction.pm
index bd4d835..5c903e9 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # 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)
@@ -48,7 +48,7 @@
 
 =head1 NAME
 
-  RT::Transaction - RT\'s transaction object
+  RT::Transaction - RT's transaction object
 
 =head1 SYNOPSIS
 
@@ -133,12 +133,6 @@ sub Create {
         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'},
@@ -169,6 +163,11 @@ sub Create {
         }
     }
 
+    # 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($_)
@@ -369,31 +368,105 @@ sub Content {
     }
 
     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
@@ -641,11 +714,14 @@ sub BriefDescription {
                 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) ) . "'"
                     )
                 );
 
@@ -712,8 +788,9 @@ sub BriefDescription {
         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();
@@ -723,6 +800,44 @@ sub BriefDescription {
         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);
         }
@@ -783,8 +898,7 @@ sub BriefDescription {
         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 {
@@ -822,8 +936,7 @@ sub BriefDescription {
         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 {
@@ -927,7 +1040,8 @@ sub BriefDescription {
         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 {
@@ -1073,6 +1187,11 @@ sub CurrentUserCanSee {
         $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");
 }
@@ -1182,37 +1301,31 @@ sub UpdateCustomFields {
     }
 }
 
+=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