+++ /dev/null
-# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Transaction.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $
-# Copyright 1999-2001 Jesse Vincent <jesse@fsck.com>
-# Released under the terms of the GNU Public License
-
-=head1 NAME
-
- RT::Transaction - RT\'s transaction object
-
-=head1 SYNOPSIS
-
- use RT::Transaction;
-
-
-=head1 DESCRIPTION
-
-
-Each RT::Transaction describes an atomic change to a ticket object
-or an update to an RT::Ticket object.
-It can have arbitrary MIME attachments.
-
-
-=head1 METHODS
-
-=begin testing
-
-ok(require RT::TestHarness);
-ok(require RT::Transaction);
-
-=end testing
-
-=cut
-
-package RT::Transaction;
-
-use RT::Record;
-@ISA= qw(RT::Record);
-
-use RT::Attachments;
-
-# {{{ sub _Init
-sub _Init {
- my $self = shift;
- $self->{'table'} = "Transactions";
- return ($self->SUPER::_Init(@_));
-
-}
-# }}}
-
-# {{{ sub Create
-
-=head2 Create
-
-Create a new transaction.
-
-This routine should _never_ be called anything other Than RT::Ticket. It should not be called
-from client code. Ever. Not ever. If you do this, we will hunt you down. and break your kneecaps.
-Then the unpleasant stuff will start.
-
-TODO: Document what gets passed to this
-
-=cut
-
-sub Create {
- my $self = shift;
- my %args = ( id => undef,
- TimeTaken => 0,
- Ticket => 0 ,
- Type => 'undefined',
- Data => '',
- Field => undef,
- OldValue => undef,
- NewValue => undef,
- MIMEObj => undef,
- ActivateScrips => 1,
- @_
- );
-
- #if we didn't specify a ticket, we need to bail
- unless ( $args{'Ticket'} ) {
- return(0, "RT::Transaction->Create couldn't, as you didn't specify a ticket id");
- }
-
- #lets create our transaction
- my $id = $self->SUPER::Create(Ticket => $args{'Ticket'},
- TimeTaken => $args{'TimeTaken'},
- Type => $args{'Type'},
- Data => $args{'Data'},
- Field => $args{'Field'},
- OldValue => $args{'OldValue'},
- NewValue => $args{'NewValue'},
- Created => $args{'Created'}
- );
- $self->Load($id);
- $self->_Attach($args{'MIMEObj'})
- if defined $args{'MIMEObj'};
-
- #Provide a way to turn off scrips if we need to
- if ($args{'ActivateScrips'}) {
-
- #We're really going to need a non-acled ticket for the scrips to work
- my $TicketAsSystem = RT::Ticket->new($RT::SystemUser);
- $TicketAsSystem->Load($args{'Ticket'}) ||
- $RT::Logger->err("$self couldn't load ticket $args{'Ticket'}\n");
-
- my $TransAsSystem = RT::Transaction->new($RT::SystemUser);
- $TransAsSystem->Load($self->id) ||
- $RT::Logger->err("$self couldn't load a copy of itself as superuser\n");
-
- # {{{ Deal with Scrips
-
- #Load a scripscopes object
- use RT::Scrips;
- my $PossibleScrips = RT::Scrips->new($RT::SystemUser);
-
- $PossibleScrips->LimitToQueue($TicketAsSystem->QueueObj->Id); #Limit it to $Ticket->QueueObj->Id
- $PossibleScrips->LimitToGlobal(); # or to "global"
- my $ConditionsAlias = $PossibleScrips->NewAlias('ScripConditions');
-
- $PossibleScrips->Join(ALIAS1 => 'main', FIELD1 => 'ScripCondition',
- ALIAS2 => $ConditionsAlias, FIELD2=> 'id');
-
-
- #We only want things where the scrip applies to this sort of transaction
- $PossibleScrips->Limit(ALIAS=> $ConditionsAlias,
- FIELD=>'ApplicableTransTypes',
- OPERATOR => 'LIKE',
- VALUE => $args{'Type'},
- ENTRYAGGREGATOR => 'OR',
- );
-
- # Or where the scrip applies to any transaction
- $PossibleScrips->Limit(ALIAS=> $ConditionsAlias,
- FIELD=>'ApplicableTransTypes',
- OPERATOR => 'LIKE',
- VALUE => "Any",
- ENTRYAGGREGATOR => 'OR',
- );
-
- #Iterate through each script and check it's applicability.
-
- while (my $Scrip = $PossibleScrips->Next()) {
-
- #TODO: properly deal with errors raised in this scrip loop
-
- #$RT::Logger->debug("$self now dealing with ".$Scrip->Id. "\n");
- eval {
- local $SIG{__DIE__} = sub { $RT::Logger->error($_[0])};
-
-
- #Load the scrip's Condition object
- $Scrip->ConditionObj->LoadCondition(TicketObj => $TicketAsSystem,
- TransactionObj => $TransAsSystem);
-
-
- #If it's applicable, prepare and commit it
-
- $RT::Logger->debug ("$self: Checking condition ".$Scrip->ConditionObj->Name. "...\n");
-
- if ( $Scrip->IsApplicable() ) {
-
- $RT::Logger->debug ("$self: Matches condition ".$Scrip->ConditionObj->Name. "...\n");
- #TODO: handle some errors here
-
- $Scrip->ActionObj->LoadAction(TicketObj => $TicketAsSystem,
- TransactionObj => $TransAsSystem);
-
-
- if ($Scrip->Prepare()) {
- $RT::Logger->debug("$self: Prepared " .
- $Scrip->ActionObj->Name . "\n");
- if ($Scrip->Commit()) {
- $RT::Logger->debug("$self: Committed " .
- $Scrip->ActionObj->Name . "\n");
- }
- else {
- $RT::Logger->info("$self: Failed to commit ".
- $Scrip->ActionObj->Name . "\n");
- }
- }
- else {
- $RT::Logger->info("$self: Failed to prepare " .
- $Scrip->ActionObj->Name . "\n");
- }
-
- #We're done with it. lets clean up.
- #TODO: something else isn't letting these get garbage collected. check em out.
- $Scrip->ActionObj->DESTROY();
- $Scrip->ConditionObj->DESTROY;
- }
-
-
- else {
- $RT::Logger->debug ("$self: Doesn't match condition ".$Scrip->ConditionObj->Name. "...\n");
-
- # TODO: why doesn't this catch all the ScripObjs we create.
- # and why do we explictly need to destroy them?
- $Scrip->ConditionObj->DESTROY;
- }
- }
- }
-
- # }}}
-
- }
-
- return ($id, "Transaction Created");
-}
-
-# }}}
-
-# {{{ sub Delete
-
-sub Delete {
- my $self = shift;
- return (0, 'Deleting this object could break referential integrity');
-}
-
-# }}}
-
-# {{{ Routines dealing with Attachments
-
-# {{{ sub Message
-
-=head2 Message
-
- Returns the RT::Attachments Object which contains the "top-level" object
- attachment for this transaction
-
-=cut
-
-sub Message {
-
- my $self = shift;
-
- if (!defined ($self->{'message'}) ){
-
- $self->{'message'} = new RT::Attachments($self->CurrentUser);
- $self->{'message'}->Limit(FIELD => 'TransactionId',
- VALUE => $self->Id);
-
- $self->{'message'}->ChildrenOf(0);
- }
- return($self->{'message'});
-}
-# }}}
-
-# {{{ sub Content
-
-=head2 Content PARAMHASH
-
-If this transaction has attached mime objects, returns the first text/ part.
-Otherwise, returns undef.
-
-Takes a paramhash. If the $args{'Quote'} parameter is set, wraps this message
-at $args{'Wrap'}. $args{'Wrap'} defaults to 70.
-
-
-=cut
-
-sub Content {
- my $self = shift;
- my %args = ( Quote => 0,
- Wrap => 70,
- @_ );
-
- my $content = undef;
-
- # If we don\'t have any content, return undef now.
- unless ($self->Message->First) {
- return (undef);
- }
-
- # Get the set of toplevel attachments to this transaction.
- my $MIMEObj = $self->Message->First();
-
- # If it's a message or a plain part, just return the
- # body.
- if ($MIMEObj->ContentType() =~ '^(text|message)(/|$)') {
- $content = $MIMEObj->Content();
- }
-
- # If it's a multipart object, first try returning the first
- # text/plain part.
-
- elsif ($MIMEObj->ContentType() =~ '^multipart/') {
- my $plain_parts = $MIMEObj->Children();
- $plain_parts->ContentType(VALUE => 'text/plain');
-
- # If we actully found a part, return its content
- if ($plain_parts->First &&
- $plain_parts->First->Content ne '') {
- $content = $plain_parts->First->Content;
- }
-
- # If that fails, return the first text/ or message/ part
- # which has some content.
-
- else {
- my $all_parts = $MIMEObj->Children();
- while (($content == undef) &&
- (my $part = $all_parts->Next)) {
- if (($part->ContentType() =~ '^(text|message)(/|$)') and
- ($part->Content())) {
- $content = $part->Content;
- }
- }
- }
-
- }
- # If all else fails, return a message that we couldn't find
- # any content
- else {
- $content = 'This transaction appears to have no content';
- }
-
- if ($args{'Quote'}) {
- # Remove quoted signature.
- $content =~ s/\n-- \n(.*)$//s;
-
- # What's the longest line like?
- foreach (split (/\n/,$content)) {
- $max=length if ( length > $max);
- }
-
- if ($max>76) {
- require Text::Wrapper;
- my $wrapper=new Text::Wrapper
- (
- columns => $args{'Wrap'},
- body_start => ($max > 70*3 ? ' ' : ''),
- par_start => ''
- );
- $content=$wrapper->wrap($content);
- }
-
- $content =~ s/^/> /gm;
- $content = '[' . $self->CreatorObj->Name() . ' - ' . $self->CreatedAsString()
- . "]:\n\n"
- . $content . "\n\n";
-
- }
-
- return ($content);
-}
-# }}}
-
-# {{{ sub Subject
-
-=head2 Subject
-
-If this transaction has attached mime objects, returns the first one's subject
-Otherwise, returns null
-
-=cut
-
-sub Subject {
- my $self = shift;
- if ($self->Message->First) {
- return ($self->Message->First->Subject);
- }
- else {
- return (undef);
- }
-}
-# }}}
-
-# {{{ sub Attachments
-
-=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;
- my $Types = '';
- $Types = shift if (@_);
-
- my $Attachments = new RT::Attachments($self->CurrentUser);
-
- #If it's a comment, return an empty object if they don't have the right to see it
- if ($self->Type eq 'Comment') {
- unless ($self->CurrentUserHasRight('ShowTicketComments')) {
- return ($Attachments);
- }
- }
- #if they ain't got rights to see, return an empty object
- else {
- unless ($self->CurrentUserHasRight('ShowTicket')) {
- return ($Attachments);
- }
- }
-
- $Attachments->Limit(FIELD => 'TransactionId',
- VALUE => $self->Id);
-
- # Get the attachments in the order they're put into
- # the database. Arguably, we should be returning a tree
- # of attachments, not a set...but no current app seems to need
- # it.
-
- $Attachments->OrderBy(ALIAS => 'main',
- FIELD => 'Id',
- ORDER => 'asc');
-
- if ($Types) {
- $Attachments->ContentType( VALUE => "$Types",
- OPERATOR => "LIKE");
- }
-
-
- return($Attachments);
-
-}
-
-# }}}
-
-# {{{ sub _Attach
-
-=head2 _Attach
-
-A private method used to attach a mime object to this transaction.
-
-=cut
-
-sub _Attach {
- my $self = shift;
- my $MIMEObject = shift;
-
- if (!defined($MIMEObject)) {
- $RT::Logger->error("$self _Attach: We can't attach a mime object if you don't give us one.\n");
- return(0, "$self: no attachment specified");
- }
-
-
- use RT::Attachment;
- my $Attachment = new RT::Attachment ($self->CurrentUser);
- $Attachment->Create(TransactionId => $self->Id,
- Attachment => $MIMEObject);
- return ($Attachment, "Attachment created");
-
-}
-
-# }}}
-
-# }}}
-
-# {{{ Routines dealing with Transaction Attributes
-
-# {{{ sub TicketObj
-
-=head2 TicketObj
-
-Returns this transaction's ticket object.
-
-=cut
-
-sub TicketObj {
- my $self = shift;
- if (! exists $self->{'TicketObj'}) {
- $self->{'TicketObj'} = new RT::Ticket($self->CurrentUser);
- $self->{'TicketObj'}->Load($self->Ticket);
- }
-
- return $self->{'TicketObj'};
-}
-# }}}
-
-# {{{ sub Description
-
-=head2 Description
-
-Returns a text string which describes this transaction
-
-=cut
-
-
-sub Description {
- my $self = shift;
-
- #Check those ACLs
- #If it's a comment, we need to be extra special careful
- if ($self->__Value('Type') eq 'Comment') {
- unless ($self->CurrentUserHasRight('ShowTicketComments')) {
- return (0, "Permission Denied");
- }
- }
-
- #if they ain't got rights to see, don't let em
- else {
- unless ($self->CurrentUserHasRight('ShowTicket')) {
- return (0, "Permission Denied");
- }
- }
-
- if (!defined($self->Type)) {
- return("No transaction type specified");
- }
-
- return ($self->BriefDescription . " by " . $self->CreatorObj->Name);
-}
-
-# }}}
-
-# {{{ sub BriefDescription
-
-=head2 BriefDescription
-
-Returns a text string which briefly describes this transaction
-
-=cut
-
-
-sub BriefDescription {
- my $self = shift;
-
- #Check those ACLs
- #If it's a comment, we need to be extra special careful
- if ($self->__Value('Type') eq 'Comment') {
- unless ($self->CurrentUserHasRight('ShowTicketComments')) {
- return (0, "Permission Denied");
- }
- }
-
- #if they ain't got rights to see, don't let em
- else {
- unless ($self->CurrentUserHasRight('ShowTicket')) {
- return (0, "Permission Denied");
- }
- }
-
- if (!defined($self->Type)) {
- return("No transaction type specified");
- }
-
- if ($self->Type eq 'Create'){
- return("Ticket created");
- }
- elsif ($self->Type =~ /Status/) {
- if ($self->Field eq 'Status') {
- if ($self->NewValue eq 'dead') {
- return ("Ticket killed");
- }
- else {
- return( "Status changed from ". $self->OldValue .
- " to ". $self->NewValue);
-
- }
- }
- # Generic:
- return ($self->Field." changed from ".($self->OldValue||"(empty value)").
- " to ".$self->NewValue );
- }
-
- if ($self->Type eq 'Correspond') {
- return("Correspondence added");
- }
-
- elsif ($self->Type eq 'Comment') {
- return( "Comments added");
- }
-
- elsif ($self->Type eq 'Keyword') {
-
- my $field = 'Keyword';
-
- if ($self->Field) {
- my $keywordsel = new RT::KeywordSelect ($self->CurrentUser);
- $keywordsel->Load($self->Field);
- $field = $keywordsel->Name();
- }
-
- if ($self->OldValue eq '') {
- return ($field." ".$self->NewValue." added");
- }
- elsif ($self->NewValue eq '') {
- return ($field." ".$self->OldValue." deleted");
-
- }
- else {
- return ($field." ".$self->OldValue . " changed to ".
- $self->NewValue);
- }
- }
-
- elsif ($self->Type eq 'Untake'){
- return( "Untaken");
- }
-
- elsif ($self->Type eq "Take") {
- return( "Taken");
- }
-
- elsif ($self->Type eq "Force") {
- my $Old = RT::User->new($self->CurrentUser);
- $Old->Load($self->OldValue);
- my $New = RT::User->new($self->CurrentUser);
- $New->Load($self->NewValue);
- return "Owner forcibly changed from ".$Old->Name . " to ". $New->Name;
- }
- elsif ($self->Type eq "Steal") {
- my $Old = RT::User->new($self->CurrentUser);
- $Old->Load($self->OldValue);
- return "Stolen from ".$Old->Name;
- }
-
- elsif ($self->Type eq "Give") {
- my $New = RT::User->new($self->CurrentUser);
- $New->Load($self->NewValue);
- return( "Given to ".$New->Name);
- }
-
- elsif ($self->Type eq 'AddWatcher'){
- return( $self->Field." ". $self->NewValue ." added");
- }
-
- elsif ($self->Type eq 'DelWatcher'){
- return( $self->Field." ".$self->OldValue ." deleted");
- }
-
- elsif ($self->Type eq 'Subject') {
- return( "Subject changed to ".$self->Data);
- }
- elsif ($self->Type eq 'Told') {
- return( "User notified");
- }
-
- elsif ($self->Type eq 'AddLink') {
- return ($self->Data);
- }
- elsif ($self->Type eq 'DeleteLink') {
- return ($self->Data);
- }
- elsif ($self->Type eq 'Set') {
- if ($self->Field eq 'Queue') {
- my $q1 = new RT::Queue($self->CurrentUser);
- $q1->Load($self->OldValue);
- my $q2 = new RT::Queue($self->CurrentUser);
- $q2->Load($self->NewValue);
- return ($self->Field . " changed from " . $q1->Name . " to ".
- $q2->Name);
- }
-
- # Write the date/time change at local time:
- elsif ($self->Field =~ /Due|Starts|Started|Told/) {
- my $t1 = new RT::Date($self->CurrentUser);
- $t1->Set(Format => 'ISO', Value => $self->NewValue);
- my $t2 = new RT::Date($self->CurrentUser);
- $t2->Set(Format => 'ISO', Value => $self->OldValue);
- return ($self->Field . " changed from " . $t2->AsString .
- " to ".$t1->AsString);
- }
- else {
- return ($self->Field . " changed from " . $self->OldValue .
- " to ".$self->NewValue);
- }
- }
- elsif ($self->Type eq 'PurgeTransaction') {
- return ("Transaction ".$self->Data. " purged");
- }
- else {
- return ("Default: ". $self->Type ."/". $self->Field .
- " changed from " . $self->OldValue .
- " to ".$self->NewValue);
-
- }
-}
-
-# }}}
-
-# {{{ Utility methods
-
-# {{{ sub IsInbound
-
-=head2 IsInbound
-
-Returns true if the creator of the transaction is a requestor of the ticket.
-Returns false otherwise
-
-=cut
-
-sub IsInbound {
- my $self=shift;
- return ($self->TicketObj->IsRequestor($self->CreatorObj));
-}
-
-# }}}
-
-# }}}
-
-# {{{ sub _Accessible
-
-sub _Accessible {
- my $self = shift;
- my %Cols = (
- TimeTaken => 'read',
- Ticket => 'read/public',
- Type=> 'read',
- Field => 'read',
- Data => 'read',
- NewValue => 'read',
- OldValue => 'read',
- Creator => 'read/auto',
- Created => 'read/auto',
- );
- return $self->SUPER::_Accessible(@_, %Cols);
-}
-
-# }}}
-
-# }}}
-
-# {{{ sub _Set
-
-sub _Set {
- my $self = shift;
- return(0, 'Transactions are immutable');
-}
-
-# }}}
-
-# {{{ sub _Value
-
-=head2 _Value
-
-Takes the name of a table column.
-Returns its value as a string, if the user passes an ACL check
-
-=cut
-
-sub _Value {
-
- my $self = shift;
- my $field = shift;
-
-
- #if the field is public, return it.
- if ($self->_Accessible($field, 'public')) {
- return($self->__Value($field));
-
- }
- #If it's a comment, we need to be extra special careful
- if ($self->__Value('Type') eq 'Comment') {
- unless ($self->CurrentUserHasRight('ShowTicketComments')) {
- return (undef);
- }
- }
- #if they ain't got rights to see, don't let em
- else {
- unless ($self->CurrentUserHasRight('ShowTicket')) {
- return (undef);
- }
- }
-
- return($self->__Value($field));
-
-}
-
-# }}}
-
-# {{{ sub CurrentUserHasRight
-
-=head2 CurrentUserHasRight RIGHT
-
-Calls $self->CurrentUser->HasQueueRight for the right passed in here.
-passed in here.
-
-=cut
-
-sub CurrentUserHasRight {
- my $self = shift;
- my $right = shift;
- return ($self->CurrentUser->HasQueueRight(Right => "$right",
- TicketObj => $self->TicketObj));
-}
-
-# }}}
-
-1;