diff options
Diffstat (limited to 'rt/lib/RT/Ticket_Overlay.pm')
| -rw-r--r-- | rt/lib/RT/Ticket_Overlay.pm | 4040 | 
1 files changed, 0 insertions, 4040 deletions
| diff --git a/rt/lib/RT/Ticket_Overlay.pm b/rt/lib/RT/Ticket_Overlay.pm deleted file mode 100644 index c88bbc90f..000000000 --- a/rt/lib/RT/Ticket_Overlay.pm +++ /dev/null @@ -1,4040 +0,0 @@ -# BEGIN LICENSE BLOCK -#  -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -#  -# (Except where explictly superceded by other copyright notices) -#  -# 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. -#  -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -#  -#  -# END LICENSE 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)); -ok($testcf->Create( Name => 'selectmulti', -                    Queue => $testqueue->id, -                               Type => 'SelectMultiple')); -ok($testcf->AddValue ( Name => 'Value1', -                        SortOrder => '1', -                        Description => 'A testing value')); -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)); -ok($t2->Subject eq 'Testing'); -ok($t2->QueueObj->Id eq $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 - -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::TicketCustomFieldValues; -use RT::Tickets; -use RT::URI::fsck_com_rt; -use RT::URI; - -=begin testing - - -ok(require RT::Ticket, "Loading the RT::Ticket library"); - -=end testing - -=cut - -# }}} - -# {{{ LINKTYPEMAP -# A helper table for relationships mapping to make it easier -# to build and parse links between tickets - -use vars '%LINKTYPEMAP'; - -%LINKTYPEMAP = ( -    MemberOf => { Type => 'MemberOf', -                  Mode => 'Target', }, -    Members => { 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', }, - -); - -# }}} - -# {{{ LINKDIRMAP -# A helper table for relationships 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', }, - -); - -# }}} - -# {{{ 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 = $self->LoadById($id); - -        unless ($ticketid) { -            $RT::Logger->debug("$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 { -        return (undef); -    } - -    #If we're merged, resolve the merge. -    if ( ( $self->EffectiveId ) and ( $self->EffectiveId != $self->Id ) ) { -        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 RT::User objects, email addresses or RT user Names -  Cc  - A reference to a list of RT::User objects, email addresses or Names -  AdminCc  - A reference to a  list of RT::User objects, email addresses or Names -  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 -  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, -                 Queue           => undef, -                 Requestor       => undef, -                 Cc              => undef, -                 AdminCc         => undef, -                 Type            => 'ticket', -                 Owner           => undef, -                 Subject         => '', -                 InitialPriority => undef, -                 FinalPriority   => 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 ( defined $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 ( defined $args{'FinalPriority'} ); - -    # {{{ 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 ( defined $args{'Owner'} ) { -        $Owner = RT::User->new( $self->CurrentUser ); -        $Owner->Load( $args{'Owner'} ); - -    } - -    #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("Invalid owner. Defaulting to 'nobody'."); - -        $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 !~ /^\d+$/); -        } -    } - - -    $RT::Handle->BeginTransaction(); - -    my %params =( Queue           => $QueueObj->Id, -                                   Owner           => $Owner->Id, -                                   Subject         => $args{'Subject'}, -                                   InitialPriority => $args{'InitialPriority'}, -                                   FinalPriority   => $args{'FinalPriority'}, -                                   Priority        => $args{'InitialPriority'}, -                                   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}); -    } - - -    my $id = $self->SUPER::Create( %params); -    unless ($id) { -        $RT::Logger->crit( "Couldn't create a ticket"); -        $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 => $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} ) ) { - -	    # 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 ( ref( $args{$arg} ) ? @{ $args{$arg} } : ( $args{$arg} ) ) { -        next unless ($value); -        $self->_AddCustomFieldValue( Field => $cfid, -                                     Value => $value, -                                     RecordTransaction => 0 -                                 ); -    } -    } -    # }}} - -    if ( $args{'_RecordTransaction'} ) { -        # {{{ Add a transaction for the create -        my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( -                                                     Type      => "Create", -                                                     TimeTaken => 0, -                                                     MIMEObj => $args{'MIMEObj'} -        ); - - -        if ( $self->Id && $Trans ) { -            $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name ); -            $ErrStr = join ( "\n", $ErrStr, @non_fatal_errors ); - -            $RT::Logger->info("Ticket ".$self->Id. " created in queue '".$QueueObj->Name."' by ".$self->CurrentUser->Name); -        } -        else { -            $RT::Handle->Rollback(); - -            # TODO where does this get errstr from? -            $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 ); - -    } -} - - -# }}} - -# {{{ sub CreateFromEmailMessage - - -=head2 CreateFromEmailMessage { Message, Queue, ExtractActorFromHeaders }  - -This code replaces what was once a large part of the email gateway. -It takes an email message as a parameter, parses out the sender, subject -and a MIME object. It then creates a ticket based on those attributes - -=cut - -sub CreateFromEmailMessage { -    my $self = shift; -    my %args = ( Message => undef, -                 Queue => undef, -                 ExtractActorFromSender => undef, -                 @_ ); - -     -    # Pull out requestor - -    # Pull out Cc? - -    #  - - -} - -# }}} - - -# {{{ CreateFrom822 - -=head2 FORMAT - -CreateTickets uses the template as a template for an ordered set of tickets  -to create. The basic format is as follows: - - - ===Create-Ticket: identifier - Param: Value - Param2: Value - Param3: Value - Content: Blah - blah - blah - ENDOFCONTENT -=head2 Acceptable fields - -A complete list of acceptable fields for this beastie: - - -    *  Queue           => Name or id# of a queue -       Subject         => A text string -       Status          => A valid status. defaults to 'new' - -       Due             => Dates can be specified in seconds since the epoch -                          to be handled literally or in a semi-free textual -                          format which RT will attempt to parse. -       Starts          =>  -       Started         =>  -       Resolved        =>  -       Owner           => Username or id of an RT user who can and should own  -                          this ticket -   +   Requestor       => Email address -   +   Cc              => Email address  -   +   AdminCc         => Email address  -       TimeWorked      =>  -       TimeEstimated   =>  -       TimeLeft        =>  -       InitialPriority =>  -       FinalPriority   =>  -       Type            =>  -    +  DependsOn       =>  -    +  DependedOnBy    => -    +  RefersTo        => -    +  ReferredToBy    =>  -    +  Members         => -    +  MemberOf        =>  -       Content         => content. Can extend to multiple lines. Everything -                          within a template after a Content: header is treated -                          as content until we hit a line containing only  -                          ENDOFCONTENT -       ContentType     => the content-type of the Content field -       CustomField-<id#> => custom field value - -Fields marked with an * are required. - -Fields marked with a + man have multiple values, simply -by repeating the fieldname on a new line with an additional value. - - -When parsed, field names are converted to lowercase and have -s stripped. -Refers-To, RefersTo, refersto, refers-to and r-e-f-er-s-tO will all  -be treated as the same thing. - - -=begin testing - -use_ok(RT::Ticket); - -=end testing - - -=cut - -sub CreateFrom822 { -    my $self    = shift; -    my $content = shift; - - - -    my %args = $self->_Parse822HeadersForAttributes($content); - -    # Now we have a %args to work with. -    # Make sure we have at least the minimum set of -    # reasonable data and do our thang -    my $ticket = RT::Ticket->new($RT::SystemUser); - -    my %ticketargs = ( -        Queue           => $args{'queue'}, -        Subject         => $args{'subject'}, -        Status          => $args{'status'}, -        Due             => $args{'due'}, -        Starts          => $args{'starts'}, -        Started         => $args{'started'}, -        Resolved        => $args{'resolved'}, -        Owner           => $args{'owner'}, -        Requestor       => $args{'requestor'}, -        Cc              => $args{'cc'}, -        AdminCc         => $args{'admincc'}, -        TimeWorked      => $args{'timeworked'}, -        TimeEstimated   => $args{'timeestimated'}, -        TimeLeft        => $args{'timeleft'}, -        InitialPriority => $args{'initialpriority'}, -        FinalPriority   => $args{'finalpriority'}, -        Type            => $args{'type'}, -        DependsOn       => $args{'dependson'}, -        DependedOnBy    => $args{'dependedonby'}, -        RefersTo        => $args{'refersto'}, -        ReferredToBy    => $args{'referredtoby'}, -        Members         => $args{'members'}, -        MemberOf        => $args{'memberof'}, -        MIMEObj         => $args{'mimeobj'} -    ); - -    # Add custom field entries to %ticketargs. -    # TODO: allow named custom fields -    map { -        /^customfield-(\d+)$/ -          && ( $ticketargs{ "CustomField-" . $1 } = $args{$_} ); -    } keys(%args); - -    my ( $id, $transid, $msg ) = $ticket->Create(%ticketargs); -    unless ($id) { -        $RT::Logger->error( "Couldn't create a related ticket for " -              . $self->TicketObj->Id . " " -              . $msg ); -    } - -    return (1); -} - -# }}} - -# {{{ UpdateFrom822  - -=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); -$ticket->Create(Subject => 'first', Queue => 'general'); -ok($ticket->Id, "Created the test ticket"); -$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 ); -    } - -    # die "updaterecordobject is a webui thingy"; -    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->DelWatcher( 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 $watcher; -    foreach $watcher ( @{ $args{'Cc'} } ) { -        $self->_AddWatcher( Type => 'Cc', Person => $watcher, Silent => 1 ); -    } -    foreach $watcher ( @{ $args{'AdminCc'} } ) { -        $self->_AddWatcher( Type => 'AdminCc', Person => $watcher, -            Silent => 1 ); -    } -    foreach $watcher ( @{ $args{'Requestor'} } ) { -        $self->_AddWatcher( Type => 'Requestor', Person => $watcher, -            Silent => 1 ); -    } - -    return ( $self->Id, $ErrStr ); -} - -# }}} - - -# {{{ Routines dealing with watchers. - -# {{{ _CreateTicketGroups  - -=head2 _CreateTicketGroups - -Create the ticket groups and relationships 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->warn( "$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 ($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->DelWatcher') ); -        } -    } - -    # 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'} )); -} - - - - -# }}} - - -# {{{ 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. - -=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. - -=cut - -## Please see file perltidy.ERR -sub Comment { -    my $self = shift; - -    my %args = ( CcMessageTo  => undef, -                 BccMessageTo => undef, -                 MIMEObj      => undef, -                 Content      => undef, -                 TimeTaken => 0, -                 @_ ); - -    unless (    ( $self->CurrentUserHasRight('CommentOnTicket') ) -             or ( $self->CurrentUserHasRight('ModifyTicket') ) ) { -        return ( 0, $self->loc("Permission Denied") ); -    } - -    unless ( $args{'MIMEObj'} ) { -        if ( $args{'Content'} ) { -            use MIME::Entity; -            $args{'MIMEObj'} = MIME::Entity->build( -		Data => ( ref $args{'Content'} ? $args{'Content'} : [ $args{'Content'} ] ) -	    ); -        } -        else { - -            return ( 0, $self->loc("No correspondence attached") ); -        } -    } - -    RT::I18N::SetMIMEEntityToUTF8($args{'MIMEObj'}); # convert text parts into utf-8 - -    # If we've been passed in CcMessageTo and BccMessageTo fields, -    # add them to the mime object for passing on to the transaction handler -    # The "NotifyOtherRecipients" scripAction will look for RT--Send-Cc: and -    # RT-Send-Bcc: headers - -    $args{'MIMEObj'}->head->add( 'RT-Send-Cc',  $args{'CcMessageTo'} ) -	if defined $args{'CcMessageTo'}; -    $args{'MIMEObj'}->head->add( 'RT-Send-Bcc', $args{'BccMessageTo'} ) -	if defined $args{'BccMessageTo'}; - -    #Record the correspondence (write the transaction) -    my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( -        Type      => 'Comment', -        Data      => ( $args{'MIMEObj'}->head->get('subject') || 'No Subject' ), -        TimeTaken => $args{'TimeTaken'}, -        MIMEObj   => $args{'MIMEObj'} -    ); - -    return ( $Trans, $self->loc("The comment has been recorded") ); -} - -# }}} - -# {{{ sub Correspond - -=head2 Correspond - -Correspond on this ticket. -Takes a hashref with the following attributes: - - -MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content - -if there's no MIMEObj, Content is used to build a MIME::Entity object - - -=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") ); -    } - -    unless ( $args{'MIMEObj'} ) { -        if ( $args{'Content'} ) { -            use MIME::Entity; -            $args{'MIMEObj'} = MIME::Entity->build( -		Data => ( ref $args{'Content'} ?  $args{'Content'} : [ $args{'Content'} ] ) -	    ); - -        } -        else { - -            return ( 0, $self->loc("No correspondence attached") ); -        } -    } - -    RT::I18N::SetMIMEEntityToUTF8($args{'MIMEObj'}); # convert text parts into utf-8 - -    # If we've been passed in CcMessageTo and BccMessageTo fields, -    # add them to the mime object for passing on to the transaction handler -    # The "NotifyOtherRecipients" scripAction will look for RT-Send-Cc: and RT-Send-Bcc: -    # headers - -    $args{'MIMEObj'}->head->add( 'RT-Send-Cc',  $args{'CcMessageTo'} ) -	if defined $args{'CcMessageTo'}; -    $args{'MIMEObj'}->head->add( 'RT-Send-Bcc', $args{'BccMessageTo'} ) -	if defined $args{'BccMessageTo'}; - -    #Record the correspondence (write the transaction) -    my ( $Trans, $msg, $TransObj ) = $self->_NewTransaction( -             Type => 'Correspond', -             Data => ( $args{'MIMEObj'}->head->get('subject') || 'No Subject' ), -             TimeTaken => $args{'TimeTaken'}, -             MIMEObj   => $args{'MIMEObj'} ); - -    unless ($Trans) { -        $RT::Logger->err( "$self couldn't init a transaction $msg"); -        return ( $Trans, $self->loc("correspondence (probably) not sent"), $args{'MIMEObj'} ); -    } - -    #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" - -    unless ( $TransObj->IsInbound ) { -        $self->_SetTold; -    } - -    return ( $Trans, $self->loc("correspondence sent") ); -} - -# }}} - -# }}} - -# {{{ Routines dealing with Links and Relations between tickets - -# {{{ Link Collections - -# {{{ sub Members - -=head2 Members - -  This returns an RT::Links object which references all the tickets  -which are 'MembersOf' this ticket - -=cut - -sub Members { -    my $self = shift; -    return ( $self->_Links( 'Target', 'MemberOf' ) ); -} - -# }}} - -# {{{ sub MemberOf - -=head2 MemberOf - -  This returns an RT::Links object which references all the tickets that this -ticket is a 'MemberOf' - -=cut - -sub MemberOf { -    my $self = shift; -    return ( $self->_Links( 'Base', 'MemberOf' ) ); -} - -# }}} - -# {{{ RefersTo - -=head2 RefersTo - -  This returns an RT::Links object which shows all references for which this ticket is a base - -=cut - -sub RefersTo { -    my $self = shift; -    return ( $self->_Links( 'Base', 'RefersTo' ) ); -} - -# }}} - -# {{{ ReferredToBy - -=head2 ReferredToBy - -  This returns an RT::Links object which shows all references for which this ticket is a target - -=cut - -sub ReferredToBy { -    my $self = shift; -    return ( $self->_Links( 'Target', 'RefersTo' ) ); -} - -# }}} - -# {{{ DependedOnBy - -=head2 DependedOnBy - -  This returns an RT::Links object which references all the tickets that depend on this one - -=cut - -sub DependedOnBy { -    my $self = shift; -    return ( $self->_Links( 'Target', 'DependsOn' ) ); -} - -# }}} - - - -=head2 HasUnresolvedDependencies - -  Takes a paramhash of Type (default to '__any').  Returns true if -$self->UnresolvedDependencies returns an object with one or more members -of that type.  Returns false otherwise - - -=begin testing - -my $t1 = RT::Ticket->new($RT::SystemUser); -my ($id, $trans, $msg) = $t1->Create(Subject => 'DepTest1', Queue => 'general'); -ok($id, "Created dep test 1 - $msg"); - -my $t2 = RT::Ticket->new($RT::SystemUser); -my ($id2, $trans, $msg2) = $t2->Create(Subject => 'DepTest2', Queue => 'general'); -ok($id2, "Created dep test 2 - $msg2"); -my $t3 = RT::Ticket->new($RT::SystemUser); -my ($id3, $trans, $msg3) = $t3->Create(Subject => 'DepTest3', Queue => 'general', Type => 'approval'); -ok($id3, "Created dep test 3 - $msg3"); - -ok ($t1->AddLink( Type => 'DependsOn', Target => $t2->id)); -ok ($t1->AddLink( Type => 'DependsOn', Target => $t3->id)); - -ok ($t1->HasUnresolvedDependencies, "Ticket ".$t1->Id." has unresolved deps"); -ok (!$t1->HasUnresolvedDependencies( Type => 'blah' ), "Ticket ".$t1->Id." has no unresolved blahs"); -ok ($t1->HasUnresolvedDependencies( Type => 'approval' ), "Ticket ".$t1->Id." has unresolved approvals"); -ok (!$t2->HasUnresolvedDependencies, "Ticket ".$t2->Id." has no unresolved deps"); -my ($rid, $rmsg)= $t1->Resolve(); -ok(!$rid, $rmsg); -ok($t2->Resolve); -($rid, $rmsg)= $t1->Resolve(); -ok(!$rid, $rmsg); -ok($t3->Resolve); -($rid, $rmsg)= $t1->Resolve(); -ok($rid, $rmsg); - - -=end testing - -=cut - -sub HasUnresolvedDependencies { -    my $self = shift; -    my %args = ( -        Type   => undef, -        @_ -    ); - -    my $deps = $self->UnresolvedDependencies; - -    if ($args{Type}) { -        $deps->Limit( FIELD => 'Type',  -              OPERATOR => '=', -              VALUE => $args{Type});  -    } -    else { -	    $deps->IgnoreType; -    } - -    if ($deps->Count > 0) { -        return 1; -    } -    else { -        return (undef); -    } -} - - -# {{{ UnresolvedDependencies  - -=head2 UnresolvedDependencies - -Returns an RT::Tickets object of tickets which this ticket depends on -and which have a status of new, open or stalled. (That list comes from -RT::Queue->ActiveStatusArray - -=cut - - -sub UnresolvedDependencies { -    my $self = shift; -    my $deps = RT::Tickets->new($self->CurrentUser); - -    my @live_statuses = RT::Queue->ActiveStatusArray(); -    foreach my $status (@live_statuses) { -        $deps->LimitStatus(VALUE => $status); -    } -    $deps->LimitDependedOnBy($self->Id); - -    return($deps); - -} - -# }}} - -# {{{ AllDependedOnBy - -=head2 AllDependedOnBy - -Returns an array of RT::Ticket objects which (directly or indirectly) -depends on this ticket; takes an optional 'Type' argument in the param -hash, which will limit returned tickets to that type, as well as cause -tickets with that type to serve as 'leaf' nodes that stops the recursive -dependency search. - -=cut - -sub AllDependedOnBy { -    my $self = shift; -    my $dep = $self->DependedOnBy; -    my %args = ( -        Type   => undef, -	_found => {}, -	_top   => 1, -        @_ -    ); - -    while (my $link = $dep->Next()) { -	next unless ($link->BaseURI->IsLocal()); -	next if $args{_found}{$link->BaseObj->Id}; - -	if (!$args{Type}) { -	    $args{_found}{$link->BaseObj->Id} = $link->BaseObj; -	    $link->BaseObj->AllDependedOnBy( %args, _top => 0 ); -	} -	elsif ($link->BaseObj->Type eq $args{Type}) { -	    $args{_found}{$link->BaseObj->Id} = $link->BaseObj; -	} -	else { -	    $link->BaseObj->AllDependedOnBy( %args, _top => 0 ); -	} -    } - -    if ($args{_top}) { -	return map { $args{_found}{$_} } sort keys %{$args{_found}}; -    } -    else { -	return 1; -    } -} - -# }}} - -# {{{ DependsOn - -=head2 DependsOn - -  This returns an RT::Links object which references all the tickets that this ticket depends on - -=cut - -sub DependsOn { -    my $self = shift; -    return ( $self->_Links( 'Base', 'DependsOn' ) ); -} - -# }}} - - - - -# {{{ sub _Links  - -sub _Links { -    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')) - -    } - -    #we want one of base and target. we don't care which -    #but we only want _one_ - -    my $direction; -    my $remote_link; - -    if ( $args{'Base'} and $args{'Target'} ) { -        $RT::Logger->debug("$self ->_DeleteLink. got both Base and Target\n"); -        return ( 0, $self->loc("Can't specifiy both base and target") ); -    } -    elsif ( $args{'Base'} ) { -        $args{'Target'} = $self->URI(); -	$remote_link = $args{'Base'}; -    	$direction = 'Target'; -    } -    elsif ( $args{'Target'} ) { -        $args{'Base'} = $self->URI(); -	$remote_link = $args{'Target'}; -        $direction='Base'; -    } -    else { -        $RT::Logger->debug("$self: Base or Target must be specified\n"); -        return ( 0, $self->loc('Either base or target must be specified') ); -    } - -    my $link = new RT::Link( $self->CurrentUser ); -    $RT::Logger->debug( "Trying to load link: " . $args{'Base'} . " " . $args{'Type'} . " " . $args{'Target'} . "\n" ); - - -    $link->LoadByParams( Base=> $args{'Base'}, Type=> $args{'Type'}, Target=>  $args{'Target'} ); -    #it's a real link.  -    if ( $link->id ) { - -        my $linkid = $link->id; -        $link->Delete(); - -        my $TransString = "Ticket $args{'Base'} no longer $args{Type} ticket $args{'Target'}."; -	my $remote_uri = RT::URI->new( $RT::SystemUser ); -    	$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, $self->loc("Link deleted ([_1])", $TransString)); -    } - -    #if it's not a link we can find -    else { -        $RT::Logger->debug("Couldn't find that link\n"); -        return ( 0, $self->loc("Link not found") ); -    } -} - -# }}} - -# {{{ sub AddLink - -=head2 AddLink - -Takes a paramhash of Type and one of Base or Target. Adds that link to this ticket. - - -=cut - -sub AddLink { -    my $self = shift; -    my %args = ( Target => '', -                 Base   => '', -                 Type   => '', -                 Silent => undef, -                 @_ ); - -    unless ( $self->CurrentUserHasRight('ModifyTicket') ) { -        return ( 0, $self->loc("Permission Denied") ); -    } - -    # Remote_link is the URI of the object that is not this ticket -    my $remote_link; -    my $direction; - -    if ( $args{'Base'} and $args{'Target'} ) { -        $RT::Logger->debug( -"$self tried to delete a link. both base and target were specified\n" ); -        return ( 0, $self->loc("Can't specifiy both base and target") ); -    } -    elsif ( $args{'Base'} ) { -        $args{'Target'} = $self->URI(); -	$remote_link = $args{'Base'}; -    	$direction = 'Target'; -    } -    elsif ( $args{'Target'} ) { -        $args{'Base'} = $self->URI(); -	$remote_link = $args{'Target'}; -        $direction='Base'; -    } -    else { -        return ( 0, $self->loc('Either base or target must be specified') ); -    } - -    # If the base isn't a URI, make it a URI.  -    # If the target isn't a URI, make it a URI.  - -    # {{{ Check if the link already exists - we don't want duplicates -    use RT::Link; -    my $old_link = RT::Link->new( $self->CurrentUser ); -    $old_link->LoadByParams( Base   => $args{'Base'}, -                             Type   => $args{'Type'}, -                             Target => $args{'Target'} ); -    if ( $old_link->Id ) { -        $RT::Logger->debug("$self Somebody tried to duplicate a link"); -        return ( $old_link->id, $self->loc("Link already exists"), 0 ); -    } - -    # }}} - -    # Storing the link in the DB. -    my $link = RT::Link->new( $self->CurrentUser ); -    my ($linkid) = $link->Create( Target => $args{Target}, -                                  Base   => $args{Base}, -                                  Type   => $args{Type} ); - -    unless ($linkid) { -        return ( 0, $self->loc("Link could not be created") ); -    } - -    my $TransString = -      "Ticket $args{'Base'} $args{Type} ticket $args{'Target'}."; - -    # Don't write the transaction if we're doing this on create -    if ( $args{'Silent'} ) { -        return ( 1, $self->loc( "Link created ([_1])", $TransString ) ); -    } -    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 ( $Trans, $self->loc( "Link created ([_1])", $TransString ) ); -    } - -} - -# }}} - -# {{{ sub URI  - -=head2 URI - -Returns this ticket's URI - -=cut - -sub URI { -    my $self = shift; -    my $uri = RT::URI::fsck_com_rt->new($self->CurrentUser); -    return($uri->URIForObject($self)); -} - -# }}} - -# {{{ sub MergeInto - -=head2 MergeInto -MergeInto take the id of the ticket to merge this ticket into. - -=cut - -sub MergeInto { -    my $self      = shift; -    my $MergeInto = shift; - -    unless ( $self->CurrentUserHasRight('ModifyTicket') ) { -        return ( 0, $self->loc("Permission Denied") ); -    } - -    # Load up the new ticket. -    my $NewTicket = RT::Ticket->new($RT::SystemUser); -    $NewTicket->Load($MergeInto); - -    # make sure it exists. -    unless ( defined $NewTicket->Id ) { -        return ( 0, $self->loc("New ticket doesn't exist") ); -    } - -    # Make sure the current user can modify the new ticket. -    unless ( $NewTicket->CurrentUserHasRight('ModifyTicket') ) { -        $RT::Logger->debug("failed..."); -        return ( 0, $self->loc("Permission Denied") ); -    } - -    $RT::Logger->debug( -        "checking if the new ticket has the same id and effective id..."); -    unless ( $NewTicket->id == $NewTicket->EffectiveId ) { -        $RT::Logger->err( "$self trying to merge into " -              . $NewTicket->Id -              . " which is itself merged.\n" ); -        return ( 0, -            $self->loc("Can't merge into a merged ticket. You should never get this error") ); -    } - -    # We use EffectiveId here even though it duplicates information from -    # the links table becasue of the massive performance hit we'd take -    # by trying to do a seperate database query for merge info everytime  -    # loaded a ticket.  - -    #update this ticket's effective id to the new ticket's id. -    my ( $id_val, $id_msg ) = $self->__Set( -        Field => 'EffectiveId', -        Value => $NewTicket->Id() -    ); - -    unless ($id_val) { -        $RT::Logger->error( -            "Couldn't set effective ID for " . $self->Id . ": $id_msg" ); -        return ( 0, $self->loc("Merge failed. Couldn't set EffectiveId") ); -    } - -    my ( $status_val, $status_msg ) = $self->__Set( Field => 'Status', Value => 'resolved'); - -    unless ($status_val) { -        $RT::Logger->error( $self->loc("[_1] couldn't set status to resolved. RT's Database may be inconsistent.", $self) ); -    } - - -    # 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); - -    while (my $link = $old_links_to->Next) { -        if ($link->Base eq $NewTicket->URI) { -            $link->Delete; -        } else { -            $link->SetTarget($NewTicket->URI); -        } - -    } - -    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 ($link->Target eq $NewTicket->URI) { -            $link->Delete; -        } else { -            $link->SetBase($NewTicket->URI); -        } - -    } - - -    #make a new link: this ticket is merged into that other ticket. -    $self->AddLink( Type   => 'MergedInto', Target => $NewTicket->Id()); - -    #add all of this ticket's watchers to that ticket. -    my $requestors = $self->Requestors->MembersObj; -    while (my $watcher = $requestors->Next) {  -        $NewTicket->_AddWatcher( Type => 'Requestor', -                                  Silent => 1, -                                  PrincipalId => $watcher->MemberId); -    } - -    my $Ccs = $self->Cc->MembersObj; -    while (my $watcher = $Ccs->Next) {  -        $NewTicket->_AddWatcher( Type => 'Cc', -                                  Silent => 1, -                                  PrincipalId => $watcher->MemberId); -    } - -    my $AdminCcs = $self->AdminCc->MembersObj; -    while (my $watcher = $AdminCcs->Next) {  -        $NewTicket->_AddWatcher( Type => 'AdminCc', -                                  Silent => 1, -                                  PrincipalId => $watcher->MemberId); -    } - - -    #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 => $NewTicket->Id() -        ); -    } - -    $NewTicket->_SetLastUpdated; - -    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'); -ok ($t->OwnerObj->Name eq 'root' , "Root owns the ticket"); -$t->Steal(); -ok ($t->OwnerObj->id eq $RT::SystemUser->id , "SystemUser owns the ticket"); -my $txns = RT::Transactions->new($RT::SystemUser); -$txns->OrderBy(FIELD => 'id', ORDER => 'DESC'); -$txns->Limit(FIELD => 'Ticket', VALUE => '1'); -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); -ok($tt->Status eq 'new', "New ticket is created as new"); - -($id, $msg) = $tt->SetStatus('open'); -ok($id, $msg); -ok ($msg =~ /open/i, "Status message is correct"); -($id, $msg) = $tt->SetStatus('resolved'); -ok($id, $msg); -ok ($msg =~ /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 -    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 ); -    } - -    if ( $args{Status} =~ /^(resolved|rejected|dead)$/ ) { - -        #When we resolve a ticket, set the 'Resolved' attribute to now. -        $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, -                          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') ); -} - -# }}} - -# }}} - -# {{{ Routines dealing with custom fields - - -# {{{ FirstCustomFieldValue - -=item FirstCustomFieldValue FIELD - -Return the content of the first value of CustomField FIELD for this ticket -Takes a field id or name - -=cut - -sub FirstCustomFieldValue { -    my $self = shift; -    my $field = shift; -    my $values = $self->CustomFieldValues($field); -    if ($values->First) { -        return $values->First->Content; -    } else { -        return undef; -    } - -} - - - -# {{{ CustomFieldValues - -=item CustomFieldValues FIELD - -Return a TicketCustomFieldValues object of all values of CustomField FIELD for this ticket.   -Takes a field id or name. - - -=cut - -sub CustomFieldValues { -    my $self  = shift; -    my $field = shift; - -    my $cf = RT::CustomField->new($self->CurrentUser); - -    if ($field =~ /^\d+$/) { -        $cf->LoadById($field); -    } else { -        $cf->LoadByNameAndQueue(Name => $field, Queue => $self->QueueObj->Id); -    } -    my $cf_values = RT::TicketCustomFieldValues->new( $self->CurrentUser ); -    $cf_values->LimitToCustomField($cf->id); -    $cf_values->LimitToTicket($self->Id()); - -    # @values is a CustomFieldValues object; -    return ($cf_values); -} - -# }}} - -# {{{ AddCustomFieldValue - -=item AddCustomFieldValue { Field => FIELD, Value => VALUE } - -VALUE can either be a CustomFieldValue object or a string. -FIELD can be a CustomField object OR a CustomField ID. - - -Adds VALUE as a value of CustomField FIELD.  If this is a single-value custom field, -deletes the old value.  -If VALUE isn't a valid value for the custom field, returns  -(0, 'Error message' ) otherwise, returns (1, 'Success Message') - -=cut - -sub AddCustomFieldValue { -    my $self = shift; -    unless ( $self->CurrentUserHasRight('ModifyTicket') ) { -        return ( 0, $self->loc("Permission Denied") ); -    } -    $self->_AddCustomFieldValue(@_); -} - -sub _AddCustomFieldValue { -    my $self = shift; -    my %args = ( -        Field => undef, -        Value => undef, -	RecordTransaction => 1, -        @_ -    ); - -    my $cf = RT::CustomField->new( $self->CurrentUser ); -    if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) { -        $cf->Load( $args{'Field'}->id ); -    } -    else { -        $cf->Load( $args{'Field'} ); -    } - -    unless ( $cf->Id ) { -        return ( 0, $self->loc("Custom field [_1] not found", $args{'Field'}) ); -    } - -    # Load up a TicketCustomFieldValues object for this custom field and this ticket -    my $values = $cf->ValuesForTicket( $self->id ); - -    unless ( $cf->ValidateValue( $args{'Value'} ) ) { -        return ( 0, $self->loc("Invalid value for custom field") ); -    } - -    # If the custom field only accepts a single value, delete the existing -    # value and record a "changed from foo to bar" transaction -    if ( $cf->SingleValue ) { - -        # We need to whack any old values here.  In most cases, the custom field should -        # only have one value to delete.  In the pathalogical case, this custom field -        # used to be a multiple and we have many values to whack.... -        my $cf_values = $values->Count; - -        if ( $cf_values > 1 ) { -            my $i = 0;   #We want to delete all but the last one, so we can then -                 # execute the same code to "change" the value from old to new -            while ( my $value = $values->Next ) { -                $i++; -                if ( $i < $cf_values ) { -                    my $old_value = $value->Content; -                    my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $value->Content); -                    unless ($val) { -                        return (0,$msg); -                    } -                    my ( $TransactionId, $Msg, $TransactionObj ) = -                      $self->_NewTransaction( -                        Type     => 'CustomField', -                        Field    => $cf->Id, -                        OldValue => $old_value -                      ); -                } -            } -        } - -        my $old_value; -        if (my $value = $cf->ValuesForTicket( $self->Id )->First) { -	    $old_value = $value->Content(); -	    return (1) if $old_value eq $args{'Value'}; -	} - -        my ( $new_value_id, $value_msg ) = $cf->AddValueForTicket( -            Ticket  => $self->Id, -            Content => $args{'Value'} -        ); - -        unless ($new_value_id) { -            return ( 0, -                $self->loc("Could not add new custom field value for ticket. [_1] ", -                  ,$value_msg) ); -        } - -        my $new_value = RT::TicketCustomFieldValue->new( $self->CurrentUser ); -        $new_value->Load($new_value_id); - -        # now that adding the new value was successful, delete the old one -	if ($old_value) { -	    my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $old_value); -	    unless ($val) {  -	    		return (0,$msg); -	    } -	} - -	if ($args{'RecordTransaction'}) { -        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction( -            Type     => 'CustomField', -            Field    => $cf->Id, -            OldValue => $old_value, -            NewValue => $new_value->Content -        ); -	} - -        if ( $old_value eq '' ) { -            return ( 1, $self->loc("[_1] [_2] added", $cf->Name, $new_value->Content) ); -        } -        elsif ( $new_value->Content eq '' ) { -            return ( 1, $self->loc("[_1] [_2] deleted", $cf->Name, $old_value) ); -        } -        else { -            return ( 1, $self->loc("[_1] [_2] changed to [_3]", $cf->Name, $old_value, $new_value->Content ) ); -        } - -    } - -    # otherwise, just add a new value and record "new value added" -    else { -        my ( $new_value_id ) = $cf->AddValueForTicket( -            Ticket  => $self->Id, -            Content => $args{'Value'} -        ); - -        unless ($new_value_id) { -            return ( 0, -                $self->loc("Could not add new custom field value for ticket. ")); -        } -    if ( $args{'RecordTransaction'} ) { -        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction( -            Type     => 'CustomField', -            Field    => $cf->Id, -            NewValue => $args{'Value'} -        ); -        unless ($TransactionId) { -            return ( 0, -                $self->loc( "Couldn't create a transaction: [_1]", $Msg ) ); -        } -    } -        return ( 1, $self->loc("[_1] added as a value for [_2]",$args{'Value'}, $cf->Name)); -    } - -} - -# }}} - -# {{{ DeleteCustomFieldValue - -=item DeleteCustomFieldValue { Field => FIELD, Value => VALUE } - -Deletes VALUE as a value of CustomField FIELD.  - -VALUE can be a string, a CustomFieldValue or a TicketCustomFieldValue. - -If VALUE isn't a valid value for the custom field, returns  -(0, 'Error message' ) otherwise, returns (1, 'Success Message') - -=cut - -sub DeleteCustomFieldValue { -    my $self = shift; -    my %args = ( -        Field => undef, -        Value => undef, -        @_); - -    unless ( $self->CurrentUserHasRight('ModifyTicket') ) { -        return ( 0, $self->loc("Permission Denied") ); -    } -    my $cf = RT::CustomField->new( $self->CurrentUser ); -    if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) { -        $cf->LoadById( $args{'Field'}->id ); -    } -    else { -        $cf->LoadById( $args{'Field'} ); -    } - -    unless ( $cf->Id ) { -        return ( 0, $self->loc("Custom field not found") ); -    } - - -     my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $args{'Value'}); -     unless ($val) {  -            return (0,$msg); -     } -        my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction( -            Type     => 'CustomField', -            Field    => $cf->Id, -            OldValue => $args{'Value'} -        ); -        unless($TransactionId) { -            return(0, $self->loc("Couldn't create a transaction: [_1]", $Msg)); -        }  - -        return($TransactionId, $self->loc("[_1] is no longer a value for custom field [_2]", $args{'Value'}, $cf->Name)); -} - -# }}} - -# }}} - -# {{{ Actions + Routines dealing with transactions - -# {{{ sub SetTold and _SetTold - -=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 ) ); -} - -# }}} - -# {{{ sub Transactions  - -=head2 Transactions - -  Returns an RT::Transactions object of all transactions on this ticket - -=cut - -sub Transactions { -    my $self = shift; - -    use RT::Transactions; -    my $transactions = RT::Transactions->new( $self->CurrentUser ); - -    #If the user has no rights, return an empty object -    if ( $self->CurrentUserHasRight('ShowTicket') ) { -        my $tickets = $transactions->NewAlias('Tickets'); -        $transactions->Join( -            ALIAS1 => 'main', -            FIELD1 => 'Ticket', -            ALIAS2 => $tickets, -            FIELD2 => 'id' -        ); -        $transactions->Limit( -            ALIAS => $tickets, -            FIELD => 'EffectiveId', -            VALUE => $self->id() -        ); - -        # if the user may not see comments do not return them -        unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { -            $transactions->Limit( -                FIELD    => 'Type', -                OPERATOR => '!=', -                VALUE    => "Comment" -            ); -        } -    } - -    return ($transactions); -} - -# }}} - -# {{{ sub _NewTransaction - -sub _NewTransaction { -    my $self = shift; -    my %args = ( -        TimeTaken => 0, -        Type      => undef, -        OldValue  => undef, -        NewValue  => undef, -        Data      => undef, -        Field     => undef, -        MIMEObj   => undef, -        @_ -    ); - -    require RT::Transaction; -    my $trans = new RT::Transaction( $self->CurrentUser ); -    my ( $transaction, $msg ) = $trans->Create( -        Ticket    => $self->Id, -        TimeTaken => $args{'TimeTaken'}, -        Type      => $args{'Type'}, -        Data      => $args{'Data'}, -        Field     => $args{'Field'}, -        NewValue  => $args{'NewValue'}, -        OldValue  => $args{'OldValue'}, -        MIMEObj   => $args{'MIMEObj'} -    ); - - -    $self->Load($self->Id); - -    $RT::Logger->warning($msg) unless $transaction; - -    $self->_SetLastUpdated; - -    if ( defined $args{'TimeTaken'} ) { -        $self->_UpdateTimeTaken( $args{'TimeTaken'} ); -    } -    return ( $transaction, $msg, $trans ); -} - -# }}} - -# }}} - -# {{{ PRIVATE UTILITY METHODS. Mostly needed so Ticket can be a DBIx::Record - -# {{{ sub _ClassAccessible - -sub _ClassAccessible { -    { -        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 }, -          Created         => { 'read' => 1,  'auto'  => 1 }, -          Creator         => { 'read' => 1,  'auto'  => 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->Description ); -    } -    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'} ) ) ) -    { -        $RT::Logger->warning("Principal attrib undefined for Ticket::HasRight"); -    } - -    return ( -        $args{'Principal'}->HasRight( -            Object => $self, -            Right     => $args{'Right'} -          ) -    ); -} - -# }}} - -# }}} - -1; - -=head1 AUTHOR - -Jesse Vincent, jesse@bestpractical.com - -=head1 SEE ALSO - -RT - -=cut - | 
