aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r--perllib/FixMyStreet/App.pm40
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm1
-rw-r--r--perllib/FixMyStreet/App/Controller/Contact.pm20
-rw-r--r--perllib/FixMyStreet/App/Controller/Photo.pm1
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm11
-rw-r--r--perllib/FixMyStreet/App/Model/PhotoSet.pm14
-rw-r--r--perllib/FixMyStreet/App/View/Email.pm7
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm2
-rw-r--r--perllib/FixMyStreet/DB/Result/Comment.pm23
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm102
-rw-r--r--perllib/FixMyStreet/Email.pm126
-rw-r--r--perllib/FixMyStreet/Map.pm5
-rw-r--r--perllib/FixMyStreet/Map/OSM.pm31
-rw-r--r--perllib/FixMyStreet/Roles/PhotoSet.pm35
-rw-r--r--perllib/FixMyStreet/Script/Alerts.pm89
-rw-r--r--perllib/FixMyStreet/Script/Questionnaires.pm3
-rw-r--r--perllib/FixMyStreet/Script/Reports.pm3
-rw-r--r--perllib/FixMyStreet/SendReport/Email.pm1
-rw-r--r--perllib/FixMyStreet/TestMech.pm42
19 files changed, 432 insertions, 124 deletions
diff --git a/perllib/FixMyStreet/App.pm b/perllib/FixMyStreet/App.pm
index be0e91101..ea7d43512 100644
--- a/perllib/FixMyStreet/App.pm
+++ b/perllib/FixMyStreet/App.pm
@@ -306,30 +306,38 @@ sub send_email {
my $sender_name = $c->cobrand->contact_name;
# create the vars to pass to the email template
+ my @include_path = @{ $c->cobrand->path_to_email_templates($c->stash->{lang_code}) };
my $vars = {
from => [ $sender, _($sender_name) ],
%{ $c->stash },
%$extra_stash_values,
- additional_template_paths => $c->cobrand->path_to_email_templates($c->stash->{lang_code}),
+ additional_template_paths => \@include_path,
};
return if FixMyStreet::Email::is_abuser($c->model('DB')->schema, $vars->{to});
- my $email = mySociety::Locale::in_gb_locale { FixMyStreet::Email::construct_email(
- {
- _body_ => $c->view('Email')->render( $c, $template, $vars ),
- _attachments_ => $extra_stash_values->{attachments},
- From => $vars->{from},
- To => $vars->{to},
- 'Message-ID' => sprintf('<fms-%s-%s@%s>',
- time(), unpack('h*', random_bytes(5, 1)), $c->config->{EMAIL_DOMAIN}
- ),
- $vars->{subject} ? (Subject => $vars->{subject}) : (),
- $vars->{'Reply-To'} ? ('Reply-To' => $vars->{'Reply-To'}) : (),
- }
- ) };
-
- # send the email
+ my @inline_images;
+ $vars->{inline_image} = sub { FixMyStreet::Email::add_inline_image(\@inline_images, @_); },
+
+ my $html_template = FixMyStreet::Email::get_html_template($template, @include_path);
+ my $html_compiled = eval {
+ $c->view('Email')->render($c, $html_template, $vars) if $html_template;
+ };
+ $c->log->debug("Error compiling HTML $template: $@") if $@;
+
+ my $data = {
+ _body_ => $c->view('Email')->render( $c, $template, $vars ),
+ _attachments_ => $extra_stash_values->{attachments},
+ From => $vars->{from},
+ To => $vars->{to},
+ 'Message-ID' => FixMyStreet::Email::message_id(),
+ };
+ $data->{Subject} = $vars->{subject} if $vars->{subject};
+ $data->{'Reply-To'} = $vars->{'Reply-To'} if $vars->{'Reply-To'};
+ $data->{_html_} = $html_compiled if $html_compiled;
+ $data->{_html_images_} = \@inline_images if @inline_images;
+
+ my $email = mySociety::Locale::in_gb_locale { FixMyStreet::Email::construct_email($data) };
$c->model('EmailSend')->send($email);
return $email;
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 93e8f4b22..44a653d62 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -8,7 +8,6 @@ use Path::Class;
use POSIX qw(strftime strcoll);
use Digest::SHA qw(sha1_hex);
use mySociety::EmailUtil qw(is_valid_email);
-use if !$ENV{TRAVIS}, 'Image::Magick';
use DateTime::Format::Strptime;
use List::Util 'first';
diff --git a/perllib/FixMyStreet/App/Controller/Contact.pm b/perllib/FixMyStreet/App/Controller/Contact.pm
index e20011471..5527256a6 100644
--- a/perllib/FixMyStreet/App/Controller/Contact.pm
+++ b/perllib/FixMyStreet/App/Controller/Contact.pm
@@ -168,26 +168,22 @@ sub prepare_params_for_email : Private {
if ( $c->stash->{update} ) {
- my $problem_url = $base_url . '/report/' . $c->stash->{update}->problem_id
+ $c->stash->{problem_url} = $base_url . '/report/' . $c->stash->{update}->problem_id
. '#update_' . $c->stash->{update}->id;
- my $admin_url = " - $admin_url" . '/update_edit/' . $c->stash->{update}->id
- if $admin_url;
- $c->stash->{message} .= sprintf(
- " \n\n[ Complaint about update %d on report %d - %s%s ]",
+ $c->stash->{admin_url} = $admin_url . '/update_edit/' . $c->stash->{update}->id;
+ $c->stash->{complaint} = sprintf(
+ "Complaint about update %d on report %d",
$c->stash->{update}->id,
$c->stash->{update}->problem_id,
- $problem_url, $admin_url
);
}
elsif ( $c->stash->{problem} ) {
- my $problem_url = $base_url . '/report/' . $c->stash->{problem}->id;
- $admin_url = " - $admin_url" . '/report_edit/' . $c->stash->{problem}->id
- if $admin_url;
- $c->stash->{message} .= sprintf(
- " \n\n[ Complaint about report %d - %s%s ]",
+ $c->stash->{problem_url} = $base_url . '/report/' . $c->stash->{problem}->id;
+ $c->stash->{admin_url} = $admin_url . '/report_edit/' . $c->stash->{problem}->id;
+ $c->stash->{complaint} = sprintf(
+ "Complaint about report %d",
$c->stash->{problem}->id,
- $problem_url, $admin_url
);
# flag this so it's automatically listed in the admin interface
diff --git a/perllib/FixMyStreet/App/Controller/Photo.pm b/perllib/FixMyStreet/App/Controller/Photo.pm
index 2734491fa..2302322bf 100644
--- a/perllib/FixMyStreet/App/Controller/Photo.pm
+++ b/perllib/FixMyStreet/App/Controller/Photo.pm
@@ -8,7 +8,6 @@ use JSON::MaybeXS;
use File::Path;
use File::Slurp;
use FixMyStreet::App::Model::PhotoSet;
-use if !$ENV{TRAVIS}, 'Image::Magick';
=head1 NAME
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index 89df4a52d..6ac3c8ea1 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -278,6 +278,17 @@ sub delete :Local :Args(1) {
return $c->res->redirect($uri);
}
+sub map : Path('') : Args(2) {
+ my ( $self, $c, $id, $map ) = @_;
+
+ $c->detach( '/page_error_404_not_found', [] ) unless $map eq 'map';
+ $c->forward( 'load_problem_or_display_error', [ $id ] );
+
+ my $image = $c->stash->{problem}->static_map;
+ $c->res->content_type($image->{content_type});
+ $c->res->body($image->{data});
+}
+
__PACKAGE__->meta->make_immutable;
1;
diff --git a/perllib/FixMyStreet/App/Model/PhotoSet.pm b/perllib/FixMyStreet/App/Model/PhotoSet.pm
index 1c8a86e3a..487786a3b 100644
--- a/perllib/FixMyStreet/App/Model/PhotoSet.pm
+++ b/perllib/FixMyStreet/App/Model/PhotoSet.pm
@@ -4,7 +4,13 @@ package FixMyStreet::App::Model::PhotoSet;
use Moose;
use Path::Tiny 'path';
-use if !$ENV{TRAVIS}, 'Image::Magick';
+
+my $IM = eval {
+ require Image::Magick;
+ Image::Magick->import;
+ 1;
+};
+
use Scalar::Util 'openhandle', 'blessed';
use Digest::SHA qw(sha1_hex);
use Image::Size;
@@ -318,7 +324,7 @@ sub rotate_image {
sub _rotate_image {
my ($photo, $direction) = @_;
- return $photo unless $Image::Magick::VERSION;
+ return $photo unless $IM;
my $image = Image::Magick->new;
$image->BlobToImage($photo);
my $err = $image->Rotate($direction);
@@ -332,7 +338,7 @@ sub _rotate_image {
# Shrinks a picture to the specified size, but keeping in proportion.
sub _shrink {
my ($photo, $size) = @_;
- return $photo unless $Image::Magick::VERSION;
+ return $photo unless $IM;
my $image = Image::Magick->new;
$image->BlobToImage($photo);
my $err = $image->Scale(geometry => "$size>");
@@ -346,7 +352,7 @@ sub _shrink {
# Shrinks a picture to 90x60, cropping so that it is exactly that.
sub _crop {
my ($photo) = @_;
- return $photo unless $Image::Magick::VERSION;
+ return $photo unless $IM;
my $image = Image::Magick->new;
$image->BlobToImage($photo);
my $err = $image->Resize( geometry => "90x60^" );
diff --git a/perllib/FixMyStreet/App/View/Email.pm b/perllib/FixMyStreet/App/View/Email.pm
index 86d5c1d60..6073ee814 100644
--- a/perllib/FixMyStreet/App/View/Email.pm
+++ b/perllib/FixMyStreet/App/View/Email.pm
@@ -14,7 +14,7 @@ __PACKAGE__->config(
],
ENCODING => 'utf8',
render_die => 1,
- expose_methods => ['loc'],
+ expose_methods => ['loc', 'file_exists'],
);
=head1 NAME
@@ -40,5 +40,10 @@ sub loc {
return _(@args);
}
+sub file_exists {
+ my ( $self, $c, @args ) = @_;
+ -e FixMyStreet->path_to(@args);
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index e5ec0c13a..686684a05 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -395,7 +395,7 @@ Return an override type of map if necessary.
=cut
sub map_type {
my $self = shift;
- return 'OSM' if $self->{c}->req->uri->host =~ /^osm\./;
+ return 'OSM' if $self->{c} && $self->{c}->req->uri->host =~ /^osm\./;
return;
}
diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm
index 85cdb29f0..25798edca 100644
--- a/perllib/FixMyStreet/DB/Result/Comment.pm
+++ b/perllib/FixMyStreet/DB/Result/Comment.pm
@@ -99,7 +99,8 @@ __PACKAGE__->rabx_column('extra');
use Moo;
use namespace::clean -except => [ 'meta' ];
-with 'FixMyStreet::Roles::Abuser';
+with 'FixMyStreet::Roles::Abuser',
+ 'FixMyStreet::Roles::PhotoSet';
my $stz = sub {
my ( $orig, $self ) = ( shift, shift );
@@ -147,26 +148,6 @@ sub confirm {
$self->confirmed( \'current_timestamp' );
}
-=head2 get_photoset
-
-Return a PhotoSet object for all photos attached to this field
-
- my $photoset = $obj->get_photoset;
- print $photoset->num_images;
- return $photoset->get_image_data(num => 0, size => 'full');
-
-=cut
-
-sub get_photoset {
- my ($self) = @_;
- my $class = 'FixMyStreet::App::Model::PhotoSet';
- eval "use $class";
- return $class->new({
- db_data => $self->photo,
- object => $self,
- });
-}
-
sub photos {
my $self = shift;
my $photoset = $self->get_photoset;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index 2599f24ae..92865ace9 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -161,9 +161,17 @@ use Moo;
use namespace::clean -except => [ 'meta' ];
use Utils;
use FixMyStreet::Map::FMS;
+use LWP::Simple qw($ua);
+
+my $IM = eval {
+ require Image::Magick;
+ Image::Magick->import;
+ 1;
+};
with 'FixMyStreet::Roles::Abuser',
- 'FixMyStreet::Roles::Extra';
+ 'FixMyStreet::Roles::Extra',
+ 'FixMyStreet::Roles::PhotoSet';
=head2
@@ -653,7 +661,7 @@ sub can_display_external_id {
if ($self->external_id && $self->send_method_used && $self->bodies_str =~ /(2237|2550)/) {
return 1;
}
- return 0;
+ return 0;
}
sub duration_string {
@@ -809,26 +817,6 @@ sub latest_moderation_log_entry {
return $self->admin_log_entries->search({ action => 'moderation' }, { order_by => 'id desc' })->first;
}
-=head2 get_photoset
-
-Return a PhotoSet object for all photos attached to this field
-
- my $photoset = $obj->get_photoset;
- print $photoset->num_images;
- return $photoset->get_image_data(num => 0, size => 'full');
-
-=cut
-
-sub get_photoset {
- my ($self) = @_;
- my $class = 'FixMyStreet::App::Model::PhotoSet';
- eval "use $class";
- return $class->new({
- db_data => $self->photo,
- object => $self,
- });
-}
-
sub photos {
my $self = shift;
my $photoset = $self->get_photoset;
@@ -855,7 +843,7 @@ __PACKAGE__->has_many(
"admin_log_entries",
"FixMyStreet::DB::Result::AdminLog",
{ "foreign.object_id" => "self.id" },
- {
+ {
cascade_copy => 0, cascade_delete => 0,
where => { 'object_type' => 'problem' },
}
@@ -892,6 +880,7 @@ has get_cobrand_logged => (
},
);
+
sub pin_data {
my ($self, $c, $page, %opts) = @_;
my $colour = $c->cobrand->pin_colour($self, $page);
@@ -904,6 +893,73 @@ sub pin_data {
title => $opts{private} ? $self->title : $self->title_safe,
problem => $self,
}
+};
+
+sub static_map {
+ my ($self) = @_;
+
+ return unless $IM;
+
+ my $orig_map_class = FixMyStreet::Map::set_map_class('OSM')
+ unless $FixMyStreet::Map::map_class->isa("FixMyStreet::Map::OSM");
+
+ my $map_data = $FixMyStreet::Map::map_class->generate_map_data(
+ { cobrand => $self->get_cobrand_logged },
+ latitude => $self->latitude,
+ longitude => $self->longitude,
+ pins => $self->used_map
+ ? [ {
+ latitude => $self->latitude,
+ longitude => $self->longitude,
+ colour => $self->get_cobrand_logged->pin_colour($self, 'report'),
+ type => 'big',
+ } ]
+ : [],
+ );
+
+ $ua->agent("FixMyStreet/1.0");
+ my $image;
+ for (my $i=0; $i<4; $i++) {
+ my $tile_url = $map_data->{tiles}->[$i];
+ if ($tile_url =~ m{^//}) {
+ $tile_url = "https:$tile_url";
+ }
+ my $tile = LWP::Simple::get($tile_url);
+ my $im = Image::Magick->new;
+ $im->BlobToImage($tile);
+ if (!$image) {
+ $image = $im;
+ $image->Extent(geometry => '512x512', gravity => 'NorthWest');
+ } else {
+ my $gravity = ($i<2?'North':'South') . ($i%2?'East':'West');
+ $image->Composite(image => $im, gravity => $gravity);
+ }
+ }
+
+ # The only pin might be the report pin, with added x/y
+ my $pin = $map_data->{pins}->[0];
+ if ($pin) {
+ my $im = Image::Magick->new;
+ $im->read(FixMyStreet->path_to('web', 'i', 'pin-yellow.png'));
+ $image->Composite(image => $im, gravity => 'NorthWest',
+ x => $pin->{px} - 24, y => $pin->{py} - 64);
+ }
+
+ # Bottom 128/ top 64 pixels will never have a pin
+ $image->Extent( geometry => '512x384', gravity => 'NorthWest');
+ $image->Extent( geometry => '512x320', gravity => 'SouthWest');
+
+ $image->Scale( geometry => "310x200>" );
+
+ my @blobs = $image->ImageToBlob(magick => 'jpeg');
+ undef $image;
+
+ FixMyStreet::Map::set_map_class($orig_map_class) if $orig_map_class;
+
+ return {
+ data => $blobs[0],
+ content_type => 'image/jpeg',
+ };
}
1;
diff --git a/perllib/FixMyStreet/Email.pm b/perllib/FixMyStreet/Email.pm
index d955f6f72..34ac1514c 100644
--- a/perllib/FixMyStreet/Email.pm
+++ b/perllib/FixMyStreet/Email.pm
@@ -8,6 +8,7 @@ package FixMyStreet::Email;
use Email::MIME;
use Encode;
+use File::Spec;
use POSIX qw();
use Template;
use Digest::HMAC_SHA1 qw(hmac_sha1_hex);
@@ -72,10 +73,77 @@ sub is_abuser {
sub _render_template {
my ($tt, $template, $vars, %options) = @_;
my $var;
- $tt->process($template, $vars, \$var);
+ $tt->process($template, $vars, \$var) || print "Template processing error: " . $tt->error() . "\n";
return $var;
}
+sub _unique_id {
+ sprintf('fms-%s-%s@%s',
+ time(), unpack('h*', random_bytes(5, 1)),
+ FixMyStreet->config('EMAIL_DOMAIN'));
+}
+
+sub message_id {
+ '<' . _unique_id() . '>'
+}
+
+sub add_inline_image {
+ my ($inline_images, $obj, $name) = @_;
+ if (ref $obj eq 'HASH') {
+ return _add_inline($inline_images, $name, $obj->{data}, $obj->{content_type});
+ } else {
+ my $file = FixMyStreet->path_to($obj);
+ return _add_inline($inline_images, $file->basename, scalar $file->slurp);
+ }
+}
+
+sub _add_inline {
+ my ($inline_images, $name, $data, $type) = @_;
+
+ return unless $data;
+
+ $name ||= 'photo';
+ if ($type) {
+ if ($name !~ /\./) {
+ my ($suffix) = $type =~ m{image/(.*)};
+ $name .= ".$suffix";
+ }
+ } else {
+ my ($b, $t) = split /\./, $name;
+ $type = "image/$t";
+ }
+
+ my $cid = _unique_id();
+ push @$inline_images, {
+ body => $data,
+ attributes => {
+ id => $cid,
+ filename => $name,
+ content_type => $type,
+ encoding => 'base64',
+ name => $name,
+ },
+ };
+ return "cid:$cid";
+}
+
+# We only want an HTML template from the same directory as the .txt
+sub get_html_template {
+ my ($template, @include_path) = @_;
+ push @include_path, FixMyStreet->path_to( 'templates', 'email', 'default' );
+ (my $html_template = $template) =~ s/\.txt$/\.html/;
+ my $template_dir = find_template_dir($template, @include_path);
+ my $html_template_dir = find_template_dir($html_template, @include_path);
+ return $html_template if $template_dir eq $html_template_dir;
+}
+
+sub find_template_dir {
+ my ($template, @include_path) = @_;
+ foreach (@include_path) {
+ return $_ if -e File::Spec->catfile($_, $template);
+ }
+}
+
sub send_cron {
my ( $schema, $template, $vars, $hdrs, $env_from, $nomail, $cobrand, $lang_code ) = @_;
@@ -88,11 +156,11 @@ sub send_cron {
return 1 if is_abuser($schema, $hdrs->{To});
- $hdrs->{'Message-ID'} = sprintf('<fms-cron-%s-%s@%s>', time(),
- unpack('h*', random_bytes(5, 1)), FixMyStreet->config('EMAIL_DOMAIN')
- );
+ $hdrs->{'Message-ID'} = message_id();
my @include_path = @{ $cobrand->path_to_email_templates($lang_code) };
+ my $html_template = get_html_template($template, @include_path);
+
push @include_path, FixMyStreet->path_to( 'templates', 'email', 'default' );
my $tt = Template->new({
ENCODING => 'utf8',
@@ -102,6 +170,14 @@ sub send_cron {
$vars->{site_name} = Utils::trim_text(_render_template($tt, 'site-name.txt', $vars));
$hdrs->{_body_} = _render_template($tt, $template, $vars);
+ if ($html_template) {
+ my @inline_images;
+ $vars->{inline_image} = sub { add_inline_image(\@inline_images, @_) };
+ $vars->{file_exists} = sub { -e FixMyStreet->path_to(@_) };
+ $hdrs->{_html_} = _render_template($tt, $html_template, $vars);
+ $hdrs->{_html_images_} = \@inline_images;
+ }
+
my $email = mySociety::Locale::in_gb_locale { construct_email($hdrs) };
if ($nomail) {
@@ -236,6 +312,47 @@ sub construct_email ($) {
),
];
+ my $overall_type;
+ if ($p->{_html_}) {
+ my $html = _mime_create(
+ body_str => $p->{_html_},
+ attributes => {
+ charset => 'utf-8',
+ encoding => 'quoted-printable',
+ content_type => 'text/html',
+ },
+ );
+ if ($p->{_html_images_} || $p->{_attachments_}) {
+ $parts = [ _mime_create(
+ attributes => { content_type => 'multipart/alternative' },
+ parts => [ $parts->[0], $html ]
+ ) ];
+ } else {
+ # The top level will be the alternative multipart if there are
+ # no images and no other attachments
+ push @$parts, $html;
+ $overall_type = 'multipart/alternative';
+ }
+ if ($p->{_html_images_}) {
+ foreach (@{$p->{_html_images_}}) {
+ my $cid = delete $_->{attributes}->{id};
+ my $part = _mime_create(%$_);
+ $part->header_set('Content-ID' => "<$cid>");
+ push @$parts, $part;
+ }
+ if ($p->{_attachments_}) {
+ $parts = [ _mime_create(
+ attributes => { content_type => 'multipart/related' },
+ parts => $parts,
+ ) ];
+ } else {
+ # The top level will be the related multipart if there are
+ # images but no other attachments
+ $overall_type = 'multipart/related';
+ }
+ }
+ }
+
if ($p->{_attachments_}) {
push @$parts, map { _mime_create(%$_) } @{$p->{_attachments_}};
}
@@ -245,6 +362,7 @@ sub construct_email ($) {
parts => $parts,
attributes => {
charset => 'utf-8',
+ $overall_type ? (content_type => $overall_type) : (),
},
);
diff --git a/perllib/FixMyStreet/Map.pm b/perllib/FixMyStreet/Map.pm
index 355fd8666..a850492b9 100644
--- a/perllib/FixMyStreet/Map.pm
+++ b/perllib/FixMyStreet/Map.pm
@@ -47,7 +47,8 @@ sub reload_allowed_maps {
=head2 map_class
-Set and return the appropriate class given a query parameter string.
+Sets the appropriate class given a query parameter string.
+Returns the old map class, if any.
=cut
@@ -57,7 +58,9 @@ sub set_map_class {
$str = __PACKAGE__.'::'.$str if $str;
my %avail = map { $_ => 1 } @ALL_MAP_CLASSES;
$str = $ALL_MAP_CLASSES[0] unless $str && $avail{$str};
+ my $old_map_class = $map_class;
$map_class = $str;
+ return $old_map_class;
}
sub display_map {
diff --git a/perllib/FixMyStreet/Map/OSM.pm b/perllib/FixMyStreet/Map/OSM.pm
index ae9e73a0a..d4000f1a4 100644
--- a/perllib/FixMyStreet/Map/OSM.pm
+++ b/perllib/FixMyStreet/Map/OSM.pm
@@ -50,6 +50,23 @@ sub copyright {
sub display_map {
my ($self, $c, %params) = @_;
+ # Map centre may be overridden in the query string
+ $params{latitude} = Utils::truncate_coordinate($c->get_param('lat') + 0)
+ if defined $c->get_param('lat');
+ $params{longitude} = Utils::truncate_coordinate($c->get_param('lon') + 0)
+ if defined $c->get_param('lon');
+
+ my %data;
+ $data{cobrand} = $c->cobrand;
+ $data{distance} = $c->stash->{distance};
+ $data{zoom} = $c->get_param('zoom') + 0 if defined $c->get_param('zoom');
+
+ $c->stash->{map} = $self->generate_map_data(\%data, %params);
+}
+
+sub generate_map_data {
+ my ($self, $data, %params) = @_;
+
my $numZoomLevels = ZOOM_LEVELS;
my $zoomOffset = MIN_ZOOM_LEVEL;
if ($params{any_zoom}) {
@@ -58,18 +75,12 @@ sub display_map {
}
# Adjust zoom level dependent upon population density
- my $dist = $c->stash->{distance}
+ my $dist = $data->{distance}
|| FixMyStreet::Gaze::get_radius_containing_population( $params{latitude}, $params{longitude} );
- my $default_zoom = $c->cobrand->default_map_zoom() ? $c->cobrand->default_map_zoom() : $numZoomLevels - 4;
+ my $default_zoom = $data->{cobrand}->default_map_zoom() || ($numZoomLevels - 4);
$default_zoom = $numZoomLevels - 3 if $dist < 10;
- # Map centre may be overridden in the query string
- $params{latitude} = Utils::truncate_coordinate($c->get_param('lat') + 0)
- if defined $c->get_param('lat');
- $params{longitude} = Utils::truncate_coordinate($c->get_param('lon') + 0)
- if defined $c->get_param('lon');
-
- my $zoom = defined $c->get_param('zoom') ? $c->get_param('zoom') + 0 : $default_zoom;
+ my $zoom = $data->{zoom} || $default_zoom;
$zoom = $numZoomLevels - 1 if $zoom >= $numZoomLevels;
$zoom = 0 if $zoom < 0;
$params{zoom_act} = $zoomOffset + $zoom;
@@ -79,7 +90,7 @@ sub display_map {
($pin->{px}, $pin->{py}) = latlon_to_px($pin->{latitude}, $pin->{longitude}, $params{x_tile}, $params{y_tile}, $params{zoom_act});
}
- $c->stash->{map} = {
+ return {
%params,
type => $self->map_template(),
map_type => $self->map_type(),
diff --git a/perllib/FixMyStreet/Roles/PhotoSet.pm b/perllib/FixMyStreet/Roles/PhotoSet.pm
new file mode 100644
index 000000000..9607b5049
--- /dev/null
+++ b/perllib/FixMyStreet/Roles/PhotoSet.pm
@@ -0,0 +1,35 @@
+package FixMyStreet::Roles::PhotoSet;
+use Moo::Role;
+
+=head1 NAME
+
+FixMyStreet::Roles::Photoset - role for accessing photosets
+
+=cut
+
+=head2 get_photoset
+
+Return a PhotoSet object for all photos attached to this field
+
+ my $photoset = $obj->get_photoset;
+ print $photoset->num_images;
+ return $photoset->get_image_data(num => 0, size => 'full');
+
+=cut
+
+sub get_photoset {
+ my ($self) = @_;
+ my $class = 'FixMyStreet::App::Model::PhotoSet';
+ eval "use $class";
+ return $class->new({
+ db_data => $self->photo,
+ object => $self,
+ });
+}
+
+sub get_first_image_fp {
+ my ($self) = @_;
+ return $self->get_photoset->get_image_data( num => 0, size => 'fp' );
+}
+
+1;
diff --git a/perllib/FixMyStreet/Script/Alerts.pm b/perllib/FixMyStreet/Script/Alerts.pm
index 062601044..91f5cd6ef 100644
--- a/perllib/FixMyStreet/Script/Alerts.pm
+++ b/perllib/FixMyStreet/Script/Alerts.pm
@@ -15,9 +15,13 @@ use RABX;
use FixMyStreet::Cobrand;
use FixMyStreet::DB;
use FixMyStreet::Email;
+use FixMyStreet::Map;
+use FixMyStreet::App::Model::PhotoSet;
FixMyStreet->configure_mysociety_dbhandle;
+my $parser = DateTime::Format::Pg->new();
+
# 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
@@ -37,6 +41,7 @@ sub send() {
$item_table.id as item_id, $item_table.text as item_text,
$item_table.name as item_name, $item_table.anonymous as item_anonymous,
$item_table.confirmed as item_confirmed,
+ $item_table.photo as item_photo,
$head_table.*
from alert, $item_table, $head_table
where alert.parameter::integer = $head_table.id
@@ -63,7 +68,7 @@ sub send() {
$query = dbh()->prepare($query);
$query->execute();
my $last_alert_id;
- my %data = ( template => $alert_type->template, data => '', schema => $schema );
+ my %data = ( template => $alert_type->template, data => [], schema => $schema );
while (my $row = $query->fetchrow_hashref) {
my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($row->{alert_cobrand})->new();
@@ -84,7 +89,7 @@ sub send() {
} );
if ($last_alert_id && $last_alert_id != $row->{alert_id}) {
_send_aggregated_alert_email(%data);
- %data = ( template => $alert_type->template, data => '', schema => $schema );
+ %data = ( template => $alert_type->template, data => [], schema => $schema );
}
# create problem status message for the templates
@@ -116,30 +121,50 @@ sub send() {
} else {
$data{problem_url} = $url . "/report/" . $row->{id};
}
- $data{data} .= $row->{item_name} . ' : ' if $row->{item_name} && !$row->{item_anonymous};
- if ( $cobrand->include_time_in_update_alerts ) {
- my $parser = DateTime::Format::Pg->new();
- my $dt = $parser->parse_timestamp( $row->{item_confirmed} );
- # We need to always set this otherwise we end up with the DateTime
- # object being in the floating timezone in which case applying a
- # subsequent timezone set will have no effect.
- # this is basically recreating the code from the inflate wrapper
- # in the database model.
- FixMyStreet->set_time_zone($dt);
- $data{data} .= $cobrand->prettify_dt( $dt, 'alert' ) . "\n\n";
- }
- $data{data} .= $row->{item_text} . "\n\n------\n\n";
+
+ my $dt = $parser->parse_timestamp( $row->{item_confirmed} );
+ # We need to always set this otherwise we end up with the DateTime
+ # object being in the floating timezone in which case applying a
+ # subsequent timezone set will have no effect.
+ # this is basically recreating the code from the inflate wrapper
+ # in the database model.
+ FixMyStreet->set_time_zone($dt);
+ $row->{confirmed} = $dt;
+
+ # Hack in the image for the non-object updates
+ $row->{get_first_image_fp} = sub {
+ return FixMyStreet::App::Model::PhotoSet->new({
+ db_data => $row->{item_photo},
+ })->get_image_data( num => 0, size => 'fp' );
+ };
+
# this is ward and council problems
} else {
- $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n";
if ( exists $row->{geocode} && $row->{geocode} && $ref =~ /ward|council/ ) {
my $nearest_st = _get_address_from_gecode( $row->{geocode} );
- $data{data} .= $nearest_st if $nearest_st;
+ $row->{nearest} = $nearest_st;
}
- $data{data} .= "\n\n------\n\n";
+
+ my $dt = $parser->parse_timestamp( $row->{confirmed} );
+ FixMyStreet->set_time_zone($dt);
+ $row->{confirmed} = $dt;
+
+ # Hack in the image for the non-object reports
+ $row->{get_first_image_fp} = sub {
+ return FixMyStreet::App::Model::PhotoSet->new({
+ db_data => $row->{photo},
+ })->get_image_data( num => 0, size => 'fp' );
+ };
}
+
+ push @{$data{data}}, $row;
+
if (!$data{alert_user_id}) {
%data = (%data, %$row);
+ if ($ref eq 'new_updates') {
+ # Get a report object for its photo and static map
+ $data{report} = $schema->resultset('Problem')->find({ id => $row->{id} });
+ }
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};
@@ -149,7 +174,7 @@ sub send() {
$data{ward_name} = $va_info->{name};
}
}
- $data{cobrand} = $row->{alert_cobrand};
+ $data{cobrand} = $cobrand;
$data{cobrand_data} = $row->{alert_cobrand_data};
$data{lang} = $row->{alert_lang};
$last_alert_id = $row->{alert_id};
@@ -183,15 +208,16 @@ sub send() {
my $states = "'" . join( "', '", FixMyStreet::DB::Result::Problem::visible_states() ) . "'";
my %data = (
template => $template,
- data => '',
+ data => [],
alert_id => $alert->id,
alert_email => $alert->user->email,
lang => $alert->lang,
- cobrand => $alert->cobrand,
+ cobrand => $cobrand,
cobrand_data => $alert->cobrand_data,
schema => $schema,
);
- my $q = "select problem.id, problem.bodies_str, problem.postcode, problem.geocode, problem.title from problem_find_nearby(?, ?, ?) as nearby, problem, users
+ my $q = "select problem.id, problem.bodies_str, problem.postcode, problem.geocode, problem.confirmed,
+ problem.title, problem.detail, problem.photo from problem_find_nearby(?, ?, ?) as nearby, problem, users
where nearby.problem_id = problem.id
and problem.user_id = users.id
and problem.state in ($states)
@@ -207,24 +233,31 @@ sub send() {
alert_id => $alert->id,
parameter => $row->{id},
} );
- my $url = $cobrand->base_url_for_report($row);
- $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n";
if ( exists $row->{geocode} && $row->{geocode} ) {
my $nearest_st = _get_address_from_gecode( $row->{geocode} );
- $data{data} .= $nearest_st if $nearest_st;
+ $row->{nearest} = $nearest_st;
}
- $data{data} .= "\n\n------\n\n";
+ my $dt = $parser->parse_timestamp( $row->{confirmed} );
+ FixMyStreet->set_time_zone($dt);
+ $row->{confirmed} = $dt;
+ $row->{get_first_image_fp} = sub {
+ return FixMyStreet::App::Model::PhotoSet->new({
+ db_data => $row->{photo},
+ })->get_image_data( num => 0, size => 'fp' );
+ };
+ push @{$data{data}}, $row;
}
- _send_aggregated_alert_email(%data) if $data{data};
+ _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();
+ my $cobrand = $data{cobrand};
$cobrand->set_lang_and_domain( $data{lang}, 1, FixMyStreet->path_to('locale')->stringify );
+ FixMyStreet::Map::set_map_class($cobrand->map_type);
if (!$data{alert_email}) {
my $user = $data{schema}->resultset('User')->find( {
diff --git a/perllib/FixMyStreet/Script/Questionnaires.pm b/perllib/FixMyStreet/Script/Questionnaires.pm
index c5bc6bfe0..3f22eb150 100644
--- a/perllib/FixMyStreet/Script/Questionnaires.pm
+++ b/perllib/FixMyStreet/Script/Questionnaires.pm
@@ -5,6 +5,7 @@ use warnings;
use Utils;
use FixMyStreet::DB;
use FixMyStreet::Email;
+use FixMyStreet::Map;
use FixMyStreet::Cobrand;
sub send {
@@ -41,6 +42,7 @@ sub send_questionnaires_period {
my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($row->cobrand)->new();
$cobrand->set_lang_and_domain($row->lang, 1);
+ FixMyStreet::Map::set_map_class($cobrand->map_type);
# Not all cobrands send questionnaires
next unless $cobrand->send_questionnaires;
@@ -53,6 +55,7 @@ sub send_questionnaires_period {
next unless $cobrand->email_host;
my %h = map { $_ => $row->$_ } qw/name title detail category/;
+ $h{report} = $row;
$h{created} = Utils::prettify_duration( time() - $row->confirmed->epoch, 'week' );
my $questionnaire = $rs->create( {
diff --git a/perllib/FixMyStreet/Script/Reports.pm b/perllib/FixMyStreet/Script/Reports.pm
index 30d24f640..311d8fec4 100644
--- a/perllib/FixMyStreet/Script/Reports.pm
+++ b/perllib/FixMyStreet/Script/Reports.pm
@@ -14,6 +14,7 @@ use FixMyStreet;
use FixMyStreet::Cobrand;
use FixMyStreet::DB;
use FixMyStreet::Email;
+use FixMyStreet::Map;
use FixMyStreet::SendReport;
sub send(;$) {
@@ -60,6 +61,7 @@ sub send(;$) {
}
$cobrand->set_lang_and_domain($row->lang, 1);
+ FixMyStreet::Map::set_map_class($cobrand->map_type);
if ( $row->is_from_abuser) {
$row->update( { state => 'hidden' } );
debug_print("hiding because its sender is flagged as an abuser", $row->id) if $debug_mode;
@@ -73,6 +75,7 @@ sub send(;$) {
# Template variables for the email
my $email_base_url = $cobrand->base_url_for_report($row);
my %h = map { $_ => $row->$_ } qw/id title detail name category latitude longitude used_map/;
+ $h{report} = $row;
map { $h{$_} = $row->user->$_ || '' } qw/email phone/;
$h{confirmed} = DateTime::Format::Pg->format_datetime( $row->confirmed->truncate (to => 'second' ) )
if $row->confirmed;
diff --git a/perllib/FixMyStreet/SendReport/Email.pm b/perllib/FixMyStreet/SendReport/Email.pm
index 8582ebb3b..2eab1c754 100644
--- a/perllib/FixMyStreet/SendReport/Email.pm
+++ b/perllib/FixMyStreet/SendReport/Email.pm
@@ -52,7 +52,6 @@ sub build_recipient_list {
sub get_template {
my ( $self, $row ) = @_;
- return 'submit-oxfordshire.txt' if $row->cobrand eq 'fixmystreet' && $row->bodies_str eq 2237;
return 'submit.txt';
}
diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm
index 937780a31..5f4a6ceed 100644
--- a/perllib/FixMyStreet/TestMech.pm
+++ b/perllib/FixMyStreet/TestMech.pm
@@ -221,6 +221,48 @@ sub get_email {
return $emails[0];
}
+sub get_text_body_from_email {
+ my ($mech, $email, $obj) = @_;
+ unless ($email) {
+ $email = $mech->get_email;
+ $mech->clear_emails_ok;
+ }
+
+ my $body;
+ $email->walk_parts(sub {
+ my $part = shift;
+ return if $part->subparts;
+ return if $part->content_type !~ m{text/plain};
+ $body = $obj ? $part : $part->body;
+ ok $body, "Found text body";
+ });
+ return $body;
+}
+
+sub get_link_from_email {
+ my ($mech, $email, $multiple) = @_;
+ unless ($email) {
+ $email = $mech->get_email;
+ $mech->clear_emails_ok;
+ }
+
+ my @links;
+ $email->walk_parts(sub {
+ my $part = shift;
+ return if $part->subparts;
+ return if $part->content_type !~ m{text/};
+ if (@links) {
+ # Must be an HTML part now, first two links are in header
+ my @html_links = $part->body =~ m{https?://[^"]+}g;
+ is $links[0], $html_links[2], 'HTML link matches text link';
+ } else {
+ @links = $part->body =~ m{https?://\S+}g;
+ ok @links, "Found links in email '@links'";
+ }
+ });
+ return $multiple ? @links : $links[0];
+}
+
=head2 get_first_email
$email = $mech->get_first_email(@emails);