diff options
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r-- | perllib/FixMyStreet/App.pm | 40 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Contact.pm | 20 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Photo.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report.pm | 11 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Model/PhotoSet.pm | 14 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/View/Email.pm | 7 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Default.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/Comment.pm | 23 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/Problem.pm | 102 | ||||
-rw-r--r-- | perllib/FixMyStreet/Email.pm | 126 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map.pm | 5 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/OSM.pm | 31 | ||||
-rw-r--r-- | perllib/FixMyStreet/Roles/PhotoSet.pm | 35 | ||||
-rw-r--r-- | perllib/FixMyStreet/Script/Alerts.pm | 89 | ||||
-rw-r--r-- | perllib/FixMyStreet/Script/Questionnaires.pm | 3 | ||||
-rw-r--r-- | perllib/FixMyStreet/Script/Reports.pm | 3 | ||||
-rw-r--r-- | perllib/FixMyStreet/SendReport/Email.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/TestMech.pm | 42 |
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); |