diff options
Diffstat (limited to 'rt/lib/RT/Interface')
| -rw-r--r-- | rt/lib/RT/Interface/CLI.pm | 8 | ||||
| -rwxr-xr-x | rt/lib/RT/Interface/Email.pm | 933 | ||||
| -rwxr-xr-x | rt/lib/RT/Interface/Email/Auth/GnuPG.pm | 6 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Email/Auth/MailFrom.pm | 11 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm | 6 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/REST.pm | 34 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Web.pm | 531 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Web/Handler.pm | 24 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Web/Menu.pm | 68 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Web/Menu/Item.pm | 86 | ||||
| -rwxr-xr-x | rt/lib/RT/Interface/Web/QueryBuilder.pm | 6 | ||||
| -rwxr-xr-x | rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm | 6 | ||||
| -rwxr-xr-x | rt/lib/RT/Interface/Web/Standalone.pm | 47 | 
13 files changed, 1096 insertions, 670 deletions
| diff --git a/rt/lib/RT/Interface/CLI.pm b/rt/lib/RT/Interface/CLI.pm index 8c9329508..a96551bef 100644 --- a/rt/lib/RT/Interface/CLI.pm +++ b/rt/lib/RT/Interface/CLI.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: @@ -55,7 +57,7 @@ BEGIN {      use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);      # set the version for version checking -    $VERSION = do { my @r = (q$Revision: 1.1.1.3 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker +    $VERSION = do { my @r = (q$Revision: 1.1.1.4 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker      @ISA         = qw(Exporter); diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm index efc4c268e..3179938a7 100755 --- a/rt/lib/RT/Interface/Email.pm +++ b/rt/lib/RT/Interface/Email.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: @@ -50,31 +52,32 @@ use Mail::Address;  use MIME::Entity;  use RT::EmailParser;  use File::Temp; +use UNIVERSAL::require;  BEGIN {      use Exporter (); -    use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); -     +    use vars qw ( @ISA @EXPORT_OK); +      # set the version for version checking -    $VERSION = do { my @r = (q$Revision: 1.1.1.6 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker -     -    @ISA         = qw(Exporter); -     +    our $VERSION = 2.0; + +    @ISA = qw(Exporter); +      # your exported package globals go here,      # as well as any optionally exported functions -    @EXPORT_OK   = qw( -              &CreateUser -              &GetMessageContent -              &CheckForLoops  -              &CheckForSuspiciousSender -              &CheckForAutoGenerated  -              &CheckForBounce  -              &MailError  -              &ParseCcAddressesFromHead -              &ParseSenderAddressFromHead  -              &ParseErrorsToAddressFromHead -              &ParseAddressFromHeader -              &Gateway); +    @EXPORT_OK = qw( +        &CreateUser +        &GetMessageContent +        &CheckForLoops +        &CheckForSuspiciousSender +        &CheckForAutoGenerated +        &CheckForBounce +        &MailError +        &ParseCcAddressesFromHead +        &ParseSenderAddressFromHead +        &ParseErrorsToAddressFromHead +        &ParseAddressFromHeader +        &Gateway);  } @@ -103,19 +106,18 @@ ok(require RT::Interface::Email);  =cut +# {{{ sub CheckForLoops -# {{{ sub CheckForLoops  - -sub CheckForLoops  { +sub CheckForLoops {      my $head = shift; -     +      #If this instance of RT sent it our, we don't want to take it in      my $RTLoop = $head->get("X-RT-Loop-Prevention") || ""; -    chomp ($RTLoop); #remove that newline -    if ($RTLoop eq "$RT::rtname") { -	return (1); +    chomp($RTLoop);    #remove that newline +    if ( $RTLoop eq "$RT::rtname" ) { +        return (1);      } -     +      # TODO: We might not trap the case where RT instance A sends a mail      # to RT instance B which sends a mail to ...      return (undef); @@ -129,23 +131,24 @@ sub CheckForSuspiciousSender {      my $head = shift;      #if it's from a postmaster or mailer daemon, it's likely a bounce. -     +      #TODO: better algorithms needed here - there is no standards for      #bounces, so it's very difficult to separate them from anything      #else.  At the other hand, the Return-To address is only ment to be      #used as an error channel, we might want to put up a separate      #Return-To address which is treated differently. -     +      #TODO: search through the whole email and find the right Ticket ID. -    my ($From, $junk) = ParseSenderAddressFromHead($head); -     -    if (($From =~ /^mailer-daemon\@/i) or -	($From =~ /^postmaster\@/i)){ -	return (1); -	 +    my ( $From, $junk ) = ParseSenderAddressFromHead($head); + +    if (   ( $From =~ /^mailer-daemon\@/i ) +        or ( $From =~ /^postmaster\@/i ) ) +    { +        return (1); +      } -     +      return (undef);  } @@ -155,15 +158,15 @@ sub CheckForSuspiciousSender {  # {{{ sub CheckForAutoGenerated  sub CheckForAutoGenerated {      my $head = shift; -     -    my $Precedence = $head->get("Precedence") || "" ; -    if ($Precedence =~ /^(bulk|junk)/i) { -	return (1); + +    my $Precedence = $head->get("Precedence") || ""; +    if ( $Precedence =~ /^(bulk|junk)/i ) { +        return (1);      } -     +      # First Class mailer uses this as a clue.      my $FCJunk = $head->get("X-FC-Machinegenerated") || ""; -    if ($FCJunk =~ /^true/i) { +    if ( $FCJunk =~ /^true/i ) {          return (1);      } @@ -175,9 +178,9 @@ sub CheckForAutoGenerated {  # {{{ sub CheckForBounce  sub CheckForBounce {      my $head = shift; -    -    my $ReturnPath = $head->get("Return-path") || "" ; -    return ($ReturnPath =~ /<>/); + +    my $ReturnPath = $head->get("Return-path") || ""; +    return ( $ReturnPath =~ /<>/ );  }  # }}} @@ -195,11 +198,12 @@ Returns false, otherwise.  sub IsRTAddress {      my $address = shift || ''; -    # Example: the following rule would tell RT not to Cc  +    # Example: the following rule would tell RT not to Cc      #   "tickets@noc.example.com" -    if ( defined($RT::RTAddressRegexp) && -                       $address =~ /$RT::RTAddressRegexp/i ) { -        return(1); +    if ( defined($RT::RTAddressRegexp) +        && $address =~ /$RT::RTAddressRegexp/i ) +    { +        return (1);      } else {          return (undef);      } @@ -217,56 +221,63 @@ Returns the same array with any IsRTAddress()es weeded out.  =cut  sub CullRTAddresses { -    return (grep { IsRTAddress($_) } @_); +    return grep !IsRTAddress($_), @_;  }  # }}} -# {{{ sub MailError  +# {{{ sub MailError  sub MailError { -    my %args = (To => $RT::OwnerEmail, -		Bcc => undef, -		From => $RT::CorrespondAddress, -		Subject => 'There has been an error', -		Explanation => 'Unexplained error', -		MIMEObj => undef, -        Attach => undef, -		LogLevel => 'crit', -		@_); - - -    $RT::Logger->log(level => $args{'LogLevel'},  -		     message => $args{'Explanation'} -		    ); -    my $entity = MIME::Entity->build( Type  =>"multipart/mixed", -				      From => $args{'From'}, -				      Bcc => $args{'Bcc'}, -				      To => $args{'To'}, -				      Subject => $args{'Subject'}, -				      Precedence => 'bulk', -				      'X-RT-Loop-Prevention' => $RT::rtname, -				    ); - -    $entity->attach(  Data => $args{'Explanation'}."\n"); -     +    my %args = ( +        To          => $RT::OwnerEmail, +        Bcc         => undef, +        From        => $RT::CorrespondAddress, +        Subject     => 'There has been an error', +        Explanation => 'Unexplained error', +        MIMEObj     => undef, +        Attach      => undef, +        LogLevel    => 'crit', +        @_ +    ); + +    $RT::Logger->log( +        level   => $args{'LogLevel'}, +        message => $args{'Explanation'} +    ); +    # the colons are necessary to make ->build include non-standard headers +    my $entity = MIME::Entity->build( +        Type                   => "multipart/mixed", +        From                   => $args{'From'}, +        Bcc                    => $args{'Bcc'}, +        To                     => $args{'To'}, +        Subject                => $args{'Subject'}, +        'Precedence:'             => 'bulk', +        'X-RT-Loop-Prevention:' => $RT::rtname, +        'In-Reply-To:'          => $args{'MIMEObj'} ? $args{'MIMEObj'}->head->get('Message-Id') : undef +    ); + +    $entity->attach( Data => $args{'Explanation'} . "\n" ); +      my $mimeobj = $args{'MIMEObj'};      if ($mimeobj) {          $mimeobj->sync_headers();          $entity->add_part($mimeobj);      } -    -    if ($args{'Attach'}) { -        $entity->attach(Data => $args{'Attach'}, Type => 'message/rfc822'); + +    if ( $args{'Attach'} ) { +        $entity->attach( Data => $args{'Attach'}, Type => 'message/rfc822' );      } -    if ($RT::MailCommand eq 'sendmailpipe') { -        open (MAIL, "|$RT::SendmailPath $RT::SendmailBounceArguments $RT::SendmailArguments") || return(0); +    if ( $RT::MailCommand eq 'sendmailpipe' ) { +        open( MAIL, +            "|$RT::SendmailPath $RT::SendmailBounceArguments $RT::SendmailArguments" +            ) +            || return (0);          print MAIL $entity->as_string;          close(MAIL); -    } -    else { -    	$entity->send($RT::MailCommand, $RT::MailParams); +    } else { +        $entity->send( $RT::MailCommand, $RT::MailParams );      }  } @@ -275,37 +286,38 @@ sub MailError {  # {{{ Create User  sub CreateUser { -    my ($Username, $Address, $Name, $ErrorsTo, $entity) = @_; +    my ( $Username, $Address, $Name, $ErrorsTo, $entity ) = @_;      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' -                      ); -     +    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) { + +        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' -                     ); + +        unless ( $NewUser->Id ) { +            MailError( +                To          => $ErrorsTo, +                Subject     => "User could not be created", +                Explanation => +                    "User creation failed in mailgateway: $Message", +                MIMEObj  => $entity, +                LogLevel => 'crit' +            );          }      } @@ -313,21 +325,25 @@ sub CreateUser {      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' -                     ); +    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  +# {{{ ParseCcAddressesFromHead  =head2 ParseCcAddressesFromHead HASHREF @@ -337,32 +353,34 @@ headers b<except> the current Queue\'s email addresses, the CurrentUser\'s  email address  and anything that the configuration sub RT::IsRTAddress matches.  =cut -   +  sub ParseCcAddressesFromHead { -    my %args = ( Head => undef, -		 QueueObj => undef, -		 CurrentUser => undef, -		 @_ ); -     +    my %args = ( +        Head        => undef, +        QueueObj    => undef, +        CurrentUser => undef, +        @_ +    ); +      my (@Addresses); -         -    my @ToObjs = Mail::Address->parse($args{'Head'}->get('To')); -    my @CcObjs = Mail::Address->parse($args{'Head'}->get('Cc')); -     -    foreach my $AddrObj (@ToObjs, @CcObjs) { -	my $Address = $AddrObj->address; -	$Address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress($Address); - 	next if ($args{'CurrentUser'}->EmailAddress =~ /^\Q$Address\E$/i); -	next if ($args{'QueueObj'}->CorrespondAddress =~ /^\Q$Address\E$/i); -	next if ($args{'QueueObj'}->CommentAddress =~ /^\Q$Address\E$/i); -	next if (RT::EmailParser->IsRTAddress($Address)); -	 -	push (@Addresses, $Address); + +    my @ToObjs = Mail::Address->parse( $args{'Head'}->get('To') ); +    my @CcObjs = Mail::Address->parse( $args{'Head'}->get('Cc') ); + +    foreach my $AddrObj ( @ToObjs, @CcObjs ) { +        my $Address = $AddrObj->address; +        $Address = $args{'CurrentUser'} +            ->UserObj->CanonicalizeEmailAddress($Address); +        next if ( $args{'CurrentUser'}->EmailAddress   =~ /^\Q$Address\E$/i ); +        next if ( $args{'QueueObj'}->CorrespondAddress =~ /^\Q$Address\E$/i ); +        next if ( $args{'QueueObj'}->CommentAddress    =~ /^\Q$Address\E$/i ); +        next if ( RT::EmailParser->IsRTAddress($Address) ); + +        push( @Addresses, $Address );      }      return (@Addresses);  } -  # }}}  # {{{ ParseSenderAdddressFromHead @@ -376,12 +394,14 @@ of the From (evaluated in order of Reply-To:, From:, Sender)  sub ParseSenderAddressFromHead {      my $head = shift; +      #Figure out who's sending this message. -    my $From = $head->get('Reply-To') ||  -      $head->get('From') ||  -	$head->get('Sender'); -    return (ParseAddressFromHeader($From)); +    my $From = $head->get('Reply-To') +        || $head->get('From') +        || $head->get('Sender'); +    return ( ParseAddressFromHeader($From) );  } +  # }}}  # {{{ ParseErrorsToAdddressFromHead @@ -396,18 +416,22 @@ From:, Sender)  sub ParseErrorsToAddressFromHead {      my $head = shift; +      #Figure out who's sending this message. -    foreach my $header ('Return-path', 'Errors-To' , 'Reply-To', 'From', 'Sender' ) { -	# If there's a header of that name -	my $headerobj = $head->get($header); -	if ($headerobj) { -		my ($addr, $name ) = ParseAddressFromHeader($headerobj); -		# If it's got actual useful content... -		return ($addr) if ($addr); -	} +    foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) { + +        # If there's a header of that name +        my $headerobj = $head->get($header); +        if ($headerobj) { +            my ( $addr, $name ) = ParseAddressFromHeader($headerobj); + +            # If it's got actual useful content... +            return ($addr) if ($addr); +        }      }  } +  # }}}  # {{{ ParseAddressFromHeader @@ -418,31 +442,29 @@ Takes an address from $head->get('Line') and returns a tuple: user@host, friendl  =cut - -sub ParseAddressFromHeader{ +sub ParseAddressFromHeader {      my $Addr = shift; -     -    # Perl 5.8.0 breaks when doing regex matches on utf8 -    Encode::_utf8_off($Addr) if $] == 5.008; + +    # Some broken mailers send:  ""Vincent, Jesse"" <jesse@fsck.com>. Hate +    $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;                                                                                                                                                        my @Addresses = Mail::Address->parse($Addr); -     -    my $AddrObj = $Addresses[0]; -    unless (ref($AddrObj)) { -	return(undef,undef); +    my ($AddrObj) = grep ref $_, @Addresses; +    unless ( $AddrObj ) { +        return ( undef, undef );      } -  -    my $Name =  ($AddrObj->phrase || $AddrObj->comment || $AddrObj->address); -     + +    my $Name = ( $AddrObj->phrase || $AddrObj->comment || $AddrObj->address ); +      #Lets take the from and load a user object.      my $Address = $AddrObj->address; -    return ($Address, $Name); +    return ( $Address, $Name );  } -# }}} -# {{{ sub ParseTicketId  +# }}} +# {{{ sub ParseTicketId  sub ParseTicketId {      my $Subject = shift; @@ -454,15 +476,13 @@ sub ParseTicketId {          my $id = $1;          $RT::Logger->debug("Found a ticket ID. It's $id");          return ($id); -    } -    else { +    } else {          return (undef);      }  }  # }}} -  =head2 Gateway ARGSREF @@ -501,64 +521,82 @@ Returns:  sub Gateway {      my $argsref = shift; +    my %args    = ( +        action  => 'correspond', +        queue   => '1', +        ticket  => undef, +        message => undef, +        %$argsref +    ); -    my %args = %$argsref; - -    # Set some reasonable defaults -    $args{'action'} ||= 'correspond'; -    $args{'queue'}  ||= '1'; +    my $SystemTicket; +    my $Right;      # Validate the action -    my ($status, @actions) = IsCorrectAction( $args{'action'} ); -    unless ( $status ) { - -        # Can't safely loc this. What object do we loc around? -        $RT::Logger->crit("Mail gateway called with an invalid action paramenter '".$actions[0]."' for queue '".$args{'queue'}."'"); - -        return ( -75, "Invalid 'action' parameter", undef ); +    my ( $status, @actions ) = IsCorrectAction( $args{'action'} ); +    unless ($status) { +        return ( +            -75, +            "Invalid 'action' parameter " +                . $actions[0] +                . " for queue " +                . $args{'queue'}, +            undef +        );      }      my $parser = RT::EmailParser->new(); +    $parser->SmartParseMIMEEntityFromScalar( Message => $args{'message'} ); +    my $Message = $parser->Entity(); -    $parser->SmartParseMIMEEntityFromScalar( Message => $args{'message'}); - -    if (!$parser->Entity()) { +    unless ($Message) {          MailError(              To          => $RT::OwnerEmail,              Subject     => "RT Bounce: Unparseable message",              Explanation => "RT couldn't process the message below", -            Attach     => $args{'message'} +            Attach      => $args{'message'}          ); -        return(0,"Failed to parse this message. Something is likely badly wrong with the message"); +        return ( 0, +            "Failed to parse this message. Something is likely badly wrong with the message" +        );      } -    my $Message = $parser->Entity(); -    my $head    = $Message->head; - -    my ( $CurrentUser, $AuthStat, $error ); - -    # Initalize AuthStat so comparisons work correctly -    $AuthStat = -9999999; +    my $head = $Message->head;      my $ErrorsTo = ParseErrorsToAddressFromHead($head);      my $MessageId = $head->get('Message-ID') -      || "<no-message-id-" . time . rand(2000) . "\@.$RT::Organization>"; +        || "<no-message-id-" . time . rand(2000) . "\@.$RT::Organization>";      #Pull apart the subject line      my $Subject = $head->get('Subject') || '';      chomp $Subject; +     +    # {{{ Lets check for mail loops of various sorts. +    my ($should_store_machine_generated_message, $IsALoop, $result); +    ( $should_store_machine_generated_message, $ErrorsTo, $result, $IsALoop ) = +      _HandleMachineGeneratedMail( +        Message  => $Message, +        ErrorsTo => $ErrorsTo, +        Subject  => $Subject, +        MessageId => $MessageId +    ); + +    # Do not pass loop messages to MailPlugins, to make sure the loop +    # is broken, unless $RT::StoreLoops is set. +    if ($IsALoop && !$should_store_machine_generated_message) { +        return ( 0, $result, undef ); +    }      $args{'ticket'} ||= ParseTicketId($Subject); -    my $SystemTicket; -    my $Right = 'CreateTicket'; -    if ( $args{'ticket'} ) { -        $SystemTicket = RT::Ticket->new($RT::SystemUser); -        $SystemTicket->Load( $args{'ticket'} ); -	# if there's an existing ticket, this must be a reply -	$Right = 'ReplyToTicket'; +    $SystemTicket = RT::Ticket->new($RT::SystemUser); +    $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ; +    if ( $SystemTicket->id ) { +        $Right = 'ReplyToTicket'; +    } else { +        $Right = 'CreateTicket';      }      #Set up a queue object @@ -566,190 +604,117 @@ sub Gateway {      $SystemQueueObj->Load( $args{'queue'} );      # We can safely have no queue of we have a known-good ticket -    unless ( $args{'ticket'} || $SystemQueueObj->id ) { +    unless ( $SystemTicket->id || $SystemQueueObj->id ) {          return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );      } -    # Authentication Level +    # Authentication Level ($AuthStat)      # -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 +    my ( $CurrentUser, $AuthStat, $error ); + +    # Initalize AuthStat so comparisons work correctly +    $AuthStat = -9999999;      push @RT::MailPlugins, "Auth::MailFrom" unless @RT::MailPlugins; -    # Since this needs loading, no matter what +    # if plugin returns AuthStat -2 we skip action +    # NOTE: this is experimental API and it would be changed +    my %skip_action = (); +    # Since this needs loading, no matter what      foreach (@RT::MailPlugins) { -        my $Code; -        my $NewAuthStat; +        my ($Code, $NewAuthStat);          if ( ref($_) eq "CODE" ) {              $Code = $_; -        } -        else { -            $_ = "RT::Interface::Email::".$_ unless $_ =~ /^RT::Interface::Email::/; -            eval "require $_;"; -            if ($@) { -                $RT::Logger->crit("Couldn't load module '$_': $@"); -                next; -            } +        } else { +            my $Class = $_; +            $Class = "RT::Interface::Email::" . $Class +                unless $Class =~ /^RT::Interface::Email::/; +            $Class->require or +                do { $RT::Logger->error("Couldn't load $Class: $@"); next }; +              no strict 'refs'; -            if ( !defined( $Code = *{ $_ . "::GetCurrentUser" }{CODE} ) ) { -                $RT::Logger->crit("No GetCurrentUser code found in $_ module"); +            unless ( defined( $Code = *{ $Class . "::GetCurrentUser" }{CODE} ) ) { +                $RT::Logger->crit( "No 'GetCurrentUser' function found in '$Class' module");                  next;              }          } -	foreach my $action ( @actions ) { - +        foreach my $action (@actions) {              ( $CurrentUser, $NewAuthStat ) = $Code->( -                Message     => $Message, +                Message       => $Message,                  RawMessageRef => \$args{'message'}, -                CurrentUser => $CurrentUser, -                AuthLevel   => $AuthStat, -                Action      => $action, -                Ticket      => $SystemTicket, -                Queue       => $SystemQueueObj +                CurrentUser   => $CurrentUser, +                AuthLevel     => $AuthStat, +                Action        => $action, +                Ticket        => $SystemTicket, +                Queue         => $SystemQueueObj              ); - -            # If a module returns a "-1" then we discard the ticket, so. -            $AuthStat = -1 if $NewAuthStat == -1; - -            # You get the highest level of authentication you were assigned. -            $AuthStat = $NewAuthStat if $NewAuthStat > $AuthStat; +# You get the highest level of authentication you were assigned, unless you get the magic -1 +# If a module returns a "-1" then we discard the ticket, so. +            $AuthStat = $NewAuthStat +                if ( $NewAuthStat > $AuthStat or $NewAuthStat == -1 or $NewAuthStat == -2 );              last if $AuthStat == -1; -	} +            $skip_action{$action}++ if $AuthStat == -2; +        } + +        # strip actions we should skip +        @actions = grep !$skip_action{$_}, @actions if $AuthStat == -2; +        last unless @actions;          last if $AuthStat == -1;      } -      # {{{ If authentication fails and no new user was created, get out. -    if ( !$CurrentUser or !$CurrentUser->Id or $AuthStat == -1 ) { +    if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {          # If the plugins refused to create one, they lose.          unless ( $AuthStat == -1 ) { - -            # Notify the RT Admin of the failure. -            # XXX Should this be configurable? -            MailError( -                To          => $RT::OwnerEmail, -                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 this email ($ErrorsTo). - -You might need to grant 'Everyone' the right '$Right' for the -queue @{[$args{'queue'}]}. - -EOT -                MIMEObj  => $Message, -                LogLevel => 'error' +            _NoAuthorizedUserFound( +                Right     => $Right, +                Message   => $Message, +                Requestor => $ErrorsTo, +                Queue     => $args{'queue'}              ); -            # Also notify the requestor that his request has been dropped. -            MailError( -                To          => $ErrorsTo, -                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. - -EOT -                MIMEObj  => $Message, -                LogLevel => 'error' -            );          }          return ( 0, "Could not load a valid user", undef );      } -    # }}} - -    # {{{ Lets check for mail loops of various sorts. -    my $IsBounce = CheckForBounce($head); - -    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 ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) { -        $SquelchReplies = 1; -        $ErrorsTo       = $RT::OwnerEmail; -    } - -    # }}} - -    # {{{ Drop it if it's disallowed +    # If we got a user, but they don't have the right to say things      if ( $AuthStat == 0 ) {          MailError(              To          => $ErrorsTo,              Subject     => "Permission Denied", -            Explanation => "You do not have permission to communicate with RT", -            MIMEObj     => $Message +            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') +        return ( +            0, +            "$ErrorsTo tried to submit a message to " +                . $args{'Queue'} +                . " without permission.", +            undef          ); -        $head->delete('RT-Squelch-Replies-To');      } -    if ($SquelchReplies) { -        # Squelch replies to the sender, and also leave a clue to -        # allow us to squelch ALL outbound messages. This way we -        # can punt the logic of "what to do when we get a bounce" -        # to the scrip. We might want to notify nobody. Or just -        # the RT Owner. Or maybe all Privileged watchers. -        my ( $Sender, $junk ) = ParseSenderAddressFromHead($head); -        $head->add( 'RT-Squelch-Replies-To', $Sender ); -        $head->add( 'RT-DetectedAutoGenerated', 'true' ); +    unless ($should_store_machine_generated_message) { +        return ( 0, $result, undef );      } - -    # }}} +     +    # if plugin's updated SystemTicket then update arguments +    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;      my $Ticket = RT::Ticket->new($CurrentUser); -    # {{{ If we don't have a ticket Id, we're creating a new ticket -    if ( (!$SystemTicket || !$SystemTicket->Id) &&  -           grep /^(comment|correspond)$/, @actions ) { - -        # {{{ Create a new ticket +    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions ) +    {          my @Cc;          my @Requestors = ( $CurrentUser->id ); @@ -772,126 +737,252 @@ EOT          if ( $id == 0 ) {              MailError(                  To          => $ErrorsTo, -                Subject     => "Ticket creation failed", +                Subject     => "Ticket creation failed: $Subject",                  Explanation => $ErrStr,                  MIMEObj     => $Message              ); -            $RT::Logger->error("Create failed: $id / $Transaction / $ErrStr "); -            return ( 0, "Ticket creation failed", $Ticket ); +            return ( 0, "Ticket creation failed: $ErrStr", $Ticket );          } -	# strip comments&corresponds from the actions we don't need record twice -	@actions = grep !/^(comment|correspond)$/, @actions; -	$args{'ticket'} = $id; -        # }}} -    } +        # strip comments&corresponds from the actions we don't need +        # to record them if we've created the ticket just now +        @actions = grep !/^(comment|correspond)$/, @actions; +        $args{'ticket'} = $id; -    $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 ); +    } elsif ( $args{'ticket'} ) { + +        $Ticket->Load( $args{'ticket'} ); +        unless ( $Ticket->Id ) { +            my $error = "Could not find a ticket with id " . $args{'ticket'}; +            MailError( +                To          => $ErrorsTo, +                Subject     => "Message not recorded: $Subject", +                Explanation => $error, +                MIMEObj     => $Message +            ); + +            return ( 0, $error ); +        } +        $args{'ticket'} = $Ticket->id; +    } else { +        return ( 1, "Success", $Ticket );      }      # }}} -    foreach my $action( @actions ) { +    foreach my $action (@actions) { +          #   If the action is comment, add a comment. -        if ( $action =~ /^(comment|correspond)$/i ) { -            my ( $status, $msg ); -            if ( $action =~ /^correspond$/i ) { -                ( $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 ); -            } -        } -        elsif ($RT::UnsafeEmailCommands && $action =~ /^take$/i ) { -            my ( $status, $msg ) = $Ticket->SetOwner( $CurrentUser->id ); -            unless ($status) { -     -                #Warn the sender that we couldn't actually submit the comment. -                MailError( -                    To          => $ErrorsTo, -                    Subject     => "Ticket not taken", -                    Explanation => $msg, -                    MIMEObj     => $Message -                ); -                return ( 0, "Ticket not taken", $Ticket ); -            } -        } -        elsif ( $RT::UnsafeEmailCommands && $action =~ /^resolve$/i ) { -            my ( $status, $msg ) = $Ticket->SetStatus( 'resolved' ); +        if ( $action =~ /^(?:comment|correspond)$/i ) { +            my $method = ucfirst lc $action; +            my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );              unless ($status) { +                  #Warn the sender that we couldn't actually submit the comment.                  MailError(                      To          => $ErrorsTo, -                    Subject     => "Ticket not resolved", +                    Subject     => "Message not recorded: $Subject",                      Explanation => $msg,                      MIMEObj     => $Message                  ); -                return ( 0, "Ticket not resolved", $Ticket ); +                return ( 0, "Message not recorded: $msg", $Ticket );              } +        } elsif ($RT::UnsafeEmailCommands) { +            my ( $status, $msg ) = _RunUnsafeAction( +                Action      => $action, +                ErrorsTo    => $ErrorsTo, +                Message     => $Message, +                Ticket      => $Ticket, +                CurrentUser => $CurrentUser, +            ); +            return ($status, $msg, $Ticket) unless $status == 1;          } -     -        else { -     -            #Return mail to the sender with an error +    } +    return ( 1, "Success", $Ticket ); +} + +sub _RunUnsafeAction { +    my %args = ( +        Action      => undef, +        ErrorsTo    => undef, +        Message     => undef, +        Ticket      => undef, +        CurrentUser => undef, +        @_ +    ); + +    if ( $args{'Action'} =~ /^take$/i ) { +        my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{'CurrentUser'}->id ); +        unless ($status) {              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 +                To          => $args{'ErrorsTo'}, +                Subject     => "Ticket not taken", +                Explanation => $msg, +                MIMEObj     => $args{'Message'}              ); -            $RT::Logger->crit( $args{'action'} . " type unknown for $MessageId" ); -            return ( -                -75, -                "Configuration error: " -                  . $args{'action'} -                  . " not a recognized action", -                $Ticket +            return ( 0, "Ticket not taken" ); +        } +    } elsif ( $args{'Action'} =~ /^resolve$/i ) { +        my ( $status, $msg ) = $args{'Ticket'}->SetStatus('resolved'); +        unless ($status) { + +            #Warn the sender that we couldn't actually submit the comment. +            MailError( +                To          => $args{'ErrorsTo'}, +                Subject     => "Ticket not resolved", +                Explanation => $msg, +                MIMEObj     => $args{'Message'}              ); -     +            return ( 0, "Ticket not resolved" );          } +    } else { +        return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} );      } +    return ( 1, "Success" ); +} -    return ( 1, "Success", $Ticket ); +=head2 _NoAuthorizedUserFound + +Emails the RT Owner and the requestor when the auth plugins return "No auth user found" + +=cut + +sub _NoAuthorizedUserFound { +    my %args = ( +        Right     => undef, +        Message   => undef, +        Requestor => undef, +        Queue     => undef, +        @_ +    ); + +    # Notify the RT Admin of the failure. +    MailError( +        To          => $RT::OwnerEmail, +        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 this email (@{[$args{Requestor}]}). + +You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the +queue @{[$args{'Queue'}]}. + +EOT +        MIMEObj  => $args{'Message'}, +        LogLevel => 'error' +    ); + +    # Also notify the requestor that his request has been dropped. +    if ($args{'Requestor'} ne $RT::OwnerEmail) { +    MailError( +        To          => $args{'Requestor'}, +        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. + +EOT +        MIMEObj  => $args{'Message'}, +        LogLevel => 'error' +    ); +    }  } -sub IsCorrectAction -{ -	my $action = shift; -	my @actions = split /-/, $action; -	foreach ( @actions ) { -		return (0, $_) unless /^(?:comment|correspond|take|resolve)$/; -	} -	return (1, @actions); +=head2 _HandleMachineGeneratedMail + +Takes named params: +    Message +    ErrorsTo +    Subject + +Checks the message to see if it's a bounce, if it looks like a loop, if it's autogenerated, etc. +Returns a triple of ("Should we continue (boolean)", "New value for $ErrorsTo", "Status message", +"This message appears to be a loop (boolean)" ); + +=cut + +sub _HandleMachineGeneratedMail { +    my %args = ( Message => undef, ErrorsTo => undef, Subject => undef, MessageId => undef, @_ ); +    my $head = $args{'Message'}->head; +    my $ErrorsTo = $args{'ErrorsTo'}; + +    my $IsBounce = CheckForBounce($head); + +    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 ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) { +        $SquelchReplies = 1; +        $ErrorsTo       = $RT::OwnerEmail; +    } + +    # Warn someone if it's a loop, before we drop it on the ground +    if ($IsALoop) { +        $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself."); + +        #Should we mail it to RTOwner? +        if ($RT::LoopsToRTOwner) { +            MailError( +                To          => $RT::OwnerEmail, +                Subject     => "RT Bounce: ".$args{'Subject'}, +                Explanation => "RT thinks this message may be a bounce", +                MIMEObj     => $args{Message} +            ); +        } + +        #Do we actually want to store it? +        return ( 0, $ErrorsTo, "Message Bounced", $IsALoop ) 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) { + +        # Squelch replies to the sender, and also leave a clue to +        # allow us to squelch ALL outbound messages. This way we +        # can punt the logic of "what to do when we get a bounce" +        # to the scrip. We might want to notify nobody. Or just +        # the RT Owner. Or maybe all Privileged watchers. +        my ( $Sender, $junk ) = ParseSenderAddressFromHead($head); +        $head->add( 'RT-Squelch-Replies-To',    $Sender ); +        $head->add( 'RT-DetectedAutoGenerated', 'true' ); +    } +    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );  } +=head2 IsCorrectAction + +Returns a list of valid actions we've found for this message + +=cut + +sub IsCorrectAction { +    my $action = shift; +    my @actions = grep $_, split /-/, $action; +    return ( 0, '(no value)' ) unless @actions; +    foreach (@actions) { +        return ( 0, $_ ) unless /^(?:comment|correspond|take|resolve)$/; +    } +    return ( 1, @actions ); +}  eval "require RT::Interface::Email_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm}); +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}); +die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm} );  1; diff --git a/rt/lib/RT/Interface/Email/Auth/GnuPG.pm b/rt/lib/RT/Interface/Email/Auth/GnuPG.pm index 2dfada755..115080722 100755 --- a/rt/lib/RT/Interface/Email/Auth/GnuPG.pm +++ b/rt/lib/RT/Interface/Email/Auth/GnuPG.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: diff --git a/rt/lib/RT/Interface/Email/Auth/MailFrom.pm b/rt/lib/RT/Interface/Email/Auth/MailFrom.pm index ef315dd53..32009c372 100644 --- a/rt/lib/RT/Interface/Email/Auth/MailFrom.pm +++ b/rt/lib/RT/Interface/Email/Auth/MailFrom.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: @@ -60,6 +62,11 @@ sub GetCurrentUser {      # We don't need to do any external lookups      my ( $Address, $Name ) = ParseSenderAddressFromHead( $args{'Message'}->head ); + +    unless ($Address) { +        return ( $args{'CurrentUser'}, -1 ); +    } +      my $CurrentUser = RT::CurrentUser->new();      $CurrentUser->LoadByEmail($Address); diff --git a/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm b/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm index c552d76e6..2f8b61cdb 100644 --- a/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm +++ b/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: diff --git a/rt/lib/RT/Interface/REST.pm b/rt/lib/RT/Interface/REST.pm index 279ddf4b3..90e3b3511 100644 --- a/rt/lib/RT/Interface/REST.pm +++ b/rt/lib/RT/Interface/REST.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: @@ -54,25 +56,37 @@ BEGIN {      use Exporter ();      use vars qw($VERSION @ISA @EXPORT); -    $VERSION = do { my @r = (q$Revision: 1.1.1.3 $ =~ /\d+/g); sprintf "%d."."%02d"x$#r, @r }; +    $VERSION = do { my @r = (q$Revision: 1.1.1.4 $ =~ /\d+/g); sprintf "%d."."%02d"x$#r, @r };      @ISA = qw(Exporter);      @EXPORT = qw(expand_list form_parse form_compose vpush vsplit);  } -my $field = '[a-zA-Z][a-zA-Z0-9_-]*'; +my $field = '(?i:[a-z][a-z0-9_-]*|C(?:ustom)?F(?:ield)?-[a-z0-9_ -]+)'; +# WARN: this code is duplicated in bin/rt.in, +# change both functions at once  sub expand_list {      my ($list) = @_; -    my ($elt, @elts, %elts); -    foreach $elt (split /,/, $list) { -        if ($elt =~ /^(\d+)-(\d+)$/) { push @elts, ($1..$2) } -        else                         { push @elts, $elt } +    my @elts; +    foreach (split /,/, $list) { +        push @elts, /^(\d+)-(\d+)$/? ($1..$2): $_;      } -    @elts{@elts}=(); -    return sort {$a<=>$b} keys %elts; +    return map $_->[0], # schwartzian transform +        sort { +            defined $a->[1] && defined $b->[1]? +                # both numbers +                $a->[1] <=> $b->[1] +                :!defined $a->[1] && !defined $b->[1]? +                    # both letters +                    $a->[2] cmp $b->[2] +                    # mix, number must be first +                    :defined $a->[1]? -1: 1 +        } +        map [ $_, (defined( /^(\d+)$/ )? $1: undef), lc($_) ], +        @elts;  }  # Returns a reference to an array of parsed forms. diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index 724d7e592..16945ab07 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: @@ -62,10 +64,13 @@ use_ok(RT::Interface::Web);  =cut -package RT::Interface::Web;  use strict; +use warnings; - +package RT::Interface::Web; +use HTTP::Date; +use RT::SavedSearches; +use URI;  # {{{ EscapeUTF8 @@ -127,7 +132,7 @@ sub WebCanonicalizeInfo {      my $user;      if ( defined $ENV{'REMOTE_USER'} ) { -	$user = lc ( $ENV{'REMOTE_USER'} ) if( length($ENV{'REMOTE_USER'}) ); +        $user = lc ( $ENV{'REMOTE_USER'} ) if( length($ENV{'REMOTE_USER'}) );      }      return $user; @@ -151,14 +156,14 @@ sub WebExternalAutoInfo {      $user_info{'Privileged'} = 1;      if ($^O !~ /^(?:riscos|MacOS|MSWin32|dos|os2)$/) { -	# Populate fields with information from Unix /etc/passwd +        # Populate fields with information from Unix /etc/passwd -	my ($comments, $realname) = (getpwnam($user))[5, 6]; -	$user_info{'Comments'} = $comments if defined $comments; -	$user_info{'RealName'} = $realname if defined $realname; +        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 +        # Populate fields with information from NT domain controller      }      # and return the wad of stuff @@ -168,8 +173,57 @@ sub WebExternalAutoInfo {  # }}} + +=head2 Redirect URL + +This routine ells the current user's browser to redirect to URL.   +Additionally, it unties the user's currently active session, helping to avoid  +A bug in Apache::Session 1.81 and earlier which clobbers sessions if we try to use  +a cached DBI statement handle twice at the same time. + +=cut + + +sub Redirect { +    my $redir_to = shift; +    untie $HTML::Mason::Commands::session; +    my $uri = URI->new($redir_to); +    my $server_uri = URI->new($RT::WebURL); + +    # If the user is coming in via a non-canonical +    # hostname, don't redirect them to the canonical host, +    # it will just upset them (and invalidate their credentials) +    if ($uri->host  eq $server_uri->host &&  +        $uri->port eq $server_uri->port) { +            $uri->host($ENV{'HTTP_HOST'}); +            $uri->port($ENV{'SERVER_PORT'}); +        } + +    $HTML::Mason::Commands::m->redirect($uri->canonical); +    $HTML::Mason::Commands::m->abort; +} + + +=head2 StaticFileHeaders  + +Send the browser a few headers to try to get it to (somewhat agressively) +cache RT's static Javascript and CSS files. + +This routine could really use _accurate_ heuristics. (XXX TODO) + +=cut + +sub StaticFileHeaders { +    # Expire things in a month. +    $HTML::Mason::Commands::r->headers_out->{'Expires'} = HTTP::Date::time2str( time() + 2592000 ); + +    # Last modified at server start time +    $HTML::Mason::Commands::r->headers_out->{'Last-Modified'} = HTTP::Date::time2str($^T); + +} + +  package HTML::Mason::Commands; -use strict;  use vars qw/$r $m %session/; @@ -194,8 +248,8 @@ sub loc {          return ($u->loc(@_));      }      else { -	# pathetic case -- SystemUser is gone. -	return $_[0]; +        # pathetic case -- SystemUser is gone. +        return $_[0];      }  } @@ -288,9 +342,18 @@ sub CreateTicket {          Body                => $ARGS{'Content'},      ); -    if ($ARGS{'Attachments'}) { -        $MIMEObj->make_multipart; -        $MIMEObj->add_part($_) foreach values %{$ARGS{'Attachments'}}; +    if ( $ARGS{'Attachments'} ) { +        my $rv = $MIMEObj->make_multipart; +        $RT::Logger->error("Couldn't make multipart message") +            if !$rv || $rv !~ /^(?:DONE|ALREADY)$/; + +        foreach ( values %{$ARGS{'Attachments'}} ) { +            unless ( $_ ) { +                $RT::Logger->error("Couldn't add empty attachemnt"); +                next; +            } +            $MIMEObj->add_part($_); +        }      }      my %create_args = ( @@ -312,13 +375,12 @@ sub CreateTicket {          MIMEObj         => $MIMEObj      );      foreach my $arg (keys %ARGS) { -            my $cfid = $1; +        next if $arg =~ /-(?:Magic|Category)$/; -            next if ($arg =~ /-Magic$/); -       #Object-RT::Ticket--CustomField-3-Values          if ($arg =~ /^Object-RT::Transaction--CustomField-/) {              $create_args{$arg} = $ARGS{$arg};          } +        # Object-RT::Ticket--CustomField-3-Values          elsif ($arg =~ /^Object-RT::Ticket--CustomField-(\d+)(.*?)$/) {              my $cfid = $1;              my $cf = RT::CustomField->new( $session{'CurrentUser'}); @@ -349,42 +411,42 @@ sub CreateTicket {      my (@dependson, @dependedonby, @parents, @children, @refersto, @referredtoby);      foreach my $luri ( split ( / /, $ARGS{"new-DependsOn"} ) ) { -	$luri =~ s/\s*$//;    # Strip trailing whitespace -	push @dependson, $luri; +        $luri =~ s/\s*$//;    # Strip trailing whitespace +        push @dependson, $luri;      }      $create_args{'DependsOn'} = \@dependson;      foreach my $luri ( split ( / /, $ARGS{"DependsOn-new"} ) ) { -	push @dependedonby, $luri; +        push @dependedonby, $luri;      }      $create_args{'DependedOnBy'} = \@dependedonby;      foreach my $luri ( split ( / /, $ARGS{"new-MemberOf"} ) ) { -	$luri =~ s/\s*$//;    # Strip trailing whitespace -	push @parents, $luri; +        $luri =~ s/\s*$//;    # Strip trailing whitespace +        push @parents, $luri;      }      $create_args{'Parents'} = \@parents;      foreach my $luri ( split ( / /, $ARGS{"MemberOf-new"} ) ) { -	push @children, $luri; +        push @children, $luri;      }      $create_args{'Children'} = \@children;      foreach my $luri ( split ( / /, $ARGS{"new-RefersTo"} ) ) { -	$luri =~ s/\s*$//;    # Strip trailing whitespace -	push @refersto, $luri; +        $luri =~ s/\s*$//;    # Strip trailing whitespace +        push @refersto, $luri;      }      $create_args{'RefersTo'} = \@refersto;      foreach my $luri ( split ( / /, $ARGS{"RefersTo-new"} ) ) { -	push @referredtoby, $luri; +        push @referredtoby, $luri;      }      $create_args{'ReferredToBy'} = \@referredtoby;      # }}}      my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args); -    unless ( $id && $Trans ) { +    unless ( $id ) {          Abort($ErrMsg);      } @@ -565,7 +627,7 @@ sub MakeMIMEEntity {              Subject => $args{'Subject'} || "",              From    => $args{'From'},              Cc      => $args{'Cc'}, -            Charset => 'utf8', +            'Charset:' => 'utf8',              Data    => [ $args{'Body'} ]          );      } @@ -893,7 +955,7 @@ sub ProcessACLChanges {               if ($object_type eq 'RT::System') {                  $obj = $RT::System; -	    } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) { +            } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) {                  $obj = $object_type->new($session{'CurrentUser'});                  $obj->Load($object_id);                    } else { @@ -922,7 +984,7 @@ sub ProcessACLChanges {               if ($object_type eq 'RT::System') {                  $obj = $RT::System; -	    } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) { +            } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) {                  $obj = $object_type->new($session{'CurrentUser'});                  $obj->Load($object_id);                    } else { @@ -964,9 +1026,9 @@ sub UpdateRecordObject {      my $Object = $args{'Object'};      my @results = $Object->Update(AttributesRef => $args{'AttributesRef'}, -				  ARGSRef       => $args{'ARGSRef'}, +                                  ARGSRef       => $args{'ARGSRef'},                    AttributePrefix => $args{'AttributePrefix'} -				  ); +                                  );      return (@results);  } @@ -1064,6 +1126,7 @@ sub ProcessTicketBasics {        Queue      ); +      if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {          my $tempqueue = RT::Queue->new($RT::SystemUser);          $tempqueue->Load( $ARGSRef->{'Queue'} ); @@ -1114,11 +1177,11 @@ sub ProcessTicketCustomFieldUpdates {      my %custom_fields_to_mod;      foreach my $arg ( keys %$ARGSRef ) {          if ( $arg =~ /^Ticket-(\d+-.*)/) { -	    $ARGSRef->{"Object-RT::Ticket-$1"} = delete $ARGSRef->{$arg}; -	} +            $ARGSRef->{"Object-RT::Ticket-$1"} = delete $ARGSRef->{$arg}; +        }          elsif ( $arg =~ /^CustomField-(\d+-.*)/) { -	    $ARGSRef->{"Object-RT::Ticket--$1"} = delete $ARGSRef->{$arg}; -	} +            $ARGSRef->{"Object-RT::Ticket--$1"} = delete $ARGSRef->{$arg}; +        }      }      return ProcessObjectCustomFieldUpdates(%args, ARGSRef => $ARGSRef); @@ -1132,161 +1195,174 @@ sub ProcessObjectCustomFieldUpdates {      # Build up a list of objects that we want to work with      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; -        } +        # format: Object-<object class>-<object id>-CustomField-<CF id>-<commands> +        next unless $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 || 0 }{ $3 }{ $4 } = $ARGSRef->{ $arg };      }      # 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'}) ; - -                        # "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; -			} -		    } - -		    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 ( $val, $msg ) = $Object->AddCustomFieldValue( -                            %$value_hash, -                            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); - -		    # 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 ) = $Object->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); -	} +        foreach my $id ( keys %{$custom_fields_to_mod{$class}} ) { +            my $Object = $args{'Object'}; +            $Object = $class->new( $session{'CurrentUser'} ) +                unless $Object && ref $Object eq $class; + +            $Object->Load( $id ) unless ($Object->id || 0) == $id; +            unless ( $Object->id ) { +                $RT::Logger->warning("Couldn't load object $class #$id"); +                next; +            } + +            foreach my $cf ( keys %{ $custom_fields_to_mod{ $class }{ $id } } ) { +                my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} ); +                $CustomFieldObj->LoadById( $cf ); +                unless ( $CustomFieldObj->id ) { +                    $RT::Logger->warning("Couldn't load custom field #$id"); +                    next; +                } +                push @results, _ProcessObjectCustomFieldUpdates( +                    Prefix      => "Object-$class-$id-CustomField-$cf-", +                    Object      => $Object, +                    CustomField => $CustomFieldObj, +                    ARGS        => $custom_fields_to_mod{$class}{$id}{$cf}, +                ); +            } +        } +    } +    return @results; +} + +sub _ProcessObjectCustomFieldUpdates { +    my %args = @_; +    my $cf = $args{'CustomField'}; +    my $cf_type = $cf->Type; + +    my @results; +    foreach my $arg ( keys %{ $args{'ARGS'} } ) { + +        # since http won't pass in a form element with a null value, we need +        # to fake it +        if ( $arg eq 'Values-Magic' ) { +            # We don't care about the magic, if there's really a values element; +            next if $args{'ARGS'}->{'Value'} || $args{'ARGS'}->{'Values'}; + +            # "Empty" values does not mean anything for Image and Binary fields +            next if $cf_type =~ /^(?:Image|Binary)$/; + +            $arg = 'Values'; +            $args{'ARGS'}->{'Values'} = undef; +        } + +        my @values = (); +        if ( ref $args{'ARGS'}->{ $arg } eq 'ARRAY' ) { +            @values = @{ $args{'ARGS'}->{$arg} }; +        } elsif ( $cf_type =~ /text/i ) { # Both Text and Wikitext +            @values = ($args{'ARGS'}->{$arg}); +        } else { +            @values = split /\n/, $args{'ARGS'}->{ $arg }; +        } +         +        if ( ( $cf_type eq 'Freeform' && !$cf->SingleValue ) || $cf_type =~ /text/i ) { +            s/\r//g foreach @values; +        } +        @values = grep defined && $_ ne '', @values; + +        if ( $arg eq 'AddValue' || $arg eq 'Value' ) { +            foreach my $value (@values) { +                my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue( +                    Field => $cf->id, +                    Value => $value +                ); +                push ( @results, $msg ); +            } +        } +        elsif ( $arg eq 'Upload' ) { +            my $value_hash = _UploadedFile( $args{'Prefix'} . $arg ) or next; +            my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue( +                %$value_hash, +                Field => $cf, +            ); +            push ( @results, $msg ); +        } +        elsif ( $arg eq 'DeleteValues' ) { +            foreach my $value ( @values ) { +                my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue( +                    Field => $cf, +                    Value => $value, +                ); +                push ( @results, $msg ); +            } +        } +        elsif ( $arg eq 'DeleteValueIds' ) { +            foreach my $value ( @values ) { +                my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue( +                    Field   => $cf, +                    ValueId => $value, +                ); +                push ( @results, $msg ); +            } +        } +        elsif ( $arg eq 'Values' && !$cf->Repeated ) { +            my $cf_values = $args{'Object'}->CustomFieldValues( $cf->id ); + +            my %values_hash; +            foreach my $value ( @values ) { +                # build up a hash of values that the new set has +                $values_hash{$value} = 1; +                next if $cf_values->HasEntry( $value ); + +                my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue( +                    Field => $cf, +                    Value => $value +                ); +                push ( @results, $msg ); +            } + +            $cf_values->RedoSearch; +            while ( my $cf_value = $cf_values->Next ) { +                next if $values_hash{ $cf_value->Content }; + +                my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue( +                    Field => $cf, +                    Value => $cf_value->Content +                ); +                push ( @results, $msg); +            } +        } +        elsif ( $arg eq 'Values' ) { +            my $cf_values = $args{'Object'}->CustomFieldValues( $cf->id ); + +            # 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 ) = $args{'Object'}->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, ref $args{'Object'}, $args{'Object'}->id ) +            ); +        }      } +    return @results;  }  # {{{ sub ProcessTicketWatchers @@ -1308,27 +1384,30 @@ sub ProcessTicketWatchers {      my $Ticket  = $args{'TicketObj'};      my $ARGSRef = $args{'ARGSRef'}; -    # {{{ Munge watchers +    # Munge watchers      foreach my $key ( keys %$ARGSRef ) { -        # {{{ Delete deletable watchers -        if ( ( $key =~ /^Ticket-DeleteWatcher-Type-(.*)-Principal-(\d+)$/ )  ) { -            my ( $code, $msg ) =  -                $Ticket->DeleteWatcher(PrincipalId => $2, -                                       Type => $1); +        # Delete deletable watchers +        if ( ( $key =~ /^Ticket-DeleteWatcher-Type-(.*)-Principal-(\d+)$/ ) ) +        { +            my ( $code, $msg ) = $Ticket->DeleteWatcher( +                PrincipalId => $2, +                Type        => $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( Email => $ARGSRef->{$key}, Type => $1 ); +        elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc)$/ ) { +            my ( $code, $msg ) = $Ticket->DeleteWatcher( +                Email => $ARGSRef->{$key}, +                Type  => $1 +            );              push @results, $msg;          } -        # }}} - -        # Add new wathchers by email address       +        # Add new wathchers by email address          elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )              and ( $key =~ /^WatcherTypeEmail(\d*)$/ ) )          { @@ -1351,18 +1430,21 @@ sub ProcessTicketWatchers {          }          # Add new  watchers by owner -        elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ ) -            and ( $key =~ /^Ticket-AddWatcher-Principal-(\d*)$/ ) ) { +        elsif ( $key =~ /^Ticket-AddWatcher-Principal-(\d*)$/ ) { +            my $principal_id = $1; +            my $form = $ARGSRef->{$key}; +            foreach my $value ( ref($form) ? @{$form} : ($form) ) { +                next unless $value =~ /^(?:AdminCc|Cc|Requestor)$/i; -            #They're in this order because otherwise $1 gets clobbered :/ -            my ( $code, $msg ) = -              $Ticket->AddWatcher( Type => $ARGSRef->{$key}, PrincipalId => $1 ); -            push @results, $msg; +                my ( $code, $msg ) = $Ticket->AddWatcher( +                    Type        => $value, +                    PrincipalId => $principal_id +                ); +                push @results, $msg; +            }          } -    } - -    # }}} +    }      return (@results);  } @@ -1444,7 +1526,7 @@ sub ProcessTicketLinks {      my (@results) = ProcessRecordLinks(RecordObj => $Ticket, -				       ARGSRef => $ARGSRef); +                                       ARGSRef => $ARGSRef);      #Merge if we need to      if ( $ARGSRef->{ $Ticket->Id . "-MergeInto" } ) { @@ -1540,6 +1622,37 @@ sub _UploadedFile {      };  } +=head2 _load_container_object ( $type, $id ); + +Instantiate container object for saving searches. + +=cut + +sub _load_container_object { +    my ($obj_type, $obj_id) = @_; +    return RT::SavedSearch->new($session{'CurrentUser'})->_load_privacy_object($obj_type, $obj_id); +} + +=head2 _parse_saved_search ( $arg ); + +Given a serialization string for saved search, and returns the +container object and the search id. + +=cut + +sub _parse_saved_search { +    my $spec = shift; +    return unless $spec; +    if ($spec  !~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/ ) { +        return; +    } +    my $obj_type  = $1; +    my $obj_id    = $2; +    my $search_id = $3; + +    return (_load_container_object ($obj_type, $obj_id), $search_id); +} +  eval "require RT::Interface::Web_Vendor";  die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web_Vendor.pm});  eval "require RT::Interface::Web_Local"; diff --git a/rt/lib/RT/Interface/Web/Handler.pm b/rt/lib/RT/Interface/Web/Handler.pm index ce9222586..1e871ec59 100644 --- a/rt/lib/RT/Interface/Web/Handler.pm +++ b/rt/lib/RT/Interface/Web/Handler.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: @@ -43,7 +45,6 @@  # those contributions and any derivatives thereof.  #   # END BPS TAGGED BLOCK }}} -  package RT::Interface::Web::Handler;  use CGI qw/-private_tempfiles/; @@ -54,7 +55,6 @@ use Time::ParseDate;  use Time::HiRes;  use HTML::Entities;  use HTML::Scrubber; -use Text::Quoted;  use RT::Interface::Web::Handler;  use File::Path qw( rmtree );  use File::Glob qw( bsd_glob ); @@ -88,16 +88,6 @@ sub new {      $class->InitSessionDir;      if ( $mod_perl::VERSION && $mod_perl::VERSION >= 1.9908 ) { -#        require Apache::RequestUtil; -#        no warnings 'redefine'; -#        my $sub = *Apache::request{CODE}; -#        *Apache::request = sub { -#            my $r; -#            eval { $r = $sub->('Apache'); }; -# -#            # warn $@ if $@; -#            return $r; -#        };          goto &NewApacheHandler;      }      elsif ($CGI::MOD_PERL) { @@ -117,11 +107,11 @@ sub InitSessionDir {          # Clean up our umask to protect session files          umask(0077); -        if ($CGI::MOD_PERL) { +        if ($CGI::MOD_PERL) { local $@; eval { +              chown( Apache->server->uid, Apache->server->gid,                  $RT::MasonSessionDir ) -            if Apache->server->can('uid'); -        } +        }}           # Die if WebSessionDir doesn't exist or we can't write to it          stat($RT::MasonSessionDir); diff --git a/rt/lib/RT/Interface/Web/Menu.pm b/rt/lib/RT/Interface/Web/Menu.pm new file mode 100644 index 000000000..f2d78ef6c --- /dev/null +++ b/rt/lib/RT/Interface/Web/Menu.pm @@ -0,0 +1,68 @@ +# BEGIN BPS TAGGED BLOCK {{{ +#  +# COPYRIGHT: +#   +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC  +#                                          <jesse@bestpractical.com> +#  +# (Except where explicitly superseded by other copyright notices) +#  +#  +# LICENSE: +#  +# 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. +#  +# 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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html. +#  +#  +# CONTRIBUTION SUBMISSION POLICY: +#  +# (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 }}} +package RT::Interface::Web::Menu; + + +sub new { +    my $class = shift; +    my $self = bless {}, $class; +    $self->{'root_node'} = RT::Interface::Web::Menu::Item->new(); +    return $self; +} + + +sub as_hash_of_hashes { + +} + +sub root { +    my $self = shift; +    return $self->{'root_node'}; +} + +1; diff --git a/rt/lib/RT/Interface/Web/Menu/Item.pm b/rt/lib/RT/Interface/Web/Menu/Item.pm new file mode 100644 index 000000000..5365db33a --- /dev/null +++ b/rt/lib/RT/Interface/Web/Menu/Item.pm @@ -0,0 +1,86 @@ +# BEGIN BPS TAGGED BLOCK {{{ +#  +# COPYRIGHT: +#   +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC  +#                                          <jesse@bestpractical.com> +#  +# (Except where explicitly superseded by other copyright notices) +#  +#  +# LICENSE: +#  +# 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. +#  +# 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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html. +#  +#  +# CONTRIBUTION SUBMISSION POLICY: +#  +# (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 }}} +package RT::Interface::Web::Menu::Item; + + +sub new { +    my $class = shift; +    my $self = bless {},$class; +    $self->{'_attributes'} = {}; +    return($self); +} + +sub label { my $self = shift; $self->_accessor( label => @_) } ; +sub absolute_url { my $self = shift; $self->_accessor( absolute_url => @_) } ; +sub rt_path { my $self = shift; $self->_accessor( rt_path => @_) } ; +sub hilight { my $self = shift; $self->_accessor( hilight => @_); +              $self->parent->hilight(1); +            } ; +sub sort_order { my $self = shift; $self->_accessor( sort_order => @_) } ; + +sub add_child { +} + +sub delete { +} + +sub children { + +} + +sub _accessor { +    my $self = shift; +    my $key = shift; +    if (@_){  +        $self->{'attributes'}->{$key} = shift; + +    } +    return $self->{'_attributes'}->{$key}; +} + +1; diff --git a/rt/lib/RT/Interface/Web/QueryBuilder.pm b/rt/lib/RT/Interface/Web/QueryBuilder.pm index b7526b30a..56c5b038c 100755 --- a/rt/lib/RT/Interface/Web/QueryBuilder.pm +++ b/rt/lib/RT/Interface/Web/QueryBuilder.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: diff --git a/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm b/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm index 67b728339..467627313 100755 --- a/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm +++ b/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm @@ -2,7 +2,7 @@  #   # COPYRIGHT:  #   -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC  +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC   #                                          <jesse@bestpractical.com>  #   # (Except where explicitly superseded by other copyright notices) @@ -22,7 +22,9 @@  #   # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html.  #   #   # CONTRIBUTION SUBMISSION POLICY: diff --git a/rt/lib/RT/Interface/Web/Standalone.pm b/rt/lib/RT/Interface/Web/Standalone.pm index bc2423e6d..319e317b8 100755 --- a/rt/lib/RT/Interface/Web/Standalone.pm +++ b/rt/lib/RT/Interface/Web/Standalone.pm @@ -1,3 +1,50 @@ +# BEGIN BPS TAGGED BLOCK {{{ +#  +# COPYRIGHT: +#   +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC  +#                                          <jesse@bestpractical.com> +#  +# (Except where explicitly superseded by other copyright notices) +#  +#  +# LICENSE: +#  +# 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. +#  +# 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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html. +#  +#  +# CONTRIBUTION SUBMISSION POLICY: +#  +# (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 }}}  package RT::Interface::Web::Standalone;  use strict; | 
