1291 lines
39 KiB
Perl
1291 lines
39 KiB
Perl
# This code was forked from the LiveJournal project owned and operated
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
|
# currently be found at:
|
|
#
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
|
#
|
|
# In accordance with the original license, this code and all its
|
|
# modifications are provided under the GNU General Public License.
|
|
# A copy of that license can be found in the LICENSE file included as
|
|
# part of this distribution.
|
|
|
|
package LJ::Support;
|
|
|
|
use strict;
|
|
use v5.10;
|
|
use Log::Log4perl;
|
|
my $log = Log::Log4perl->get_logger(__PACKAGE__);
|
|
|
|
use DW::Task::SupportNotify;
|
|
|
|
use Digest::MD5 qw(md5_hex);
|
|
|
|
use LJ::Sysban;
|
|
use LJ::Faq;
|
|
|
|
# Constants
|
|
my $SECONDS_IN_DAY = 3600 * 24;
|
|
our @SUPPORT_PRIVS = (
|
|
qw/supportclose
|
|
supporthelp
|
|
supportread
|
|
supportviewinternal
|
|
supportmakeinternal
|
|
supportmovetouch
|
|
supportviewscreened
|
|
supportviewstocks
|
|
supportchangesummary/
|
|
);
|
|
|
|
# <LJFUNC>
|
|
# name: LJ::Support::slow_query_dbh
|
|
# des: Retrieve a database handle to be used for support-related
|
|
# slow queries... defaults to 'slow' role but can be
|
|
# overriden by [ljconfig[support_slow_roles]].
|
|
# args: none
|
|
# returns: master database handle.
|
|
# </LJFUNC>
|
|
sub slow_query_dbh {
|
|
return LJ::get_dbh(@LJ::SUPPORT_SLOW_ROLES);
|
|
}
|
|
|
|
# basic function to add or update a support category.
|
|
# args: hashref corresponding to row values of supportcat table
|
|
# returns: spcatid on success, undef on failure
|
|
sub define_cat {
|
|
my ($opts) = @_;
|
|
if ( $opts->{catkey} ) {
|
|
|
|
# see if this category is already defined (catkey is unique)
|
|
my $cat = get_cat_by_key( load_cats(), $opts->{catkey} );
|
|
if ($cat) {
|
|
|
|
# use the existing category id
|
|
$opts->{spcatid} = $cat->{spcatid};
|
|
delete $opts->{catkey};
|
|
}
|
|
}
|
|
|
|
my @columns = qw/ catkey catname sortorder basepoints is_selectable
|
|
public_read public_help allow_screened hide_helpers
|
|
user_closeable replyaddress no_autoreply scope /;
|
|
my %row;
|
|
foreach (@columns) {
|
|
$row{$_} = $opts->{$_} if exists $opts->{$_};
|
|
delete $opts->{$_};
|
|
}
|
|
|
|
my $id = delete $opts->{spcatid};
|
|
return $id unless %row;
|
|
|
|
# if we have any $opts remaining here, they're invalid
|
|
my $invalid = join ', ', keys %$opts;
|
|
die "Invalid opts passed to LJ::Support::define_cat: $invalid"
|
|
if $invalid;
|
|
|
|
my $dbh = LJ::get_db_writer() or return;
|
|
|
|
if ($id) { # update path
|
|
my ( @cols, @vals );
|
|
while ( my ( $col, $val ) = each %row ) {
|
|
push @cols, "$col=?";
|
|
push @vals, $val;
|
|
}
|
|
my $bind = join ', ', @cols;
|
|
|
|
$dbh->do( "UPDATE supportcat SET $bind WHERE spcatid=?", undef, @vals, $id );
|
|
|
|
}
|
|
else { # insert path
|
|
my @cols = keys %row;
|
|
my @vals = @row{@cols};
|
|
my $colbind = join ',', map { $_ } @cols;
|
|
my $valbind = join ',', map { '?' } @vals;
|
|
|
|
$dbh->do( "INSERT INTO supportcat ($colbind) VALUES ($valbind)", undef, @vals );
|
|
$id = $dbh->{mysql_insertid};
|
|
}
|
|
|
|
die $dbh->errstr if $dbh->err;
|
|
return $id;
|
|
}
|
|
|
|
sub delete_cat {
|
|
my ($id) = @_;
|
|
my $dbh = LJ::get_db_writer() or return;
|
|
$dbh->do( "DELETE FROM supportcat WHERE spcatid=?", undef, $id );
|
|
die $dbh->errstr if $dbh->err;
|
|
return 1; # regardless of whether the id was in the table
|
|
}
|
|
|
|
## pass $id of zero or blank to get all categories
|
|
sub load_cats {
|
|
my ($id) = @_;
|
|
my $hashref = {};
|
|
$id += 0;
|
|
my $where = $id ? "WHERE spcatid=$id" : "";
|
|
my $dbr = LJ::get_db_reader();
|
|
my $sth = $dbr->prepare("SELECT * FROM supportcat $where");
|
|
$sth->execute;
|
|
$hashref->{ $_->{'spcatid'} } = $_ while ( $_ = $sth->fetchrow_hashref );
|
|
return $hashref;
|
|
}
|
|
|
|
sub load_email_to_cat_map {
|
|
my $map = {};
|
|
my $dbr = LJ::get_db_reader();
|
|
my $sth = $dbr->prepare("SELECT * FROM supportcat ORDER BY sortorder DESC");
|
|
$sth->execute;
|
|
while ( my $sp = $sth->fetchrow_hashref ) {
|
|
next unless ( $sp->{'replyaddress'} );
|
|
$map->{ $sp->{'replyaddress'} } = $sp;
|
|
}
|
|
return $map;
|
|
}
|
|
|
|
sub calc_points {
|
|
my ( $sp, $secs, $spcat ) = @_;
|
|
$spcat ||= $sp->{_cat};
|
|
my $base = $spcat->{basepoints} || 1;
|
|
$secs = int( $secs / ( 3600 * 6 ) );
|
|
my $total = ( $base + $secs );
|
|
$total = 10 if $total > 10;
|
|
return $total;
|
|
}
|
|
|
|
sub init_remote {
|
|
my $remote = shift;
|
|
return unless $remote;
|
|
$remote->load_user_privs(@SUPPORT_PRIVS);
|
|
}
|
|
|
|
sub has_any_support_priv {
|
|
my $u = shift;
|
|
return 0 unless $u;
|
|
foreach my $support_priv (@SUPPORT_PRIVS) {
|
|
return 1 if $u->has_priv($support_priv);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# given all the categories, maps a catkey into a cat
|
|
sub get_cat_by_key {
|
|
my ( $cats, $cat ) = @_;
|
|
$cat ||= '';
|
|
foreach ( keys %$cats ) {
|
|
if ( $cats->{$_}->{'catkey'} eq $cat ) {
|
|
return $cats->{$_};
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub filter_cats {
|
|
my $remote = shift;
|
|
my $cats = shift;
|
|
|
|
return grep { can_read_cat( $_, $remote ); } sorted_cats($cats);
|
|
}
|
|
|
|
sub sorted_cats {
|
|
my $cats = shift;
|
|
return sort { $a->{'catname'} cmp $b->{'catname'} } values %$cats;
|
|
}
|
|
|
|
# takes raw support request record and puts category info in it
|
|
# so it can be used in other functions like can_*
|
|
sub fill_request_with_cat {
|
|
my ( $sp, $cats ) = @_;
|
|
$sp->{_cat} = $cats->{ $sp->{'spcatid'} };
|
|
}
|
|
|
|
sub open_request_status {
|
|
my ( $timetouched, $timelasthelp ) = @_;
|
|
my $status;
|
|
if ( $timelasthelp > $timetouched + 5 ) {
|
|
$status = "awaiting close";
|
|
}
|
|
elsif ($timelasthelp
|
|
&& $timetouched > $timelasthelp + 5 )
|
|
{
|
|
$status = "still needs help";
|
|
}
|
|
else {
|
|
$status = "open";
|
|
}
|
|
return $status;
|
|
}
|
|
|
|
sub is_poster {
|
|
my ( $sp, $remote, $auth ) = @_;
|
|
|
|
if ( $sp->{'reqtype'} eq "user" ) {
|
|
return 1 if $remote && $remote->id == $sp->{'requserid'};
|
|
|
|
}
|
|
else {
|
|
if ($remote) {
|
|
return 1 if lc( $remote->email_raw ) eq lc( $sp->{'reqemail'} );
|
|
}
|
|
else {
|
|
return 1 if $auth && $auth eq mini_auth($sp);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
sub can_see_helper {
|
|
my ( $sp, $remote ) = @_;
|
|
if ( $sp->{_cat}->{'hide_helpers'} ) {
|
|
if ( can_help( $sp, $remote ) ) {
|
|
return 1;
|
|
}
|
|
if ( $remote && $remote->has_priv( "supportviewinternal", $sp->{_cat}->{'catkey'} ) ) {
|
|
return 1;
|
|
}
|
|
if ( $remote && $remote->has_priv( "supportviewscreened", $sp->{_cat}->{'catkey'} ) ) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub can_read {
|
|
my ( $sp, $remote, $auth ) = @_;
|
|
return ( is_poster( $sp, $remote, $auth ) || can_read_cat( $sp->{_cat}, $remote ) );
|
|
}
|
|
|
|
sub can_read_cat {
|
|
my ( $cat, $remote ) = @_;
|
|
return unless ($cat);
|
|
return ( $cat->{'public_read'}
|
|
|| ( $remote && $remote->has_priv( "supportread", $cat->{'catkey'} ) ) );
|
|
}
|
|
|
|
*can_bounce = \&can_close_cat;
|
|
*can_lock = \&can_close_cat;
|
|
|
|
# if they can close in this category
|
|
sub can_close_cat {
|
|
my ( $sp, $remote ) = @_;
|
|
return 1 if $sp->{_cat}->{public_read} && $remote && $remote->has_priv( 'supportclose', '' );
|
|
return 1 if $remote && $remote->has_priv( 'supportclose', $sp->{_cat}->{catkey} );
|
|
return 0;
|
|
}
|
|
|
|
# if they can close this particular request
|
|
sub can_close {
|
|
my ( $sp, $remote, $auth ) = @_;
|
|
return 1 if $sp->{_cat}->{user_closeable} && is_poster( $sp, $remote, $auth );
|
|
return can_close_cat( $sp, $remote );
|
|
}
|
|
|
|
# if they can reopen a request
|
|
sub can_reopen {
|
|
my ( $sp, $remote, $auth ) = @_;
|
|
return 1 if is_poster( $sp, $remote, $auth );
|
|
return can_close_cat( $sp, $remote );
|
|
}
|
|
|
|
sub can_append {
|
|
my ( $sp, $remote, $auth ) = @_;
|
|
if ( is_poster( $sp, $remote, $auth ) ) { return 1; }
|
|
return 0 unless $remote;
|
|
return 0 unless $remote->is_visible;
|
|
if ( $sp->{_cat}->{'allow_screened'} ) { return 1; }
|
|
if ( can_help( $sp, $remote ) ) { return 1; }
|
|
return 0;
|
|
}
|
|
|
|
sub is_locked {
|
|
my $sp = shift;
|
|
my $spid = ref $sp ? $sp->{spid} : $sp + 0;
|
|
return undef unless $spid;
|
|
my $props = LJ::Support::load_props($spid);
|
|
return $props->{locked} ? 1 : 0;
|
|
}
|
|
|
|
sub lock {
|
|
my $sp = shift;
|
|
my $spid = ref $sp ? $sp->{spid} : $sp + 0;
|
|
return undef unless $spid;
|
|
my $dbh = LJ::get_db_writer();
|
|
$dbh->do( "REPLACE INTO supportprop (spid, prop, value) VALUES (?, 'locked', 1)", undef,
|
|
$spid );
|
|
}
|
|
|
|
sub unlock {
|
|
my $sp = shift;
|
|
my $spid = ref $sp ? $sp->{spid} : $sp + 0;
|
|
return undef unless $spid;
|
|
my $dbh = LJ::get_db_writer();
|
|
$dbh->do( "DELETE FROM supportprop WHERE spid = ? AND prop = 'locked'", undef, $spid );
|
|
}
|
|
|
|
# privilege policy:
|
|
# supporthelp with no argument gives you all abilities in all public_read categories
|
|
# supporthelp with a catkey arg gives you all abilities in that non-public_read category
|
|
# supportread with a catkey arg is required to view requests in a non-public_read category
|
|
# all other privs work like:
|
|
# no argument = global, where category is public_read or user has supportread on that category
|
|
# argument = local, priv applies in that category only if it's public or user has supportread
|
|
sub support_check_priv {
|
|
my ( $sp, $remote, $priv ) = @_;
|
|
return 1 if can_help( $sp, $remote );
|
|
return 0 unless can_read_cat( $sp->{_cat}, $remote );
|
|
return 1 if $remote && $remote->has_priv( $priv, '' ) && $sp->{_cat}->{public_read};
|
|
return 1 if $remote && $remote->has_priv( $priv, $sp->{_cat}->{catkey} );
|
|
return 0;
|
|
}
|
|
|
|
# can they read internal comments? if they're a helper or have
|
|
# extended supportread (with a plus sign at the end of the category key)
|
|
sub can_read_internal {
|
|
my ( $sp, $remote ) = @_;
|
|
return 1 if LJ::Support::support_check_priv( $sp, $remote, 'supportviewinternal' );
|
|
return 1 if $remote && $remote->has_priv( "supportread", $sp->{_cat}->{catkey} . "+" );
|
|
return 0;
|
|
}
|
|
|
|
sub can_make_internal {
|
|
return LJ::Support::support_check_priv( @_, 'supportmakeinternal' );
|
|
}
|
|
|
|
sub can_read_screened {
|
|
return LJ::Support::support_check_priv( @_, 'supportviewscreened' );
|
|
}
|
|
|
|
sub can_read_response {
|
|
my ( $sp, $u, $rtype, $posterid ) = @_;
|
|
return 1 if $posterid == $u->id;
|
|
return 0
|
|
if $rtype eq 'screened'
|
|
&& !LJ::Support::can_read_screened( $sp, $u );
|
|
return 0
|
|
if $rtype eq 'internal'
|
|
&& !LJ::Support::can_read_internal( $sp, $u );
|
|
return 1;
|
|
}
|
|
|
|
sub can_perform_actions {
|
|
return LJ::Support::support_check_priv( @_, 'supportmovetouch' );
|
|
}
|
|
|
|
sub can_change_summary {
|
|
return LJ::Support::support_check_priv( @_, 'supportchangesummary' );
|
|
}
|
|
|
|
sub can_see_stocks {
|
|
return LJ::Support::support_check_priv( @_, 'supportviewstocks' );
|
|
}
|
|
|
|
sub can_help {
|
|
my ( $sp, $remote ) = @_;
|
|
if ( $sp->{_cat}->{'public_read'} ) {
|
|
return 1 if $sp->{_cat}->{'public_help'};
|
|
return 1 if $remote && $remote->has_priv( "supporthelp", "" );
|
|
}
|
|
my $catkey = $sp->{_cat}->{'catkey'};
|
|
return 1 if $remote && $remote->has_priv( "supporthelp", $catkey );
|
|
return 0;
|
|
}
|
|
|
|
sub load_props {
|
|
my $spid = shift;
|
|
return unless $spid;
|
|
|
|
my %props = (); # prop => value
|
|
|
|
my $dbr = LJ::get_db_reader();
|
|
my $sth = $dbr->prepare("SELECT prop, value FROM supportprop WHERE spid=?");
|
|
$sth->execute($spid);
|
|
while ( my ( $prop, $value ) = $sth->fetchrow_array ) {
|
|
$props{$prop} = $value;
|
|
}
|
|
|
|
return \%props;
|
|
}
|
|
|
|
sub prop {
|
|
my ( $spid, $propname ) = @_;
|
|
|
|
my $props = LJ::Support::load_props($spid);
|
|
|
|
return $props->{$propname} || undef;
|
|
}
|
|
|
|
sub set_prop {
|
|
my ( $spid, $propname, $propval ) = @_;
|
|
|
|
# TODO:
|
|
# -- delete on 'undef' propval
|
|
# -- allow setting of multiple
|
|
|
|
my $dbh = LJ::get_db_writer()
|
|
or die "couldn't contact global master";
|
|
|
|
$dbh->do( "REPLACE INTO supportprop (spid, prop, value) VALUES (?,?,?)",
|
|
undef, $spid, $propname, $propval );
|
|
die $dbh->errstr if $dbh->err;
|
|
|
|
return 1;
|
|
}
|
|
|
|
# $loadreq is used by /abuse/report.bml and
|
|
# to signify that the full request
|
|
# should not be loaded. To simplify code going live,
|
|
# Whitaker and I decided to not try and merge it
|
|
# into the new $opts hash.
|
|
|
|
# $opts->{'db_force'} loads the request from a
|
|
# global master. Needed to prevent a race condition
|
|
# where the request may not have replicated to slaves
|
|
# in the time needed to load an auth code.
|
|
|
|
sub load_request {
|
|
my ( $spid, $loadreq, $opts ) = @_;
|
|
my $sth;
|
|
|
|
$spid += 0;
|
|
|
|
# load the support request
|
|
my $db = $opts->{'db_force'} ? LJ::get_db_writer() : LJ::get_db_reader();
|
|
|
|
$sth = $db->prepare("SELECT * FROM support WHERE spid=$spid");
|
|
$sth->execute;
|
|
my $sp = $sth->fetchrow_hashref;
|
|
|
|
return undef unless $sp;
|
|
|
|
# load the category the support requst is in
|
|
$sth = $db->prepare("SELECT * FROM supportcat WHERE spcatid=$sp->{'spcatid'}");
|
|
$sth->execute;
|
|
$sp->{_cat} = $sth->fetchrow_hashref;
|
|
|
|
# now load the user's request text, if necessary
|
|
if ($loadreq) {
|
|
$sp->{body} =
|
|
$db->selectrow_array( "SELECT message FROM supportlog WHERE spid = ? AND type = 'req'",
|
|
undef, $sp->{spid} );
|
|
}
|
|
|
|
return $sp;
|
|
}
|
|
|
|
# load_requests:
|
|
# Given an arrayref, fetches information about the requests
|
|
# with these spid's; unlike load_request(), it doesn't fetch information
|
|
# about supportcats.
|
|
|
|
sub load_requests {
|
|
my ($spids) = @_;
|
|
my $dbr = LJ::get_db_reader() or return;
|
|
|
|
my $list = join( ',', map { '?' } @$spids );
|
|
my $requests = $dbr->selectall_arrayref(
|
|
"SELECT spid, reqtype, requserid, reqname, reqemail, state,"
|
|
. " authcode, spcatid, subject, timecreate, timetouched, timeclosed,"
|
|
. " timelasthelp, timemodified FROM support WHERE spid IN ($list)",
|
|
{ Slice => {} },
|
|
map { $_ + 0 } @$spids
|
|
);
|
|
die $dbr->errstr if $dbr->err;
|
|
|
|
return $requests;
|
|
}
|
|
|
|
sub load_response {
|
|
my $splid = shift;
|
|
my $sth;
|
|
|
|
$splid += 0;
|
|
|
|
# load the support request. we hit the master because we generally
|
|
# only invoke this when we want the freshest version of the row.
|
|
# (ie, approving a response changes its type from screened to
|
|
# answer ... then we fetch the row again and make decisions on its type.
|
|
# so we want the authoritative version)
|
|
my $dbh = LJ::get_db_writer();
|
|
$sth = $dbh->prepare("SELECT * FROM supportlog WHERE splid=$splid");
|
|
$sth->execute;
|
|
my $res = $sth->fetchrow_hashref;
|
|
|
|
return $res;
|
|
}
|
|
|
|
sub get_answer_types {
|
|
my ( $sp, $remote, $auth ) = @_;
|
|
my @ans_type;
|
|
|
|
if ( is_poster( $sp, $remote, $auth ) ) {
|
|
push @ans_type, ( "comment", LJ::Lang::ml("support.answertype.moreinfo") );
|
|
return @ans_type;
|
|
}
|
|
|
|
if ( can_help( $sp, $remote ) ) {
|
|
push @ans_type,
|
|
(
|
|
"screened" => LJ::Lang::ml("support.answertype.screened"),
|
|
"answer" => LJ::Lang::ml("support.answertype.answer"),
|
|
"comment" => LJ::Lang::ml("support.answertype.comment")
|
|
);
|
|
}
|
|
elsif ( $sp->{_cat}->{'allow_screened'} ) {
|
|
push @ans_type, ( "screened" => LJ::Lang::ml("support.answertype.screened") );
|
|
}
|
|
|
|
if ( can_make_internal( $sp, $remote )
|
|
&& !$sp->{_cat}->{'public_help'} )
|
|
{
|
|
push @ans_type, ( "internal" => LJ::Lang::ml("support.answertype.internal") );
|
|
}
|
|
|
|
if ( can_bounce( $sp, $remote ) ) {
|
|
push @ans_type, ( "bounce" => LJ::Lang::ml("support.answertype.bounce") );
|
|
}
|
|
|
|
return @ans_type;
|
|
}
|
|
|
|
sub file_request {
|
|
my $errors = shift;
|
|
my $o = shift;
|
|
|
|
my $email = $o->{'reqtype'} eq "email" ? $o->{'reqemail'} : "";
|
|
unless ( LJ::is_enabled('loggedout_support_requests') || !$email ) {
|
|
push @$errors, LJ::Lang::ml("error.support.mustbeloggedin");
|
|
}
|
|
my $log = {
|
|
'uniq' => $o->{'uniq'},
|
|
'email' => $email
|
|
};
|
|
my $userid = 0;
|
|
|
|
unless ($email) {
|
|
if ( $o->{'reqtype'} eq "user" ) {
|
|
my $u = LJ::load_userid( $o->{'requserid'} );
|
|
$userid = $u->{'userid'};
|
|
|
|
$log->{'user'} = $u->user;
|
|
$log->{'email'} = $u->email_raw;
|
|
|
|
unless ( $u->is_person || $u->is_identity ) {
|
|
push @$errors, LJ::Lang::ml("error.support.nonuser");
|
|
}
|
|
|
|
if ( LJ::sysban_check( 'support_user', $u->{'user'} ) ) {
|
|
return LJ::Sysban::block( $userid, "Support request blocked based on user", $log );
|
|
}
|
|
|
|
$email = $u->email_raw || $o->{'reqemail'};
|
|
}
|
|
}
|
|
|
|
if ( LJ::sysban_check( 'support_email', $email ) ) {
|
|
return LJ::Sysban::block( $userid, "Support request blocked based on email", $log );
|
|
}
|
|
if ( LJ::sysban_check( 'support_uniq', $o->{'uniq'} ) ) {
|
|
return LJ::Sysban::block( $userid, "Support request blocked based on uniq", $log );
|
|
}
|
|
|
|
my $reqsubject = LJ::trim( $o->{'subject'} );
|
|
my $reqbody = LJ::trim( $o->{'body'} );
|
|
|
|
# remove the auth portion of any see_request links
|
|
$reqbody = LJ::strip_request_auth($reqbody);
|
|
|
|
unless ($reqsubject) {
|
|
push @$errors, LJ::Lang::ml("error.support.nosummary");
|
|
}
|
|
unless ($reqbody) {
|
|
push @$errors, LJ::Lang::ml("error.support.norequest");
|
|
}
|
|
|
|
my $cats = LJ::Support::load_cats();
|
|
push @$errors, LJ::Lang::ml { "error.support.invalid_category" }
|
|
unless $cats->{ $o->{'spcatid'} + 0 };
|
|
|
|
if (@$errors) { return 0; }
|
|
|
|
if ( LJ::is_enabled("support_request_language") ) {
|
|
$o->{'language'} = undef unless grep { $o->{'language'} eq $_ } ( @LJ::LANGS, "xx" );
|
|
$reqsubject = "[$o->{'language'}] $reqsubject"
|
|
if $o->{'language'} && $o->{'language'} !~ /^en_/;
|
|
}
|
|
|
|
my $dbh = LJ::get_db_writer();
|
|
|
|
my $dup_id = 0;
|
|
my $qsubject = $dbh->quote($reqsubject);
|
|
my $qbody = $dbh->quote($reqbody);
|
|
my $qreqtype = $dbh->quote( $o->{'reqtype'} );
|
|
my $qrequserid = $o->{'requserid'} + 0;
|
|
my $qreqname = $dbh->quote( $o->{'reqname'} );
|
|
my $qreqemail = $dbh->quote( $o->{'reqemail'} );
|
|
my $qspcatid = $o->{'spcatid'} + 0;
|
|
|
|
my $scat = $cats->{$qspcatid};
|
|
|
|
# make the authcode
|
|
my $authcode = LJ::make_auth_code(15);
|
|
my $qauthcode = $dbh->quote($authcode);
|
|
|
|
my $md5 = md5_hex("$qreqname$qreqemail$qsubject$qbody");
|
|
my $sth;
|
|
|
|
$dbh->do("LOCK TABLES support WRITE, duplock WRITE");
|
|
|
|
unless ( $o->{ignore_dup_check} ) {
|
|
$sth = $dbh->prepare(
|
|
"SELECT dupid FROM duplock WHERE realm='support' AND reid=0 AND userid=$qrequserid AND digest='$md5'"
|
|
);
|
|
$sth->execute;
|
|
($dup_id) = $sth->fetchrow_array;
|
|
if ($dup_id) {
|
|
$dbh->do("UNLOCK TABLES");
|
|
return $dup_id;
|
|
}
|
|
}
|
|
|
|
my ( $urlauth, $url, $spid ); # used at the bottom
|
|
|
|
my $sql =
|
|
"INSERT INTO support (spid, reqtype, requserid, reqname, reqemail, state, authcode, spcatid, subject, timecreate, timetouched, timeclosed, timelasthelp) VALUES (NULL, $qreqtype, $qrequserid, $qreqname, $qreqemail, 'open', $qauthcode, $qspcatid, $qsubject, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 0)";
|
|
$sth = $dbh->prepare($sql);
|
|
$sth->execute;
|
|
|
|
if ( $dbh->err ) {
|
|
my $error = $dbh->errstr;
|
|
$dbh->do("UNLOCK TABLES");
|
|
push @$errors, "<b>Database error:</b> (report this)<br>$error";
|
|
return 0;
|
|
}
|
|
$spid = $dbh->{'mysql_insertid'};
|
|
|
|
$dbh->do(
|
|
"INSERT INTO duplock (realm, reid, userid, digest, dupid, instime) VALUES ('support', 0, $qrequserid, '$md5', $spid, NOW())"
|
|
) unless $o->{ignore_dup_check};
|
|
$dbh->do("UNLOCK TABLES");
|
|
|
|
unless ($spid) {
|
|
push @$errors, "<b>Database error:</b> (report this)<br>Didn't get a spid.";
|
|
return 0;
|
|
}
|
|
|
|
# save meta-data for this request
|
|
my @data;
|
|
my $add_data = sub {
|
|
my $q = $dbh->quote( $_[1] );
|
|
return unless $q && $q ne 'NULL';
|
|
push @data, "($spid, '$_[0]', $q)";
|
|
};
|
|
if ( LJ::is_enabled("support_request_language") && $o->{language} ne "xx" ) {
|
|
$add_data->( $_, $o->{$_} ) foreach qw(uniq useragent language);
|
|
}
|
|
else {
|
|
$add_data->( $_, $o->{$_} ) foreach qw(uniq useragent);
|
|
}
|
|
$dbh->do( "INSERT INTO supportprop (spid, prop, value) VALUES " . join( ',', @data ) );
|
|
|
|
$dbh->do( "INSERT INTO supportlog (splid, spid, timelogged, type, faqid, userid, message) "
|
|
. "VALUES (NULL, $spid, UNIX_TIMESTAMP(), 'req', 0, $qrequserid, $qbody)" );
|
|
|
|
my $body;
|
|
my $miniauth = mini_auth( { 'authcode' => $authcode } );
|
|
$url = "$LJ::SITEROOT/support/see_request?id=$spid";
|
|
$urlauth = "$url&auth=$miniauth";
|
|
|
|
$body = LJ::Lang::ml(
|
|
"support.email.confirmation.body",
|
|
{
|
|
sitename => $LJ::SITENAME,
|
|
subject => $o->{'subject'},
|
|
number => $spid,
|
|
url => $urlauth
|
|
}
|
|
);
|
|
|
|
if ( $scat->{user_closeable} ) {
|
|
$body .= "\n\n" . LJ::Lang::ml("support.email.confirmation.close") . "\n\n";
|
|
$body .= "$LJ::SITEROOT/support/act?close;$spid;$authcode";
|
|
}
|
|
|
|
# disable auto-replies for the entire category, or per request
|
|
unless ( $scat->{'no_autoreply'} || $o->{'no_autoreply'} ) {
|
|
LJ::send_mail(
|
|
{
|
|
'to' => $email,
|
|
'from' => $LJ::BOGUS_EMAIL,
|
|
'fromname' =>
|
|
LJ::Lang::ml( "support.email.fromname", { sitename => $LJ::SITENAME } ),
|
|
'charset' => 'utf-8',
|
|
'subject' => LJ::Lang::ml( "support.email.subject", { number => $spid } ),
|
|
'body' => $body
|
|
}
|
|
);
|
|
}
|
|
|
|
support_notify( { spid => $spid, type => 'new' } );
|
|
|
|
# and we're done
|
|
return $spid;
|
|
}
|
|
|
|
sub append_request {
|
|
my $sp = shift; # support request to be appended to.
|
|
my $re = shift; # hashref of attributes of response to be appended
|
|
my $sth;
|
|
|
|
# $re->{'body'}
|
|
# $re->{'type'} (req, answer, comment, internal, screened)
|
|
# $re->{'faqid'}
|
|
# $re->{'remote'} (remote if known)
|
|
# $re->{'uniq'} (uniq of remote)
|
|
# $re->{'tier'} (tier of response if type is answer or internal)
|
|
|
|
my $remote = $re->{'remote'};
|
|
my $posterid = $remote ? $remote->{'userid'} : 0;
|
|
|
|
# check for a sysban
|
|
my $log = { 'uniq' => $re->{'uniq'} };
|
|
if ($remote) {
|
|
|
|
$log->{'user'} = $remote->user;
|
|
$log->{'email'} = $remote->email_raw;
|
|
|
|
if ( LJ::sysban_check( 'support_user', $remote->{'user'} ) ) {
|
|
return LJ::Sysban::block( $remote->{userid}, "Support request blocked based on user",
|
|
$log );
|
|
}
|
|
if ( LJ::sysban_check( 'support_email', $remote->email_raw ) ) {
|
|
return LJ::Sysban::block( $remote->{userid}, "Support request blocked based on email",
|
|
$log );
|
|
}
|
|
}
|
|
|
|
if ( LJ::sysban_check( 'support_uniq', $re->{'uniq'} ) ) {
|
|
my $userid = $remote ? $remote->{'userid'} : 0;
|
|
return LJ::Sysban::block( $userid, "Support request blocked based on uniq", $log );
|
|
}
|
|
|
|
my $message = $re->{'body'};
|
|
$message =~ s/^\s+//;
|
|
$message =~ s/\s+$//;
|
|
|
|
my $dbh = LJ::get_db_writer();
|
|
|
|
my $qmessage = $dbh->quote($message);
|
|
my $qtype = $dbh->quote( $re->{'type'} );
|
|
|
|
my $qfaqid = $re->{'faqid'} ? $re->{'faqid'} + 0 : 0;
|
|
my $quserid = $posterid + 0;
|
|
my $spid = $sp->{'spid'} + 0;
|
|
my $qtier = $re->{'tier'} ? ( $re->{'tier'} + 0 ) . "0" : "NULL";
|
|
|
|
my $sql;
|
|
if ( LJ::is_enabled("support_response_tier") ) {
|
|
$sql =
|
|
"INSERT INTO supportlog (splid, spid, timelogged, type, faqid, userid, message, tier) VALUES (NULL, $spid, UNIX_TIMESTAMP(), $qtype, $qfaqid, $quserid, $qmessage, $qtier)";
|
|
}
|
|
else {
|
|
$sql =
|
|
"INSERT INTO supportlog (splid, spid, timelogged, type, faqid, userid, message) VALUES (NULL, $spid, UNIX_TIMESTAMP(), $qtype, $qfaqid, $quserid, $qmessage)";
|
|
}
|
|
$dbh->do($sql);
|
|
my $splid = $dbh->{'mysql_insertid'};
|
|
|
|
# mark this as an interesting update
|
|
$dbh->do( 'UPDATE support SET timemodified=UNIX_TIMESTAMP() WHERE spid=?', undef, $spid );
|
|
|
|
if ($posterid) {
|
|
|
|
# add to our index of recently replied to support requests per-user.
|
|
$dbh->do( "INSERT IGNORE INTO support_youreplied (userid, spid) VALUES (?, ?)",
|
|
undef, $posterid, $spid );
|
|
die $dbh->errstr if $dbh->err;
|
|
|
|
# and also lazily clean out old stuff:
|
|
$sth =
|
|
$dbh->prepare( "SELECT s.spid FROM support s, support_youreplied yr "
|
|
. "WHERE yr.userid=? AND yr.spid=s.spid AND s.state='closed' "
|
|
. "AND s.timeclosed < UNIX_TIMESTAMP() - 3600*72" );
|
|
$sth->execute($posterid);
|
|
my @to_del;
|
|
push @to_del, $_ while ($_) = $sth->fetchrow_array;
|
|
if (@to_del) {
|
|
my $in = join( ", ", map { $_ + 0 } @to_del );
|
|
$dbh->do( "DELETE FROM support_youreplied WHERE userid=? AND spid IN ($in)",
|
|
undef, $posterid );
|
|
}
|
|
}
|
|
|
|
support_notify( { spid => $spid, splid => $splid, type => 'update' } );
|
|
|
|
return $splid;
|
|
}
|
|
|
|
# userid may be undef/0 in the setting to zero case
|
|
sub set_points {
|
|
my ( $spid, $userid, $points ) = @_;
|
|
|
|
my $dbh = LJ::get_db_writer();
|
|
if ($points) {
|
|
$dbh->do( "REPLACE INTO supportpoints (spid, userid, points) " . "VALUES (?,?,?)",
|
|
undef, $spid, $userid, $points );
|
|
}
|
|
else {
|
|
$userid ||=
|
|
$dbh->selectrow_array( "SELECT userid FROM supportpoints WHERE spid=?", undef, $spid );
|
|
$dbh->do( "DELETE FROM supportpoints WHERE spid=?", undef, $spid );
|
|
}
|
|
|
|
$dbh->do(
|
|
"REPLACE INTO supportpointsum (userid, totpoints, lastupdate) "
|
|
. "SELECT userid, SUM(points), UNIX_TIMESTAMP() FROM supportpoints "
|
|
. "WHERE userid=? GROUP BY 1",
|
|
undef, $userid
|
|
) if $userid;
|
|
|
|
# clear caches
|
|
if ($userid) {
|
|
my $u = LJ::load_userid($userid);
|
|
delete $u->{_supportpointsum} if $u;
|
|
|
|
my $memkey = [ $userid, "supportpointsum:$userid" ];
|
|
LJ::MemCache::delete($memkey);
|
|
}
|
|
}
|
|
|
|
# closes request, assigning points for the last response left to the request
|
|
|
|
sub close_request_with_points {
|
|
my ( $sp, $spcat, $remote ) = @_;
|
|
|
|
my $spid = $sp->{spid} + 0;
|
|
my $dbh = LJ::get_db_writer() or return;
|
|
|
|
# close the request
|
|
$dbh->do(
|
|
'UPDATE support SET state="closed", '
|
|
. 'timeclosed=UNIX_TIMESTAMP(), timemodified=UNIX_TIMESTAMP() WHERE spid=?',
|
|
undef, $spid
|
|
);
|
|
die $dbh->errstr if $dbh->err;
|
|
|
|
# check to see who should get the points
|
|
my $response = $dbh->selectrow_hashref(
|
|
'SELECT splid, timelogged, userid FROM supportlog '
|
|
. 'WHERE spid=? AND type="answer" '
|
|
. 'ORDER BY timelogged DESC LIMIT 1',
|
|
undef, $spid
|
|
);
|
|
die $dbh->errstr if $dbh->err;
|
|
|
|
# deliberately not using LJ::Support::append_request
|
|
# to avoid sysban checks etc.; this sub is supposed to be fast.
|
|
|
|
my $sth =
|
|
$dbh->prepare( 'INSERT INTO supportlog '
|
|
. '(spid, timelogged, type, userid, message) VALUES '
|
|
. '(?, UNIX_TIMESTAMP(), "internal", ?, ?)' );
|
|
|
|
unless ( defined $response ) {
|
|
|
|
# no points awarded
|
|
$sth->execute(
|
|
$spid,
|
|
LJ::want_userid($remote),
|
|
"(Request has been closed as part of mass closure)"
|
|
);
|
|
die $sth->errstr if $sth->err;
|
|
return 1;
|
|
}
|
|
|
|
# award the points
|
|
my $userid = $response->{userid};
|
|
my $points =
|
|
LJ::Support::calc_points( $sp, $response->{timelogged} - $sp->{timecreate}, $spcat );
|
|
|
|
LJ::Support::set_points( $spid, $userid, $points );
|
|
|
|
my $username = LJ::want_user($userid)->display_name;
|
|
|
|
$sth->execute( $spid, LJ::want_userid($remote),
|
|
"(Request has been closed as part of mass closure, "
|
|
. "granting $points points to $username for response #"
|
|
. $response->{splid}
|
|
. ")" );
|
|
die $sth->errstr if $sth->err;
|
|
return 1;
|
|
}
|
|
|
|
sub touch_request {
|
|
my ($spid) = @_;
|
|
|
|
# no touching if the request is locked
|
|
return 0 if LJ::Support::is_locked($spid);
|
|
|
|
my $dbh = LJ::get_db_writer();
|
|
|
|
$dbh->do(
|
|
"UPDATE support"
|
|
. " SET state='open', timeclosed=0, timetouched=UNIX_TIMESTAMP(), timemodified=UNIX_TIMESTAMP()"
|
|
. " WHERE spid=?",
|
|
undef, $spid
|
|
) or return 0;
|
|
|
|
set_points( $spid, undef, 0 );
|
|
|
|
return 1;
|
|
}
|
|
|
|
# Extra email addresses are stored as support properties
|
|
# - nb_extra_addresses: number of extra addresses (if not present, 0)
|
|
# - extra_address_$n: extra address $n (0<=$n<nb_extra_addresses)
|
|
|
|
sub add_email_address {
|
|
my ( $sp, $address ) = @_;
|
|
|
|
# Already present?
|
|
return if grep { $_ eq $address } all_email_addresses($sp);
|
|
|
|
# Add
|
|
my $props = load_props( $sp->{spid} + 0 );
|
|
my $nb_extra_addresses = $props->{nb_extra_addresses} || 0;
|
|
set_prop( $sp->{spid}, 'nb_extra_addresses', $nb_extra_addresses + 1 );
|
|
set_prop( $sp->{spid}, "extra_address_$nb_extra_addresses", $address );
|
|
}
|
|
|
|
sub all_email_addresses {
|
|
my ($sp) = @_;
|
|
|
|
my $props = load_props( $sp->{spid} + 0 );
|
|
my @emails =
|
|
map { $props->{"extra_address_$_"} } 0 .. ( ( $props->{nb_extra_addresses} || 0 ) - 1 );
|
|
|
|
if ( $sp->{reqtype} eq 'email' ) {
|
|
push @emails, $sp->{reqemail};
|
|
}
|
|
else {
|
|
my $u = LJ::load_userid( $sp->{requserid} );
|
|
push @emails, ( $u->email_raw || $sp->{reqemail} );
|
|
}
|
|
|
|
return @emails;
|
|
}
|
|
|
|
sub mail_response_to_user {
|
|
my $sp = shift;
|
|
my $splid = shift;
|
|
|
|
$splid += 0;
|
|
|
|
my $res = load_response($splid);
|
|
my $u;
|
|
$u = LJ::load_userid( $sp->{requserid} ) if $sp->{reqtype} ne 'email';
|
|
|
|
my $spid = $sp->{'spid'} + 0;
|
|
my $faqid = $res->{'faqid'} + 0;
|
|
|
|
my $type = $res->{'type'};
|
|
|
|
# don't mail internal comments (user shouldn't see) or
|
|
# screened responses (have to wait for somebody to approve it first)
|
|
return if ( $type eq "internal" || $type eq "screened" );
|
|
|
|
# the only way it can be zero is if it's a reply to an email, so it's
|
|
# problem the person replying to their own request, so we don't want
|
|
# to mail them:
|
|
return unless ( $res->{'userid'} );
|
|
|
|
# also, don't send them their own replies:
|
|
return if ( $sp->{'requserid'} == $res->{'userid'} );
|
|
|
|
my $lang;
|
|
$lang = LJ::Support::prop( $spid, 'language' )
|
|
if LJ::is_enabled('support_request_language');
|
|
$lang ||= $LJ::DEFAULT_LANG;
|
|
|
|
my $body = "";
|
|
my $dbh = LJ::get_db_writer();
|
|
$body .=
|
|
$type eq "answer"
|
|
? LJ::Lang::ml( "support.email.update.body_a", { subject => $sp->{'subject'} } )
|
|
: LJ::Lang::ml( "support.email.update.body_c", { subject => $sp->{'subject'} } );
|
|
$body .= "\n";
|
|
|
|
my $miniauth = mini_auth($sp);
|
|
$body .= "($LJ::SITEROOT/support/see_request?id=$spid&auth=$miniauth).\n\n";
|
|
|
|
$body .= "=" x 70 . "\n\n";
|
|
if ($faqid) {
|
|
|
|
# Get requesting username and journal URL, or example user's username
|
|
# and journal URL
|
|
my ( $user, $user_url );
|
|
$u ||= LJ::load_user($LJ::EXAMPLE_USER_ACCOUNT);
|
|
$user =
|
|
$u ? $u->user : "<b>" . LJ::Lang::ml("support.email.update.unknown_username") . "</b>";
|
|
$user_url =
|
|
$u
|
|
? $u->journal_base
|
|
: "<b>" . LJ::Lang::ml("support.email.update.unknown_username") . "</b>";
|
|
|
|
my $faq = LJ::Faq->load( $faqid, lang => $lang );
|
|
if ($faq) {
|
|
$faq->render_in_place;
|
|
$body .= LJ::Lang::ml("support.email.update.faqref") . " " . $faq->question_raw . "\n";
|
|
$body .= $faq->url_full;
|
|
$body .= "\n\n";
|
|
}
|
|
}
|
|
|
|
$body .= "$res->{'message'}\n\n";
|
|
|
|
if ( $sp->{_cat}->{user_closeable} ) {
|
|
my $closeurl = "$LJ::SITEROOT/support/act?close;$spid;$sp->{'authcode'}"
|
|
. ( $type eq "answer" ? ";$splid" : "" );
|
|
$body .= LJ::Lang::ml(
|
|
"support.email.update.close",
|
|
{
|
|
close => $closeurl,
|
|
reply => "$LJ::SITEROOT/support/see_request?id=$spid&auth=$miniauth"
|
|
}
|
|
);
|
|
$body .= "\n\n";
|
|
}
|
|
|
|
$body .= LJ::Lang::ml("support.email.update.linkserror");
|
|
|
|
my $fromemail;
|
|
if ( $sp->{_cat}->{'replyaddress'} ) {
|
|
my $miniauth = mini_auth($sp);
|
|
$fromemail = $sp->{_cat}->{'replyaddress'};
|
|
|
|
# insert mini-auth stuff:
|
|
my $rep = "+${spid}z$miniauth\@";
|
|
$fromemail =~ s/\@/$rep/;
|
|
}
|
|
else {
|
|
$fromemail = $LJ::BOGUS_EMAIL;
|
|
$body .= "\n\n" . LJ::Lang::ml("support.email.update.noreply");
|
|
}
|
|
|
|
foreach my $email ( all_email_addresses($sp) ) {
|
|
LJ::send_mail(
|
|
{
|
|
to => $email,
|
|
from => $fromemail,
|
|
fromname => LJ::Lang::ml( 'support.email.fromname', { sitename => $LJ::SITENAME } ),
|
|
charset => 'utf-8',
|
|
subject =>
|
|
LJ::Lang::ml( 'support.email.update.subject', { subject => $sp->{subject} } ),
|
|
body => $body
|
|
}
|
|
);
|
|
}
|
|
|
|
if ( $type eq "answer" ) {
|
|
$dbh->do(
|
|
"UPDATE support SET timelasthelp=UNIX_TIMESTAMP(), timemodified=UNIX_TIMESTAMP() WHERE spid=$spid"
|
|
);
|
|
}
|
|
}
|
|
|
|
sub mini_auth {
|
|
my $sp = shift;
|
|
return substr( $sp->{'authcode'}, 0, 4 );
|
|
}
|
|
|
|
sub support_notify {
|
|
my $params = shift;
|
|
|
|
my $h = DW::TaskQueue->dispatch( DW::Task::SupportNotify->new($params) );
|
|
return $h ? 1 : 0;
|
|
}
|
|
|
|
package LJ::Worker::SupportNotify;
|
|
use base 'TheSchwartz::Worker';
|
|
|
|
sub work {
|
|
my ( $class, $job ) = @_;
|
|
my $a = $job->arg;
|
|
|
|
# load basic stuff common to both paths
|
|
my $type = $a->{type};
|
|
my $spid = $a->{spid} + 0;
|
|
my $load_body = $type eq 'new' ? 1 : 0;
|
|
my $sp = LJ::Support::load_request( $spid, $load_body, { force => 1 } ); # force from master
|
|
|
|
# we're only going to be reading anyway, but these jobs
|
|
# sometimes get processed faster than replication allows,
|
|
# causing the message not to load from the reader
|
|
my $dbr = LJ::get_db_writer();
|
|
|
|
# now branch a bit to select the right user information
|
|
my $level = $type eq 'new' ? "'new', 'all'" : "'all'";
|
|
my $data = $dbr->selectcol_arrayref(
|
|
"SELECT userid FROM supportnotify WHERE spcatid=? AND level IN ($level)",
|
|
undef, $sp->{_cat}{spcatid} );
|
|
my $userids = LJ::load_userids(@$data);
|
|
|
|
# prepare the email
|
|
my $body;
|
|
my @emails;
|
|
|
|
if ( $type eq 'new' ) {
|
|
my $show_name = $sp->{reqname};
|
|
if ( $sp->{reqtype} eq 'user' ) {
|
|
my $u = LJ::load_userid( $sp->{requserid} );
|
|
$show_name = $u->display_name if $u;
|
|
}
|
|
|
|
$body = LJ::Lang::ml(
|
|
"support.email.notif.new.body2",
|
|
{
|
|
sitename => $LJ::SITENAMESHORT,
|
|
category => $sp->{_cat}{catname},
|
|
subject => $sp->{subject},
|
|
username => LJ::trim($show_name),
|
|
url => "$LJ::SITEROOT/support/see_request?id=$spid",
|
|
text => $sp->{body}
|
|
}
|
|
);
|
|
$body .= "\n\n" . "=" x 4 . "\n\n";
|
|
$body .= LJ::Lang::ml(
|
|
"support.email.notif.new.footer",
|
|
{
|
|
url => "$LJ::SITEROOT/support/see_request?id=$spid",
|
|
setting => "$LJ::SITEROOT/support/changenotify"
|
|
}
|
|
);
|
|
|
|
foreach my $u ( values %$userids ) {
|
|
next unless $u->should_receive_support_notifications( $sp->{_cat}{spcatid} );
|
|
push @emails, $u->email_raw;
|
|
}
|
|
|
|
}
|
|
elsif ( $type eq 'update' ) {
|
|
|
|
# load the response we want to stuff in the email
|
|
my ( $resp, $rtype, $posterid, $faqid ) =
|
|
$dbr->selectrow_array(
|
|
"SELECT message, type, userid, faqid FROM supportlog WHERE spid = ? AND splid = ?",
|
|
undef, $sp->{spid}, $a->{splid} + 0 );
|
|
|
|
# set up $show_name for this environment
|
|
my $show_name;
|
|
if ($posterid) {
|
|
my $u = LJ::load_userid($posterid);
|
|
$show_name = $u->display_name if $u;
|
|
}
|
|
|
|
$show_name ||= $sp->{reqname};
|
|
|
|
# set up $response_type for this environment
|
|
my $response_type = {
|
|
req => "New Request", # not applicable here
|
|
answer => "Answer",
|
|
comment => "Comment",
|
|
internal => "Internal Comment",
|
|
screened => "Screened Answer",
|
|
}->{$rtype};
|
|
|
|
# build body
|
|
$body = LJ::Lang::ml(
|
|
"support.email.notif.update.body4",
|
|
{
|
|
sitename => $LJ::SITENAMESHORT,
|
|
category => $sp->{_cat}{catname},
|
|
subject => $sp->{subject},
|
|
username => LJ::trim($show_name),
|
|
url => "$LJ::SITEROOT/support/see_request?id=$spid",
|
|
type => $response_type
|
|
}
|
|
);
|
|
if ($faqid) {
|
|
|
|
# need to set up $lang
|
|
my ( $lang, $u );
|
|
$u = LJ::load_userid($posterid) if $posterid;
|
|
$lang = LJ::Support::prop( $spid, 'language' )
|
|
if LJ::is_enabled('support_request_language');
|
|
$lang ||= $LJ::DEFAULT_LANG;
|
|
|
|
# now actually get the FAQ
|
|
my $faq = LJ::Faq->load( $faqid, lang => $lang );
|
|
if ($faq) {
|
|
$faq->render_in_place;
|
|
my $faqref = $faq->question_raw . " " . $faq->url_full;
|
|
|
|
# now add it to the e-mail!
|
|
$body .= "\n"
|
|
. LJ::Lang::ml(
|
|
"support.email.notif.update.body.faqref",
|
|
{
|
|
faqref => $faqref
|
|
}
|
|
);
|
|
$body .= "\n";
|
|
}
|
|
}
|
|
$body .= LJ::Lang::ml(
|
|
"support.email.notif.update.body.text",
|
|
{
|
|
text => $resp
|
|
}
|
|
);
|
|
$body .= "\n\n" . "=" x 4 . "\n\n";
|
|
$body .= LJ::Lang::ml(
|
|
"support.email.notif.update.footer",
|
|
{
|
|
url => "$LJ::SITEROOT/support/see_request?id=$spid",
|
|
setting => "$LJ::SITEROOT/support/changenotify"
|
|
}
|
|
);
|
|
|
|
# now see who this should be sent to
|
|
foreach my $u ( values %$userids ) {
|
|
next unless $u->should_receive_support_notifications( $sp->{_cat}{spcatid} );
|
|
next unless LJ::Support::can_read_response( $sp, $u, $rtype, $posterid );
|
|
next if $posterid == $u->id && !$u->prop('opt_getselfsupport');
|
|
push @emails, $u->email_raw;
|
|
}
|
|
}
|
|
|
|
# send the email
|
|
LJ::send_mail(
|
|
{
|
|
bcc => join( ', ', @emails ),
|
|
from => $LJ::BOGUS_EMAIL,
|
|
fromname => LJ::Lang::ml( "support.email.fromname", { sitename => $LJ::SITENAME } ),
|
|
charset => 'utf-8',
|
|
subject => (
|
|
$type eq 'update'
|
|
? LJ::Lang::ml( "support.email.notif.update.subject", { number => $spid } )
|
|
: LJ::Lang::ml( "support.email.subject", { number => $spid } )
|
|
),
|
|
body => $body,
|
|
wrap => 1,
|
|
}
|
|
) if @emails;
|
|
|
|
$job->completed;
|
|
return 1;
|
|
}
|
|
|
|
sub keep_exit_status_for { 0 }
|
|
sub grab_for { 30 }
|
|
sub max_retries { 5 }
|
|
|
|
sub retry_delay {
|
|
my ( $class, $fails ) = @_;
|
|
return 30;
|
|
}
|
|
|
|
1;
|