-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
#
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
#
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
#
# This work is made available to you under the terms of Version 2 of
# the GNU General Public License. A copy of that license should have
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
#
-# END LICENSE BLOCK
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
# {{{ Front Material
=head1 SYNOPSIS
ok($testqueue->Id != 0);
use_ok(RT::CustomField);
ok(my $testcf = RT::CustomField->new($RT::SystemUser));
-ok($testcf->Create( Name => 'selectmulti',
+my ($ret, $cmsg) = $testcf->Create( Name => 'selectmulti',
Queue => $testqueue->id,
- Type => 'SelectMultiple'));
-ok($testcf->AddValue ( Name => 'Value1',
+ Type => 'SelectMultiple');
+ok($ret,"Created the custom field - ".$cmsg);
+($ret,$cmsg) = $testcf->AddValue ( Name => 'Value1',
SortOrder => '1',
- Description => 'A testing value'));
+ Description => 'A testing value');
+
+ok($ret, "Added a value - ".$cmsg);
+
ok($testcf->AddValue ( Name => 'Value2',
SortOrder => '2',
Description => 'Another testing value'));
ok(my $t2 = RT::Ticket->new($RT::SystemUser));
ok($t2->Load($id));
-ok($t2->Subject eq 'Testing');
-ok($t2->QueueObj->Id eq $testqueue->id);
+is($t2->Subject, 'Testing');
+is($t2->QueueObj->Id, $testqueue->id);
ok($t2->OwnerObj->Id == $u->Id);
my $t3 = RT::Ticket->new($RT::SystemUser);
=cut
+
+package RT::Ticket;
+
use strict;
no warnings qw(redefine);
use RT::Links;
use RT::Date;
use RT::CustomFields;
-use RT::TicketCustomFieldValues;
use RT::Tickets;
+use RT::Transactions;
+use RT::Reminders;
use RT::URI::fsck_com_rt;
use RT::URI;
+use MIME::Entity;
=begin testing
# }}}
# {{{ LINKTYPEMAP
-# A helper table for relationships mapping to make it easier
+# A helper table for links mapping to make it easier
# to build and parse links between tickets
use vars '%LINKTYPEMAP';
%LINKTYPEMAP = (
MemberOf => { Type => 'MemberOf',
Mode => 'Target', },
+ Parents => { Type => 'MemberOf',
+ Mode => 'Target', },
Members => { Type => 'MemberOf',
Mode => 'Base', },
+ Children => { Type => 'MemberOf',
+ Mode => 'Base', },
HasMember => { Type => 'MemberOf',
Mode => 'Base', },
RefersTo => { Type => 'RefersTo',
Mode => 'Target', },
DependedOnBy => { Type => 'DependsOn',
Mode => 'Base', },
+ MergedInto => { Type => 'MergedInto',
+ Mode => 'Target', },
);
# }}}
# {{{ LINKDIRMAP
-# A helper table for relationships mapping to make it easier
+# A helper table for links mapping to make it easier
# to build and parse links between tickets
use vars '%LINKDIRMAP';
Target => 'ReferredToBy', },
DependsOn => { Base => 'DependsOn',
Target => 'DependedOnBy', },
+ MergedInto => { Base => 'MergedInto',
+ Target => 'MergedInto', },
);
# }}}
+sub LINKTYPEMAP { return \%LINKTYPEMAP }
+sub LINKDIRMAP { return \%LINKDIRMAP }
+
# {{{ sub Load
=head2 Load
#TODO modify this routine to look at EffectiveId and do the recursive load
# thing. be careful to cache all the interim tickets we try so we don't loop forever.
+
#If it's a local URI, turn it into a ticket id
- if ( $id =~ /^$RT::TicketBaseURI(\d+)$/ ) {
+ if ( $RT::TicketBaseURI && $id =~ /^$RT::TicketBaseURI(\d+)$/ ) {
$id = $1;
}
#If we have an integer URI, load the ticket
if ( $id =~ /^\d+$/ ) {
- my $ticketid = $self->LoadById($id);
+ my ($ticketid,$msg) = $self->LoadById($id);
- unless ($ticketid) {
- $RT::Logger->debug("$self tried to load a bogus ticket: $id\n");
+ unless ($self->Id) {
+ $RT::Logger->crit("$self tried to load a bogus ticket: $id\n");
return (undef);
}
}
#It's not a URI. It's not a numerical ticket ID. Punt!
else {
+ $RT::Logger->warning("Tried to load a bogus ticket id: '$id'");
return (undef);
}
#If we're merged, resolve the merge.
if ( ( $self->EffectiveId ) and ( $self->EffectiveId != $self->Id ) ) {
+ $RT::Logger->debug ("We found a merged ticket.". $self->id ."/".$self->EffectiveId);
return ( $self->Load( $self->EffectiveId ) );
}
id
Queue - Either a Queue object or a Queue Name
- Requestor - A reference to a list of RT::User objects, email addresses or RT user Names
- Cc - A reference to a list of RT::User objects, email addresses or Names
- AdminCc - A reference to a list of RT::User objects, email addresses or Names
+ Requestor - A reference to a list of email addresses or RT user Names
+ Cc - A reference to a list of email addresses or Names
+ AdminCc - A reference to a list of email addresses or Names
Type -- The ticket\'s type. ignore this for now
Owner -- This ticket\'s owner. either an RT::User object or this user\'s id
Subject -- A string describing the subject of the ticket
+ Priority -- an integer from 0 to 99
InitialPriority -- an integer from 0 to 99
FinalPriority -- an integer from 0 to 99
Status -- any valid status (Defined in RT::Queue)
MIMEObj -- a MIME::Entity object with the content of the initial ticket request.
CustomField-<n> -- a scalar or array of values for the customfield with the id <n>
+Ticket links can be set up during create by passing the link type as a hask key and
+the ticket id to be linked to as a value (or a URI when linking to other objects).
+Multiple links of the same type can be created by passing an array ref. For example:
-Returns: TICKETID, Transaction Object, Error Message
+ Parent => 45,
+ DependsOn => [ 15, 22 ],
+ RefersTo => 'http://www.bestpractical.com',
+
+Supported link types are C<MemberOf>, C<HasMember>, C<RefersTo>, C<ReferredToBy>,
+C<DependsOn> and C<DependedOnBy>. Also, C<Parents> is alias for C<MemberOf> and
+C<Members> and C<Children> are aliases for C<HasMember>.
+Returns: TICKETID, Transaction Object, Error Message
=begin testing
sub Create {
my $self = shift;
- my %args = ( id => undef,
- EffectiveId => undef,
- Queue => undef,
- Requestor => undef,
- Cc => undef,
- AdminCc => undef,
- Type => 'ticket',
- Owner => undef,
- Subject => '',
- InitialPriority => undef,
- FinalPriority => undef,
- Priority => undef,
- Status => 'new',
- TimeWorked => "0",
- TimeLeft => 0,
- TimeEstimated => 0,
- Due => undef,
- Starts => undef,
- Started => undef,
- Resolved => undef,
- MIMEObj => undef,
- _RecordTransaction => 1,
-
-
-
- @_ );
+ my %args = (
+ id => undef,
+ EffectiveId => undef,
+ Queue => undef,
+ Requestor => undef,
+ Cc => undef,
+ AdminCc => undef,
+ Type => 'ticket',
+ Owner => undef,
+ Subject => '',
+ InitialPriority => undef,
+ FinalPriority => undef,
+ Priority => undef,
+ Status => 'new',
+ TimeWorked => "0",
+ TimeLeft => 0,
+ TimeEstimated => 0,
+ Due => undef,
+ Starts => undef,
+ Started => undef,
+ Resolved => undef,
+ MIMEObj => undef,
+ _RecordTransaction => 1,
+ @_
+ );
my ( $ErrStr, $Owner, $resolved );
my (@non_fatal_errors);
my $QueueObj = RT::Queue->new($RT::SystemUser);
-
if ( ( defined( $args{'Queue'} ) ) && ( !ref( $args{'Queue'} ) ) ) {
$QueueObj->Load( $args{'Queue'} );
}
$QueueObj->Load( $args{'Queue'}->Id );
}
else {
- $RT::Logger->debug( $args{'Queue'} . " not a recognised queue object.");
+ $RT::Logger->debug( $args{'Queue'} . " not a recognised queue object." );
}
-;
#Can't create a ticket without a queue.
unless ( defined($QueueObj) && $QueueObj->Id ) {
}
#Now that we have a queue, Check the ACLS
- unless ( $self->CurrentUser->HasRight( Right => 'CreateTicket',
- Object => $QueueObj )
- ) {
- return ( 0, 0,
- $self->loc( "No permission to create tickets in the queue '[_1]'", $QueueObj->Name ) );
+ unless (
+ $self->CurrentUser->HasRight(
+ Right => 'CreateTicket',
+ Object => $QueueObj
+ )
+ )
+ {
+ return (
+ 0, 0,
+ $self->loc( "No permission to create tickets in the queue '[_1]'", $QueueObj->Name));
}
unless ( $QueueObj->IsValidStatus( $args{'Status'} ) ) {
return ( 0, 0, $self->loc('Invalid value for status') );
}
-
#Since we have a queue, we can set queue defaults
#Initial Priority
# If there's no queue default initial priority and it's not set, set it to 0
$args{'InitialPriority'} = ( $QueueObj->InitialPriority || 0 )
- unless ( defined $args{'InitialPriority'} );
+ unless ( $args{'InitialPriority'} );
- #Final priority
+ #Final priority
# If there's no queue default final priority and it's not set, set it to 0
$args{'FinalPriority'} = ( $QueueObj->FinalPriority || 0 )
- unless ( defined $args{'FinalPriority'} );
+ unless ( $args{'FinalPriority'} );
# Priority may have changed from InitialPriority, for the case
# where we're importing tickets (eg, from an older RT version.)
my $priority = $args{'Priority'} || $args{'InitialPriority'};
-
# {{{ Dates
#TODO we should see what sort of due date we're getting, rather +
# than assuming it's in ISO format.
my $Due = new RT::Date( $self->CurrentUser );
if ( $args{'Due'} ) {
- $Due->Set( Format => 'ISO', Value => $args{'Due'} );
+ $Due->Set( Format => 'ISO', Value => $args{'Due'} );
}
- elsif ( $QueueObj->DefaultDueIn ) {
+ elsif ( my $due_in = $QueueObj->DefaultDueIn ) {
$Due->SetToNow;
- $Due->AddDays( $QueueObj->DefaultDueIn );
+ $Due->AddDays( $due_in );
}
my $Starts = new RT::Date( $self->CurrentUser );
if ( defined $args{'Starts'} ) {
- $Starts->Set( Format => 'ISO', Value => $args{'Starts'} );
+ $Starts->Set( Format => 'ISO', Value => $args{'Starts'} );
}
my $Started = new RT::Date( $self->CurrentUser );
if ( defined $args{'Started'} ) {
- $Started->Set( Format => 'ISO', Value => $args{'Started'} );
+ $Started->Set( Format => 'ISO', Value => $args{'Started'} );
}
my $Resolved = new RT::Date( $self->CurrentUser );
if ( defined $args{'Resolved'} ) {
- $Resolved->Set( Format => 'ISO', Value => $args{'Resolved'} );
+ $Resolved->Set( Format => 'ISO', Value => $args{'Resolved'} );
}
-
#If the status is an inactive status, set the resolved date
- if ($QueueObj->IsInactiveStatus($args{'Status'}) && !$args{'Resolved'}) {
- $RT::Logger->debug("Got a ".$args{'Status'} . "ticket with a resolved of ".$args{'Resolved'});
+ if ( $QueueObj->IsInactiveStatus( $args{'Status'} ) && !$args{'Resolved'} )
+ {
+ $RT::Logger->debug( "Got a ". $args{'Status'}
+ ." ticket with undefined resolved date. Setting to now."
+ );
$Resolved->SetToNow;
}
}
#If we've been handed something else, try to load the user.
- elsif ( defined $args{'Owner'} ) {
+ elsif ( $args{'Owner'} ) {
$Owner = RT::User->new( $self->CurrentUser );
$Owner->Load( $args{'Owner'} );
+ push( @non_fatal_errors,
+ $self->loc("Owner could not be set.") . " "
+ . $self->loc( "User '[_1]' could not be found.", $args{'Owner'} )
+ )
+ unless ( $Owner->Id );
}
- #If we have a proposed owner and they don't have the right
+ #If we have a proposed owner and they don't have the right
#to own a ticket, scream about it and make them not the owner
- if ( ( defined($Owner) )
- and ( $Owner->Id )
- and ( $Owner->Id != $RT::Nobody->Id )
- and ( !$Owner->HasRight( Object => $QueueObj,
- Right => 'OwnTicket' ) )
- ) {
+ if (
+ ( defined($Owner) )
+ and ( $Owner->Id )
+ and ( $Owner->Id != $RT::Nobody->Id )
+ and (
+ !$Owner->HasRight(
+ Object => $QueueObj,
+ Right => 'OwnTicket'
+ )
+ )
+ )
+ {
$RT::Logger->warning( "User "
- . $Owner->Name . "("
- . $Owner->id
- . ") was proposed "
- . "as a ticket owner but has no rights to own "
- . "tickets in ".$QueueObj->Name );
+ . $Owner->Name . "("
+ . $Owner->id
+ . ") was proposed "
+ . "as a ticket owner but has no rights to own "
+ . "tickets in "
+ . $QueueObj->Name );
- push @non_fatal_errors, $self->loc("Invalid owner. Defaulting to 'nobody'.");
+ push @non_fatal_errors,
+ $self->loc( "Owner '[_1]' does not have rights to own this ticket.",
+ $Owner->Name
+ );
$Owner = undef;
}
# }}}
- # We attempt to load or create each of the people who might have a role for this ticket
- # _outside_ the transaction, so we don't get into ticket creation races
+# We attempt to load or create each of the people who might have a role for this ticket
+# _outside_ the transaction, so we don't get into ticket creation races
foreach my $type ( "Cc", "AdminCc", "Requestor" ) {
- next unless (defined $args{$type});
- foreach my $watcher ( ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) ) {
- my $user = RT::User->new($RT::SystemUser);
- $user->LoadOrCreateByEmail($watcher) if ($watcher && $watcher !~ /^\d+$/);
+ next unless ( defined $args{$type} );
+ foreach my $watcher (
+ ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
+ {
+ my $user = RT::User->new($RT::SystemUser);
+ $user->LoadOrCreateByEmail($watcher)
+ if ( $watcher && $watcher !~ /^\d+$/ );
}
}
-
$RT::Handle->BeginTransaction();
- my %params =( Queue => $QueueObj->Id,
- Owner => $Owner->Id,
- Subject => $args{'Subject'},
- InitialPriority => $args{'InitialPriority'},
- FinalPriority => $args{'FinalPriority'},
- Priority => $priority,
- Status => $args{'Status'},
- TimeWorked => $args{'TimeWorked'},
- TimeEstimated => $args{'TimeEstimated'},
- TimeLeft => $args{'TimeLeft'},
- Type => $args{'Type'},
- Starts => $Starts->ISO,
- Started => $Started->ISO,
- Resolved => $Resolved->ISO,
- Due => $Due->ISO );
-
- # Parameters passed in during an import that we probably don't want to touch, otherwise
+ my %params = (
+ Queue => $QueueObj->Id,
+ Owner => $Owner->Id,
+ Subject => $args{'Subject'},
+ InitialPriority => $args{'InitialPriority'},
+ FinalPriority => $args{'FinalPriority'},
+ Priority => $priority,
+ Status => $args{'Status'},
+ TimeWorked => $args{'TimeWorked'},
+ TimeEstimated => $args{'TimeEstimated'},
+ TimeLeft => $args{'TimeLeft'},
+ Type => $args{'Type'},
+ Starts => $Starts->ISO,
+ Started => $Started->ISO,
+ Resolved => $Resolved->ISO,
+ Due => $Due->ISO
+ );
+
+# Parameters passed in during an import that we probably don't want to touch, otherwise
foreach my $attr qw(id Creator Created LastUpdated LastUpdatedBy) {
- $params{$attr} = $args{$attr} if ($args{$attr});
+ $params{$attr} = $args{$attr} if ( $args{$attr} );
}
# Delete null integer parameters
- foreach my $attr qw(TimeWorked TimeLeft TimeEstimated InitialPriority FinalPriority) {
- delete $params{$attr} unless (exists $params{$attr} && $params{$attr});
+ foreach my $attr
+ qw(TimeWorked TimeLeft TimeEstimated InitialPriority FinalPriority) {
+ delete $params{$attr}
+ unless ( exists $params{$attr} && $params{$attr} );
}
-
- my $id = $self->SUPER::Create( %params);
+ # Delete the time worked if we're counting it in the transaction
+ delete $params{TimeWorked} if $args{'_RecordTransaction'};
+
+ my ($id,$ticket_message) = $self->SUPER::Create( %params);
unless ($id) {
- $RT::Logger->crit( "Couldn't create a ticket");
+ $RT::Logger->crit( "Couldn't create a ticket: " . $ticket_message );
$RT::Handle->Rollback();
- return ( 0, 0, $self->loc( "Ticket could not be created due to an internal error") );
+ return ( 0, 0,
+ $self->loc("Ticket could not be created due to an internal error")
+ );
}
#Set the ticket's effective ID now that we've created it.
- my ( $val, $msg ) = $self->__Set( Field => 'EffectiveId', Value => ($args{'EffectiveId'} || $id ) );
+ my ( $val, $msg ) = $self->__Set(
+ Field => 'EffectiveId',
+ Value => ( $args{'EffectiveId'} || $id )
+ );
unless ($val) {
$RT::Logger->crit("$self ->Create couldn't set EffectiveId: $msg\n");
$RT::Handle->Rollback();
- return ( 0, 0, $self->loc( "Ticket could not be created due to an internal error") );
+ return ( 0, 0,
+ $self->loc("Ticket could not be created due to an internal error")
+ );
}
my $create_groups_ret = $self->_CreateTicketGroups();
unless ($create_groups_ret) {
$RT::Logger->crit( "Couldn't create ticket groups for ticket "
- . $self->Id
- . ". aborting Ticket creation." );
+ . $self->Id
+ . ". aborting Ticket creation." );
$RT::Handle->Rollback();
return ( 0, 0,
- $self->loc( "Ticket could not be created due to an internal error") );
+ $self->loc("Ticket could not be created due to an internal error")
+ );
}
- # Set the owner in the Groups table
- # We denormalize it into the Ticket table too because doing otherwise would
- # kill performance, bigtime. It gets kept in lockstep thanks to the magic of transactionalization
+# Set the owner in the Groups table
+# We denormalize it into the Ticket table too because doing otherwise would
+# kill performance, bigtime. It gets kept in lockstep thanks to the magic of transactionalization
- $self->OwnerGroup->_AddMember( PrincipalId => $Owner->PrincipalId , InsideTransaction => 1);
+ $self->OwnerGroup->_AddMember(
+ PrincipalId => $Owner->PrincipalId,
+ InsideTransaction => 1
+ );
# {{{ Deal with setting up watchers
-
foreach my $type ( "Cc", "AdminCc", "Requestor" ) {
- next unless (defined $args{$type});
- foreach my $watcher ( ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) ) {
+ next unless ( defined $args{$type} );
+ foreach my $watcher (
+ ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
+ {
- # If there is an empty entry in the list, let's get out of here.
- next unless $watcher;
+ # If there is an empty entry in the list, let's get out of here.
+ next unless $watcher;
- # we reason that all-digits number must be a principal id, not email
- # this is the only way to can add
- my $field = 'Email';
- $field = 'PrincipalId' if $watcher =~ /^\d+$/;
+ # we reason that all-digits number must be a principal id, not email
+ # this is the only way to can add
+ my $field = 'Email';
+ $field = 'PrincipalId' if $watcher =~ /^\d+$/;
- my ( $wval, $wmsg );
+ my ( $wval, $wmsg );
if ( $type eq 'AdminCc' ) {
- # Note that we're using AddWatcher, rather than _AddWatcher, as we
- # actually _want_ that ACL check. Otherwise, random ticket creators
- # could make themselves adminccs and maybe get ticket rights. that would
- # be poor
- ( $wval, $wmsg ) = $self->AddWatcher( Type => $type,
- $field => $watcher,
- Silent => 1 );
+ # Note that we're using AddWatcher, rather than _AddWatcher, as we
+ # actually _want_ that ACL check. Otherwise, random ticket creators
+ # could make themselves adminccs and maybe get ticket rights. that would
+ # be poor
+ ( $wval, $wmsg ) = $self->AddWatcher(
+ Type => $type,
+ $field => $watcher,
+ Silent => 1
+ );
}
else {
- ( $wval, $wmsg ) = $self->_AddWatcher( Type => $type,
- $field => $watcher,
- Silent => 1 );
+ ( $wval, $wmsg ) = $self->_AddWatcher(
+ Type => $type,
+ $field => $watcher,
+ Silent => 1
+ );
}
push @non_fatal_errors, $wmsg unless ($wval);
# }}}
# {{{ Deal with setting up links
-
foreach my $type ( keys %LINKTYPEMAP ) {
- next unless (defined $args{$type});
+ next unless ( defined $args{$type} );
foreach my $link (
ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
{
- my ( $wval, $wmsg ) = $self->AddLink(
+ # Check rights on the other end of the link if we must
+ # then run _AddLink that doesn't check for ACLs
+ if ( $RT::StrictLinkACL ) {
+ my ($val, $msg, $obj) = $self->__GetTicketFromURI( URI => $link );
+ unless ( $val ) {
+ push @non_fatal_errors, $msg;
+ next;
+ }
+ if ( $obj && !$obj->CurrentUserHasRight('ModifyTicket') ) {
+ push @non_fatal_errors, $self->loc('Linking. Permission denied');
+ next;
+ }
+ }
+
+ my ( $wval, $wmsg ) = $self->_AddLink(
Type => $LINKTYPEMAP{$type}->{'Type'},
$LINKTYPEMAP{$type}->{'Mode'} => $link,
Silent => 1
# }}}
- # {{{ Add all the custom fields
+ # {{{ Deal with auto-customer association
+
+ #unless we already have (a) customer(s)...
+ unless ( $self->Customers->Count ) {
+
+ #first find any requestors with emails but *without* customer targets
+ my @NoCust_Requestors =
+ grep { $_->EmailAddress && ! $_->Customers->Count }
+ @{ $self->_Requestors->UserMembersObj->ItemsArrayRef };
+
+ for my $Requestor (@NoCust_Requestors) {
+
+ #perhaps the stuff in here should be in a User method??
+ my @Customers =
+ &RT::URI::freeside::email_search( email=>$Requestor->EmailAddress );
+
+ foreach my $custnum ( map $_->{'custnum'}, @Customers ) {
+
+ ## false laziness w/RT/Interface/Web_Vendor.pm
+ my @link = ( 'Type' => 'MemberOf',
+ 'Target' => "freeside://freeside/cust_main/$custnum",
+ );
+
+ my( $val, $msg ) = $Requestor->_AddLink(@link);
+ #XXX should do something with $msg# push @non_fatal_errors, $msg;
+
+ }
+
+ }
+
+ #find any requestors with customer targets
+
+ my %cust_target = ();
+
+ my @Requestors =
+ grep { $_->Customers->Count }
+ @{ $self->_Requestors->UserMembersObj->ItemsArrayRef };
+
+ foreach my $Requestor ( @Requestors ) {
+ foreach my $cust_link ( @{ $Requestor->Customers->ItemsArrayRef } ) {
+ $cust_target{ $cust_link->Target } = 1;
+ }
+ }
+
+ #and then auto-associate this ticket with those customers
+
+ foreach my $cust_target ( keys %cust_target ) {
+
+ my @link = ( 'Type' => 'MemberOf',
+ #'Target' => "freeside://freeside/cust_main/$custnum",
+ 'Target' => $cust_target,
+ );
+
+ my( $val, $msg ) = $self->_AddLink(@link);
+ push @non_fatal_errors, $msg;
+
+ }
- foreach my $arg ( keys %args ) {
- next unless ( $arg =~ /^CustomField-(\d+)$/i );
- my $cfid = $1;
- foreach
- my $value ( ref( $args{$arg} ) ? @{ $args{$arg} } : ( $args{$arg} ) ) {
- next unless (length($value));
- $self->_AddCustomFieldValue( Field => $cfid,
- Value => $value,
- RecordTransaction => 0
- );
}
+
+ # }}}
+
+ # {{{ Add all the custom fields
+
+ foreach my $arg ( keys %args ) {
+ next unless ( $arg =~ /^CustomField-(\d+)$/i );
+ my $cfid = $1;
+ foreach
+ my $value ( UNIVERSAL::isa( $args{$arg} => 'ARRAY' ) ? @{ $args{$arg} } : ( $args{$arg} ) )
+ {
+ next unless ( length($value) );
+
+ # Allow passing in uploaded LargeContent etc by hash reference
+ $self->_AddCustomFieldValue(
+ (UNIVERSAL::isa( $value => 'HASH' )
+ ? %$value
+ : (Value => $value)
+ ),
+ Field => $cfid,
+ RecordTransaction => 0,
+ );
+ }
}
+
# }}}
if ( $args{'_RecordTransaction'} ) {
+
# {{{ Add a transaction for the create
my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
Type => "Create",
- TimeTaken => 0,
+ TimeTaken => $args{'TimeWorked'},
MIMEObj => $args{'MIMEObj'}
);
-
if ( $self->Id && $Trans ) {
- $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name );
- $ErrStr = join ( "\n", $ErrStr, @non_fatal_errors );
- $RT::Logger->info("Ticket ".$self->Id. " created in queue '".$QueueObj->Name."' by ".$self->CurrentUser->Name);
+ $TransObj->UpdateCustomFields(ARGSRef => \%args);
+
+ $RT::Logger->info( "Ticket " . $self->Id . " created in queue '" . $QueueObj->Name . "' by " . $self->CurrentUser->Name );
+ $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name );
+ $ErrStr = join( "\n", $ErrStr, @non_fatal_errors );
}
else {
$RT::Handle->Rollback();
- # TODO where does this get errstr from?
+ $ErrStr = join( "\n", $ErrStr, @non_fatal_errors );
$RT::Logger->error("Ticket couldn't be created: $ErrStr");
return ( 0, 0, $self->loc( "Ticket could not be created due to an internal error"));
}
$RT::Handle->Commit();
return ( $self->Id, $TransObj->Id, $ErrStr );
+
# }}}
}
else {
# Not going to record a transaction
$RT::Handle->Commit();
$ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name );
- $ErrStr = join ( "\n", $ErrStr, @non_fatal_errors );
- return ( $self->Id, $0, $ErrStr );
+ $ErrStr = join( "\n", $ErrStr, @non_fatal_errors );
+ return ( $self->Id, 0, $ErrStr );
}
}
# }}}
-# {{{ sub CreateFromEmailMessage
-
-
-=head2 CreateFromEmailMessage { Message, Queue, ExtractActorFromHeaders }
-
-This code replaces what was once a large part of the email gateway.
-It takes an email message as a parameter, parses out the sender, subject
-and a MIME object. It then creates a ticket based on those attributes
-
-=cut
-
-sub CreateFromEmailMessage {
- my $self = shift;
- my %args = ( Message => undef,
- Queue => undef,
- ExtractActorFromSender => undef,
- @_ );
-
-
- # Pull out requestor
-
- # Pull out Cc?
-
- #
-
-
-}
-
-# }}}
-
-
-# {{{ CreateFrom822
-
-=head2 FORMAT
-
-CreateTickets uses the template as a template for an ordered set of tickets
-to create. The basic format is as follows:
-
-
- ===Create-Ticket: identifier
- Param: Value
- Param2: Value
- Param3: Value
- Content: Blah
- blah
- blah
- ENDOFCONTENT
-=head2 Acceptable fields
-
-A complete list of acceptable fields for this beastie:
-
-
- * Queue => Name or id# of a queue
- Subject => A text string
- Status => A valid status. defaults to 'new'
-
- Due => Dates can be specified in seconds since the epoch
- to be handled literally or in a semi-free textual
- format which RT will attempt to parse.
- Starts =>
- Started =>
- Resolved =>
- Owner => Username or id of an RT user who can and should own
- this ticket
- + Requestor => Email address
- + Cc => Email address
- + AdminCc => Email address
- TimeWorked =>
- TimeEstimated =>
- TimeLeft =>
- InitialPriority =>
- FinalPriority =>
- Type =>
- + DependsOn =>
- + DependedOnBy =>
- + RefersTo =>
- + ReferredToBy =>
- + Members =>
- + MemberOf =>
- Content => content. Can extend to multiple lines. Everything
- within a template after a Content: header is treated
- as content until we hit a line containing only
- ENDOFCONTENT
- ContentType => the content-type of the Content field
- CustomField-<id#> => custom field value
-
-Fields marked with an * are required.
-
-Fields marked with a + man have multiple values, simply
-by repeating the fieldname on a new line with an additional value.
-
-
-When parsed, field names are converted to lowercase and have -s stripped.
-Refers-To, RefersTo, refersto, refers-to and r-e-f-er-s-tO will all
-be treated as the same thing.
-
-
-=begin testing
-
-use_ok(RT::Ticket);
-
-=end testing
-
-
-=cut
-
-sub CreateFrom822 {
- my $self = shift;
- my $content = shift;
-
-
-
- my %args = $self->_Parse822HeadersForAttributes($content);
-
- # Now we have a %args to work with.
- # Make sure we have at least the minimum set of
- # reasonable data and do our thang
- my $ticket = RT::Ticket->new($RT::SystemUser);
-
- my %ticketargs = (
- Queue => $args{'queue'},
- Subject => $args{'subject'},
- Status => $args{'status'},
- Due => $args{'due'},
- Starts => $args{'starts'},
- Started => $args{'started'},
- Resolved => $args{'resolved'},
- Owner => $args{'owner'},
- Requestor => $args{'requestor'},
- Cc => $args{'cc'},
- AdminCc => $args{'admincc'},
- TimeWorked => $args{'timeworked'},
- TimeEstimated => $args{'timeestimated'},
- TimeLeft => $args{'timeleft'},
- InitialPriority => $args{'initialpriority'},
- FinalPriority => $args{'finalpriority'},
- Type => $args{'type'},
- DependsOn => $args{'dependson'},
- DependedOnBy => $args{'dependedonby'},
- RefersTo => $args{'refersto'},
- ReferredToBy => $args{'referredtoby'},
- Members => $args{'members'},
- MemberOf => $args{'memberof'},
- MIMEObj => $args{'mimeobj'}
- );
-
- # Add custom field entries to %ticketargs.
- # TODO: allow named custom fields
- map {
- /^customfield-(\d+)$/
- && ( $ticketargs{ "CustomField-" . $1 } = $args{$_} );
- } keys(%args);
-
- my ( $id, $transid, $msg ) = $ticket->Create(%ticketargs);
- unless ($id) {
- $RT::Logger->error( "Couldn't create a related ticket for "
- . $self->TicketObj->Id . " "
- . $msg );
- }
-
- return (1);
-}
-
-# }}}
# {{{ UpdateFrom822
$ticketargs{'Queue'} = $tempqueue->Id() if ( $tempqueue->id );
}
- # die "updaterecordobject is a webui thingy";
my @results;
foreach my $attribute (@attribs) {
}
}
+ my $create_groups_ret = $self->_CreateTicketGroups();
+ unless ($create_groups_ret) {
+ $RT::Logger->crit(
+ "Couldn't create ticket groups for ticket " . $self->Id );
+ }
+
+ $self->OwnerGroup->_AddMember( PrincipalId => $Owner->PrincipalId );
+
my $watcher;
foreach $watcher ( @{ $args{'Cc'} } ) {
- $self->_AddWatcher( Type => 'Cc', Person => $watcher, Silent => 1 );
+ $self->_AddWatcher( Type => 'Cc', Email => $watcher, Silent => 1 );
}
foreach $watcher ( @{ $args{'AdminCc'} } ) {
- $self->_AddWatcher( Type => 'AdminCc', Person => $watcher,
+ $self->_AddWatcher( Type => 'AdminCc', Email => $watcher,
Silent => 1 );
}
foreach $watcher ( @{ $args{'Requestor'} } ) {
- $self->_AddWatcher( Type => 'Requestor', Person => $watcher,
+ $self->_AddWatcher( Type => 'Requestor', Email => $watcher,
Silent => 1 );
}
# }}}
-
# {{{ Routines dealing with watchers.
# {{{ _CreateTicketGroups
=head2 _CreateTicketGroups
-Create the ticket groups and relationships for this ticket.
+Create the ticket groups and links for this ticket.
This routine expects to be called from Ticket->Create _inside of a transaction_
It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
@_
);
+ # XXX, FIXME, BUG: if only email is provided then we only check
+ # for ModifyTicket right, but must try to get PrincipalId and
+ # check Watch* rights too if user exist
+
# {{{ Check ACLS
#If the watcher we're trying to add is for the current user
- if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
+ if ( $self->CurrentUser->PrincipalId == ($args{'PrincipalId'} || 0)
+ or lc( $self->CurrentUser->UserObj->EmailAddress )
+ eq lc( RT::User->CanonicalizeEmailAddress( $args{'Email'} ) || '' ) )
+ {
# If it's an AdminCc and they don't have
# 'WatchAsAdminCc' or 'ModifyTicket', bail
if ( $args{'Type'} eq 'AdminCc' ) {
}
}
else {
- $RT::Logger->warn( "$self -> AddWatcher got passed a bogus type");
+ $RT::Logger->warning( "$self -> AddWatcher got passed a bogus type");
return ( 0, $self->loc('Error in parameters to Ticket->AddWatcher') );
}
}
if ($args{'Email'}) {
my $user = RT::User->new($RT::SystemUser);
my ($pid, $msg) = $user->LoadOrCreateByEmail($args{'Email'});
+ # If we can't load the user by email address, let's try to load by username
+ unless ($pid) {
+ ($pid,$msg) = $user->Load($args{'Email'})
+ }
if ($pid) {
$args{'PrincipalId'} = $pid;
}
sub DeleteWatcher {
my $self = shift;
- my %args = ( Type => undef,
+ my %args = ( Type => undef,
PrincipalId => undef,
- Email => undef,
+ Email => undef,
@_ );
- unless ($args{'PrincipalId'} || $args{'Email'} ) {
- return(0, $self->loc("No principal specified"));
+ unless ( $args{'PrincipalId'} || $args{'Email'} ) {
+ return ( 0, $self->loc("No principal specified") );
}
- my $principal = RT::Principal->new($self->CurrentUser);
- if ($args{'PrincipalId'} ) {
+ my $principal = RT::Principal->new( $self->CurrentUser );
+ if ( $args{'PrincipalId'} ) {
- $principal->Load($args{'PrincipalId'});
- } else {
- my $user = RT::User->new($self->CurrentUser);
- $user->LoadByEmail($args{'Email'});
- $principal->Load($user->Id);
+ $principal->Load( $args{'PrincipalId'} );
+ }
+ else {
+ my $user = RT::User->new( $self->CurrentUser );
+ $user->LoadByEmail( $args{'Email'} );
+ $principal->Load( $user->Id );
}
+
# If we can't find this watcher, we need to bail.
- unless ($principal->Id) {
- return(0, $self->loc("Could not find that principal"));
+ unless ( $principal->Id ) {
+ return ( 0, $self->loc("Could not find that principal") );
}
- my $group = RT::Group->new($self->CurrentUser);
- $group->LoadTicketRoleGroup(Type => $args{'Type'}, Ticket => $self->Id);
- unless ($group->id) {
- return(0,$self->loc("Group not found"));
+ my $group = RT::Group->new( $self->CurrentUser );
+ $group->LoadTicketRoleGroup( Type => $args{'Type'}, Ticket => $self->Id );
+ unless ( $group->id ) {
+ return ( 0, $self->loc("Group not found") );
}
# {{{ Check ACLS
#If the watcher we're trying to add is for the current user
- if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
- # If it's an AdminCc and they don't have
+ if ( $self->CurrentUser->PrincipalId == $principal->id ) {
+
+ # If it's an AdminCc and they don't have
# 'WatchAsAdminCc' or 'ModifyTicket', bail
if ( $args{'Type'} eq 'AdminCc' ) {
- unless ( $self->CurrentUserHasRight('ModifyTicket')
- or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
- return ( 0, $self->loc('Permission Denied'))
+ unless ( $self->CurrentUserHasRight('ModifyTicket')
+ or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
+ return ( 0, $self->loc('Permission Denied') );
}
}
# If it's a Requestor or Cc and they don't have
# 'Watch' or 'ModifyTicket', bail
- elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
- unless ( $self->CurrentUserHasRight('ModifyTicket')
- or $self->CurrentUserHasRight('Watch') ) {
- return ( 0, $self->loc('Permission Denied'))
+ elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) )
+ {
+ unless ( $self->CurrentUserHasRight('ModifyTicket')
+ or $self->CurrentUserHasRight('Watch') ) {
+ return ( 0, $self->loc('Permission Denied') );
}
}
else {
- $RT::Logger->warn( "$self -> DeleteWatcher got passed a bogus type");
- return ( 0, $self->loc('Error in parameters to Ticket->DelWatcher') );
+ $RT::Logger->warn("$self -> DeleteWatcher got passed a bogus type");
+ return ( 0,
+ $self->loc('Error in parameters to Ticket->DeleteWatcher') );
}
}
- # If the watcher isn't the current user
+ # If the watcher isn't the current user
# and the current user doesn't have 'ModifyTicket' bail
else {
unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
# }}}
-
# see if this user is already a watcher.
- unless ( $group->HasMember($principal)) {
- return ( 0,
- $self->loc('That principal is not a [_1] for this ticket', $args{'Type'}) );
+ unless ( $group->HasMember($principal) ) {
+ return ( 0,
+ $self->loc( 'That principal is not a [_1] for this ticket',
+ $args{'Type'} ) );
}
- my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
+ my ( $m_id, $m_msg ) = $group->_DeleteMember( $principal->Id );
unless ($m_id) {
- $RT::Logger->error("Failed to delete ".$principal->Id.
- " as a member of group ".$group->Id."\n".$m_msg);
+ $RT::Logger->error( "Failed to delete "
+ . $principal->Id
+ . " as a member of group "
+ . $group->Id . "\n"
+ . $m_msg );
- return ( 0, $self->loc('Could not remove that principal as a [_1] for this ticket', $args{'Type'}) );
+ return (0,
+ $self->loc(
+ 'Could not remove that principal as a [_1] for this ticket',
+ $args{'Type'} ) );
}
unless ( $args{'Silent'} ) {
- $self->_NewTransaction(
- Type => 'DelWatcher',
- OldValue => $principal->Id,
- Field => $args{'Type'}
- );
+ $self->_NewTransaction( Type => 'DelWatcher',
+ OldValue => $principal->Id,
+ Field => $args{'Type'} );
}
- return ( 1, $self->loc("[_1] is no longer a [_2] for this ticket.", $principal->Object->Name, $args{'Type'} ));
+ return ( 1,
+ $self->loc( "[_1] is no longer a [_2] for this ticket.",
+ $principal->Object->Name,
+ $args{'Type'} ) );
}
-
# }}}
-# {{{ a set of [foo]AsString subs that will return the various sorts of watchers for a ticket/queue as a comma delineated string
+=head2 SquelchMailTo [EMAIL]
-=head2 RequestorAddresses
+Takes an optional email address to never email about updates to this ticket.
- B<Returns> String: All Ticket Requestor email addresses as a string.
-=cut
+Returns an array of the RT::Attribute objects for this ticket's 'SquelchMailTo' attributes.
-sub RequestorAddresses {
- my $self = shift;
+=begin testing
- unless ( $self->CurrentUserHasRight('ShowTicket') ) {
- return undef;
- }
+my $t = RT::Ticket->new($RT::SystemUser);
+ok($t->Create(Queue => 'general', Subject => 'SquelchTest'));
- return ( $self->Requestors->MemberEmailAddressesAsString );
+is($#{$t->SquelchMailTo}, -1, "The ticket has no squelched recipients");
+
+my @returned = $t->SquelchMailTo('nobody@example.com');
+
+is($#returned, 0, "The ticket has one squelched recipients");
+
+my @names = $t->Attributes->Names;
+is(shift @names, 'SquelchMailTo', "The attribute we have is SquelchMailTo");
+@returned = $t->SquelchMailTo('nobody@example.com');
+
+
+is($#returned, 0, "The ticket has one squelched recipients");
+
+@names = $t->Attributes->Names;
+is(shift @names, 'SquelchMailTo', "The attribute we have is SquelchMailTo");
+
+
+my ($ret, $msg) = $t->UnsquelchMailTo('nobody@example.com');
+ok($ret, "Removed nobody as a squelched recipient - ".$msg);
+@returned = $t->SquelchMailTo();
+is($#returned, -1, "The ticket has no squelched recipients". join(',',@returned));
+
+
+=end testing
+
+=cut
+
+sub SquelchMailTo {
+ my $self = shift;
+ if (@_) {
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return undef;
+ }
+ my $attr = shift;
+ $self->AddAttribute( Name => 'SquelchMailTo', Content => $attr )
+ unless grep { $_->Content eq $attr }
+ $self->Attributes->Named('SquelchMailTo');
+
+ }
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return undef;
+ }
+ my @attributes = $self->Attributes->Named('SquelchMailTo');
+ return (@attributes);
+}
+
+
+=head2 UnsquelchMailTo ADDRESS
+
+Takes an address and removes it from this ticket's "SquelchMailTo" list. If an address appears multiple times, each instance is removed.
+
+Returns a tuple of (status, message)
+
+=cut
+
+sub UnsquelchMailTo {
+ my $self = shift;
+
+ my $address = shift;
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ my ($val, $msg) = $self->Attributes->DeleteEntry ( Name => 'SquelchMailTo', Content => $address);
+ return ($val, $msg);
+}
+
+
+# {{{ a set of [foo]AsString subs that will return the various sorts of watchers for a ticket/queue as a comma delineated string
+
+=head2 RequestorAddresses
+
+ B<Returns> String: All Ticket Requestor email addresses as a string.
+
+=cut
+
+sub RequestorAddresses {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return undef;
+ }
+
+ return ( $self->Requestors->MemberEmailAddressesAsString );
}
# }}}
+# {{{ sub _Requestors
+
+=head2 _Requestors
+
+Private non-ACLed variant of Reqeustors so that we can look them up for the
+purposes of customer auto-association during create.
+
+=cut
+
+sub _Requestors {
+ my $self = shift;
+
+ my $group = RT::Group->new($RT::SystemUser);
+ $group->LoadTicketRoleGroup(Type => 'Requestor', Ticket => $self->Id);
+ return ($group);
+}
+
+# }}}
+
# {{{ sub Cc
=head2 Cc
Returns true if the specified principal (or the one corresponding to the
specified address) is a member of the group Type for this ticket.
+XX TODO: This should be Memoized.
+
=cut
sub IsWatcher {
)
)
{
- $self->Untake();
+ my $clone = RT::Ticket->new( $RT::SystemUser );
+ $clone->Load( $self->Id );
+ unless ( $clone->Id ) {
+ return ( 0, $self->loc("Couldn't load copy of ticket #[_1].", $self->Id) );
+ }
+ my ($status, $msg) = $clone->SetOwner( $RT::Nobody->Id, 'Force' );
+ $RT::Logger->error("Couldn't set owner on queue change: $msg") unless $status;
}
return ( $self->_Set( Field => 'Queue', Value => $NewQueueObj->Id() ) );
-
}
# }}}
my $time = shift || 0;
unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
- return ( 0, self->loc("Permission Denied") );
+ return ( 0, $self->loc("Permission Denied") );
}
#We create a date object to catch date weirdness
my $time_obj = new RT::Date( $self->CurrentUser() );
- if ( $time != 0 ) {
+ if ( $time ) {
$time_obj->Set( Format => 'ISO', Value => $time );
}
else {
If MIMEObj is undefined, Content will be used to build a MIME::Entity for this
commentl
-MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content.
+MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content, DryRun
+
+If DryRun is defined, this update WILL NOT BE RECORDED. Scrips will not be committed.
+They will, however, be prepared and you'll be able to access them through the TransactionObj
+
+Returns: Transaction id, Error Message, Transaction Object
+(note the different order from Create()!)
=cut
-## Please see file perltidy.ERR
sub Comment {
my $self = shift;
MIMEObj => undef,
Content => undef,
TimeTaken => 0,
+ DryRun => 0,
@_ );
unless ( ( $self->CurrentUserHasRight('CommentOnTicket') )
or ( $self->CurrentUserHasRight('ModifyTicket') ) ) {
- return ( 0, $self->loc("Permission Denied") );
+ return ( 0, $self->loc("Permission Denied"), undef );
}
+ $args{'NoteType'} = 'Comment';
- unless ( $args{'MIMEObj'} ) {
- if ( $args{'Content'} ) {
- use MIME::Entity;
- $args{'MIMEObj'} = MIME::Entity->build(
- Data => ( ref $args{'Content'} ? $args{'Content'} : [ $args{'Content'} ] )
- );
- }
- else {
-
- return ( 0, $self->loc("No correspondence attached") );
- }
+ if ($args{'DryRun'}) {
+ $RT::Handle->BeginTransaction();
+ $args{'CommitScrips'} = 0;
}
- RT::I18N::SetMIMEEntityToUTF8($args{'MIMEObj'}); # convert text parts into utf-8
-
- # If we've been passed in CcMessageTo and BccMessageTo fields,
- # add them to the mime object for passing on to the transaction handler
- # The "NotifyOtherRecipients" scripAction will look for RT--Send-Cc: and
- # RT-Send-Bcc: headers
-
- $args{'MIMEObj'}->head->add( 'RT-Send-Cc',
- RT::User::CanonicalizeEmailAddress(undef, $args{'CcMessageTo'}) )
- if defined $args{'CcMessageTo'};
- $args{'MIMEObj'}->head->add( 'RT-Send-Bcc',
- RT::User::CanonicalizeEmailAddress(undef, $args{'BccMessageTo'}) )
- if defined $args{'BccMessageTo'};
-
- #Record the correspondence (write the transaction)
- my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
- Type => 'Comment',
- Data => ( $args{'MIMEObj'}->head->get('subject') || 'No Subject' ),
- TimeTaken => $args{'TimeTaken'},
- MIMEObj => $args{'MIMEObj'}
- );
+ my @results = $self->_RecordNote(%args);
+ if ($args{'DryRun'}) {
+ $RT::Handle->Rollback();
+ }
- return ( $Trans, $self->loc("The comment has been recorded") );
+ return(@results);
}
-
# }}}
# {{{ sub Correspond
Takes a hashref with the following attributes:
-MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content
+MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content, DryRun
if there's no MIMEObj, Content is used to build a MIME::Entity object
+If DryRun is defined, this update WILL NOT BE RECORDED. Scrips will not be committed.
+They will, however, be prepared and you'll be able to access them through the TransactionObj
+
+Returns: Transaction id, Error Message, Transaction Object
+(note the different order from Create()!)
+
=cut
unless ( ( $self->CurrentUserHasRight('ReplyToTicket') )
or ( $self->CurrentUserHasRight('ModifyTicket') ) ) {
- return ( 0, $self->loc("Permission Denied") );
+ return ( 0, $self->loc("Permission Denied"), undef );
}
- unless ( $args{'MIMEObj'} ) {
- if ( $args{'Content'} ) {
- use MIME::Entity;
- $args{'MIMEObj'} = MIME::Entity->build(
- Data => ( ref $args{'Content'} ? $args{'Content'} : [ $args{'Content'} ] )
- );
-
- }
- else {
-
- return ( 0, $self->loc("No correspondence attached") );
- }
+ $args{'NoteType'} = 'Correspond';
+ if ($args{'DryRun'}) {
+ $RT::Handle->BeginTransaction();
+ $args{'CommitScrips'} = 0;
}
- RT::I18N::SetMIMEEntityToUTF8($args{'MIMEObj'}); # convert text parts into utf-8
-
- # If we've been passed in CcMessageTo and BccMessageTo fields,
- # add them to the mime object for passing on to the transaction handler
- # The "NotifyOtherRecipients" scripAction will look for RT-Send-Cc: and RT-Send-Bcc:
- # headers
-
- $args{'MIMEObj'}->head->add( 'RT-Send-Cc',
- RT::User::CanonicalizeEmailAddress(undef, $args{'CcMessageTo'}) )
- if defined $args{'CcMessageTo'};
- $args{'MIMEObj'}->head->add( 'RT-Send-Bcc',
- RT::User::CanonicalizeEmailAddress(undef, $args{'BccMessageTo'}) )
- if defined $args{'BccMessageTo'};
-
- #Record the correspondence (write the transaction)
- my ( $Trans, $msg, $TransObj ) = $self->_NewTransaction(
- Type => 'Correspond',
- Data => ( $args{'MIMEObj'}->head->get('subject') || 'No Subject' ),
- TimeTaken => $args{'TimeTaken'},
- MIMEObj => $args{'MIMEObj'} );
-
- unless ($Trans) {
- $RT::Logger->err( "$self couldn't init a transaction $msg");
- return ( $Trans, $self->loc("correspondence (probably) not sent"), $args{'MIMEObj'} );
- }
+ my @results = $self->_RecordNote(%args);
#Set the last told date to now if this isn't mail from the requestor.
#TODO: Note that this will wrongly ack mail from any non-requestor as a "told"
+ $self->_SetTold unless ( $self->IsRequestor($self->CurrentUser->id));
- unless ( $TransObj->IsInbound ) {
- $self->_SetTold;
+ if ($args{'DryRun'}) {
+ $RT::Handle->Rollback();
}
- return ( $Trans, $self->loc("correspondence sent") );
-}
-
-# }}}
-
-# }}}
-
-# {{{ Routines dealing with Links and Relations between tickets
-
-# {{{ Link Collections
-
-# {{{ sub Members
-
-=head2 Members
-
- This returns an RT::Links object which references all the tickets
-which are 'MembersOf' this ticket
-
-=cut
-
-sub Members {
- my $self = shift;
- return ( $self->_Links( 'Target', 'MemberOf' ) );
-}
-
-# }}}
-
-# {{{ sub MemberOf
-
-=head2 MemberOf
-
- This returns an RT::Links object which references all the tickets that this
-ticket is a 'MemberOf'
-
-=cut
-
-sub MemberOf {
- my $self = shift;
- return ( $self->_Links( 'Base', 'MemberOf' ) );
-}
-
-# }}}
-
-# {{{ RefersTo
-
-=head2 RefersTo
+ return (@results);
- This returns an RT::Links object which shows all references for which this ticket is a base
-
-=cut
-
-sub RefersTo {
- my $self = shift;
- return ( $self->_Links( 'Base', 'RefersTo' ) );
}
# }}}
-# {{{ ReferredToBy
-
-=head2 ReferredToBy
-
- This returns an RT::Links object which shows all references for which this ticket is a target
-
-=cut
-
-sub ReferredToBy {
- my $self = shift;
- return ( $self->_Links( 'Target', 'RefersTo' ) );
-}
-
-# }}}
+# {{{ sub _RecordNote
-# {{{ DependedOnBy
+=head2 _RecordNote
-=head2 DependedOnBy
+the meat of both comment and correspond.
- This returns an RT::Links object which references all the tickets that depend on this one
+Performs no access control checks. hence, dangerous.
=cut
-sub DependedOnBy {
- my $self = shift;
- return ( $self->_Links( 'Target', 'DependsOn' ) );
-}
-
-# }}}
-
-
-
-=head2 HasUnresolvedDependencies
-
- Takes a paramhash of Type (default to '__any'). Returns true if
-$self->UnresolvedDependencies returns an object with one or more members
-of that type. Returns false otherwise
-
-
-=begin testing
-
-my $t1 = RT::Ticket->new($RT::SystemUser);
-my ($id, $trans, $msg) = $t1->Create(Subject => 'DepTest1', Queue => 'general');
-ok($id, "Created dep test 1 - $msg");
-
-my $t2 = RT::Ticket->new($RT::SystemUser);
-my ($id2, $trans, $msg2) = $t2->Create(Subject => 'DepTest2', Queue => 'general');
-ok($id2, "Created dep test 2 - $msg2");
-my $t3 = RT::Ticket->new($RT::SystemUser);
-my ($id3, $trans, $msg3) = $t3->Create(Subject => 'DepTest3', Queue => 'general', Type => 'approval');
-ok($id3, "Created dep test 3 - $msg3");
-
-ok ($t1->AddLink( Type => 'DependsOn', Target => $t2->id));
-ok ($t1->AddLink( Type => 'DependsOn', Target => $t3->id));
-
-ok ($t1->HasUnresolvedDependencies, "Ticket ".$t1->Id." has unresolved deps");
-ok (!$t1->HasUnresolvedDependencies( Type => 'blah' ), "Ticket ".$t1->Id." has no unresolved blahs");
-ok ($t1->HasUnresolvedDependencies( Type => 'approval' ), "Ticket ".$t1->Id." has unresolved approvals");
-ok (!$t2->HasUnresolvedDependencies, "Ticket ".$t2->Id." has no unresolved deps");
-my ($rid, $rmsg)= $t1->Resolve();
-ok(!$rid, $rmsg);
-ok($t2->Resolve);
-($rid, $rmsg)= $t1->Resolve();
-ok(!$rid, $rmsg);
-ok($t3->Resolve);
-($rid, $rmsg)= $t1->Resolve();
-ok($rid, $rmsg);
-
-
-=end testing
-
-=cut
+sub _RecordNote {
-sub HasUnresolvedDependencies {
my $self = shift;
- my %args = (
- Type => undef,
- @_
- );
-
- my $deps = $self->UnresolvedDependencies;
-
- if ($args{Type}) {
- $deps->Limit( FIELD => 'Type',
- OPERATOR => '=',
- VALUE => $args{Type});
- }
- else {
- $deps->IgnoreType;
- }
+ my %args = ( CcMessageTo => undef,
+ BccMessageTo => undef,
+ MIMEObj => undef,
+ Content => undef,
+ TimeTaken => 0,
+ CommitScrips => 1,
+ @_ );
- if ($deps->Count > 0) {
- return 1;
- }
- else {
- return (undef);
+ unless ( $args{'MIMEObj'} || $args{'Content'} ) {
+ return ( 0, $self->loc("No message attached"), undef );
}
-}
-
-
-# {{{ UnresolvedDependencies
-
-=head2 UnresolvedDependencies
+ unless ( $args{'MIMEObj'} ) {
+ $args{'MIMEObj'} = MIME::Entity->build( Data => (
+ ref $args{'Content'}
+ ? $args{'Content'}
+ : [ $args{'Content'} ]
+ ) );
+ }
-Returns an RT::Tickets object of tickets which this ticket depends on
-and which have a status of new, open or stalled. (That list comes from
-RT::Queue->ActiveStatusArray
+ # convert text parts into utf-8
+ RT::I18N::SetMIMEEntityToUTF8( $args{'MIMEObj'} );
-=cut
+# If we've been passed in CcMessageTo and BccMessageTo fields,
+# add them to the mime object for passing on to the transaction handler
+# The "NotifyOtherRecipients" scripAction will look for RT-Send-Cc: and RT-Send-Bcc:
+# headers
-sub UnresolvedDependencies {
- my $self = shift;
- my $deps = RT::Tickets->new($self->CurrentUser);
+ foreach my $type (qw/Cc Bcc/) {
+ if ( defined $args{ $type . 'MessageTo' } ) {
- my @live_statuses = RT::Queue->ActiveStatusArray();
- foreach my $status (@live_statuses) {
- $deps->LimitStatus(VALUE => $status);
+ my $addresses = join ', ', (
+ map { RT::User->CanonicalizeEmailAddress( $_->address ) }
+ Mail::Address->parse( $args{ $type . 'MessageTo' } ) );
+ $args{'MIMEObj'}->head->add( 'RT-Send-' . $type, $addresses );
+ }
}
- $deps->LimitDependedOnBy($self->Id);
-
- return($deps);
-
-}
-# }}}
-
-# {{{ AllDependedOnBy
-
-=head2 AllDependedOnBy
-
-Returns an array of RT::Ticket objects which (directly or indirectly)
-depends on this ticket; takes an optional 'Type' argument in the param
-hash, which will limit returned tickets to that type, as well as cause
-tickets with that type to serve as 'leaf' nodes that stops the recursive
-dependency search.
-
-=cut
+ # If this is from an external source, we need to come up with its
+ # internal Message-ID now, so all emails sent because of this
+ # message have a common Message-ID
+ unless ( ($args{'MIMEObj'}->head->get('Message-ID') || '')
+ =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$RT::Organization>/ )
+ {
+ $args{'MIMEObj'}->head->set( 'RT-Message-ID',
+ "<rt-"
+ . $RT::VERSION . "-"
+ . $$ . "-"
+ . CORE::time() . "-"
+ . int(rand(2000)) . '.'
+ . $self->id . "-"
+ . "0" . "-" # Scrip
+ . "0" . "@" # Email sent
+ . $RT::Organization
+ . ">" );
+ }
-sub AllDependedOnBy {
- my $self = shift;
- my $dep = $self->DependedOnBy;
- my %args = (
- Type => undef,
- _found => {},
- _top => 1,
- @_
+ #Record the correspondence (write the transaction)
+ my ( $Trans, $msg, $TransObj ) = $self->_NewTransaction(
+ Type => $args{'NoteType'},
+ Data => ( $args{'MIMEObj'}->head->get('subject') || 'No Subject' ),
+ TimeTaken => $args{'TimeTaken'},
+ MIMEObj => $args{'MIMEObj'},
+ CommitScrips => $args{'CommitScrips'},
);
- while (my $link = $dep->Next()) {
- next unless ($link->BaseURI->IsLocal());
- next if $args{_found}{$link->BaseObj->Id};
-
- if (!$args{Type}) {
- $args{_found}{$link->BaseObj->Id} = $link->BaseObj;
- $link->BaseObj->AllDependedOnBy( %args, _top => 0 );
- }
- elsif ($link->BaseObj->Type eq $args{Type}) {
- $args{_found}{$link->BaseObj->Id} = $link->BaseObj;
- }
- else {
- $link->BaseObj->AllDependedOnBy( %args, _top => 0 );
- }
+ unless ($Trans) {
+ $RT::Logger->err("$self couldn't init a transaction $msg");
+ return ( $Trans, $self->loc("Message could not be recorded"), undef );
}
- if ($args{_top}) {
- return map { $args{_found}{$_} } sort keys %{$args{_found}};
- }
- else {
- return 1;
- }
+ return ( $Trans, $self->loc("Message recorded"), $TransObj );
}
# }}}
-# {{{ DependsOn
-
-=head2 DependsOn
-
- This returns an RT::Links object which references all the tickets that this ticket depends on
-
-=cut
-
-sub DependsOn {
- my $self = shift;
- return ( $self->_Links( 'Base', 'DependsOn' ) );
-}
-
# }}}
-
-
-
# {{{ sub _Links
sub _Links {
unless ( $self->{"$field$type"} ) {
$self->{"$field$type"} = new RT::Links( $self->CurrentUser );
- if ( $self->CurrentUserHasRight('ShowTicket') ) {
+
+ #not sure what this ACL was supposed to do... but returning the
+ # bare (unlimited) RT::Links certainly seems wrong, it causes the
+ # $Ticket->Customers method during creation to return results for every
+ # ticket...
+ #if ( $self->CurrentUserHasRight('ShowTicket') ) {
+
# Maybe this ticket is a merged ticket
my $Tickets = new RT::Tickets( $self->CurrentUser );
# at least to myself
$self->{"$field$type"}->Limit( FIELD => 'Type',
VALUE => $type )
if ($type);
- }
+ #}
}
return ( $self->{"$field$type"} );
}
# }}}
-# }}}
-
# {{{ sub DeleteLink
=head2 DeleteLink
@_
);
- #check acls
- unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
- $RT::Logger->debug("No permission to delete links\n");
- return ( 0, $self->loc('Permission Denied'))
+ unless ( $args{'Target'} || $args{'Base'} ) {
+ $RT::Logger->error("Base or Target must be specified\n");
+ return ( 0, $self->loc('Either base or target must be specified') );
+ }
+ #check acls
+ my $right = 0;
+ $right++ if $self->CurrentUserHasRight('ModifyTicket');
+ if ( !$right && $RT::StrictLinkACL ) {
+ return ( 0, $self->loc("Permission Denied") );
}
- #we want one of base and target. we don't care which
- #but we only want _one_
+ # If the other URI is an RT::Ticket, we want to make sure the user
+ # can modify it too...
+ my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
+ return (0, $msg) unless $status;
+ if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
+ $right++;
+ }
+ if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
+ ( $RT::StrictLinkACL && $right < 2 ) )
+ {
+ return ( 0, $self->loc("Permission Denied") );
+ }
- my $direction;
- my $remote_link;
+ my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
- if ( $args{'Base'} and $args{'Target'} ) {
- $RT::Logger->debug("$self ->_DeleteLink. got both Base and Target\n");
- return ( 0, $self->loc("Can't specifiy both base and target") );
+ if ( !$val ) {
+ $RT::Logger->debug("Couldn't find that link\n");
+ return ( 0, $Msg );
}
- elsif ( $args{'Base'} ) {
- $args{'Target'} = $self->URI();
+
+ my ($direction, $remote_link);
+
+ if ( $args{'Base'} ) {
$remote_link = $args{'Base'};
$direction = 'Target';
}
elsif ( $args{'Target'} ) {
- $args{'Base'} = $self->URI();
$remote_link = $args{'Target'};
$direction='Base';
}
- else {
- $RT::Logger->debug("$self: Base or Target must be specified\n");
- return ( 0, $self->loc('Either base or target must be specified') );
- }
- my $link = new RT::Link( $self->CurrentUser );
- $RT::Logger->debug( "Trying to load link: " . $args{'Base'} . " " . $args{'Type'} . " " . $args{'Target'} . "\n" );
-
-
- $link->LoadByParams( Base=> $args{'Base'}, Type=> $args{'Type'}, Target=> $args{'Target'} );
- #it's a real link.
- if ( $link->id ) {
-
- my $linkid = $link->id;
- $link->Delete();
-
- my $TransString = "Ticket $args{'Base'} no longer $args{Type} ticket $args{'Target'}.";
- my $remote_uri = RT::URI->new( $RT::SystemUser );
+ if ( $args{'Silent'} ) {
+ return ( $val, $Msg );
+ }
+ else {
+ my $remote_uri = RT::URI->new( $self->CurrentUser );
$remote_uri->FromURI( $remote_link );
my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
TimeTaken => 0
);
- return ( $Trans, $self->loc("Link deleted ([_1])", $TransString));
- }
+ if ( $remote_uri->IsLocal ) {
- #if it's not a link we can find
- else {
- $RT::Logger->debug("Couldn't find that link\n");
- return ( 0, $self->loc("Link not found") );
+ my $OtherObj = $remote_uri->Object;
+ my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
+ Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
+ : $LINKDIRMAP{$args{'Type'}}->{Target},
+ OldValue => $self->URI,
+ ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
+ TimeTaken => 0 );
+ }
+
+ return ( $Trans, $Msg );
}
}
Takes a paramhash of Type and one of Base or Target. Adds that link to this ticket.
-
=cut
sub AddLink {
Silent => undef,
@_ );
- unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
- return ( 0, $self->loc("Permission Denied") );
+ unless ( $args{'Target'} || $args{'Base'} ) {
+ $RT::Logger->error("Base or Target must be specified\n");
+ return ( 0, $self->loc('Either base or target must be specified') );
}
- # Remote_link is the URI of the object that is not this ticket
- my $remote_link;
- my $direction;
+ my $right = 0;
+ $right++ if $self->CurrentUserHasRight('ModifyTicket');
+ if ( !$right && $RT::StrictLinkACL ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
- if ( $args{'Base'} and $args{'Target'} ) {
- $RT::Logger->debug(
-"$self tried to delete a link. both base and target were specified\n" );
- return ( 0, $self->loc("Can't specifiy both base and target") );
+ # If the other URI is an RT::Ticket, we want to make sure the user
+ # can modify it too...
+ my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
+ return (0, $msg) unless $status;
+ if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
+ $right++;
}
- elsif ( $args{'Base'} ) {
- $args{'Target'} = $self->URI();
- $remote_link = $args{'Base'};
- $direction = 'Target';
+ if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
+ ( $RT::StrictLinkACL && $right < 2 ) )
+ {
+ return ( 0, $self->loc("Permission Denied") );
}
- elsif ( $args{'Target'} ) {
- $args{'Base'} = $self->URI();
- $remote_link = $args{'Target'};
- $direction='Base';
+
+ return $self->_AddLink(%args);
+}
+
+sub __GetTicketFromURI {
+ my $self = shift;
+ my %args = ( URI => '', @_ );
+
+ # If the other URI is an RT::Ticket, we want to make sure the user
+ # can modify it too...
+ my $uri_obj = RT::URI->new( $self->CurrentUser );
+ $uri_obj->FromURI( $args{'URI'} );
+
+ unless ( $uri_obj->Resolver && $uri_obj->Scheme ) {
+ my $msg = $self->loc( "Couldn't resolve '[_1]' into a URI.", $args{'URI'} );
+ $RT::Logger->warning( "$msg\n" );
+ return( 0, $msg );
}
- else {
- return ( 0, $self->loc('Either base or target must be specified') );
+ my $obj = $uri_obj->Resolver->Object;
+ unless ( UNIVERSAL::isa($obj, 'RT::Ticket') && $obj->id ) {
+ return (1, 'Found not a ticket', undef);
}
+ return (1, 'Found ticket', $obj);
+}
- # If the base isn't a URI, make it a URI.
- # If the target isn't a URI, make it a URI.
+=head2 _AddLink
- # {{{ Check if the link already exists - we don't want duplicates
- use RT::Link;
- my $old_link = RT::Link->new( $self->CurrentUser );
- $old_link->LoadByParams( Base => $args{'Base'},
- Type => $args{'Type'},
- Target => $args{'Target'} );
- if ( $old_link->Id ) {
- $RT::Logger->debug("$self Somebody tried to duplicate a link");
- return ( $old_link->id, $self->loc("Link already exists"), 0 );
- }
+Private non-acled variant of AddLink so that links can be added during create.
- # }}}
+=cut
- # Storing the link in the DB.
- my $link = RT::Link->new( $self->CurrentUser );
- my ($linkid) = $link->Create( Target => $args{Target},
- Base => $args{Base},
- Type => $args{Type} );
+sub _AddLink {
+ my $self = shift;
+ my %args = ( Target => '',
+ Base => '',
+ Type => '',
+ Silent => undef,
+ @_ );
- unless ($linkid) {
- return ( 0, $self->loc("Link could not be created") );
- }
+ my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
+ return ($val, $msg) if !$val || $exist;
- my $TransString =
- "Ticket $args{'Base'} $args{Type} ticket $args{'Target'}.";
+ my ($direction, $remote_link);
+ if ( $args{'Target'} ) {
+ $remote_link = $args{'Target'};
+ $direction = 'Base';
+ } elsif ( $args{'Base'} ) {
+ $remote_link = $args{'Base'};
+ $direction = 'Target';
+ }
# Don't write the transaction if we're doing this on create
if ( $args{'Silent'} ) {
- return ( 1, $self->loc( "Link created ([_1])", $TransString ) );
+ return ( $val, $msg );
}
else {
- my $remote_uri = RT::URI->new( $RT::SystemUser );
+ my $remote_uri = RT::URI->new( $self->CurrentUser );
$remote_uri->FromURI( $remote_link );
#Write the transaction
- my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
- Type => 'AddLink',
- Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
- NewValue => $remote_uri->URI || $remote_link,
- TimeTaken => 0 );
- return ( $Trans, $self->loc( "Link created ([_1])", $TransString ) );
+ my ( $Trans, $Msg, $TransObj ) =
+ $self->_NewTransaction(Type => 'AddLink',
+ Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
+ NewValue => $remote_uri->URI || $remote_link,
+ TimeTaken => 0 );
+
+ if ( $remote_uri->IsLocal ) {
+
+ my $OtherObj = $remote_uri->Object;
+ my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
+ Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
+ : $LINKDIRMAP{$args{'Type'}}->{Target},
+ NewValue => $self->URI,
+ ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
+ TimeTaken => 0 );
+ }
+ return ( $val, $Msg );
}
}
# }}}
-# {{{ sub URI
-=head2 URI
+# {{{ sub MergeInto
-Returns this ticket's URI
+=head2 MergeInto
-=cut
+MergeInto take the id of the ticket to merge this ticket into.
-sub URI {
- my $self = shift;
- my $uri = RT::URI::fsck_com_rt->new($self->CurrentUser);
- return($uri->URIForObject($self));
-}
-# }}}
+=begin testing
-# {{{ sub MergeInto
+my $t1 = RT::Ticket->new($RT::SystemUser);
+$t1->Create ( Subject => 'Merge test 1', Queue => 'general', Requestor => 'merge1@example.com');
+my $t1id = $t1->id;
+my $t2 = RT::Ticket->new($RT::SystemUser);
+$t2->Create ( Subject => 'Merge test 2', Queue => 'general', Requestor => 'merge2@example.com');
+my $t2id = $t2->id;
+my ($msg, $val) = $t1->MergeInto($t2->id);
+ok ($msg,$val);
+$t1 = RT::Ticket->new($RT::SystemUser);
+is ($t1->id, undef, "ok. we've got a blank ticket1");
+$t1->Load($t1id);
+
+is ($t1->id, $t2->id);
+
+is ($t1->Requestors->MembersObj->Count, 2);
-=head2 MergeInto
-MergeInto take the id of the ticket to merge this ticket into.
+
+=end testing
=cut
sub MergeInto {
my $self = shift;
- my $MergeInto = shift;
+ my $ticket_id = shift;
unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
return ( 0, $self->loc("Permission Denied") );
}
# Load up the new ticket.
- my $NewTicket = RT::Ticket->new($RT::SystemUser);
- $NewTicket->Load($MergeInto);
+ my $MergeInto = RT::Ticket->new($RT::SystemUser);
+ $MergeInto->Load($ticket_id);
# make sure it exists.
- unless ( defined $NewTicket->Id ) {
+ unless ( $MergeInto->Id ) {
return ( 0, $self->loc("New ticket doesn't exist") );
}
# Make sure the current user can modify the new ticket.
- unless ( $NewTicket->CurrentUserHasRight('ModifyTicket') ) {
- $RT::Logger->debug("failed...");
+ unless ( $MergeInto->CurrentUserHasRight('ModifyTicket') ) {
return ( 0, $self->loc("Permission Denied") );
}
- $RT::Logger->debug(
- "checking if the new ticket has the same id and effective id...");
- unless ( $NewTicket->id == $NewTicket->EffectiveId ) {
- $RT::Logger->err( "$self trying to merge into "
- . $NewTicket->Id
- . " which is itself merged.\n" );
- return ( 0,
- $self->loc("Can't merge into a merged ticket. You should never get this error") );
- }
+ $RT::Handle->BeginTransaction();
# We use EffectiveId here even though it duplicates information from
# the links table becasue of the massive performance hit we'd take
- # by trying to do a seperate database query for merge info everytime
+ # by trying to do a separate database query for merge info everytime
# loaded a ticket.
#update this ticket's effective id to the new ticket's id.
my ( $id_val, $id_msg ) = $self->__Set(
Field => 'EffectiveId',
- Value => $NewTicket->Id()
+ Value => $MergeInto->Id()
);
unless ($id_val) {
- $RT::Logger->error(
- "Couldn't set effective ID for " . $self->Id . ": $id_msg" );
+ $RT::Handle->Rollback();
return ( 0, $self->loc("Merge failed. Couldn't set EffectiveId") );
}
- my ( $status_val, $status_msg ) = $self->__Set( Field => 'Status', Value => 'resolved');
- unless ($status_val) {
- $RT::Logger->error( $self->loc("[_1] couldn't set status to resolved. RT's Database may be inconsistent.", $self) );
- }
+ if ( $self->__Value('Status') ne 'resolved' ) {
+
+ my ( $status_val, $status_msg )
+ = $self->__Set( Field => 'Status', Value => 'resolved' );
+ unless ($status_val) {
+ $RT::Handle->Rollback();
+ $RT::Logger->error(
+ $self->loc(
+ "[_1] couldn't set status to resolved. RT's Database may be inconsistent.",
+ $self
+ )
+ );
+ return ( 0, $self->loc("Merge failed. Couldn't set Status") );
+ }
+ }
# update all the links that point to that old ticket
my $old_links_to = RT::Links->new($self->CurrentUser);
$old_links_to->Limit(FIELD => 'Target', VALUE => $self->URI);
+ my %old_seen;
while (my $link = $old_links_to->Next) {
- if ($link->Base eq $NewTicket->URI) {
+ if (exists $old_seen{$link->Base."-".$link->Type}) {
+ $link->Delete;
+ }
+ elsif ($link->Base eq $MergeInto->URI) {
$link->Delete;
} else {
- $link->SetTarget($NewTicket->URI);
+ # First, make sure the link doesn't already exist. then move it over.
+ my $tmp = RT::Link->new($RT::SystemUser);
+ $tmp->LoadByCols(Base => $link->Base, Type => $link->Type, LocalTarget => $MergeInto->id);
+ if ($tmp->id) {
+ $link->Delete;
+ } else {
+ $link->SetTarget($MergeInto->URI);
+ $link->SetLocalTarget($MergeInto->id);
+ }
+ $old_seen{$link->Base."-".$link->Type} =1;
}
}
$old_links_from->Limit(FIELD => 'Base', VALUE => $self->URI);
while (my $link = $old_links_from->Next) {
- if ($link->Target eq $NewTicket->URI) {
+ if (exists $old_seen{$link->Type."-".$link->Target}) {
+ $link->Delete;
+ }
+ if ($link->Target eq $MergeInto->URI) {
$link->Delete;
} else {
- $link->SetBase($NewTicket->URI);
+ # First, make sure the link doesn't already exist. then move it over.
+ my $tmp = RT::Link->new($RT::SystemUser);
+ $tmp->LoadByCols(Target => $link->Target, Type => $link->Type, LocalBase => $MergeInto->id);
+ if ($tmp->id) {
+ $link->Delete;
+ } else {
+ $link->SetBase($MergeInto->URI);
+ $link->SetLocalBase($MergeInto->id);
+ $old_seen{$link->Type."-".$link->Target} =1;
+ }
}
}
+ # Update time fields
+ foreach my $type qw(TimeEstimated TimeWorked TimeLeft) {
- #add all of this ticket's watchers to that ticket.
- my $requestors = $self->Requestors->MembersObj;
- while (my $watcher = $requestors->Next) {
- $NewTicket->_AddWatcher( Type => 'Requestor',
- Silent => 1,
- PrincipalId => $watcher->MemberId);
- }
+ my $mutator = "Set$type";
+ $MergeInto->$mutator(
+ ( $MergeInto->$type() || 0 ) + ( $self->$type() || 0 ) );
- my $Ccs = $self->Cc->MembersObj;
- while (my $watcher = $Ccs->Next) {
- $NewTicket->_AddWatcher( Type => 'Cc',
- Silent => 1,
- PrincipalId => $watcher->MemberId);
}
+#add all of this ticket's watchers to that ticket.
+ foreach my $watcher_type qw(Requestors Cc AdminCc) {
+
+ my $people = $self->$watcher_type->MembersObj;
+ my $addwatcher_type = $watcher_type;
+ $addwatcher_type =~ s/s$//;
- my $AdminCcs = $self->AdminCc->MembersObj;
- while (my $watcher = $AdminCcs->Next) {
- $NewTicket->_AddWatcher( Type => 'AdminCc',
- Silent => 1,
- PrincipalId => $watcher->MemberId);
+ while ( my $watcher = $people->Next ) {
+
+ my ($val, $msg) = $MergeInto->_AddWatcher(
+ Type => $addwatcher_type,
+ Silent => 1,
+ PrincipalId => $watcher->MemberId
+ );
+ unless ($val) {
+ $RT::Logger->warning($msg);
+ }
}
+ }
#find all of the tickets that were merged into this ticket.
my $old_mergees = new RT::Tickets( $self->CurrentUser );
while ( my $ticket = $old_mergees->Next() ) {
my ( $val, $msg ) = $ticket->__Set(
Field => 'EffectiveId',
- Value => $NewTicket->Id()
+ Value => $MergeInto->Id()
);
}
#make a new link: this ticket is merged into that other ticket.
- $self->AddLink( Type => 'MergedInto', Target => $NewTicket->Id());
+ $self->AddLink( Type => 'MergedInto', Target => $MergeInto->Id());
- $NewTicket->_SetLastUpdated;
+ $MergeInto->_SetLastUpdated;
+ $RT::Handle->Commit();
return ( 1, $self->loc("Merge Successful") );
}
my $t = RT::Ticket->new($RT::SystemUser);
$t->Load(1);
$t->SetOwner('root');
-ok ($t->OwnerObj->Name eq 'root' , "Root owns the ticket");
+is ($t->OwnerObj->Name, 'root' , "Root owns the ticket");
$t->Steal();
-ok ($t->OwnerObj->id eq $RT::SystemUser->id , "SystemUser owns the ticket");
+is ($t->OwnerObj->id, $RT::SystemUser->id , "SystemUser owns the ticket");
my $txns = RT::Transactions->new($RT::SystemUser);
$txns->OrderBy(FIELD => 'id', ORDER => 'DESC');
-$txns->Limit(FIELD => 'Ticket', VALUE => '1');
+$txns->Limit(FIELD => 'ObjectId', VALUE => '1');
+$txns->Limit(FIELD => 'ObjectType', VALUE => 'RT::Ticket');
+$txns->Limit(FIELD => 'Type', OPERATOR => '!=', VALUE => 'EmailRecord');
+
my $steal = $txns->First;
ok($steal->OldValue == $root->Id , "Stolen from root");
ok($steal->NewValue == $RT::SystemUser->Id , "Stolen by the systemuser");
my $NewOwner = shift;
my $Type = shift || "Give";
+ $RT::Handle->BeginTransaction();
+
+ $self->_SetLastUpdated(); # lock the ticket
+ $self->Load( $self->id ); # in case $self changed while waiting for lock
+
+ my $OldOwnerObj = $self->OwnerObj;
+
+ my $NewOwnerObj = RT::User->new( $self->CurrentUser );
+ $NewOwnerObj->Load( $NewOwner );
+ unless ( $NewOwnerObj->Id ) {
+ $RT::Handle->Rollback();
+ return ( 0, $self->loc("That user does not exist") );
+ }
+
+
# must have ModifyTicket rights
# or TakeTicket/StealTicket and $NewOwner is self
# see if it's a take
- if ( $self->OwnerObj->Id == $RT::Nobody->Id ) {
+ if ( $OldOwnerObj->Id == $RT::Nobody->Id ) {
unless ( $self->CurrentUserHasRight('ModifyTicket')
|| $self->CurrentUserHasRight('TakeTicket') ) {
+ $RT::Handle->Rollback();
return ( 0, $self->loc("Permission Denied") );
}
}
# see if it's a steal
- elsif ( $self->OwnerObj->Id != $RT::Nobody->Id
- && $self->OwnerObj->Id != $self->CurrentUser->id ) {
+ elsif ( $OldOwnerObj->Id != $RT::Nobody->Id
+ && $OldOwnerObj->Id != $self->CurrentUser->id ) {
unless ( $self->CurrentUserHasRight('ModifyTicket')
|| $self->CurrentUserHasRight('StealTicket') ) {
+ $RT::Handle->Rollback();
return ( 0, $self->loc("Permission Denied") );
}
}
else {
unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ $RT::Handle->Rollback();
return ( 0, $self->loc("Permission Denied") );
}
}
- my $NewOwnerObj = RT::User->new( $self->CurrentUser );
- my $OldOwnerObj = $self->OwnerObj;
-
- $NewOwnerObj->Load($NewOwner);
- if ( !$NewOwnerObj->Id ) {
- return ( 0, $self->loc("That user does not exist") );
- }
-
- #If thie ticket has an owner and it's not the current user
- if ( ( $Type ne 'Steal' )
- and ( $Type ne 'Force' )
- and #If we're not stealing
- ( $self->OwnerObj->Id != $RT::Nobody->Id ) and #and the owner is set
- ( $self->CurrentUser->Id ne $self->OwnerObj->Id() )
- ) { #and it's not us
- return ( 0,
- $self->loc(
-"You can only reassign tickets that you own or that are unowned" ) );
+ # If we're not stealing and the ticket has an owner and it's not
+ # the current user
+ if ( $Type ne 'Steal' and $Type ne 'Force'
+ and $OldOwnerObj->Id != $RT::Nobody->Id
+ and $OldOwnerObj->Id != $self->CurrentUser->Id )
+ {
+ $RT::Handle->Rollback();
+ return ( 0, $self->loc("You can only take tickets that are unowned") )
+ if $NewOwnerObj->id == $self->CurrentUser->id;
+ return (
+ 0,
+ $self->loc("You can only reassign tickets that you own or that are unowned" )
+ );
}
#If we've specified a new owner and that user can't modify the ticket
- elsif ( ( $NewOwnerObj->Id )
- and ( !$NewOwnerObj->HasRight( Right => 'OwnTicket',
- Object => $self ) )
- ) {
+ elsif ( !$NewOwnerObj->HasRight( Right => 'OwnTicket', Object => $self ) ) {
+ $RT::Handle->Rollback();
return ( 0, $self->loc("That user may not own tickets in that queue") );
}
- #If the ticket has an owner and it's the new owner, we don't need
- #To do anything
- elsif ( ( $self->OwnerObj )
- and ( $NewOwnerObj->Id eq $self->OwnerObj->Id ) ) {
+ # If the ticket has an owner and it's the new owner, we don't need
+ # To do anything
+ elsif ( $NewOwnerObj->Id == $OldOwnerObj->Id ) {
+ $RT::Handle->Rollback();
return ( 0, $self->loc("That user already owns that ticket") );
}
- $RT::Handle->BeginTransaction();
-
# Delete the owner in the owner group, then add a new one
# TODO: is this safe? it's not how we really want the API to work
# for most things, but it's fast.
return ( 0, $self->loc("Could not change owner. ") . $msg );
}
- $RT::Handle->Commit();
-
- my ( $trans, $msg, undef ) = $self->_NewTransaction(
- Type => $Type,
- Field => 'Owner',
- NewValue => $NewOwnerObj->Id,
- OldValue => $OldOwnerObj->Id,
- TimeTaken => 0 );
+ ($val, $msg) = $self->_NewTransaction(
+ Type => $Type,
+ Field => 'Owner',
+ NewValue => $NewOwnerObj->Id,
+ OldValue => $OldOwnerObj->Id,
+ TimeTaken => 0,
+ );
- if ($trans) {
+ if ( $val ) {
$msg = $self->loc( "Owner changed from [_1] to [_2]",
$OldOwnerObj->Name, $NewOwnerObj->Name );
-
- # TODO: make sure the trans committed properly
}
- return ( $trans, $msg );
+ else {
+ $RT::Handle->Rollback();
+ return ( 0, $msg );
+ }
+
+ $RT::Handle->Commit();
+ return ( $val, $msg );
}
# }}}
my ($id, $tid, $msg)= $tt->Create(Queue => 'general',
Subject => 'test');
ok($id, $msg);
-ok($tt->Status eq 'new', "New ticket is created as new");
+is($tt->Status, 'new', "New ticket is created as new");
($id, $msg) = $tt->SetStatus('open');
ok($id, $msg);
-ok ($msg =~ /open/i, "Status message is correct");
+like($msg, qr/open/i, "Status message is correct");
($id, $msg) = $tt->SetStatus('resolved');
ok($id, $msg);
-ok ($msg =~ /resolved/i, "Status message is correct");
+like($msg, qr/resolved/i, "Status message is correct");
($id, $msg) = $tt->SetStatus('resolved');
ok(!$id,$msg);
RecordTransaction => 0 );
}
- if ( $args{Status} =~ /^(resolved|rejected|dead)$/ ) {
-
- #When we resolve a ticket, set the 'Resolved' attribute to now.
+ #When we close a ticket, set the 'Resolved' attribute to now.
+ # It's misnamed, but that's just historical.
+ if ( $self->QueueObj->IsInactiveStatus($args{Status}) ) {
$self->_Set( Field => 'Resolved',
Value => $now->ISO,
RecordTransaction => 0 );
my ($val, $msg)= $self->_Set( Field => 'Status',
Value => $args{Status},
TimeTaken => 0,
+ CheckACL => 0,
TransactionType => 'Status' );
return($val,$msg);
sub Kill {
my $self = shift;
- $RT::Logger->crit("'Kill' is deprecated. use 'Delete' instead.");
+ $RT::Logger->crit("'Kill' is deprecated. use 'Delete' instead at (". join(":",caller).").");
return $self->Delete;
}
# }}}
-# {{{ Routines dealing with custom fields
-
-
-# {{{ FirstCustomFieldValue
-
-=item FirstCustomFieldValue FIELD
-
-Return the content of the first value of CustomField FIELD for this ticket
-Takes a field id or name
-
-=cut
-
-sub FirstCustomFieldValue {
- my $self = shift;
- my $field = shift;
- my $values = $self->CustomFieldValues($field);
- if ($values->First) {
- return $values->First->Content;
- } else {
- return undef;
- }
-
-}
-
-
-
-# {{{ CustomFieldValues
-
-=item CustomFieldValues FIELD
-
-Return a TicketCustomFieldValues object of all values of CustomField FIELD for this ticket.
-Takes a field id or name.
-
-
-=cut
-
-sub CustomFieldValues {
- my $self = shift;
- my $field = shift;
-
- my $cf = RT::CustomField->new($self->CurrentUser);
-
- if ($field =~ /^\d+$/) {
- $cf->LoadById($field);
- } else {
- $cf->LoadByNameAndQueue(Name => $field, Queue => $self->QueueObj->Id);
- }
- my $cf_values = RT::TicketCustomFieldValues->new( $self->CurrentUser );
- $cf_values->LimitToCustomField($cf->id);
- $cf_values->LimitToTicket($self->Id());
- $cf_values->OrderBy( FIELD => 'id' );
-
- # @values is a CustomFieldValues object;
- return ($cf_values);
-}
-
-# }}}
-
-# {{{ AddCustomFieldValue
-
-=item AddCustomFieldValue { Field => FIELD, Value => VALUE }
-
-VALUE should be a string.
-FIELD can be a CustomField object OR a CustomField ID.
-
-
-Adds VALUE as a value of CustomField FIELD. If this is a single-value custom field,
-deletes the old value.
-If VALUE isn't a valid value for the custom field, returns
-(0, 'Error message' ) otherwise, returns (1, 'Success Message')
-
-=cut
-
-sub AddCustomFieldValue {
- my $self = shift;
- unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
- return ( 0, $self->loc("Permission Denied") );
- }
- $self->_AddCustomFieldValue(@_);
-}
-
-sub _AddCustomFieldValue {
- my $self = shift;
- my %args = (
- Field => undef,
- Value => undef,
- RecordTransaction => 1,
- @_
- );
-
- my $cf = RT::CustomField->new( $self->CurrentUser );
- if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) {
- $cf->Load( $args{'Field'}->id );
- }
- else {
- $cf->Load( $args{'Field'} );
- }
-
- unless ( $cf->Id ) {
- return ( 0, $self->loc("Custom field [_1] not found", $args{'Field'}) );
- }
-
- # Load up a TicketCustomFieldValues object for this custom field and this ticket
- my $values = $cf->ValuesForTicket( $self->id );
-
- unless ( $cf->ValidateValue( $args{'Value'} ) ) {
- return ( 0, $self->loc("Invalid value for custom field") );
- }
-
- # If the custom field only accepts a single value, delete the existing
- # value and record a "changed from foo to bar" transaction
- if ( $cf->SingleValue ) {
-
- # We need to whack any old values here. In most cases, the custom field should
- # only have one value to delete. In the pathalogical case, this custom field
- # used to be a multiple and we have many values to whack....
- my $cf_values = $values->Count;
-
- if ( $cf_values > 1 ) {
- my $i = 0; #We want to delete all but the last one, so we can then
- # execute the same code to "change" the value from old to new
- while ( my $value = $values->Next ) {
- $i++;
- if ( $i < $cf_values ) {
- my $old_value = $value->Content;
- my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $value->Content);
- unless ($val) {
- return (0,$msg);
- }
- my ( $TransactionId, $Msg, $TransactionObj ) =
- $self->_NewTransaction(
- Type => 'CustomField',
- Field => $cf->Id,
- OldValue => $old_value
- );
- }
- }
- }
-
- my $old_value;
- if (my $value = $cf->ValuesForTicket( $self->Id )->First) {
- $old_value = $value->Content();
- return (1) if $old_value eq $args{'Value'};
- }
-
- my ( $new_value_id, $value_msg ) = $cf->AddValueForTicket(
- Ticket => $self->Id,
- Content => $args{'Value'}
- );
-
- unless ($new_value_id) {
- return ( 0,
- $self->loc("Could not add new custom field value for ticket. [_1] ",
- ,$value_msg) );
- }
-
- my $new_value = RT::TicketCustomFieldValue->new( $self->CurrentUser );
- $new_value->Load($new_value_id);
-
- # now that adding the new value was successful, delete the old one
- if ($old_value) {
- my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $old_value);
- unless ($val) {
- return (0,$msg);
- }
- }
-
- if ($args{'RecordTransaction'}) {
- my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
- Type => 'CustomField',
- Field => $cf->Id,
- OldValue => $old_value,
- NewValue => $new_value->Content
- );
- }
-
- if ( $old_value eq '' ) {
- return ( 1, $self->loc("[_1] [_2] added", $cf->Name, $new_value->Content) );
- }
- elsif ( $new_value->Content eq '' ) {
- return ( 1, $self->loc("[_1] [_2] deleted", $cf->Name, $old_value) );
- }
- else {
- return ( 1, $self->loc("[_1] [_2] changed to [_3]", $cf->Name, $old_value, $new_value->Content ) );
- }
-
- }
-
- # otherwise, just add a new value and record "new value added"
- else {
- my ( $new_value_id ) = $cf->AddValueForTicket(
- Ticket => $self->Id,
- Content => $args{'Value'}
- );
-
- unless ($new_value_id) {
- return ( 0,
- $self->loc("Could not add new custom field value for ticket. "));
- }
- if ( $args{'RecordTransaction'} ) {
- my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
- Type => 'CustomField',
- Field => $cf->Id,
- NewValue => $args{'Value'}
- );
- unless ($TransactionId) {
- return ( 0,
- $self->loc( "Couldn't create a transaction: [_1]", $Msg ) );
- }
- }
- return ( 1, $self->loc("[_1] added as a value for [_2]",$args{'Value'}, $cf->Name));
- }
-
-}
-
-# }}}
-
-# {{{ DeleteCustomFieldValue
-
-=item DeleteCustomFieldValue { Field => FIELD, Value => VALUE }
-
-Deletes VALUE as a value of CustomField FIELD.
-
-VALUE can be a string, a CustomFieldValue or a TicketCustomFieldValue.
-
-If VALUE isn't a valid value for the custom field, returns
-(0, 'Error message' ) otherwise, returns (1, 'Success Message')
-
-=cut
-
-sub DeleteCustomFieldValue {
- my $self = shift;
- my %args = (
- Field => undef,
- Value => undef,
- @_);
-
- unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
- return ( 0, $self->loc("Permission Denied") );
- }
- my $cf = RT::CustomField->new( $self->CurrentUser );
- if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) {
- $cf->LoadById( $args{'Field'}->id );
- }
- else {
- $cf->LoadById( $args{'Field'} );
- }
-
- unless ( $cf->Id ) {
- return ( 0, $self->loc("Custom field not found") );
- }
-
-
- my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $args{'Value'});
- unless ($val) {
- return (0,$msg);
- }
- my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
- Type => 'CustomField',
- Field => $cf->Id,
- OldValue => $args{'Value'}
- );
- unless($TransactionId) {
- return(0, $self->loc("Couldn't create a transaction: [_1]", $Msg));
- }
-
- return($TransactionId, $self->loc("[_1] is no longer a value for custom field [_2]", $args{'Value'}, $cf->Name));
-}
-
-# }}}
-
-# }}}
-
+
# {{{ Actions + Routines dealing with transactions
# {{{ sub SetTold and _SetTold
# }}}
-# {{{ sub Transactions
-
-=head2 Transactions
-
- Returns an RT::Transactions object of all transactions on this ticket
-
-=cut
-
-sub Transactions {
- my $self = shift;
-
- use RT::Transactions;
- my $transactions = RT::Transactions->new( $self->CurrentUser );
-
- #If the user has no rights, return an empty object
- if ( $self->CurrentUserHasRight('ShowTicket') ) {
- my $tickets = $transactions->NewAlias('Tickets');
- $transactions->Join(
- ALIAS1 => 'main',
- FIELD1 => 'Ticket',
- ALIAS2 => $tickets,
- FIELD2 => 'id'
- );
- $transactions->Limit(
- ALIAS => $tickets,
- FIELD => 'EffectiveId',
- VALUE => $self->id()
- );
-
- # if the user may not see comments do not return them
- unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
- $transactions->Limit(
- FIELD => 'Type',
- OPERATOR => '!=',
- VALUE => "Comment"
- );
- }
- }
-
- return ($transactions);
-}
-
-# }}}
-
-# {{{ sub _NewTransaction
-
-sub _NewTransaction {
- my $self = shift;
- my %args = (
- TimeTaken => 0,
- Type => undef,
- OldValue => undef,
- NewValue => undef,
- Data => undef,
- Field => undef,
- MIMEObj => undef,
- @_
- );
-
- require RT::Transaction;
- my $trans = new RT::Transaction( $self->CurrentUser );
- my ( $transaction, $msg ) = $trans->Create(
- Ticket => $self->Id,
- TimeTaken => $args{'TimeTaken'},
- Type => $args{'Type'},
- Data => $args{'Data'},
- Field => $args{'Field'},
- NewValue => $args{'NewValue'},
- OldValue => $args{'OldValue'},
- MIMEObj => $args{'MIMEObj'}
- );
-
-
- $self->Load($self->Id);
-
- $RT::Logger->warning($msg) unless $transaction;
-
- $self->_SetLastUpdated;
-
- if ( defined $args{'TimeTaken'} ) {
- $self->_UpdateTimeTaken( $args{'TimeTaken'} );
- }
- if ( $RT::UseTransactionBatch and $transaction ) {
- push @{$self->{_TransactionBatch}}, $trans;
- }
- return ( $transaction, $msg, $trans );
-}
-
-# }}}
-
=head2 TransactionBatch
Returns an array reference of all transactions created on this ticket during
sub DESTROY {
my $self = shift;
+ # DESTROY methods need to localize $@, or it may unset it. This
+ # causes $m->abort to not bubble all of the way up. See perlbug
+ # http://rt.perl.org/rt3/Ticket/Display.html?id=17650
+ local $@;
+
# The following line eliminates reentrancy.
# It protects against the fact that perl doesn't deal gracefully
# when an object's refcount is changed in its destructor.
return if $self->{_Destroyed}++;
my $batch = $self->TransactionBatch or return;
+ return unless @$batch;
+
require RT::Scrips;
RT::Scrips->new($RT::SystemUser)->Apply(
Stage => 'TransactionBatch',
TicketObj => $self,
TransactionObj => $batch->[0],
+ Type => join(',', (map { $_->Type } @{$batch}) )
);
}
# {{{ PRIVATE UTILITY METHODS. Mostly needed so Ticket can be a DBIx::Record
-# {{{ sub _ClassAccessible
+# {{{ sub _OverlayAccessible
-sub _ClassAccessible {
+sub _OverlayAccessible {
{
EffectiveId => { 'read' => 1, 'write' => 1, 'public' => 1 },
Queue => { 'read' => 1, 'write' => 1 },
TimeEstimated => { 'read' => 1, 'write' => 1 },
TimeWorked => { 'read' => 1, 'write' => 1 },
TimeLeft => { 'read' => 1, 'write' => 1 },
- Created => { 'read' => 1, 'auto' => 1 },
- Creator => { 'read' => 1, 'auto' => 1 },
Told => { 'read' => 1, 'write' => 1 },
Resolved => { 'read' => 1 },
Type => { 'read' => 1 },
#If we can't actually set the field to the value, don't record
# a transaction. instead, get out of here.
- if ( $ret == 0 ) { return ( 0, $msg ); }
+ return ( 0, $msg ) unless $ret;
}
if ( $args{'RecordTransaction'} == 1 ) {
OldValue => $Old,
TimeTaken => $args{'TimeTaken'},
);
- return ( $Trans, scalar $TransObj->Description );
+ return ( $Trans, scalar $TransObj->BriefDescription );
}
else {
return ( $ret, $msg );
unless ( ( defined $args{'Principal'} ) and ( ref( $args{'Principal'} ) ) )
{
- $RT::Logger->warning("Principal attrib undefined for Ticket::HasRight");
+ Carp::cluck;
+ $RT::Logger->crit("Principal attrib undefined for Ticket::HasRight");
+ return(undef);
}
return (
# }}}
+=head2 Reminders
+
+Return the Reminders object for this ticket. (It's an RT::Reminders object.)
+It isn't acutally a searchbuilder collection itself.
+
+=cut
+
+sub Reminders {
+ my $self = shift;
+
+ unless ($self->{'__reminders'}) {
+ $self->{'__reminders'} = RT::Reminders->new($self->CurrentUser);
+ $self->{'__reminders'}->Ticket($self->id);
+ }
+ return $self->{'__reminders'};
+
+}
+
+
+
+# {{{ sub Transactions
+
+=head2 Transactions
+
+ Returns an RT::Transactions object of all transactions on this ticket
+
+=cut
+
+sub Transactions {
+ my $self = shift;
+
+ my $transactions = RT::Transactions->new( $self->CurrentUser );
+
+ #If the user has no rights, return an empty object
+ if ( $self->CurrentUserHasRight('ShowTicket') ) {
+ $transactions->LimitToTicket($self->id);
+
+ # if the user may not see comments do not return them
+ unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
+ $transactions->Limit(
+ FIELD => 'Type',
+ OPERATOR => '!=',
+ VALUE => "Comment"
+ );
+ $transactions->Limit(
+ FIELD => 'Type',
+ OPERATOR => '!=',
+ VALUE => "CommentEmailRecord",
+ ENTRYAGGREGATOR => 'AND'
+ );
+
+ }
+ }
+
+ return ($transactions);
+}
+
+# }}}
+
+
+# {{{ TransactionCustomFields
+
+=head2 TransactionCustomFields
+
+ Returns the custom fields that transactions on tickets will have.
+
+=cut
+
+sub TransactionCustomFields {
+ my $self = shift;
+ return $self->QueueObj->TicketTransactionCustomFields;
+}
+
+# }}}
+
+# {{{ sub CustomFieldValues
+
+=head2 CustomFieldValues
+
+# Do name => id mapping (if needed) before falling back to
+# RT::Record's CustomFieldValues
+
+See L<RT::Record>
+
+=cut
+
+sub CustomFieldValues {
+ my $self = shift;
+ my $field = shift;
+ if ( $field and $field !~ /^\d+$/ ) {
+ my $cf = RT::CustomField->new( $self->CurrentUser );
+ $cf->LoadByNameAndQueue( Name => $field, Queue => $self->Queue );
+ unless ( $cf->id ) {
+ $cf->LoadByNameAndQueue( Name => $field, Queue => 0 );
+ }
+ unless ( $cf->id ) {
+ # If we didn't find a valid cfid, give up.
+ return RT::CustomFieldValues->new($self->CurrentUser);
+ }
+ }
+ return $self->SUPER::CustomFieldValues($field);
+}
+
+# }}}
+
+# {{{ sub CustomFieldLookupType
+
+=head2 CustomFieldLookupType
+
+Returns the RT::Ticket lookup type, which can be passed to
+RT::CustomField->Create() via the 'LookupType' hash key.
+
+=cut
+
+# }}}
+
+sub CustomFieldLookupType {
+ "RT::Queue-RT::Ticket";
+}
+
1;
=head1 AUTHOR