reverting to vendor branch rt 3.0.4, hopefully
[freeside.git] / rt / lib / RT / Ticket.pm
index f7275e4..2f075a2 100755 (executable)
-# $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 <jesse@fsck.com>
-# This software is redistributable under the terms of the GNU GPL
+# 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
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# 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-<id> -- 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-<int> 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<Returns> 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<Returns> 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<RT::ObjectKeywords> 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<Keyword> 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
-
-