aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/DB/ResultSet
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/DB/ResultSet')
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Alert.pm50
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/AlertType.pm210
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Comment.pm41
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Contact.pm33
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Nearby.pm50
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Problem.pm203
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm149
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/User.pm8
8 files changed, 744 insertions, 0 deletions
diff --git a/perllib/FixMyStreet/DB/ResultSet/Alert.pm b/perllib/FixMyStreet/DB/ResultSet/Alert.pm
new file mode 100644
index 000000000..5848265f1
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/Alert.pm
@@ -0,0 +1,50 @@
+package FixMyStreet::DB::ResultSet::Alert;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+
+sub timeline_created {
+ my ( $rs, $restriction ) = @_;
+
+ my $prefetch =
+ FixMyStreet::App->model('DB')->schema->storage->sql_maker->quote_char ?
+ [ qw/alert_type user/ ] :
+ [ qw/alert_type/ ];
+
+ return $rs->search(
+ {
+ whensubscribed => { '>=', \"ms_current_timestamp()-'7 days'::interval" },
+ confirmed => 1,
+ %{ $restriction },
+ },
+ {
+ prefetch => $prefetch,
+ }
+ );
+}
+
+sub timeline_disabled {
+ my ( $rs, $restriction ) = @_;
+
+ return $rs->search(
+ {
+ whendisabled => { '>=', \"ms_current_timestamp()-'7 days'::interval" },
+ %{ $restriction },
+ },
+ );
+}
+
+sub summary_count {
+ my ( $rs, $restriction ) = @_;
+
+ return $rs->search(
+ $restriction,
+ {
+ group_by => ['confirmed'],
+ select => [ 'confirmed', { count => 'id' } ],
+ as => [qw/confirmed confirmed_count/]
+ }
+ );
+}
+1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/AlertType.pm b/perllib/FixMyStreet/DB/ResultSet/AlertType.pm
new file mode 100644
index 000000000..46009cb85
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/AlertType.pm
@@ -0,0 +1,210 @@
+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_id as alert_user_id, 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.text as item_text,
+ $item_table.name as item_name, $item_table.anonymous as item_anonymous,
+ $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.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} && !$row->{item_anonymous};
+ $data{data} .= $row->{item_text} . "\n\n------\n\n";
+ } else {
+ $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n";
+ }
+ if (!$data{alert_user_id}) {
+ %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 );
+
+ if (!$data{alert_email}) {
+ my $user = FixMyStreet::App->model('DB::User')->find( {
+ id => $data{alert_user_id}
+ } );
+ $data{alert_email} = $user->email;
+ }
+
+ 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{lang}, "$data{template}.txt"
+ )->stringify;
+ $template = FixMyStreet->path_to( "templates", "email", $cobrand->moniker, "$data{template}.txt" )->stringify
+ unless -e $template;
+ $template = FixMyStreet->path_to( "templates", "email", "default", "$data{template}.txt" )->stringify
+ unless -e $template;
+ $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/DB/ResultSet/Comment.pm b/perllib/FixMyStreet/DB/ResultSet/Comment.pm
new file mode 100644
index 000000000..70f8027aa
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/Comment.pm
@@ -0,0 +1,41 @@
+package FixMyStreet::DB::ResultSet::Comment;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+
+sub timeline {
+ my ( $rs, $restriction ) = @_;
+
+ my $prefetch =
+ FixMyStreet::App->model('DB')->schema->storage->sql_maker->quote_char ?
+ [ qw/user/ ] :
+ [];
+
+ return $rs->search(
+ {
+ state => 'confirmed',
+ created => { '>=', \"ms_current_timestamp()-'7 days'::interval" },
+ %{ $restriction },
+ },
+ {
+ prefetch => $prefetch,
+ }
+ );
+}
+
+sub summary_count {
+ my ( $rs, $restriction ) = @_;
+
+ return $rs->search(
+ $restriction,
+ {
+ group_by => ['me.state'],
+ select => [ 'me.state', { count => 'me.id' } ],
+ as => [qw/state state_count/],
+ join => 'problem'
+ }
+ );
+}
+
+1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Contact.pm b/perllib/FixMyStreet/DB/ResultSet/Contact.pm
new file mode 100644
index 000000000..6fa6a03a0
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/Contact.pm
@@ -0,0 +1,33 @@
+package FixMyStreet::DB::ResultSet::Contact;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+
+=head2 not_deleted
+
+ $rs = $rs->not_deleted();
+
+Filter down to not deleted contacts - which have C<deleted> set to false;
+
+=cut
+
+sub not_deleted {
+ my $rs = shift;
+ return $rs->search( { deleted => 0 } );
+}
+
+sub summary_count {
+ my ( $rs, $restriction ) = @_;
+
+ return $rs->search(
+ $restriction,
+ {
+ group_by => ['confirmed'],
+ select => [ 'confirmed', { count => 'id' } ],
+ as => [qw/confirmed confirmed_count/]
+ }
+ );
+}
+
+1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Nearby.pm b/perllib/FixMyStreet/DB/ResultSet/Nearby.pm
new file mode 100644
index 000000000..3b3a3d90b
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/Nearby.pm
@@ -0,0 +1,50 @@
+package FixMyStreet::DB::ResultSet::Nearby;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+
+sub nearby {
+ my ( $rs, $c, $dist, $ids, $limit, $mid_lat, $mid_lon, $interval ) = @_;
+
+ my $params = {
+ state => [ 'confirmed', 'fixed' ],
+ };
+ $params->{'current_timestamp-lastupdate'} = { '<', \"'$interval'::interval" }
+ if $interval;
+ $params->{id} = { -not_in => $ids }
+ if $ids;
+ $params = {
+ %{ $c->cobrand->problems_clause },
+ %$params
+ } if $c->cobrand->problems_clause;
+
+ my $attrs = {
+ join => 'problem',
+ columns => [
+ 'problem.id', 'problem.title', 'problem.latitude',
+ 'problem.longitude', 'distance', 'problem.state',
+ 'problem.confirmed'
+ ],
+ bind => [ $mid_lat, $mid_lon, $dist ],
+ order_by => [ 'distance', { -desc => 'created' } ],
+ rows => $limit,
+ };
+
+ my @problems = mySociety::Locale::in_gb_locale { $rs->search( $params, $attrs )->all };
+ return \@problems;
+}
+
+# XXX Not currently used, so not migrating at present.
+#sub fixed_nearby {
+# my ($dist, $mid_lat, $mid_lon) = @_;
+# mySociety::Locale::in_gb_locale { select_all(
+# "select id, title, latitude, longitude, distance
+# from problem_find_nearby(?, ?, $dist) as nearby, problem
+# where nearby.problem_id = problem.id and state='fixed'
+# site_restriction
+# order by lastupdate desc", $mid_lat, $mid_lon);
+# }
+#}
+
+1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Problem.pm b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
new file mode 100644
index 000000000..ca329ab59
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
@@ -0,0 +1,203 @@
+package FixMyStreet::DB::ResultSet::Problem;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+
+my $site_restriction;
+my $site_key;
+
+sub set_restriction {
+ my ( $rs, $sql, $key, $restriction ) = @_;
+ $site_key = $key;
+ $site_restriction = $restriction;
+}
+
+# Front page statistics
+
+sub recent_fixed {
+ my $rs = shift;
+ my $key = "recent_fixed:$site_key";
+ my $result = Memcached::get($key);
+ unless ($result) {
+ $result = $rs->search( {
+ state => 'fixed',
+ lastupdate => { '>', \"current_timestamp-'1 month'::interval" },
+ } )->count;
+ Memcached::set($key, $result, 3600);
+ }
+ return $result;
+}
+
+sub number_comments {
+ my $rs = shift;
+ my $key = "number_comments:$site_key";
+ my $result = Memcached::get($key);
+ unless ($result) {
+ $result = $rs->search(
+ { 'comments.state' => 'confirmed' },
+ { join => 'comments' }
+ )->count;
+ Memcached::set($key, $result, 3600);
+ }
+ return $result;
+}
+
+sub recent_new {
+ my ( $rs, $interval ) = @_;
+ (my $key = $interval) =~ s/\s+//g;
+ $key = "recent_new:$site_key:$key";
+ my $result = Memcached::get($key);
+ unless ($result) {
+ $result = $rs->search( {
+ state => [ 'confirmed', 'fixed' ],
+ confirmed => { '>', \"current_timestamp-'$interval'::interval" },
+ } )->count;
+ Memcached::set($key, $result, 3600);
+ }
+ return $result;
+}
+
+# Front page recent lists
+
+sub recent {
+ my ( $rs ) = @_;
+ my $key = "recent:$site_key";
+ my $result = Memcached::get($key);
+ unless ($result) {
+ $result = [ $rs->search( {
+ state => [ 'confirmed', 'fixed' ]
+ }, {
+ columns => [ 'id', 'title' ],
+ order_by => { -desc => 'confirmed' },
+ rows => 5,
+ } )->all ];
+ Memcached::set($key, $result, 3600);
+ }
+ return $result;
+}
+
+sub recent_photos {
+ my ( $rs, $num, $lat, $lon, $dist ) = @_;
+ my $probs;
+ my $query = {
+ state => [ 'confirmed', 'fixed' ],
+ photo => { '!=', undef },
+ };
+ my $attrs = {
+ columns => [ 'id', 'title' ],
+ order_by => { -desc => 'confirmed' },
+ rows => $num,
+ };
+ if (defined $lat) {
+ my $dist2 = $dist; # Create a copy of the variable to stop it being stringified into a locale in the next line!
+ my $key = "recent_photos:$site_key:$num:$lat:$lon:$dist2";
+ $probs = Memcached::get($key);
+ unless ($probs) {
+ $attrs->{bind} = [ $lat, $lon, $dist ];
+ $attrs->{join} = 'nearby';
+ $probs = [ mySociety::Locale::in_gb_locale {
+ $rs->search( $query, $attrs )->all;
+ } ];
+ Memcached::set($key, $probs, 3600);
+ }
+ } else {
+ my $key = "recent_photos:$site_key:$num";
+ $probs = Memcached::get($key);
+ unless ($probs) {
+ $probs = [ $rs->search( $query, $attrs )->all ];
+ Memcached::set($key, $probs, 3600);
+ }
+ }
+ return $probs;
+}
+
+# Problems around a location
+
+sub around_map {
+ my ( $rs, $min_lat, $max_lat, $min_lon, $max_lon, $interval, $limit ) = @_;
+ my $attr = {
+ order_by => { -desc => 'created' },
+ columns => [
+ 'id', 'title' ,'latitude', 'longitude', 'state', 'confirmed'
+ ],
+ };
+ $attr->{rows} = $limit if $limit;
+
+ my $q = {
+ state => [ 'confirmed', 'fixed' ],
+ latitude => { '>=', $min_lat, '<', $max_lat },
+ longitude => { '>=', $min_lon, '<', $max_lon },
+ };
+ $q->{'current_timestamp - lastupdate'} = { '<', \"'$interval'::interval" }
+ if $interval;
+
+ my @problems = mySociety::Locale::in_gb_locale { $rs->search( $q, $attr )->all };
+ return \@problems;
+}
+
+# Admin functions
+
+sub timeline {
+ my ( $rs ) = @_;
+
+ my $prefetch =
+ FixMyStreet::App->model('DB')->schema->storage->sql_maker->quote_char ?
+ [ qw/user/ ] :
+ [];
+
+ return $rs->search(
+ {
+ -or => {
+ created => { '>=', \"ms_current_timestamp()-'7 days'::interval" },
+ confirmed => { '>=', \"ms_current_timestamp()-'7 days'::interval" },
+ whensent => { '>=', \"ms_current_timestamp()-'7 days'::interval" },
+ }
+ },
+ {
+ prefetch => $prefetch,
+ }
+ );
+}
+
+sub summary_count {
+ my ( $rs ) = @_;
+
+ return $rs->search(
+ undef,
+ {
+ group_by => ['state'],
+ select => [ 'state', { count => 'id' } ],
+ as => [qw/state state_count/]
+ }
+ );
+}
+
+sub unique_users {
+ my ( $rs ) = @_;
+
+ return $rs->search( {
+ state => [ 'confirmed', 'fixed' ],
+ }, {
+ select => [ { count => { distinct => 'user_id' } } ],
+ as => [ 'count' ]
+ } )->first->get_column('count');
+}
+
+sub categories_summary {
+ my ( $rs ) = @_;
+
+ my $categories = $rs->search( {
+ state => [ 'confirmed', 'fixed' ],
+ whensent => { '<' => \"NOW() - INTERVAL '4 weeks'" },
+ }, {
+ select => [ 'category', { count => 'id' }, { count => \"case when state='fixed' then 1 else null end" } ],
+ as => [ 'category', 'c', 'fixed' ],
+ group_by => [ 'category' ],
+ result_class => 'DBIx::Class::ResultClass::HashRefInflator'
+ } );
+ my %categories = map { $_->{category} => { total => $_->{c}, fixed => $_->{fixed} } } $categories->all;
+ return \%categories;
+}
+
+1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm b/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm
new file mode 100644
index 000000000..e490c77a6
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm
@@ -0,0 +1,149 @@
+package FixMyStreet::DB::ResultSet::Questionnaire;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+use File::Slurp;
+use Utils;
+use mySociety::EmailUtil;
+
+sub send_questionnaires {
+ my ( $rs, $params ) = @_;
+ $rs->send_questionnaires_period( '4 weeks', $params );
+ $rs->send_questionnaires_period( '26 weeks', $params )
+ if $params->{site} eq 'emptyhomes';
+}
+
+sub send_questionnaires_period {
+ my ( $rs, $period, $params ) = @_;
+
+ # Select all problems that need a questionnaire email sending
+ my $q_params = {
+ state => [ 'confirmed', 'fixed' ],
+ whensent => [
+ '-and',
+ { '!=', undef },
+ { '<', \"ms_current_timestamp() - '$period'::interval" },
+ ],
+ send_questionnaire => 1,
+ };
+ # FIXME Do these a bit better...
+ if ($params->{site} eq 'emptyhomes' && $period eq '4 weeks') {
+ $q_params->{'(select max(whensent) from questionnaire where me.id=problem_id)'} = undef;
+ } elsif ($params->{site} eq 'emptyhomes' && $period eq '26 weeks') {
+ $q_params->{'(select max(whensent) from questionnaire where me.id=problem_id)'} = { '!=', undef };
+ } else {
+ $q_params->{'-or'} = [
+ '(select max(whensent) from questionnaire where me.id=problem_id)' => undef,
+ '(select max(whenanswered) from questionnaire where me.id=problem_id)' => { '<', \"ms_current_timestamp() - '$period'::interval" }
+ ];
+ }
+
+ my $unsent = FixMyStreet::App->model('DB::Problem')->search( $q_params, {
+ order_by => { -desc => 'confirmed' }
+ } );
+
+ while (my $row = $unsent->next) {
+
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($row->cobrand)->new();
+ $cobrand->set_lang_and_domain($row->lang, 1);
+
+ # 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;
+
+ my $template;
+ if ($params->{site} eq 'emptyhomes') {
+ ($template = $period) =~ s/ //;
+ $template = File::Slurp::read_file( FixMyStreet->path_to( "templates/email/emptyhomes/" . $row->lang . "/questionnaire-$template.txt" )->stringify );
+ } else {
+ $template = FixMyStreet->path_to( "templates", "email", $cobrand->moniker, "questionnaire.txt" )->stringify;
+ $template = FixMyStreet->path_to( "templates", "email", "default", "questionnaire.txt" )->stringify
+ unless -e $template;
+ $template = File::Slurp::read_file( $template );
+ }
+
+ my %h = map { $_ => $row->$_ } qw/name title detail category/;
+ $h{created} = Utils::prettify_duration( time() - $row->confirmed->epoch, 'week' );
+
+ my $questionnaire = FixMyStreet::App->model('DB::Questionnaire')->create( {
+ problem_id => $row->id,
+ whensent => \'ms_current_timestamp()',
+ } );
+
+ # We won't send another questionnaire unless they ask for it (or it was
+ # the first EHA questionnaire.
+ $row->send_questionnaire( 0 )
+ if $params->{site} ne 'emptyhomes' || $period eq '26 weeks';
+
+ my $token = FixMyStreet::App->model("DB::Token")->new_result( {
+ scope => 'questionnaire',
+ data => $questionnaire->id,
+ } );
+ $h{url} = $cobrand->base_url_for_emails($row->cobrand_data) . '/Q/' . $token->token;
+
+ my $sender = $cobrand->contact_email;
+ my $sender_name = _($cobrand->contact_name);
+ $sender =~ s/team/fms-DO-NOT-REPLY/;
+
+ print "Sending questionnaire " . $questionnaire->id . ", problem "
+ . $row->id . ", token " . $token->token . " to "
+ . $row->user->email . "\n"
+ if $params->{verbose};
+
+ my $result = FixMyStreet::App->send_email_cron(
+ {
+ _template_ => $template,
+ _parameters_ => \%h,
+ To => [ [ $row->user->email, $row->name ] ],
+ From => [ $sender, $sender_name ],
+ },
+ $sender,
+ [ $row->user->email ],
+ $params->{nomail}
+ );
+ if ($result == mySociety::EmailUtil::EMAIL_SUCCESS) {
+ print " ...success\n" if $params->{verbose};
+ $row->update();
+ $token->insert();
+ } else {
+ print " ...failed\n" if $params->{verbose};
+ $questionnaire->delete;
+ }
+ }
+}
+
+sub timeline {
+ my ( $rs, $restriction ) = @_;
+
+ return $rs->search(
+ {
+ -or => {
+ whenanswered => { '>=', \"ms_current_timestamp()-'7 days'::interval" },
+ 'me.whensent' => { '>=', \"ms_current_timestamp()-'7 days'::interval" },
+ },
+ %{ $restriction },
+ },
+ {
+ -select => [qw/me.*/],
+ prefetch => [qw/problem/],
+ }
+ );
+}
+
+sub summary_count {
+ my ( $rs, $restriction ) = @_;
+
+ return $rs->search(
+ $restriction,
+ {
+ group_by => [ \'whenanswered is not null' ],
+ select => [ \'(whenanswered is not null)', { count => 'me.id' } ],
+ as => [qw/answered questionnaire_count/],
+ join => 'problem'
+ }
+ );
+}
+1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/User.pm b/perllib/FixMyStreet/DB/ResultSet/User.pm
new file mode 100644
index 000000000..7e657a936
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/User.pm
@@ -0,0 +1,8 @@
+package FixMyStreet::DB::ResultSet::User;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+
+
+1;