summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Ticket_Overlay.pm
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/Ticket_Overlay.pm')
-rw-r--r--rt/lib/RT/Ticket_Overlay.pm317
1 files changed, 206 insertions, 111 deletions
diff --git a/rt/lib/RT/Ticket_Overlay.pm b/rt/lib/RT/Ticket_Overlay.pm
index a294dca..b4e3259 100644
--- a/rt/lib/RT/Ticket_Overlay.pm
+++ b/rt/lib/RT/Ticket_Overlay.pm
@@ -2,7 +2,7 @@
#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -22,7 +22,9 @@
#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+# 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:
@@ -151,6 +153,7 @@ use RT::Date;
use RT::CustomFields;
use RT::Tickets;
use RT::Transactions;
+use RT::Reminders;
use RT::URI::fsck_com_rt;
use RT::URI;
use MIME::Entity;
@@ -327,9 +330,19 @@ Arguments: ARGS is a hash of named parameters. Valid parameters are:
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
@@ -463,10 +476,9 @@ sub Create {
#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'} );
+ $RT::Logger->debug( "Got a ". $args{'Status'}
+ ." ticket with undefined resolved date. Setting to now."
+ );
$Resolved->SetToNow;
}
@@ -678,6 +690,20 @@ sub Create {
foreach my $link (
ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
{
+ # 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,
@@ -1321,7 +1347,10 @@ sub AddWatcher {
# {{{ 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' ) {
@@ -1485,7 +1514,7 @@ sub DeleteWatcher {
# {{{ 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 == $principal->id ) {
# If it's an AdminCc and they don't have
# 'WatchAsAdminCc' or 'ModifyTicket', bail
@@ -2096,12 +2125,12 @@ sub SetStarted {
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 {
@@ -2381,21 +2410,23 @@ sub _RecordNote {
# 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'};
+
+ foreach my $type (qw/Cc Bcc/) {
+ if ( defined $args{ $type . 'MessageTo' } ) {
+
+ my $addresses = join ', ', (
+ map { RT::User->CanonicalizeEmailAddress( $_->address ) }
+ Mail::Address->parse( $args{ $type . 'MessageTo' } ) );
+ $args{'MIMEObj'}->head->add( 'RT-Send-' . $type, $addresses );
+ }
+ }
# 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)\@$RT::Organization>/) {
+ 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 . "-"
@@ -2485,11 +2516,29 @@ sub DeleteLink {
@_
);
+ 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
- unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
- $RT::Logger->debug("No permission to delete links\n");
- return ( 0, $self->loc('Permission Denied'))
+ my $right = 0;
+ $right++ if $self->CurrentUserHasRight('ModifyTicket');
+ if ( !$right && $RT::StrictLinkACL ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ # 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 ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
@@ -2557,13 +2606,52 @@ sub AddLink {
Silent => undef,
@_ );
+ 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') );
+ }
- unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ my $right = 0;
+ $right++ if $self->CurrentUserHasRight('ModifyTicket');
+ if ( !$right && $RT::StrictLinkACL ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ # 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") );
}
+ 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'} );
- $self->_AddLink(%args);
+ 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 );
+ }
+ 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);
}
=head2 _AddLink
@@ -2580,45 +2668,8 @@ sub _AddLink {
Silent => undef,
@_ );
- # {{{ If the other URI is an RT::Ticket, we want to make sure the user
- # can modify it too...
- my $other_ticket_uri = RT::URI->new($self->CurrentUser);
-
- if ( $args{'Target'} ) {
- $other_ticket_uri->FromURI( $args{'Target'} );
-
- }
- elsif ( $args{'Base'} ) {
- $other_ticket_uri->FromURI( $args{'Base'} );
- }
-
- unless ( $other_ticket_uri->Resolver && $other_ticket_uri->Scheme ) {
- my $msg = $args{'Target'} ? $self->loc("Couldn't resolve target '[_1]' into a URI.", $args{'Target'})
- : $self->loc("Couldn't resolve base '[_1]' into a URI.", $args{'Base'});
- $RT::Logger->warning( "$self $msg\n" );
-
- return( 0, $msg );
- }
-
- if ( $other_ticket_uri->Resolver->Scheme eq 'fsck.com-rt') {
- my $object = $other_ticket_uri->Resolver->Object;
-
- if ( UNIVERSAL::isa( $object, 'RT::Ticket' )
- && $object->id
- && !$object->CurrentUserHasRight('ModifyTicket') )
- {
- return ( 0, $self->loc("Permission Denied") );
- }
-
- }
-
- # }}}
-
- my ($val, $Msg) = $self->SUPER::_AddLink(%args);
-
- if (!$val) {
- return ($val, $Msg);
- }
+ my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
+ return ($val, $msg) if !$val || $exist;
my ($direction, $remote_link);
if ( $args{'Target'} ) {
@@ -2631,10 +2682,10 @@ sub _AddLink {
# Don't write the transaction if we're doing this on create
if ( $args{'Silent'} ) {
- return ( $val, $Msg );
+ return ( $val, $msg );
}
else {
- my $remote_uri = RT::URI->new( $self->CurrentUser );
+ my $remote_uri = RT::URI->new( $self->CurrentUser );
$remote_uri->FromURI( $remote_link );
#Write the transaction
@@ -2732,14 +2783,23 @@ sub MergeInto {
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::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") );
- }
+ 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);
@@ -2916,6 +2976,8 @@ my $txns = RT::Transactions->new($RT::SystemUser);
$txns->OrderBy(FIELD => 'id', ORDER => 'DESC');
$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");
@@ -2929,68 +2991,77 @@ sub SetOwner {
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.
@@ -3025,8 +3096,6 @@ sub SetOwner {
return ( 0, $self->loc("Could not change owner. ") . $msg );
}
- $RT::Handle->Commit();
-
($val, $msg) = $self->_NewTransaction(
Type => $Type,
Field => 'Owner',
@@ -3038,9 +3107,14 @@ sub SetOwner {
if ( $val ) {
$msg = $self->loc( "Owner changed from [_1] to [_2]",
$OldOwnerObj->Name, $NewOwnerObj->Name );
-
- # TODO: make sure the trans committed properly
}
+ else {
+ $RT::Handle->Rollback();
+ return ( 0, $msg );
+ }
+
+ $RT::Handle->Commit();
+
return ( $val, $msg );
}
@@ -3385,6 +3459,8 @@ sub DESTROY {
return if $self->{_Destroyed}++;
my $batch = $self->TransactionBatch or return;
+ return unless @$batch;
+
require RT::Scrips;
RT::Scrips->new($RT::SystemUser)->Apply(
Stage => 'TransactionBatch',
@@ -3617,6 +3693,26 @@ sub HasRight {
# }}}
+=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
@@ -3661,7 +3757,7 @@ sub Transactions {
=head2 TransactionCustomFields
- Returns the custom fields that transactions on tickets will ahve.
+ Returns the custom fields that transactions on tickets will have.
=cut
@@ -3696,7 +3792,6 @@ sub CustomFieldValues {
# If we didn't find a valid cfid, give up.
return RT::CustomFieldValues->new($self->CurrentUser);
}
- $field = $cf->id;
}
return $self->SUPER::CustomFieldValues($field);
}