use strict;
use vars qw( @ISA $DEBUG $me );
use Data::Dumper;
+use Date::Format qw( time2str );
use MIME::Entity;
+use Encode;
use FS::UID qw(dbh);
use FS::CGI qw(popurl);
use FS::TicketSystem::RT_Libs;
sub session {
my( $self, $session ) = @_;
- if ( $session && $session->{'Current_User'} ) { # does this even work?
+ if ( $session && $session->{'CurrentUser'} ) { # does this even work?
warn "$me session: using existing session and CurrentUser: \n".
Dumper($session->{'CurrentUser'})
if $DEBUG;
# this needs to be done on each fork
warn "$me init: initializing RT\n" if $DEBUG;
{
+ local $SIG{__WARN__};
local $SIG{__DIE__};
eval 'RT::Init("NoSignalHandlers"=>1);';
}
warn "$me init: complete" if $DEBUG;
}
-=item customer_tickets CUSTNUM [ LIMIT ] [ PRIORITYVALUE ]
+=item customer_tickets CUSTNUM [ PARAMS ]
Replacement for the one in RT_External so that we can access custom fields
-properly.
+properly. Accepts a hashref with the following parameters:
+
+number - custnum/svcnum
+
+limit
+
+priority
+
+status
+
+queueid
+
+resolved - only return tickets resolved after this timestamp
=cut
-sub _customer_tickets_search {
- my ( $self, $custnum, $limit, $priority ) = @_;
+# create an RT::Tickets object for a specified custnum or svcnum
+
+sub _tickets_search {
+ my $self = shift;
+ my $type = shift;
+
+ my( $number, $limit, $priority, $status, $queueid, $opt );
+ if ( ref($_[0]) eq 'HASH' ) {
+ $opt = shift;
+ $number = $$opt{'number'};
+ $limit = $$opt{'limit'};
+ $priority = $$opt{'priority'};
+ $status = $$opt{'status'};
+ $queueid = $$opt{'queueid'};
+ } else {
+ ( $number, $limit, $priority, $status, $queueid ) = @_;
+ $opt = {};
+ }
- $custnum =~ /^\d+$/ or die "invalid custnum: $custnum";
+ $type =~ /^Customer|Service$/ or die "invalid type: $type";
+ $number =~ /^\d+$/ or die "invalid custnum/svcnum: $number";
$limit =~ /^\d+$/ or die "invalid limit: $limit";
my $session = $self->session();
my $Tickets = RT::Tickets->new($CurrentUser);
- my $rtql = "MemberOf = 'freeside://freeside/cust_main/$custnum'";
+ # "Customer.number" searches tickets linked via cust_svc also
+ my $rtql = "$type.number = $number";
if ( defined( $priority ) ) {
my $custom_priority = FS::Conf->new->config('ticket_system-custom_priority_field');
}
}
- $rtql .= ' AND ( ' .
- join(' OR ', map { "Status = '$_'" } $self->statuses) .
- ' )';
+ my @statuses;
+ if ( defined($status) && $status ) {
+ if ( ref($status) ) {
+ if ( ref($status) eq 'HASH' ) {
+ @statuses = grep $status->{$_}, keys %$status;
+ } elsif ( ref($status) eq 'ARRAY' ) {
+ @statuses = @$status;
+ } else {
+ #what should be the failure mode here? die? return no tickets?
+ die 'unknown status ref '. ref($status);
+ }
+ } else {
+ @statuses = ( $status );
+ }
+ @statuses = grep /^\w+$/, @statuses; #injection prevention
+ } else {
+ @statuses = $self->statuses;
+ }
+
+ $rtql .= ' AND ( '.
+ join(' OR ', map { "Status = '$_'" } @statuses).
+ ' ) ';
+
+ $rtql .= " AND Queue = $queueid " if $queueid;
+
+ if ($$opt{'resolved'}) {
+ $rtql .= " AND Resolved >= " . dbh->quote(time2str('%Y-%m-%d %H:%M:%S',$$opt{'resolved'}));
+ }
warn "$me _customer_tickets_search:\n$rtql\n" if $DEBUG;
$Tickets->FromSQL($rtql);
return $Tickets;
}
+sub href_customer_tickets {
+ my ($self, $custnum) = (shift, shift);
+ if ($custnum =~ /^(\d+)$/) {
+ return $self->href_search_tickets("Customer.number = $custnum", @_);
+ }
+ warn "bad custnum $custnum"; '';
+}
+
+sub href_service_tickets {
+ my ($self, $svcnum) = (shift, shift);
+ if ($svcnum =~ /^(\d+)$/ ) {
+ return $self->href_search_tickets("Service.number = $svcnum", @_);
+ }
+ warn "bad svcnum $svcnum"; '';
+}
+
sub customer_tickets {
- my $Tickets = _customer_tickets_search(@_);
+ my $self = shift;
+ my $Tickets = $self->_tickets_search('Customer', @_);
my $conf = FS::Conf->new;
my $priority_order =
$conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
- my $custom_priority =
- $conf->config('ticket_system-custom_priority_field') || '';
- my @order_by;
- my $ss_priority = selfservice_priority();
- push @order_by, { FIELD => "CF.{$ss_priority}", ORDER => $priority_order }
- if $ss_priority;
- push @order_by,
+ my @order_by = (
{ FIELD => 'Priority', ORDER => $priority_order },
{ FIELD => 'Id', ORDER => 'DESC' },
- ;
+ );
$Tickets->OrderByCols(@order_by);
while ( my $t = $Tickets->Next ) {
push @tickets, _ticket_info($t);
}
+
return \@tickets;
}
sub num_customer_tickets {
- my $Tickets = _customer_tickets_search(@_);
- return $Tickets->CountAll;
+ my ( $self, $custnum, $priority ) = @_;
+ $self->_tickets_search('Customer', $custnum, 0, $priority)->CountAll;
+}
+
+sub service_tickets {
+ my $self = shift;
+ my $Tickets = $self->_tickets_search('Service', @_);
+
+ my $conf = FS::Conf->new;
+ my $priority_order =
+ $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
+
+ my @order_by = (
+ { FIELD => 'Priority', ORDER => $priority_order },
+ { FIELD => 'Id', ORDER => 'DESC' },
+ );
+
+ $Tickets->OrderByCols(@order_by);
+
+ my @tickets;
+ while ( my $t = $Tickets->Next ) {
+ push @tickets, _ticket_info($t);
+ }
+
+ return \@tickets;
}
sub _ticket_info {
# Takes an RT::Ticket; returns a hashref of the ticket's fields, including
# custom fields. Also returns custom and selfservice priority values as
- # _custom_priority and _selfservice_priority.
+ # _custom_priority and _selfservice_priority, and the IsUnreplied property
+ # as is_unreplied.
my $t = shift;
my $custom_priority =
}
$ticket_info{'owner'} = $t->OwnerObj->Name;
$ticket_info{'queue'} = $t->QueueObj->Name;
+ $ticket_info{'_cf_sort_order'} = {};
+ my $cf_sort = 0;
foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
+ $ticket_info{'_cf_sort_order'}{$CF->Name} = $cf_sort++;
my $name = 'CF.{'.$CF->Name.'}';
$ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
}
if ( $ss_priority ) {
$ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
}
+ $ticket_info{'is_unreplied'} = $t->IsUnreplied;
+ my $svcnums = [
+ map { $_->Target =~ /cust_svc\/(\d+)/; $1 }
+ @{ $t->Services->ItemsArrayRef }
+ ];
+ $ticket_info{'svcnums'} = $svcnums;
+
return \%ticket_info;
}
: ( $param{'cc'} ? [ $param{'cc'} ] : [] );
my $mimeobj = MIME::Entity->build(
- 'Data' => $param{'message'},
+ 'Data' => Encode::encode_utf8( $param{'message'} ),
'Type' => ( $param{'mime_type'} || 'text/plain' ),
);
my $self = shift;
my ($session, %opt) = @_;
$session = $self->session(shift);
- my $Ticket = RT::Ticket->new($session->{CurrentUser});
- $Ticket->Load($opt{'ticket_id'});
- return if ( !$Ticket->id );
- my $custnum = $opt{'custnum'};
- if ( defined($custnum) && $custnum =~ /^\d+$/ ) {
- # probably the most efficient way to check ticket ownership
- my $Link = RT::Link->new($session->{CurrentUser});
- $Link->LoadByCols( LocalBase => $opt{'ticket_id'},
- Type => 'MemberOf',
- Target => "freeside://freeside/cust_main/$custnum",
- );
- return if ( !$Link->id );
+ # use a small search here so we can check ticket ownership
+ my $query;
+ if ( $opt{'ticket_id'} =~ /^(\d+)$/ ) {
+ $query = "id = $1";
+ } else {
+ return;
+ }
+ if ( $opt{'custnum'} =~ /^(\d+)$/ ) {
+ $query .= " AND Customer.number = $1"; # also checks ownership via services
}
- return $Ticket;
+ my $Tickets = RT::Tickets->new($session->{CurrentUser});
+ $Tickets->FromSQL($query);
+ return $Tickets->First;
}
-
=item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
Class method. Correspond on a ticket. If there is an error, returns the scalar
# now get user specific information, to better create our user.
my $new_user_info
- = RT::Interface::Web::WebExternalAutoInfo($user);
+ = RT::Interface::Web::WebRemoteUserAutocreateInfo($user);
# set the attributes that have been defined.
# FIXME: this is a horrible kludge. I'm sure there's something cleaner
# we failed to successfully create the user. abort abort abort.
delete $session->{'CurrentUser'};
- die "can't auto-create RT user"; #an error message would be nice :/
+ die "can't auto-create RT user: $msg"; #an error message would be nice :/
#$m->abort() unless $RT::WebFallbackToInternalAuth;
#$m->comp( '/Elements/Login', %ARGS,
# Error => loc( 'Cannot create user: [_1]', $msg ) );
}
}
+=item custom_fields
+
+Returns a hash of custom field names and descriptions.
+
+Accepts the following options:
+
+lookuptype - limit results to this lookuptype
+
+valuetype - limit results to this valuetype
+
+Fields must be visible to CurrentUser.
+
+=cut
+
+sub custom_fields {
+ my $self = shift;
+ my %opt = @_;
+ my $lookuptype = $opt{lookuptype};
+ my $valuetype = $opt{valuetype};
+
+ my $CurrentUser = RT::CurrentUser->new();
+ $CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username);
+ die "RT not configured" unless $CurrentUser->id;
+ my $CFs = RT::CustomFields->new($CurrentUser);
+
+ $CFs->UnLimit;
+
+ $CFs->Limit(FIELD => 'LookupType',
+ OPERATOR => 'ENDSWITH',
+ VALUE => $lookuptype)
+ if $lookuptype;
+
+ $CFs->Limit(FIELD => 'Type',
+ VALUE => $valuetype)
+ if $valuetype;
+
+ my @fields;
+ while (my $CF = $CFs->Next) {
+ push @fields, $CF->Name, ($CF->Description || $CF->Name);
+ }
+
+ return @fields;
+}
+
1;