X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FTicket.pm;h=2f075a20cd21770b81eda23d13fc0a75661ad377;hp=f7275e4e37ca97269850023872437487045ee514;hb=ded0451e9582df33cae6099a2fb72b4ea25076cf;hpb=0ebeec96313dd7edfca340f01f8fbbbac1f4aa1d diff --git a/rt/lib/RT/Ticket.pm b/rt/lib/RT/Ticket.pm index f7275e4e3..2f075a20c 100755 --- a/rt/lib/RT/Ticket.pm +++ b/rt/lib/RT/Ticket.pm @@ -1,3004 +1,662 @@ -# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Ticket.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ -# (c) 1996-2001 Jesse Vincent -# This software is redistributable under the terms of the GNU GPL +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent +# +# (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 +# Autogenerated by DBIx::SearchBuilder factory (by ) +# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST. +# +# !! DO NOT EDIT THIS FILE !! # -=head1 NAME +use strict; - RT::Ticket - RT ticket object -=head1 SYNOPSIS +=head1 NAME - use RT::Ticket; - my $ticket = new RT::Ticket($CurrentUser); - $ticket->Load($ticket_id); +RT::Ticket -=head1 DESCRIPTION -This module lets you manipulate RT\'s ticket object. +=head1 SYNOPSIS +=head1 DESCRIPTION =head1 METHODS =cut - - package RT::Ticket; +use RT::Record; use RT::Queue; -use RT::User; -use RT::Record; -use RT::Link; -use RT::Links; -use RT::Date; -use RT::Watcher; - - -@ISA= qw(RT::Record); - -=begin testing -use RT::TestHarness; - -ok(require RT::Ticket, "Loading the RT::Ticket library"); - -=end testing - -=cut - -# {{{ sub _Init +use vars qw( @ISA ); +@ISA= qw( RT::Record ); sub _Init { - my $self = shift; - $self->{'table'} = "Tickets"; - return ($self->SUPER::_Init(@_)); -} - -# }}} - -# {{{ 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); - -} - -# }}} + my $self = shift; -# {{{ 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); - } + $self->Table('Tickets'); + $self->SUPER::_Init(@_); } -# }}} - -# {{{ sub Create - -=head2 Create (ARGS) - -Arguments: ARGS is a hash of named parameters. Valid parameters are: - - 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) - TimeWorked -- an integer - TimeLeft -- an integer - 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. - - KeywordSelect- -- an array of keyword ids for that keyword select - - -Returns: TICKETID, Transaction Object, Error Message - - -=begin testing - -my $t = RT::Ticket->new($RT::SystemUser); - -ok( $t->Create(Queue => 'General', Subject => 'This is a subject'), "Ticket Created"); - -ok ( my $id = $t->Id, "Got ticket id"); - -=end testing - -=cut - -sub Create { - my $self = shift; - - my %args = ( - Queue => undef, - Requestor => undef, - Cc => undef, - AdminCc => undef, - Type => 'ticket', - Owner => $RT::Nobody->UserObj, - Subject => '[no subject]', - InitialPriority => undef, - FinalPriority => undef, - Status => 'new', - TimeWorked => "0", - TimeLeft => 0, - Due => undef, - Starts => undef, - MIMEObj => undef, - @_); - - my ($ErrStr, $QueueObj, $Owner, $resolved); - my (@non_fatal_errors); - - my $now = RT::Date->new($self->CurrentUser); - $now->SetToNow(); - - if ( (defined($args{'Queue'})) && (!ref($args{'Queue'})) ) { - $QueueObj=RT::Queue->new($RT::SystemUser); - $QueueObj->Load($args{'Queue'}); - } - 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)) { - $RT::Logger->debug( "$self No queue given for ticket creation."); - return (0, 0,'Could not create ticket. Queue not set'); - } - - #Now that we have a queue, Check the ACLS - unless ($self->CurrentUser->HasQueueRight(Right => 'CreateTicket', - QueueObj => $QueueObj )) { - return (0,0,"No permission to create tickets in the queue '". - $QueueObj->Name."'."); - } - - #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'}); - - - #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 (defined $args{'Due'}) { - $due->Set (Format => 'ISO', - Value => $args{'Due'}); - } - elsif (defined ($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'}); - } - - - # {{{ Deal with setting the owner - - if (ref($args{'Owner'}) eq 'RT::User') { - $Owner = $args{'Owner'}; - } - #If we've been handed something else, try to load the user. - elsif ($args{'Owner'}) { - $Owner = new RT::User($self->CurrentUser); - $Owner->Load($args{'Owner'}); - - } - #If we can't handle it, call it nobody - else { - if (ref($args{'Owner'})) { - $RT::Logger->warning("$ticket ->Create called with an Owner of ". - "type ".ref($args{'Owner'}) .". Defaulting to nobody.\n"); - - push @non_fatal_errors, "Invalid owner. Defaulting to 'nobody'."; - } - else { - $RT::Logger->warning("$self ->Create called with an ". - "unknown datatype for Owner: ".$args{'Owner'} . - ". Defaulting to Nobody.\n"); - } - } - - #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->HasQueueRight( QueueObj => $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 this queue\n"); - - push @non_fatal_errors, "Invalid owner. Defaulting to 'nobody'."; - - $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,0,'Invalid value for status'); - } - - if ($args{'Status'} eq 'resolved') { - $resolved = $now->ISO; - } else{ - $resolved = undef; - } - - my $id = $self->SUPER::Create( - Queue => $QueueObj->Id, - Owner => $Owner->Id, - Subject => $args{'Subject'}, - InitialPriority => $args{'InitialPriority'}, - FinalPriority => $args{'FinalPriority'}, - Priority => $args{'InitialPriority'}, - Status => $args{'Status'}, - TimeWorked => $args{'TimeWorked'}, - TimeLeft => $args{'TimeLeft'}, - Type => $args{'Type'}, - Starts => $starts->ISO, - Resolved => $resolved, - Due => $due->ISO - ); - #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->err("$self ->Create couldn't set EffectiveId: $msg\n"); - } - - - my $watcher; - foreach $watcher (@{$args{'Cc'}}) { - my ($wval, $wmsg) = - $self->_AddWatcher( Type => 'Cc', Person => $watcher, Silent => 1); - push @non_fatal_errors, $wmsg unless ($wval); - } - - foreach $watcher (@{$args{'Requestor'}}) { - my ($wval, $wmsg) = - $self->_AddWatcher( Type => 'Requestor', Person => $watcher, Silent => 1); - push @non_fatal_errors, $wmsg unless ($wval); - } - - foreach $watcher (@{$args{'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 - my ($wval, $wmsg) = - $self->AddWatcher( Type => 'AdminCc', Person => $watcher, Silent => 1); - push @non_fatal_errors, $wmsg unless ($wval); - } - - # Iterate through all the KeywordSelect- params passed in, calling _AddKeyword - # for each of them - - - foreach my $key (keys %args) { - - next unless ($key =~ /^KeywordSelect-(.*)$/); - - my $ks = $1; - - - my @keywords = ref($args{$key}) eq 'ARRAY' ? - @{$args{$key}} : ($args{$key}); - - foreach my $keyword (@keywords) { - my ($kval, $kmsg) = $self->_AddKeyword(KeywordSelect => $ks, - Keyword => $keyword, - Silent => 1); - } - push @non_fatal_errors, $kmsg unless ($kval); - } - - - - #Add a transaction for the create - my ($Trans, $Msg, $TransObj) = - $self->_NewTransaction( Type => "Create", - TimeTaken => 0, - MIMEObj=>$args{'MIMEObj'}); - - # Logging - if ($self->Id && $Trans) { - $ErrStr = "Ticket ".$self->Id . " created in queue '". $QueueObj->Name. - "'.\n" . join("\n", @non_fatal_errors); - - $RT::Logger->info($ErrStr); - } - else { - # TODO where does this get errstr from? - $RT::Logger->warning("Ticket couldn't be created: $ErrStr"); - } - - return($self->Id, $TransObj->Id, $ErrStr); -} -# }}} -# {{{ sub Import -=head2 Import PARAMHASH -Import a ticket. -Doesn\'t create a transaction. -Doesn\'t supply queue defaults, etc. +=item Create PARAMHASH -Arguments are identical to Create(), with the addition of - Id - Ticket Id +Create takes a hash of values and creates a row in the database: -Returns: TICKETID + int(11) 'EffectiveId'. + int(11) 'Queue'. + varchar(16) 'Type'. + int(11) 'IssueStatement'. + int(11) 'Resolution'. + int(11) 'Owner'. + varchar(200) 'Subject' defaults to '[no subject]'. + int(11) 'InitialPriority'. + int(11) 'FinalPriority'. + int(11) 'Priority'. + int(11) 'TimeEstimated'. + int(11) 'TimeWorked'. + varchar(10) 'Status'. + int(11) 'TimeLeft'. + datetime 'Told'. + datetime 'Starts'. + datetime 'Started'. + datetime 'Due'. + datetime 'Resolved'. + smallint(6) 'Disabled'. =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,'Could not create ticket. Queue not set'); - } - - #Now that we have a queue, Check the ACLS - unless ($self->CurrentUser->HasQueueRight(Right => 'CreateTicket', - QueueObj => $QueueObj )) { - return (0,"No permission to create tickets in the queue '". - $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->HasQueueRight( QueueObj => $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,"'$args{'Status'}' is an invalid value for 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'}, - InitialPriority => $args{'InitialPriority'}, - FinalPriority => $args{'FinalPriority'}, - Priority => $args{'InitialPriority'}, - Status => $args{'Status'}, - TimeWorked => $args{'TimeWorked'}, - Type => $args{'Type'}, - Created => $args{'Created'}, - Told => $args{'Told'}, - LastUpdated => $args{'Updated'}, - Resolved => $args{Resolved}, - Due => $args{'Due'}, - ); - - - - # 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); -} - -# }}} - -# {{{ sub Delete - -sub Delete { - my $self = shift; - return (0, 'Deleting this object would violate referential integrity.'. - ' That\'s bad.'); -} -# }}} - -# {{{ Routines dealing with watchers. - -# {{{ Routines dealing with adding new watchers - -# {{{ sub AddWatcher - -=head2 AddWatcher - -AddWatcher takes a parameter hash. The keys are as follows: - -Email -Type -Owner - -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 = ( Email => undef, - Type => undef, - Owner => undef, - @_ - ); - - # {{{ Check ACLS - #If the watcher we're trying to add is for the current user - if ( ( $self->CurrentUser->EmailAddress && - ($args{'Email'} eq $self->CurrentUser->EmailAddress) ) or - ($args{'Owner'} eq $self->CurrentUser->Id) - ) { - - - # If it's an AdminCc and they don't have - # 'WatchAsAdminCc' or 'ModifyTicket', bail - if ($args{'Type'} eq 'AdminCc') { - unless ($self->CurrentUserHasRight('ModifyTicket') or - $self->CurrentUserHasRight('WatchAsAdminCc')) { - return(0, '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, 'Permission Denied'); - } - } - else { - $RT::Logger->warn("$self -> AddWatcher hit code". - " it never should. We got passed ". - " a type of ". $args{'Type'}); - return (0,'Error in parameters to TicketAddWatcher'); - } - } - # If the watcher isn't the current user - # and the current user doesn't have 'ModifyTicket' - # bail - else { - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return (0, "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, - Email => undef, - Owner => 0, - Person => undef, - @_ ); - - - - #clear the watchers cache - $self->{'watchers_cache'} = undef; - - if (defined $args{'Person'}) { - #if it's an RT::User object, pull out the id and shove it in Owner - if (ref ($args{'Person'}) =~ /RT::User/) { - $args{'Owner'} = $args{'Person'}->id; - } - #if it's an int, shove it in Owner - elsif ($args{'Person'} =~ /^\d+$/) { - $args{'Owner'} = $args{'Person'}; - } - #if it's an email address, shove it in Email - else { - $args{'Email'} = $args{'Person'}; - } - } - - # Turn an email address int a watcher if we possibly can. - if ($args{'Email'}) { - my $watcher = new RT::User($self->CurrentUser); - $watcher->LoadByEmail($args{'Email'}); - if ($watcher->Id) { - $args{'Owner'} = $watcher->Id; - delete $args{'Email'}; - } - } - - - # see if this user is already a watcher. if we have an owner, check it - # otherwise, we've got an email-address watcher. use that. - - if ($self->IsWatcher(Type => $args{'Type'}, - Id => ($args{'Owner'} || $args{'Email'}) ) ) { - - - return(0, 'That user is already that sort of watcher for this ticket'); - } - - - require RT::Watcher; - my $Watcher = new RT::Watcher ($self->CurrentUser); - my ($retval, $msg) = ($Watcher->Create( Value => $self->Id, - Scope => 'Ticket', - Email => $args{'Email'}, - Type => $args{'Type'}, - Owner => $args{'Owner'}, - )); - - unless ($args{'Silent'}) { - $self->_NewTransaction( Type => 'AddWatcher', - NewValue => $Watcher->Email, - Field => $Watcher->Type); - } - - return ($retval, $msg); -} - -# }}} - -# {{{ sub AddRequestor - -=head2 AddRequestor - -AddRequestor takes what AddWatcher does, except it presets -the "Type" parameter to \'Requestor\' - -=cut - -sub AddRequestor { - my $self = shift; - return ($self->AddWatcher ( Type => 'Requestor', @_)); -} - -# }}} - -# {{{ sub AddCc - -=head2 AddCc - -AddCc takes what AddWatcher does, except it presets -the "Type" parameter to \'Cc\' - -=cut - -sub AddCc { - my $self = shift; - return ($self->AddWatcher ( Type => 'Cc', @_)); -} -# }}} - -# {{{ sub AddAdminCc - -=head2 AddAdminCc - -AddAdminCc takes what AddWatcher does, except it presets -the "Type" parameter to \'AdminCc\' - -=cut - -sub AddAdminCc { - my $self = shift; - return ($self->AddWatcher ( Type => 'AdminCc', @_)); -} - -# }}} - -# }}} - -# {{{ sub DeleteWatcher - -=head2 DeleteWatcher id [type] - -DeleteWatcher takes a single argument which is either an email address -or a watcher id. -If the first argument is an email address, you need to specify the watcher type you're talking -about as the second argument. Valid values are 'Requestor', 'Cc' or 'AdminCc'. -It removes that watcher from this Ticket\'s list of watchers. - - -=cut -#TODO It is lame that you can't call this the same way you can call AddWatcher -sub DeleteWatcher { +sub Create { my $self = shift; - my $id = shift; - - my $type; - - $type = shift if (@_); - - my $Watcher = new RT::Watcher($self->CurrentUser); - - #If it\'s a numeric watcherid - if ($id =~ /^(\d*)$/) { - $Watcher->Load($id); - } - - #Otherwise, we'll assume it's an email address - elsif ($type) { - my ($result, $msg) = - $Watcher->LoadByValue( Email => $id, - Scope => 'Ticket', - Value => $self->id, - Type => $type); - return (0,$msg) unless ($result); - } - - else { - return(0,"Can\'t delete a watcher by email address without specifying a type"); - } - - # {{{ Check ACLS - - #If the watcher we're trying to delete is for the current user - if ($Watcher->Email eq $self->CurrentUser->EmailAddress) { - - # If it's an AdminCc and they don't have - # 'WatchAsAdminCc' or 'ModifyTicket', bail - if ($Watcher->Type eq 'AdminCc') { - unless ($self->CurrentUserHasRight('ModifyTicket') or - $self->CurrentUserHasRight('WatchAsAdminCc')) { - return(0, 'Permission Denied'); - } - } - - # If it's a Requestor or Cc and they don't have - # 'Watch' or 'ModifyTicket', bail - elsif (($Watcher->Type eq 'Cc') or - ($Watcher->Type eq 'Requestor')) { - - unless ($self->CurrentUserHasRight('ModifyTicket') or - $self->CurrentUserHasRight('Watch')) { - return(0, 'Permission Denied'); - } - } - else { - $RT::Logger->warn("$self -> DeleteWatcher hit code". - " it never should. We got passed ". - " a type of ". $args{'Type'}); - return (0,'Error in parameters to $self DeleteWatcher'); - } - } - # If the watcher isn't the current user - # and the current user doesn't have 'ModifyTicket' - # bail - else { - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return (0, "Permission Denied"); - } - } - - # }}} - - unless (($Watcher->Scope eq 'Ticket') and - ($Watcher->Value == $self->id) ) { - return (0, "Not a watcher for this ticket"); - } - - - #Clear out the watchers hash. - $self->{'watchers'} = undef; - - #If we\'ve validated that it is a watcher for this ticket - $self->_NewTransaction ( Type => 'DelWatcher', - OldValue => $Watcher->Email, - Field => $Watcher->Type, - ); - - my $retval = $Watcher->Delete(); - - unless ($retval) { - return(0,"Watcher could not be deleted. Database inconsistency possible."); - } - - return(1, "Watcher deleted"); -} - -# {{{ sub DeleteRequestor - -=head2 DeleteRequestor EMAIL + my %args = ( + EffectiveId => '0', + Queue => '0', + Type => '', + IssueStatement => '0', + Resolution => '0', + Owner => '0', + Subject => '[no subject]', + InitialPriority => '0', + FinalPriority => '0', + Priority => '0', + TimeEstimated => '0', + TimeWorked => '0', + Status => '', + TimeLeft => '0', + Told => '', + Starts => '', + Started => '', + Due => '', + Resolved => '', + Disabled => '0', -Takes an email address. It calls DeleteWatcher with a preset -type of 'Requestor' - - -=cut + @_); + $self->SUPER::Create( + EffectiveId => $args{'EffectiveId'}, + Queue => $args{'Queue'}, + Type => $args{'Type'}, + IssueStatement => $args{'IssueStatement'}, + Resolution => $args{'Resolution'}, + Owner => $args{'Owner'}, + Subject => $args{'Subject'}, + InitialPriority => $args{'InitialPriority'}, + FinalPriority => $args{'FinalPriority'}, + Priority => $args{'Priority'}, + TimeEstimated => $args{'TimeEstimated'}, + TimeWorked => $args{'TimeWorked'}, + Status => $args{'Status'}, + TimeLeft => $args{'TimeLeft'}, + Told => $args{'Told'}, + Starts => $args{'Starts'}, + Started => $args{'Started'}, + Due => $args{'Due'}, + Resolved => $args{'Resolved'}, + Disabled => $args{'Disabled'}, +); -sub DeleteRequestor { - my $self = shift; - my $id = shift; - return ($self->DeleteWatcher ($id, 'Requestor')) } -# }}} - -# {{{ sub DeleteCc - -=head2 DeleteCc EMAIL - -Takes an email address. It calls DeleteWatcher with a preset -type of 'Cc' - - -=cut - -sub DeleteCc { - my $self = shift; - my $id = shift; - return ($self->DeleteWatcher ($id, 'Cc')) -} - -# }}} - -# {{{ sub DeleteAdminCc - -=head2 DeleteAdminCc EMAIL - -Takes an email address. It calls DeleteWatcher with a preset -type of 'AdminCc' - - -=cut - -sub DeleteAdminCc { - my $self = shift; - my $id = shift; - return ($self->DeleteWatcher ($id, 'AdminCc')) -} - -# }}} - - -# }}} -# {{{ sub Watchers -=head2 Watchers +=item id -Watchers returns a Watchers object preloaded with this ticket\'s watchers. +Returns the current value of id. +(In the database, id is stored as int(11).) -# It should return only the ticket watchers. the actual FooAsString -# methods capture the queue watchers too. I don't feel thrilled about this, -# but we don't want the Cc Requestors and AdminCc objects to get filled up -# with all the queue watchers too. we've got seperate objects for that. - # should we rename these as s/(.*)AsString/$1Addresses/ or somesuch? =cut -sub Watchers { - my $self = shift; - - require RT::Watchers; - my $watchers=RT::Watchers->new($self->CurrentUser); - if ($self->CurrentUserHasRight('ShowTicket')) { - $watchers->LimitToTicket($self->id); - } - - return($watchers); - -} - -# }}} -# {{{ a set of [foo]AsString subs that will return the various sorts of watchers for a ticket/queue as a comma delineated string +=item EffectiveId -=head2 RequestorsAsString +Returns the current value of EffectiveId. +(In the database, EffectiveId is stored as int(11).) - B String: All Ticket Requestor email addresses as a string. -=cut -sub RequestorsAsString { - my $self=shift; +=item SetEffectiveId VALUE - unless ($self->CurrentUserHasRight('ShowTicket')) { - return undef; - } - - return ($self->Requestors->EmailsAsString() ); -} -=head2 WatchersAsString +Set EffectiveId to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, EffectiveId will be stored as a int(11).) -B String: All Ticket Watchers email addresses as a string =cut -sub WatchersAsString { - my $self=shift; - - unless ($self->CurrentUserHasRight('ShowTicket')) { - return (0, "Permission Denied"); - } - - return ($self->Watchers->EmailsAsString()); - -} -=head2 AdminCcAsString +=item Queue -returns String: All Ticket AdminCc email addresses as a string +Returns the current value of Queue. +(In the database, Queue is stored as int(11).) -=cut -sub AdminCcAsString { - my $self=shift; +=item SetQueue VALUE - unless ($self->CurrentUserHasRight('ShowTicket')) { - return undef; - } - - return ($self->AdminCc->EmailsAsString()); - -} -=head2 CcAsString +Set Queue to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Queue will be stored as a int(11).) -returns String: All Ticket Ccs as a string of email addresses =cut -sub CcAsString { - my $self=shift; - - unless ($self->CurrentUserHasRight('ShowTicket')) { - return undef; - } - - return ($self->Cc->EmailsAsString()); - -} - -# }}} - -# {{{ Routines that return RT::Watchers objects of Requestors, Ccs and AdminCcs -# {{{ sub Requestors +=item QueueObj -=head2 Requestors +Returns the Queue Object which has the id returned by Queue -Takes nothing. -Returns this ticket's Requestors as an RT::Watchers object - -=cut - -sub Requestors { - my $self = shift; - - my $requestors = $self->Watchers(); - if ($self->CurrentUserHasRight('ShowTicket')) { - $requestors->LimitToRequestors(); - } - - return($requestors); - -} - -# }}} - -# {{{ sub Cc - -=head2 Cc - -Takes nothing. -Returns a watchers object which contains this ticket's Cc watchers - -=cut - -sub Cc { - my $self = shift; - - my $cc = $self->Watchers(); - - if ($self->CurrentUserHasRight('ShowTicket')) { - $cc->LimitToCc(); - } - - return($cc); - -} - -# }}} - -# {{{ sub AdminCc - -=head2 AdminCc - -Takes nothing. -Returns this ticket\'s administrative Ccs as an RT::Watchers object - -=cut - -sub AdminCc { - my $self = shift; - - my $admincc = $self->Watchers(); - if ($self->CurrentUserHasRight('ShowTicket')) { - $admincc->LimitToAdminCc(); - } - return($admincc); -} - -# }}} - -# }}} - -# {{{ IsWatcher,IsRequestor,IsCc, IsAdminCc - -# {{{ sub IsWatcher -# a generic routine to be called by IsRequestor, IsCc and IsAdminCc - -=head2 IsWatcher - -Takes a param hash with the attributes Type and User. User is either a user object or string containing an email address. Returns true if that user or string -is a ticket watcher. Returns undef otherwise - -=cut - -sub IsWatcher { - my $self = shift; - - my %args = ( Type => 'Requestor', - Email => undef, - Id => undef, - @_ - ); - - my %cols = ('Type' => $args{'Type'}, - 'Scope' => 'Ticket', - 'Value' => $self->Id, - 'Owner' => undef, - 'Email' => undef - ); - - if (ref($args{'Id'})){ - #If it's a ref, it's an RT::User object; - $cols{'Owner'} = $args{'Id'}->Id; - } - elsif ($args{'Id'} =~ /^\d+$/) { - # if it's an integer, it's a reference to an RT::User obj - $cols{'Owner'} = $args{'Id'}; - } - else { - $cols{'Email'} = $args{'Id'}; - } - - if ($args{'Email'}) { - $cols{'Email'} = $args{'Email'}; - } - - my $description = join(":",%cols); - - #If we've cached a positive match... - if (defined $self->{'watchers_cache'}->{"$description"}) { - if ($self->{'watchers_cache'}->{"$description"} == 1) { - return(1); - } - else { #If we've cached a negative match... - return(undef); - } - } - - - my $watcher = new RT::Watcher($self->CurrentUser); - $watcher->LoadByCols(%cols); - - - if ($watcher->id) { - $self->{'watchers_cache'}->{"$description"} = 1; - return(1); - } - else { - $self->{'watchers_cache'}->{"$description"} = 0; - return(undef); - } - -} -# }}} - -# {{{ sub IsRequestor - -=head2 IsRequestor - - Takes an email address, RT::User object or integer (RT user id) - Returns true if the string is a requestor of the current ticket. - - -=cut - -sub IsRequestor { - my $self = shift; - my $person = shift; - - return ($self->IsWatcher(Type => 'Requestor', Id => $person)); - -}; - -# }}} - -# {{{ sub IsCc - -=head2 IsCc - -Takes a string. Returns true if the string is a Cc watcher of the current ticket. - -=cut - -sub IsCc { - my $self = shift; - my $cc = shift; - - return ($self->IsWatcher( Type => 'Cc', Id => $cc )); - -} - -# }}} - -# {{{ sub IsAdminCc - -=head2 IsAdminCc - -Takes a string. Returns true if the string is an AdminCc watcher of the current ticket. - -=cut - -sub IsAdminCc { - my $self = shift; - my $person = shift; - - return ($self->IsWatcher( Type => 'AdminCc', Id => $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; - - #TODO I don't think this should be here. We shouldn't allow anything to have an undef queue, - 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, "Permission Denied"); - } - - - my $NewQueueObj = RT::Queue->new($self->CurrentUser); - $NewQueueObj->Load($NewQueue); - - unless ($NewQueueObj->Id()) { - return (0, "That queue does not exist"); - } - - if ($NewQueueObj->Id == $self->QueueObj->Id) { - return (0, 'That is the same value'); - } - unless ($self->CurrentUser->HasQueueRight(Right =>'CreateTicket', - QueueObj => $NewQueueObj )) { - return (0, "You may not create requests in that queue."); - } - - unless ($self->OwnerObj->HasQueueRight(Right=> 'OwnTicket', - QueueObj => $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); + my $self = shift; + my $Queue = RT::Queue->new($self->CurrentUser); + $Queue->Load($self->__Value('Queue')); + return($Queue); } +=item Type -# }}} +Returns the current value of Type. +(In the database, Type is stored as varchar(16).) -# }}} -# {{{ 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; -} -# }}} +=item SetType VALUE -# {{{ sub DueAsString -=head2 DueAsString +Set Type to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Type will be stored as a varchar(16).) -Returns this ticket's due date as a human readable string =cut -sub DueAsString { - my $self = shift; - return $self->DueObj->AsString(); -} - -# }}} -# {{{ sub GraceTimeAsString +=item IssueStatement -=head2 GraceTimeAsString +Returns the current value of IssueStatement. +(In the database, IssueStatement is stored as int(11).) -Return the time until this ticket is due as a string - -=cut - -# TODO This should be deprecated - -sub GraceTimeAsString { - my $self=shift; - - if ($self->Due) { - return ($self->DueObj->AgeAsString()); - } else { - return ""; - } -} -# }}} +=item SetIssueStatement VALUE -# {{{ sub ResolvedObj -=head2 ResolvedObj +Set IssueStatement to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, IssueStatement will be stored as a int(11).) - 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; -} -# }}} +=item Resolution -# {{{ sub SetStarted +Returns the current value of Resolution. +(In the database, Resolution is stored as int(11).) -=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, "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)); - -} +=item SetResolution VALUE -# }}} -# {{{ sub StartedObj +Set Resolution to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Resolution will be stored as a int(11).) -=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; -} -# }}} +=item Owner -# {{{ sub StartsObj +Returns the current value of Owner. +(In the database, Owner is stored as int(11).) -=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; -} -# }}} +=item SetOwner VALUE -# {{{ sub ToldObj -=head2 ToldObj +Set Owner to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Owner will be stored as a int(11).) - 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; -} +=item Subject -# }}} +Returns the current value of Subject. +(In the database, Subject is stored as varchar(200).) -# {{{ sub LongSinceToldAsString -# TODO this should be deprecated +=item SetSubject VALUE -sub LongSinceToldAsString { - my $self = shift; - - if ($self->Told) { - return $self->ToldObj->AgeAsString(); - } else { - return "Never"; - } -} -# }}} - -# {{{ sub ToldAsString -=head2 ToldAsString +Set Subject to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Subject will be stored as a varchar(200).) -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 +=item InitialPriority -=head2 TimeWorkedAsString +Returns the current value of InitialPriority. +(In the database, InitialPriority is stored as int(11).) -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)); -} +=item SetInitialPriority VALUE -# }}} +Set InitialPriority to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, InitialPriority will be stored as a int(11).) -# }}} - -# {{{ Routines dealing with correspondence/comments - -# {{{ sub Comment - -=head2 Comment - -Comment on this ticket. -Takes a hashref with the follwoing attributes: - -MIMEObj, TimeTaken, CcMessageTo, BccMessageTo =cut -sub Comment { - my $self = shift; - - my %args = ( - CcMessageTo => undef, - BccMessageTo => undef, - MIMEObj => undef, - TimeTaken => 0, - @_ ); - - unless (($self->CurrentUserHasRight('CommentOnTicket')) or - ($self->CurrentUserHasRight('ModifyTicket'))) { - return (0, "Permission Denied"); - } - - unless ($args{'MIMEObj'}) { - return(0,"No correspondence attached"); - } - - # 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'}); - $args{'MIMEObj'}->head->add('RT-Send-Bcc', $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, "The comment has been recorded"); -} - -# }}} -# {{{ sub Correspond +=item FinalPriority -=head2 Correspond +Returns the current value of FinalPriority. +(In the database, FinalPriority is stored as int(11).) -Correspond on this ticket. -Takes a hashref with the following attributes: - - -MIMEObj, TimeTaken, CcMessageTo, BccMessageTo - -=cut - -sub Correspond { - my $self = shift; - my %args = ( - CcMessageTo => undef, - BccMessageTo => undef, - MIMEObj => undef, - TimeTaken => 0, - @_ ); - - unless (($self->CurrentUserHasRight('ReplyToTicket')) or - ($self->CurrentUserHasRight('ModifyTicket'))) { - return (0, "Permission Denied"); - } - - unless ($args{'MIMEObj'}) { - return(0,"No correspondence attached"); - } - - # 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'}); - $args{'MIMEObj'}->head->add('RT-Send-Bcc', $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'} - ); - - # TODO this bit of logic should really become a scrip for 2.2 - my $TicketAsSystem = new RT::Ticket($RT::SystemUser); - $TicketAsSystem->Load($self->Id); - - if ( - ($TicketAsSystem->Status ne 'open') and - ($TicketAsSystem->Status ne 'new') - ) { - - my $oldstatus = $TicketAsSystem->Status(); - $TicketAsSystem->__Set(Field => 'Status', Value => 'open'); - $TicketAsSystem->_NewTransaction - ( Type => 'Set', - Field => 'Status', - OldValue => $oldstatus, - NewValue => 'open', - Data => 'Ticket auto-opened on incoming correspondence' - ); - } - - unless ($Trans) { - $RT::Logger->err("$self couldn't init a transaction ($msg)\n"); - return ($Trans, "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, "correspondence sent"); -} -# }}} -# }}} +=item SetFinalPriority VALUE -# {{{ Routines dealing with Links and Relations between tickets -# {{{ Link Collections +Set FinalPriority to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, FinalPriority will be stored as a int(11).) -# {{{ 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 +=item Priority -=head2 MemberOf +Returns the current value of Priority. +(In the database, Priority is stored as int(11).) - 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')); -} -# }}} +=item SetPriority VALUE -# {{{ RefersTo -=head2 RefersTo +Set Priority to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Priority will be stored as a int(11).) - 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 +=item TimeEstimated -=head2 ReferredToBy +Returns the current value of TimeEstimated. +(In the database, TimeEstimated is stored as int(11).) - 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')); -} +=item SetTimeEstimated VALUE -# }}} -# {{{ DependedOnBy +Set TimeEstimated to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, TimeEstimated will be stored as a int(11).) -=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')); -} - -# }}} - -# {{{ 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')) { - - $self->{"$field$type"}->Limit(FIELD=>$field, VALUE=>$self->URI); - $self->{"$field$type"}->Limit(FIELD=>'Type', - VALUE=>$type) if ($type); - } - } - return ($self->{"$field$type"}); -} - -# }}} - -# }}} -# {{{ sub DeleteLink +=item TimeWorked -=head2 DeleteLink +Returns the current value of TimeWorked. +(In the database, TimeWorked is stored as int(11).) -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, 'Permission Denied'); - - - } - - #we want one of base and target. we don't care which - #but we only want _one_ - - if ($args{'Base'} and $args{'Target'}) { - $RT::Logger->debug("$self ->_DeleteLink. got both Base and Target\n"); - return (0, 'Can\'t specifiy both base and target'); - } - elsif ($args{'Base'}) { - $args{'Target'} = $self->Id(); - } - elsif ($args{'Target'}) { - $args{'Base'} = $self->Id(); - } - else { - $RT::Logger->debug("$self: Base or Target must be specified\n"); - return (0, '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->Load($args{'Base'}, $args{'Type'}, $args{'Target'}); - - - - #it's a real link. - if ($link->id) { - $RT::Logger->debug("We're going to delete link ".$link->id."\n"); - $link->Delete(); - - my $TransString= - "Ticket $args{'Base'} no longer $args{Type} ticket $args{'Target'}."; - my ($Trans, $Msg, $TransObj) = $self->_NewTransaction - (Type => 'DeleteLink', - Field => $args{'Type'}, - Data => $TransString, - TimeTaken => 0 - ); - - return ($linkid, "Link deleted ($TransString)", $transactionid); - } - #if it's not a link we can find - else { - $RT::Logger->debug("Couldn't find that link\n"); - return (0, "Link not found"); - } -} +=item SetTimeWorked VALUE -# }}} -# {{{ sub AddLink - -=head2 AddLink - -Takes a paramhash of Type and one of Base or Target. Adds that link to this ticket. +Set TimeWorked to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, TimeWorked will be stored as a int(11).) =cut -sub AddLink { - my $self = shift; - my %args = ( Target => '', - Base => '', - Type => '', - @_ ); - - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return (0, "Permission Denied"); - } - - if ($args{'Base'} and $args{'Target'}) { - $RT::Logger->debug("$self tried to delete a link. both base and target were specified\n"); - return (0, 'Can\'t specifiy both base and target'); - } - elsif ($args{'Base'}) { - $args{'Target'} = $self->Id(); - } - elsif ($args{'Target'}) { - $args{'Base'} = $self->Id(); - } - else { - return (0, 'Either base or target must be specified'); - } - - # {{{ We don't want references to ourself - if ($args{Base} eq $args{Target}) { - return (0, "Can\'t link a ticket to itself"); - } - - # }}} - - # 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 - my $old_link= new RT::Link ($self->CurrentUser); - $old_link->Load($args{'Base'}, $args{'Type'}, $args{'Target'}); - if ($old_link->Id) { - $RT::Logger->debug("$self Somebody tried to duplicate a link"); - return ($old_link->id, "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,"Link could not be created"); - } - #Write the transaction - - my $TransString="Ticket $args{'Base'} $args{Type} ticket $args{'Target'}."; - - my ($Trans, $Msg, $TransObj) = $self->_NewTransaction - (Type => 'AddLink', - Field => $args{'Type'}, - Data => $TransString, - TimeTaken => 0 - ); - - return ($Trans, "Link created ($TransString)"); - - -} -# }}} -# {{{ sub URI +=item Status -=head2 URI +Returns the current value of Status. +(In the database, Status is stored as varchar(10).) -Returns this ticket's URI -=cut -sub URI { - my $self = shift; - return $RT::TicketBaseURI.$self->id; -} +=item SetStatus VALUE -# }}} -# {{{ sub MergeInto +Set Status to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Status will be stored as a varchar(10).) -=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, "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, '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, "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, "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,"Merge failed. Couldn't set EffectiveId"); - } - - my ($status_val, $status_msg) = $self->__Set(Field => 'Status', - Value => 'resolved'); - - unless ($status_val) { - $RT::Logger->error("$self couldn't set status to resolved.". - "RT's Database may be inconsistent."); - } - - #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 $watchers = $self->Watchers(); - - while (my $watcher = $watchers->Next()) { - unless ( - ($watcher->Owner && - $NewTicket->IsWatcher (Type => $watcher->Type, - Id => $watcher->Owner)) or - ($watcher->Email && - $NewTicket->IsWatcher (Type => $watcher->Type, - Id => $watcher->Email)) - ) { - - - - $NewTicket->_AddWatcher(Silent => 1, - Type => $watcher->Type, - Email => $watcher->Email, - Owner => $watcher->Owner); - } - } - - - #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 ($TransactionObj, "Merge Successful"); -} - -# }}} - -# }}} - -# {{{ Routines dealing with keywords - -# {{{ sub KeywordsObj - -=head2 KeywordsObj [KEYWORD_SELECT_ID] - - Returns an B object preloaded with this ticket's ObjectKeywords. -If the optional KEYWORD_SELECT_ID parameter is set, limit the keywords object to that keyword -select. - -=cut - -sub KeywordsObj { - my $self = shift; - my $keyword_select; - - $keyword_select = shift if (@_); - - use RT::ObjectKeywords; - my $Keywords = new RT::ObjectKeywords($self->CurrentUser); - - #ACL check - if ($self->CurrentUserHasRight('ShowTicket')) { - $Keywords->LimitToTicket($self->id); - if ($keyword_select) { - $Keywords->LimitToKeywordSelect($keyword_select); - } - } - return ($Keywords); -} -# }}} -# {{{ sub AddKeyword +=item TimeLeft -=head2 AddKeyword +Returns the current value of TimeLeft. +(In the database, TimeLeft is stored as int(11).) -Takes a paramhash of Keyword and KeywordSelect. If Keyword is a valid choice -for KeywordSelect, creates a KeywordObject. If the KeywordSelect says this should -be a single KeywordObject, automatically removes the old value. - Issues: probably doesn't enforce the depth restrictions or make sure that keywords -are coming from the right part of the tree. really should. -=cut +=item SetTimeLeft VALUE -sub AddKeyword { - my $self = shift; - #ACL check - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return (0, 'Permission Denied'); - } - - return($self->_AddKeyword(@_)); - -} +Set TimeLeft to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, TimeLeft will be stored as a int(11).) -# Helper version of AddKeyword without that pesky ACL check -sub _AddKeyword { - my $self = shift; - my %args = ( KeywordSelect => undef, # id of a keyword select record - Keyword => undef, #id of the keyword to add - Silent => 0, - @_ - ); - - my ($OldValue); - - #TODO make sure that $args{'Keyword'} is valid for $args{'KeywordSelect'} - - #TODO: make sure that $args{'KeywordSelect'} applies to this ticket's queue. - - my $Keyword = new RT::Keyword($self->CurrentUser); - unless ($Keyword->Load($args{'Keyword'}) ) { - $RT::Logger->err("$self Couldn't load Keyword ".$args{'Keyword'} ."\n"); - return(0, "Couldn't load keyword"); - } - - my $KeywordSelectObj = new RT::KeywordSelect($self->CurrentUser); - unless ($KeywordSelectObj->Load($args{'KeywordSelect'})) { - $RT::Logger->err("$self Couldn't load KeywordSelect ".$args{'KeywordSelect'}); - return(0, "Couldn't load keywordselect"); - } - - my $Keywords = $self->KeywordsObj($KeywordSelectObj->id); - - #If the ticket already has this keyword, just get out of here. - if ($Keywords->HasEntry($Keyword->id)) { - return(0, "That is already the current value"); - } - - #If the keywordselect wants this to be a singleton: - - if ($KeywordSelectObj->Single) { - - #Whack any old values...keep track of the last value that we get. - #we shouldn't need a loop ehre, but we do it anyway, to try to - # help keep the database clean. - while (my $OldKey = $Keywords->Next) { - $OldValue = $OldKey->KeywordObj->Name; - $OldKey->Delete(); - } - - - } - - # create the new objectkeyword - my $ObjectKeyword = new RT::ObjectKeyword($self->CurrentUser); - my $result = $ObjectKeyword->Create( Keyword => $Keyword->Id, - ObjectType => 'Ticket', - ObjectId => $self->Id, - KeywordSelect => $KeywordSelectObj->Id ); - - - # record a single transaction, unless we were told not to - unless ($args{'Silent'}) { - my ($TransactionId, $Msg, $TransactionObj) = - $self->_NewTransaction( Type => 'Keyword', - Field => $KeywordSelectObj->Id, - OldValue => $OldValue, - NewValue => $Keyword->Name ); - } - return ($TransactionId, "Keyword ".$ObjectKeyword->KeywordObj->Name ." added."); - -} - -# }}} - -# {{{ sub DeleteKeyword - -=head2 DeleteKeyword - - Takes a paramhash. Deletes the Keyword denoted by the I parameter from this - ticket's object keywords. =cut -sub DeleteKeyword { - my $self = shift; - my %args = ( Keyword => undef, - KeywordSelect => undef, - @_ ); - - #ACL check - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return (0, 'Permission Denied'); - } - - - #Load up the ObjectKeyword we\'re talking about - my $ObjectKeyword = new RT::ObjectKeyword($self->CurrentUser); - $ObjectKeyword->LoadByCols(Keyword => $args{'Keyword'}, - KeywordSelect => $args{'KeywordSelect'}, - ObjectType => 'Ticket', - ObjectId => $self->id() - ); - - #if we can\'t find it, bail - unless ($ObjectKeyword->id) { - $RT::Logger->err("Couldn't find the keyword ".$args{'Keyword'} . - " for keywordselect ". $args{'KeywordSelect'} . - "for ticket ".$self->id ); - return (undef, "Couldn't load keyword while trying to delete it."); - }; - - #record transaction here. - my ($TransactionId, $Msg, $TransObj) = - $self->_NewTransaction( Type => 'Keyword', - OldValue => $ObjectKeyword->KeywordObj->Name); - - $ObjectKeyword->Delete(); - - return ($TransactionId, "Keyword ".$ObjectKeyword->KeywordObj->Name ." deleted."); - -} -# }}} +=item Told -# }}} +Returns the current value of Told. +(In the database, Told is stored as datetime.) -# {{{ Routines dealing with ownership -# {{{ sub OwnerObj -=head2 OwnerObj +=item SetTold VALUE -Takes nothing and returns an RT::User object of -this ticket's owner -=cut +Set Told to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Told will be stored as a datetime.) -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 - - $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); -} +=item Starts -# }}} +Returns the current value of Starts. +(In the database, Starts is stored as datetime.) -# {{{ 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. - -=cut - -sub SetOwner { - my $self = shift; - my $NewOwner = shift; - my $Type = shift || "Give"; - - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return (0, "Permission Denied"); - } - - my $NewOwnerObj = RT::User->new($self->CurrentUser); - my $OldOwnerObj = $self->OwnerObj; - - $NewOwnerObj->Load($NewOwner); - if (!$NewOwnerObj->Id) { - return (0, "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, "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->HasQueueRight(Right => 'OwnTicket', - QueueObj => $self->QueueObj, - TicketObj => $self)) - ) { - return (0, "That user may not own requests 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, "That user already owns that request"); - } - - - my ($trans,$msg)=$self->_Set(Field => 'Owner', - Value => $NewOwnerObj->Id, - TimeTaken => 0, - TransactionType => $Type); - - if ($trans) { - $msg = "Owner changed from ".$OldOwnerObj->Name." to ".$NewOwnerObj->Name; - } - return ($trans, $msg); - -} -# }}} +=item SetStarts VALUE -# {{{ sub Take -=head2 Take +Set Starts to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Starts will be stored as a datetime.) -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')); -} -# }}} +=item Started -# {{{ sub Untake +Returns the current value of Started. +(In the database, Started is stored as datetime.) -=head2 Untake -Convenience method to set the owner to 'nobody' if the current user is the owner. -=cut +=item SetStarted VALUE -sub Untake { - my $self = shift; - return($self->SetOwner($RT::Nobody->UserObj->Id, 'Untake')); -} -# }}} - -# {{{ sub Steal -=head2 Steal +Set Started to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Started will be stored as a datetime.) -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,"You already own this ticket"); - } else { - return($self->SetOwner($self->CurrentUser->Id, 'Steal')); - - } - -} - -# }}} - -# }}} -# {{{ Routines dealing with status +=item Due -# {{{ sub ValidateStatus +Returns the current value of Due. +(In the database, Due is stored as datetime.) -=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; +=item SetDue VALUE - #Make sure the status passed in is valid - unless ($self->QueueObj->IsValidStatus($status)) { - return (undef); - } - - return (1); -} +Set Due to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Due will be stored as a datetime.) -# }}} +=cut -# {{{ sub SetStatus -=head2 SetStatus STATUS +=item Resolved -Set this ticket\'s status. STATUS can be one of: new, open, stalled, resolved or dead. +Returns the current value of Resolved. +(In the database, Resolved is stored as datetime.) -=cut -sub SetStatus { - my $self = shift; - my $status = shift; - - #Check ACL - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return (0, 'Permission Denied'); - } - - my $now = new RT::Date($self->CurrentUser); - $now->SetToNow(); - - #If we're changing the status from new, record that we've started - if (($self->Status =~ /new/) && ($status ne 'new')) { - #Set the Started time to "now" - $self->_Set(Field => 'Started', - Value => $now->ISO, - RecordTransaction => 0); - } - - - if ($status eq 'resolved') { - #When we resolve a ticket, set the 'Resolved' attribute to now. - $self->_Set(Field => 'Resolved', - Value => $now->ISO, - RecordTransaction => 0); - } - - - #Actually update the status - return($self->_Set(Field => 'Status', - Value => $status, - TimeTaken => 0, - TransactionType => 'Status')); -} -# }}} +=item SetResolved VALUE -# {{{ sub Kill -=head2 Kill +Set Resolved to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Resolved will be stored as a datetime.) -Takes no arguments. Marks this ticket for garbage collection =cut -sub Kill { - my $self = shift; - return ($self->SetStatus('dead')); - # TODO: garbage collection -} - -# }}} -# {{{ sub Stall +=item LastUpdatedBy -=head2 Stall +Returns the current value of LastUpdatedBy. +(In the database, LastUpdatedBy is stored as int(11).) -Sets this ticket's status to stalled =cut -sub Stall { - my $self = shift; - return ($self->SetStatus('stalled')); -} - -# }}} -# {{{ sub Open +=item LastUpdated -=head2 Open +Returns the current value of LastUpdated. +(In the database, LastUpdated is stored as datetime.) -Sets this ticket\'s status to Open =cut -sub Open { - my $self = shift; - return ($self->SetStatus('open')); -} - -# }}} -# {{{ sub Resolve +=item Creator -=head2 Resolve +Returns the current value of Creator. +(In the database, Creator is stored as int(11).) -Sets this ticket\'s status to Resolved =cut -sub Resolve { - my $self = shift; - return ($self->SetStatus('resolved')); -} - -# }}} - -# }}} - -# {{{ Actions + Routines dealing with transactions -# {{{ sub SetTold and _SetTold +=item Created -=head2 SetTold ISO [TIMETAKEN] +Returns the current value of Created. +(In the database, Created is stored as datetime.) -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, "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 +=item Disabled -Updates the told without a transaction or acl check. Useful when we're sending replies. +Returns the current value of Disabled. +(In the database, Disabled is stored as smallint(6).) -=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)); -} -# }}} +=item SetDisabled VALUE -# {{{ sub Transactions -=head2 Transactions +Set Disabled to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, Disabled will be stored as a smallint(6).) - 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'} - ); - - $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 }, - 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}, - 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; - - unless ($self->CurrentUserHasRight('ModifyTicket')) { - return (0, "Permission Denied"); - } - - my %args = (Field => undef, - Value => undef, - TimeTaken => 0, - RecordTransaction => 1, - TransactionType => 'Set', - @_ - ); - #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'}"); - - #Set the new value - my ($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,$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); -} + + id => + {read => 1, type => 'int(11)', default => ''}, + EffectiveId => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + Queue => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + Type => + {read => 1, write => 1, type => 'varchar(16)', default => ''}, + IssueStatement => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + Resolution => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + Owner => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + Subject => + {read => 1, write => 1, type => 'varchar(200)', default => '[no subject]'}, + InitialPriority => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + FinalPriority => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + Priority => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + TimeEstimated => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + TimeWorked => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + Status => + {read => 1, write => 1, type => 'varchar(10)', default => ''}, + TimeLeft => + {read => 1, write => 1, type => 'int(11)', default => '0'}, + Told => + {read => 1, write => 1, type => 'datetime', default => ''}, + Starts => + {read => 1, write => 1, type => 'datetime', default => ''}, + Started => + {read => 1, write => 1, type => 'datetime', default => ''}, + Due => + {read => 1, write => 1, type => 'datetime', default => ''}, + Resolved => + {read => 1, write => 1, type => 'datetime', default => ''}, + LastUpdatedBy => + {read => 1, auto => 1, type => 'int(11)', default => '0'}, + LastUpdated => + {read => 1, auto => 1, type => 'datetime', default => ''}, + Creator => + {read => 1, auto => 1, type => 'int(11)', default => '0'}, + Created => + {read => 1, auto => 1, type => 'datetime', default => ''}, + Disabled => + {read => 1, write => 1, type => 'smallint(6)', default => '0'}, + + } +}; -# }}} -# }}} + eval "require RT::Ticket_Overlay"; + if ($@ && $@ !~ qr{^Can't locate RT/Ticket_Overlay.pm}) { + die $@; + }; -# {{{ Routines dealing with ACCESS CONTROL + eval "require RT::Ticket_Vendor"; + if ($@ && $@ !~ qr{^Can't locate RT/Ticket_Vendor.pm}) { + die $@; + }; -# {{{ sub CurrentUserHasRight + eval "require RT::Ticket_Local"; + if ($@ && $@ !~ qr{^Can't locate RT/Ticket_Local.pm}) { + die $@; + }; -=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")); +=head1 SEE ALSO -} +This class allows "overlay" methods to be placed +into the following files _Overlay is for a System overlay by the original author, +_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations. -# }}} +These overlay files can contain new subs or subs to replace existing subs in this module. -# {{{ sub HasRight +If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line -=head2 HasRight + no warnings qw(redefine); - 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 +so that perl does not kick and scream when you redefine a subroutine or variable in your overlay. - Returns 1 if the principal has the right. Returns undef if not. +RT::Ticket_Overlay, RT::Ticket_Vendor, RT::Ticket_Local =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'}->HasQueueRight(TicketObj => $self, - Right => $args{'Right'})); -} - -# }}} - -# }}} - 1; - -=head1 AUTHOR - -Jesse Vincent, jesse@fsck.com - -=head1 SEE ALSO - -RT - -=cut - -