diff options
Diffstat (limited to 'rt/lib/RT/Interface')
| -rw-r--r-- | rt/lib/RT/Interface/CLI.pm | 112 | ||||
| -rwxr-xr-x | rt/lib/RT/Interface/Email.pm | 683 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Email/Auth/MailFrom.pm | 131 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm | 63 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Web.pm | 1124 | 
5 files changed, 870 insertions, 1243 deletions
| diff --git a/rt/lib/RT/Interface/CLI.pm b/rt/lib/RT/Interface/CLI.pm index ec0e877b4..a3bf92d5f 100644 --- a/rt/lib/RT/Interface/CLI.pm +++ b/rt/lib/RT/Interface/CLI.pm @@ -1,31 +1,9 @@ -# 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 -use strict; +# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Interface/CLI.pm,v 1.1 2002-08-12 06:17:08 ivan Exp $ +# RT is (c) 1996-2001 Jesse Vincent <jesse@fsck.com> -use RT;  package RT::Interface::CLI; +use strict;  BEGIN { @@ -33,14 +11,14 @@ BEGIN {      use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);      # set the version for version checking -    $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker +    $VERSION = do { my @r = (q$Revision: 1.1 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker      @ISA         = qw(Exporter);      # your exported package globals go here,      # as well as any optionally exported functions -    @EXPORT_OK   = qw(&CleanEnv  -		      &GetCurrentUser &GetMessageContent &debug &loc); +    @EXPORT_OK   = qw(&CleanEnv &LoadConfig &DBConnect  +		      &GetCurrentUser &GetMessageContent &debug);  }  =head1 NAME @@ -49,28 +27,25 @@ BEGIN {  =head1 SYNOPSIS -  use lib "/path/to/rt/libraries/"; +  use lib "!!RT_LIB_PATH!!"; +  use lib "!!RT_ETC_PATH!!"; -  use RT::Interface::CLI  qw(CleanEnv  -	  		   GetCurrentUser GetMessageContent loc); +  use RT::Interface::CLI  qw(CleanEnv LoadConfig DBConnect  +	  		   GetCurrentUser GetMessageContent);    #Clean out all the nasties from the environment    CleanEnv(); -  #let's talk to RT' -  use RT; +  #Load etc/config.pm and drop privs +  LoadConfig(); -  #Load RT's config file -  RT::LoadConfig(); +  #Connect to the database and get RT::SystemUser and RT::Nobody loaded +  DBConnect(); -  # Connect to the database. set up loggign -  RT::Init();    #Get the current user all loaded    my $CurrentUser = GetCurrentUser(); -  print loc('Hello!'); # Synonym of $CuurentUser->loc('Hello!'); -  =head1 DESCRIPTION @@ -78,6 +53,7 @@ BEGIN {  =begin testing +ok(require RT::TestHarness);  ok(require RT::Interface::CLI);  =end testing @@ -101,10 +77,35 @@ sub CleanEnv { +=head2 LoadConfig + +Loads RT's config file and then drops setgid privileges. + +=cut + +sub LoadConfig { +     +    #This drags in  RT's config.pm +    use config; +     +}	 + + + +=head2 DBConnect + +  Calls RT::Init, which creates a database connection and then creates $RT::Nobody +  and $RT::SystemUser + +=cut + + +sub DBConnect { +    use RT; +    RT::Init(); +} -{ -    my $CurrentUser; # shared betwen GetCurrentUser and loc  # {{{ sub GetCurrentUser  @@ -114,14 +115,15 @@ sub CleanEnv {  loaded with that user.  if the current user isn't found, returns a copy of RT::Nobody.  =cut -  sub GetCurrentUser  { +    my ($Gecos, $CurrentUser); +          require RT::CurrentUser;      #Instantiate a user object -    my $Gecos= ($^O eq 'MSWin32') ? Win32::LoginName() : (getpwuid($<))[0]; +    $Gecos=(getpwuid($<))[0];      #If the current user is 0, then RT will assume that the User object      #is that of the currentuser. @@ -132,29 +134,10 @@ sub GetCurrentUser  {      unless ($CurrentUser->Id) {  	$RT::Logger->debug("No user with a unix login of '$Gecos' was found. ");      } -      return($CurrentUser);  }  # }}} - -# {{{ sub loc  - -=head2 loc - -  Synonym of $CurrentUser->loc(). - -=cut - -sub loc { -    die "No current user yet" unless $CurrentUser ||= RT::CurrentUser->new; -    return $CurrentUser->loc(@_); -} -# }}} - -} - -  # {{{ sub GetMessageContent  =head2 GetMessageContent @@ -238,9 +221,4 @@ sub debug {  # }}} -eval "require RT::Interface::CLI_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/CLI_Vendor.pm}); -eval "require RT::Interface::CLI_Local"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/CLI_Local.pm}); -  1; diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm index 7eec0502f..e95436091 100755 --- a/rt/lib/RT/Interface/Email.pm +++ b/rt/lib/RT/Interface/Email.pm @@ -1,58 +1,41 @@ -# 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 +# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Interface/Email.pm,v 1.1 2002-08-12 06:17:08 ivan Exp $ +# RT is (c) 1996-2001 Jesse Vincent <jesse@fsck.com> +  package RT::Interface::Email;  use strict;  use Mail::Address;  use MIME::Entity; -use RT::EmailParser; -  BEGIN {      use Exporter ();      use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);      # set the version for version checking -    $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker +    $VERSION = do { my @r = (q$Revision: 1.1 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker      @ISA         = qw(Exporter);      # your exported package globals go here,      # as well as any optionally exported functions -    @EXPORT_OK   = qw( -              &CreateUser +    @EXPORT_OK   = qw(&CleanEnv  +		      &LoadConfig  +		      &DBConnect  +		      &GetCurrentUser  		      &GetMessageContent  		      &CheckForLoops   		      &CheckForSuspiciousSender  		      &CheckForAutoGenerated  +		      &ParseMIMEEntityFromSTDIN +		      &ParseTicketId   		      &MailError   		      &ParseCcAddressesFromHead  		      &ParseSenderAddressFromHead  -		      &ParseErrorsToAddressFromHead -                      &ParseAddressFromHeader -              &Gateway); +   		      &ParseErrorsToAddressFromHead +              &ParseAddressFromHeader + +		      &debug);  }  =head1 NAME @@ -64,13 +47,28 @@ BEGIN {    use lib "!!RT_LIB_PATH!!";    use lib "!!RT_ETC_PATH!!"; -  use RT::Interface::Email  qw(Gateway CreateUser); +  use RT::Interface::Email  qw(CleanEnv LoadConfig DBConnect  +			      ); + +  #Clean out all the nasties from the environment +  CleanEnv(); + +  #Load etc/config.pm and drop privs +  LoadConfig(); + +  #Connect to the database and get RT::SystemUser and RT::Nobody loaded +  DBConnect(); + + +  #Get the current user all loaded +  my $CurrentUser = GetCurrentUser();  =head1 DESCRIPTION  =begin testing +ok(require RT::TestHarness);  ok(require RT::Interface::Email);  =end testing @@ -81,6 +79,71 @@ ok(require RT::Interface::Email);  =cut +=head2 CleanEnv + +Removes some of the nastiest nasties from the user\'s environment. + +=cut + +sub CleanEnv { +    $ENV{'PATH'} = '/bin:/usr/bin';    # or whatever you need +    $ENV{'CDPATH'} = '' if defined $ENV{'CDPATH'}; +    $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'}; +    $ENV{'ENV'} = '' if defined $ENV{'ENV'}; +    $ENV{'IFS'} = '' if defined $ENV{'IFS'}; +} + + + +=head2 LoadConfig + +Loads RT's config file and then drops setgid privileges. + +=cut + +sub LoadConfig { +     +    #This drags in  RT's config.pm +    use config; +     +}	 + + + +=head2 DBConnect + +  Calls RT::Init, which creates a database connection and then creates $RT::Nobody +  and $RT::SystemUser + +=cut + + +sub DBConnect { +    use RT; +    RT::Init(); +} + + + +# {{{ sub debug + +sub debug { +    my $val = shift; +    my ($debug); +    if ($val) { +	$RT::Logger->debug($val."\n"); +	if ($debug) { +	    print STDERR "$val\n"; +	} +    } +    if ($debug) { +	return(1); +    }	 +} + +# }}} + +  # {{{ sub CheckForLoops   sub CheckForLoops  { @@ -144,6 +207,82 @@ sub CheckForAutoGenerated {  # }}} +# {{{ sub ParseMIMEEntityFromSTDIN + +sub ParseMIMEEntityFromSTDIN { + +    # Create a new parser object: +     +    my $parser = new MIME::Parser; +     +    # {{{ Config $parser to store large attacments in temp dir + +    ## TODO: Does it make sense storing to disk at all?  After all, we +    ## need to put each msg as an in-core scalar before saving it to +    ## the database, don't we? + +    ## At the same time, we should make sure that we nuke attachments  +    ## Over max size and return them + +    ## TODO: Remove the temp dir when we don't need it any more. + +    my $AttachmentDir = File::Temp::tempdir (TMPDIR => 1, CLEANUP => 1); +     +    # Set up output directory for files: +    $parser->output_dir("$AttachmentDir"); +   +    #If someone includes a message, don't extract it +    $parser->extract_nested_messages(0); + +    +    # Set up the prefix for files with auto-generated names: +    $parser->output_prefix("part"); + +    # If content length is <= 20000 bytes, store each msg as in-core scalar; +    # Else, write to a disk file (the default action): +   +    $parser->output_to_core(20000); + +    # }}} (temporary directory) + +    #Ok. now that we're set up, let's get the stdin. +    my $entity; +    unless ($entity = $parser->read(\*STDIN)) { +	die "couldn't parse MIME stream"; +    } +    #Now we've got a parsed mime object.  +     +    # Get the head, a MIME::Head: +    my $head = $entity->head; +    + +    # Unfold headers that are have embedded newlines +    $head->unfold;  +     +    # TODO - information about the charset is lost here! +    $head->decode; + +    return ($entity, $head); + +} +# }}} + +# {{{ sub ParseTicketId  + +sub ParseTicketId { +    my $Subject = shift; +    my ($Id); +     +    if ($Subject =~ s/\[$RT::rtname \#(\d+)\]//i) { +	$Id = $1; +	$RT::Logger->debug("Found a ticket ID. It's $Id"); +	return($Id); +    } +    else { +	return(undef); +    } +} +# }}}  # {{{ sub MailError   sub MailError { @@ -174,8 +313,8 @@ sub MailError {      if ($mimeobj) {          $mimeobj->sync_headers();          $entity->add_part($mimeobj); -    } -     +    }  +      if ($RT::MailCommand eq 'sendmailpipe') {          open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0);          print MAIL $entity->as_string; @@ -188,66 +327,144 @@ sub MailError {  # }}} -# {{{ Create User +# {{{ sub GetCurrentUser  + +sub GetCurrentUser  { +    my $head = shift; +    my $entity = shift; +    my $ErrorsTo = shift; -sub CreateUser { -    my ($Username, $Address, $Name, $ErrorsTo, $entity) = @_; -    my $NewUser = RT::User->new($RT::SystemUser); +    my %UserInfo = (); -    # This data is tainted by some Very Broken mailers. -    # (Sometimes they send raw ISO 8859-1 data here. fear that. -    require Encode; -    $Username = Encode::encode(utf8 => $Username, Encode::FB_PERLQQ()) if defined $Username; -    $Name = Encode::encode(utf8 => $Name, Encode::FB_PERLQQ()) if defined $Name; -     -    my ($Val, $Message) =  -      $NewUser->Create(Name => ($Username || $Address), -                       EmailAddress => $Address, -                       RealName => $Name, -                       Password => undef, -                       Privileged => 0, -                       Comments => 'Autocreated on ticket submission' -                      ); +    #Suck the address of the sender out of the header +    my ($Address, $Name) = ParseSenderAddressFromHead($head); -    unless ($Val) { -         -        # Deal with the race condition of two account creations at once -        # -        if ($Username) { -            $NewUser->LoadByName($Username); -        } -         -        unless ($NewUser->Id) { -            $NewUser->LoadByEmail($Address); -        } -         -        unless ($NewUser->Id) {   -            MailError( To => $ErrorsTo, -                       Subject => "User could not be created", -                       Explanation => "User creation failed in mailgateway: $Message", -                       MIMEObj => $entity, -                       LogLevel => 'crit' -                     ); -        } +    #This will apply local address canonicalization rules +    $Address = RT::CanonicalizeAddress($Address); +   +    #If desired, synchronize with an external database + +    my $UserFoundInExternalDatabase = 0; + +    # Username is the 'Name' attribute of the user that RT uses for things +    # like authentication +    my $Username = undef; +    if ($RT::LookupSenderInExternalDatabase) { +	($UserFoundInExternalDatabase, %UserInfo) =  +	  RT::LookupExternalUserInfo($Address, $Name); +    +       $Address = $UserInfo{'EmailAddress'}; +       $Username = $UserInfo{'Name'};       } - -    #Load the new user object +          my $CurrentUser = RT::CurrentUser->new(); -    $CurrentUser->LoadByEmail($Address); - -    unless ($CurrentUser->id) { -            $RT::Logger->warning("Couldn't load user '$Address'.".  "giving up"); -                MailError( To => $ErrorsTo, -                           Subject => "User could not be loaded", -                           Explanation => "User  '$Address' could not be loaded in the mail gateway", -                           MIMEObj => $entity, -                           LogLevel => 'crit' -                     ); -    } +     +    # First try looking up by a username, if we got one from the external +    # db lookup. Next, try looking up by email address. Failing that, +    # try looking up by users who have this user's email address as their +    # username. + +    if ($Username) { +	$CurrentUser->LoadByName($Username); +    }	 +     +    unless ($CurrentUser->Id) { +	$CurrentUser->LoadByEmail($Address); +    }	 -    return $CurrentUser; +    #If we can't get it by email address, try by name.   +    unless ($CurrentUser->Id) { +	$CurrentUser->LoadByName($Address); +    } +     +     +    unless ($CurrentUser->Id) { +        #If we couldn't load a user, determine whether to create a user + +        # {{{ If we require an incoming address to be found in the external +	# user database, reject the incoming message appropriately +        if ( $RT::LookupSenderInExternalDatabase && +	     $RT::SenderMustExistInExternalDatabase &&  +	     !$UserFoundInExternalDatabase) { +	     +	    my $Message = "Sender's email address was not found in the user database."; + +	    # {{{  This code useful only if you've defined an AutoRejectRequest template +	     +	    require RT::Template; +	    my $template = new RT::Template($RT::Nobody); +	    $template->Load('AutoRejectRequest'); +	    $Message = $template->Content || $Message; +	     +	    # }}} +	     +	    MailError( To => $ErrorsTo, +		       Subject => "Ticket Creation failed: user could not be created", +		       Explanation => $Message, +		       MIMEObj => $entity, +		       LogLevel => 'notice' +		     ); + +	    return($CurrentUser); + +	}  +	# }}} +	 +	else { +	    my $NewUser = RT::User->new($RT::SystemUser); +	     +	    my ($Val, $Message) =  +	      $NewUser->Create(Name => ($Username || $Address), +			       EmailAddress => $Address, +			       RealName => "$Name", +			       Password => undef, +			       Privileged => 0, +			       Comments => 'Autocreated on ticket submission' +			      ); +	     +	    unless ($Val) { +		 +		# Deal with the race condition of two account creations at once +		# +           	if ($Username) { +		    $NewUser->LoadByName($Username); +		} +		 +		unless ($NewUser->Id) { +	            $NewUser->LoadByEmail($Address); +		} +		 +		unless ($NewUser->Id) {   +		    MailError( To => $ErrorsTo, +			       Subject => "User could not be created", +			       Explanation => "User creation failed in mailgateway: $Message", +			       MIMEObj => $entity, +			       LogLevel => 'crit' +			     ); +		} +	    } +	} +	 +	#Load the new user object +	$CurrentUser->LoadByEmail($Address); +	 +	unless ($CurrentUser->id) { +	    $RT::Logger->warning("Couldn't load user '$Address'.".  "giving up"); +		MailError( To => $ErrorsTo, +			   Subject => "User could not be loaded", +			   Explanation => "User  '$Address' could not be loaded in the mail gateway", +			   MIMEObj => $entity, +			   LogLevel => 'crit' +			 ); +	     +	} +    } +     +    return ($CurrentUser); +      } -# }}}	     +# }}} +  # {{{ ParseCcAddressesFromHead   =head2 ParseCcAddressesFromHead HASHREF @@ -272,11 +489,11 @@ sub ParseCcAddressesFromHead {      foreach my $AddrObj (@ToObjs, @CcObjs) {  	my $Address = $AddrObj->address; -	$Address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress($Address); +	$Address = RT::CanonicalizeAddress($Address);   	next if ($args{'CurrentUser'}->EmailAddress =~ /^$Address$/i);  	next if ($args{'QueueObj'}->CorrespondAddress =~ /^$Address$/i);  	next if ($args{'QueueObj'}->CommentAddress =~ /^$Address$/i); -	next if (RT::EmailParser::IsRTAddress(undef, $Address)); +	next if (RT::IsRTAddress($Address));  	push (@Addresses, $Address);      } @@ -351,7 +568,8 @@ sub ParseAddressFromHeader{      }      my $Name =  ($AddrObj->phrase || $AddrObj->comment || $AddrObj->address); -     + +      #Lets take the from and load a user object.      my $Address = $AddrObj->address; @@ -360,289 +578,4 @@ sub ParseAddressFromHeader{  # }}} - -=head2 Gateway - -This performs all the "guts" of the mail rt-mailgate program, and is -designed to be called from the web interface with a message, user -object, and so on. - -=cut - -sub Gateway { -    my %args = ( message => undef, -                 queue   => 1, -                 action  => 'correspond', -                 ticket  => undef, -                 @_ ); - -    # Validate the action -    unless ( $args{'action'} =~ /^(comment|correspond|action)$/ ) { - -        # Can't safely loc this. What object do we loc around? -        return ( 0, "Invalid 'action' parameter", undef ); -    } - -    my $parser = RT::EmailParser->new(); -    $parser->ParseMIMEEntityFromScalar( $args{'message'} ); - -    my $Message = $parser->Entity(); -    my $head = $Message->head; - -    my ( $CurrentUser, $AuthStat, $status, $error ); - -    my $ErrorsTo = ParseErrorsToAddressFromHead($head); - -    my $MessageId = $head->get('Message-Id') -      || "<no-message-id-" . time . rand(2000) . "\@.$RT::Organization>"; - -    #Pull apart the subject line -    my $Subject = $head->get('Subject') || ''; -    chomp $Subject; - - -    $args{'ticket'} ||= $parser->ParseTicketId($Subject); - -    my $SystemTicket; -    if ($args{'ticket'} ) { -        $SystemTicket = RT::Ticket->new($RT::SystemUser); -        $SystemTicket->Load($args{'ticket'}); -    } - -    #Set up a queue object -    my $SystemQueueObj = RT::Queue->new($RT::SystemUser); -    $SystemQueueObj->Load( $args{'queue'} ); - - -    # We can safely have no queue of we have a known-good ticket -    unless ( $args{'ticket'} || $SystemQueueObj->id ) { -        MailError( -                 To          => $RT::OwnerEmail, -                 Subject     => "RT Bounce: $Subject", -                 Explanation => "RT couldn't find the queue: " . $args{'queue'}, -                 MIMEObj     => $Message ); -        return ( 0, "RT couldn't find the queue: " . $args{'queue'}, undef ); -    } - -    # Authentication Level -    # -1 - Get out.  this user has been explicitly declined  -    # 0 - User may not do anything (Not used at the moment) -    # 1 - Normal user -    # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate - -    push @RT::MailPlugins, "Auth::MailFrom"   unless @RT::MailPlugins; -    # Since this needs loading, no matter what - -    for (@RT::MailPlugins) { -        my $Code; -        my $NewAuthStat; -        if ( ref($_) eq "CODE" ) { -            $Code = $_; -        } -        else { -            $_ = "RT::Interface::Email::$_" unless /^RT::Interface::Email::/; -            eval "require $_;"; -            if ($@) { -                die ("Couldn't load module $_: $@"); -                next; -            } -            no strict 'refs'; -            if ( !defined( $Code = *{ $_ . "::GetCurrentUser" }{CODE} ) ) { -                die ("No GetCurrentUser code found in $_ module"); -                next; -            } -        } - -        ( $CurrentUser, $NewAuthStat ) = $Code->( Message     => $Message, -                                                  CurrentUser => $CurrentUser, -                                                  AuthLevel   => $AuthStat, -                                                  Action => $args{'action'}, -                                                  Ticket => $SystemTicket, -                                                  Queue  => $SystemQueueObj ); - -        # You get the highest level of authentication you were assigned. -        last if $AuthStat == -1; -        $AuthStat = $NewAuthStat if $NewAuthStat > $AuthStat; -    } - -    # {{{ If authentication fails and no new user was created, get out. -    if ( !$CurrentUser or !$CurrentUser->Id or $AuthStat == -1 ) { - -        # If the plugins refused to create one, they lose. -        MailError( -            Subject     => "Could not load a valid user", -            Explanation => <<EOT, -RT could not load a valid user, and RT's configuration does not allow -for the creation of a new user for your email. - -Your RT administrator needs to grant 'Everyone' the right 'CreateTicket' -for this queue. - -EOT -            MIMEObj  => $Message, -            LogLevel => 'error' ) -          unless $AuthStat == -1; -        return ( 0, "Could not load a valid user", undef ); -    } - -    # }}} - -    # {{{ Lets check for mail loops of various sorts. -    my $IsAutoGenerated = CheckForAutoGenerated($head); - -    my $IsSuspiciousSender = CheckForSuspiciousSender($head); - -    my $IsALoop = CheckForLoops($head); - -    my $SquelchReplies = 0; - -    #If the message is autogenerated, we need to know, so we can not -    # send mail to the sender -    if ( $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) { -        $SquelchReplies = 1; -        $ErrorsTo       = $RT::OwnerEmail; -    } - -    # }}} - -    # {{{ Drop it if it's disallowed -    if ( $AuthStat == 0 ) { -        MailError( -             To          => $ErrorsTo, -             Subject     => "Permission Denied", -             Explanation => "You do not have permission to communicate with RT", -             MIMEObj     => $Message ); -    } - -    # }}} -    # {{{ Warn someone  if it's a loop - -    # Warn someone if it's a loop, before we drop it on the ground -    if ($IsALoop) { -        $RT::Logger->crit("RT Recieved mail ($MessageId) from itself."); - -        #Should we mail it to RTOwner? -        if ($RT::LoopsToRTOwner) { -            MailError( To          => $RT::OwnerEmail, -                       Subject     => "RT Bounce: $Subject", -                       Explanation => "RT thinks this message may be a bounce", -                       MIMEObj     => $Message ); - -            #Do we actually want to store it? -            return ( 0, "Message Bounced", undef ) unless ($RT::StoreLoops); -        } -    } - -    # }}} - -    # {{{ Squelch replies if necessary -    # Don't let the user stuff the RT-Squelch-Replies-To header. -    if ( $head->get('RT-Squelch-Replies-To') ) { -        $head->add( 'RT-Relocated-Squelch-Replies-To', -                    $head->get('RT-Squelch-Replies-To') ); -        $head->delete('RT-Squelch-Replies-To'); -    } - -    if ($SquelchReplies) { -        ## TODO: This is a hack.  It should be some other way to -        ## indicate that the transaction should be "silent". - -        my ( $Sender, $junk ) = ParseSenderAddressFromHead($head); -        $head->add( 'RT-Squelch-Replies-To', $Sender ); -    } - -    # }}} - -    my $Ticket = RT::Ticket->new($CurrentUser); - -    # {{{ If we don't have a ticket Id, we're creating a new ticket -    if ( !$args{'ticket'} ) { - -        # {{{ Create a new ticket - -        my @Cc; -        my @Requestors = ( $CurrentUser->id ); - -        if ($RT::ParseNewMessageForTicketCcs) { -            @Cc = ParseCcAddressesFromHead( Head        => $head, -                                            CurrentUser => $CurrentUser, -                                            QueueObj    => $SystemQueueObj ); -        } - -        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create( -                                                      Queue     => $SystemQueueObj->Id, -                                                      Subject   => $Subject, -                                                      Requestor => \@Requestors, -                                                      Cc        => \@Cc, -                                                      MIMEObj   => $Message ); -        if ( $id == 0 ) { -            MailError( To          => $ErrorsTo, -                       Subject     => "Ticket creation failed", -                       Explanation => $ErrStr, -                       MIMEObj     => $Message ); -            $RT::Logger->error("Create failed: $id / $Transaction / $ErrStr "); -            return ( 0, "Ticket creation failed", $Ticket ); -        } - -        # }}} -    } - -    # }}} - -    #   If the action is comment, add a comment. -    elsif ( $args{'action'} =~ /^(comment|correspond)$/i ) { -        $Ticket->Load($args{'ticket'}); -        unless ( $Ticket->Id ) { -            my $message = "Could not find a ticket with id ".$args{'ticket'}; -            MailError( To          => $ErrorsTo, -                     Subject     => "Message not recorded", -                     Explanation => $message, -                     MIMEObj     => $Message ); - -            return ( 0, $message); -        } - -        my ( $status, $msg ); -        if ( $args{'action'} =~ /^correspond$/ ) { -            ( $status, $msg ) = $Ticket->Correspond( MIMEObj => $Message ); -        } -        else { -            ( $status, $msg ) = $Ticket->Comment( MIMEObj => $Message ); -        } -        unless ($status) { - -            #Warn the sender that we couldn't actually submit the comment. -            MailError( To          => $ErrorsTo, -                       Subject     => "Message not recorded", -                       Explanation => $msg, -                       MIMEObj     => $Message ); -            return ( 0, "Message not recorded", $Ticket ); -        } -    } - -    else { - -        #Return mail to the sender with an error -        MailError( To          => $ErrorsTo, -                   Subject     => "RT Configuration error", -                   Explanation => "'" -                     . $args{'action'} -                     . "' not a recognized action." -                     . " Your RT administrator has misconfigured " -                     . "the mail aliases which invoke RT", -                   MIMEObj => $Message ); -        $RT::Logger->crit( $args{'action'} . " type unknown for $MessageId" ); -        return ( 0, "Configuration error: " . $args{'action'} . " not a recognized action", $Ticket ); - -    } - - -return ( 1, "Success", $Ticket ); -} - -eval "require RT::Interface::Email_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm}); -eval "require RT::Interface::Email_Local"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm}); -  1; diff --git a/rt/lib/RT/Interface/Email/Auth/MailFrom.pm b/rt/lib/RT/Interface/Email/Auth/MailFrom.pm deleted file mode 100644 index eb778ff30..000000000 --- a/rt/lib/RT/Interface/Email/Auth/MailFrom.pm +++ /dev/null @@ -1,131 +0,0 @@ -# BEGIN LICENSE BLOCK -#  -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -#  -# (Except where explictly superceded by other copyright notices) -#  -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -#  -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU -# General Public License for more details. -#  -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -#  -#  -# END LICENSE BLOCK -package RT::Interface::Email::Auth::MailFrom; -use RT::Interface::Email qw(ParseSenderAddressFromHead CreateUser); - -# This is what the ordinary, non-enhanced gateway does at the moment. - -sub GetCurrentUser { -    my %args = ( Message     => undef, -                 CurrentUser => undef, -                 AuthLevel   => undef, -                 Ticket      => undef, -                 Queue       => undef, -                 Action      => undef, -                 @_ ); - -    # We don't need to do any external lookups -    my ( $Address, $Name ) = ParseSenderAddressFromHead( $args{'Message'}->head ); -    my $CurrentUser = RT::CurrentUser->new(); -    $CurrentUser->LoadByEmail($Address); - -    unless ( $CurrentUser->Id ) { -        $CurrentUser->LoadByName($Address); -    } - -    if ( $CurrentUser->Id ) { -        return ( $CurrentUser, 1 ); -    } -     - - -    # If the user can't be loaded, we may need to create one. Figure out the acl situation. -    my $unpriv = RT::Group->new($RT::SystemUser); -    $unpriv->LoadSystemInternalGroup('Unprivileged'); -    unless ( $unpriv->Id ) { -        $RT::Logger->crit( "Auth::MailFrom couldn't find the 'Unprivileged' internal group" ); -        return ( $args{'CurrentUser'}, -1 ); -    } - -    my $everyone = RT::Group->new($RT::SystemUser); -    $everyone->LoadSystemInternalGroup('Everyone'); -    unless ( $everyone->Id ) { -        $RT::Logger->crit( "Auth::MailFrom couldn't find the 'Everyone' internal group"); -        return ( $args{'CurrentUser'}, -1 ); -    } - -    # but before we do that, we need to make sure that the created user would have the right -    # to do what we're doing. -    if ( $args{'Ticket'} && $args{'Ticket'}->Id ) { -        # We have a ticket. that means we're commenting or corresponding -        if ( $args{'Action'} =~ /^comment$/i ) { - -            # check to see whether "Everybody" or "Unprivileged users" can comment on tickets -            unless ( $everyone->PrincipalObj->HasRight( -                                                      Object => $args{'Queue'}, -                                                      Right => 'CommentOnTicket' -                     ) -                     || $unpriv->PrincipalObj->HasRight( -                                                      Object => $args{'Queue'}, -                                                      Right => 'CommentOnTicket' -                     ) -              ) { -                return ( $args{'CurrentUser'}, 0 ); -            } -        } -        elsif ( $args{'Action'} =~ /^correspond$/i ) { - -            # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets -            unless ( $everyone->PrincipalObj->HasRight(Object => $args{'Queue'}, -                                                       Right  => 'ReplyToTicket' -                     ) -                     || $unpriv->PrincipalObj->HasRight( -                                                       Object => $args{'Queue'}, -                                                       Right  => 'ReplyToTicket' -                     ) -              ) { -                return ( $args{'CurrentUser'}, 0 ); -            } - -        } -        else { -            return ( $args{'CurrentUser'}, 0 ); -        } -    } - -    # We're creating a ticket -    elsif ( $args{'Queue'} && $args{'Queue'}->Id ) { - -        # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue -        unless ( $everyone->PrincipalObj->HasRight( Object => $args{'Queue'}, -                                                    Right  => 'CreateTicket' ) -                 || $unpriv->PrincipalObj->HasRight( Object => $args{'Queue'}, -                                                     Right  => 'CreateTicket' ) -          ) { -            return ( $args{'CurrentUser'}, 0 ); -        } - -    } - -    $CurrentUser = CreateUser( undef, $Address, $Name, $args{'Message'} ); - -    return ( $CurrentUser, 1 ); -} - -eval "require RT::Interface::Email::Auth::MailFrom_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Auth/MailFrom_Vendor.pm}); -eval "require RT::Interface::Email::Auth::MailFrom_Local"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Auth/MailFrom_Local.pm}); - -1; diff --git a/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm b/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm deleted file mode 100644 index f00e2d82b..000000000 --- a/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm +++ /dev/null @@ -1,63 +0,0 @@ -# BEGIN LICENSE BLOCK -#  -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -#  -# (Except where explictly superceded by other copyright notices) -#  -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -#  -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU -# General Public License for more details. -#  -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -#  -#  -# END LICENSE BLOCK -package RT::Interface::Email::Filter::SpamAssassin; - -use Mail::SpamAssassin; -my $spamtest = Mail::SpamAssassin->new(); - -sub GetCurrentUser { -    my $item = shift; -    my $status = $spamtest->check ($item); -    return (undef, 0) unless $status->is_spam(); -    eval { $status->rewrite_mail() }; -    if ($status->get_hits > $status->get_required_hits()*1.5) {  -        # Spammy indeed -        return (undef, -1); -    } -    return (undef, 0); -} - -=head1 NAME - -RT::Interface::Email::Filter::SpamAssassin - Spam filter for RT - -=head1 SYNOPSIS - -    @RT::MailPlugins = ("Filter::SpamAssassin", ...); - -=head1 DESCRIPTION - -This plugin checks to see if an incoming mail is spam (using -C<spamassassin>) and if so, rewrites its headers. If the mail is very -definitely spam - 1.5x more hits than required - then it is dropped on -the floor; otherwise, it is passed on as normal. - -=cut - -eval "require RT::Interface::Email::Filter::SpamAssassin_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Filter/SpamAssassin_Vendor.pm}); -eval "require RT::Interface::Email::Filter::SpamAssassin_Local"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Filter/SpamAssassin_Local.pm}); - -1; diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index 5097f54a4..6b5272848 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -1,214 +1,129 @@ -# 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 +## $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Interface/Web.pm,v 1.1 2002-08-12 06:17:08 ivan Exp $ +  ## Portions Copyright 2000 Tobias Brox <tobix@fsck.com> +## Copyright 1996-2002 Jesse Vincent <jesse@bestpractical.com>  ## This is a library of static subs to be used by the Mason web  ## interface to RT - -=head1 NAME - -RT::Interface::Web - -=begin testing - -use_ok(RT::Interface::Web); - -=end testing - -=cut - -  package RT::Interface::Web; -use strict; +# {{{ sub NewParser +=head2 NewParser - - -# {{{ sub NewApacheHandler  - -=head2 NewApacheHandler - -  Takes extra options to pass to HTML::Mason::ApacheHandler->new -  Returns a new Mason::ApacheHandler object +  Returns a new Mason::Parser object. Takes a param hash of things  +  that get passed to HTML::Mason::Parser. Currently hard coded to only +  take the parameter 'allow_globals'.  =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", +sub NewParser { +    my %args = ( +        allow_globals => undef,          @_      ); -    $ah->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 ); -     -    return ($ah); +    my $parser = new HTML::Mason::Parser( +        default_escape_flags => 'h', +        allow_globals        => $args{'allow_globals'} +    ); +    return ($parser);  }  # }}} -# {{{ sub NewCGIHandler  +# {{{ sub NewInterp -=head2 NewCGIHandler +=head2 NewInterp  -  Returns a new Mason::CGIHandler object +  Takes a paremeter hash. Needs a param called 'parser' which is a reference +  to an HTML::Mason::Parser. +  returns a new Mason::Interp object  =cut -sub NewCGIHandler { -    my %args = ( -        @_ -    ); - -    my $handler = HTML::Mason::CGIHandler->new( +sub NewInterp { +    my %params = (          comp_root                    => [              [ local    => $RT::MasonLocalComponentRoot ],              [ standard => $RT::MasonComponentRoot ]          ],          data_dir => "$RT::MasonDataDir", -        default_escape_flags => 'h', -        allow_globals        => [qw(%session)] +        @_      ); -   - -    $handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 ); +    #We allow recursive autohandlers to allow for RT auth. -    return ($handler); +    use HTML::Mason::Interp; +    my $interp = new HTML::Mason::Interp(%params);  } -# }}} +# }}} -# {{{ EscapeUTF8 +# {{{ sub NewApacheHandler  -=head2 EscapeUTF8 SCALARREF +=head2 NewApacheHandler -does a css-busting but minimalist escaping of whatever html you're passing in. +  Takes a Mason::Interp object +  Returns a new Mason::ApacheHandler object  =cut -sub EscapeUTF8  { -        my  $ref = shift; -        my $val = $$ref; -        use bytes; -        $val =~ s/&/&/g; -        $val =~ s/</</g;  -        $val =~ s/>/>/g; -        $val =~ s/\(/(/g; -        $val =~ s/\)/)/g; -        $val =~ s/"/"/g; -        $val =~ s/'/'/g; -        $$ref = $val; -        Encode::_utf8_on($$ref); - +sub NewApacheHandler { +    my $interp = shift; +    my $ah = new HTML::Mason::ApacheHandler( interp => $interp ); +    return ($ah);  }  # }}} -package HTML::Mason::Commands; -use strict; -use vars qw/$r $m %session/; - - -# {{{ loc +# {{{ sub NewMason11ApacheHandler -=head2 loc ARRAY +=head2 NewMason11ApacheHandler -loc is a nice clean global routine which calls $session{'CurrentUser'}->loc() -with whatever it's called with. If there is no $session{'CurrentUser'},  -it creates a temporary user, so we have something to get a localisation handle -through +  Returns a new Mason::ApacheHandler object  =cut -sub loc { - -    if ($session{'CurrentUser'} &&  -        UNIVERSAL::can($session{'CurrentUser'}, 'loc')){ -        return($session{'CurrentUser'}->loc(@_)); -    } -    else  { -        my $u = RT::CurrentUser->new($RT::SystemUser); -        return ($u->loc(@_)); -    } +sub NewMason11ApacheHandler { +        my %args = ( default_escape_flags => 'h', +                    allow_globals        => [%session], +        comp_root                    => [ +            [ local    => $RT::MasonLocalComponentRoot ], +            [ standard => $RT::MasonComponentRoot ] +        ], +        data_dir => "$RT::MasonDataDir", +        args_method => 'CGI' +    ); +    my $ah = new HTML::Mason::ApacheHandler(%args); +    return ($ah);  }  # }}} -# {{{ loc_fuzzy - -=head2 loc_fuzzy STRING -loc_fuzzy is for handling localizations of messages that may already -contain interpolated variables, typically returned from libraries -outside RT's control.  It takes the message string and extracts the -variable array automatically by matching against the candidate entries -inside the lexicon file. - -=cut -sub loc_fuzzy { -    my $msg  = shift; -     -    if ($session{'CurrentUser'} &&  -        UNIVERSAL::can($session{'CurrentUser'}, 'loc')){ -        return($session{'CurrentUser'}->loc_fuzzy($msg)); -    } -    else  { -        my $u = RT::CurrentUser->new($RT::SystemUser); -        return ($u->loc_fuzzy($msg)); -    } -}  # }}} +package HTML::Mason::Commands;  # {{{ sub Abort  # Error - calls Error and aborts  sub Abort { -    if ($session{'ErrorDocument'} &&  -        $session{'ErrorDocumentType'}) { -        $r->content_type($session{'ErrorDocumentType'}); -        $m->comp($session{'ErrorDocument'} , Why => shift); +    if ( $session{'ErrorDocument'} && $session{'ErrorDocumentType'} ) { +        SetContentType( $session{'ErrorDocumentType'} ); +        $m->comp( $session{'ErrorDocument'}, Why => shift );          $m->abort; -    }  -    else  { -        $m->comp("/Elements/Error" , Why => shift); +    } +    else { +        SetContentType('text/html'); +        $m->comp( "/Elements/Error", Why => shift );          $m->abort;      }  } @@ -220,7 +135,6 @@ sub Abort {  =head2 CreateTicket ARGS  Create a new ticket, using Mason's %ARGS.  returns @results. -  =cut  sub CreateTicket { @@ -244,45 +158,38 @@ sub CreateTicket {      my $starts = new RT::Date( $session{'CurrentUser'} );      $starts->Set( Format => 'unknown', Value => $ARGS{'Starts'} ); -    my @Requestors = split ( /\s*,\s*/, $ARGS{'Requestors'} ); -    my @Cc         = split ( /\s*,\s*/, $ARGS{'Cc'} ); -    my @AdminCc    = split ( /\s*,\s*/, $ARGS{'AdminCc'} ); +    my @Requestors = split ( /,/, $ARGS{'Requestors'} ); +    my @Cc         = split ( /,/, $ARGS{'Cc'} ); +    my @AdminCc    = split ( /,/, $ARGS{'AdminCc'} );      my $MIMEObj = MakeMIMEEntity(          Subject             => $ARGS{'Subject'},          From                => $ARGS{'From'},          Cc                  => $ARGS{'Cc'},          Body                => $ARGS{'Content'}, +        AttachmentFieldName => 'Attach'      ); -    if ($ARGS{'Attachments'}) { -        $MIMEObj->make_multipart; -        $MIMEObj->add_part($_) foreach values %{$ARGS{'Attachments'}}; -    } -      my %create_args = ( -        Queue           => $ARGS{'Queue'}, -        Owner           => $ARGS{'Owner'}, -        InitialPriority => $ARGS{'InitialPriority'}, -        FinalPriority   => $ARGS{'FinalPriority'}, -        TimeLeft        => $ARGS{'TimeLeft'}, -        TimeEstimated        => $ARGS{'TimeEstimated'}, -        TimeWorked      => $ARGS{'TimeWorked'}, +        Queue           => $ARGS{Queue}, +        Owner           => $ARGS{Owner}, +        InitialPriority => $ARGS{InitialPriority}, +        FinalPriority   => $ARGS{FinalPriority}, +        TimeLeft        => $ARGS{TimeLeft}, +        TimeWorked      => $ARGS{TimeWorked},          Requestor       => \@Requestors,          Cc              => \@Cc,          AdminCc         => \@AdminCc, -        Subject         => $ARGS{'Subject'}, -        Status          => $ARGS{'Status'}, +        Subject         => $ARGS{Subject}, +        Status          => $ARGS{Status},          Due             => $due->ISO,          Starts          => $starts->ISO,          MIMEObj         => $MIMEObj      ); -  foreach my $arg (%ARGS) { -        if ($arg =~ /^CustomField-(\d+)(.*?)$/) { -            next if ($arg =~ /-Magic$/); -            $create_args{"CustomField-".$1} = $ARGS{"$arg"}; -        } -    } + +    # we need to get any KeywordSelect-<integer> fields into %create_args.. +    grep { $_ =~ /^KeywordSelect-/ &&{ $create_args{$_} = $ARGS{$_} } } %ARGS; +      my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args);      unless ( $id && $Trans ) {          Abort($ErrMsg); @@ -309,7 +216,7 @@ sub CreateTicket {          }      } -    push ( @Actions, split("\n", $ErrMsg) ); +    push ( @Actions, $ErrMsg );      unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {          Abort( "No permission to view newly created ticket #"              . $Ticket->id . "." ); @@ -376,38 +283,80 @@ sub ProcessUpdateMessage {          my $Message = MakeMIMEEntity(              Subject             => $args{ARGSRef}->{'UpdateSubject'},              Body                => $args{ARGSRef}->{'UpdateContent'}, +            AttachmentFieldName => 'UpdateAttachment'          ); -        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 ) = $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 ); -        } +	## Check whether this was a refresh or not.   + +	# Match Correspondence or Comments. +        my $trans_flag = -2; +	my $trans_type = undef; +	my $orig_trans = $args{ARGSRef}->{'UpdateType'}; +        if ( $orig_trans =~ /^(private|public)$/ ) { +	    $trans_type = "Comment"; +        }elsif ( $orig_trans eq 'response' ) { +	    $trans_type = "Correspond"; +	} + +	# Do we have a transaction that we need to update on? session +	if( defined( $trans_type ) ){ +	    $trans_flag = 0; + +	    # Prepare a checksum. +	    # See perldoc -f unpack for example of this. +	    my $this_checksum = unpack("%32C*", $Message->body_as_string ) % 65535; + +	    # The above *could* generate duplicate checksums.  Crosscheck with +	    # the length. +	    my $this_length = length( $Message->body_as_string ); + +	    # Don't forget the ticket id. +	    my $this_id = $args{TicketObj}->id; + +	    # Check whether the previous transaction in the +	    # ticket is the same as the current transaction. +	    if( defined( $session{'prev_trans_type'} ) && defined( $session{'prev_trans_chksum'} ) && defined( $session{'prev_trans_length'} ) && defined( $session{'prev_trans_tickid'} ) ){ +		if( $session{'prev_trans_type'} eq $orig_trans && $session{'prev_trans_chksum'} == $this_checksum && $session{'prev_trans_length'} == $this_length && $session{'prev_trans_tickid'} == $this_id ){ +		    # Its the same as the previous transaction for this user. +		    $trans_flag = -1; +		} +	    } + +	    # Store them for next time. +	    $session{'prev_trans_type'} = $orig_trans; +	    $session{'prev_trans_chksum'} = $this_checksum; +	    $session{'prev_trans_length'} = $this_length; +	    $session{'prev_trans_tickid'} = $this_id; + +	    if( $trans_flag == -1 ){ +                push ( @{ $args{'Actions'} }, +"This appears to be a duplicate of your previous update (please do not refresh this page)" ); +	    } + + +            if ( $trans_type eq 'Comment' && $trans_flag >= 0 ) { +                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 ( $trans_type eq 'Correspond' && $trans_flag >= 0 ) { +                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.") -            ); +    "Update type was neither correspondence nor comment. Update not recorded" +                );          }      }  } @@ -433,66 +382,61 @@ sub MakeMIMEEntity {          Cc                  => undef,          Body                => undef,          AttachmentFieldName => undef, -        map Encode::encode_utf8($_), @_, +        @_      );      #Make the update content have no 'weird' newlines in it      $args{'Body'} =~ s/\r\n/\n/gs; -    my $Message; -    { -        # MIME::Head is not happy in utf-8 domain.  This only happens -        # when processing an incoming email (so far observed). -        no utf8; -        use bytes; -        $Message = MIME::Entity->build( -            Subject => $args{'Subject'} || "", -            From    => $args{'From'}, -            Cc      => $args{'Cc'}, -            Data    => [ $args{'Body'} ] -        ); -    } - -    my $cgi_object = $m->cgi_object; +    my $Message = MIME::Entity->build( +        Subject => $args{'Subject'} || "", +        From    => $args{'From'}, +        Cc      => $args{'Cc'}, +        Data    => [ $args{'Body'} ] +    ); -    if (my $filehandle = $cgi_object->upload( $args{'AttachmentFieldName'} ) ) { +    my $cgi_object = CGIObject(); +    if ( $cgi_object->param( $args{'AttachmentFieldName'} ) ) { +        my $cgi_filehandle = +          $cgi_object->upload( $args{'AttachmentFieldName'} ); +        use File::Temp qw(tempfile tempdir); -    use File::Temp qw(tempfile tempdir); +        #foreach my $filehandle (@filenames) { -    #foreach my $filehandle (@filenames) { +        # my ( $fh, $temp_file ) = tempfile(); -    my ( $fh, $temp_file ) = tempfile(); +        #$binmode $fh;    #thank you, windows -    binmode $fh;    #thank you, windows -    my ($buffer); -    while ( my $bytesread = read( $filehandle, $buffer, 4096 ) ) { -        print $fh $buffer; -    } +        # We're having trouble with tempfiles not getting created. Let's try it with  +        # a scalar instead -    my $uploadinfo = $cgi_object->uploadInfo($filehandle); +        my ( $buffer, @file ); -    # Prefer the cached name first over CGI.pm stringification. -    my $filename = $RT::Mason::CGI::Filename; -    $filename = "$filehandle" unless defined($filename); -                    -    $filename =~ s#^.*[\\/]##; +        while ( my $bytesread = read( $cgi_filehandle, $buffer, 4096 ) ) { +            push ( @file, $buffer ); +        } -    $Message->attach( -        Path     => $temp_file, -        Filename => $filename, -        Type     => $uploadinfo->{'Content-Type'}, -    ); -    close($fh); +        $RT::Logger->debug($file); +        my $filename = "$cgi_filehandle"; +        $filename =~ s#^(.*)/##; +        $filename =~ s#^(.*)\\##; +        my $uploadinfo = $cgi_object->uploadInfo($cgi_filehandle); +        $Message->attach( +            Data => \@file, + +            #Path     => $temp_file, +            Filename => $filename, +            Type     => $uploadinfo->{'Content-Type'} +        ); -    #   } +        #close($fh); +        #unlink($temp_file); +        #	}      } -      $Message->make_singlepart(); -    RT::I18N::SetMIMEEntityToUTF8($Message); # convert text parts into utf-8 -      return ($Message);  } @@ -541,9 +485,6 @@ sub ProcessSearchQuery {      elsif ( $args{ARGS}->{'GotoPage'} eq 'Prev' ) {          $session{'tickets'}->PrevPage;      } -    elsif ( $args{ARGS}->{'GotoPage'} > 0 ) { -        $session{'tickets'}->GotoPage( $args{ARGS}->{GotoPage} - 1 ); -    }      # }}} @@ -635,12 +576,8 @@ sub ProcessSearchQuery {      # }}}      # {{{ Limit Subject      if ( $args{ARGS}->{'ValueOfSubject'} ne '' ) { -            my $val = $args{ARGS}->{'ValueOfSubject'}; -        if ($args{ARGS}->{'SubjectOp'} =~ /like/) { -            $val = "%".$val."%"; -        }          $session{'tickets'}->LimitSubject( -            VALUE    => $val, +            VALUE    => $args{ARGS}->{'ValueOfSubject'},              OPERATOR => $args{ARGS}->{'SubjectOp'},          );      } @@ -648,59 +585,40 @@ sub ProcessSearchQuery {      # }}}          # {{{ Limit Dates      if ( $args{ARGS}->{'ValueOfDate'} ne '' ) { +          my $date = ParseDateToISO( $args{ARGS}->{'ValueOfDate'} );          $args{ARGS}->{'DateType'} =~ s/_Date$//; -        if ( $args{ARGS}->{'DateType'} eq 'Updated' ) { -            $session{'tickets'}->LimitTransactionDate( -                                            VALUE    => $date, -                                            OPERATOR => $args{ARGS}->{'DateOp'}, -            ); -        } -        else { -            $session{'tickets'}->LimitDate( FIELD => $args{ARGS}->{'DateType'}, -                                            VALUE => $date, -                                            OPERATOR => $args{ARGS}->{'DateOp'}, -            ); -        } +        $session{'tickets'}->LimitDate( +            FIELD    => $args{ARGS}->{'DateType'}, +            VALUE    => $date, +            OPERATOR => $args{ARGS}->{'DateOp'}, +        );      }      # }}}          # {{{ Limit Content -    if ( $args{ARGS}->{'ValueOfAttachmentField'} ne '' ) { -        my $val = $args{ARGS}->{'ValueOfAttachmentField'}; -        if ($args{ARGS}->{'AttachmentFieldOp'} =~ /like/) { -            $val = "%".$val."%"; -        } -        $session{'tickets'}->Limit( -            FIELD   => $args{ARGS}->{'AttachmentField'}, -            VALUE    => $val, -            OPERATOR => $args{ARGS}->{'AttachmentFieldOp'}, +    if ( $args{ARGS}->{'ValueOfContent'} ne '' ) { +        $session{'tickets'}->LimitContent( +            VALUE    => $args{ARGS}->{'ValueOfContent'}, +            OPERATOR => $args{ARGS}->{'ContentOp'},          );      }      # }}}    +    # {{{ Limit KeywordSelects - # {{{ Limit CustomFields - -    foreach my $arg ( keys %{ $args{ARGS} } ) { -        my $id; -        if ( $arg =~ /^CustomField(\d+)$/ ) { -            $id = $1; -        } -        else { -            next; -        } -        next unless ( $args{ARGS}->{$arg} ); - -        my $form = $args{ARGS}->{$arg}; -        my $oper = $args{ARGS}->{ "CustomFieldOp" . $id }; -        foreach my $value ( ref($form) ? @{$form} : ($form) ) { +    foreach my $KeywordSelectId ( +        map { /^KeywordSelect(\d+)$/; $1 } +        grep { /^KeywordSelect(\d+)$/; } keys %{ $args{ARGS} } +      ) +    { +        my $form = $args{ARGS}->{"KeywordSelect$KeywordSelectId"}; +        my $oper = $args{ARGS}->{"KeywordSelectOp$KeywordSelectId"}; +        foreach my $KeywordId ( ref($form) ? @{$form} : ($form) ) { +            next unless ($KeywordId);              my $quote = 1; -            if ($oper =~ /like/i) { -                $value = "%".$value."%"; -            } -            if ( $value =~ /^null$/i ) { +            if ( $KeywordId =~ /^null$/i ) {                  #Don't quote the string 'null'                  $quote = 0; @@ -709,16 +627,17 @@ sub ProcessSearchQuery {                  $oper = 'IS'     if ( $oper eq '=' );                  $oper = 'IS NOT' if ( $oper eq '!=' );              } -            $session{'tickets'}->LimitCustomField( CUSTOMFIELD => $id, -                                                   OPERATOR    => $oper, -                                                   QUOTEVALUE  => $quote, -                                                   VALUE       => $value ); +            $session{'tickets'}->LimitKeyword( +                KEYWORDSELECT => $KeywordSelectId, +                OPERATOR      => $oper, +                QUOTEVALUE    => $quote, +                KEYWORD       => $KeywordId +            );          }      }      # }}} -  }  # }}} @@ -735,7 +654,7 @@ Returns an ISO date and time in GMT  sub ParseDateToISO {      my $date = shift; -    my $date_obj = RT::Date->new($session{'CurrentUser'}); +    my $date_obj = new RT::Date($CurrentUser);      $date_obj->Set(          Format => 'unknown',          Value  => $date @@ -761,83 +680,173 @@ sub Config {  # {{{ sub ProcessACLChanges  sub ProcessACLChanges { +    my $ACLref  = shift;      my $ARGSref = shift; +    my @CheckACL = @$ACLref;      my %ARGS     = %$ARGSref;      my ( $ACL, @results ); +    # {{{ Add rights +    foreach $ACL (@CheckACL) { +        my ($Principal); -    foreach my $arg (keys %ARGS) { -        if ($arg =~ /GrantRight-(\d+)-(.*?)-(\d+)$/) { -            my $principal_id = $1; -            my $object_type = $2; -            my $object_id = $3; -            my $rights = $ARGS{$arg}; +        next unless ($ACL); -            my $principal = RT::Principal->new($session{'CurrentUser'}); -            $principal->Load($principal_id); +        # Parse out what we're really talking about.  +        if ( $ACL =~ /^(.*?)-(\d+)-(.*?)-(\d+)/ ) { +            my $PrincipalType = $1; +            my $PrincipalId   = $2; +            my $Scope         = $3; +            my $AppliesTo     = $4; -            my $obj; +            # {{{ Create an object called Principal +            # so we can do rights operations -            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);       +            if ( $PrincipalType eq 'User' ) { +                $Principal = new RT::User( $session{'CurrentUser'} ); +            } +            elsif ( $PrincipalType eq 'Group' ) { +                $Principal = new RT::Group( $session{'CurrentUser'} ); +            } +            else { +                Abort("$PrincipalType unknown principal type"); +            } -            } elsif ($object_type eq 'RT::System') { -                $obj = $RT::System; -            } else { -                push (@results, loc("System Error"). -                                loc("Rights could not be granted for [_1]", $object_type)); -                next; +            $Principal->Load($PrincipalId) +              || Abort("$PrincipalType $PrincipalId couldn't be loaded"); + +            # }}} + +            # {{{ load up an RT::ACL object with the same current vals of this ACL + +            my $CurrentACL = new RT::ACL( $session{'CurrentUser'} ); +            if ( $Scope eq 'Queue' ) { +                $CurrentACL->LimitToQueue($AppliesTo);              } +            elsif ( $Scope eq 'System' ) { +                $CurrentACL->LimitToSystem(); +            } + +            $CurrentACL->LimitPrincipalToType($PrincipalType); +            $CurrentACL->LimitPrincipalToId($PrincipalId); + +            # }}} + +            # {{{ Get the values of the select we're working with  +            # into an array. it will contain all the new rights that have  +            # been granted +            #Hack to turn the ACL returned into an array +            my @rights = +              ref( $ARGS{"GrantACE-$ACL"} ) eq 'ARRAY' +              ? @{ $ARGS{"GrantACE-$ACL"} } +              : ( $ARGS{"GrantACE-$ACL"} ); + +            # }}} + +            # {{{ Add any rights we need. -            my @rights = ref($ARGS{$arg}) eq 'ARRAY' ? @{$ARGS{$arg}} : ($ARGS{$arg});              foreach my $right (@rights) {                  next unless ($right); -                my ($val, $msg) = $principal->GrantRight(Object => $obj, Right => $right); -                push (@results, $msg); -            } -        } -       elsif ($arg =~ /RevokeRight-(\d+)-(.*?)-(\d+)-(.*?)$/) { -            my $principal_id = $1; -            my $object_type = $2; -            my $object_id = $3; -            my $right = $4; - -            my $principal = RT::Principal->new($session{'CurrentUser'}); -            $principal->Load($principal_id); -            next unless ($right); -            my $obj; - -            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"). -                                loc("Rights could not be revoked for [_1]", $object_type)); -                next; + +                #if the right that's been selected wasn't there before, add it. +                unless ( +                    $CurrentACL->HasEntry( +                        RightScope     => "$Scope", +                        RightName      => "$right", +                        RightAppliesTo => "$AppliesTo", +                        PrincipalType  => $PrincipalType, +                        PrincipalId    => $Principal->Id +                    ) +                  ) +                { + +                    #Add new entry to list of rights. +                    if ( $Scope eq 'Queue' ) { +                        my $Queue = new RT::Queue( $session{'CurrentUser'} ); +                        $Queue->Load($AppliesTo); +                        unless ( $Queue->id ) { +                            Abort("Couldn't find a queue called $AppliesTo"); +                        } + +                        my ( $val, $msg ) = $Principal->GrantQueueRight( +                            RightAppliesTo => $Queue->id, +                            RightName      => "$right" +                        ); + +                        if ($val) { +                            push ( @results, +                                "Granted right $right to " +                                  . $Principal->Name +                                  . " for queue " +                                  . $Queue->Name ); +                        } +                        else { +                            push ( @results, $msg ); +                        } +                    } +                    elsif ( $Scope eq 'System' ) { +                        my ( $val, $msg ) = $Principal->GrantSystemRight( +                            RightAppliesTo => $AppliesTo, +                            RightName      => "$right" +                        ); +                        if ($val) { +                            push ( @results, "Granted system right '$right' to " +                                  . $Principal->Name ); +                        } +                        else { +                            push ( @results, $msg ); +                        } +                    } +                }              } -            my ($val, $msg) = $principal->RevokeRight(Object => $obj, Right => $right); -            push (@results, $msg); + +            # }}}          } +    } +    # }}} Add rights -    } +    # {{{ remove any rights that have been deleted -    return (@results); +    my @RevokeACE = +      ref( $ARGS{"RevokeACE"} ) eq 'ARRAY'  +      ? @{ $ARGS{"RevokeACE"} } +      : ( $ARGS{"RevokeACE"} ); +    foreach my $aceid (@RevokeACE) { + +        my $right = new RT::ACE( $session{'CurrentUser'} ); +        $right->Load($aceid); +        next unless ( $right->id ); + +        my $phrase = "Revoked " +          . $right->PrincipalType . " " +          . $right->PrincipalObj->Name +          . "'s right to " +          . $right->RightName; + +        if ( $right->RightScope eq 'System' ) { +            $phrase .= ' across all queues.'; +        } +        else { +            $phrase .= ' for the queue ' . $right->AppliesToObj->Name . '.'; +        } +        my ( $val, $msg ) = $right->Delete(); +        if ($val) { +            push ( @results, $phrase ); +        } +        else { +            push ( @results, $msg ); +        }      } +    # }}} + +    return (@results); +} +  # }}}  # {{{ sub UpdateRecordObj @@ -855,7 +864,6 @@ sub UpdateRecordObject {          ARGSRef       => undef,          AttributesRef => undef,          Object        => undef, -        AttributePrefix => undef,          @_      ); @@ -864,94 +872,17 @@ sub UpdateRecordObject {      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); -} - -# }}} - -# {{{ Sub ProcessCustomFieldUpdates - -sub ProcessCustomFieldUpdates { -    my %args = ( -        CustomFieldObj => undef, -        ARGSRef        => undef, -        @_ -    ); - -    my $Object  = $args{'CustomFieldObj'}; -    my $ARGSRef = $args{'ARGSRef'}; -    my @attribs = qw( Name Type Description Queue SortOrder); -    my @results = UpdateRecordObject( -        AttributesRef => \@attribs, -        Object        => $Object, -        ARGSRef       => $ARGSRef -    ); +    foreach $attribute (@$attributes) { +        if ( ( defined $ARGSRef->{"$attribute"} ) +            and ( $ARGSRef->{"$attribute"} ne $object->$attribute() ) ) +        { +            $ARGSRef->{"$attribute"} =~ s/\r\n/\n/gs; -    if ( $ARGSRef->{ "CustomField-" . $Object->Id . "-AddValue-Name" } ) { - -        my ( $addval, $addmsg ) = $Object->AddValue( -            Name => -              $ARGSRef->{ "CustomField-" . $Object->Id . "-AddValue-Name" }, -            Description => $ARGSRef->{ "CustomField-" -                  . $Object->Id -                  . "-AddValue-Description" }, -            SortOrder => $ARGSRef->{ "CustomField-" -                  . $Object->Id -                  . "-AddValue-SortOrder" }, -        ); -        push ( @results, $addmsg ); -    } -    my @delete_values = ( -        ref $ARGSRef->{ 'CustomField-' . $Object->Id . '-DeleteValue' } eq -          'ARRAY' ) -      ? @{ $ARGSRef->{ 'CustomField-' . $Object->Id . '-DeleteValue' } } -      : ( $ARGSRef->{ 'CustomField-' . $Object->Id . '-DeleteValue' } ); -    foreach my $id (@delete_values) { -        next unless defined $id; -        my ( $err, $msg ) = $Object->DeleteValue($id); -        push ( @results, $msg ); +            my $method = "Set$attribute"; +            my ( $code, $msg ) = $object->$method( $ARGSRef->{"$attribute"} ); +            push @results, "$attribute: $msg"; +        }      }      return (@results);  } @@ -982,7 +913,6 @@ sub ProcessTicketBasics {        Subject        FinalPriority        Priority -      TimeEstimated        TimeWorked        TimeLeft        Status @@ -1004,7 +934,7 @@ sub ProcessTicketBasics {      );      # We special case owner changing, so we can use ForceOwnerChange -    if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner != $ARGSRef->{'Owner'} ) ) { +    if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner ne $ARGSRef->{'Owner'} ) ) {          my ($ChownType);          if ( $ARGSRef->{'ForceOwnerChange'} ) {              $ChownType = "Force"; @@ -1015,7 +945,7 @@ sub ProcessTicketBasics {          my ( $val, $msg ) =            $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType ); -        push ( @results, $msg ); +        push ( @results, "$msg" );      }      # }}} @@ -1025,142 +955,6 @@ sub ProcessTicketBasics {  # }}} -# {{{ Sub ProcessTicketCustomFieldUpdates - -sub ProcessTicketCustomFieldUpdates { -    my %args = ( -        ARGSRef => undef, -        @_ -    ); - -    my @results; - -    my $ARGSRef = $args{'ARGSRef'}; - -    # 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 =~ /^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 tickets -    foreach my $tick ( keys %custom_fields_to_mod ) { -        my $Ticket = RT::Ticket->new( $session{'CurrentUser'} ); -        $Ticket->Load($tick); - -        # For each custom field   -        foreach my $cf ( keys %{ $custom_fields_to_mod{$tick} } ) { - -	    my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'}); -	    $CustomFieldObj->LoadById($cf); - -            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, -                            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; -		    foreach my $old_cf (@{$cf_values->ItemsArrayRef}) { -			if (!$delete_flag and @values and $old_cf->Content eq $values[0]) { -			    shift @values; -			    next; -			} - -			$delete_flag ||= 1; -			$old_cf->Delete; -		    } - -		    # now add/replace extra things, if any -		    foreach my $value (@values) { -			my ( $val, $msg ) = $Ticket->AddCustomFieldValue( -			    Field => $cf, -			    Value => $value -			); -			push ( @results, $msg ); -		    } -		} -                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 ); @@ -1184,22 +978,18 @@ sub ProcessTicketWatchers {      foreach my $key ( keys %$ARGSRef ) { -        # {{{ Delete deletable watchers -        if ( ( $key =~ /^Ticket-DelWatcher-Type-(.*)-Principal-(\d+)$/ )  ) { -            my ( $code, $msg ) =  -                $Ticket->DeleteWatcher(PrincipalId => $2, -                                       Type => $1); +        # Delete deletable watchers +        if ( ( $key =~ /^DelWatcher(\d*)$/ ) and ( $ARGSRef->{$key} ) ) { +            my ( $code, $msg ) = $Ticket->DeleteWatcher($1);              push @results, $msg;          }          # Delete watchers in the simple style demanded by the bulk manipulator          elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc)$/ ) { -            my ( $code, $msg ) = $Ticket->DeleteWatcher( Type => $ARGSRef->{$key}, PrincipalId => $1 ); +            my ( $code, $msg ) = $Ticket->DeleteWatcher( $ARGSRef->{$key}, $1 );              push @results, $msg;          } -        # }}} -          # Add new wathchers by email address                elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )              and ( $key =~ /^WatcherTypeEmail(\d*)$/ ) ) @@ -1224,11 +1014,12 @@ sub ProcessTicketWatchers {          # Add new  watchers by owner          elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ ) -            and ( $key =~ /^Ticket-AddWatcher-Principal-(\d*)$/ ) ) { +            and ( $key =~ /^WatcherTypeUser(\d*)$/ ) ) +        {              #They're in this order because otherwise $1 gets clobbered :/              my ( $code, $msg ) = -              $Ticket->AddWatcher( Type => $ARGSRef->{$key}, PrincipalId => $1 ); +              $Ticket->AddWatcher( Type => $ARGSRef->{$key}, Owner => $1 );              push @results, $msg;          }      } @@ -1270,7 +1061,7 @@ sub ProcessTicketDates {      );      #Run through each field in this list. update the value if apropriate -    foreach my $field (@date_fields) { +    foreach $field (@date_fields) {          my ( $code, $msg );          my $DateObj = RT::Date->new( $session{'CurrentUser'} ); @@ -1307,9 +1098,11 @@ Returns an array of results messages.  =cut  sub ProcessTicketLinks { -    my %args = ( TicketObj => undef, -                 ARGSRef   => undef, -                 @_ ); +    my %args = ( +        TicketObj => undef, +        ARGSRef   => undef, +        @_ +    );      my $Ticket  = $args{'TicketObj'};      my $ARGSRef = $args{'ARGSRef'}; @@ -1325,9 +1118,11 @@ sub ProcessTicketLinks {              push @results,                "Trying to delete: Base: $base Target: $target  Type $type"; -            my ( $val, $msg ) = $Ticket->DeleteLink( Base   => $base, -                                                     Type   => $type, -                                                     Target => $target ); +            my ( $val, $msg ) = $Ticket->DeleteLink( +                Base   => $base, +                Type   => $type, +                Target => $target +            );              push @results, $msg; @@ -1338,23 +1133,26 @@ sub ProcessTicketLinks {      my @linktypes = qw( DependsOn MemberOf RefersTo );      foreach my $linktype (@linktypes) { -        if ( $ARGSRef->{ $Ticket->Id . "-$linktype" } ) { -            for my $luri ( split ( / /, $ARGSRef->{ $Ticket->Id . "-$linktype" } ) ) { -                $luri =~ s/\s*$//;    # Strip trailing whitespace -                my ( $val, $msg ) = $Ticket->AddLink( Target => $luri, -                                                      Type   => $linktype ); -                push @results, $msg; -            } + +        for my $luri ( split ( / /, $ARGSRef->{ $Ticket->Id . "-$linktype" } ) ) +        { +            $luri =~ s/\s*$//;    # Strip trailing whitespace +            my ( $val, $msg ) = $Ticket->AddLink( +                Target => $luri, +                Type   => $linktype +            ); +            push @results, $msg;          } -        if ( $ARGSRef->{ "$linktype-" . $Ticket->Id } ) { -            for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Ticket->Id } ) ) { -                my ( $val, $msg ) = $Ticket->AddLink( Base => $luri, -                                                      Type => $linktype ); +        for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Ticket->Id } ) ) +        { +            my ( $val, $msg ) = $Ticket->AddLink( +                Base => $luri, +                Type => $linktype +            ); -                push @results, $msg; -            } -        }  +            push @results, $msg; +        }      }      #Merge if we need to @@ -1369,9 +1167,121 @@ sub ProcessTicketLinks {  # }}} -eval "require RT::Interface::Web_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web_Vendor.pm}); -eval "require RT::Interface::Web_Local"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web_Local.pm}); +# {{{ sub ProcessTicketObjectKeywords + +=head2 ProcessTicketObjectKeywords ( TicketObj => $Ticket, ARGSRef => \%ARGS ); + +Returns an array of results messages. + +=cut + +sub ProcessTicketObjectKeywords { +    my %args = ( +        TicketObj => undef, +        ARGSRef   => undef, +        @_ +    ); + +    my $TicketObj = $args{'TicketObj'}; +    my $ARGSRef   = $args{'ARGSRef'}; + +    my (@results); + +    # {{{ set ObjectKeywords. + +    my $KeywordSelects = $TicketObj->QueueObj->KeywordSelects; + +    # iterate through all the keyword selects for this queue +    while ( my $KeywordSelect = $KeywordSelects->Next ) { + +        # {{{ do some setup + +        # if we have KeywordSelectMagic for this keywordselect: +        next +          unless +          defined $ARGSRef->{ 'KeywordSelectMagic' . $KeywordSelect->id }; + +        # Lets get a hash of the possible values to work with +        my $value = $ARGSRef->{ 'KeywordSelect' . $KeywordSelect->id } || []; + +        #lets get all those values in a hash. regardless of # of entries +        #we'll use this for adding and deleting keywords from this object. +        my %values = map { $_ => 1 } ref($value) ? @{$value} : ($value); + +        # Load up the ObjectKeywords for this KeywordSelect for this ticket +        my $ObjectKeys = $TicketObj->KeywordsObj( $KeywordSelect->id ); + +        # }}} +        # {{{ add new keywords + +        foreach my $key ( keys %values ) { + +            #unless the ticket has that keyword for that keyword select, +            unless ( $ObjectKeys->HasEntry($key) ) { + +                #Add the keyword +                my ( $result, $msg ) = $TicketObj->AddKeyword( +                    Keyword       => $key, +                    KeywordSelect => $KeywordSelect->id +                ); +                push ( @results, $msg ); +            } +        } + +        # }}} +        # {{{ Delete unused keywords + +        #redo this search, so we don't ask it to delete things that are already gone +        # such as when a single keyword select gets its value changed. +        $ObjectKeys = $TicketObj->KeywordsObj( $KeywordSelect->id ); + +        while ( my $TicketKey = $ObjectKeys->Next ) { + +            # if the hash defined above doesn\'t contain the keyword mentioned, +            unless ( $values{ $TicketKey->Keyword } ) { + +                #I'd really love to just call $keyword->Delete, but then  +                # we wouldn't get a transaction recorded +                my ( $result, $msg ) = $TicketObj->DeleteKeyword( +                    Keyword       => $TicketKey->Keyword, +                    KeywordSelect => $KeywordSelect->id +                ); +                push ( @results, $msg ); +            } +        } + +        # }}} +    } + +    #Iterate through the keyword selects for BulkManipulator style access +    while ( my $KeywordSelect = $KeywordSelects->Next ) { +        if ( $ARGSRef->{ "AddToKeywordSelect" . $KeywordSelect->Id } ) { + +            #Add the keyword +            my ( $result, $msg ) = $TicketObj->AddKeyword( +                Keyword => +                $ARGSRef->{ "AddToKeywordSelect" . $KeywordSelect->Id }, +                KeywordSelect => $KeywordSelect->id +            ); +            push ( @results, $msg ); +        } +        if ( $ARGSRef->{ "DeleteFromKeywordSelect" . $KeywordSelect->Id } ) { + +            #Delete the keyword +            my ( $result, $msg ) = $TicketObj->DeleteKeyword( +                Keyword => +                $ARGSRef->{ "DeleteFromKeywordSelect" . $KeywordSelect->Id }, +                KeywordSelect => $KeywordSelect->id +            ); +            push ( @results, $msg ); +        } +    } + +    # }}} + +    return (@results); +} + +# }}}  1; | 
