diff options
Diffstat (limited to 'rt/lib/RT/Ticket_Overlay.pm')
-rw-r--r-- | rt/lib/RT/Ticket_Overlay.pm | 3740 |
1 files changed, 0 insertions, 3740 deletions
diff --git a/rt/lib/RT/Ticket_Overlay.pm b/rt/lib/RT/Ticket_Overlay.pm deleted file mode 100644 index d04ececd8..000000000 --- a/rt/lib/RT/Ticket_Overlay.pm +++ /dev/null @@ -1,3740 +0,0 @@ -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC -# <jesse@bestpractical.com> -# -# (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 -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# 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. -# -# -# 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.) -# -# 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 - - use RT::Ticket; - my $ticket = new RT::Ticket($CurrentUser); - $ticket->Load($ticket_id); - -=head1 DESCRIPTION - -This module lets you manipulate RT\'s ticket object. - - -=head1 METHODS - -=begin testing - -use_ok ( RT::Queue); -ok(my $testqueue = RT::Queue->new($RT::SystemUser)); -ok($testqueue->Create( Name => 'ticket tests')); -ok($testqueue->Id != 0); -use_ok(RT::CustomField); -ok(my $testcf = RT::CustomField->new($RT::SystemUser)); -my ($ret, $cmsg) = $testcf->Create( Name => 'selectmulti', - Queue => $testqueue->id, - Type => 'SelectMultiple'); -ok($ret,"Created the custom field - ".$cmsg); -($ret,$cmsg) = $testcf->AddValue ( Name => 'Value1', - SortOrder => '1', - Description => 'A testing value'); - -ok($ret, "Added a value - ".$cmsg); - -ok($testcf->AddValue ( Name => 'Value2', - SortOrder => '2', - Description => 'Another testing value')); -ok($testcf->AddValue ( Name => 'Value3', - SortOrder => '3', - Description => 'Yet Another testing value')); - -ok($testcf->Values->Count == 3); - -use_ok(RT::Ticket); - -my $u = RT::User->new($RT::SystemUser); -$u->Load("root"); -ok ($u->Id, "Found the root user"); -ok(my $t = RT::Ticket->new($RT::SystemUser)); -ok(my ($id, $msg) = $t->Create( Queue => $testqueue->Id, - Subject => 'Testing', - Owner => $u->Id - )); -ok($id != 0); -ok ($t->OwnerObj->Id == $u->Id, "Root is the ticket owner"); -ok(my ($cfv, $cfm) =$t->AddCustomFieldValue(Field => $testcf->Id, - Value => 'Value1')); -ok($cfv != 0, "Custom field creation didn't return an error: $cfm"); -ok($t->CustomFieldValues($testcf->Id)->Count == 1); -ok($t->CustomFieldValues($testcf->Id)->First && - $t->CustomFieldValues($testcf->Id)->First->Content eq 'Value1');; - -ok(my ($cfdv, $cfdm) = $t->DeleteCustomFieldValue(Field => $testcf->Id, - Value => 'Value1')); -ok ($cfdv != 0, "Deleted a custom field value: $cfdm"); -ok($t->CustomFieldValues($testcf->Id)->Count == 0); - -ok(my $t2 = RT::Ticket->new($RT::SystemUser)); -ok($t2->Load($id)); -is($t2->Subject, 'Testing'); -is($t2->QueueObj->Id, $testqueue->id); -ok($t2->OwnerObj->Id == $u->Id); - -my $t3 = RT::Ticket->new($RT::SystemUser); -my ($id3, $msg3) = $t3->Create( Queue => $testqueue->Id, - Subject => 'Testing', - Owner => $u->Id); -my ($cfv1, $cfm1) = $t->AddCustomFieldValue(Field => $testcf->Id, - Value => 'Value1'); -ok($cfv1 != 0, "Adding a custom field to ticket 1 is successful: $cfm"); -my ($cfv2, $cfm2) = $t3->AddCustomFieldValue(Field => $testcf->Id, - Value => 'Value2'); -ok($cfv2 != 0, "Adding a custom field to ticket 2 is successful: $cfm"); -my ($cfv3, $cfm3) = $t->AddCustomFieldValue(Field => $testcf->Id, - Value => 'Value3'); -ok($cfv3 != 0, "Adding a custom field to ticket 1 is successful: $cfm"); -ok($t->CustomFieldValues($testcf->Id)->Count == 2, - "This ticket has 2 custom field values"); -ok($t3->CustomFieldValues($testcf->Id)->Count == 1, - "This ticket has 1 custom field value"); - -=end testing - -=cut - - -package RT::Ticket; - -use strict; -no warnings qw(redefine); - -use RT::Queue; -use RT::User; -use RT::Record; -use RT::Links; -use RT::Date; -use RT::CustomFields; -use RT::Tickets; -use RT::Transactions; -use RT::URI::fsck_com_rt; -use RT::URI; -use MIME::Entity; - -=begin testing - - -ok(require RT::Ticket, "Loading the RT::Ticket library"); - -=end testing - -=cut - -# }}} - -# {{{ LINKTYPEMAP -# 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', }, - ReferredToBy => { Type => 'RefersTo', - Mode => 'Base', }, - DependsOn => { Type => 'DependsOn', - Mode => 'Target', }, - DependedOnBy => { Type => 'DependsOn', - Mode => 'Base', }, - MergedInto => { Type => 'MergedInto', - Mode => 'Target', }, - -); - -# }}} - -# {{{ LINKDIRMAP -# A helper table for links mapping to make it easier -# to build and parse links between tickets - -use vars '%LINKDIRMAP'; - -%LINKDIRMAP = ( - MemberOf => { Base => 'MemberOf', - Target => 'HasMember', }, - RefersTo => { Base => 'RefersTo', - Target => 'ReferredToBy', }, - DependsOn => { Base => 'DependsOn', - Target => 'DependedOnBy', }, - MergedInto => { Base => 'MergedInto', - Target => 'MergedInto', }, - -); - -# }}} - -sub LINKTYPEMAP { return \%LINKTYPEMAP } -sub LINKDIRMAP { return \%LINKDIRMAP } - -# {{{ sub Load - -=head2 Load - -Takes a single argument. This can be a ticket id, ticket alias or -local ticket uri. If the ticket can't be loaded, returns undef. -Otherwise, returns the ticket id. - -=cut - -sub Load { - my $self = shift; - my $id = shift; - - #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+)$/ ) { - $id = $1; - } - - #If it's a remote URI, we're going to punt for now - elsif ( $id =~ '://' ) { - return (undef); - } - - #If we have an integer URI, load the ticket - if ( $id =~ /^\d+$/ ) { - my ($ticketid,$msg) = $self->LoadById($id); - - 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 ) ); - } - - #Ok. we're loaded. lets get outa here. - return ( $self->Id ); - -} - -# }}} - -# {{{ sub LoadByURI - -=head2 LoadByURI - -Given a local ticket URI, loads the specified ticket. - -=cut - -sub LoadByURI { - my $self = shift; - my $uri = shift; - - if ( $uri =~ /^$RT::TicketBaseURI(\d+)$/ ) { - my $id = $1; - return ( $self->Load($id) ); - } - else { - return (undef); - } -} - -# }}} - -# {{{ sub Create - -=head2 Create (ARGS) - -Arguments: ARGS is a hash of named parameters. Valid parameters are: - - id - Queue - Either a Queue object or a Queue Name - 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) - TimeEstimated -- an integer. estimated time for this task in minutes - TimeWorked -- an integer. time worked so far in minutes - TimeLeft -- an integer. time remaining in minutes - Starts -- an ISO date describing the ticket\'s start date and time in GMT - Due -- an ISO date describing the ticket\'s due date and time in GMT - 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> - - -Returns: TICKETID, Transaction Object, Error Message - - -=begin testing - -my $t = RT::Ticket->new($RT::SystemUser); - -ok( $t->Create(Queue => 'General', Due => '2002-05-21 00:00:00', ReferredToBy => 'http://www.cpan.org', RefersTo => 'http://fsck.com', Subject => 'This is a subject'), "Ticket Created"); - -ok ( my $id = $t->Id, "Got ticket id"); -ok ($t->RefersTo->First->Target =~ /fsck.com/, "Got refers to"); -ok ($t->ReferredToBy->First->Base =~ /cpan.org/, "Got referredtoby"); -ok ($t->ResolvedObj->Unix == -1, "It hasn't been resolved - ". $t->ResolvedObj->Unix); - -=end testing - -=cut - -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 ( $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'} ); - } - elsif ( ref( $args{'Queue'} ) eq 'RT::Queue' ) { - $QueueObj->Load( $args{'Queue'}->Id ); - } - else { - $RT::Logger->debug( $args{'Queue'} . " not a recognised queue object." ); - } - - #Can't create a ticket without a queue. - unless ( defined($QueueObj) && $QueueObj->Id ) { - $RT::Logger->debug("$self No queue given for ticket creation."); - return ( 0, 0, $self->loc('Could not create ticket. Queue not set') ); - } - - #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 ( $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 ( $args{'InitialPriority'} ); - - #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 ( $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. - - #Set the due date. if we didn't get fed one, use the queue default due in - my $Due = new RT::Date( $self->CurrentUser ); - - if ( $args{'Due'} ) { - $Due->Set( Format => 'ISO', Value => $args{'Due'} ); - } - elsif ( $QueueObj->DefaultDueIn ) { - $Due->SetToNow; - $Due->AddDays( $QueueObj->DefaultDueIn ); - } - - my $Starts = new RT::Date( $self->CurrentUser ); - if ( defined $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'} ); - } - - my $Resolved = new RT::Date( $self->CurrentUser ); - if ( defined $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'} ); - $Resolved->SetToNow; - } - - # }}} - - # {{{ Dealing with time fields - - $args{'TimeEstimated'} = 0 unless defined $args{'TimeEstimated'}; - $args{'TimeWorked'} = 0 unless defined $args{'TimeWorked'}; - $args{'TimeLeft'} = 0 unless defined $args{'TimeLeft'}; - - # }}} - - # {{{ Deal with setting the owner - - if ( ref( $args{'Owner'} ) eq 'RT::User' ) { - $Owner = $args{'Owner'}; - } - - #If we've been handed something else, try to load the user. - 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 - #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' - ) - ) - ) - { - - $RT::Logger->warning( "User " - . $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( "Owner '[_1]' does not have rights to own this ticket.", - $Owner->Name - ); - - $Owner = undef; - } - - #If we haven't been handed a valid owner, make it nobody. - unless ( defined($Owner) && $Owner->Id ) { - $Owner = new RT::User( $self->CurrentUser ); - $Owner->Load( $RT::Nobody->Id ); - } - - # }}} - -# 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+$/ ); - } - } - - $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 - foreach my $attr qw(id Creator Created LastUpdated LastUpdatedBy) { - $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} ); - } - - # 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: " . $ticket_message ); - $RT::Handle->Rollback(); - 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 ) - ); - - 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") - ); - } - - 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." ); - $RT::Handle->Rollback(); - return ( 0, 0, - $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 - - $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} ) ) - { - - # 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+$/; - - 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 - ); - } - else { - ( $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} ); - foreach my $link ( - ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) ) - { - my ( $wval, $wmsg ) = $self->_AddLink( - Type => $LINKTYPEMAP{$type}->{'Type'}, - $LINKTYPEMAP{$type}->{'Mode'} => $link, - Silent => 1 - ); - - push @non_fatal_errors, $wmsg unless ($wval); - } - } - - # }}} - - # {{{ 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 => $args{'TimeWorked'}, - MIMEObj => $args{'MIMEObj'} - ); - - if ( $self->Id && $Trans ) { - - $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(); - - $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 ); - - } -} - - -# }}} - - -# {{{ UpdateFrom822 - -=head2 UpdateFrom822 $MESSAGE - -Takes an RFC822 format message as a string and uses it to make a bunch of changes to a ticket. -Returns an um. ask me again when the code exists - - -=begin testing - -my $simple_update = <<EOF; -Subject: target -AddRequestor: jesse\@example.com -EOF - -my $ticket = RT::Ticket->new($RT::SystemUser); -my ($id,$msg) =$ticket->Create(Subject => 'first', Queue => 'general'); -ok($ticket->Id, "Created the test ticket - ".$id ." - ".$msg); -$ticket->UpdateFrom822($simple_update); -is($ticket->Subject, 'target', "changed the subject"); -my $jesse = RT::User->new($RT::SystemUser); -$jesse->LoadByEmail('jesse@example.com'); -ok ($jesse->Id, "There's a user for jesse"); -ok($ticket->Requestors->HasMember( $jesse->PrincipalObj), "It has the jesse principal object as a requestor "); - -=end testing - - -=cut - -sub UpdateFrom822 { - my $self = shift; - my $content = shift; - my %args = $self->_Parse822HeadersForAttributes($content); - - - 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'}, - Priority => $args{'priority'}, - 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'} - ); - - foreach my $type qw(Requestor Cc Admincc) { - - foreach my $action ( 'Add', 'Del', '' ) { - - my $lctag = lc($action) . lc($type); - foreach my $list ( $args{$lctag}, $args{ $lctag . 's' } ) { - - foreach my $entry ( ref($list) ? @{$list} : ($list) ) { - push @{$ticketargs{ $action . $type }} , split ( /\s*,\s*/, $entry ); - } - - } - - # Todo: if we're given an explicit list, transmute it into a list of adds/deletes - - } - } - - # Add custom field entries to %ticketargs. - # TODO: allow named custom fields - map { - /^customfield-(\d+)$/ - && ( $ticketargs{ "CustomField-" . $1 } = $args{$_} ); - } keys(%args); - -# for each ticket we've been told to update, iterate through the set of -# rfc822 headers and perform that update to the ticket. - - - # {{{ Set basic fields - my @attribs = qw( - Subject - FinalPriority - Priority - TimeEstimated - TimeWorked - TimeLeft - Status - Queue - Type - ); - - - # Resolve the queue from a name to a numeric id. - if ( $ticketargs{'Queue'} and ( $ticketargs{'Queue'} !~ /^(\d+)$/ ) ) { - my $tempqueue = RT::Queue->new($RT::SystemUser); - $tempqueue->Load( $ticketargs{'Queue'} ); - $ticketargs{'Queue'} = $tempqueue->Id() if ( $tempqueue->id ); - } - - my @results; - - foreach my $attribute (@attribs) { - my $value = $ticketargs{$attribute}; - - if ( $value ne $self->$attribute() ) { - - my $method = "Set$attribute"; - my ( $code, $msg ) = $self->$method($value); - - push @results, $self->loc($attribute) . ': ' . $msg; - - } - } - - # We special case owner changing, so we can use ForceOwnerChange - if ( $ticketargs{'Owner'} && ( $self->Owner != $ticketargs{'Owner'} ) ) { - my $ChownType = "Give"; - $ChownType = "Force" if ( $ticketargs{'ForceOwnerChange'} ); - - my ( $val, $msg ) = $self->SetOwner( $ticketargs{'Owner'}, $ChownType ); - push ( @results, $msg ); - } - - # }}} -# Deal with setting watchers - - -# Acceptable arguments: -# Requestor -# Requestors -# AddRequestor -# AddRequestors -# DelRequestor - - foreach my $type qw(Requestor Cc AdminCc) { - - # If we've been given a number of delresses to del, do it. - foreach my $address (@{$ticketargs{'Del'.$type}}) { - my ($id, $msg) = $self->DeleteWatcher( Type => $type, Email => $address); - push (@results, $msg) ; - } - - # If we've been given a number of addresses to add, do it. - foreach my $address (@{$ticketargs{'Add'.$type}}) { - $RT::Logger->debug("Adding $address as a $type"); - my ($id, $msg) = $self->AddWatcher( Type => $type, Email => $address); - push (@results, $msg) ; - - } - - -} - - -} -# }}} - -# {{{ _Parse822HeadersForAttributes Content - -=head2 _Parse822HeadersForAttributes Content - -Takes an RFC822 style message and parses its attributes into a hash. - -=cut - -sub _Parse822HeadersForAttributes { - my $self = shift; - my $content = shift; - my %args; - - my @lines = ( split ( /\n/, $content ) ); - while ( defined( my $line = shift @lines ) ) { - if ( $line =~ /^(.*?):(?:\s+(.*))?$/ ) { - my $value = $2; - my $tag = lc($1); - - $tag =~ s/-//g; - if ( defined( $args{$tag} ) ) - { #if we're about to get a second value, make it an array - $args{$tag} = [ $args{$tag} ]; - } - if ( ref( $args{$tag} ) ) - { #If it's an array, we want to push the value - push @{ $args{$tag} }, $value; - } - else { #if there's nothing there, just set the value - $args{$tag} = $value; - } - } elsif ($line =~ /^$/) { - - #TODO: this won't work, since "" isn't of the form "foo:value" - - while ( defined( my $l = shift @lines ) ) { - push @{ $args{'content'} }, $l; - } - } - - } - - foreach my $date qw(due starts started resolved) { - my $dateobj = RT::Date->new($RT::SystemUser); - if ( $args{$date} =~ /^\d+$/ ) { - $dateobj->Set( Format => 'unix', Value => $args{$date} ); - } - else { - $dateobj->Set( Format => 'unknown', Value => $args{$date} ); - } - $args{$date} = $dateobj->ISO; - } - $args{'mimeobj'} = MIME::Entity->new(); - $args{'mimeobj'}->build( - Type => ( $args{'contenttype'} || 'text/plain' ), - Data => ($args{'content'} || '') - ); - - return (%args); -} - -# }}} - -# {{{ sub Import - -=head2 Import PARAMHASH - -Import a ticket. -Doesn\'t create a transaction. -Doesn\'t supply queue defaults, etc. - -Returns: TICKETID - -=cut - -sub Import { - my $self = shift; - my ( $ErrStr, $QueueObj, $Owner ); - - my %args = ( - id => undef, - EffectiveId => undef, - Queue => undef, - Requestor => undef, - Type => 'ticket', - Owner => $RT::Nobody->Id, - Subject => '[no subject]', - InitialPriority => undef, - FinalPriority => undef, - Status => 'new', - TimeWorked => "0", - Due => undef, - Created => undef, - Updated => undef, - Resolved => undef, - Told => undef, - @_ - ); - - if ( ( defined( $args{'Queue'} ) ) && ( !ref( $args{'Queue'} ) ) ) { - $QueueObj = RT::Queue->new($RT::SystemUser); - $QueueObj->Load( $args{'Queue'} ); - - #TODO error check this and return 0 if it\'s not loading properly +++ - } - elsif ( ref( $args{'Queue'} ) eq 'RT::Queue' ) { - $QueueObj = RT::Queue->new($RT::SystemUser); - $QueueObj->Load( $args{'Queue'}->Id ); - } - else { - $RT::Logger->debug( - "$self " . $args{'Queue'} . " not a recognised queue object." ); - } - - #Can't create a ticket without a queue. - unless ( defined($QueueObj) and $QueueObj->Id ) { - $RT::Logger->debug("$self No queue given for ticket creation."); - return ( 0, $self->loc('Could not create ticket. Queue not set') ); - } - - #Now that we have a queue, Check the ACLS - unless ( - $self->CurrentUser->HasRight( - Right => 'CreateTicket', - Object => $QueueObj - ) - ) - { - return ( 0, - $self->loc("No permission to create tickets in the queue '[_1]'" - , $QueueObj->Name)); - } - - # {{{ Deal with setting the owner - - # Attempt to take user object, user name or user id. - # Assign to nobody if lookup fails. - if ( defined( $args{'Owner'} ) ) { - if ( ref( $args{'Owner'} ) ) { - $Owner = $args{'Owner'}; - } - else { - $Owner = new RT::User( $self->CurrentUser ); - $Owner->Load( $args{'Owner'} ); - if ( !defined( $Owner->id ) ) { - $Owner->Load( $RT::Nobody->id ); - } - } - } - - #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 != $RT::Nobody->Id ) - and ( - !$Owner->HasRight( - Object => $QueueObj, - Right => 'OwnTicket' - ) - ) - ) - { - - $RT::Logger->warning( "$self user " - . $Owner->Name . "(" - . $Owner->id - . ") was proposed " - . "as a ticket owner but has no rights to own " - . "tickets in '" - . $QueueObj->Name . "'\n" ); - - $Owner = undef; - } - - #If we haven't been handed a valid owner, make it nobody. - unless ( defined($Owner) ) { - $Owner = new RT::User( $self->CurrentUser ); - $Owner->Load( $RT::Nobody->UserObj->Id ); - } - - # }}} - - unless ( $self->ValidateStatus( $args{'Status'} ) ) { - return ( 0, $self->loc("'[_1]' is an invalid value for status", $args{'Status'}) ); - } - - $self->{'_AccessibleCache'}{Created} = { 'read' => 1, 'write' => 1 }; - $self->{'_AccessibleCache'}{Creator} = { 'read' => 1, 'auto' => 1 }; - $self->{'_AccessibleCache'}{LastUpdated} = { 'read' => 1, 'write' => 1 }; - $self->{'_AccessibleCache'}{LastUpdatedBy} = { 'read' => 1, 'auto' => 1 }; - - # If we're coming in with an id, set that now. - my $EffectiveId = undef; - if ( $args{'id'} ) { - $EffectiveId = $args{'id'}; - - } - - my $id = $self->SUPER::Create( - id => $args{'id'}, - EffectiveId => $EffectiveId, - Queue => $QueueObj->Id, - Owner => $Owner->Id, - Subject => $args{'Subject'}, # loc - InitialPriority => $args{'InitialPriority'}, # loc - FinalPriority => $args{'FinalPriority'}, # loc - Priority => $args{'InitialPriority'}, # loc - Status => $args{'Status'}, # loc - TimeWorked => $args{'TimeWorked'}, # loc - Type => $args{'Type'}, # loc - Created => $args{'Created'}, # loc - Told => $args{'Told'}, # loc - LastUpdated => $args{'Updated'}, # loc - Resolved => $args{'Resolved'}, # loc - Due => $args{'Due'}, # loc - ); - - # If the ticket didn't have an id - # Set the ticket's effective ID now that we've created it. - if ( $args{'id'} ) { - $self->Load( $args{'id'} ); - } - else { - my ( $val, $msg ) = - $self->__Set( Field => 'EffectiveId', Value => $id ); - - unless ($val) { - $RT::Logger->err( - $self . "->Import couldn't set EffectiveId: $msg\n" ); - } - } - - 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', Email => $watcher, Silent => 1 ); - } - foreach $watcher ( @{ $args{'AdminCc'} } ) { - $self->_AddWatcher( Type => 'AdminCc', Email => $watcher, - Silent => 1 ); - } - foreach $watcher ( @{ $args{'Requestor'} } ) { - $self->_AddWatcher( Type => 'Requestor', Email => $watcher, - Silent => 1 ); - } - - return ( $self->Id, $ErrStr ); -} - -# }}} - -# {{{ Routines dealing with watchers. - -# {{{ _CreateTicketGroups - -=head2 _CreateTicketGroups - -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. - -It will return true on success and undef on failure. - -=begin testing - -my $ticket = RT::Ticket->new($RT::SystemUser); -my ($id, $msg) = $ticket->Create(Subject => "Foo", - Owner => $RT::SystemUser->Id, - Status => 'open', - Requestor => ['jesse@example.com'], - Queue => '1' - ); -ok ($id, "Ticket $id was created"); -ok(my $group = RT::Group->new($RT::SystemUser)); -ok($group->LoadTicketRoleGroup(Ticket => $id, Type=> 'Requestor')); -ok ($group->Id, "Found the requestors object for this ticket"); - -ok(my $jesse = RT::User->new($RT::SystemUser), "Creating a jesse rt::user"); -$jesse->LoadByEmail('jesse@example.com'); -ok($jesse->Id, "Found the jesse rt user"); - - -ok ($ticket->IsWatcher(Type => 'Requestor', PrincipalId => $jesse->PrincipalId), "The ticket actually has jesse at fsck.com as a requestor"); -ok ((my $add_id, $add_msg) = $ticket->AddWatcher(Type => 'Requestor', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor"); -ok ($add_id, "Add succeeded: ($add_msg)"); -ok(my $bob = RT::User->new($RT::SystemUser), "Creating a bob rt::user"); -$bob->LoadByEmail('bob@fsck.com'); -ok($bob->Id, "Found the bob rt user"); -ok ($ticket->IsWatcher(Type => 'Requestor', PrincipalId => $bob->PrincipalId), "The ticket actually has bob at fsck.com as a requestor");; -ok ((my $add_id, $add_msg) = $ticket->DeleteWatcher(Type =>'Requestor', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor"); -ok (!$ticket->IsWatcher(Type => 'Requestor', Principal => $bob->PrincipalId), "The ticket no longer has bob at fsck.com as a requestor");; - - -$group = RT::Group->new($RT::SystemUser); -ok($group->LoadTicketRoleGroup(Ticket => $id, Type=> 'Cc')); -ok ($group->Id, "Found the cc object for this ticket"); -$group = RT::Group->new($RT::SystemUser); -ok($group->LoadTicketRoleGroup(Ticket => $id, Type=> 'AdminCc')); -ok ($group->Id, "Found the AdminCc object for this ticket"); -$group = RT::Group->new($RT::SystemUser); -ok($group->LoadTicketRoleGroup(Ticket => $id, Type=> 'Owner')); -ok ($group->Id, "Found the Owner object for this ticket"); -ok($group->HasMember($RT::SystemUser->UserObj->PrincipalObj), "the owner group has the member 'RT_System'"); - -=end testing - -=cut - - -sub _CreateTicketGroups { - my $self = shift; - - my @types = qw(Requestor Owner Cc AdminCc); - - foreach my $type (@types) { - my $type_obj = RT::Group->new($self->CurrentUser); - my ($id, $msg) = $type_obj->CreateRoleGroup(Domain => 'RT::Ticket-Role', - Instance => $self->Id, - Type => $type); - unless ($id) { - $RT::Logger->error("Couldn't create a ticket group of type '$type' for ticket ". - $self->Id.": ".$msg); - return(undef); - } - } - return(1); - -} - -# }}} - -# {{{ sub OwnerGroup - -=head2 OwnerGroup - -A constructor which returns an RT::Group object containing the owner of this ticket. - -=cut - -sub OwnerGroup { - my $self = shift; - my $owner_obj = RT::Group->new($self->CurrentUser); - $owner_obj->LoadTicketRoleGroup( Ticket => $self->Id, Type => 'Owner'); - return ($owner_obj); -} - -# }}} - - -# {{{ sub AddWatcher - -=head2 AddWatcher - -AddWatcher takes a parameter hash. The keys are as follows: - -Type One of Requestor, Cc, AdminCc - -PrinicpalId The RT::Principal id of the user or group that's being added as a watcher - -Email The email address of the new watcher. If a user with this - email address can't be found, a new nonprivileged user will be created. - -If the watcher you\'re trying to set has an RT account, set the Owner paremeter to their User Id. Otherwise, set the Email parameter to their Email address. - -=cut - -sub AddWatcher { - my $self = shift; - my %args = ( - Type => undef, - PrincipalId => undef, - Email => undef, - @_ - ); - - # {{{ 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 - # 'WatchAsAdminCc' or 'ModifyTicket', bail - if ( $args{'Type'} eq 'AdminCc' ) { - 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')) - } - } - else { - $RT::Logger->warning( "$self -> AddWatcher got passed a bogus type"); - return ( 0, $self->loc('Error in parameters to Ticket->AddWatcher') ); - } - } - - # If the watcher isn't the current user - # and the current user doesn't have 'ModifyTicket' - # bail - else { - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - return ( 0, $self->loc("Permission Denied") ); - } - } - - # }}} - - return ( $self->_AddWatcher(%args) ); -} - -#This contains the meat of AddWatcher. but can be called from a routine like -# Create, which doesn't need the additional acl check -sub _AddWatcher { - my $self = shift; - my %args = ( - Type => undef, - Silent => undef, - PrincipalId => undef, - Email => undef, - @_ - ); - - - my $principal = RT::Principal->new($self->CurrentUser); - 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; - } - } - if ($args{'PrincipalId'}) { - $principal->Load($args{'PrincipalId'}); - } - - - # If we can't find this watcher, we need to bail. - unless ($principal->Id) { - $RT::Logger->error("Could not load create a user with the email address '".$args{'Email'}. "' to add as a watcher for ticket ".$self->Id); - return(0, $self->loc("Could not find or create that user")); - } - - - 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")); - } - - if ( $group->HasMember( $principal)) { - - return ( 0, $self->loc('That principal is already a [_1] for this ticket', $self->loc($args{'Type'})) ); - } - - - my ( $m_id, $m_msg ) = $group->_AddMember( PrincipalId => $principal->Id, - InsideTransaction => 1 ); - unless ($m_id) { - $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id."\n".$m_msg); - - return ( 0, $self->loc('Could not make that principal a [_1] for this ticket', $self->loc($args{'Type'})) ); - } - - unless ( $args{'Silent'} ) { - $self->_NewTransaction( - Type => 'AddWatcher', - NewValue => $principal->Id, - Field => $args{'Type'} - ); - } - - return ( 1, $self->loc('Added principal as a [_1] for this ticket', $self->loc($args{'Type'})) ); -} - -# }}} - - -# {{{ sub DeleteWatcher - -=head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS } - - -Deletes a Ticket watcher. Takes two arguments: - -Type (one of Requestor,Cc,AdminCc) - -and one of - -PrincipalId (an RT::Principal Id of the watcher you want to remove) - OR -Email (the email address of an existing wathcer) - - -=cut - - -sub DeleteWatcher { - my $self = shift; - - my %args = ( Type => undef, - PrincipalId => undef, - Email => undef, - @_ ); - - unless ( $args{'PrincipalId'} || $args{'Email'} ) { - return ( 0, $self->loc("No principal specified") ); - } - 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 ); - } - - # If we can't find this watcher, we need to bail. - 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") ); - } - - # {{{ 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 - # 'WatchAsAdminCc' or 'ModifyTicket', bail - if ( $args{'Type'} eq 'AdminCc' ) { - 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') ); - } - } - else { - $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 - # and the current user doesn't have 'ModifyTicket' bail - else { - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - return ( 0, $self->loc("Permission Denied") ); - } - } - - # }}} - - # 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'} ) ); - } - - 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 ); - - 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'} ); - } - - return ( 1, - $self->loc( "[_1] is no longer a [_2] for this ticket.", - $principal->Object->Name, - $args{'Type'} ) ); -} - - - -# }}} - - -=head2 SquelchMailTo [EMAIL] - -Takes an optional email address to never email about updates to this ticket. - - -Returns an array of the RT::Attribute objects for this ticket's 'SquelchMailTo' attributes. - -=begin testing - -my $t = RT::Ticket->new($RT::SystemUser); -ok($t->Create(Queue => 'general', Subject => 'SquelchTest')); - -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 ); -} - - -=head2 AdminCcAddresses - -returns String: All Ticket AdminCc email addresses as a string - -=cut - -sub AdminCcAddresses { - my $self = shift; - - unless ( $self->CurrentUserHasRight('ShowTicket') ) { - return undef; - } - - return ( $self->AdminCc->MemberEmailAddressesAsString ) - -} - -=head2 CcAddresses - -returns String: All Ticket Ccs as a string of email addresses - -=cut - -sub CcAddresses { - my $self = shift; - - unless ( $self->CurrentUserHasRight('ShowTicket') ) { - return undef; - } - - return ( $self->Cc->MemberEmailAddressesAsString); - -} - -# }}} - -# {{{ Routines that return RT::Watchers objects of Requestors, Ccs and AdminCcs - -# {{{ sub Requestors - -=head2 Requestors - -Takes nothing. -Returns this ticket's Requestors as an RT::Group object - -=cut - -sub Requestors { - my $self = shift; - - my $group = RT::Group->new($self->CurrentUser); - if ( $self->CurrentUserHasRight('ShowTicket') ) { - $group->LoadTicketRoleGroup(Type => 'Requestor', Ticket => $self->Id); - } - return ($group); - -} - -# }}} - -# {{{ sub Cc - -=head2 Cc - -Takes nothing. -Returns an RT::Group object which contains this ticket's Ccs. -If the user doesn't have "ShowTicket" permission, returns an empty group - -=cut - -sub Cc { - my $self = shift; - - my $group = RT::Group->new($self->CurrentUser); - if ( $self->CurrentUserHasRight('ShowTicket') ) { - $group->LoadTicketRoleGroup(Type => 'Cc', Ticket => $self->Id); - } - return ($group); - -} - -# }}} - -# {{{ sub AdminCc - -=head2 AdminCc - -Takes nothing. -Returns an RT::Group object which contains this ticket's AdminCcs. -If the user doesn't have "ShowTicket" permission, returns an empty group - -=cut - -sub AdminCc { - my $self = shift; - - my $group = RT::Group->new($self->CurrentUser); - if ( $self->CurrentUserHasRight('ShowTicket') ) { - $group->LoadTicketRoleGroup(Type => 'AdminCc', Ticket => $self->Id); - } - return ($group); - -} - -# }}} - -# }}} - -# {{{ IsWatcher,IsRequestor,IsCc, IsAdminCc - -# {{{ sub IsWatcher -# a generic routine to be called by IsRequestor, IsCc and IsAdminCc - -=head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL } - -Takes a param hash with the attributes Type and either PrincipalId or Email - -Type is one of Requestor, Cc, AdminCc and Owner - -PrincipalId is an RT::Principal id, and Email is an email address. - -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 { - my $self = shift; - - my %args = ( Type => 'Requestor', - PrincipalId => undef, - Email => undef, - @_ - ); - - # Load the relevant group. - my $group = RT::Group->new($self->CurrentUser); - $group->LoadTicketRoleGroup(Type => $args{'Type'}, Ticket => $self->id); - - # Find the relevant principal. - my $principal = RT::Principal->new($self->CurrentUser); - if (!$args{PrincipalId} && $args{Email}) { - # Look up the specified user. - my $user = RT::User->new($self->CurrentUser); - $user->LoadByEmail($args{Email}); - if ($user->Id) { - $args{PrincipalId} = $user->PrincipalId; - } - else { - # A non-existent user can't be a group member. - return 0; - } - } - $principal->Load($args{'PrincipalId'}); - - # Ask if it has the member in question - return ($group->HasMember($principal)); -} - -# }}} - -# {{{ sub IsRequestor - -=head2 IsRequestor PRINCIPAL_ID - - Takes an RT::Principal id - Returns true if the principal is a requestor of the current ticket. - - -=cut - -sub IsRequestor { - my $self = shift; - my $person = shift; - - return ( $self->IsWatcher( Type => 'Requestor', PrincipalId => $person ) ); - -}; - -# }}} - -# {{{ sub IsCc - -=head2 IsCc PRINCIPAL_ID - - Takes an RT::Principal id. - Returns true if the principal is a requestor of the current ticket. - - -=cut - -sub IsCc { - my $self = shift; - my $cc = shift; - - return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) ); - -} - -# }}} - -# {{{ sub IsAdminCc - -=head2 IsAdminCc PRINCIPAL_ID - - Takes an RT::Principal id. - Returns true if the principal is a requestor of the current ticket. - -=cut - -sub IsAdminCc { - my $self = shift; - my $person = shift; - - return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) ); - -} - -# }}} - -# {{{ sub IsOwner - -=head2 IsOwner - - Takes an RT::User object. Returns true if that user is this ticket's owner. -returns undef otherwise - -=cut - -sub IsOwner { - my $self = shift; - my $person = shift; - - # no ACL check since this is used in acl decisions - # unless ($self->CurrentUserHasRight('ShowTicket')) { - # return(undef); - # } - - #Tickets won't yet have owners when they're being created. - unless ( $self->OwnerObj->id ) { - return (undef); - } - - if ( $person->id == $self->OwnerObj->id ) { - return (1); - } - else { - return (undef); - } -} - -# }}} - -# }}} - -# }}} - -# {{{ Routines dealing with queues - -# {{{ sub ValidateQueue - -sub ValidateQueue { - my $self = shift; - my $Value = shift; - - if ( !$Value ) { - $RT::Logger->warning( " RT:::Queue::ValidateQueue called with a null value. this isn't ok."); - return (1); - } - - my $QueueObj = RT::Queue->new( $self->CurrentUser ); - my $id = $QueueObj->Load($Value); - - if ($id) { - return (1); - } - else { - return (undef); - } -} - -# }}} - -# {{{ sub SetQueue - -sub SetQueue { - my $self = shift; - my $NewQueue = shift; - - #Redundant. ACL gets checked in _Set; - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - return ( 0, $self->loc("Permission Denied") ); - } - - my $NewQueueObj = RT::Queue->new( $self->CurrentUser ); - $NewQueueObj->Load($NewQueue); - - unless ( $NewQueueObj->Id() ) { - return ( 0, $self->loc("That queue does not exist") ); - } - - if ( $NewQueueObj->Id == $self->QueueObj->Id ) { - return ( 0, $self->loc('That is the same value') ); - } - unless ( - $self->CurrentUser->HasRight( - Right => 'CreateTicket', - Object => $NewQueueObj - ) - ) - { - return ( 0, $self->loc("You may not create requests in that queue.") ); - } - - unless ( - $self->OwnerObj->HasRight( - Right => 'OwnTicket', - Object => $NewQueueObj - ) - ) - { - $self->Untake(); - } - - return ( $self->_Set( Field => 'Queue', Value => $NewQueueObj->Id() ) ); - -} - -# }}} - -# {{{ sub QueueObj - -=head2 QueueObj - -Takes nothing. returns this ticket's queue object - -=cut - -sub QueueObj { - my $self = shift; - - my $queue_obj = RT::Queue->new( $self->CurrentUser ); - - #We call __Value so that we can avoid the ACL decision and some deep recursion - my ($result) = $queue_obj->Load( $self->__Value('Queue') ); - return ($queue_obj); -} - -# }}} - -# }}} - -# {{{ Date printing routines - -# {{{ sub DueObj - -=head2 DueObj - - Returns an RT::Date object containing this ticket's due date - -=cut - -sub DueObj { - my $self = shift; - - my $time = new RT::Date( $self->CurrentUser ); - - # -1 is RT::Date slang for never - if ( $self->Due ) { - $time->Set( Format => 'sql', Value => $self->Due ); - } - else { - $time->Set( Format => 'unix', Value => -1 ); - } - - return $time; -} - -# }}} - -# {{{ sub DueAsString - -=head2 DueAsString - -Returns this ticket's due date as a human readable string - -=cut - -sub DueAsString { - my $self = shift; - return $self->DueObj->AsString(); -} - -# }}} - -# {{{ sub ResolvedObj - -=head2 ResolvedObj - - Returns an RT::Date object of this ticket's 'resolved' time. - -=cut - -sub ResolvedObj { - my $self = shift; - - my $time = new RT::Date( $self->CurrentUser ); - $time->Set( Format => 'sql', Value => $self->Resolved ); - return $time; -} - -# }}} - -# {{{ sub SetStarted - -=head2 SetStarted - -Takes a date in ISO format or undef -Returns a transaction id and a message -The client calls "Start" to note that the project was started on the date in $date. -A null date means "now" - -=cut - -sub SetStarted { - my $self = shift; - my $time = shift || 0; - - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - 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 ) { - $time_obj->Set( Format => 'ISO', Value => $time ); - } - else { - $time_obj->SetToNow(); - } - - #Now that we're starting, open this ticket - #TODO do we really want to force this as policy? it should be a scrip - - #We need $TicketAsSystem, in case the current user doesn't have - #ShowTicket - # - my $TicketAsSystem = new RT::Ticket($RT::SystemUser); - $TicketAsSystem->Load( $self->Id ); - if ( $TicketAsSystem->Status eq 'new' ) { - $TicketAsSystem->Open(); - } - - return ( $self->_Set( Field => 'Started', Value => $time_obj->ISO ) ); - -} - -# }}} - -# {{{ sub StartedObj - -=head2 StartedObj - - Returns an RT::Date object which contains this ticket's -'Started' time. - -=cut - -sub StartedObj { - my $self = shift; - - my $time = new RT::Date( $self->CurrentUser ); - $time->Set( Format => 'sql', Value => $self->Started ); - return $time; -} - -# }}} - -# {{{ sub StartsObj - -=head2 StartsObj - - Returns an RT::Date object which contains this ticket's -'Starts' time. - -=cut - -sub StartsObj { - my $self = shift; - - my $time = new RT::Date( $self->CurrentUser ); - $time->Set( Format => 'sql', Value => $self->Starts ); - return $time; -} - -# }}} - -# {{{ sub ToldObj - -=head2 ToldObj - - Returns an RT::Date object which contains this ticket's -'Told' time. - -=cut - -sub ToldObj { - my $self = shift; - - my $time = new RT::Date( $self->CurrentUser ); - $time->Set( Format => 'sql', Value => $self->Told ); - return $time; -} - -# }}} - -# {{{ sub ToldAsString - -=head2 ToldAsString - -A convenience method that returns ToldObj->AsString - -TODO: This should be deprecated - -=cut - -sub ToldAsString { - my $self = shift; - if ( $self->Told ) { - return $self->ToldObj->AsString(); - } - else { - return ("Never"); - } -} - -# }}} - -# {{{ sub TimeWorkedAsString - -=head2 TimeWorkedAsString - -Returns the amount of time worked on this ticket as a Text String - -=cut - -sub TimeWorkedAsString { - my $self = shift; - return "0" unless $self->TimeWorked; - - #This is not really a date object, but if we diff a number of seconds - #vs the epoch, we'll get a nice description of time worked. - - my $worked = new RT::Date( $self->CurrentUser ); - - #return the #of minutes worked turned into seconds and written as - # a simple text string - - return ( $worked->DurationAsString( $self->TimeWorked * 60 ) ); -} - -# }}} - -# }}} - -# {{{ Routines dealing with correspondence/comments - -# {{{ sub Comment - -=head2 Comment - -Comment on this ticket. -Takes a hashref with the following attributes: -If MIMEObj is undefined, Content will be used to build a MIME::Entity for this -commentl - -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 - -sub Comment { - my $self = shift; - - my %args = ( CcMessageTo => undef, - BccMessageTo => undef, - MIMEObj => undef, - Content => undef, - TimeTaken => 0, - DryRun => 0, - @_ ); - - unless ( ( $self->CurrentUserHasRight('CommentOnTicket') ) - or ( $self->CurrentUserHasRight('ModifyTicket') ) ) { - return ( 0, $self->loc("Permission Denied"), undef ); - } - $args{'NoteType'} = 'Comment'; - - if ($args{'DryRun'}) { - $RT::Handle->BeginTransaction(); - $args{'CommitScrips'} = 0; - } - - my @results = $self->_RecordNote(%args); - if ($args{'DryRun'}) { - $RT::Handle->Rollback(); - } - - return(@results); -} -# }}} - -# {{{ sub Correspond - -=head2 Correspond - -Correspond on this ticket. -Takes a hashref with the following attributes: - - -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 - -sub Correspond { - my $self = shift; - my %args = ( CcMessageTo => undef, - BccMessageTo => undef, - MIMEObj => undef, - Content => undef, - TimeTaken => 0, - @_ ); - - unless ( ( $self->CurrentUserHasRight('ReplyToTicket') ) - or ( $self->CurrentUserHasRight('ModifyTicket') ) ) { - return ( 0, $self->loc("Permission Denied"), undef ); - } - - $args{'NoteType'} = 'Correspond'; - if ($args{'DryRun'}) { - $RT::Handle->BeginTransaction(); - $args{'CommitScrips'} = 0; - } - - 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)); - - if ($args{'DryRun'}) { - $RT::Handle->Rollback(); - } - - return (@results); - -} - -# }}} - -# {{{ sub _RecordNote - -=head2 _RecordNote - -the meat of both comment and correspond. - -Performs no access control checks. hence, dangerous. - -=cut - -sub _RecordNote { - - my $self = shift; - my %args = ( CcMessageTo => undef, - BccMessageTo => undef, - MIMEObj => undef, - Content => undef, - TimeTaken => 0, - CommitScrips => 1, - @_ ); - - unless ( $args{'MIMEObj'} || $args{'Content'} ) { - return ( 0, $self->loc("No message attached"), undef ); - } - unless ( $args{'MIMEObj'} ) { - $args{'MIMEObj'} = MIME::Entity->build( Data => ( - ref $args{'Content'} - ? $args{'Content'} - : [ $args{'Content'} ] - ) ); - } - - # convert text parts into utf-8 - RT::I18N::SetMIMEEntityToUTF8( $args{'MIMEObj'} ); - -# 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'}; - - # 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>/) { - $args{'MIMEObj'}->head->set( 'RT-Message-ID', - "<rt-" - . $RT::VERSION . "-" - . $$ . "-" - . CORE::time() . "-" - . int(rand(2000)) . '.' - . $self->id . "-" - . "0" . "-" # Scrip - . "0" . "@" # Email sent - . $RT::Organization - . ">" ); - } - - #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'}, - ); - - unless ($Trans) { - $RT::Logger->err("$self couldn't init a transaction $msg"); - return ( $Trans, $self->loc("Message could not be recorded"), undef ); - } - - return ( $Trans, $self->loc("Message recorded"), $TransObj ); -} - -# }}} - -# }}} - -# {{{ sub _Links - -sub _Links { - my $self = shift; - - #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic --- - #tobias meant by $f - my $field = shift; - my $type = shift || ""; - - unless ( $self->{"$field$type"} ) { - $self->{"$field$type"} = new RT::Links( $self->CurrentUser ); - 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 => $field, - VALUE => $self->URI, - ENTRYAGGREGATOR => 'OR' ); - $Tickets->Limit( FIELD => 'EffectiveId', - VALUE => $self->EffectiveId ); - while (my $Ticket = $Tickets->Next) { - $self->{"$field$type"}->Limit( FIELD => $field, - VALUE => $Ticket->URI, - ENTRYAGGREGATOR => 'OR' ); - } - $self->{"$field$type"}->Limit( FIELD => 'Type', - VALUE => $type ) - if ($type); - } - } - return ( $self->{"$field$type"} ); -} - -# }}} - -# {{{ sub DeleteLink - -=head2 DeleteLink - -Delete a link. takes a paramhash of Base, Target and Type. -Either Base or Target must be null. The null value will -be replaced with this ticket\'s id - -=cut - -sub DeleteLink { - my $self = shift; - my %args = ( - Base => undef, - Target => undef, - Type => undef, - @_ - ); - - #check acls - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - $RT::Logger->debug("No permission to delete links\n"); - return ( 0, $self->loc('Permission Denied')) - - } - - my ($val, $Msg) = $self->SUPER::_DeleteLink(%args); - - if ( !$val ) { - $RT::Logger->debug("Couldn't find that link\n"); - return ( 0, $Msg ); - } - - my ($direction, $remote_link); - - if ( $args{'Base'} ) { - $remote_link = $args{'Base'}; - $direction = 'Target'; - } - elsif ( $args{'Target'} ) { - $remote_link = $args{'Target'}; - $direction='Base'; - } - - if ( $val ) { - my $remote_uri = RT::URI->new( $RT::SystemUser ); - $remote_uri->FromURI( $remote_link ); - - my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( - Type => 'DeleteLink', - Field => $LINKDIRMAP{$args{'Type'}}->{$direction}, - OldValue => $remote_uri->URI || $remote_link, - TimeTaken => 0 - ); - - return ( $Trans, $Msg ); - } -} - -# }}} - -# {{{ sub AddLink - -=head2 AddLink - -Takes a paramhash of Type and one of Base or Target. Adds that link to this ticket. - -=begin testing - -my $q1 = RT::Queue->new($RT::SystemUser); -my ($id,$msg) = $q1->Create(Name => 'LinkTest1'); -ok ($id,$msg); -my $q2 = RT::Queue->new($RT::SystemUser); -($id,$msg) = $q2->Create(Name => 'LinkTest2'); -ok ($id,$msg); - -my $u1 = RT::User->new($RT::SystemUser); -($id,$msg) =$u1->Create(Name => 'LinkTestUser'); - -ok ($id,$msg); - -($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'CreateTicket'); -ok ($id,$msg); -($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'ModifyTicket'); -ok ($id,$msg); - -my $tid; - -my $creator = RT::CurrentUser->new($u1->id); - -my $ticket = RT::Ticket->new( $creator); -ok($ticket->isa('RT::Ticket')); -($id,$tid, $msg) = $ticket->Create(Subject => 'Link test 1', Queue => $q1->id); -ok ($id,$msg); - - -my $ticket2 = RT::Ticket->new($RT::SystemUser); -($id, $tid, $msg) = $ticket2->Create(Subject => 'Link test 2', Queue => $q2->id); -ok ($id, $msg); - -($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); -ok(!$id,$msg); -($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'CreateTicket'); -ok ($id,$msg); -($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'ModifyTicket'); -ok ($id,$msg); -($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id); -ok($id,$msg); -($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => -1); -ok(!$id,$msg); - -=end testing - -=cut - -sub AddLink { - my $self = shift; - my %args = ( Target => '', - Base => '', - Type => '', - Silent => undef, - @_ ); - - - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - return ( 0, $self->loc("Permission Denied") ); - } - - - $self->_AddLink(%args); -} - -=head2 _AddLink - -Private non-acled variant of AddLink so that links can be added during create. - -=cut - -sub _AddLink { - my $self = shift; - my %args = ( Target => '', - Base => '', - Type => '', - 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 ($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 ( $val, $Msg ); - } - else { - my $remote_uri = RT::URI->new( $RT::SystemUser ); - $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 ( $val, $Msg ); - } - -} - -# }}} - - -# {{{ sub MergeInto - -=head2 MergeInto - -MergeInto take the id of the ticket to merge this ticket into. - - -=begin testing - -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); - - -=end testing - -=cut - -sub MergeInto { - my $self = shift; - my $ticket_id = shift; - - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - return ( 0, $self->loc("Permission Denied") ); - } - - # Load up the new ticket. - my $MergeInto = RT::Ticket->new($RT::SystemUser); - $MergeInto->Load($ticket_id); - - # make sure it exists. - unless ( $MergeInto->Id ) { - return ( 0, $self->loc("New ticket doesn't exist") ); - } - - # Make sure the current user can modify the new ticket. - unless ( $MergeInto->CurrentUserHasRight('ModifyTicket') ) { - return ( 0, $self->loc("Permission Denied") ); - } - - $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 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 => $MergeInto->Id() - ); - - unless ($id_val) { - $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::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 (exists $old_seen{$link->Base."-".$link->Type}) { - $link->Delete; - } - elsif ($link->Base eq $MergeInto->URI) { - $link->Delete; - } else { - # 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; - } - - } - - my $old_links_from = RT::Links->new($self->CurrentUser); - $old_links_from->Limit(FIELD => 'Base', VALUE => $self->URI); - - while (my $link = $old_links_from->Next) { - if (exists $old_seen{$link->Type."-".$link->Target}) { - $link->Delete; - } - if ($link->Target eq $MergeInto->URI) { - $link->Delete; - } else { - # 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) { - - my $mutator = "Set$type"; - $MergeInto->$mutator( - ( $MergeInto->$type() || 0 ) + ( $self->$type() || 0 ) ); - - } -#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$//; - - 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 ); - $old_mergees->Limit( - FIELD => 'EffectiveId', - OPERATOR => '=', - VALUE => $self->Id - ); - - # update their EffectiveId fields to the new ticket's id - while ( my $ticket = $old_mergees->Next() ) { - my ( $val, $msg ) = $ticket->__Set( - Field => 'EffectiveId', - Value => $MergeInto->Id() - ); - } - - #make a new link: this ticket is merged into that other ticket. - $self->AddLink( Type => 'MergedInto', Target => $MergeInto->Id()); - - $MergeInto->_SetLastUpdated; - - $RT::Handle->Commit(); - return ( 1, $self->loc("Merge Successful") ); -} - -# }}} - -# }}} - -# {{{ Routines dealing with ownership - -# {{{ sub OwnerObj - -=head2 OwnerObj - -Takes nothing and returns an RT::User object of -this ticket's owner - -=cut - -sub OwnerObj { - my $self = shift; - - #If this gets ACLed, we lose on a rights check in User.pm and - #get deep recursion. if we need ACLs here, we need - #an equiv without ACLs - - my $owner = new RT::User( $self->CurrentUser ); - $owner->Load( $self->__Value('Owner') ); - - #Return the owner object - return ($owner); -} - -# }}} - -# {{{ sub OwnerAsString - -=head2 OwnerAsString - -Returns the owner's email address - -=cut - -sub OwnerAsString { - my $self = shift; - return ( $self->OwnerObj->EmailAddress ); - -} - -# }}} - -# {{{ sub SetOwner - -=head2 SetOwner - -Takes two arguments: - the Id or Name of the owner -and (optionally) the type of the SetOwner Transaction. It defaults -to 'Give'. 'Steal' is also a valid option. - -=begin testing - -my $root = RT::User->new($RT::SystemUser); -$root->Load('root'); -ok ($root->Id, "Loaded the root user"); -my $t = RT::Ticket->new($RT::SystemUser); -$t->Load(1); -$t->SetOwner('root'); -is ($t->OwnerObj->Name, 'root' , "Root owns the ticket"); -$t->Steal(); -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 => 'ObjectId', VALUE => '1'); -$txns->Limit(FIELD => 'ObjectType', VALUE => 'RT::Ticket'); -my $steal = $txns->First; -ok($steal->OldValue == $root->Id , "Stolen from root"); -ok($steal->NewValue == $RT::SystemUser->Id , "Stolen by the systemuser"); - -=end testing - -=cut - -sub SetOwner { - my $self = shift; - my $NewOwner = shift; - my $Type = shift || "Give"; - - # must have ModifyTicket rights - # or TakeTicket/StealTicket and $NewOwner is self - # see if it's a take - if ( $self->OwnerObj->Id == $RT::Nobody->Id ) { - unless ( $self->CurrentUserHasRight('ModifyTicket') - || $self->CurrentUserHasRight('TakeTicket') ) { - 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 ) { - - unless ( $self->CurrentUserHasRight('ModifyTicket') - || $self->CurrentUserHasRight('StealTicket') ) { - return ( 0, $self->loc("Permission Denied") ); - } - } - else { - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - 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've specified a new owner and that user can't modify the ticket - elsif ( ( $NewOwnerObj->Id ) - and ( !$NewOwnerObj->HasRight( Right => 'OwnTicket', - Object => $self ) ) - ) { - 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 ) ) { - 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. - my ( $del_id, $del_msg ) = $self->OwnerGroup->MembersObj->First->Delete(); - unless ($del_id) { - $RT::Handle->Rollback(); - return ( 0, $self->loc("Could not change owner. ") . $del_msg ); - } - - my ( $add_id, $add_msg ) = $self->OwnerGroup->_AddMember( - PrincipalId => $NewOwnerObj->PrincipalId, - InsideTransaction => 1 ); - unless ($add_id) { - $RT::Handle->Rollback(); - return ( 0, $self->loc("Could not change owner. ") . $add_msg ); - } - - # We call set twice with slightly different arguments, so - # as to not have an SQL transaction span two RT transactions - - my ( $val, $msg ) = $self->_Set( - Field => 'Owner', - RecordTransaction => 0, - Value => $NewOwnerObj->Id, - TimeTaken => 0, - TransactionType => $Type, - CheckACL => 0, # don't check acl - ); - - unless ($val) { - $RT::Handle->Rollback; - 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 ); - - if ($trans) { - $msg = $self->loc( "Owner changed from [_1] to [_2]", - $OldOwnerObj->Name, $NewOwnerObj->Name ); - - # TODO: make sure the trans committed properly - } - return ( $trans, $msg ); - -} - -# }}} - -# {{{ sub Take - -=head2 Take - -A convenince method to set the ticket's owner to the current user - -=cut - -sub Take { - my $self = shift; - return ( $self->SetOwner( $self->CurrentUser->Id, 'Take' ) ); -} - -# }}} - -# {{{ sub Untake - -=head2 Untake - -Convenience method to set the owner to 'nobody' if the current user is the owner. - -=cut - -sub Untake { - my $self = shift; - return ( $self->SetOwner( $RT::Nobody->UserObj->Id, 'Untake' ) ); -} - -# }}} - -# {{{ sub Steal - -=head2 Steal - -A convenience method to change the owner of the current ticket to the -current user. Even if it's owned by another user. - -=cut - -sub Steal { - my $self = shift; - - if ( $self->IsOwner( $self->CurrentUser ) ) { - return ( 0, $self->loc("You already own this ticket") ); - } - else { - return ( $self->SetOwner( $self->CurrentUser->Id, 'Steal' ) ); - - } - -} - -# }}} - -# }}} - -# {{{ Routines dealing with status - -# {{{ sub ValidateStatus - -=head2 ValidateStatus STATUS - -Takes a string. Returns true if that status is a valid status for this ticket. -Returns false otherwise. - -=cut - -sub ValidateStatus { - my $self = shift; - my $status = shift; - - #Make sure the status passed in is valid - unless ( $self->QueueObj->IsValidStatus($status) ) { - return (undef); - } - - return (1); - -} - -# }}} - -# {{{ sub SetStatus - -=head2 SetStatus STATUS - -Set this ticket\'s status. STATUS can be one of: new, open, stalled, resolved, rejected or deleted. - -Alternatively, you can pass in a list of named parameters (Status => STATUS, Force => FORCE). If FORCE is true, ignore unresolved dependencies and force a status change. - -=begin testing - -my $tt = RT::Ticket->new($RT::SystemUser); -my ($id, $tid, $msg)= $tt->Create(Queue => 'general', - Subject => 'test'); -ok($id, $msg); -is($tt->Status, 'new', "New ticket is created as new"); - -($id, $msg) = $tt->SetStatus('open'); -ok($id, $msg); -like($msg, qr/open/i, "Status message is correct"); -($id, $msg) = $tt->SetStatus('resolved'); -ok($id, $msg); -like($msg, qr/resolved/i, "Status message is correct"); -($id, $msg) = $tt->SetStatus('resolved'); -ok(!$id,$msg); - - -=end testing - - -=cut - -sub SetStatus { - my $self = shift; - my %args; - - if (@_ == 1) { - $args{Status} = shift; - } - else { - %args = (@_); - } - - #Check ACL - if ( $args{Status} eq 'deleted') { - unless ($self->CurrentUserHasRight('DeleteTicket')) { - return ( 0, $self->loc('Permission Denied') ); - } - } else { - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return ( 0, $self->loc('Permission Denied') ); - } - } - - if (!$args{Force} && ($args{'Status'} eq 'resolved') && $self->HasUnresolvedDependencies) { - return (0, $self->loc('That ticket has unresolved dependencies')); - } - - my $now = RT::Date->new( $self->CurrentUser ); - $now->SetToNow(); - - #If we're changing the status from new, record that we've started - if ( ( $self->Status =~ /new/ ) && ( $args{Status} ne 'new' ) ) { - - #Set the Started time to "now" - $self->_Set( Field => 'Started', - Value => $now->ISO, - RecordTransaction => 0 ); - } - - #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 ); - } - - #Actually update the status - my ($val, $msg)= $self->_Set( Field => 'Status', - Value => $args{Status}, - TimeTaken => 0, - CheckACL => 0, - TransactionType => 'Status' ); - - return($val,$msg); -} - -# }}} - -# {{{ sub Kill - -=head2 Kill - -Takes no arguments. Marks this ticket for garbage collection - -=cut - -sub Kill { - my $self = shift; - $RT::Logger->crit("'Kill' is deprecated. use 'Delete' instead."); - return $self->Delete; -} - -sub Delete { - my $self = shift; - return ( $self->SetStatus('deleted') ); - - # TODO: garbage collection -} - -# }}} - -# {{{ sub Stall - -=head2 Stall - -Sets this ticket's status to stalled - -=cut - -sub Stall { - my $self = shift; - return ( $self->SetStatus('stalled') ); -} - -# }}} - -# {{{ sub Reject - -=head2 Reject - -Sets this ticket's status to rejected - -=cut - -sub Reject { - my $self = shift; - return ( $self->SetStatus('rejected') ); -} - -# }}} - -# {{{ sub Open - -=head2 Open - -Sets this ticket\'s status to Open - -=cut - -sub Open { - my $self = shift; - return ( $self->SetStatus('open') ); -} - -# }}} - -# {{{ sub Resolve - -=head2 Resolve - -Sets this ticket\'s status to Resolved - -=cut - -sub Resolve { - my $self = shift; - return ( $self->SetStatus('resolved') ); -} - -# }}} - -# }}} - - -# {{{ Actions + Routines dealing with transactions - -# {{{ sub SetTold and _SetTold - -=head2 SetTold ISO [TIMETAKEN] - -Updates the told and records a transaction - -=cut - -sub SetTold { - my $self = shift; - my $told; - $told = shift if (@_); - my $timetaken = shift || 0; - - unless ( $self->CurrentUserHasRight('ModifyTicket') ) { - return ( 0, $self->loc("Permission Denied") ); - } - - my $datetold = new RT::Date( $self->CurrentUser ); - if ($told) { - $datetold->Set( Format => 'iso', - Value => $told ); - } - else { - $datetold->SetToNow(); - } - - return ( $self->_Set( Field => 'Told', - Value => $datetold->ISO, - TimeTaken => $timetaken, - TransactionType => 'Told' ) ); -} - -=head2 _SetTold - -Updates the told without a transaction or acl check. Useful when we're sending replies. - -=cut - -sub _SetTold { - my $self = shift; - - my $now = new RT::Date( $self->CurrentUser ); - $now->SetToNow(); - - #use __Set to get no ACLs ;) - return ( $self->__Set( Field => 'Told', - Value => $now->ISO ) ); -} - -# }}} - -=head2 TransactionBatch - - Returns an array reference of all transactions created on this ticket during - this ticket object's lifetime, or undef if there were none. - - Only works when the $RT::UseTransactionBatch config variable is set to true. - -=cut - -sub TransactionBatch { - my $self = shift; - return $self->{_TransactionBatch}; -} - -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; - 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 _OverlayAccessible - -sub _OverlayAccessible { - { - EffectiveId => { 'read' => 1, 'write' => 1, 'public' => 1 }, - Queue => { 'read' => 1, 'write' => 1 }, - Requestors => { 'read' => 1, 'write' => 1 }, - Owner => { 'read' => 1, 'write' => 1 }, - Subject => { 'read' => 1, 'write' => 1 }, - InitialPriority => { 'read' => 1, 'write' => 1 }, - FinalPriority => { 'read' => 1, 'write' => 1 }, - Priority => { 'read' => 1, 'write' => 1 }, - Status => { 'read' => 1, 'write' => 1 }, - TimeEstimated => { 'read' => 1, 'write' => 1 }, - TimeWorked => { 'read' => 1, 'write' => 1 }, - TimeLeft => { 'read' => 1, 'write' => 1 }, - Told => { 'read' => 1, 'write' => 1 }, - Resolved => { 'read' => 1 }, - Type => { 'read' => 1 }, - Starts => { 'read' => 1, 'write' => 1 }, - Started => { 'read' => 1, 'write' => 1 }, - Due => { 'read' => 1, 'write' => 1 }, - Creator => { 'read' => 1, 'auto' => 1 }, - Created => { 'read' => 1, 'auto' => 1 }, - LastUpdatedBy => { 'read' => 1, 'auto' => 1 }, - LastUpdated => { 'read' => 1, 'auto' => 1 } - }; - -} - -# }}} - -# {{{ sub _Set - -sub _Set { - my $self = shift; - - my %args = ( Field => undef, - Value => undef, - TimeTaken => 0, - RecordTransaction => 1, - UpdateTicket => 1, - CheckACL => 1, - TransactionType => 'Set', - @_ ); - - if ($args{'CheckACL'}) { - unless ( $self->CurrentUserHasRight('ModifyTicket')) { - return ( 0, $self->loc("Permission Denied")); - } - } - - unless ($args{'UpdateTicket'} || $args{'RecordTransaction'}) { - $RT::Logger->error("Ticket->_Set called without a mandate to record an update or update the ticket"); - return(0, $self->loc("Internal Error")); - } - - #if the user is trying to modify the record - - #Take care of the old value we really don't want to get in an ACL loop. - # so ask the super::_Value - my $Old = $self->SUPER::_Value("$args{'Field'}"); - - my ($ret, $msg); - if ( $args{'UpdateTicket'} ) { - - #Set the new value - ( $ret, $msg ) = $self->SUPER::_Set( Field => $args{'Field'}, - Value => $args{'Value'} ); - - #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 ); } - } - - if ( $args{'RecordTransaction'} == 1 ) { - - my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( - Type => $args{'TransactionType'}, - Field => $args{'Field'}, - NewValue => $args{'Value'}, - OldValue => $Old, - TimeTaken => $args{'TimeTaken'}, - ); - return ( $Trans, scalar $TransObj->BriefDescription ); - } - else { - return ( $ret, $msg ); - } -} - -# }}} - -# {{{ sub _Value - -=head2 _Value - -Takes the name of a table column. -Returns its value as a string, if the user passes an ACL check - -=cut - -sub _Value { - - my $self = shift; - my $field = shift; - - #if the field is public, return it. - if ( $self->_Accessible( $field, 'public' ) ) { - - #$RT::Logger->debug("Skipping ACL check for $field\n"); - return ( $self->SUPER::_Value($field) ); - - } - - #If the current user doesn't have ACLs, don't let em at it. - - unless ( $self->CurrentUserHasRight('ShowTicket') ) { - return (undef); - } - return ( $self->SUPER::_Value($field) ); - -} - -# }}} - -# {{{ sub _UpdateTimeTaken - -=head2 _UpdateTimeTaken - -This routine will increment the timeworked counter. it should -only be called from _NewTransaction - -=cut - -sub _UpdateTimeTaken { - my $self = shift; - my $Minutes = shift; - my ($Total); - - $Total = $self->SUPER::_Value("TimeWorked"); - $Total = ( $Total || 0 ) + ( $Minutes || 0 ); - $self->SUPER::_Set( - Field => "TimeWorked", - Value => $Total - ); - - return ($Total); -} - -# }}} - -# }}} - -# {{{ Routines dealing with ACCESS CONTROL - -# {{{ sub CurrentUserHasRight - -=head2 CurrentUserHasRight - - Takes the textual name of a Ticket scoped right (from RT::ACE) and returns -1 if the user has that right. It returns 0 if the user doesn't have that right. - -=cut - -sub CurrentUserHasRight { - my $self = shift; - my $right = shift; - - return ( - $self->HasRight( - Principal => $self->CurrentUser->UserObj(), - Right => "$right" - ) - ); - -} - -# }}} - -# {{{ sub HasRight - -=head2 HasRight - - Takes a paramhash with the attributes 'Right' and 'Principal' - 'Right' is a ticket-scoped textual right from RT::ACE - 'Principal' is an RT::User object - - Returns 1 if the principal has the right. Returns undef if not. - -=cut - -sub HasRight { - my $self = shift; - my %args = ( - Right => undef, - Principal => undef, - @_ - ); - - unless ( ( defined $args{'Principal'} ) and ( ref( $args{'Principal'} ) ) ) - { - Carp::cluck; - $RT::Logger->crit("Principal attrib undefined for Ticket::HasRight"); - return(undef); - } - - return ( - $args{'Principal'}->HasRight( - Object => $self, - Right => $args{'Right'} - ) - ); -} - -# }}} - -# }}} - -# {{{ 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 ahve. - -=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; - unless ( $field =~ /^\d+$/ ) { - my $cf = RT::CustomField->new( $self->CurrentUser ); - $cf->LoadByNameAndQueue( Name => $field, Queue => $self->QueueObj->Id ); - unless ( $cf->id ) { - $cf->LoadByNameAndQueue( Name => $field, Queue => '0' ); - } - $field = $cf->id; - } - 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 - -Jesse Vincent, jesse@bestpractical.com - -=head1 SEE ALSO - -RT - -=cut - |