This commit was generated by cvs2svn to compensate for changes in r4407,
[freeside.git] / rt / lib / RT / Interface / Web.pm
index 724d7e5..5097f54 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (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
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
+# 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.
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-# 
-# END BPS TAGGED BLOCK }}}
+# END LICENSE BLOCK
 ## Portions Copyright 2000 Tobias Brox <tobix@fsck.com>
 
 ## This is a library of static subs to be used by the Mason web
@@ -67,102 +45,94 @@ use strict;
 
 
 
-# {{{ EscapeUTF8
 
-=head2 EscapeUTF8 SCALARREF
 
-does a css-busting but minimalist escaping of whatever html you're passing in.
+# {{{ sub NewApacheHandler 
 
-=cut
+=head2 NewApacheHandler
 
-sub EscapeUTF8  {
-        my  $ref = shift;
-        return unless defined $$ref;
-        my $val = $$ref;
-        use bytes;
-        $val =~ s/&/&#38;/g;
-        $val =~ s/</&lt;/g; 
-        $val =~ s/>/&gt;/g;
-        $val =~ s/\(/&#40;/g;
-        $val =~ s/\)/&#41;/g;
-        $val =~ s/"/&#34;/g;
-        $val =~ s/'/&#39;/g;
-        $$ref = $val;
-        Encode::_utf8_on($$ref);
+  Takes extra options to pass to HTML::Mason::ApacheHandler->new
+  Returns a new Mason::ApacheHandler object
 
+=cut
 
+sub NewApacheHandler {
+    require HTML::Mason::ApacheHandler;
+    my $ah = new HTML::Mason::ApacheHandler( 
+    
+        comp_root                    => [
+            [ local    => $RT::MasonLocalComponentRoot ],
+            [ standard => $RT::MasonComponentRoot ]
+        ],
+        args_method => "CGI",
+        default_escape_flags => 'h',
+        allow_globals        => [qw(%session)],
+        data_dir => "$RT::MasonDataDir",
+        @_
+    );
+
+    $ah->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
+    
+    return ($ah);
 }
 
 # }}}
 
-# {{{ EscapeURI
+# {{{ sub NewCGIHandler 
 
-=head2 EscapeURI SCALARREF
+=head2 NewCGIHandler
 
-Escapes URI component according to RFC2396
+  Returns a new Mason::CGIHandler object
 
 =cut
 
-use Encode qw();
-sub EscapeURI {
-    my $ref = shift;
-    $$ref = Encode::encode_utf8( $$ref );
-    $$ref =~ s/([^a-zA-Z0-9_.!~*'()-])/uc sprintf("%%%02X", ord($1))/eg;
-    Encode::_utf8_on( $$ref );
-}
-
-# }}}
-
-# {{{ WebCanonicalizeInfo
+sub NewCGIHandler {
+    my %args = (
+        @_
+    );
 
-=head2 WebCanonicalizeInfo();
+    my $handler = HTML::Mason::CGIHandler->new(
+        comp_root                    => [
+            [ local    => $RT::MasonLocalComponentRoot ],
+            [ standard => $RT::MasonComponentRoot ]
+        ],
+        data_dir => "$RT::MasonDataDir",
+        default_escape_flags => 'h',
+        allow_globals        => [qw(%session)]
+    );
+  
 
-Different web servers set different environmental varibles. This
-function must return something suitable for REMOTE_USER. By default,
-just downcase $ENV{'REMOTE_USER'}
+    $handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
 
-=cut
 
-sub WebCanonicalizeInfo {
-    my $user;
+    return ($handler);
 
-    if ( defined $ENV{'REMOTE_USER'} ) {
-       $user = lc ( $ENV{'REMOTE_USER'} ) if( length($ENV{'REMOTE_USER'}) );
-    }
-
-    return $user;
 }
-
 # }}}
 
-# {{{ WebExternalAutoInfo
-
-=head2 WebExternalAutoInfo($user);
-
-Returns a hash of user attributes, used when WebExternalAuto is set.
-
-=cut
 
-sub WebExternalAutoInfo {
-    my $user = shift;
+# {{{ EscapeUTF8
 
-    my %user_info;
+=head2 EscapeUTF8 SCALARREF
 
-    $user_info{'Privileged'} = 1;
+does a css-busting but minimalist escaping of whatever html you're passing in.
 
-    if ($^O !~ /^(?:riscos|MacOS|MSWin32|dos|os2)$/) {
-       # Populate fields with information from Unix /etc/passwd
+=cut
 
-       my ($comments, $realname) = (getpwnam($user))[5, 6];
-       $user_info{'Comments'} = $comments if defined $comments;
-       $user_info{'RealName'} = $realname if defined $realname;
-    }
-    elsif ($^O eq 'MSWin32' and eval 'use Net::AdminMisc; 1') {
-       # Populate fields with information from NT domain controller
-    }
+sub EscapeUTF8  {
+        my  $ref = shift;
+        my $val = $$ref;
+        use bytes;
+        $val =~ s/&/&#38;/g;
+        $val =~ s/</&lt;/g; 
+        $val =~ s/>/&gt;/g;
+        $val =~ s/\(/&#40;/g;
+        $val =~ s/\)/&#41;/g;
+        $val =~ s/"/&#34;/g;
+        $val =~ s/'/&#39;/g;
+        $$ref = $val;
+        Encode::_utf8_on($$ref);
 
-    # and return the wad of stuff
-    return {%user_info};
 }
 
 # }}}
@@ -190,13 +160,10 @@ sub loc {
         UNIVERSAL::can($session{'CurrentUser'}, 'loc')){
         return($session{'CurrentUser'}->loc(@_));
     }
-    elsif ( my $u = eval { RT::CurrentUser->new($RT::SystemUser->Id) } ) {
+    else  {
+        my $u = RT::CurrentUser->new($RT::SystemUser);
         return ($u->loc(@_));
     }
-    else {
-       # pathetic case -- SystemUser is gone.
-       return $_[0];
-    }
 }
 
 # }}}
@@ -222,7 +189,7 @@ sub loc_fuzzy {
         return($session{'CurrentUser'}->loc_fuzzy($msg));
     }
     else  {
-        my $u = RT::CurrentUser->new($RT::SystemUser->Id);
+        my $u = RT::CurrentUser->new($RT::SystemUser);
         return ($u->loc_fuzzy($msg));
     }
 }
@@ -294,7 +261,6 @@ sub CreateTicket {
     }
 
     my %create_args = (
-        Type            => $ARGS{'Type'} || 'ticket',
         Queue           => $ARGS{'Queue'},
         Owner           => $ARGS{'Owner'},
         InitialPriority => $ARGS{'InitialPriority'},
@@ -311,81 +277,36 @@ sub CreateTicket {
         Starts          => $starts->ISO,
         MIMEObj         => $MIMEObj
     );
-    foreach my $arg (keys %ARGS) {
-            my $cfid = $1;
-
+  foreach my $arg (%ARGS) {
+        if ($arg =~ /^CustomField-(\d+)(.*?)$/) {
             next if ($arg =~ /-Magic$/);
-       #Object-RT::Ticket--CustomField-3-Values
-        if ($arg =~ /^Object-RT::Transaction--CustomField-/) {
-            $create_args{$arg} = $ARGS{$arg};
-        }
-        elsif ($arg =~ /^Object-RT::Ticket--CustomField-(\d+)(.*?)$/) {
-            my $cfid = $1;
-            my $cf = RT::CustomField->new( $session{'CurrentUser'});
-            $cf->Load($cfid);
-
-            if ( $cf->Type eq 'Freeform' && ! $cf->SingleValue) {
-                $ARGS{$arg} =~ s/\r\n/\n/g;
-                $ARGS{$arg} = [split('\n', $ARGS{$arg})];
-            }
-
-            if ( $cf->Type =~ /text/i) { # Catch both Text and Wikitext
-                $ARGS{$arg} =~ s/\r//g;
-            }
-
-            if ( $arg =~ /-Upload$/ ) {
-                $create_args{"CustomField-".$cfid} = _UploadedFile($arg);
-            }
-            else {
-                $create_args{"CustomField-".$cfid} = $ARGS{"$arg"};
-            }
+            $create_args{"CustomField-".$1} = $ARGS{"$arg"};
         }
     }
-
-
-    # XXX TODO This code should be about six lines. and badly needs refactoring.
-    # {{{ turn new link lists into arrays, and pass in the proper arguments
-    my (@dependson, @dependedonby, @parents, @children, @refersto, @referredtoby);
-
-    foreach my $luri ( split ( / /, $ARGS{"new-DependsOn"} ) ) {
-       $luri =~ s/\s*$//;    # Strip trailing whitespace
-       push @dependson, $luri;
-    }
-    $create_args{'DependsOn'} = \@dependson;
-
-    foreach my $luri ( split ( / /, $ARGS{"DependsOn-new"} ) ) {
-       push @dependedonby, $luri;
-    }
-    $create_args{'DependedOnBy'} = \@dependedonby;
-
-    foreach my $luri ( split ( / /, $ARGS{"new-MemberOf"} ) ) {
-       $luri =~ s/\s*$//;    # Strip trailing whitespace
-       push @parents, $luri;
+    my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args);
+    unless ( $id && $Trans ) {
+        Abort($ErrMsg);
     }
-    $create_args{'Parents'} = \@parents;
+    my @linktypes = qw( DependsOn MemberOf RefersTo );
 
-    foreach my $luri ( split ( / /, $ARGS{"MemberOf-new"} ) ) {
-       push @children, $luri;
-    }
-    $create_args{'Children'} = \@children;
+    foreach my $linktype (@linktypes) {
+        foreach my $luri ( split ( / /, $ARGS{"new-$linktype"} ) ) {
+            $luri =~ s/\s*$//;    # Strip trailing whitespace
+            my ( $val, $msg ) = $Ticket->AddLink(
+                Target => $luri,
+                Type   => $linktype
+            );
+            push ( @Actions, $msg ) unless ($val);
+        }
 
-    foreach my $luri ( split ( / /, $ARGS{"new-RefersTo"} ) ) {
-       $luri =~ s/\s*$//;    # Strip trailing whitespace
-       push @refersto, $luri;
-    }
-    $create_args{'RefersTo'} = \@refersto;
+        foreach my $luri ( split ( / /, $ARGS{"$linktype-new"} ) ) {
+            my ( $val, $msg ) = $Ticket->AddLink(
+                Base => $luri,
+                Type => $linktype
+            );
 
-    foreach my $luri ( split ( / /, $ARGS{"RefersTo-new"} ) ) {
-       push @referredtoby, $luri;
-    }
-    $create_args{'ReferredToBy'} = \@referredtoby;
-    # }}}
-  
-    my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args);
-    unless ( $id && $Trans ) {
-        Abort($ErrMsg);
+            push ( @Actions, $msg ) unless ($val);
+        }
     }
 
     push ( @Actions, split("\n", $ErrMsg) );
@@ -444,10 +365,7 @@ sub ProcessUpdateMessage {
     );
 
     #Make the update content have no 'weird' newlines in it
-    if (   $args{ARGSRef}->{'UpdateTimeWorked'}
-        || $args{ARGSRef}->{'UpdateContent'}
-        || $args{ARGSRef}->{'UpdateAttachments'} )
-    {
+    if ( $args{ARGSRef}->{'UpdateContent'} ) {
 
         if (
             $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject() )
@@ -456,76 +374,43 @@ sub ProcessUpdateMessage {
         }
 
         my $Message = MakeMIMEEntity(
-            Subject => $args{ARGSRef}->{'UpdateSubject'},
-            Body    => $args{ARGSRef}->{'UpdateContent'},
+            Subject             => $args{ARGSRef}->{'UpdateSubject'},
+            Body                => $args{ARGSRef}->{'UpdateContent'},
         );
 
-        $Message->head->add( 'Message-ID' => 
-              "<rt-"
-              . $RT::VERSION . "-"
-              . $$ . "-"
-              . CORE::time() . "-"
-              . int(rand(2000)) . "."
-              . $args{'TicketObj'}->id . "-"
-              . "0" . "-"  # Scrip
-              . "0" . "@"  # Email sent
-              . $RT::Organization
-              . ">" );
-        my $old_txn = RT::Transaction->new( $session{'CurrentUser'} );
-        if ( $args{ARGSRef}->{'QuoteTransaction'} ) {
-            $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} );
+        if ($args{ARGSRef}->{'UpdateAttachments'}) {
+            $Message->make_multipart;
+            $Message->add_part($_) foreach values %{$args{ARGSRef}->{'UpdateAttachments'}};
         }
-        else {
-            $old_txn = $args{TicketObj}->Transactions->First();
-        }
-
-        if ( $old_txn->Message && $old_txn->Message->First ) {
-            my @in_reply_to = split(/\s+/m, $old_txn->Message->First->GetHeader('In-Reply-To') || '');  
-            my @references = split(/\s+/m, $old_txn->Message->First->GetHeader('References') || '' );  
-            my @msgid = split(/\s+/m,$old_txn->Message->First->GetHeader('Message-ID') || ''); 
-            my @rtmsgid = split(/\s+/m,$old_txn->Message->First->GetHeader('RT-Message-ID') || ''); 
 
-            $Message->head->replace( 'In-Reply-To', join (' ', @rtmsgid ? @rtmsgid : @msgid));
-            $Message->head->replace( 'References', join(' ', @references, @msgid, @rtmsgid));
+        ## TODO: Implement public comments
+        if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
+            my ( $Transaction, $Description ) = $args{TicketObj}->Comment(
+                CcMessageTo  => $args{ARGSRef}->{'UpdateCc'},
+                BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
+                MIMEObj      => $Message,
+                TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
+            );
+            push ( @{ $args{Actions} }, $Description );
+        }
+        elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
+            my ( $Transaction, $Description ) = $args{TicketObj}->Correspond(
+                CcMessageTo  => $args{ARGSRef}->{'UpdateCc'},
+                BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
+                MIMEObj      => $Message,
+                TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
+            );
+            push ( @{ $args{Actions} }, $Description );
+        }
+        else {
+            push ( @{ $args{'Actions'} },
+                loc("Update type was neither correspondence nor comment.").
+                " ".
+                loc("Update not recorded.")
+            );
         }
-
-    if ( $args{ARGSRef}->{'UpdateAttachments'} ) {
-        $Message->make_multipart;
-        $Message->add_part($_)
-          foreach values %{ $args{ARGSRef}->{'UpdateAttachments'} };
-    }
-
-    ## TODO: Implement public comments
-    if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
-        my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Comment(
-            CcMessageTo  => $args{ARGSRef}->{'UpdateCc'},
-            BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
-            MIMEObj      => $Message,
-            TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
-        );
-        push( @{ $args{Actions} }, $Description );
-        $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
-    }
-    elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
-        my ( $Transaction, $Description, $Object ) =
-          $args{TicketObj}->Correspond(
-            CcMessageTo  => $args{ARGSRef}->{'UpdateCc'},
-            BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
-            MIMEObj      => $Message,
-            TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
-          );
-        push( @{ $args{Actions} }, $Description );
-        $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
-    }
-    else {
-        push(
-            @{ $args{'Actions'} },
-            loc("Update type was neither correspondence nor comment.") . " "
-              . loc("Update not recorded.")
-        );
     }
 }
-}
 
 # }}}
 
@@ -548,8 +433,7 @@ sub MakeMIMEEntity {
         Cc                  => undef,
         Body                => undef,
         AttachmentFieldName => undef,
-#        map Encode::encode_utf8($_), @_,
-        @_,
+        map Encode::encode_utf8($_), @_,
     );
 
     #Make the update content have no 'weird' newlines in it
@@ -565,7 +449,6 @@ sub MakeMIMEEntity {
             Subject => $args{'Subject'} || "",
             From    => $args{'From'},
             Cc      => $args{'Cc'},
-            Charset => 'utf8',
             Data    => [ $args{'Body'} ]
         );
     }
@@ -580,14 +463,7 @@ sub MakeMIMEEntity {
 
     #foreach my $filehandle (@filenames) {
 
-    my ( $fh, $temp_file );
-    for ( 1 .. 10 ) {
-        # on NFS and NTFS, it is possible that tempfile() conflicts
-        # with other processes, causing a race condition. we try to
-        # accommodate this by pausing and retrying.
-        last if ($fh, $temp_file) = eval { tempfile( UNLINK => 1) };
-        sleep 1;
-    }
+    my ( $fh, $temp_file ) = tempfile();
 
     binmode $fh;    #thank you, windows
     my ($buffer);
@@ -605,7 +481,7 @@ sub MakeMIMEEntity {
 
     $Message->attach(
         Path     => $temp_file,
-        Filename => Encode::decode_utf8($filename),
+        Filename => $filename,
         Type     => $uploadinfo->{'Content-Type'},
     );
     close($fh);
@@ -718,13 +594,13 @@ sub ProcessSearchQuery {
 
     # }}}
     # {{{ Limit requestor email
-     if ( $args{ARGS}->{'ValueOfWatcherRole'} ne '' ) {
-         $session{'tickets'}->LimitWatcher(
-             TYPE     => $args{ARGS}->{'WatcherRole'},
-             VALUE    => $args{ARGS}->{'ValueOfWatcherRole'},
-             OPERATOR => $args{ARGS}->{'WatcherRoleOp'},
 
+    if ( $args{ARGS}->{'ValueOfRequestor'} ne '' ) {
+        my $alias = $session{'tickets'}->LimitRequestor(
+            VALUE    => $args{ARGS}->{'ValueOfRequestor'},
+            OPERATOR => $args{ARGS}->{'RequestorOp'},
         );
+
     }
 
     # }}}
@@ -869,6 +745,19 @@ sub ParseDateToISO {
 
 # }}}
 
+# {{{ sub Config 
+# TODO: This might eventually read the cookies, user configuration
+# information from the DB, queue configuration information from the
+# DB, etc.
+
+sub Config {
+    my $args = shift;
+    my $key  = shift;
+    return $args->{$key} || $RT::WebOptions{$key};
+}
+
+# }}}
+
 # {{{ sub ProcessACLChanges
 
 sub ProcessACLChanges {
@@ -891,13 +780,17 @@ sub ProcessACLChanges {
 
             my $obj;
 
-             if ($object_type eq 'RT::System') {
-                $obj = $RT::System;
-           } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) {
-                $obj = $object_type->new($session{'CurrentUser'});
+            if ($object_type eq 'RT::Queue') {
+                $obj = RT::Queue->new($session{'CurrentUser'});
+                $obj->Load($object_id);      
+            } elsif ($object_type eq 'RT::Group') {
+                $obj = RT::Group->new($session{'CurrentUser'});
                 $obj->Load($object_id);      
+
+            } elsif ($object_type eq 'RT::System') {
+                $obj = $RT::System;
             } else {
-                push (@results, loc("System Error"). ': '.
+                push (@results, loc("System Error").
                                 loc("Rights could not be granted for [_1]", $object_type));
                 next;
             }
@@ -920,13 +813,17 @@ sub ProcessACLChanges {
             next unless ($right);
             my $obj;
 
-             if ($object_type eq 'RT::System') {
-                $obj = $RT::System;
-           } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) {
-                $obj = $object_type->new($session{'CurrentUser'});
+            if ($object_type eq 'RT::Queue') {
+                $obj = RT::Queue->new($session{'CurrentUser'});
+                $obj->Load($object_id);      
+            } elsif ($object_type eq 'RT::Group') {
+                $obj = RT::Group->new($session{'CurrentUser'});
                 $obj->Load($object_id);      
+
+            } elsif ($object_type eq 'RT::System') {
+                $obj = $RT::System;
             } else {
-                push (@results, loc("System Error"). ': '.
+                push (@results, loc("System Error").
                                 loc("Rights could not be revoked for [_1]", $object_type));
                 next;
             }
@@ -962,12 +859,52 @@ sub UpdateRecordObject {
         @_
     );
 
-    my $Object = $args{'Object'};
-    my @results = $Object->Update(AttributesRef => $args{'AttributesRef'},
-                                 ARGSRef       => $args{'ARGSRef'},
-                  AttributePrefix => $args{'AttributePrefix'}
-                                 );
+    my (@results);
+
+    my $object     = $args{'Object'};
+    my $attributes = $args{'AttributesRef'};
+    my $ARGSRef    = $args{'ARGSRef'};
+    foreach my $attribute (@$attributes) {
+        my $value;
+        if ( defined $ARGSRef->{$attribute} ) {
+            $value = $ARGSRef->{$attribute};
+        }
+        elsif (
+              defined( $args{'AttributePrefix'} )
+              && defined(
+                  $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute }
+              )
+          ) {
+            $value = $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute };
+
+        } else {
+                next;
+        }
 
+            $value =~ s/\r\n/\n/gs;
+
+        if ($value ne $object->$attribute()){
+
+              my $method = "Set$attribute";
+              my ( $code, $msg ) = $object->$method($value);
+
+              push @results, loc($attribute) . ': ' . loc_fuzzy($msg);
+=for loc
+                                   "[_1] could not be set to [_2].",       # loc
+                                   "That is already the current value",    # loc
+                                   "No value sent to _Set!\n",             # loc
+                                   "Illegal value for [_1]",               # loc
+                                   "The new value has been set.",          # loc
+                                   "No column specified",                  # loc
+                                   "Immutable field",                      # loc
+                                   "Nonexistant field?",                   # loc
+                                   "Invalid data",                         # loc
+                                   "Couldn't find row",                    # loc
+                                   "Missing a primary key?: [_1]",         # loc
+                                   "Found Object",                         # loc
+=cut
+          };
+    }
     return (@results);
 }
 
@@ -1016,17 +953,6 @@ sub ProcessCustomFieldUpdates {
         my ( $err, $msg ) = $Object->DeleteValue($id);
         push ( @results, $msg );
     }
-
-    my $vals = $Object->Values();
-    while (my $cfv = $vals->Next()) {
-        if (my $so = $ARGSRef->{ 'CustomField-' . $Object->Id . '-SortOrder' . $cfv->Id }) {
-            if ($cfv->SortOrder != $so) {
-                my ( $err, $msg ) = $cfv->SetSortOrder($so);
-                push ( @results, $msg );
-            }
-        }
-    }
-
     return (@results);
 }
 
@@ -1059,7 +985,6 @@ sub ProcessTicketBasics {
       TimeEstimated
       TimeWorked
       TimeLeft
-      Type
       Status
       Queue
     );
@@ -1072,11 +997,6 @@ sub ProcessTicketBasics {
         }
     }
 
-
-   # Status isn't a field that can be set to a null value.
-   # RT core complains if you try
-    delete $ARGSRef->{'Status'} unless ($ARGSRef->{'Status'});
-    
     my @results = UpdateRecordObject(
         AttributesRef => \@attribs,
         Object        => $TicketObj,
@@ -1105,158 +1025,109 @@ sub ProcessTicketBasics {
 
 # }}}
 
-sub ProcessTicketCustomFieldUpdates {
-    my %args = @_;
-    $args{'Object'} = delete $args{'TicketObj'};
-    my $ARGSRef = { %{ $args{'ARGSRef'} } };
+# {{{ Sub ProcessTicketCustomFieldUpdates
 
-    # Build up a list of objects that we want to work with
-    my %custom_fields_to_mod;
-    foreach my $arg ( keys %$ARGSRef ) {
-        if ( $arg =~ /^Ticket-(\d+-.*)/) {
-           $ARGSRef->{"Object-RT::Ticket-$1"} = delete $ARGSRef->{$arg};
-       }
-        elsif ( $arg =~ /^CustomField-(\d+-.*)/) {
-           $ARGSRef->{"Object-RT::Ticket--$1"} = delete $ARGSRef->{$arg};
-       }
-    }
+sub ProcessTicketCustomFieldUpdates {
+    my %args = (
+        ARGSRef => undef,
+        @_
+    );
 
-    return ProcessObjectCustomFieldUpdates(%args, ARGSRef => $ARGSRef);
-}
+    my @results;
 
-sub ProcessObjectCustomFieldUpdates {
-    my %args = @_;
     my $ARGSRef = $args{'ARGSRef'};
-    my @results;
 
-    # Build up a list of objects that we want to work with
+    # Build up a list of tickets that we want to work with
+    my %tickets_to_mod;
     my %custom_fields_to_mod;
-    foreach my $arg ( keys %$ARGSRef ) {
-        if ( $arg =~ /^Object-([\w:]+)-(\d*)-CustomField-(\d+)-/ ) {
-            # For each of those objects, find out what custom fields we want to work with.
-            $custom_fields_to_mod{$1}{$2 || $args{'Object'}->Id}{$3} = 1;
+    foreach my $arg ( keys %{$ARGSRef} ) {
+        if ( $arg =~ /^Ticket-(\d+)-CustomField-(\d+)-/ ) {
+
+            # For each of those tickets, find out what custom fields we want to work with.
+            $custom_fields_to_mod{$1}{$2} = 1;
         }
     }
 
-    # For each of those objects
-    foreach my $class ( keys %custom_fields_to_mod ) {
-       foreach my $id ( keys %{$custom_fields_to_mod{$class}} ) {
-           my $Object = $args{'Object'};
-           if (!$Object or ref($Object) ne $class or $Object->id != $id) {
-               $Object = $class->new( $session{'CurrentUser'} );
-               $Object->Load($id);
-       }
-
-           # For each custom field  
-           foreach my $cf ( keys %{ $custom_fields_to_mod{$class}{$id} } ) {
-           my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
-           $CustomFieldObj->LoadById($cf);
-
-               foreach my $arg ( keys %{$ARGSRef} ) {
-                   # Only interested in args for the current CF:
-                   next unless ( $arg =~ /^Object-$class-(?:$id)?-CustomField-$cf-/ );
-
-                   # since http won't pass in a form element with a null value, we need
-                   # to fake it
-                   if ($arg =~ /^(.*?)-Values-Magic$/ ) {
-                       # We don't care about the magic, if there's really a values element;
-                       next if ($ARGSRef->{$1.'-Value'} || $ARGSRef->{$1.'-Values'}) ;
+    # For each of those tickets
+    foreach my $tick ( keys %custom_fields_to_mod ) {
+        my $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
+        $Ticket->Load($tick);
 
-                        # "Empty" values does not mean anything for Image and Binary fields
-                        next if $CustomFieldObj->Type =~ /^(?:Image|Binary)$/;
-
-                       $arg = $1."-Values";
-                       $ARGSRef->{$1."-Values"} = undef;
-                   
-                   }
-                   my @values = ();
-                   if (ref( $ARGSRef->{$arg} ) eq 'ARRAY' ) {
-                       @values = @{ $ARGSRef->{$arg} };
-                   } elsif ($CustomFieldObj->Type =~ /text/i) { # Both Text and Wikitext
-                       @values = ($ARGSRef->{$arg});
-                   } else {
-                       @values = split /\n/, $ARGSRef->{$arg};
-                   }
-                   
-                   if ( ($CustomFieldObj->Type eq 'Freeform' 
-                         && ! $CustomFieldObj->SingleValue) ||
-                         $CustomFieldObj->Type =~ /text/i) {
-                       foreach my $val (@values) {
-                           $val =~ s/\r//g;
-                       }
-                   }
+        # For each custom field  
+        foreach my $cf ( keys %{ $custom_fields_to_mod{$tick} } ) {
 
-                   if ( ( $arg =~ /-AddValue$/ ) || ( $arg =~ /-Value$/ ) ) {
-                       foreach my $value (@values) {
-                           next unless length($value);
-                           my ( $val, $msg ) = $Object->AddCustomFieldValue(
-                               Field => $cf,
-                               Value => $value
-                           );
-                           push ( @results, $msg );
-                       }
-                   }
-                   elsif ( $arg =~ /-Upload$/ ) {
-                        my $value_hash = _UploadedFile($arg) or next;
+           my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
+           $CustomFieldObj->LoadById($cf);
 
-                       my ( $val, $msg ) = $Object->AddCustomFieldValue(
-                            %$value_hash,
+            foreach my $arg ( keys %{$ARGSRef} ) {
+                # since http won't pass in a form element with a null value, we need
+                # to fake it
+                if ($arg =~ /^(.*?)-Values-Magic$/ ) {
+                    # We don't care about the magic, if there's really a values element;
+                    next if (exists $ARGSRef->{$1.'-Values'}) ;
+
+                    $arg = $1."-Values";
+                    $ARGSRef->{$1."-Values"} = undef;
+                
+                }
+                next unless ( $arg =~ /^Ticket-$tick-CustomField-$cf-/ );
+                my @values =
+                  ( ref( $ARGSRef->{$arg} ) eq 'ARRAY' ) 
+                  ? @{ $ARGSRef->{$arg} }
+                  : ( $ARGSRef->{$arg} );
+                if ( ( $arg =~ /-AddValue$/ ) || ( $arg =~ /-Value$/ ) ) {
+                    foreach my $value (@values) {
+                        next unless ($value);
+                        my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
                             Field => $cf,
-                       );
-                       push ( @results, $msg );
-                   }
-                   elsif ( $arg =~ /-DeleteValues$/ ) {
-                       foreach my $value (@values) {
-                           next unless length($value);
-                           my ( $val, $msg ) = $Object->DeleteCustomFieldValue(
-                               Field => $cf,
-                               Value => $value
-                           );
-                           push ( @results, $msg );
-                       }
-                   }
-                   elsif ( $arg =~ /-DeleteValueIds$/ ) {
-                       foreach my $value (@values) {
-                           next unless length($value);
-                           my ( $val, $msg ) = $Object->DeleteCustomFieldValue(
-                               Field => $cf,
-                               ValueId => $value,
-                           );
-                           push ( @results, $msg );
-                       }
-                   }
-                   elsif ( $arg =~ /-Values$/ and !$CustomFieldObj->Repeated) {
-                       my $cf_values = $Object->CustomFieldValues($cf);
-
-                       my %values_hash;
-                       foreach my $value (@values) {
-                           next unless length($value);
-
-                           # build up a hash of values that the new set has
-                           $values_hash{$value} = 1;
-
-                           unless ( $cf_values->HasEntry($value) ) {
-                               my ( $val, $msg ) = $Object->AddCustomFieldValue(
-                                   Field => $cf,
-                                   Value => $value
-                               );
-                               push ( @results, $msg );
-                           }
-
-                       }
-                       while ( my $cf_value = $cf_values->Next ) {
-                           unless ( $values_hash{ $cf_value->Content } == 1 ) {
-                               my ( $val, $msg ) = $Object->DeleteCustomFieldValue(
-                                   Field => $cf,
-                                   Value => $cf_value->Content
-                               );
-                               push ( @results, $msg);
-
-                           }
-                       }
-                   }
-                   elsif ( $arg =~ /-Values$/ ) {
-                       my $cf_values = $Object->CustomFieldValues($cf);
+                            Value => $value
+                        );
+                        push ( @results, $msg );
+                    }
+                }
+                elsif ( $arg =~ /-DeleteValues$/ ) {
+                    foreach my $value (@values) {
+                        next unless ($value);
+                        my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue(
+                            Field => $cf,
+                            Value => $value
+                        );
+                        push ( @results, $msg );
+                    }
+                }
+                elsif ( $arg =~ /-Values$/ and $CustomFieldObj->Type !~ /Entry/) {
+                    my $cf_values = $Ticket->CustomFieldValues($cf);
+
+                    my %values_hash;
+                    foreach my $value (@values) {
+                        next unless ($value);
+
+                        # build up a hash of values that the new set has
+                        $values_hash{$value} = 1;
+
+                        unless ( $cf_values->HasEntry($value) ) {
+                            my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
+                                Field => $cf,
+                                Value => $value
+                            );
+                            push ( @results, $msg );
+                        }
+
+                    }
+                    while ( my $cf_value = $cf_values->Next ) {
+                        unless ( $values_hash{ $cf_value->Content } == 1 ) {
+                            my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue(
+                                Field => $cf,
+                                Value => $cf_value->Content
+                            );
+                            push ( @results, $msg);
+
+                        }
+
+                    }
+                }
+                elsif ( $arg =~ /-Values$/ ) {
+                    my $cf_values = $Ticket->CustomFieldValues($cf);
 
                    # keep everything up to the point of difference, delete the rest
                    my $delete_flag;
@@ -1272,23 +1143,24 @@ sub ProcessObjectCustomFieldUpdates {
 
                    # now add/replace extra things, if any
                    foreach my $value (@values) {
-                           my ( $val, $msg ) = $Object->AddCustomFieldValue(
+                       my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
                            Field => $cf,
                            Value => $value
                        );
                        push ( @results, $msg );
                    }
                }
-                   else {
-                       push ( @results, loc("User asked for an unknown update type for custom field [_1] for [_2] object #[_3]", $cf->Name, $class, $Object->id ) );
-                   }
-               }
-           }
-           return (@results);
-       }
+                else {
+                    push ( @results, "User asked for an unknown update type for custom field " . $cf->Name . " for ticket " . $Ticket->id );
+                }
+            }
+        }
+        return (@results);
     }
 }
 
+# }}}
+
 # {{{ sub ProcessTicketWatchers
 
 =head2 ProcessTicketWatchers ( TicketObj => $Ticket, ARGSRef => \%ARGS );
@@ -1313,7 +1185,7 @@ sub ProcessTicketWatchers {
     foreach my $key ( keys %$ARGSRef ) {
 
         # {{{ Delete deletable watchers
-        if ( ( $key =~ /^Ticket-DeleteWatcher-Type-(.*)-Principal-(\d+)$/ )  ) {
+        if ( ( $key =~ /^Ticket-DelWatcher-Type-(.*)-Principal-(\d+)$/ )  ) {
             my ( $code, $msg ) = 
                 $Ticket->DeleteWatcher(PrincipalId => $2,
                                        Type => $1);
@@ -1321,8 +1193,8 @@ sub ProcessTicketWatchers {
         }
 
         # Delete watchers in the simple style demanded by the bulk manipulator
-        elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc)$/ ) {       
-            my ( $code, $msg ) = $Ticket->DeleteWatcher( Email => $ARGSRef->{$key}, Type => $1 );
+        elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc)$/ ) {
+            my ( $code, $msg ) = $Ticket->DeleteWatcher( Type => $ARGSRef->{$key}, PrincipalId => $1 );
             push @results, $msg;
         }
 
@@ -1442,30 +1314,6 @@ sub ProcessTicketLinks {
     my $Ticket  = $args{'TicketObj'};
     my $ARGSRef = $args{'ARGSRef'};
 
-
-    my (@results) = ProcessRecordLinks(RecordObj => $Ticket,
-                                      ARGSRef => $ARGSRef);
-
-    #Merge if we need to
-    if ( $ARGSRef->{ $Ticket->Id . "-MergeInto" } ) {
-        my ( $val, $msg ) =
-          $Ticket->MergeInto( $ARGSRef->{ $Ticket->Id . "-MergeInto" } );
-        push @results, $msg;
-    }
-
-    return (@results);
-}
-
-# }}}
-
-sub ProcessRecordLinks {
-    my %args = ( RecordObj => undef,
-                 ARGSRef   => undef,
-                 @_ );
-
-    my $Record  = $args{'RecordObj'};
-    my $ARGSRef = $args{'ARGSRef'};
-
     my (@results);
 
     # Delete links that are gone gone gone.
@@ -1477,7 +1325,7 @@ sub ProcessRecordLinks {
 
             push @results,
               "Trying to delete: Base: $base Target: $target  Type $type";
-            my ( $val, $msg ) = $Record->DeleteLink( Base   => $base,
+            my ( $val, $msg ) = $Ticket->DeleteLink( Base   => $base,
                                                      Type   => $type,
                                                      Target => $target );
 
@@ -1490,18 +1338,18 @@ sub ProcessRecordLinks {
     my @linktypes = qw( DependsOn MemberOf RefersTo );
 
     foreach my $linktype (@linktypes) {
-        if ( $ARGSRef->{ $Record->Id . "-$linktype" } ) {
-            for my $luri ( split ( / /, $ARGSRef->{ $Record->Id . "-$linktype" } ) ) {
+        if ( $ARGSRef->{ $Ticket->Id . "-$linktype" } ) {
+            for my $luri ( split ( / /, $ARGSRef->{ $Ticket->Id . "-$linktype" } ) ) {
                 $luri =~ s/\s*$//;    # Strip trailing whitespace
-                my ( $val, $msg ) = $Record->AddLink( Target => $luri,
+                my ( $val, $msg ) = $Ticket->AddLink( Target => $luri,
                                                       Type   => $linktype );
                 push @results, $msg;
             }
         }
-        if ( $ARGSRef->{ "$linktype-" . $Record->Id } ) {
+        if ( $ARGSRef->{ "$linktype-" . $Ticket->Id } ) {
 
-            for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Record->Id } ) ) {
-                my ( $val, $msg ) = $Record->AddLink( Base => $luri,
+            for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Ticket->Id } ) ) {
+                my ( $val, $msg ) = $Ticket->AddLink( Base => $luri,
                                                       Type => $linktype );
 
                 push @results, $msg;
@@ -1509,36 +1357,17 @@ sub ProcessRecordLinks {
         } 
     }
 
+    #Merge if we need to
+    if ( $ARGSRef->{ $Ticket->Id . "-MergeInto" } ) {
+        my ( $val, $msg ) =
+          $Ticket->MergeInto( $ARGSRef->{ $Ticket->Id . "-MergeInto" } );
+        push @results, $msg;
+    }
+
     return (@results);
 }
 
-
-=head2 _UploadedFile ( $arg );
-
-Takes a CGI parameter name; if a file is uploaded under that name,
-return a hash reference suitable for AddCustomFieldValue's use:
-C<( Value => $filename, LargeContent => $content, ContentType => $type )>.
-
-Returns C<undef> if no files were uploaded in the C<$arg> field.
-
-=cut
-
-sub _UploadedFile {
-    my $arg = shift;
-    my $cgi_object = $m->cgi_object;
-    my $fh = $cgi_object->upload($arg) or return undef;
-    my $upload_info = $cgi_object->uploadInfo($fh);
-
-    my $filename = "$fh";
-    $filename =~ s#^.*[\\/]##;
-    binmode($fh);
-
-    return {
-        Value => $filename,
-        LargeContent => do { local $/; scalar <$fh> },
-        ContentType => $upload_info->{'Content-Type'},
-    };
-}
+# }}}
 
 eval "require RT::Interface::Web_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web_Vendor.pm});