diff options
Diffstat (limited to 'perllib/FixMyStreet')
40 files changed, 774 insertions, 1147 deletions
diff --git a/perllib/FixMyStreet/App.pm b/perllib/FixMyStreet/App.pm index b8ce2e051..79ca7f9ee 100644 --- a/perllib/FixMyStreet/App.pm +++ b/perllib/FixMyStreet/App.pm @@ -6,7 +6,6 @@ use Catalyst::Runtime 5.80; use FixMyStreet; use FixMyStreet::Cobrand; use Memcached; -use mySociety::Email; use mySociety::Random qw(random_bytes); use FixMyStreet::Map; use FixMyStreet::Email; @@ -247,7 +246,7 @@ sub setup_dev_overrides { delete $params{$_} for grep { !m{^_override_} } keys %params; # stop if there is nothing to add - return 1 unless scalar keys %params; + return unless scalar keys %params; # Check to see if we should clear all if ( $params{_override_clear_all} ) { @@ -271,14 +270,14 @@ sub setup_dev_overrides { Checks the overrides for the value given and returns it if found, undef if not. -Always returns undef unless on a staging site (avoids autovivifying overrides -hash in session and so creating a session for all users). +Always returns undef unless on a staging site and we already have a session +(avoids autovivifying overrides hash and so creating a session for all users). =cut sub get_override { my ( $c, $key ) = @_; - return unless $c->config->{STAGING_SITE}; + return unless $c->config->{STAGING_SITE} && $c->sessionid; return $c->session->{overrides}->{$key}; } @@ -319,37 +318,23 @@ sub send_email { return if FixMyStreet::Email::is_abuser($c->model('DB')->schema, $vars->{to}); - # render the template - my $content = $c->view('Email')->render( $c, $template, $vars ); - - # create an email - will parse headers out of content - my $email = Email::Simple->new($content); - $email->header_set( 'Subject', $vars->{subject} ) if $vars->{subject}; - $email->header_set( 'Reply-To', $vars->{'Reply-To'} ) if $vars->{'Reply-To'}; - - $email->header_set( 'Message-ID', sprintf('<fms-%s-%s@%s>', - time(), unpack('h*', random_bytes(5, 1)), $c->config->{EMAIL_DOMAIN} - ) ); - - # pass the email into mySociety::Email to construct the on the wire 7bit - # format - this should probably happen in the transport instead but hohum. - my $email_text = mySociety::Locale::in_gb_locale { mySociety::Email::construct_email( + my $email = mySociety::Locale::in_gb_locale { FixMyStreet::Email::construct_email( { - _template_ => $email->body, # will get line wrapped + _template_ => $c->view('Email')->render( $c, $template, $vars ), _parameters_ => {}, - _line_indent => '', + _attachments_ => $extra_stash_values->{attachments}, From => $vars->{from}, To => $vars->{to}, - $email->header_pairs + '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'}) : (), } ) }; - if (my $attachments = $extra_stash_values->{attachments}) { - $email_text = FixMyStreet::Email::munge_attachments($email_text, $attachments); - } - # send the email - $c->model('EmailSend')->send($email_text); + $c->model('EmailSend')->send($email); return $email; } diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index 2bf215c56..f54a862ab 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -345,8 +345,6 @@ sub update_contacts : Private { $errors{email} = _('Please enter a valid email') unless is_valid_email($email) || $email eq 'REFUSED'; $errors{note} = _('Please enter a message') unless $c->get_param('note'); - $category = 'Empty property' if $c->cobrand->moniker eq 'emptyhomes'; - my $contact = $c->model('DB::Contact')->find_or_new( { body_id => $c->stash->{body_id}, @@ -743,16 +741,6 @@ sub report_edit : Path('report_edit') : Args(1) { my $new_state = $c->get_param('state'); my $old_state = $problem->state; - if ( $new_state eq 'confirmed' - && $problem->state eq 'unconfirmed' - && $c->cobrand->moniker eq 'emptyhomes' ) - { - $c->stash->{status_message} = - '<p><em>' - . _('I am afraid you cannot confirm unconfirmed reports.') - . '</em></p>'; - $done = 1; - } my $flagged = $c->get_param('flagged') ? 1 : 0; my $non_public = $c->get_param('non_public') ? 1 : 0; diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm index 1e6d9ec9e..b0340204a 100644 --- a/perllib/FixMyStreet/App/Controller/Around.pm +++ b/perllib/FixMyStreet/App/Controller/Around.pm @@ -47,7 +47,7 @@ sub index : Path : Args(0) { } # Check to see if the spot is covered by a area - if not show an error. - return unless $c->cobrand->moniker eq 'fixmybarangay' || $c->forward('check_location_is_acceptable'); + return unless $c->forward('check_location_is_acceptable'); # If we have a partial - redirect to /report/new so that it can be # completed. @@ -182,7 +182,7 @@ sub display_location : Private { # create a list of all the pins my @pins; - unless ($c->get_param('no_pins') || $c->cobrand->moniker eq 'emptyhomes') { + unless ($c->get_param('no_pins')) { @pins = map { # Here we might have a DB::Problem or a DB::Nearby, we always want the problem. my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_; diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm index 9e8fb29aa..c5a6cf9bf 100644 --- a/perllib/FixMyStreet/App/Controller/Auth.pm +++ b/perllib/FixMyStreet/App/Controller/Auth.pm @@ -9,6 +9,7 @@ use Net::Domain::TLD; use mySociety::AuthToken; use JSON::MaybeXS; use Net::Facebook::Oauth2; +use Net::Twitter::Lite::WithAPIv1_1; =head1 NAME @@ -38,6 +39,7 @@ sub general : Path : Args(0) { # decide which action to take $c->detach('facebook_sign_in') if $c->get_param('facebook_sign_in'); + $c->detach('twitter_sign_in') if $c->get_param('twitter_sign_in'); my $clicked_password = $c->get_param('sign_in'); my $clicked_email = $c->get_param('email_sign_in'); @@ -133,6 +135,8 @@ sub email_sign_in : Private { }; $token_data->{facebook_id} = $c->session->{oauth}{facebook_id} if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id}; + $token_data->{twitter_id} = $c->session->{oauth}{twitter_id} + if $c->get_param('oauth_need_email') && $c->session->{oauth}{twitter_id}; my $token_obj = $c->model('DB::Token')->create({ scope => 'email_sign_in', @@ -180,6 +184,7 @@ sub token : Path('/M') : Args(1) { $user->name( $data->{name} ) if $data->{name}; $user->password( $data->{password}, 1 ) if $data->{password}; $user->facebook_id( $data->{facebook_id} ) if $data->{facebook_id}; + $user->twitter_id( $data->{twitter_id} ) if $data->{twitter_id}; $user->update; $c->authenticate( { email => $user->email }, 'no_password' ); @@ -203,7 +208,7 @@ sub fb : Private { } sub facebook_sign_in : Private { - my( $self, $c ) = @_; + my ( $self, $c ) = @_; my $fb = $c->forward('/auth/fb'); my $url = $fb->get_authorization_url(scope => ['email']); @@ -223,17 +228,9 @@ Handles the Facebook callback request and completes the authentication sequence. =cut sub facebook_callback: Path('/auth/Facebook') : Args(0) { - my( $self, $c ) = @_; + my ( $self, $c ) = @_; - if ( $c->get_param('error_code') ) { - $c->stash->{oauth_failure} = 1; - if ($c->session->{oauth}{detach_to}) { - $c->detach($c->session->{oauth}{detach_to}, $c->session->{oauth}{detach_args}); - } else { - $c->stash->{template} = 'auth/general.html'; - $c->detach; - } - } + $c->detach('oauth_failure') if $c->get_param('error_code'); my $fb = $c->forward('/auth/fb'); my $access_token; @@ -250,12 +247,92 @@ sub facebook_callback: Path('/auth/Facebook') : Args(0) { $c->session->{oauth}{token} = $access_token; my $info = $fb->get('https://graph.facebook.com/me?fields=name,email')->as_hash(); - my $name = $info->{name}; my $email = lc ($info->{email} || ""); - my $uid = $info->{id}; + $c->forward('oauth_success', [ 'facebook', $info->{id}, $info->{name}, $email ]); +} + +=head2 twitter_sign_in + +Starts the Twitter authentication sequence. + +=cut + +sub tw : Private { + my ($self, $c) = @_; + Net::Twitter::Lite::WithAPIv1_1->new( + ssl => 1, + consumer_key => $c->config->{TWITTER_KEY}, + consumer_secret => $c->config->{TWITTER_SECRET}, + ); +} + +sub twitter_sign_in : Private { + my ( $self, $c ) = @_; + + my $twitter = $c->forward('/auth/tw'); + my $url = $twitter->get_authentication_url(callback => $c->uri_for('/auth/Twitter')); + + my %oauth; + $oauth{return_url} = $c->get_param('r'); + $oauth{detach_to} = $c->stash->{detach_to}; + $oauth{detach_args} = $c->stash->{detach_args}; + $oauth{token} = $twitter->request_token; + $oauth{token_secret} = $twitter->request_token_secret; + $c->session->{oauth} = \%oauth; + $c->res->redirect($url); +} + +=head2 twitter_callback + +Handles the Twitter callback request and completes the authentication sequence. + +=cut + +sub twitter_callback: Path('/auth/Twitter') : Args(0) { + my ( $self, $c ) = @_; + + my $request_token = $c->req->param('oauth_token'); + my $verifier = $c->req->param('oauth_verifier'); + my $oauth = $c->session->{oauth}; + + $c->detach('oauth_failure') if $c->get_param('denied') || $request_token ne $oauth->{token}; + + my $twitter = $c->forward('/auth/tw'); + $twitter->request_token($oauth->{token}); + $twitter->request_token_secret($oauth->{token_secret}); + + eval { + # request_access_token no longer returns UID or name + $twitter->request_access_token(verifier => $verifier); + }; + if ($@) { + ($c->stash->{message} = $@) =~ s/at [^ ]*Auth.pm.*//; + $c->stash->{template} = 'errors/generic.html'; + $c->detach; + } + + my $info = $twitter->verify_credentials(); + $c->forward('oauth_success', [ 'twitter', $info->{id}, $info->{name} ]); +} + +sub oauth_failure : Private { + my ( $self, $c ) = @_; + + $c->stash->{oauth_failure} = 1; + if ($c->session->{oauth}{detach_to}) { + $c->detach($c->session->{oauth}{detach_to}, $c->session->{oauth}{detach_args}); + } else { + $c->stash->{template} = 'auth/general.html'; + $c->detach; + } +} + +sub oauth_success : Private { + my ($self, $c, $type, $uid, $name, $email) = @_; my $user; if ($email) { + # Only Facebook gets here # We've got an ID and an email address # Remove any existing mention of this ID my $existing = $c->model('DB::User')->find( { facebook_id => $uid } ); @@ -267,14 +344,14 @@ sub facebook_callback: Path('/auth/Facebook') : Args(0) { $user->in_storage() ? $user->update : $user->insert; } else { # We've got an ID, but no email - $user = $c->model('DB::User')->find( { facebook_id => $uid } ); + $user = $c->model('DB::User')->find( { $type . '_id' => $uid } ); if ($user) { - # Matching Facebook ID in our database + # Matching ID in our database $user->name($name); $user->update; } else { # No matching ID, store ID for use later - $c->session->{oauth}{facebook_id} = $uid; + $c->session->{oauth}{$type . '_id'} = $uid; $c->stash->{oauth_need_email} = 1; } } diff --git a/perllib/FixMyStreet/App/Controller/Contact.pm b/perllib/FixMyStreet/App/Controller/Contact.pm index 115f4e3d2..e20011471 100644 --- a/perllib/FixMyStreet/App/Controller/Contact.pm +++ b/perllib/FixMyStreet/App/Controller/Contact.pm @@ -242,8 +242,7 @@ sub send_email : Private { my $from = [ $c->stash->{em}, $c->stash->{form_name} ]; my $params = { - to => [ [ $recipient, _($recipient_name) ] ], - subject => 'FMS message: ' . $c->stash->{subject}, + to => [ [ $recipient, _($recipient_name) ] ], }; if (FixMyStreet::Email::test_dmarc($c->stash->{em})) { $params->{'Reply-To'} = [ $from ]; diff --git a/perllib/FixMyStreet/App/Controller/Location.pm b/perllib/FixMyStreet/App/Controller/Location.pm index 6def423ce..c457c8fce 100644 --- a/perllib/FixMyStreet/App/Controller/Location.pm +++ b/perllib/FixMyStreet/App/Controller/Location.pm @@ -134,22 +134,6 @@ sub check_location : Private { return 1; } -=head2 country_message - -Displays the country_message template, used for displaying a message to -people using the site from outside the host country. - -=cut - -sub country_message : Path('/country_message') : Args(0) { - my ( $self, $c ) = @_; - - # we do not want to cache this as we always want to check if displaying this - # is the right thing to do. - $c->res->header( 'Cache_Control' => 'max-age=0' ); - $c->stash->{template} = 'front/international_banner.html'; -} - # Utility function for if someone (rarely) enters a grid reference sub gridref_to_latlon { my ( $a, $b, $num ) = @_; diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm index f35dc64a5..4f1727b1a 100644 --- a/perllib/FixMyStreet/App/Controller/Open311.pm +++ b/perllib/FixMyStreet/App/Controller/Open311.pm @@ -284,7 +284,7 @@ sub output_requests : Private { my $display_photos = $c->cobrand->allow_photo_display($problem); if ($display_photos && $problem->photo) { my $url = $c->cobrand->base_url(); - my $imgurl = $url . "/photo/$id.full.jpeg"; + my $imgurl = $url . $problem->photos->[0]->{url_full}; $request->{'media_url'} = [ $imgurl ]; } push(@problemlist, $request); diff --git a/perllib/FixMyStreet/App/Controller/Photo.pm b/perllib/FixMyStreet/App/Controller/Photo.pm index bfb1c5535..d24d3ff71 100644 --- a/perllib/FixMyStreet/App/Controller/Photo.pm +++ b/perllib/FixMyStreet/App/Controller/Photo.pm @@ -29,12 +29,12 @@ Display a photo =cut -sub during :LocalRegex('^([0-9a-f]{40})\.(temp|fulltemp)\.jpeg$') { +sub during :LocalRegex('^(temp|fulltemp)\.([0-9a-f]{40}\.(?:jpeg|png|gif|tiff))$') { my ( $self, $c ) = @_; - my ( $hash, $size ) = @{ $c->req->captures }; + my ( $size, $filename ) = @{ $c->req->captures }; my $photoset = FixMyStreet::App::Model::PhotoSet->new({ - data_items => [ $hash ] + data_items => [ $filename ] }); $size = $size eq 'temp' ? 'default' : 'full'; @@ -43,7 +43,7 @@ sub during :LocalRegex('^([0-9a-f]{40})\.(temp|fulltemp)\.jpeg$') { $c->forward( 'output', [ $photo ] ); } -sub index :LocalRegex('^(c/)?(\d+)(?:\.(\d+))?(?:\.(full|tn|fp))?\.jpeg$') { +sub index :LocalRegex('^(c/)?(\d+)(?:\.(\d+))?(?:\.(full|tn|fp))?\.(?:jpeg|png|gif|tiff)$') { my ( $self, $c ) = @_; my ( $is_update, $id, $photo_number, $size ) = @{ $c->req->captures }; @@ -79,10 +79,10 @@ sub output : Private { # Save to file File::Path::make_path( FixMyStreet->path_to( 'web', 'photo', 'c' )->stringify ); - File::Slurp::write_file( FixMyStreet->path_to( 'web', $c->req->path )->stringify, \$photo ); + File::Slurp::write_file( FixMyStreet->path_to( 'web', $c->req->path )->stringify, \$photo->{data} ); - $c->res->content_type( 'image/jpeg' ); - $c->res->body( $photo ); + $c->res->content_type( $photo->{content_type} ); + $c->res->body( $photo->{data} ); } sub no_photo : Private { @@ -140,7 +140,7 @@ sub process_photo_upload_or_cache : Private { /^photo/ ? # photo, photo1, photo2 etc. ($c->req->upload($_)) : () } sort $c->req->upload), - split /,/, ($c->get_param('upload_fileid') || '') + grep { $_ } split /,/, ($c->get_param('upload_fileid') || '') ); my $photoset = FixMyStreet::App::Model::PhotoSet->new({ diff --git a/perllib/FixMyStreet/App/Controller/Questionnaire.pm b/perllib/FixMyStreet/App/Controller/Questionnaire.pm index 8fe2514c0..017a552db 100755 --- a/perllib/FixMyStreet/App/Controller/Questionnaire.pm +++ b/perllib/FixMyStreet/App/Controller/Questionnaire.pm @@ -47,14 +47,6 @@ sub check_questionnaire : Private { $c->stash->{problem} = $problem; $c->stash->{answered_ever_reported} = $problem->user->answered_ever_reported; - - # EHA needs to know how many to alter display, and whether to send another or not - if ($c->cobrand->moniker eq 'emptyhomes') { - $c->stash->{num_questionnaire} = $c->model('DB::Questionnaire')->count( - { problem_id => $problem->id } - ); - } - } =head2 submit @@ -236,11 +228,6 @@ sub process_questionnaire : Private { $c->stash->{update} = Utils::cleanup_text($c->stash->{update}, { allow_multiline => 1 }); - # EHA questionnaires done for you - if ($c->cobrand->moniker eq 'emptyhomes') { - $c->stash->{another} = $c->stash->{num_questionnaire}==1 ? 'Yes' : 'No'; - } - my @errors; push @errors, _('Please state whether or not the problem has been fixed') unless $c->stash->{been_fixed}; diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm index 5cc44bb8f..475a29fb5 100644 --- a/perllib/FixMyStreet/App/Controller/Report.pm +++ b/perllib/FixMyStreet/App/Controller/Report.pm @@ -230,7 +230,7 @@ to moderation, however we'd need to inform all the other users too about this change, at which point we can delete: - this method - - the call to it in templates/web/fixmystreet/report/display.html + - the call to it in templates/web/base/report/display_tools.html - the users_can_hide cobrand method, in favour of user->has_permission_to =cut diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 66dc20a3a..5df182506 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -221,7 +221,7 @@ sub category_extras_ajax : Path('category_extras') : Args(0) { $c->forward('setup_categories_and_bodies'); $c->forward('check_for_category'); - my $category = $c->stash->{category}; + my $category = $c->stash->{category} || ""; my $category_extra = ''; my $generate; if ( $c->stash->{category_extras}->{$category} && @{ $c->stash->{category_extras}->{$category} } >= 1 ) { @@ -616,58 +616,47 @@ sub setup_categories_and_bodies : Private { $c->stash->{unresponsive} = {}; if (keys %bodies == 1 && $first_body->send_method && $first_body->send_method eq 'Refused') { - $c->stash->{unresponsive}{ALL} = $first_body->id; - } - - # FIXME - implement in cobrand - if ( $c->cobrand->moniker eq 'emptyhomes' ) { - - # add all bodies found to the list - foreach (@contacts) { - $bodies_to_list{ $_->body_id } = 1; + # If there's only one body, and it's set to refused, we can show the + # message immediately, before they select a category. + if ($c->action->name eq 'category_extras_ajax' && $c->req->method eq 'POST') { + # The mobile app doesn't currently use this, in which case make + # sure the message is output, either below with a category, or when + # a blank category call is made. + $c->stash->{unresponsive}{""} = $first_body->id; + } else { + $c->stash->{unresponsive}{ALL} = $first_body->id; } + } - # set our own categories - @category_options = ( - _('-- Pick a property type --'), - _('Empty house or bungalow'), - _('Empty flat or maisonette'), - _('Whole block of empty flats'), - _('Empty office or other commercial'), - _('Empty pub or bar'), - _('Empty public building - school, hospital, etc.') - ); - - } else { - - # keysort does not appear to obey locale so use strcoll (see i18n.t) - @contacts = sort { strcoll( $a->category, $b->category ) } @contacts; + # keysort does not appear to obey locale so use strcoll (see i18n.t) + @contacts = sort { strcoll( $a->category, $b->category ) } @contacts; - my %seen; - foreach my $contact (@contacts) { + my %seen; + foreach my $contact (@contacts) { - $bodies_to_list{ $contact->body_id } = 1; + $bodies_to_list{ $contact->body_id } = 1; - unless ( $seen{$contact->category} ) { - push @category_options, $contact->category; + unless ( $seen{$contact->category} ) { + push @category_options, $contact->category; - my $metas = $contact->get_extra_fields; - $category_extras{ $contact->category } = $metas - if scalar @$metas; + my $metas = $contact->get_extra_fields; + $category_extras{ $contact->category } = $metas + if scalar @$metas; - $c->stash->{unresponsive}{$contact->category} = $contact->body_id - if $contact->email =~ /^REFUSED$/i; + my $body_send_method = $bodies{$contact->body_id}->send_method || ''; + $c->stash->{unresponsive}{$contact->category} = $contact->body_id + if !$c->stash->{unresponsive}{ALL} && + ($contact->email =~ /^REFUSED$/i || $body_send_method eq 'Refused'); - $non_public_categories{ $contact->category } = 1 if $contact->non_public; - } - $seen{$contact->category} = 1; + $non_public_categories{ $contact->category } = 1 if $contact->non_public; } + $seen{$contact->category} = 1; + } - if (@category_options) { - # If there's an Other category present, put it at the bottom - @category_options = ( _('-- Pick a category --'), grep { $_ ne _('Other') } @category_options ); - push @category_options, _('Other') if $seen{_('Other')}; - } + if (@category_options) { + # If there's an Other category present, put it at the bottom + @category_options = ( _('-- Pick a category --'), grep { $_ ne _('Other') } @category_options ); + push @category_options, _('Other') if $seen{_('Other')}; } $c->cobrand->munge_category_list(\@category_options, \@contacts, \%category_extras) @@ -849,12 +838,7 @@ sub process_report : Private { my $first_area = ( values %$areas )[0]; my $first_body = ( values %$bodies )[0]; - if ( $c->cobrand->moniker eq 'emptyhomes' ) { - - $bodies = join( ',', @{ $c->stash->{bodies_to_list} } ) || -1; - $report->bodies_str( $bodies ); - - } elsif ( $report->category ) { + if ( $report->category ) { # FIXME All contacts were fetched in setup_categories_and_bodies, # so can this DB call also be avoided? @@ -1034,6 +1018,8 @@ sub tokenize_user : Private { }; $c->stash->{token_data}{facebook_id} = $c->session->{oauth}{facebook_id} if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id}; + $c->stash->{token_data}{twitter_id} = $c->session->{oauth}{twitter_id} + if $c->get_param('oauth_need_email') && $c->session->{oauth}{twitter_id}; } =head2 save_user_and_report @@ -1061,7 +1047,7 @@ sub save_user_and_report : Private { $report->bodies_str( undef ) if $report->bodies_str eq '-1'; # if there is a Message Manager message ID, pass it back to the client view - if ($c->cobrand->moniker eq 'fixmybarangay' && $c->get_param('external_source_id') =~ /^\d+$/) { + if (($c->get_param('external_source_id') || "") =~ /^\d+$/) { $c->stash->{external_source_id} = $c->get_param('external_source_id'); $report->external_source_id( $c->get_param('external_source_id') ); $report->external_source( $c->config->{MESSAGE_MANAGER_URL} ) ; diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm index 8d6bc2019..af4ccff03 100644 --- a/perllib/FixMyStreet/App/Controller/Report/Update.pm +++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm @@ -382,6 +382,8 @@ sub tokenize_user : Private { }; $c->stash->{token_data}{facebook_id} = $c->session->{oauth}{facebook_id} if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id}; + $c->stash->{token_data}{twitter_id} = $c->session->{oauth}{twitter_id} + if $c->get_param('oauth_need_email') && $c->session->{oauth}{twitter_id}; } =head2 save_update diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm index 027b0d5a4..5bcbb3dc4 100644 --- a/perllib/FixMyStreet/App/Controller/Reports.pm +++ b/perllib/FixMyStreet/App/Controller/Reports.pm @@ -141,15 +141,9 @@ sub ward : Path : Args(2) { area => $c->stash->{ward} ? $c->stash->{ward}->{id} : [ keys %{$c->stash->{body}->areas} ], any_zoom => 1, ); - if ( $c->cobrand->moniker eq 'emptyhomes' ) { - FixMyStreet::Map::display_map( - $c, %map_params, latitude => 0, longitude => 0, - ); - } else { - FixMyStreet::Map::display_map( - $c, %map_params, pins => $pins, - ); - } + FixMyStreet::Map::display_map( + $c, %map_params, pins => $pins, + ); $c->cobrand->tweak_all_reports_map( $c ); @@ -188,15 +182,6 @@ sub rss_area_ward : Path('/rss/area') : Args(2) { # We're checking an area here, but this function is currently doing that. return if $c->cobrand->reports_body_check( $c, $area ); - # If we're passed an ID number (don't think this is used anywhere, it - # certainly shouldn't be), just look that up on mapit and redirect - if ($area =~ /^\d+$/) { - my $council = mySociety::MaPit::call('area', $area); - $c->detach( 'redirect_index') if $council->{error}; - $c->stash->{body} = $council; - $c->detach( 'redirect_body' ); - } - # We must now have a string to check on mapit my $areas = mySociety::MaPit::call( 'areas', $area, type => $c->cobrand->area_types, @@ -296,15 +281,6 @@ sub body_check : Private { # Oslo/ kommunes sharing a name in Norway return if $c->cobrand->reports_body_check( $c, $q_body ); - # If we're passed an ID number (don't think this is used anywhere, it - # certainly shouldn't be), just look that up on MaPit and redirect - if ($q_body =~ /^\d+$/) { - my $area = mySociety::MaPit::call('area', $q_body); - $c->detach( 'redirect_index') if $area->{error}; - $c->stash->{body} = $area; - $c->detach( 'redirect_body' ); - } - # We must now have a string to check my @bodies = $c->model('DB::Body')->search( { name => { -like => "$q_body%" } } )->all; diff --git a/perllib/FixMyStreet/App/Controller/Rss.pm b/perllib/FixMyStreet/App/Controller/Rss.pm index 6047f063b..8d4f8313c 100755 --- a/perllib/FixMyStreet/App/Controller/Rss.pm +++ b/perllib/FixMyStreet/App/Controller/Rss.pm @@ -6,6 +6,8 @@ use POSIX qw(strftime); use URI::Escape; use XML::RSS; +use FixMyStreet::App::Model::PhotoSet; + use mySociety::Gaze; use mySociety::Locale; use mySociety::MaPit; @@ -160,7 +162,6 @@ sub local_problems_ll : Private { sub output : Private { my ( $self, $c ) = @_; - $c->detach( '/page_error_404_not_found', [ 'Feed not found' ] ) if $c->cobrand->moniker eq 'emptyhomes'; $c->forward( 'lookup_type' ); $c->forward( 'query_main' ); $c->forward( 'generate' ); @@ -277,8 +278,13 @@ sub add_row : Private { $item{category} = ent($row->{category}) if $row->{category}; if ($c->cobrand->allow_photo_display($row) && $row->{photo}) { + # Bit yucky as we don't have full objects here + my $photoset = FixMyStreet::App::Model::PhotoSet->new({ db_data => $row->{photo} }); + my $first_fn = $photoset->get_id(0); + my ($hash, $format) = split /\./, $first_fn; + my $cachebust = substr($hash, 0, 8); my $key = $alert_type->item_table eq 'comment' ? 'c/' : ''; - $item{description} .= ent("\n<br><img src=\"". $base_url . "/photo/$key$row->{id}.jpeg\">"); + $item{description} .= ent("\n<br><img src=\"". $base_url . "/photo/$key$row->{id}.0.$format?$cachebust\">"); } if ( $row->{used_map} ) { diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm index eb35fd152..38f344250 100644 --- a/perllib/FixMyStreet/App/Controller/Tokens.pm +++ b/perllib/FixMyStreet/App/Controller/Tokens.pm @@ -106,6 +106,7 @@ sub confirm_problem : Path('/P') { $problem->user->password( $data->{password}, 1 ) if $data->{password}; $problem->user->title( $data->{title} ) if $data->{title}; $problem->user->facebook_id( $data->{facebook_id} ) if $data->{facebook_id}; + $problem->user->twitter_id( $data->{twitter_id} ) if $data->{twitter_id}; $problem->user->update; } $c->authenticate( { email => $problem->user->email }, 'no_password' ); @@ -232,6 +233,7 @@ sub confirm_update : Path('/C') { $comment->user->name( $data->{name} ) if $data->{name}; $comment->user->password( $data->{password}, 1 ) if $data->{password}; $comment->user->facebook_id( $data->{facebook_id} ) if $data->{facebook_id}; + $comment->user->twitter_id( $data->{twitter_id} ) if $data->{twitter_id}; $comment->user->update; } diff --git a/perllib/FixMyStreet/App/Model/PhotoSet.pm b/perllib/FixMyStreet/App/Model/PhotoSet.pm index 54457bae9..b44bf4b38 100644 --- a/perllib/FixMyStreet/App/Model/PhotoSet.pm +++ b/perllib/FixMyStreet/App/Model/PhotoSet.pm @@ -8,6 +8,8 @@ use if !$ENV{TRAVIS}, 'Image::Magick'; use Scalar::Util 'openhandle', 'blessed'; use Digest::SHA qw(sha1_hex); use Image::Size; +use IPC::Cmd qw(can_run); +use IPC::Open3; use MIME::Base64; has c => ( @@ -49,7 +51,7 @@ has data_items => ( # either a) split from db_data or b) provided by photo uploa my $self = shift; my $data = $self->db_data or return []; - return [$data] if (_jpeg_magic($data)); + return [$data] if (detect_type($data)); return [ split ',' => $data ]; }, @@ -70,10 +72,12 @@ has upload_dir => ( }, ); -sub _jpeg_magic { - $_[0] =~ /^\x{ff}\x{d8}/; # JPEG - # NB: should we also handle \x{89}\x{50} (PNG, 15 results in live DB) ? - # and \x{49}\x{49} (Tiff, 3 results in live DB) ? +sub detect_type { + return 'jpeg' if $_[0] =~ /^\x{ff}\x{d8}/; + return 'png' if $_[0] =~ /^\x{89}\x{50}/; + return 'tiff' if $_[0] =~ /^II/; + return 'gif' if $_[0] =~ /^GIF/; + return ''; } =head2 C<ids>, C<num_images>, C<get_id>, C<all_ids> @@ -106,15 +110,17 @@ has ids => ( # Arrayref of $fileid tuples (always, so post upload/raw data proc my $part = $_; if (blessed $part and $part->isa('Catalyst::Request::Upload')) { - # check that the photo is a jpeg my $upload = $part; my $ct = $upload->type; $ct =~ s/x-citrix-//; # Thanks, Citrix + my ($type) = $ct =~ m{image/(jpeg|pjpeg|gif|tiff|png)}; + $type = 'jpeg' if $type && $type eq 'pjpeg'; # Had a report of a JPEG from an Android 2.1 coming through as a byte stream - unless ( $ct eq 'image/jpeg' || $ct eq 'image/pjpeg' || $ct eq 'application/octet-stream' ) { + $type = 'jpeg' if !$type && $ct eq 'application/octet-stream'; + unless ( $type ) { my $c = $self->c; $c->log->info('Bad photo tried to upload, type=' . $ct); - $c->stash->{photo_error} = _('Please upload a JPEG image only'); + $c->stash->{photo_error} = _('Please upload an image only'); return (); } @@ -139,12 +145,18 @@ has ids => ( # Arrayref of $fileid tuples (always, so post upload/raw data proc # get the photo into a variable my $photo_blob = eval { my $filename = $upload->tempname; - my $out = `jhead -se -autorot $filename 2>&1`; + my $out; + if ($type eq 'jpeg' && can_run('jhead')) { + my $pid = open3(undef, my $stdout, undef, 'jhead', '-se', '-autorot', $filename); + $out = join('', <$stdout>); + waitpid($pid, 0); + close $stdout; + } unless (defined $out) { my ($w, $h, $err) = Image::Size::imgsize($filename); - die _("Please upload a JPEG image only") . "\n" if !defined $w || $err ne 'JPG'; + die _("Please upload an image only") . "\n" if !defined $w || $err !~ /JPG|GIF|PNG|TIF/; } - die _("Please upload a JPEG image only") . "\n" if $out && $out =~ /Not JPEG:/; + die _("Please upload an image only") . "\n" if $out && $out =~ /Not JPEG:/; my $photo = $upload->slurp; }; if ( my $error = $@ ) { @@ -157,29 +169,30 @@ has ids => ( # Arrayref of $fileid tuples (always, so post upload/raw data proc # we have an image we can use - save it to the upload dir for storage my $fileid = $self->get_fileid($photo_blob); - my $file = $self->get_file($fileid); + my $file = $self->get_file($fileid, $type); $upload->copy_to( $file ); - return $fileid; + return $file->basename; } - if (_jpeg_magic($part)) { + if (my $type = detect_type($part)) { my $photo_blob = $part; my $fileid = $self->get_fileid($photo_blob); - my $file = $self->get_file($fileid); + my $file = $self->get_file($fileid, $type); $file->spew_raw($photo_blob); - return $fileid; + return $file->basename; } - if (length($part) == 40) { - my $fileid = $part; - my $file = $self->get_file($fileid); + my ($fileid, $type) = split /\./, $part; + $type ||= 'jpeg'; + if ($fileid && length($fileid) == 40) { + my $file = $self->get_file($fileid, $type); if ($file->exists) { - $fileid; + $file->basename; } else { - warn "File $fileid doesn't exist"; + warn "File $part doesn't exist"; (); } } else { - warn sprintf "Received bad photo hash of length %d", length($part); + # A bad hash, probably a bot spamming with bad data. (); } }); @@ -193,18 +206,23 @@ sub get_fileid { } sub get_file { - my ($self, $fileid) = @_; + my ($self, $fileid, $type) = @_; my $cache_dir = $self->upload_dir; - return path( $cache_dir, "$fileid.jpeg" ); + return path( $cache_dir, "$fileid.$type" ); } -sub get_raw_image_data { +sub get_raw_image { my ($self, $index) = @_; - my $fileid = $self->get_id($index); - my $file = $self->get_file($fileid); + my $filename = $self->get_id($index); + my ($fileid, $type) = split /\./, $filename; + my $file = $self->get_file($fileid, $type); if ($file->exists) { my $photo = $file->slurp_raw; - return $photo; + return { + data => $photo, + content_type => "image/$type", + extension => $type, + }; } } @@ -212,8 +230,9 @@ sub get_image_data { my ($self, %args) = @_; my $num = $args{num} || 0; - my $photo = $self->get_raw_image_data( $num ) + my $image = $self->get_raw_image( $num ) or return; + my $photo = $image->{data}; my $size = $args{size}; if ( $size eq 'tn' ) { @@ -226,7 +245,10 @@ sub get_image_data { $photo = _shrink( $photo, $args{default} || '250x250' ); } - return $photo; + return { + data => $photo, + content_type => $image->{content_type}, + }; } sub delete_cached { @@ -250,13 +272,15 @@ sub remove_images { --$dec; } + $self->delete_cached(); + + return undef if !@images; + my $new_set = (ref $self)->new({ data_items => \@images, object => $self->object, }); - $self->delete_cached(); - return $new_set->data; # e.g. new comma-separated fileid } @@ -266,8 +290,8 @@ sub rotate_image { my @images = $self->all_ids; return if $index > $#images; - my $image_data = $self->get_raw_image_data($index); - $images[$index] = _rotate_image( $image_data, $direction ); + my $image = $self->get_raw_image($index); + $images[$index] = _rotate_image( $image->{data}, $direction ); my $new_set = (ref $self)->new({ data_items => \@images, diff --git a/perllib/FixMyStreet/Cobrand/Angus.pm b/perllib/FixMyStreet/Cobrand/Angus.pm new file mode 100644 index 000000000..23d0d2c58 --- /dev/null +++ b/perllib/FixMyStreet/Cobrand/Angus.pm @@ -0,0 +1,127 @@ +package FixMyStreet::Cobrand::Angus; +use parent 'FixMyStreet::Cobrand::UKCouncils'; + +use strict; +use warnings; + +sub council_id { return 2550; } +sub council_area { return 'Angus'; } +sub council_name { return 'Angus Council'; } +sub council_url { return 'angus'; } + +sub base_url { + return FixMyStreet->config('BASE_URL') if FixMyStreet->config('STAGING_SITE'); + return 'https://fix.angus.gov.uk'; +} + +sub enter_postcode_text { + my ($self) = @_; + return 'Enter an Angus postcode, or street name and area'; +} + +sub example_places { + return ( 'DD8 3AP', "Canmore Street" ); +} + +sub default_show_name { 0 } + +sub disambiguate_location { + my $self = shift; + my $string = shift; + + return { + %{ $self->SUPER::disambiguate_location() }, + town => 'Angus', + centre => '56.7240845983561,-2.91774391131183', + span => '0.525195055746977,0.985870680170788', + bounds => [ 56.4616875530489, -3.40703662677109, 56.9868826087959, -2.4211659466003 ], + }; +} + +sub pin_colour { + my ( $self, $p, $context ) = @_; + return 'grey' if $p->state eq 'not responsible'; + return 'green' if $p->is_fixed || $p->is_closed; + return 'red' if $p->state eq 'confirmed'; + return 'yellow'; +} + +sub contact_email { + my $self = shift; + return join( '@', 'accessline', 'angus.gov.uk' ); +} + +=head2 temp_email_to_update, temp_update_contacts + +Temporary helper routines to update the extra for potholes (temporary setup +hack, cargo-culted from Harrogate, may in future be superseded either by +Open311/integration or a better mechanism for manually creating rich contacts). + +Can run with a script or command line like: + + bin/cron-wrapper perl -MFixMyStreet::App -MFixMyStreet::Cobrand::Angus -e \ + 'FixMyStreet::Cobrand::Angus->new({c => FixMyStreet::App->new})->temp_update_contacts' + +=cut + +sub temp_update_contacts { + my $self = shift; + + my $contact_rs = $self->{c}->model('DB::Contact'); + + my $_update = sub { + my ($category, $field, $category_details) = @_; + # NB: we're accepting just 1 field, but supply as array [ $field ] + + my $contact = $contact_rs->find_or_create( + { + body_id => $self->council_id, + category => $category, + %{ $category_details || {} }, + }, + { + key => 'contacts_body_id_category_idx' + } + ); + + my %default = ( + variable => 'true', + order => '1', + required => 'no', + datatype => 'string', + datatype_description => 'a string', + ); + + if ($field->{datatype} || '' eq 'boolean') { + my $description = $field->{description}; + %default = ( + %default, + datatype => 'singlevaluelist', + datatype_description => 'Yes or No', + values => { value => [ + { key => ['No'], name => ['No'] }, + { key => ['Yes'], name => ['Yes'] }, + ] }, + ); + } + + $contact->update({ + # XXX: we're just setting extra with the expected layout, + # this could be encapsulated more nicely + extra => { _fields => [ { %default, %$field } ] }, + confirmed => 1, + deleted => 0, + editor => 'automated script', + whenedited => \'NOW()', + note => 'Edited by script as per requirements Jan 2016', + }); + }; + + $_update->( 'Street lighting', { + code => 'column_id', + description => 'Lamp post number', + }); + +} + +1; diff --git a/perllib/FixMyStreet/Cobrand/Barnet.pm b/perllib/FixMyStreet/Cobrand/Barnet.pm deleted file mode 100644 index df8757009..000000000 --- a/perllib/FixMyStreet/Cobrand/Barnet.pm +++ /dev/null @@ -1,28 +0,0 @@ -package FixMyStreet::Cobrand::Barnet; -use parent 'FixMyStreet::Cobrand::UKCouncils'; - -use strict; -use warnings; - -sub council_id { return 2489; } -sub council_area { return 'Barnet'; } -sub council_name { return 'Barnet Council'; } -sub council_url { return 'barnet'; } - -sub disambiguate_location { - my $self = shift; - return { - %{ $self->SUPER::disambiguate_location() }, - town => 'Barnet', - centre => '51.612832,-0.218169', - span => '0.0563,0.09', - bounds => [ 51.584682, -0.263169, 51.640982, -0.173169 ], - }; -} - -sub example_places { - return [ 'N11 1NP', 'Wood St' ]; -} - -1; - diff --git a/perllib/FixMyStreet/Cobrand/BellaVistaEnAccion.pm b/perllib/FixMyStreet/Cobrand/BellaVistaEnAccion.pm deleted file mode 100644 index 58bc6973d..000000000 --- a/perllib/FixMyStreet/Cobrand/BellaVistaEnAccion.pm +++ /dev/null @@ -1,25 +0,0 @@ -package FixMyStreet::Cobrand::BellaVistaEnAccion; -use base 'FixMyStreet::Cobrand::Default'; - -use strict; -use warnings; - -sub country { - return 'CL'; -} - -sub example_places { - return ( 'Dominica, Recoleta', 'Pio Nono' ); -} - -sub languages { [ 'es-cl,Castellano,es_CL' ] } - -sub disambiguate_location { - return { - country => 'cl', - town => 'Santiago', - }; -} - -1; - diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index b3b830f06..4dc024d48 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -20,7 +20,7 @@ use mySociety::PostcodeUtil; $path = $cobrand->path_to_web_templates( ); Returns the path to the templates for this cobrand - by default -"templates/web/$moniker" and "templates/web/fixmystreet" +"templates/web/$moniker" (and then base in Web.pm). =cut @@ -28,7 +28,6 @@ sub path_to_web_templates { my $self = shift; my $paths = [ FixMyStreet->path_to( 'templates/web', $self->moniker )->stringify, - FixMyStreet->path_to( 'templates/web/fixmystreet' )->stringify, ]; return $paths; } @@ -625,6 +624,7 @@ sub short_name { my ($area) = @_; my $name = $area->{name} || $area->name; + $name =~ tr{/}{_}; $name = URI::Escape::uri_escape_utf8($name); $name =~ s/%20/+/g; return $name; @@ -936,10 +936,6 @@ sub updates_as_hashref { return {}; } -sub get_country_for_ip_address { - return 0; -} - sub jurisdiction_id_example { my $self = shift; return $self->moniker; diff --git a/perllib/FixMyStreet/Cobrand/EmptyHomes.pm b/perllib/FixMyStreet/Cobrand/EmptyHomes.pm deleted file mode 100644 index 995c39c85..000000000 --- a/perllib/FixMyStreet/Cobrand/EmptyHomes.pm +++ /dev/null @@ -1,549 +0,0 @@ -package FixMyStreet::Cobrand::EmptyHomes; -use base 'FixMyStreet::Cobrand::UK'; - -use strict; -use warnings; - -use FixMyStreet; -use mySociety::Locale; -use Carp; - -sub path_to_web_templates { - my $self = shift; - return [ FixMyStreet->path_to( 'templates/web', $self->moniker )->stringify ]; -} - -sub _fallback_body_sender { - my ( $self, $body, $category ) = @_; - - return { method => 'EmptyHomes' }; -}; - -=item - -Return the base url for this cobranded site - -=cut - -sub base_url { - my $base_url = FixMyStreet->config('BASE_URL'); - if ( $base_url !~ /emptyhomes/ ) { - $base_url =~ s/http:\/\//http:\/\/emptyhomes\./g; - } - return $base_url; -} - -sub area_types { - [ 'DIS', 'LBO', 'MTD', 'UTA', 'LGD', 'COI' ]; # No CTY -} - -sub base_url_with_lang { - my $self = shift; - - my $base = $self->base_url; - - my $lang = $mySociety::Locale::lang; - if ($lang eq 'cy') { - $base =~ s{http://}{$&cy.}; - } else { - $base =~ s{http://}{$&en.}; - } - return $base; -} - -sub languages { [ 'en-gb,English,en_GB', 'cy,Cymraeg,cy_GB' ] } -sub language_domain { 'FixMyStreet-EmptyHomes' } - -=item shorten_recency_if_new_greater_than_fixed - -For empty homes we don't want to shorten the recency - -=cut - -sub shorten_recency_if_new_greater_than_fixed { - return 0; -} - -=head2 default_photo_resize - -Size that photos are to be resized to for display. If photos aren't -to be resized then return 0; - -=cut - -sub default_photo_resize { return '195x'; } - -sub short_name { - my $self = shift; - my ($area) = @_; - - my $name = $area->{name} || $area->name; - $name =~ s/ (Borough|City|District|County) Council$//; - $name =~ s/ Council$//; - $name =~ s/ & / and /; - $name =~ s{/}{_}g; - $name = URI::Escape::uri_escape_utf8($name); - $name =~ s/%20/-/g; - return $name; -} - -=item council_rss_alert_options - -Generate a set of options for council rss alerts. - -=cut - -sub council_rss_alert_options { - my $self = shift; - my $all_councils = shift; - my $c = shift; - - my %councils = map { $_ => 1 } @{$self->area_types}; - - my $num_councils = scalar keys %$all_councils; - - my ( @options, @reported_to_options ); - my ($council, $ward); - foreach (values %$all_councils) { - $_->{short_name} = $self->short_name( $_ ); - ( $_->{id_name} = $_->{short_name} ) =~ tr/+/_/; - if ($councils{$_->{type}}) { - $council = $_; - } else { - $ward = $_; - } - } - - push @options, { - type => 'council', - id => sprintf( 'council:%s:%s', $council->{id}, $council->{id_name} ), - text => sprintf( _('Problems within %s'), $council->{name}), - rss_text => sprintf( _('RSS feed of problems within %s'), $council->{name}), - uri => $c->uri_for( '/rss/reports/' . $council->{short_name} ), - }; - push @options, { - type => 'ward', - id => sprintf( 'ward:%s:%s:%s:%s', $council->{id}, $ward->{id}, $council->{id_name}, $ward->{id_name} ), - rss_text => sprintf( _('RSS feed of problems within %s ward'), $ward->{name}), - text => sprintf( _('Problems within %s ward'), $ward->{name}), - uri => $c->uri_for( '/rss/reports/' . $council->{short_name} . '/' . $ward->{short_name} ), - }; - - return ( \@options, @reported_to_options ? \@reported_to_options : undef ); -} - -sub report_form_extras { - ( { name => 'address', required => 1 } ) -} - -sub front_stats_data { - my ( $self ) = @_; - my $key = "recent_new"; - my $result = Memcached::get($key); - unless ($result) { - $result = $self->problems->search( - { state => [ FixMyStreet::DB::Result::Problem->visible_states() ] } - )->count; - foreach my $v (values %{$self->old_site_stats}) { - $result += $v; - } - Memcached::set($key, $result, 3600); - } - return $result; -} - -# A record of the number of reports from the Channel 4 site and other old data -sub old_site_stats { - return { - 2223 => 95, - 2238 => 82, - 2245 => 54, - 2248 => 31, - 2250 => 132, - 2253 => 15, - 2255 => 25, - 2256 => 8, - 2257 => 3, - 2258 => 14, - 2259 => 5, - 2260 => 22, - 2261 => 12, - 2262 => 21, - 2263 => 14, - 2264 => 1, - 2267 => 1, - 2271 => 13, - 2272 => 7, - 2273 => 13, - 2274 => 7, - 2275 => 15, - 2276 => 14, - 2277 => 10, - 2278 => 7, - 2279 => 23, - 2280 => 16, - 2281 => 25, - 2282 => 14, - 2283 => 10, - 2284 => 22, - 2285 => 25, - 2286 => 32, - 2287 => 13, - 2288 => 13, - 2289 => 16, - 2290 => 18, - 2291 => 1, - 2292 => 9, - 2293 => 15, - 2294 => 16, - 2295 => 12, - 2296 => 4, - 2299 => 2, - 2300 => 1, - 2304 => 10, - 2305 => 17, - 2306 => 6, - 2307 => 11, - 2308 => 17, - 2309 => 9, - 2310 => 6, - 2311 => 9, - 2312 => 26, - 2313 => 2, - 2314 => 34, - 2315 => 18, - 2316 => 13, - 2317 => 17, - 2318 => 7, - 2319 => 14, - 2320 => 4, - 2321 => 20, - 2322 => 7, - 2323 => 10, - 2324 => 7, - 2325 => 15, - 2326 => 12, - 2327 => 25, - 2328 => 23, - 2329 => 11, - 2330 => 4, - 2331 => 29, - 2332 => 12, - 2333 => 7, - 2334 => 5, - 2335 => 16, - 2336 => 7, - 2337 => 7, - 2338 => 2, - 2339 => 12, - 2340 => 2, - 2341 => 7, - 2342 => 14, - 2343 => 20, - 2344 => 13, - 2345 => 17, - 2346 => 6, - 2347 => 4, - 2348 => 6, - 2349 => 18, - 2350 => 13, - 2351 => 11, - 2352 => 24, - 2353 => 10, - 2354 => 20, - 2355 => 14, - 2356 => 13, - 2357 => 14, - 2358 => 8, - 2359 => 6, - 2360 => 10, - 2361 => 36, - 2362 => 17, - 2363 => 8, - 2364 => 7, - 2365 => 8, - 2366 => 26, - 2367 => 19, - 2368 => 20, - 2369 => 8, - 2370 => 14, - 2371 => 79, - 2372 => 10, - 2373 => 5, - 2374 => 4, - 2375 => 12, - 2376 => 10, - 2377 => 24, - 2378 => 9, - 2379 => 8, - 2380 => 25, - 2381 => 13, - 2382 => 11, - 2383 => 16, - 2384 => 18, - 2385 => 12, - 2386 => 18, - 2387 => 5, - 2388 => 8, - 2389 => 12, - 2390 => 11, - 2391 => 23, - 2392 => 11, - 2393 => 16, - 2394 => 9, - 2395 => 27, - 2396 => 8, - 2397 => 27, - 2398 => 14, - 2402 => 1, - 2403 => 18, - 2404 => 14, - 2405 => 7, - 2406 => 9, - 2407 => 12, - 2408 => 3, - 2409 => 8, - 2410 => 23, - 2411 => 27, - 2412 => 9, - 2413 => 20, - 2414 => 96, - 2415 => 11, - 2416 => 20, - 2417 => 18, - 2418 => 24, - 2419 => 18, - 2420 => 7, - 2421 => 29, - 2427 => 7, - 2428 => 15, - 2429 => 18, - 2430 => 32, - 2431 => 9, - 2432 => 17, - 2433 => 8, - 2434 => 10, - 2435 => 14, - 2436 => 13, - 2437 => 11, - 2438 => 5, - 2439 => 4, - 2440 => 23, - 2441 => 8, - 2442 => 18, - 2443 => 12, - 2444 => 3, - 2445 => 8, - 2446 => 31, - 2447 => 15, - 2448 => 3, - 2449 => 12, - 2450 => 11, - 2451 => 8, - 2452 => 20, - 2453 => 25, - 2454 => 8, - 2455 => 6, - 2456 => 24, - 2457 => 6, - 2458 => 10, - 2459 => 15, - 2460 => 17, - 2461 => 20, - 2462 => 12, - 2463 => 16, - 2464 => 5, - 2465 => 14, - 2466 => 20, - 2467 => 14, - 2468 => 12, - 2469 => 4, - 2470 => 1, - 2471 => 1, - 2474 => 9, - 2475 => 12, - 2476 => 11, - 2477 => 9, - 2478 => 10, - 2479 => 21, - 2480 => 26, - 2481 => 30, - 2482 => 38, - 2483 => 46, - 2484 => 63, - 2485 => 7, - 2486 => 14, - 2487 => 16, - 2488 => 14, - 2489 => 39, - 2490 => 112, - 2491 => 79, - 2492 => 137, - 2493 => 55, - 2494 => 18, - 2495 => 41, - 2496 => 41, - 2497 => 22, - 2498 => 26, - 2499 => 46, - 2500 => 62, - 2501 => 90, - 2502 => 47, - 2503 => 32, - 2504 => 33, - 2505 => 47, - 2506 => 56, - 2507 => 26, - 2508 => 48, - 2509 => 47, - 2510 => 16, - 2511 => 6, - 2512 => 4, - 2513 => 41, - 2514 => 138, - 2515 => 48, - 2516 => 65, - 2517 => 35, - 2518 => 40, - 2519 => 31, - 2520 => 27, - 2521 => 25, - 2522 => 34, - 2523 => 27, - 2524 => 47, - 2525 => 22, - 2526 => 125, - 2527 => 126, - 2528 => 93, - 2529 => 23, - 2530 => 28, - 2531 => 24, - 2532 => 46, - 2533 => 22, - 2534 => 24, - 2535 => 27, - 2536 => 44, - 2537 => 54, - 2538 => 17, - 2539 => 13, - 2540 => 29, - 2541 => 15, - 2542 => 19, - 2543 => 14, - 2544 => 34, - 2545 => 30, - 2546 => 38, - 2547 => 32, - 2548 => 22, - 2549 => 37, - 2550 => 9, - 2551 => 41, - 2552 => 17, - 2553 => 36, - 2554 => 10, - 2555 => 20, - 2556 => 13, - 2557 => 19, - 2558 => 13, - 2559 => 23, - 2560 => 13, - 2561 => 62, - 2562 => 29, - 2563 => 31, - 2564 => 34, - 2565 => 57, - 2566 => 16, - 2567 => 22, - 2568 => 40, - 2569 => 5, - 2570 => 38, - 2571 => 17, - 2572 => 9, - 2573 => 12, - 2574 => 10, - 2575 => 16, - 2576 => 2, - 2577 => 28, - 2578 => 37, - 2579 => 79, - 2580 => 17, - 2581 => 734, - 2582 => 11, - 2583 => 23, - 2584 => 16, - 2585 => 4, - 2586 => 33, - 2587 => 3, - 2588 => 22, - 2589 => 19, - 2590 => 14, - 2591 => 9, - 2592 => 19, - 2593 => 11, - 2594 => 14, - 2595 => 13, - 2596 => 21, - 2597 => 10, - 2598 => 16, - 2599 => 26, - 2600 => 1, - 2601 => 19, - 2602 => 23, - 2603 => 12, - 2604 => 31, - 2605 => 30, - 2606 => 5, - 2607 => 32, - 2608 => 14, - 2609 => 27, - 2610 => 15, - 2611 => 20, - 2612 => 22, - 2613 => 20, - 2614 => 97, - 2615 => 29, - 2616 => 6, - 2617 => 34, - 2618 => 16, - 2619 => 25, - 2620 => 12, - 2621 => 29, - 2622 => 18, - 2623 => 12, - 2624 => 58, - 2625 => 54, - 2626 => 15, - 2627 => 1, - 2629 => 17, - 2630 => 22, - 2636 => 13, - 2637 => 13, - 2638 => 25, - 2639 => 57, - 2640 => 15, - 2641 => 11, - 2642 => 14, - 2643 => 38, - 2644 => 19, - 2645 => 6, - 2646 => 1, - 2647 => 16, - 2648 => 25, - 2649 => 38, - 2650 => 12, - 2651 => 78, - 2652 => 12, - 2654 => 16, - 2655 => 13, - 2656 => 15, - 2657 => 44, - 2658 => 53, - 16869 => 73, - 21068 => 44, - 21069 => 57, - 21070 => 20, - }; -} - -1; - diff --git a/perllib/FixMyStreet/Cobrand/FixMyBarangay.pm b/perllib/FixMyStreet/Cobrand/FixMyBarangay.pm deleted file mode 100644 index 194556e72..000000000 --- a/perllib/FixMyStreet/Cobrand/FixMyBarangay.pm +++ /dev/null @@ -1,55 +0,0 @@ -package FixMyStreet::Cobrand::FixMyBarangay; -use base 'FixMyStreet::Cobrand::Default'; - -use strict; -use warnings; - -sub country { - return 'PH'; -} - -sub language_domain { 'FixMyBarangay' } - -sub area_types { - return [ 'BGY' ]; -} - -sub disambiguate_location { - return { - country => 'ph', - bing_country => 'Philippines', - }; -} - -sub only_authed_can_create { - return 1; -} - -# effectively allows barangay staff to hide reports -sub council_id { return '1,2' ; } - -sub areas_on_around { - return [ 1, 2 ]; -} - -sub can_support_problems { - return 1; -} - -sub default_show_name { - my $self = shift; - - return 0 if $self->{c}->user->from_council; - return 1; -} - -# makes no sense to send questionnaires since FMB's reporters are mostly staff -sub send_questionnaires { - return 0; -} - -# let staff hide reports in their own barangay -sub users_can_hide { 1 } - -1; - diff --git a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm index 159f2f5db..c8c1eef66 100644 --- a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm +++ b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm @@ -1,6 +1,5 @@ package FixMyStreet::Cobrand::FixMyStreet; use base 'FixMyStreet::Cobrand::UK'; -use mySociety::Gaze; use constant COUNCIL_ID_BROMLEY => 2482; @@ -9,7 +8,6 @@ sub path_to_web_templates { my $self = shift; return [ FixMyStreet->path_to( 'templates/web/fixmystreet.com' )->stringify, - FixMyStreet->path_to( 'templates/web/fixmystreet' )->stringify ]; } @@ -52,13 +50,6 @@ sub extra_contact_validation { return %errors; } -sub get_country_for_ip_address { - my $self = shift; - my $ip = shift; - - return mySociety::Gaze::get_country_from_ip($ip); -} - sub report_form_extras { ( { name => 'gender', required => 0 }, { name => 'variant', required => 0 } ) } diff --git a/perllib/FixMyStreet/Cobrand/Greenwich.pm b/perllib/FixMyStreet/Cobrand/Greenwich.pm index 7535a34bf..d23e62138 100644 --- a/perllib/FixMyStreet/Cobrand/Greenwich.pm +++ b/perllib/FixMyStreet/Cobrand/Greenwich.pm @@ -9,6 +9,11 @@ sub council_area { return 'Greenwich'; } sub council_name { return 'Royal Borough of Greenwich'; } sub council_url { return 'greenwich'; } +sub base_url { + return FixMyStreet->config('BASE_URL') if FixMyStreet->config('STAGING_SITE'); + return 'https://fix.royalgreenwich.gov.uk'; +} + sub example_places { return ( 'SE18 6HQ', "Woolwich Road" ); } diff --git a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm index c78ae5e09..543dd431a 100644 --- a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm +++ b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm @@ -52,6 +52,8 @@ sub default_show_name { 0 } Returns the number of working days that are expected to elapse between the problem being reported and it being responded to by the council/body. +If the value 'emergency' is returned, a different template block +is triggered that has custom wording. =cut @@ -79,7 +81,7 @@ sub problem_response_days { return 10 if $p->category eq 'Road traffic signs'; return 10 if $p->category eq 'Roads/highways'; return 10 if $p->category eq 'Skips and scaffolding'; - return 10 if $p->category eq 'Street lighting'; + return 'emergency' if $p->category eq 'Street lighting'; return 10 if $p->category eq 'Traffic lights'; # phone if urgent return 10 if $p->category eq 'Traffic'; return 10 if $p->category eq 'Trees'; diff --git a/perllib/FixMyStreet/Cobrand/UK.pm b/perllib/FixMyStreet/Cobrand/UK.pm index c33b39aac..0eff48b00 100644 --- a/perllib/FixMyStreet/Cobrand/UK.pm +++ b/perllib/FixMyStreet/Cobrand/UK.pm @@ -109,7 +109,7 @@ sub short_name { $name =~ s/ (Borough|City|District|County) Council$//; $name =~ s/ Council$//; $name =~ s/ & / and /; - $name =~ s{/}{_}g; + $name =~ tr{/}{_}; $name = URI::Escape::uri_escape_utf8($name); $name =~ s/%20/+/g; return $name; diff --git a/perllib/FixMyStreet/Cobrand/UKCouncils.pm b/perllib/FixMyStreet/Cobrand/UKCouncils.pm index b4b91b7dd..0321e0297 100644 --- a/perllib/FixMyStreet/Cobrand/UKCouncils.pm +++ b/perllib/FixMyStreet/Cobrand/UKCouncils.pm @@ -18,7 +18,6 @@ sub path_to_web_templates { return [ FixMyStreet->path_to( 'templates/web', $self->moniker )->stringify, FixMyStreet->path_to( 'templates/web/fixmystreet-uk-councils' )->stringify, - FixMyStreet->path_to( 'templates/web/fixmystreet' )->stringify ]; } diff --git a/perllib/FixMyStreet/Cobrand/ZeroTB.pm b/perllib/FixMyStreet/Cobrand/ZeroTB.pm deleted file mode 100644 index ef1b0b1e1..000000000 --- a/perllib/FixMyStreet/Cobrand/ZeroTB.pm +++ /dev/null @@ -1,56 +0,0 @@ -package FixMyStreet::Cobrand::ZeroTB; -use base 'FixMyStreet::Cobrand::Default'; - -use strict; -use warnings; - -sub enter_postcode_text { return _ ('Enter a nearby street name and area, postal code or district in Delhi'); } - -sub country { - return 'IN'; -} - -sub disambiguate_location { - return { - country => 'in', - town => 'Delhi', - bounds => [ 28.404625000000024, 76.838845800000072, 28.884380600000028, 77.347877500000067 ], - }; -} - -sub only_authed_can_create { return 1; } -sub allow_photo_display { return 0; } -sub allow_photo_upload{ return 0; } -sub send_questionnaires { return 0; } -sub on_map_default_max_pin_age { return 0; } -sub never_confirm_updates { 1; } -sub include_time_in_update_alerts { 1; } - -sub pin_colour { - return 'clinic'; -} - -sub path_to_pin_icons { - return '/cobrands/zerotb/images/'; -} - -sub get_clinic_list { - my $self = shift; - - return $self->problems->search({ state => 'confirmed' }, { order_by => 'title' }); -} - -sub prettify_dt { - my ( $self, $dt, $type ) = @_; - $type ||= ''; - - if ( $type eq 'alert' ) { - return $dt->strftime('%H:%M %Y-%m-%d'); - } else { - return Utils::prettify_dt( $dt, $type ); - } - -} - -1; - diff --git a/perllib/FixMyStreet/Cobrand/Zurich.pm b/perllib/FixMyStreet/Cobrand/Zurich.pm index c7e3c4d45..d31b1c84e 100644 --- a/perllib/FixMyStreet/Cobrand/Zurich.pm +++ b/perllib/FixMyStreet/Cobrand/Zurich.pm @@ -1018,18 +1018,19 @@ sub munge_sendreport_params { or return; my $id = $row->id; my @attachments = map { + my $image = $photoset->get_raw_image($_); { - body => $photoset->get_raw_image_data($_), + body => $image->{data}, attributes => { - filename => "$id.$_.jpeg", - content_type => 'image/jpeg', + filename => "$id.$_." . $image->{extension}, + content_type => $image->{content_type}, encoding => 'base64', # quoted-printable ends up with newlines corrupting binary data - name => "$id.$_.jpeg", + name => "$id.$_." . $image->{extension}, }, } } (0..$num-1); - $params->{attachments} = \@attachments; + $params->{_attachments_} = \@attachments; } } diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm index 5734ff8d5..85cdb29f0 100644 --- a/perllib/FixMyStreet/DB/Result/Comment.pm +++ b/perllib/FixMyStreet/DB/Result/Comment.pm @@ -173,12 +173,12 @@ sub photos { my $i = 0; my $id = $self->id; my @photos = map { - my $format = 'jpeg'; my $cachebust = substr($_, 0, 8); + my ($hash, $format) = split /\./, $_; { - id => $_, - url_temp => "/photo/$_.temp.$format", - url_temp_full => "/photo/$_.fulltemp.$format", + id => $hash, + url_temp => "/photo/temp.$hash.$format", + url_temp_full => "/photo/fulltemp.$hash.$format", url => "/photo/c/$id.$i.$format?$cachebust", url_full => "/photo/c/$id.$i.full.$format?$cachebust", idx => $i++, @@ -187,14 +187,14 @@ sub photos { return \@photos; } -=head2 meta_problem_state +=head2 problem_state_display Returns a string suitable for display lookup in the update meta section. Removes the '- council/user' bit from fixed states. =cut -sub meta_problem_state { +sub problem_state_display { my $self = shift; my $state = $self->problem_state; diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm index 2a90d0bec..12dad073e 100644 --- a/perllib/FixMyStreet/DB/Result/Problem.pm +++ b/perllib/FixMyStreet/DB/Result/Problem.pm @@ -531,6 +531,19 @@ sub is_visible { return exists $self->visible_states->{ $self->state } ? 1 : 0; } +=head2 state_display + +Returns a string suitable for display lookup in the update meta section. +Removes the '- council/user' bit from fixed states. + +=cut + +sub state_display { + my $self = shift; + (my $state = $self->state) =~ s/ -.*$//; + return $state; +} + =head2 meta_line Returns a string to be used on a problem report page, describing some of the @@ -544,59 +557,48 @@ sub meta_line { my $date_time = Utils::prettify_dt( $problem->confirmed ); my $meta = ''; - # FIXME Should be in cobrand - if ($c->cobrand->moniker eq 'emptyhomes') { - - my $category = _($problem->category); - utf8::decode($category); - $meta = sprintf(_('%s, reported at %s'), $category, $date_time); - - } else { - - if ( $problem->anonymous ) { - if ( $problem->service - and $problem->category && $problem->category ne _('Other') ) - { - $meta = - sprintf( _('Reported via %s in the %s category anonymously at %s'), - $problem->service, $problem->category, $date_time ); - } - elsif ( $problem->service ) { - $meta = sprintf( _('Reported via %s anonymously at %s'), - $problem->service, $date_time ); - } - elsif ( $problem->category and $problem->category ne _('Other') ) { - $meta = sprintf( _('Reported in the %s category anonymously at %s'), - $problem->category, $date_time ); - } - else { - $meta = sprintf( _('Reported anonymously at %s'), $date_time ); - } + if ( $problem->anonymous ) { + if ( $problem->service + and $problem->category && $problem->category ne _('Other') ) + { + $meta = + sprintf( _('Reported via %s in the %s category anonymously at %s'), + $problem->service, $problem->category, $date_time ); + } + elsif ( $problem->service ) { + $meta = sprintf( _('Reported via %s anonymously at %s'), + $problem->service, $date_time ); + } + elsif ( $problem->category and $problem->category ne _('Other') ) { + $meta = sprintf( _('Reported in the %s category anonymously at %s'), + $problem->category, $date_time ); } else { - if ( $problem->service - and $problem->category && $problem->category ne _('Other') ) - { - $meta = sprintf( - _('Reported via %s in the %s category by %s at %s'), - $problem->service, $problem->category, - $problem->name, $date_time - ); - } - elsif ( $problem->service ) { - $meta = sprintf( _('Reported via %s by %s at %s'), - $problem->service, $problem->name, $date_time ); - } - elsif ( $problem->category and $problem->category ne _('Other') ) { - $meta = sprintf( _('Reported in the %s category by %s at %s'), - $problem->category, $problem->name, $date_time ); - } - else { - $meta = - sprintf( _('Reported by %s at %s'), $problem->name, $date_time ); - } + $meta = sprintf( _('Reported anonymously at %s'), $date_time ); + } + } + else { + if ( $problem->service + and $problem->category && $problem->category ne _('Other') ) + { + $meta = sprintf( + _('Reported via %s in the %s category by %s at %s'), + $problem->service, $problem->category, + $problem->name, $date_time + ); + } + elsif ( $problem->service ) { + $meta = sprintf( _('Reported via %s by %s at %s'), + $problem->service, $problem->name, $date_time ); + } + elsif ( $problem->category and $problem->category ne _('Other') ) { + $meta = sprintf( _('Reported in the %s category by %s at %s'), + $problem->category, $problem->name, $date_time ); + } + else { + $meta = + sprintf( _('Reported by %s at %s'), $problem->name, $date_time ); } - } return $meta; @@ -647,14 +649,14 @@ sub response_templates { } # returns true if the external id is the council's ref, i.e., useful to publish it -# (by way of an example, the barnet send method returns a useful reference when +# (by way of an example, the Oxfordshire send method returns a useful reference when # it succeeds, so that is the ref we should show on the problem report page). # Future: this is installation-dependent so maybe should be using the contact # data to determine if the external id is public on a council-by-council basis. # Note: this only makes sense when called on a problem that has been sent! sub can_display_external_id { my $self = shift; - if ($self->external_id && $self->send_method_used && $self->bodies_str =~ /2237/) { + if ($self->external_id && $self->send_method_used && $self->bodies_str =~ /(2237|2550)/) { return 1; } return 0; @@ -862,12 +864,12 @@ sub photos { my $i = 0; my $id = $self->id; my @photos = map { - my $format = 'jpeg'; my $cachebust = substr($_, 0, 8); + my ($hash, $format) = split /\./, $_; { - id => $_, - url_temp => "/photo/$_.temp.$format", - url_temp_full => "/photo/$_.fulltemp.$format", + id => $hash, + url_temp => "/photo/temp.$hash.$format", + url_temp_full => "/photo/fulltemp.$hash.$format", url => "/photo/$id.$i.$format?$cachebust", url_full => "/photo/$id.$i.full.$format?$cachebust", url_tn => "/photo/$id.$i.tn.$format?$cachebust", diff --git a/perllib/FixMyStreet/Email.pm b/perllib/FixMyStreet/Email.pm index 1787c32da..49f4632a8 100644 --- a/perllib/FixMyStreet/Email.pm +++ b/perllib/FixMyStreet/Email.pm @@ -1,6 +1,8 @@ package FixMyStreet::Email; +use Email::MIME; use Encode; +use POSIX qw(); use Template; use Digest::HMAC_SHA1 qw(hmac_sha1_hex); use mySociety::Email; @@ -105,15 +107,10 @@ sub send_cron { $site_name = Utils::trim_text(Encode::decode('utf8', $site_name)); $params->{_parameters_}->{site_name} = $site_name; - $params->{_line_indent} = ''; - my $attachments = delete $params->{attachments}; - - my $email = mySociety::Locale::in_gb_locale { mySociety::Email::construct_email($params) }; - - $email = munge_attachments($email, $attachments) if $attachments; + my $email = mySociety::Locale::in_gb_locale { construct_email($params) }; if ($nomail) { - print $email; + print $email->as_string; return 1; # Failure } else { my $result = FixMyStreet::EmailSend->new({ env_from => $env_from })->send($email); @@ -121,41 +118,152 @@ sub send_cron { } } -sub munge_attachments { - my ($message, $attachments) = @_; - # $attachments should be an array_ref of things that can be parsed to Email::MIME, - # for example - # [ - # body => $binary_data, - # attributes => { - # content_type => 'image/jpeg', - # encoding => 'base64', - # filename => '1234.1.jpeg', - # name => '1234.1.jpeg', - # }, - # ... - # ] - # - # XXX: mySociety::Email::construct_email isn't using a MIME library and - # requires more analysis to refactor, so for now, we'll simply parse the - # generated MIME and add attachments. - # - # (Yes, this means that the email is constructed by Email::Simple, munged - # manually by custom code, turned back into Email::Simple, and then munged - # with Email::MIME. What's your point?) - - require Email::MIME; - my $mime = Email::MIME->new($message); - $mime->parts_add([ map { Email::MIME->create(%$_)} @$attachments ]); - my $data = $mime->as_string; - - # unsure why Email::MIME adds \r\n. Possibly mail client should handle - # gracefully, BUT perhaps as the segment constructed by - # mySociety::Email::construct_email strips to \n, they seem not to. - # So we re-run the same regexp here to the added part. - $data =~ s/\r\n/\n/gs; - - return $data; +=item construct_email SPEC + +Construct an email message according to SPEC, which is an associative array +containing elements as given below. Returns an Email::MIME email. + +=over 4 + +=item _template_, _parameters_ + +Templated body text and an associative array of template parameters. _template +contains optional substititutions <?=$values['name']?>, each of which is +replaced by the value of the corresponding named value in _parameters_. It is +an error to use a substitution when the corresponding parameter is not present +or undefined. The first line of the template will be interpreted as contents of +the Subject: header of the mail if it begins with the literal string 'Subject: +' followed by a blank line. The templated text will be word-wrapped to produce +lines of appropriate length. + +=item _attachments_ + +An arrayref of hashrefs that can be passed to Email::MIME. + +=item To + +Contents of the To: header, as a literal UTF-8 string or an array of addresses +or [address, name] pairs. + +=item From + +Contents of the From: header, as an email address or an [address, name] pair. + +=item Cc + +Contents of the Cc: header, as for To. + +=item Reply-To + +Contents of the Reply-To: header, as for To. + +=item Subject + +Contents of the Subject: header, as a UTF-8 string. + +=item I<any other element> + +interpreted as the literal value of a header with the same name. + +=back + +If no Date is given, the current date is used. If no To is given, then the +string "Undisclosed-Recipients: ;" is used. It is an error to fail to give a +templated body, From or Subject (perhaps from the template). + +=cut +sub construct_email ($) { + my $p = shift; + + throw mySociety::Email::Error("Must specify both '_template_' and '_parameters_'") + if !exists($p->{_template_}) || !exists($p->{_parameters_}); + throw mySociety::Email::Error("Template parameters '_parameters_' must be an associative array") + if (ref($p->{_parameters_}) ne 'HASH'); + + (my $subject, $body) = mySociety::Email::do_template_substitution($p->{_template_}, $p->{_parameters_}, ''); + $p->{Subject} = $subject if defined($subject); + + if (!exists($p->{Subject})) { + # XXX Try to find out what's causing this very occasionally + (my $error = $body) =~ s/\n/ | /g; + $error = "missing field 'Subject' in MESSAGE - $error"; + throw mySociety::Email::Error($error); + } + throw mySociety::Email::Error("missing field 'From' in MESSAGE") unless exists($p->{From}); + + # Construct email headers + my %hdr; + + foreach my $h (grep { exists($p->{$_}) } qw(To Cc Reply-To)) { + if (ref($p->{$h}) eq '') { + # Interpret as a literal string in UTF-8, so all we need to do is + # escape it. + $hdr{$h} = $p->{$h}; + } elsif (ref($p->{$h}) eq 'ARRAY') { + # Array of addresses or [address, name] pairs. + $hdr{$h} = join(', ', map { mailbox($_, $h) } @{$p->{$h}}); + } else { + throw mySociety::Email::Error("Field '$h' in MESSAGE should be single value or an array"); + } + } + + foreach my $h (grep { exists($p->{$_}) } qw(From Sender)) { + $hdr{$h} = mailbox($p->{$h}, $h); + } + + # Some defaults + $hdr{To} ||= 'Undisclosed-recipients: ;'; + $hdr{Date} ||= POSIX::strftime("%a, %d %h %Y %T %z", localtime(time())); + + # Other headers, including Subject + foreach (keys(%$p)) { + $hdr{$_} = $p->{$_} if ($_ !~ /^_/ && !exists($hdr{$_})); + } + + my $parts = [ + _mime_create( + body_str => $body, + attributes => { + charset => 'utf-8', + encoding => 'quoted-printable', + }, + ), + ]; + + if ($p->{_attachments_}) { + push @$parts, map { _mime_create(%$_) } @{$p->{_attachments_}}; + } + + my $email = Email::MIME->create( + header_str => [ %hdr ], + parts => $parts, + attributes => { + charset => 'utf-8', + }, + ); + + return $email; +} + +# Handle being given a string, or an arrayref of [ name, email ] +sub mailbox { + my ($e, $header) = @_; + if (ref($e) eq '') { + return $e; + } elsif (ref($e) ne 'ARRAY' || @$e != 2) { + throw mySociety::Email::Error("'$header' field should be string or 2-element array"); + } else { + return Email::Address->new($e->[1], $e->[0]); + } +} + +# Don't want Date/MIME-Version headers that Email::MIME adds to all parts +sub _mime_create { + my %h = @_; + my $e = Email::MIME->create(%h); + $e->header_set('Date'); + $e->header_set('MIME-Version'); + return $e; } 1; diff --git a/perllib/FixMyStreet/Map/Zurich.pm b/perllib/FixMyStreet/Map/Zurich.pm index 3e198f820..4c597c30b 100644 --- a/perllib/FixMyStreet/Map/Zurich.pm +++ b/perllib/FixMyStreet/Map/Zurich.pm @@ -57,7 +57,7 @@ sub map_tiles { sub base_tile_url { # use the new 512px maps as used by Javascript - return 'http://www.gis.stadt-zuerich.ch/maps/rest/services/tiled/LuftbildHybrid/MapServer/WMTS/tile/1.0.0/tiled_LuftbildHybrid/default/default028mm'; + return '//www.gis.stadt-zuerich.ch/maps/rest/services/tiled/LuftbildHybrid/MapServer/WMTS/tile/1.0.0/tiled_LuftbildHybrid/default/default028mm'; } sub copyright { diff --git a/perllib/FixMyStreet/Script/Questionnaires.pm b/perllib/FixMyStreet/Script/Questionnaires.pm index 2d676f15d..f72f59077 100644 --- a/perllib/FixMyStreet/Script/Questionnaires.pm +++ b/perllib/FixMyStreet/Script/Questionnaires.pm @@ -10,8 +10,6 @@ use FixMyStreet::Cobrand; sub send { my ( $params ) = @_; send_questionnaires_period( '4 weeks', $params ); - send_questionnaires_period( '26 weeks', $params ) - if $params->{site} eq 'emptyhomes'; } sub send_questionnaires_period { @@ -29,17 +27,11 @@ sub send_questionnaires_period { ], 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)' => { '<', \"current_timestamp - '$period'::interval" } - ]; - } + + $q_params->{'-or'} = [ + '(select max(whensent) from questionnaire where me.id=problem_id)' => undef, + '(select max(whenanswered) from questionnaire where me.id=problem_id)' => { '<', \"current_timestamp - '$period'::interval" } + ]; my $unsent = FixMyStreet::DB->resultset('Problem')->search( $q_params, { order_by => { -desc => 'confirmed' } @@ -60,13 +52,7 @@ sub send_questionnaires_period { # 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 = Utils::read_file( FixMyStreet->path_to( "templates/email/emptyhomes/" . $row->lang . "/questionnaire-$template.txt" )->stringify ); - } else { - $template = FixMyStreet->get_email_template($cobrand->moniker, $row->lang, 'questionnaire.txt'); - } + my $template = FixMyStreet->get_email_template($cobrand->moniker, $row->lang, 'questionnaire.txt'); my %h = map { $_ => $row->$_ } qw/name title detail category/; $h{created} = Utils::prettify_duration( time() - $row->confirmed->epoch, 'week' ); @@ -76,10 +62,8 @@ sub send_questionnaires_period { whensent => \'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'; + # We won't send another questionnaire unless they ask for it + $row->send_questionnaire( 0 ); my $token = FixMyStreet::DB->resultset("Token")->new_result( { scope => 'questionnaire', diff --git a/perllib/FixMyStreet/Script/Reports.pm b/perllib/FixMyStreet/Script/Reports.pm index e1eb5e2c5..75111b852 100644 --- a/perllib/FixMyStreet/Script/Reports.pm +++ b/perllib/FixMyStreet/Script/Reports.pm @@ -83,7 +83,7 @@ sub send(;$) { $h{phone_line} = $h{phone} ? _('Phone:') . " $h{phone}\n\n" : ''; if ($row->photo) { $h{has_photo} = _("This web page also contains a photo of the problem, provided by the user.") . "\n\n"; - $h{image_url} = $email_base_url . '/photo/' . $row->id . '.full.jpeg'; + $h{image_url} = $email_base_url . $row->photos->[0]->{url_full}; } else { $h{has_photo} = ''; $h{image_url} = ''; @@ -145,7 +145,7 @@ sub send(;$) { $skip = 1; debug_print("skipped by sender " . $sender_info->{method} . " (might be due to previous failed attempts?)", $row->id) if $debug_mode; } else { - debug_print("OK, adding recipient body " . $body->id . ":" . $body->name . ", " . $body->send_method, $row->id) if $debug_mode; + debug_print("OK, adding recipient body " . $body->id . ":" . $body->name . ", " . $sender_info->{method}, $row->id) if $debug_mode; push @dear, $body->name; $reporters{ $sender }->add_body( $body, $sender_info->{config} ); } @@ -203,7 +203,7 @@ sub send(;$) { if (FixMyStreet->config('STAGING_SITE') && !FixMyStreet->config('SEND_REPORTS_ON_STAGING')) { # on a staging server send emails to ourselves rather than the bodies - %reporters = map { $_ => $reporters{$_} } grep { /FixMyStreet::SendReport::(Email|EmptyHomes)/ } keys %reporters; + %reporters = map { $_ => $reporters{$_} } grep { /FixMyStreet::SendReport::Email/ } keys %reporters; unless (%reporters) { %reporters = ( 'FixMyStreet::SendReport::Email' => FixMyStreet::SendReport::Email->new() ); } diff --git a/perllib/FixMyStreet/SendReport/Angus.pm b/perllib/FixMyStreet/SendReport/Angus.pm new file mode 100644 index 000000000..cab5de173 --- /dev/null +++ b/perllib/FixMyStreet/SendReport/Angus.pm @@ -0,0 +1,169 @@ +package FixMyStreet::SendReport::Angus; + +use Moo; + +BEGIN { extends 'FixMyStreet::SendReport'; } + +use Try::Tiny; +use Encode; +use XML::Simple; +use mySociety::Web qw(ent); + +sub get_auth_token { + my ($self, $authxml) = @_; + + my $xml = new XML::Simple; + my $obj; + + eval { + $obj = $xml->parse_string( $authxml ); + }; + + my $success = $obj->{success}; + $success =~ s/^\s+|\s+$//g if defined $success; + my $token = $obj->{AuthenticateResult}; + $token =~ s/^\s+|\s+$//g if defined $token; + + if (defined $success && $success eq 'True' && defined $token) { + return $token; + } else { + $self->error("Couldn't authenticate against Angus endpoint."); + } +} + +sub get_external_id { + my ($self, $resultxml) = @_; + + my $xml = new XML::Simple; + my $obj; + + eval { + $obj = $xml->parse_string( $resultxml ); + }; + + my $success = $obj->{success}; + $success =~ s/^\s+|\s+$//g if defined $success; + my $external_id = $obj->{CreateRequestResult}->{RequestId}; + + if (defined $success && $success eq 'True' && defined $external_id) { + return $external_id; + } else { + $self->error("Couldn't find external id in response from Angus endpoint."); + return undef; + } +} + +sub crm_request_type { + my ($self, $row, $h) = @_; + return 'StLight'; # TODO: Set this according to report category +} + +sub jadu_form_fields { + my ($self, $row, $h) = @_; + my $xml = XML::Simple->new( + NoAttr=> 1, + KeepRoot => 1, + SuppressEmpty => 0, + ); + my $metas = $row->get_extra_fields(); + my %extras; + foreach my $field (@$metas) { + $extras{$field->{name}} = $field->{value}; + } + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($row->cobrand)->new(); + my $output = $xml->XMLout({ + formfields => { + formfield => [ + { + name => 'RequestTitle', + value => $h->{title} + }, + { + name => 'RequestDetails', + value => $h->{detail} + }, + { + name => 'ReporterName', + value => $h->{name} + }, + { + name => 'ReporterEmail', + value => $h->{email} + }, + { + name => 'ReporterAnonymity', + value => $row->anonymous ? 'True' : 'False' + }, + { + name => 'ReportedDateTime', + value => $h->{confirmed} + }, + { + name => 'ColumnId', + value => $extras{'column_id'} || '' + }, + { + name => 'ReportId', + value => $h->{id} + }, + { + name => 'ReportedNorthing', + value => $h->{northing} + }, + { + name => 'ReportedEasting', + value => $h->{easting} + }, + { + name => 'Imageurl1', + value => $row->photos->[0] ? ($cobrand->base_url . $row->photos->[0]->{url_full}) : '' + }, + { + name => 'Imageurl2', + value => $row->photos->[1] ? ($cobrand->base_url . $row->photos->[1]->{url_full}) : '' + }, + { + name => 'Imageurl3', + value => $row->photos->[2] ? ($cobrand->base_url . $row->photos->[2]->{url_full}) : '' + } + ] + } + }); + # The endpoint crashes if the JADUFormFields string has whitespace between XML elements, so strip it out... + $output =~ s/>[\s\n]+</></g; + return $output; +} + +sub send { + my ( $self, $row, $h ) = @_; + + # FIXME: should not recreate this each time + my $angus_service; + + require Integrations::AngusSOAP; + + my $return = 1; + $angus_service ||= Integrations::AngusSOAP->on_fault(sub { my($soap, $res) = @_; die ref $res ? $res->faultstring : $soap->transport->status, "\n"; }); + try { + my $authresult = $angus_service->AuthenticateJADU(); + my $authtoken = $self->get_auth_token( $authresult ); + # authenticationtoken, CallerId, CallerAddressId, DeliveryId, DeliveryAddressId, CRMRequestType, JADUXFormRef, PaymentRef, JADUFormFields + my $result = $angus_service->CreateServiceRequest( + $authtoken, '1', '1', '1', '1', $self->crm_request_type($row, $h), + 'FMS', '', $self->jadu_form_fields($row, $h) + ); + my $external_id = $self->get_external_id( $result ); + if ( $external_id ) { + $row->external_id( $external_id ); + $row->send_method_used('Angus'); + $return = 0; + } + } catch { + my $e = $_; + $self->error( "Error sending to Angus: $e" ); + }; + $self->success( !$return ); + return $return; +} + +1; diff --git a/perllib/FixMyStreet/SendReport/EastHants.pm b/perllib/FixMyStreet/SendReport/EastHants.pm index 3eb8ffcfa..55ec79613 100644 --- a/perllib/FixMyStreet/SendReport/EastHants.pm +++ b/perllib/FixMyStreet/SendReport/EastHants.pm @@ -35,12 +35,12 @@ sub send { # FIXME: should not recreate this each time my $eh_service; - require EastHantsWSDL; + require Integrations::EastHantsWSDL; $h->{category} = 'Customer Services' if $h->{category} eq 'Other'; $h->{message} = construct_message( %$h ); my $return = 1; - $eh_service ||= EastHantsWSDL->on_fault(sub { my($soap, $res) = @_; die ref $res ? $res->faultstring : $soap->transport->status, "\n"; }); + $eh_service ||= Integrations::EastHantsWSDL->on_fault(sub { my($soap, $res) = @_; die ref $res ? $res->faultstring : $soap->transport->status, "\n"; }); try { # ServiceName, RemoteCreatedBy, Salutation, FirstName, Name, Email, Telephone, HouseNoName, Street, Town, County, Country, Postcode, Comments, FurtherInfo, ImageURL my $message = ent(encode_utf8($h->{message})); @@ -52,7 +52,6 @@ sub send { $return = 0 if $result eq 'Report received'; } catch { my $e = $_; - print "Caught an error: $e\n"; $self->error( "Error sending to East Hants: $e" ); }; $self->success( !$return ); diff --git a/perllib/FixMyStreet/SendReport/EmptyHomes.pm b/perllib/FixMyStreet/SendReport/EmptyHomes.pm deleted file mode 100644 index b5faf8ddc..000000000 --- a/perllib/FixMyStreet/SendReport/EmptyHomes.pm +++ /dev/null @@ -1,56 +0,0 @@ -package FixMyStreet::SendReport::EmptyHomes; - -use Moo; -use namespace::autoclean; - -use mySociety::MaPit; - -BEGIN { extends 'FixMyStreet::SendReport::Email'; } - -sub build_recipient_list { - my ( $self, $row, $h ) = @_; - - my $all_confirmed = 1; - foreach my $body ( @{ $self->bodies } ) { - my $contact = $row->result_source->schema->resultset("Contact")->find( { - deleted => 0, - body_id => $body->id, - category => 'Empty property', - } ); - - my ($body_email, $confirmed, $note) = ( $contact->email, $contact->confirmed, $contact->note ); - - unless ($confirmed) { - $all_confirmed = 0; - #$note = 'Council ' . $row->body . ' deleted' - #unless $note; - $body_email = 'N/A' unless $body_email; - #$notgot{$body_email}{$row->category}++; - #$note{$body_email}{$row->category} = $note; - } - - push @{ $self->to }, [ $body_email, $body->name ]; - - my $area_info = mySociety::MaPit::call('area', $body->body_areas->first->area_id); - my $country = $area_info->{country}; - if ($country eq 'W') { - push @{$self->bcc}, 'wales@' . FixMyStreet->config('EMAIL_DOMAIN'); - } elsif ($country eq 'S') { - push @{$self->bcc}, 'scotland@' . FixMyStreet->config('EMAIL_DOMAIN'); - } else { - push @{$self->bcc}, 'eha@' . FixMyStreet->config('EMAIL_DOMAIN'); - } - } - - # Set address email parameter from added data - $h->{address} = $row->extra->{address}; - - return $all_confirmed && @{$self->to}; -} - -sub get_template { - my ( $self, $row ) = @_; - return Utils::read_file( FixMyStreet->path_to( "templates", "email", "emptyhomes", $row->lang, "submit.txt" )->stringify ); -} - -1; diff --git a/perllib/FixMyStreet/SendReport/Open311.pm b/perllib/FixMyStreet/SendReport/Open311.pm index 4844aa2e9..bf5ed3e30 100644 --- a/perllib/FixMyStreet/SendReport/Open311.pm +++ b/perllib/FixMyStreet/SendReport/Open311.pm @@ -126,8 +126,8 @@ sub send { $revert = 1; } - if ($row->cobrand eq 'fixmybarangay' || $row->bodies_str =~ /$COUNCIL_ID_GREENWICH/) { - # FixMyBarangay endpoints expect external_id as an attribute, as do Greenwich + if ($row->bodies_str =~ /$COUNCIL_ID_GREENWICH/) { + # Greenwich endpoint expects external_id as an attribute $row->set_extra_fields( { 'name' => 'external_id', 'value' => $row->id } ); $revert = 1; } @@ -144,14 +144,11 @@ sub send { $self->success( 1 ); } else { $result *= 1; - # temporary fix to resolve some issues with west berks - if ( $row->bodies_str =~ /2619/ ) { - $result *= 0; - } + $self->error( "Failed to send over Open311\n" ) unless $self->error; + $self->error( $self->error . "\n" . $open311->error ); } } - $self->error( 'Failed to send over Open311' ) unless $self->success; return $result; } diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm index f3ee7787b..2ad820d1f 100644 --- a/perllib/FixMyStreet/TestMech.pm +++ b/perllib/FixMyStreet/TestMech.pm @@ -423,7 +423,7 @@ sub extract_problem_list { my $mech = shift; my $result = scraper { - process 'ul.item-list--reports li a h4', 'problems[]', 'TEXT'; + process 'ul.item-list--reports li a h3', 'problems[]', 'TEXT'; }->scrape( $mech->response ); return $result->{ problems } || []; |