diff options
-rwxr-xr-x | bin/send-alerts | 25 | ||||
-rwxr-xr-x | bin/send-reports | 9 | ||||
-rw-r--r-- | conf/general-example | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/Alert.pm | 242 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Alert.pm | 16 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report/New.pm | 18 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report/Update.pm | 13 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Reports.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Tokens.pm | 13 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/Alert.pm | 12 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/ResultSet/AlertType.pm | 204 | ||||
-rw-r--r-- | perllib/FixMyStreet/TestMech.pm | 5 | ||||
-rw-r--r-- | t/app/controller/alert_new.t | 141 | ||||
-rw-r--r-- | t/app/controller/report_new.t | 17 | ||||
-rw-r--r-- | templates/web/default/alert/list.html | 4 | ||||
-rw-r--r-- | templates/web/default/report/display.html | 2 |
16 files changed, 399 insertions, 324 deletions
diff --git a/bin/send-alerts b/bin/send-alerts index c52af4059..89dc18ee7 100755 --- a/bin/send-alerts +++ b/bin/send-alerts @@ -5,37 +5,18 @@ # # Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. # Email: matthew@mysociety.org. WWW: http://www.mysociety.org -# -# $Id: send-alerts,v 1.5 2010-01-06 16:50:25 louise Exp $ use strict; require 5.8.0; use CGI; # XXX - -# Horrible boilerplate to set up appropriate library paths. -use FindBin; -use lib "$FindBin::Bin/../perllib"; -use lib "$FindBin::Bin/../commonlib/perllib"; use CronFns; use mySociety::Config; -use mySociety::DBHandle qw(dbh); -use FixMyStreet::Alert; - -BEGIN { - mySociety::Config::set_file("$FindBin::Bin/../conf/general"); - mySociety::DBHandle::configure( - Name => mySociety::Config::get('BCI_DB_NAME'), - User => mySociety::Config::get('BCI_DB_USER'), - Password => mySociety::Config::get('BCI_DB_PASS'), - Host => mySociety::Config::get('BCI_DB_HOST', undef), - Port => mySociety::Config::get('BCI_DB_PORT', undef) - ); -} +use FixMyStreet::App; my $site = CronFns::site(mySociety::Config::get('BASE_URL')); CronFns::language($site); -my $testing_email = mySociety::Config::get('TESTING_EMAIL'); -FixMyStreet::Alert::email_alerts($testing_email); + +FixMyStreet::App->model('DB::AlertType')->email_alerts(); diff --git a/bin/send-reports b/bin/send-reports index feee74c76..fbbd3db5e 100755 --- a/bin/send-reports +++ b/bin/send-reports @@ -192,14 +192,11 @@ while (my $row = $unsent->next) { die 'Report not going anywhere for ID ' . $row->id . '!'; } - my $testing_email = mySociety::Config::get('TESTING_EMAIL'); - if ($row->user->email eq $testing_email) { - @recips = ( $testing_email ); - $send_web = 0; - $send_email = 1; - } elsif (mySociety::Config::get('STAGING_SITE')) { + if (mySociety::Config::get('STAGING_SITE')) { # on a staging server send emails to ourselves rather than the councils @recips = ( mySociety::Config::get('CONTACT_EMAIL') ); + $send_web = 0; + $send_email = 1; } elsif ($site eq 'emptyhomes') { my $council = $row->council; my $country = $areas_info->{$council}->{country}; diff --git a/conf/general-example b/conf/general-example index 7e750de96..510557e46 100644 --- a/conf/general-example +++ b/conf/general-example @@ -30,7 +30,6 @@ define('OPTION_BASE_URL', 'http://www.example.org'); # Which country are you operating in? ISO3166-alpha2 code please define('OPTION_COUNTRY', 'GB'); -define('OPTION_TESTING_EMAIL', 'testing@example.com'); define('OPTION_EMAIL_DOMAIN', 'example.org'); define('OPTION_CONTACT_EMAIL', 'team@'.OPTION_EMAIL_DOMAIN); define('OPTION_TEST_EMAIL_PREFIX', null); diff --git a/perllib/FixMyStreet/Alert.pm b/perllib/FixMyStreet/Alert.pm deleted file mode 100644 index 1857abbba..000000000 --- a/perllib/FixMyStreet/Alert.pm +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/perl -w -# -# FixMyStreet::Alert.pm -# Alerts by email or RSS. -# -# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. -# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ -# -# $Id: Alert.pm,v 1.71 2010-01-06 16:50:27 louise Exp $ - -package FixMyStreet::Alert::Error; - -use Error qw(:try); - -@FixMyStreet::Alert::Error::ISA = qw(Error::Simple); - -package FixMyStreet::Alert; - -use strict; -use Error qw(:try); -use File::Slurp; -use FindBin; -use POSIX qw(strftime); - -use Cobrand; -use mySociety::AuthToken; -use mySociety::DBHandle qw(dbh); -use mySociety::Email; -use mySociety::EmailUtil; -use mySociety::Gaze; -use mySociety::Locale; -use mySociety::MaPit; -use mySociety::Random qw(random_bytes); - -# Add a new alert -sub create ($$$$;@) { - my ($email, $alert_type, $cobrand, $cobrand_data, @params) = @_; - my $already = 0; - if (0==@params) { - ($already) = dbh()->selectrow_array('select id from alert where alert_type=? and email=? limit 1', - {}, $alert_type, $email); - } elsif (1==@params) { - ($already) = dbh()->selectrow_array('select id from alert where alert_type=? and email=? and parameter=? limit 1', - {}, $alert_type, $email, @params); - } elsif (2==@params) { - ($already) = dbh()->selectrow_array('select id from alert where alert_type=? and email=? and parameter=? and parameter2=? limit 1', - {}, $alert_type, $email, @params); - } - return $already if $already; - - my $id = dbh()->selectrow_array("select nextval('alert_id_seq');"); - my $lang = $mySociety::Locale::lang; - if (0==@params) { - dbh()->do('insert into alert (id, alert_type, email, lang, cobrand, cobrand_data) - values (?, ?, ?, ?, ?, ?)', {}, $id, $alert_type, $email, $lang, $cobrand, $cobrand_data); - } elsif (1==@params) { - dbh()->do('insert into alert (id, alert_type, parameter, email, lang, cobrand, cobrand_data) - values (?, ?, ?, ?, ?, ?, ?)', {}, $id, $alert_type, @params, $email, $lang, $cobrand, $cobrand_data); - } elsif (2==@params) { - dbh()->do('insert into alert (id, alert_type, parameter, parameter2, email, lang, cobrand, cobrand_data) - values (?, ?, ?, ?, ?, ?, ?, ?)', {}, $id, $alert_type, @params, $email, $lang, $cobrand, $cobrand_data); - } - dbh()->commit(); - return $id; -} - -sub confirm ($) { - my $id = shift; - dbh()->do("update alert set confirmed=1, whendisabled=null where id=?", {}, $id); - dbh()->commit(); -} - -# Delete an alert -sub delete ($) { - my $id = shift; - dbh()->do('update alert set whendisabled = ms_current_timestamp() where id = ?', {}, $id); - dbh()->commit(); -} - -# This makes load of assumptions, but still should be useful -# -# Child must have confirmed, id, email, state(!) columns -# If parent/child, child table must also have name and text -# and foreign key to parent must be PARENT_id - -sub email_alerts ($) { - my ($testing_email) = @_; - my $url; - my $q = dbh()->prepare("select * from alert_type where ref not like '%local_problems%'"); - $q->execute(); - my $testing_email_clause = ''; - while (my $alert_type = $q->fetchrow_hashref) { - my $ref = $alert_type->{ref}; - my $head_table = $alert_type->{head_table}; - my $item_table = $alert_type->{item_table}; - my $testing_email_clause = "and $item_table.email <> '$testing_email'" if $testing_email; - my $query = 'select alert.id as alert_id, alert.email as alert_email, alert.lang as alert_lang, alert.cobrand as alert_cobrand, - alert.cobrand_data as alert_cobrand_data, alert.parameter as alert_parameter, alert.parameter2 as alert_parameter2, '; - if ($head_table) { - $query .= " - $item_table.id as item_id, $item_table.name as item_name, $item_table.text as item_text, - $head_table.* - from alert - inner join $item_table on alert.parameter::integer = $item_table.${head_table}_id - inner join $head_table on alert.parameter::integer = $head_table.id"; - } else { - $query .= " $item_table.*, - $item_table.id as item_id - from alert, $item_table"; - } - $query .= " - where alert_type='$ref' and whendisabled is null and $item_table.confirmed >= whensubscribed - and $item_table.confirmed >= ms_current_timestamp() - '7 days'::interval - and (select whenqueued from alert_sent where alert_sent.alert_id = alert.id and alert_sent.parameter::integer = $item_table.id) is null - and $item_table.email <> alert.email - $testing_email_clause - and $alert_type->{item_where} - and alert.confirmed = 1 - order by alert.id, $item_table.confirmed"; - # XXX Ugh - needs work - $query =~ s/\?/alert.parameter/ if ($query =~ /\?/); - $query =~ s/\?/alert.parameter2/ if ($query =~ /\?/); - $query = dbh()->prepare($query); - $query->execute(); - my $last_alert_id; - my %data = ( template => $alert_type->{template}, data => '' ); - while (my $row = $query->fetchrow_hashref) { - # Cobranded and non-cobranded messages can share a database. In this case, the conf file - # should specify a vhost to send the reports for each cobrand, so that they don't get sent - # more than once if there are multiple vhosts running off the same database. The email_host - # call checks if this is the host that sends mail for this cobrand. - next unless (Cobrand::email_host($row->{alert_cobrand})); - - dbh()->do('insert into alert_sent (alert_id, parameter) values (?,?)', {}, $row->{alert_id}, $row->{item_id}); - if ($last_alert_id && $last_alert_id != $row->{alert_id}) { - _send_aggregated_alert_email(%data); - %data = ( template => $alert_type->{template}, data => '' ); - } - - # create problem status message for the templates - $data{state_message} = - $row->{state} eq 'fixed' - ? _("This report is currently marked as fixed.") - : _("This report is currently marked as open."); - - $url = Cobrand::base_url_for_emails($row->{alert_cobrand}, $row->{alert_cobrand_data}); - if ($row->{item_text}) { - $data{problem_url} = $url . "/report/" . $row->{id}; - $data{data} .= $row->{item_name} . ' : ' if $row->{item_name}; - $data{data} .= $row->{item_text} . "\n\n------\n\n"; - } else { - $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n"; - } - if (!$data{alert_email}) { - %data = (%data, %$row); - if ($ref eq 'area_problems' || $ref eq 'council_problems' || $ref eq 'ward_problems') { - my $va_info = mySociety::MaPit::call('area', $row->{alert_parameter}); - $data{area_name} = $va_info->{name}; - } - if ($ref eq 'ward_problems') { - my $va_info = mySociety::MaPit::call('area', $row->{alert_parameter2}); - $data{ward_name} = $va_info->{name}; - } - } - $data{cobrand} = $row->{alert_cobrand}; - $data{cobrand_data} = $row->{alert_cobrand_data}; - $data{lang} = $row->{alert_lang}; - $last_alert_id = $row->{alert_id}; - } - if ($last_alert_id) { - _send_aggregated_alert_email(%data); - } - } - - # Nearby done separately as the table contains the parameters - my $template = dbh()->selectrow_array("select template from alert_type where ref = 'local_problems'"); - my $query = "select * from alert where alert_type='local_problems' and whendisabled is null and confirmed=1 order by id"; - $query = dbh()->prepare($query); - $query->execute(); - while (my $alert = $query->fetchrow_hashref) { - next unless (Cobrand::email_host($alert->{cobrand})); - my $longitude = $alert->{parameter}; - my $latitude = $alert->{parameter2}; - $url = Cobrand::base_url_for_emails($alert->{cobrand}, $alert->{cobrand_data}); - my ($site_restriction, $site_id) = Cobrand::site_restriction($alert->{cobrand}, $alert->{cobrand_data}); - my $d = mySociety::Gaze::get_radius_containing_population($latitude, $longitude, 200000); - # Convert integer to GB locale string (with a ".") - $d = mySociety::Locale::in_gb_locale { - sprintf("%f", int($d*10+0.5)/10); - }; - my $testing_email_clause = "and problem.email <> '$testing_email'" if $testing_email; - my %data = ( template => $template, data => '', alert_id => $alert->{id}, alert_email => $alert->{email}, lang => $alert->{lang}, cobrand => $alert->{cobrand}, cobrand_data => $alert->{cobrand_data} ); - my $q = "select * from problem_find_nearby(?, ?, ?) as nearby, problem - where nearby.problem_id = problem.id and problem.state in ('confirmed', 'fixed') - and problem.confirmed >= ? and problem.confirmed >= ms_current_timestamp() - '7 days'::interval - and (select whenqueued from alert_sent where alert_sent.alert_id = ? and alert_sent.parameter::integer = problem.id) is null - and problem.email <> ? - $testing_email_clause - $site_restriction - order by confirmed desc"; - $q = dbh()->prepare($q); - $q->execute($latitude, $longitude, $d, $alert->{whensubscribed}, $alert->{id}, $alert->{email}); - while (my $row = $q->fetchrow_hashref) { - dbh()->do('insert into alert_sent (alert_id, parameter) values (?,?)', {}, $alert->{id}, $row->{id}); - $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n"; - } - _send_aggregated_alert_email(%data) if $data{data}; - } -} - -sub _send_aggregated_alert_email(%) { - my %data = @_; - Cobrand::set_lang_and_domain($data{cobrand}, $data{lang}, 1); - - $data{unsubscribe_url} = Cobrand::base_url_for_emails($data{cobrand}, $data{cobrand_data}) . '/A/' - . mySociety::AuthToken::store('alert', { id => $data{alert_id}, type => 'unsubscribe', email => $data{alert_email} } ); - my $template = "$FindBin::Bin/../templates/emails/$data{template}"; - if ($data{cobrand}) { - my $template_cobrand = "$FindBin::Bin/../templates/emails/$data{cobrand}/$data{template}"; - $template = $template_cobrand if -e $template_cobrand; - } - $template = File::Slurp::read_file($template); - my $sender = Cobrand::contact_email($data{cobrand}); - my $sender_name = Cobrand::contact_name($data{cobrand}); - (my $from = $sender) =~ s/team/fms-DO-NOT-REPLY/; # XXX - my $email = mySociety::Email::construct_email({ - _template_ => _($template), - _parameters_ => \%data, - From => [ $from, _($sender_name) ], - To => $data{alert_email}, - 'Message-ID' => sprintf('<alert-%s-%s@mysociety.org>', time(), unpack('h*', random_bytes(5, 1))), - }); - - my $result = mySociety::EmailUtil::send_email($email, $sender, $data{alert_email}); - if ($result == mySociety::EmailUtil::EMAIL_SUCCESS) { - dbh()->commit(); - } else { - dbh()->rollback(); - throw FixMyStreet::Alert::Error("Failed to send alert $data{alert_id}!"); - } -} - diff --git a/perllib/FixMyStreet/App/Controller/Alert.pm b/perllib/FixMyStreet/App/Controller/Alert.pm index bea5345e3..b3067abc9 100644 --- a/perllib/FixMyStreet/App/Controller/Alert.pm +++ b/perllib/FixMyStreet/App/Controller/Alert.pm @@ -63,6 +63,9 @@ sub subscribe : Path('subscribe') : Args(0) { elsif ( exists $c->req->params->{'rznvy'} ) { $c->detach('subscribe_email'); } + elsif ( $c->req->params->{'id'} ) { + $c->go('updates'); + } # shouldn't get to here but if we have then do something sensible $c->go('index'); @@ -140,7 +143,7 @@ sub subscribe_email : Private { $c->forward('set_local_alert_options'); } else { - throw FixMyStreet::Alert::Error('Invalid type'); + $c->detach( '/page_error_404_not_found', [ 'Invalid type' ] ); } $c->forward('create_alert'); @@ -162,7 +165,7 @@ sub updates : Path('updates') : Args(0) { =head2 confirm -Confirm signup to an alert. Forwarded here from Tokens. +Confirm signup to or unsubscription from an alert. Forwarded here from Tokens. =cut @@ -173,11 +176,9 @@ sub confirm : Private { if ( $c->stash->{confirm_type} eq 'subscribe' ) { $alert->confirm(); - $alert->update; } elsif ( $c->stash->{confirm_type} eq 'unsubscribe' ) { - $alert->delete(); - $alert->update; + $alert->disable(); } } @@ -198,8 +199,9 @@ sub create_alert : Private { unless ($alert) { $options->{cobrand} = $c->cobrand->moniker(); $options->{cobrand_data} = $c->cobrand->extra_update_data(); + $options->{lang} = $c->stash->{lang_code}; - if ( $c->user && $c->user->id == $c->stash->{alert_user}->id ) { + if ( $c->user && $c->stash->{alert_user}->in_storage && $c->user->id == $c->stash->{alert_user}->id ) { $options->{confirmed} = 1; } @@ -258,7 +260,7 @@ sub set_local_alert_options : Private { m{ \A local: ( [\+\-]? \d+ \.? \d* ) : ( [\+\-]? \d+ \.? \d* ) }xms ) { $type = 'local_problems'; - push @params, $1, $2; + push @params, $2, $1; # Note alert parameters are lon,lat } my $options = { diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 671454272..9e0193d3f 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -931,8 +931,10 @@ sub redirect_or_confirm_creation : Private { my ( $self, $c ) = @_; my $report = $c->stash->{report}; - # If confirmed send the user straigh there. + # If confirmed send the user straight there. if ( $report->confirmed ) { + # Subscribe problem reporter to email updates + $c->forward( 'create_reporter_alert' ); my $report_uri = $c->uri_for( '/report', $report->id ); $c->res->redirect($report_uri); $c->detach; @@ -950,6 +952,20 @@ sub redirect_or_confirm_creation : Private { $c->stash->{email_type} = 'problem'; } +sub create_reporter_alert : Private { + my ( $self, $c ) = @_; + + my $problem = $c->stash->{report}; + my $alert = $c->model('DB::Alert')->find_or_create( { + user => $problem->user, + alert_type => 'new_updates', + parameter => $problem->id, + cobrand => $problem->cobrand, + cobrand_data => $problem->cobrand_data, + lang => $problem->lang, + } )->confirm; +} + =head2 redirect_to_around Redirect the user to '/around' passing along all the relevant parameters. diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm index eadf2beea..a1120470b 100644 --- a/perllib/FixMyStreet/App/Controller/Report/Update.pm +++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm @@ -315,12 +315,15 @@ sub signup_for_alerts : Private { my ( $self, $c ) = @_; if ( $c->stash->{add_alert} ) { + my $update = $c->stash->{update}; my $alert = $c->model('DB::Alert')->find_or_create( - user => $c->stash->{update}->user, - alert_type => 'new_updates', - parameter => $c->stash->{update}->problem_id, - confirmed => 1, - ); + user => $update->user, + alert_type => 'new_updates', + parameter => $update->problem_id, + cobrand => $update->cobrand, + cobrand_data => $update->cobrand_data, + lang => $update->lang, + )->confirm(); $alert->update; } diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm index 92dbced15..993cd752e 100644 --- a/perllib/FixMyStreet/App/Controller/Reports.pm +++ b/perllib/FixMyStreet/App/Controller/Reports.pm @@ -4,7 +4,6 @@ use namespace::autoclean; use Problems; use POSIX qw(strcoll); -# use FixMyStreet::Alert; use mySociety::MaPit; use mySociety::VotingArea; diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm index 792c875fd..92a44e756 100644 --- a/perllib/FixMyStreet/App/Controller/Tokens.pm +++ b/perllib/FixMyStreet/App/Controller/Tokens.pm @@ -4,8 +4,6 @@ use namespace::autoclean; BEGIN { extends 'Catalyst::Controller'; } -use FixMyStreet::Alert; - =head1 NAME FixMyStreet::App::Controller::Tokens - Handle auth tokens @@ -57,15 +55,8 @@ sub confirm_problem : Path('/P') { ) if $problem->state eq 'unconfirmed'; # Subscribe problem reporter to email updates - my $alert = $c->model('DB::Alert')->find_or_create( - { - user => $problem->user, - alert_type => 'new_updates', - cobrand => $problem->cobrand, - cobrand_data => $problem->cobrand_data, - parameter => $problem->id - } - )->confirm; + $c->stash->{report} = $c->stash->{problem}; + $c->forward( '/report/new/create_reporter_alert' ); # log the problem creation user in to the site $c->authenticate( { email => $problem->user->email }, 'no_password' ); diff --git a/perllib/FixMyStreet/DB/Result/Alert.pm b/perllib/FixMyStreet/DB/Result/Alert.pm index b99c83a45..53cb96ff4 100644 --- a/perllib/FixMyStreet/DB/Result/Alert.pm +++ b/perllib/FixMyStreet/DB/Result/Alert.pm @@ -85,10 +85,18 @@ Sets the state of the alert to confirmed. sub confirm { my $self = shift; - return if $self->confirmed and $self->confirmed == 1 and $self->whendisabled ne 'null'; - $self->confirmed(1); $self->whendisabled(undef); + $self->update; + + return 1; +} + +sub disable { + my $self = shift; + + $self->whendisabled( \'ms_current_timestamp()' ); + $self->update; return 1; } diff --git a/perllib/FixMyStreet/DB/ResultSet/AlertType.pm b/perllib/FixMyStreet/DB/ResultSet/AlertType.pm new file mode 100644 index 000000000..405210861 --- /dev/null +++ b/perllib/FixMyStreet/DB/ResultSet/AlertType.pm @@ -0,0 +1,204 @@ +package FixMyStreet::DB::ResultSet::AlertType; +use base 'DBIx::Class::ResultSet'; + +use strict; +use warnings; + +use File::Slurp; + +use mySociety::DBHandle qw(dbh); +use mySociety::EmailUtil; +use mySociety::Gaze; +use mySociety::Locale; +use mySociety::MaPit; + +# Child must have confirmed, id, email, state(!) columns +# If parent/child, child table must also have name and text +# and foreign key to parent must be PARENT_id +sub email_alerts ($) { + my ( $rs ) = @_; + + my $q = $rs->search( { ref => { -not_like => '%local_problems%' } } ); + while (my $alert_type = $q->next) { + my $ref = $alert_type->ref; + my $head_table = $alert_type->head_table; + my $item_table = $alert_type->item_table; + my $query = 'select alert.id as alert_id, alert_user.email as alert_email, alert.lang as alert_lang, alert.cobrand as alert_cobrand, + alert.cobrand_data as alert_cobrand_data, alert.parameter as alert_parameter, alert.parameter2 as alert_parameter2, '; + if ($head_table) { + $query .= " + $item_table.id as item_id, $item_table.name as item_name, $item_table.text as item_text, + $head_table.* + from alert + inner join $item_table on alert.parameter::integer = $item_table.${head_table}_id + inner join $head_table on alert.parameter::integer = $head_table.id + inner join users as alert_user on alert.user_id = alert_user.id"; + } else { + $query .= " $item_table.*, + $item_table.id as item_id + from alert + cross join $item_table + inner join users as alert_user on alert.user_id = alert_user.id"; + } + $query .= " + where alert_type='$ref' and whendisabled is null and $item_table.confirmed >= whensubscribed + and $item_table.confirmed >= ms_current_timestamp() - '7 days'::interval + and (select whenqueued from alert_sent where alert_sent.alert_id = alert.id and alert_sent.parameter::integer = $item_table.id) is null + and $item_table.user_id <> alert.user_id + and " . $alert_type->item_where . " + and alert.confirmed = 1 + order by alert.id, $item_table.confirmed"; + # XXX Ugh - needs work + $query =~ s/\?/alert.parameter/ if ($query =~ /\?/); + $query =~ s/\?/alert.parameter2/ if ($query =~ /\?/); + $query = dbh()->prepare($query); + $query->execute(); + my $last_alert_id; + my %data = ( template => $alert_type->template, data => '' ); + while (my $row = $query->fetchrow_hashref) { + + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($row->{alert_cobrand})->new(); + + # Cobranded and non-cobranded messages can share a database. In this case, the conf file + # should specify a vhost to send the reports for each cobrand, so that they don't get sent + # more than once if there are multiple vhosts running off the same database. The email_host + # call checks if this is the host that sends mail for this cobrand. + next unless $cobrand->email_host; + + FixMyStreet::App->model('DB::AlertSent')->create( { + alert_id => $row->{alert_id}, + parameter => $row->{item_id}, + } ); + if ($last_alert_id && $last_alert_id != $row->{alert_id}) { + _send_aggregated_alert_email(%data); + %data = ( template => $alert_type->template, data => '' ); + } + + # create problem status message for the templates + $data{state_message} = + $row->{state} eq 'fixed' + ? _("This report is currently marked as fixed.") + : _("This report is currently marked as open."); + + my $url = $cobrand->base_url_for_emails( $row->{alert_cobrand_data} ); + if ($row->{item_text}) { + $data{problem_url} = $url . "/report/" . $row->{id}; + $data{data} .= $row->{item_name} . ' : ' if $row->{item_name}; + $data{data} .= $row->{item_text} . "\n\n------\n\n"; + } else { + $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n"; + } + if (!$data{alert_email}) { + %data = (%data, %$row); + if ($ref eq 'area_problems' || $ref eq 'council_problems' || $ref eq 'ward_problems') { + my $va_info = mySociety::MaPit::call('area', $row->{alert_parameter}); + $data{area_name} = $va_info->{name}; + } + if ($ref eq 'ward_problems') { + my $va_info = mySociety::MaPit::call('area', $row->{alert_parameter2}); + $data{ward_name} = $va_info->{name}; + } + } + $data{cobrand} = $row->{alert_cobrand}; + $data{cobrand_data} = $row->{alert_cobrand_data}; + $data{lang} = $row->{alert_lang}; + $last_alert_id = $row->{alert_id}; + } + if ($last_alert_id) { + _send_aggregated_alert_email(%data); + } + } + + # Nearby done separately as the table contains the parameters + my $template = $rs->find( { ref => 'local_problems' } )->template; + my $query = FixMyStreet::App->model('DB::Alert')->search( { + alert_type => 'local_problems', + whendisabled => undef, + confirmed => 1 + }, { + order_by => 'id' + } ); + while (my $alert = $query->next) { + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($alert->cobrand)->new(); + next unless $cobrand->email_host; + + my $longitude = $alert->parameter; + my $latitude = $alert->parameter2; + my $url = $cobrand->base_url_for_emails( $alert->cobrand_data ); + my ($site_restriction, $site_id) = $cobrand->site_restriction( $alert->cobrand_data ); + my $d = mySociety::Gaze::get_radius_containing_population($latitude, $longitude, 200000); + # Convert integer to GB locale string (with a ".") + $d = mySociety::Locale::in_gb_locale { + sprintf("%f", int($d*10+0.5)/10); + }; + my %data = ( template => $template, data => '', alert_id => $alert->id, alert_email => $alert->user->email, lang => $alert->lang, cobrand => $alert->cobrand, cobrand_data => $alert->cobrand_data ); + my $q = "select problem.id, problem.title from problem_find_nearby(?, ?, ?) as nearby, problem, users + where nearby.problem_id = problem.id + and problem.user_id = users.id + and problem.state in ('confirmed', 'fixed') + and problem.confirmed >= ? and problem.confirmed >= ms_current_timestamp() - '7 days'::interval + and (select whenqueued from alert_sent where alert_sent.alert_id = ? and alert_sent.parameter::integer = problem.id) is null + and users.email <> ? + $site_restriction + order by confirmed desc"; + $q = dbh()->prepare($q); + $q->execute($latitude, $longitude, $d, $alert->whensubscribed, $alert->id, $alert->user->email); + while (my $row = $q->fetchrow_hashref) { + FixMyStreet::App->model('DB::AlertSent')->create( { + alert_id => $alert->id, + parameter => $row->{id}, + } ); + $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n"; + } + _send_aggregated_alert_email(%data) if $data{data}; + } +} + +sub _send_aggregated_alert_email(%) { + my %data = @_; + + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($data{cobrand})->new(); + + $cobrand->set_lang_and_domain( $data{lang}, 1 ); + + my $token = FixMyStreet::App->model("DB::Token")->new_result( { + scope => 'alert', + data => { + id => $data{alert_id}, + type => 'unsubscribe', + email => $data{alert_email}, + } + } ); + $data{unsubscribe_url} = $cobrand->base_url_for_emails( $data{cobrand_data} ) . '/A/' . $token->token; + + my $template = FixMyStreet->path_to( + "templates", "email", $cobrand->moniker, "$data{template}.txt" + )->stringify; + my $template_cobrand = FixMyStreet->path_to( + "templates", "email", $cobrand->moniker, $data{lang}, "$data{template}.txt" + )->stringify; + $template = $template_cobrand if -e $template_cobrand; + $template = File::Slurp::read_file($template); + + my $sender = $cobrand->contact_email; + (my $from = $sender) =~ s/team/fms-DO-NOT-REPLY/; # XXX + my $result = FixMyStreet::App->send_email_cron( + { + _template_ => $template, + _parameters_ => \%data, + From => [ $from, _($cobrand->contact_name) ], + To => $data{alert_email}, + }, + $sender, + [ $data{alert_email} ], + 0, + ); + + if ($result == mySociety::EmailUtil::EMAIL_SUCCESS) { + $token->insert(); + } else { + print "Failed to send alert $data{alert_id}!"; + } +} + +1; diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm index c9f1d7dde..3dd4e84e9 100644 --- a/perllib/FixMyStreet/TestMech.pm +++ b/perllib/FixMyStreet/TestMech.pm @@ -151,8 +151,11 @@ sub delete_user { ok( $_->delete, "delete questionnaire " . $_->id ) for $p->questionnaires; ok( $p->delete, "delete problem " . $p->title ); } + for my $a ( $user->alerts ) { + $a->alert_sents->delete; + ok( $a->delete, "delete alert " . $a->alert_type ); + } ok( $_->delete, "delete comment " . $_->text ) for $user->comments; - ok( $_->delete, "delete alert " . $_->alert_type ) for $user->alerts; ok $user->delete, "delete test user " . $user->email; return 1; diff --git a/t/app/controller/alert_new.t b/t/app/controller/alert_new.t index 91e86bd46..ff1acd480 100644 --- a/t/app/controller/alert_new.t +++ b/t/app/controller/alert_new.t @@ -43,8 +43,8 @@ foreach my $test ( email_text => 'confirm the alert', uri => '/alert/subscribe?type=local&rznvy=test@example.com&feed=local:10.2:20.1', - param1 => 10.2, - param2 => 20.1, + param1 => 20.1, + param2 => 10.2, }, { email => 'test@example.com', @@ -66,7 +66,6 @@ foreach my $test ( ->find( { email => $test->{email} } ); # we don't want an alert - my $alert; if ($user) { $mech->delete_user($user); } @@ -80,7 +79,7 @@ foreach my $test ( ok $user, 'user created for alert'; - $alert = FixMyStreet::App->model('DB::Alert')->find( + my $alert = FixMyStreet::App->model('DB::Alert')->find( { user => $user, alert_type => $type, @@ -129,7 +128,7 @@ foreach my $test ( ); ok $token, 'new token found in database'; - ok $token->data->{id} == $existing_id, 'subscribed to exsiting alert'; + ok $token->data->{id} == $existing_id, 'subscribed to existing alert'; $mech->get_ok("/A/$url_token"); $mech->content_contains('successfully confirmed'); @@ -138,6 +137,7 @@ foreach my $test ( FixMyStreet::App->model('DB::Alert')->find( { id => $existing_id, } ); ok $alert->confirmed, 'alert set to confirmed'; + $mech->delete_user($user); }; } @@ -162,18 +162,14 @@ foreach my $test ( FixMyStreet::App->model('DB::User') ->find_or_create( { email => $test->{email} } ); - my $alert; - if ($user) { - $alert = FixMyStreet::App->model('DB::Alert')->find( - { - user => $user, - alert_type => $type - } - ); - - # clear existing data so we can be sure we're creating it - ok $alert->delete() if $alert; - } + my $alert = FixMyStreet::App->model('DB::Alert')->find( + { + user => $user, + alert_type => $type + } + ); + # clear existing data so we can be sure we're creating it + ok $alert->delete() if $alert; $mech->get_ok( $test->{uri} ); @@ -190,6 +186,7 @@ foreach my $test ( $mech->content_contains( 'Now check your email' ); ok $alert, 'New alert created with existing user'; + $mech->delete_user($user); }; } @@ -199,8 +196,6 @@ foreach my $test ( user => 'test-login@example.com', email => 'test-login@example.com', type => 'council', - content => 'your alert will not be activated', - email_text => 'confirm the alert', param1 => 2651, param2 => 2651, confirmed => 1, @@ -210,8 +205,6 @@ foreach my $test ( user => 'loggedin@example.com', email => 'test-login@example.com', type => 'council', - content => 'your alert will not be activated', - email_text => 'confirm the alert', param1 => 2651, param2 => 2651, confirmed => 0, @@ -350,6 +343,112 @@ for my $test ( ok !$alert->confirmed, 'alert not set to confirmed'; $abuse->delete; + $mech->delete_user($user); }; } + +subtest "Test normal alert signups and that alerts are sent" => sub { + my $user1 = FixMyStreet::App->model('DB::User') + ->find_or_create( { email => 'reporter@example.com', name => 'Reporter User' } ); + ok $user1, "created test user"; + $user1->alerts->delete; + + my $user2 = FixMyStreet::App->model('DB::User') + ->find_or_create( { email => 'alerts@example.com', name => 'Alert User' } ); + ok $user2, "created test user"; + $user2->alerts->delete; + + for my $alert ( + { feed => 'local:55.951963:-3.189944', email_confirm => 1 }, + { feed => 'council:2651:City_of_Edinburgh', }, + ) { + $mech->get_ok( '/alert' ); + $mech->submit_form_ok( { with_fields => { pc => 'EH11BB' } } ); + $mech->submit_form_ok( { + button => 'alert', + with_fields => { + rznvy => $user2->email, + feed => $alert->{feed}, + } + } ); + if ( $alert->{email_confirm} ) { + my $email = $mech->get_email; + $mech->clear_emails_ok; + my ( $url, $url_token ) = $email->body =~ m{http://\S+(/A/(\S+))}; + my $token = FixMyStreet::App->model('DB::Token')->find( { token => $url_token, scope => 'alert' } ); + $mech->get_ok( $url ); + $mech->content_contains('successfully confirmed'); + } else { + $mech->content_contains('successfully created'); + } + } + + my $dt = DateTime->now()->add( days => 2); + + my $report_time = '2011-03-01 12:00:00'; + my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { + postcode => 'EH1 1BB', + council => '2651', + areas => ',11808,135007,14419,134935,2651,20728,', + category => 'Street lighting', + title => 'Testing', + detail => 'Testing Detail', + used_map => 1, + name => $user1->name, + anonymous => 0, + state => 'confirmed', + confirmed => $dt, + lastupdate => $dt, + whensent => $dt->clone->add( minutes => 5 ), + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 1, + latitude => '55.951963', + longitude => '-3.189944', + user_id => $user1->id, + } ); + my $report_id = $report->id; + ok $report, "created test report - $report_id"; + + my $alert = FixMyStreet::App->model('DB::Alert')->create( { + parameter => $report_id, + alert_type => 'new_updates', + user => $user1, + } )->confirm; + ok $alert, 'created alert for reporter'; + + my $update = FixMyStreet::App->model('DB::Comment')->create( { + problem_id => $report_id, + user_id => $user2->id, + name => 'Other User', + mark_fixed => 'false', + text => 'This is some update text', + state => 'confirmed', + confirmed => $dt->clone->add( hours => 7 ), + anonymous => 'f', + } ); + my $update_id = $update->id; + ok $update, "created test update - $update_id"; + + FixMyStreet::App->model('DB::AlertType')->email_alerts(); + $mech->email_count_is(3); + my @emails = $mech->get_email; + my $count; + for (@emails) { + $count++ if $_->body =~ /The following updates have been left on this problem:/; + $count++ if $_->body =~ /The following new problems have been reported to City of\s*Edinburgh Council:/; + $count++ if $_->body =~ /The following nearby problems have been added:/; + } + is $count, 3, 'Three emails with the right things in them'; + + my ( $url, $url_token ) = $emails[0]->body =~ m{http://\S+(/A/(\S+))}; + $mech->get_ok( $url ); + $mech->content_contains('successfully deleted'); + + $mech->delete_user($user1); + $mech->delete_user($user2); +}; + done_testing(); diff --git a/t/app/controller/report_new.t b/t/app/controller/report_new.t index 760dfacc6..01c29ecf4 100644 --- a/t/app/controller/report_new.t +++ b/t/app/controller/report_new.t @@ -343,10 +343,17 @@ subtest "test report creation for a user who does not have an account" => sub { $mech->get_ok($url); $report->discard_changes; is $report->state, 'confirmed', "Report is now confirmed"; - is $report->state, 'confirmed', "report is now confirmed"; $mech->get_ok( '/report/' . $report->id ); + # check that the reporter has an alert + my $alert = FixMyStreet::App->model('DB::Alert')->find( { + user => $report->user, + alert_type => 'new_updates', + parameter => $report->id, + } ); + ok $alert, "created new alert"; + # user is created and logged in $mech->logged_in_ok; @@ -440,6 +447,14 @@ foreach my $test ( is $report->state, 'confirmed', "report is now confirmed"; $mech->get_ok( '/report/' . $report->id ); + # check that the reporter has an alert + my $alert = FixMyStreet::App->model('DB::Alert')->find( { + user => $report->user, + alert_type => 'new_updates', + parameter => $report->id, + } ); + ok $alert, "created new alert"; + # user is still logged in $mech->logged_in_ok; diff --git a/templates/web/default/alert/list.html b/templates/web/default/alert/list.html index a8f962c5d..bb515232a 100644 --- a/templates/web/default/alert/list.html +++ b/templates/web/default/alert/list.html @@ -35,13 +35,13 @@ [% IF pretty_pc %] [% tprintf( loc('Here are the types of local problem alerts for ‘%s’.'), pretty_pc ) %] [% END %] - [% loc('Select which type of alert you\'d like and click the button for an RSS feed, or enter your email address to subscribe to an email alert') %] + [% loc('Select which type of alert you\'d like and click the button for an RSS feed, or enter your email address to subscribe to an email alert.') %] </p> [% INCLUDE 'errors.html' %] <p> - [% loc('The simplest alert is our geographic one') %] + [% loc('The simplest alert is our geographic one:') %] </p> <p id="rss_local"> diff --git a/templates/web/default/report/display.html b/templates/web/default/report/display.html index 4fa58c1db..b877874e5 100644 --- a/templates/web/default/report/display.html +++ b/templates/web/default/report/display.html @@ -39,7 +39,7 @@ </p> <div id="alert_links"> - <a rel="nofollow" id="email_alert" href="[% c.uri_for( '/alert', { type => 'updates', id => problem.id } ) %]">[% loc('Email me updates' ) %]</a> + <a rel="nofollow" id="email_alert" href="[% c.uri_for( '/alert/subscribe', { id => problem.id } ) %]">[% loc('Email me updates' ) %]</a> <form action="[% c.uri_for( '/alert/subscribe' ) %]" method="post" id="email_alert_box"> <p>[% loc('Receive email when updates are left on this problem.' ) %]</p> |