aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/DB
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/DB')
-rw-r--r--perllib/FixMyStreet/DB/Factories.pm2
-rw-r--r--perllib/FixMyStreet/DB/Result/Body.pm34
-rw-r--r--perllib/FixMyStreet/DB/Result/Comment.pm34
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm55
-rw-r--r--perllib/FixMyStreet/DB/Result/User.pm59
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Nearby.pm4
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Problem.pm12
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/State.pm11
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/User.pm35
9 files changed, 199 insertions, 47 deletions
diff --git a/perllib/FixMyStreet/DB/Factories.pm b/perllib/FixMyStreet/DB/Factories.pm
index ec4dd630a..7b8234aec 100644
--- a/perllib/FixMyStreet/DB/Factories.pm
+++ b/perllib/FixMyStreet/DB/Factories.pm
@@ -118,7 +118,7 @@ use parent "DBIx::Class::Factory";
__PACKAGE__->resultset(FixMyStreet::DB->resultset("ResponsePriority"));
__PACKAGE__->fields({
- name => __PACKAGE__->seq(sub { 'Priority #' . (shift()+1) }),
+ name => __PACKAGE__->seq(sub { 'Priority ' . (shift()+1) }),
description => __PACKAGE__->seq(sub { 'Description #' . (shift()+1) }),
});
diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm
index 6481d5cfc..e5cd2b907 100644
--- a/perllib/FixMyStreet/DB/Result/Body.pm
+++ b/perllib/FixMyStreet/DB/Result/Body.pm
@@ -156,12 +156,13 @@ sub areas {
}
sub first_area_children {
- my ( $self, $c ) = @_;
+ my ( $self ) = @_;
my $area_id = $self->body_areas->first->area_id;
+ my $cobrand = $self->result_source->schema->cobrand;
my $children = mySociety::MaPit::call('area/children', $area_id,
- type => $c->cobrand->area_types_children,
+ type => $cobrand->area_types_children,
);
return $children;
@@ -182,4 +183,33 @@ sub get_cobrand_handler {
return FixMyStreet::Cobrand->body_handler($self->areas);
}
+sub calculate_average {
+ my ($self) = @_;
+
+ my $substmt = "select min(id) from comment where me.problem_id=comment.problem_id and (problem_state in ('fixed', 'fixed - council', 'fixed - user') or mark_fixed)";
+ my $subquery = FixMyStreet::DB->resultset('Comment')->to_body($self)->search({
+ -or => [
+ problem_state => [ FixMyStreet::DB::Result::Problem->fixed_states() ],
+ mark_fixed => 1,
+ ],
+ 'me.id' => \"= ($substmt)",
+ 'me.state' => 'confirmed',
+ }, {
+ select => [
+ { extract => "epoch from me.confirmed-problem.confirmed", -as => 'time' },
+ ],
+ as => [ qw/time/ ],
+ rows => 100,
+ order_by => { -desc => 'me.confirmed' },
+ join => 'problem'
+ })->as_subselect_rs;
+
+ my $avg = $subquery->search({
+ }, {
+ select => [ { avg => "time" } ],
+ as => [ qw/avg/ ],
+ })->first->get_column('avg');
+ return $avg;
+}
+
1;
diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm
index 562f29693..60fd31510 100644
--- a/perllib/FixMyStreet/DB/Result/Comment.pm
+++ b/perllib/FixMyStreet/DB/Result/Comment.pm
@@ -229,13 +229,24 @@ sub meta_line {
if ($self->anonymous or !$self->name) {
$meta = sprintf( _( 'Posted anonymously at %s' ), Utils::prettify_dt( $self->confirmed ) )
- } elsif ($self->user->from_body) {
+ } elsif ($self->user->from_body || $self->get_extra_metadata('is_body_user') || $self->get_extra_metadata('is_superuser') ) {
my $user_name = FixMyStreet::Template::html_filter($self->user->name);
- my $body = $self->user->body;
- if ($body eq 'Bromley Council') {
- $body = "$body <img src='/cobrands/bromley/favicon.png' alt=''>";
- } elsif ($body eq 'Royal Borough of Greenwich') {
- $body = "$body <img src='/cobrands/greenwich/favicon.png' alt=''>";
+ my $body;
+ if ($self->get_extra_metadata('is_superuser')) {
+ $body = _('an administrator');
+ } else {
+ # use this meta data in preference to the user's from_body setting
+ # in case they are no longer with the body, or have changed body.
+ if (my $body_id = $self->get_extra_metadata('is_body_user')) {
+ $body = FixMyStreet::App->model('DB::Body')->find({id => $body_id})->name;
+ } else {
+ $body = $self->user->body;
+ }
+ if ($body eq 'Bromley Council') {
+ $body = "$body <img src='/cobrands/bromley/favicon.png' alt=''>";
+ } elsif ($body eq 'Royal Borough of Greenwich') {
+ $body = "$body <img src='/cobrands/greenwich/favicon.png' alt=''>";
+ }
}
my $can_view_contribute = $c->user_exists && $c->user->has_permission_to('view_body_contribute_details', $self->problem->bodies_str_ids);
if ($self->text) {
@@ -274,14 +285,9 @@ sub problem_state_display {
return FixMyStreet::DB->resultset("State")->display('confirmed', 1);
} elsif ($self->problem_state) {
my $state = $self->problem_state;
- if ($state eq 'not responsible') {
- $update_state = _( "not the council's responsibility" );
- if ($cobrand eq 'bromley' || $self->problem->to_body_named('Bromley')) {
- $update_state = 'third party responsibility';
- }
- } else {
- $update_state = FixMyStreet::DB->resultset("State")->display($state, 1);
- }
+ my $cobrand_name = $cobrand;
+ $cobrand_name = 'bromley' if $self->problem->to_body_named('Bromley');
+ $update_state = FixMyStreet::DB->resultset("State")->display($state, 1, $cobrand_name);
}
return $update_state;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index 3b622b561..8625bf17a 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -485,12 +485,21 @@ Return a url for this problem report that logs a user in
sub tokenised_url {
my ($self, $user, $params) = @_;
+ my %params;
+ if ($user->email_verified) {
+ $params{email} = $user->email;
+ } elsif ($user->phone_verified) {
+ $params{phone} = $user->phone;
+ # This is so the email token can look up/ log in a phone user
+ $params{login_type} = 'phone';
+ }
+
my $token = FixMyStreet::App->model('DB::Token')->create(
{
scope => 'email_sign_in',
data => {
+ %params,
id => $self->id,
- email => $user->email,
r => $self->url,
p => $params,
}
@@ -618,6 +627,15 @@ sub meta_line {
return $meta;
}
+sub nearest_address {
+ my $self = shift;
+
+ return '' unless $self->geocode;
+
+ my $address = $self->geocode->{resourceSets}[0]{resources}[0];
+ return $address->{name};
+}
+
sub body {
my ( $problem, $c ) = @_;
my $body;
@@ -849,10 +867,23 @@ sub update_send_failed {
} );
}
+sub add_send_method {
+ my $self = shift;
+ my $sender = shift;
+ ($sender = ref $sender) =~ s/^.*:://;
+ if (my $send_method = $self->send_method_used) {
+ $self->send_method_used("$send_method,$sender");
+ } else {
+ $self->send_method_used($sender);
+ }
+}
+
sub as_hashref {
my $self = shift;
my $c = shift;
+ my $state_t = FixMyStreet::DB->resultset("State")->display($self->state);
+
return {
id => $self->id,
title => $self->title,
@@ -863,12 +894,16 @@ sub as_hashref {
postcode => $self->postcode,
areas => $self->areas,
state => $self->state,
- state_t => _( $self->state ),
+ state_t => $state_t,
used_map => $self->used_map,
is_fixed => $self->fixed_states->{ $self->state } ? 1 : 0,
photos => [ map { $_->{url} } @{$self->photos} ],
meta => $self->confirmed ? $self->meta_line( $c ) : '',
- confirmed_pp => $self->confirmed ? $c->cobrand->prettify_dt( $self->confirmed ): '',
+ ($self->confirmed ? (
+ confirmed => $self->confirmed,
+ confirmed_pp => $c->cobrand->prettify_dt( $self->confirmed ),
+ ) : ()),
+ created => $self->created,
created_pp => $c->cobrand->prettify_dt( $self->created ),
};
}
@@ -896,16 +931,20 @@ sub photos {
# if LOGIN_REQUIRED is set. To stop this happening, Varnish should be
# configured to not strip cookies if the cookie_passthrough param is
# present, which this line ensures will be if LOGIN_REQUIRED is set.
- my $extra = (FixMyStreet->config('LOGIN_REQUIRED')) ? "&cookie_passthrough=1" : "";
+ my $extra = '';
+ if (FixMyStreet->config('LOGIN_REQUIRED')) {
+ $cachebust .= '&cookie_passthrough=1';
+ $extra = '?cookie_passthrough=1';
+ }
my ($hash, $format) = split /\./, $_;
{
id => $hash,
url_temp => "/photo/temp.$hash.$format$extra",
url_temp_full => "/photo/fulltemp.$hash.$format$extra",
- url => "/photo/$id.$i.$format?$cachebust$extra",
- url_full => "/photo/$id.$i.full.$format?$cachebust$extra",
- url_tn => "/photo/$id.$i.tn.$format?$cachebust$extra",
- url_fp => "/photo/$id.$i.fp.$format?$cachebust$extra",
+ url => "/photo/$id.$i.$format?$cachebust",
+ url_full => "/photo/$id.$i.full.$format?$cachebust",
+ url_tn => "/photo/$id.$i.tn.$format?$cachebust",
+ url_fp => "/photo/$id.$i.fp.$format?$cachebust",
idx => $i++,
}
} $photoset->all_ids;
diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm
index 19adf5d49..d02039ac3 100644
--- a/perllib/FixMyStreet/DB/Result/User.pm
+++ b/perllib/FixMyStreet/DB/Result/User.pm
@@ -19,7 +19,7 @@ __PACKAGE__->add_columns(
sequence => "users_id_seq",
},
"email",
- { data_type => "text", is_nullable => 0 },
+ { data_type => "text", is_nullable => 1 },
"name",
{ data_type => "text", is_nullable => 1 },
"phone",
@@ -30,21 +30,24 @@ __PACKAGE__->add_columns(
{ data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
"flagged",
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "is_superuser",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
"title",
{ data_type => "text", is_nullable => 1 },
"twitter_id",
{ data_type => "bigint", is_nullable => 1 },
"facebook_id",
{ data_type => "bigint", is_nullable => 1 },
- "is_superuser",
- { data_type => "boolean", default_value => \"false", is_nullable => 0 },
"area_id",
{ data_type => "integer", is_nullable => 1 },
"extra",
{ data_type => "text", is_nullable => 1 },
+ "email_verified",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "phone_verified",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
-__PACKAGE__->add_unique_constraint("users_email_key", ["email"]);
__PACKAGE__->add_unique_constraint("users_facebook_id_key", ["facebook_id"]);
__PACKAGE__->add_unique_constraint("users_twitter_id_key", ["twitter_id"]);
__PACKAGE__->has_many(
@@ -102,13 +105,19 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-09-16 14:22:10
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7wfF1VnZax2QTXCIPXr+vg
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-09-19 18:02:17
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OKHKCSahWD3Ov6ulj+2f/w
+
+# These are not fully unique constraints (they only are when the *_verified
+# is true), but this is managed in ResultSet::User's find() wrapper.
+__PACKAGE__->add_unique_constraint("users_email_verified_key", ["email", "email_verified"]);
+__PACKAGE__->add_unique_constraint("users_phone_verified_key", ["phone", "phone_verified"]);
__PACKAGE__->load_components("+FixMyStreet::DB::RABXColumn");
__PACKAGE__->rabx_column('extra');
use Moo;
+use FixMyStreet::SMS;
use mySociety::EmailUtil;
use namespace::clean -except => [ 'meta' ];
@@ -125,6 +134,26 @@ __PACKAGE__->add_columns(
},
);
+=head2 username
+
+Returns a verified email or phone for this user, preferring email,
+or undef if neither verified (shouldn't happen).
+
+=cut
+
+sub username {
+ my $self = shift;
+ return $self->email if $self->email_verified;
+ return $self->phone_display if $self->phone_verified;
+}
+
+sub phone_display {
+ my $self = shift;
+ return $self->phone unless $self->phone;
+ my $parsed = FixMyStreet::SMS->parse_username($self->phone);
+ return $parsed->{phone} ? $parsed->{phone}->format : $self->phone;
+}
+
sub latest_anonymity {
my $self = shift;
my $p = $self->problems->search(undef, { order_by => { -desc => 'id' } } )->first;
@@ -157,11 +186,19 @@ sub check_for_errors {
$errors{name} = _('Please enter your name');
}
- if ( $self->email !~ /\S/ ) {
- $errors{email} = _('Please enter your email');
- }
- elsif ( !mySociety::EmailUtil::is_valid_email( $self->email ) ) {
- $errors{email} = _('Please enter a valid email');
+ if ($self->email_verified) {
+ if ($self->email !~ /\S/) {
+ $errors{username} = _('Please enter your email');
+ } elsif (!mySociety::EmailUtil::is_valid_email($self->email)) {
+ $errors{username} = _('Please enter a valid email');
+ }
+ } elsif ($self->phone_verified) {
+ my $parsed = FixMyStreet::SMS->parse_username($self->phone);
+ if (!$parsed->{phone}) {
+ $errors{username} = _('Please check your phone number is correct');
+ } elsif (!$parsed->{may_be_mobile}) {
+ $errors{username} = _('Please enter a mobile number');
+ }
}
return \%errors;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Nearby.pm b/perllib/FixMyStreet/DB/ResultSet/Nearby.pm
index 8b8951007..6e5e0220f 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Nearby.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Nearby.pm
@@ -10,7 +10,7 @@ sub to_body {
}
sub nearby {
- my ( $rs, $c, $dist, $ids, $limit, $mid_lat, $mid_lon, $interval, $categories, $states ) = @_;
+ my ( $rs, $c, $dist, $ids, $limit, $mid_lat, $mid_lon, $categories, $states ) = @_;
unless ( $states ) {
$states = FixMyStreet::DB::Result::Problem->visible_states();
@@ -20,8 +20,6 @@ sub nearby {
non_public => 0,
state => [ keys %$states ],
};
- $params->{'current_timestamp-lastupdate'} = { '<', \"'$interval'::interval" }
- if $interval;
$params->{id} = { -not_in => $ids }
if $ids;
$params->{category} = $categories if $categories && @$categories;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Problem.pm b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
index f1ed50721..ae45351c4 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Problem.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
@@ -140,11 +140,11 @@ sub _recent {
# Problems around a location
sub around_map {
- my ( $rs, $limit, %p) = @_;
+ my ( $rs, $c, %p) = @_;
my $attr = {
order_by => $p{order},
};
- $attr->{rows} = $limit if $limit;
+ $attr->{rows} = $c->cobrand->reports_per_page;
unless ( $p{states} ) {
$p{states} = FixMyStreet::DB::Result::Problem->visible_states();
@@ -156,12 +156,12 @@ sub around_map {
latitude => { '>=', $p{min_lat}, '<', $p{max_lat} },
longitude => { '>=', $p{min_lon}, '<', $p{max_lon} },
};
- $q->{'current_timestamp - lastupdate'} = { '<', \"'$p{interval}'::interval" }
- if $p{interval};
$q->{category} = $p{categories} if $p{categories} && @{$p{categories}};
- my @problems = mySociety::Locale::in_gb_locale { $rs->search( $q, $attr )->include_comment_counts->all };
- return \@problems;
+ my $problems = mySociety::Locale::in_gb_locale {
+ $rs->search( $q, $attr )->include_comment_counts->page($p{page});
+ };
+ return $problems;
}
# Admin functions
diff --git a/perllib/FixMyStreet/DB/ResultSet/State.pm b/perllib/FixMyStreet/DB/ResultSet/State.pm
index ac13ec2a4..3e6169aeb 100644
--- a/perllib/FixMyStreet/DB/ResultSet/State.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/State.pm
@@ -24,7 +24,10 @@ sub states {
my $rs = shift;
my $states = Memcached::get('states');
- if ($states) {
+ # If tests are run in parallel, the cached state in Memcached could be
+ # corrupted by multiple tests changing it at the same time
+ # uncoverable branch true
+ if ($states && !FixMyStreet->test_mode) {
# Need to reattach schema
$states->[0]->result_source->schema( $rs->result_source->schema ) if $states->[0];
return $states;
@@ -55,7 +58,7 @@ sub fixed { [ $_[0]->_filter(sub { $_->type eq 'fixed' }) ] }
# This function can be used to return that label's display name.
sub display {
- my ($rs, $label, $single_fixed) = @_;
+ my ($rs, $label, $single_fixed, $cobrand) = @_;
my $unchanging = {
unconfirmed => _("Unconfirmed"),
hidden => _("Hidden"),
@@ -69,6 +72,10 @@ sub display {
};
$label = 'fixed' if $single_fixed && $label =~ /^fixed - (council|user)$/;
return $unchanging->{$label} if $unchanging->{$label};
+ if ($cobrand && $label eq 'not responsible') {
+ return 'third party responsibility' if $cobrand eq 'bromley';
+ return _("not the council's responsibility");
+ }
my ($state) = $rs->_filter(sub { $_->label eq $label });
return $label unless $state;
$state->name($translate_now->{$label}) if $translate_now->{$label};
diff --git a/perllib/FixMyStreet/DB/ResultSet/User.pm b/perllib/FixMyStreet/DB/ResultSet/User.pm
index 7e657a936..9a8a50559 100644
--- a/perllib/FixMyStreet/DB/ResultSet/User.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/User.pm
@@ -4,5 +4,40 @@ use base 'DBIx::Class::ResultSet';
use strict;
use warnings;
+use Moo;
+
+# The database has a partial unique index on email (when email_verified is
+# true), and phone (when phone_verified is true). In the code, we can only
+# say these are fully unique indices, which they aren't, as there could be
+# multiple identical unverified phone numbers.
+#
+# We assume that any and all calls to find (also called using find_or_new,
+# find_or_create, or update_or_new/create) are to look up verified entries
+# only (it would make no sense to find() a non-unique entry). Therefore we
+# help the code along by specifying the most appropriate key to use, given
+# the data provided, and setting the appropriate verified boolean.
+
+around find => sub {
+ my ($orig, $self) = (shift, shift);
+ # If there's already a key, assume caller knows what they're doing
+ if (ref $_[0] eq 'HASH' && !$_[1]->{key}) {
+ if ($_[0]->{id}) {
+ $_[1]->{key} = 'primary';
+ } elsif (exists $_[0]->{email} && exists $_[0]->{phone}) {
+ # If there's both email and phone, caller must also have specified
+ # a verified boolean so that we know what we're looking for
+ if (!$_[0]->{email_verified} && !$_[0]->{phone_verified}) {
+ die "Cannot perform a User find() with both email and phone and no verified";
+ }
+ } elsif (exists $_[0]->{email}) {
+ $_[0]->{email_verified} = 1;
+ $_[1]->{key} = 'users_email_verified_key';
+ } elsif (exists $_[0]->{phone}) {
+ $_[0]->{phone_verified} = 1;
+ $_[1]->{key} = 'users_phone_verified_key';
+ }
+ }
+ $self->$orig(@_);
+};
1;