diff options
Diffstat (limited to 'perllib/FixMyStreet/App')
22 files changed, 643 insertions, 408 deletions
diff --git a/perllib/FixMyStreet/App/Controller/About.pm b/perllib/FixMyStreet/App/Controller/About.pm new file mode 100755 index 000000000..78e548c5f --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/About.pm @@ -0,0 +1,67 @@ +package FixMyStreet::App::Controller::About; +use Moose; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller'; } + +=head1 NAME + +FixMyStreet::App::Controller::About - Catalyst Controller + +=head1 DESCRIPTION + +About pages Catalyst Controller. + +=head1 METHODS + +=cut + +my %found; + +sub page : Path("/about") : Args(1) { + my ( $self, $c, $page ) = @_; + my $template = $c->forward('find_template'); + $c->detach('/page_error_404_not_found', []) unless $template; + $c->stash->{template} = $template; +} + +sub index : Path("/about") : Args(0) { + my ( $self, $c ) = @_; + $c->forward('page', [ 'about' ]); +} + +# We have multiple possibilities to try, and we want to cache where we find it +sub find_template : Private { + my ( $self, $c, $page ) = @_; + + return $found{$page} if !FixMyStreet->config('STAGING_SITE') && exists $found{$page}; + + my $lang_code = $c->stash->{lang_code}; + foreach my $dir_templates (@{$c->stash->{additional_template_paths}}, @{$c->view('Web')->paths}) { + foreach my $dir_static (static_dirs($page, $dir_templates)) { + foreach my $file ("$page-$lang_code.html", "$page.html") { + if (-e "$dir_templates/$dir_static/$file") { + $found{$page} = "$dir_static/$file"; + return $found{$page}; + } + } + } + } + # Cache that the page does not exist, so we don't look next time + $found{$page} = undef; + return $found{$page}; +} + +sub static_dirs { + my ($page, $dir_templates) = @_; + my @v = ("about"); + # If legacy directories exist, check for templates there too; + # The FAQ page used to be in its own directory + push @v, "static" if -d "$dir_templates/static"; + push @v, "faq" if -d "$dir_templates/faq" && $page =~ /faq/; + return @v; +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index a61032988..2bf215c56 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -84,14 +84,14 @@ sub index : Path : Args(0) { for ( FixMyStreet::DB::Result::Problem->visible_states() ); $c->stash->{total_problems_users} = $c->cobrand->problems->unique_users; - my $comments = $c->model('DB::Comment')->summary_count( $c->cobrand->body_restriction ); + my $comments = $c->cobrand->updates->summary_count; my %comment_counts = map { $_->state => $_->get_column('state_count') } $comments->all; $c->stash->{comments} = \%comment_counts; - my $alerts = $c->model('DB::Alert')->summary_count( $c->cobrand->restriction ); + my $alerts = $c->model('DB::Alert')->summary_report_alerts( $c->cobrand->restriction ); my %alert_counts = map { $_->confirmed => $_->get_column('confirmed_count') } $alerts->all; @@ -171,7 +171,7 @@ sub timeline : Path( 'timeline' ) : Args(0) { push @{$time{$_->whenanswered->epoch}}, { type => 'quesAnswered', date => $_->whenanswered, obj => $_ } if $_->whenanswered; } - my $updates = $c->model('DB::Comment')->timeline( $c->cobrand->body_restriction ); + my $updates = $c->cobrand->updates->timeline; foreach ($updates->all) { push @{$time{$_->created->epoch}}, { type => 'update', date => $_->created, obj => $_} ; @@ -622,9 +622,7 @@ sub reports : Path('reports') { } if (@$query) { - my $updates = $c->model('DB::Comment') - ->to_body($c->cobrand->body_restriction) - ->search( + my $updates = $c->cobrand->updates->search( { -or => $query, }, @@ -685,7 +683,7 @@ sub report_edit : Path('report_edit') : Args(1) { } if (my $rotate_photo_param = $self->_get_rotate_photo_param($c)) { - $self->rotate_photo($c, @$rotate_photo_param); + $self->rotate_photo($c, $problem, @$rotate_photo_param); if ( $c->cobrand->moniker eq 'zurich' ) { # Clicking the photo rotation buttons should do nothing # except for rotating the photo, so return the user @@ -792,11 +790,12 @@ sub report_edit : Path('report_edit') : Args(1) { } # Deal with photos - if ( $c->get_param('remove_photo') ) { - $problem->photo(undef); + my $remove_photo_param = $self->_get_remove_photo_param($c); + if ($remove_photo_param) { + $self->remove_photo($c, $problem, $remove_photo_param); } - if ( $c->get_param('remove_photo') || $new_state eq 'hidden' ) { + if ( $remove_photo_param || $new_state eq 'hidden' ) { unlink glob FixMyStreet->path_to( 'web', 'photo', $problem->id . '.*' ); } @@ -967,9 +966,7 @@ sub users: Path('users') : Args(0) { sub update_edit : Path('update_edit') : Args(1) { my ( $self, $c, $id ) = @_; - my $update = $c->model('DB::Comment') - ->to_body($c->cobrand->body_restriction) - ->search({ id => $id })->first; + my $update = $c->cobrand->updates->search({ id => $id })->first; $c->detach( '/page_error_404_not_found' ) unless $update; @@ -978,6 +975,11 @@ sub update_edit : Path('update_edit') : Args(1) { $c->stash->{update} = $update; + if (my $rotate_photo_param = $self->_get_rotate_photo_param($c)) { + $self->rotate_photo($c, $update, @$rotate_photo_param); + return 1; + } + $c->forward('check_email_for_abuse', [ $update->user->email ] ); if ( $c->get_param('banuser') ) { @@ -1007,13 +1009,14 @@ sub update_edit : Path('update_edit') : Args(1) { || $c->get_param('anonymous') ne $update->anonymous || $c->get_param('text') ne $update->text ) { $edited = 1; - } + } - if ( $c->get_param('remove_photo') ) { - $update->photo(undef); + my $remove_photo_param = $self->_get_remove_photo_param($c); + if ($remove_photo_param) { + $self->remove_photo($c, $update, $remove_photo_param); } - if ( $c->get_param('remove_photo') || $new_state eq 'hidden' ) { + if ( $remove_photo_param || $new_state eq 'hidden' ) { unlink glob FixMyStreet->path_to( 'web', 'photo', 'c', $update->id . '.*' ); } @@ -1076,16 +1079,18 @@ sub user_add : Path('user_edit') : Args(0) { $c->forward('get_token'); $c->forward('fetch_all_bodies'); - return 1 unless $c->get_param('submit'); + return unless $c->get_param('submit'); $c->forward('check_token'); - if ( $c->cobrand->moniker eq 'zurich' and $c->get_param('email') eq '' ) { + unless ($c->get_param('email')) { $c->stash->{field_errors}->{email} = _('Please enter a valid email'); - return 1; + return; + } + unless ($c->get_param('name')) { + $c->stash->{field_errors}->{name} = _('Please enter a name'); + return; } - - return unless $c->get_param('name') && $c->get_param('email'); my $user = $c->model('DB::User')->find_or_create( { name => $c->get_param('name'), @@ -1133,12 +1138,16 @@ sub user_edit : Path('user_edit') : Args(1) { $user->from_body( $c->get_param('body') || undef ); $user->flagged( $c->get_param('flagged') || 0 ); - if ( $c->cobrand->moniker eq 'zurich' and $user->email eq '' ) { + unless ($user->email) { $c->stash->{field_errors}->{email} = _('Please enter a valid email'); - return 1; + return; + } + unless ($user->name) { + $c->stash->{field_errors}->{name} = _('Please enter a name'); + return; } - $user->update; + $user->update; if ($edited) { $c->forward( 'log_edit', [ $id, 'user', 'edit' ] ); } @@ -1316,9 +1325,9 @@ Generate a token based on user and secret sub get_token : Private { my ( $self, $c ) = @_; - my $secret = $c->model('DB::Secret')->search()->first; + my $secret = $c->model('DB::Secret')->get; my $user = $c->forward('get_user'); - my $token = sha1_hex($user . $secret->secret); + my $token = sha1_hex($user . $secret); $c->stash->{token} = $token; return 1; @@ -1486,25 +1495,51 @@ sub _get_rotate_photo_param { my $key = first { /^rotate_photo/ } keys %{ $c->req->params } or return; my ($index) = $key =~ /(\d+)$/; my $direction = $c->get_param($key); - return [ $index || 0, $key, $direction ]; + return [ $index || 0, $direction ]; } sub rotate_photo : Private { - my ( $self, $c, $index, $key, $direction ) = @_; + my ( $self, $c, $object, $index, $direction ) = @_; return unless $direction eq _('Rotate Left') or $direction eq _('Rotate Right'); - my $problem = $c->stash->{problem}; - my $fileid = $problem->get_photoset($c)->rotate_image( + my $fileid = $object->get_photoset->rotate_image( $index, $direction eq _('Rotate Left') ? -90 : 90 ) or return; - $problem->update({ photo => $fileid }); + $object->update({ photo => $fileid }); return 1; } +=head2 remove_photo + +Remove a photo from a report + +=cut + +# Returns index of photo(s) to remove, if any +sub _get_remove_photo_param { + my ($self, $c) = @_; + + return 'ALL' if $c->get_param('remove_photo'); + + my @keys = map { /(\d+)$/ } grep { /^remove_photo_/ } keys %{ $c->req->params } or return; + return \@keys; +} + +sub remove_photo : Private { + my ($self, $c, $object, $keys) = @_; + if ($keys eq 'ALL') { + $object->photo(undef); + } else { + my $fileids = $object->get_photoset->remove_images($keys); + $object->photo($fileids); + } + return 1; +} + =head2 check_page_allowed Checks if the current catalyst action is in the list of allowed pages and diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm index 4aa695ae5..1e6d9ec9e 100644 --- a/perllib/FixMyStreet/App/Controller/Around.pm +++ b/perllib/FixMyStreet/App/Controller/Around.pm @@ -6,6 +6,7 @@ BEGIN { extends 'Catalyst::Controller'; } use FixMyStreet::Map; use Encode; +use JSON::MaybeXS; use Utils; =head1 NAME @@ -306,7 +307,7 @@ sub ajax : Path('/ajax') { # JSON encode the response my $json = { pins => $pins }; $json->{current} = $on_map_list_html if $on_map_list_html; - my $body = JSON->new->utf8(1)->encode($json); + my $body = encode_json($json); $c->res->body($body); } @@ -350,8 +351,10 @@ sub _geocode : Private { } else { if ( ref($suggestions) eq 'ARRAY' ) { foreach (@$suggestions) { - push @addresses, decode_utf8($_->{address}); - push @locations, { address => decode_utf8($_->{address}), lat => $_->{latitude}, long => $_->{longitude} }; + my $address = $_->{address}; + $address = decode_utf8($address) if !utf8::is_utf8($address); + push @addresses, $address; + push @locations, { address => $address, lat => $_->{latitude}, long => $_->{longitude} }; } $response = { suggestions => \@addresses, locations => \@locations }; } else { @@ -363,9 +366,7 @@ sub _geocode : Private { $response = \@addresses; } - my $body = JSON->new->utf8(1)->encode( - $response - ); + my $body = encode_json($response); $c->res->body($body); } diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm index 6de416c53..9e8fb29aa 100644 --- a/perllib/FixMyStreet/App/Controller/Auth.pm +++ b/perllib/FixMyStreet/App/Controller/Auth.pm @@ -7,7 +7,8 @@ BEGIN { extends 'Catalyst::Controller'; } use Email::Valid; use Net::Domain::TLD; use mySociety::AuthToken; -use JSON; +use JSON::MaybeXS; +use Net::Facebook::Oauth2; =head1 NAME @@ -36,6 +37,8 @@ sub general : Path : Args(0) { return unless $c->req->method eq 'POST'; # decide which action to take + $c->detach('facebook_sign_in') if $c->get_param('facebook_sign_in'); + my $clicked_password = $c->get_param('sign_in'); my $clicked_email = $c->get_param('email_sign_in'); my $data_password = $c->get_param('password_sign_in'); @@ -122,18 +125,19 @@ sub email_sign_in : Private { if $c->get_param('password_register'); my $user = $c->model('DB::User')->new( $user_params ); - my $token_obj = $c->model('DB::Token') # - ->create( - { - scope => 'email_sign_in', - data => { - email => $good_email, - r => $c->get_param('r'), - name => $c->get_param('name'), - password => $user->password, - } - } - ); + my $token_data = { + email => $good_email, + r => $c->get_param('r'), + name => $c->get_param('name'), + password => $user->password, + }; + $token_data->{facebook_id} = $c->session->{oauth}{facebook_id} + if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id}; + + my $token_obj = $c->model('DB::Token')->create({ + scope => 'email_sign_in', + data => $token_data, + }); $c->stash->{token} = $token_obj->token; $c->send_email( 'login.txt', { to => $good_email } ); @@ -175,6 +179,7 @@ sub token : Path('/M') : Args(1) { my $user = $c->model('DB::User')->find_or_create( { email => $data->{email} } ); $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->update; $c->authenticate( { email => $user->email }, 'no_password' ); @@ -182,6 +187,113 @@ sub token : Path('/M') : Args(1) { $c->detach( 'redirect_on_signin', [ $data->{r} ] ); } +=head2 facebook_sign_in + +Starts the Facebook authentication sequence. + +=cut + +sub fb : Private { + my ($self, $c) = @_; + Net::Facebook::Oauth2->new( + application_id => $c->config->{FACEBOOK_APP_ID}, + application_secret => $c->config->{FACEBOOK_APP_SECRET}, + callback => $c->uri_for('/auth/Facebook'), + ); +} + +sub facebook_sign_in : Private { + my( $self, $c ) = @_; + + my $fb = $c->forward('/auth/fb'); + my $url = $fb->get_authorization_url(scope => ['email']); + + my %oauth; + $oauth{return_url} = $c->get_param('r'); + $oauth{detach_to} = $c->stash->{detach_to}; + $oauth{detach_args} = $c->stash->{detach_args}; + $c->session->{oauth} = \%oauth; + $c->res->redirect($url); +} + +=head2 facebook_callback + +Handles the Facebook callback request and completes the authentication sequence. + +=cut + +sub facebook_callback: Path('/auth/Facebook') : Args(0) { + 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; + } + } + + my $fb = $c->forward('/auth/fb'); + my $access_token; + eval { + $access_token = $fb->get_access_token(code => $c->get_param('code')); + }; + if ($@) { + ($c->stash->{message} = $@) =~ s/at [^ ]*Auth.pm.*//; + $c->stash->{template} = 'errors/generic.html'; + $c->detach; + } + + # save this token in session + $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}; + + my $user; + if ($email) { + # 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 } ); + $existing->update( { facebook_id => undef } ) if $existing; + # Get or create a user, give it this Facebook ID + $user = $c->model('DB::User')->find_or_new( { email => $email } ); + $user->facebook_id($uid); + $user->name($name); + $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 } ); + if ($user) { + # Matching Facebook 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->stash->{oauth_need_email} = 1; + } + } + + # If we've got here with a full user, log in + if ($user) { + $c->authenticate( { email => $user->email }, 'no_password' ); + $c->stash->{login_success} = 1; + } + + if ($c->session->{oauth}{detach_to}) { + $c->detach($c->session->{oauth}{detach_to}, $c->session->{oauth}{detach_args}); + } elsif ($c->stash->{oauth_need_email}) { + $c->stash->{template} = 'auth/general.html'; + } else { + $c->detach( 'redirect_on_signin', [ $c->session->{oauth}{return_url} ] ); + } +} + =head2 redirect_on_signin Used after signing in to take the person back to where they were. @@ -275,7 +387,7 @@ sub ajax_sign_in : Path('ajax/sign_in') { $return->{error} = 1; } - my $body = JSON->new->utf8(1)->encode( $return ); + my $body = encode_json($return); $c->res->content_type('application/json; charset=utf-8'); $c->res->body($body); @@ -287,7 +399,7 @@ sub ajax_sign_out : Path('ajax/sign_out') { $c->logout(); - my $body = JSON->new->utf8(1)->encode( { signed_out => 1 } ); + my $body = encode_json( { signed_out => 1 } ); $c->res->content_type('application/json; charset=utf-8'); $c->res->body($body); @@ -305,7 +417,7 @@ sub ajax_check_auth : Path('ajax/check_auth') { $code = 200; } - my $body = JSON->new->utf8(1)->encode( $data ); + my $body = encode_json($data); $c->res->content_type('application/json; charset=utf-8'); $c->res->code($code); $c->res->body($body); diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm index faddaa89e..9189b28e5 100644 --- a/perllib/FixMyStreet/App/Controller/Dashboard.pm +++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm @@ -4,6 +4,7 @@ use namespace::autoclean; use DateTime; use File::Slurp; +use JSON::MaybeXS; BEGIN { extends 'Catalyst::Controller'; } @@ -40,7 +41,7 @@ sub example : Local : Args(0) { my $data = File::Slurp::read_file( FixMyStreet->path_to( 'data/dashboard.json' )->stringify ); - my $j = JSON->new->utf8->decode($data); + my $j = decode_json($data); if ( !$c->stash->{ward} && !$c->stash->{category} ) { $c->stash->{problems} = $j->{counts_all}; } else { diff --git a/perllib/FixMyStreet/App/Controller/FakeMapit.pm b/perllib/FixMyStreet/App/Controller/FakeMapit.pm index 253c75ba4..a4adadd09 100755 --- a/perllib/FixMyStreet/App/Controller/FakeMapit.pm +++ b/perllib/FixMyStreet/App/Controller/FakeMapit.pm @@ -1,6 +1,7 @@ package FixMyStreet::App::Controller::FakeMapit; use Moose; use namespace::autoclean; +use JSON::MaybeXS; BEGIN { extends 'Catalyst::Controller'; } @@ -22,7 +23,7 @@ my $area = { "name" => "Everywhere", "type" => "ZZZ", "id" => 161 }; sub output : Private { my ( $self, $c, $data ) = @_; - my $body = JSON->new->utf8(1)->encode( $data ); + my $body = encode_json($data); $c->res->content_type('application/json; charset=utf-8'); $c->res->body( $body ); } diff --git a/perllib/FixMyStreet/App/Controller/JSON.pm b/perllib/FixMyStreet/App/Controller/JSON.pm index 959ead245..d3cd33546 100644 --- a/perllib/FixMyStreet/App/Controller/JSON.pm +++ b/perllib/FixMyStreet/App/Controller/JSON.pm @@ -4,11 +4,10 @@ use namespace::autoclean; BEGIN { extends 'Catalyst::Controller'; } -use JSON; +use JSON::MaybeXS; use DateTime; use DateTime::Format::ISO8601; use List::MoreUtils 'uniq'; -use FixMyStreet::App; =head1 NAME @@ -81,7 +80,7 @@ sub problems : Local { $date_col = 'lastupdate'; } - my $dt_parser = FixMyStreet::App->model('DB')->schema->storage->datetime_parser; + my $dt_parser = $c->model('DB')->schema->storage->datetime_parser; my $one_day = DateTime::Duration->new( days => 1 ); my $query = { diff --git a/perllib/FixMyStreet/App/Controller/Location.pm b/perllib/FixMyStreet/App/Controller/Location.pm index ff90d3d60..6def423ce 100644 --- a/perllib/FixMyStreet/App/Controller/Location.pm +++ b/perllib/FixMyStreet/App/Controller/Location.pm @@ -95,7 +95,8 @@ sub determine_location_from_pc : Private { # $error doubles up to return multiple choices by being an array if ( ref($error) eq 'ARRAY' ) { foreach (@$error) { - my $a = decode_utf8($_->{address}); + my $a = $_->{address}; + $a = decode_utf8($a) if !utf8::is_utf8($a); $a =~ s/, United Kingdom//; $a =~ s/, UK//; $_->{address} = $a; diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm index 3c4ce2cf7..8eb7f079e 100644 --- a/perllib/FixMyStreet/App/Controller/My.pm +++ b/perllib/FixMyStreet/App/Controller/My.pm @@ -36,6 +36,7 @@ sub my : Path : Args(0) { my $states = $c->stash->{filter_problem_states}; my $params = { state => [ keys %$states ], + user_id => $c->user->id, }; my $category = $c->get_param('filter_category'); @@ -44,9 +45,7 @@ sub my : Path : Args(0) { $c->stash->{filter_category} = $category; } - my $rs = $c->user->problems - ->to_body($c->cobrand->body_restriction) - ->search( $params, { + my $rs = $c->cobrand->problems->search( $params, { order_by => { -desc => 'confirmed' }, rows => 50 } )->page( $p_page ); @@ -77,7 +76,7 @@ sub my : Path : Args(0) { $c->stash->{updates} = \@updates; $c->stash->{updates_pager} = $rs->pager; - my @categories = $c->user->problems->search( undef, { + my @categories = $c->cobrand->problems->search( { user_id => $c->user->id }, { columns => [ 'category' ], distinct => 1, order_by => [ 'category' ], diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm index 96066ca93..f35dc64a5 100644 --- a/perllib/FixMyStreet/App/Controller/Open311.pm +++ b/perllib/FixMyStreet/App/Controller/Open311.pm @@ -4,7 +4,7 @@ use utf8; use Moose; use namespace::autoclean; -use JSON; +use JSON::MaybeXS; use XML::Simple; use DateTime::Format::W3CDTF; diff --git a/perllib/FixMyStreet/App/Controller/Photo.pm b/perllib/FixMyStreet/App/Controller/Photo.pm index bc72f4bfb..bfb1c5535 100644 --- a/perllib/FixMyStreet/App/Controller/Photo.pm +++ b/perllib/FixMyStreet/App/Controller/Photo.pm @@ -4,11 +4,9 @@ use namespace::autoclean; BEGIN {extends 'Catalyst::Controller'; } -use DateTime::Format::HTTP; -use Digest::SHA qw(sha1_hex); +use JSON::MaybeXS; use File::Path; use File::Slurp; -use Path::Class; use FixMyStreet::App::Model::PhotoSet; use if !$ENV{TRAVIS}, 'Image::Magick'; @@ -35,16 +33,12 @@ sub during :LocalRegex('^([0-9a-f]{40})\.(temp|fulltemp)\.jpeg$') { my ( $self, $c ) = @_; my ( $hash, $size ) = @{ $c->req->captures }; - my $file = file( $c->config->{UPLOAD_DIR}, "$hash.jpeg" ); - my $photo = $file->slurp; + my $photoset = FixMyStreet::App::Model::PhotoSet->new({ + data_items => [ $hash ] + }); - if ( $size eq 'temp' ) { - if ( $c->cobrand->default_photo_resize ) { - $photo = _shrink( $photo, $c->cobrand->default_photo_resize ); - } else { - $photo = _shrink( $photo, '250x250' ); - } - } + $size = $size eq 'temp' ? 'default' : 'full'; + my $photo = $photoset->get_image_data(size => $size, default => $c->cobrand->default_photo_resize); $c->forward( 'output', [ $photo ] ); } @@ -61,15 +55,9 @@ sub index :LocalRegex('^(c/)?(\d+)(?:\.(\d+))?(?:\.(full|tn|fp))?\.jpeg$') { photo => { '!=', undef }, } ); } else { - # GoogleBot-Image is doing this for some reason? - if ( $id =~ m{ ^(\d+) \D .* $ }x ) { - return $c->res->redirect( $c->uri_with( { id => $1 } ), 301 ); - } - - $c->detach( 'no_photo' ) if $id =~ /\D/; ($item) = $c->cobrand->problems->search( { id => $id, - state => [ FixMyStreet::DB::Result::Problem->visible_states(), 'partial' ], + state => [ FixMyStreet::DB::Result::Problem->visible_states() ], photo => { '!=', undef }, } ); } @@ -79,29 +67,9 @@ sub index :LocalRegex('^(c/)?(\d+)(?:\.(\d+))?(?:\.(full|tn|fp))?\.jpeg$') { $c->detach( 'no_photo' ) unless $c->cobrand->allow_photo_display($item); # Should only be for reports, not updates my $photo; - if ($item->can('get_photoset')) { - $photo = $item->get_photoset( $c ) - ->get_image_data( num => $photo_number, size => $size ) + $photo = $item->get_photoset + ->get_image_data( num => $photo_number, size => $size, default => $c->cobrand->default_photo_resize ) or $c->detach( 'no_photo' ); - } else { - $photo = $item->photo; - # If photo field contains a hash - if (length($photo) == 40) { - my $file = file( $c->config->{UPLOAD_DIR}, "$photo.jpeg" ); - $photo = $file->slurp; - } - - if ( $size eq 'tn' ) { - $photo = _shrink( $photo, 'x100' ); - } elsif ( $size eq 'fp' ) { - $photo = _crop( $photo ); - } elsif ( $size eq 'full' ) { - } elsif ( $c->cobrand->default_photo_resize ) { - $photo = _shrink( $photo, $c->cobrand->default_photo_resize ); - } else { - $photo = _shrink( $photo, '250x250' ); - } - } $c->forward( 'output', [ $photo ] ); } @@ -122,32 +90,30 @@ sub no_photo : Private { $c->detach( '/page_error_404_not_found', [ 'No photo' ] ); } -# Shrinks a picture to the specified size, but keeping in proportion. -sub _shrink { - my ($photo, $size) = @_; - my $image = Image::Magick->new; - $image->BlobToImage($photo); - my $err = $image->Scale(geometry => "$size>"); - throw Error::Simple("resize failed: $err") if "$err"; - $image->Strip(); - my @blobs = $image->ImageToBlob(); - undef $image; - return $blobs[0]; -} +sub upload : Local { + my ( $self, $c ) = @_; + my @items = ( + ( map { + /^photo/ ? # photo, photo1, photo2 etc. + ($c->req->upload($_)) : () + } sort $c->req->upload), + ); + my $photoset = FixMyStreet::App::Model::PhotoSet->new({ + c => $c, + data_items => \@items, + }); + + my $fileid = $photoset->data; + my $out; + if ($c->stash->{photo_error} || !$fileid) { + $c->res->status(500); + $out = { error => $c->stash->{photo_error} || _('Unknown error') }; + } else { + $out = { id => $fileid }; + } -# Shrinks a picture to 90x60, cropping so that it is exactly that. -sub _crop { - my ($photo) = @_; - my $image = Image::Magick->new; - $image->BlobToImage($photo); - my $err = $image->Resize( geometry => "90x60^" ); - throw Error::Simple("resize failed: $err") if "$err"; - $err = $image->Extent( geometry => '90x60', gravity => 'Center' ); - throw Error::Simple("resize failed: $err") if "$err"; - $image->Strip(); - my @blobs = $image->ImageToBlob(); - undef $image; - return $blobs[0]; + $c->res->content_type('application/json; charset=utf-8'); + $c->res->body(encode_json($out)); } =head2 process_photo diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm index d7cae05d4..5cc44bb8f 100644 --- a/perllib/FixMyStreet/App/Controller/Report.pm +++ b/perllib/FixMyStreet/App/Controller/Report.pm @@ -2,6 +2,8 @@ package FixMyStreet::App::Controller::Report; use Moose; use namespace::autoclean; +use JSON::MaybeXS; + BEGIN { extends 'Catalyst::Controller'; } =head1 NAME @@ -184,7 +186,7 @@ sub format_problem_for_display : Private { if ( $c->stash->{ajax} ) { $c->res->content_type('application/json; charset=utf-8'); - my $content = JSON->new->utf8(1)->encode( + my $content = encode_json( { report => $c->cobrand->problem_as_hashref( $problem, $c ), updates => $c->cobrand->updates_as_hashref( $problem, $c ), diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 246facbee..66dc20a3a 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -12,7 +12,7 @@ use mySociety::MaPit; use Path::Class; use Utils; use mySociety::EmailUtil; -use JSON; +use JSON::MaybeXS; =head1 NAME @@ -149,6 +149,7 @@ sub report_new_ajax : Path('mobile') : Args(0) { } } ); if ( $report->confirmed ) { + $c->forward( 'create_reporter_alert' ); $c->stash->{ json_response } = { success => 1, report => $report->id }; } else { $c->stash->{token_url} = $c->uri_for_email( '/P', $token->token ); @@ -164,9 +165,7 @@ sub report_new_ajax : Path('mobile') : Args(0) { sub send_json_response : Private { my ( $self, $c ) = @_; - my $body = JSON->new->utf8(1)->encode( - $c->stash->{json_response}, - ); + my $body = encode_json($c->stash->{json_response}); $c->res->content_type('application/json; charset=utf-8'); $c->res->body($body); } @@ -178,9 +177,7 @@ sub report_form_ajax : Path('ajax') : Args(0) { # work out the location for this report and do some checks if ( ! $c->forward('determine_location') ) { - my $body = JSON->new->utf8(1)->encode( { - error => $c->stash->{location_error}, - } ); + my $body = encode_json({ error => $c->stash->{location_error} }); $c->res->content_type('application/json; charset=utf-8'); $c->res->body($body); return; @@ -197,7 +194,7 @@ sub report_form_ajax : Path('ajax') : Args(0) { my $extra_titles_list = $c->cobrand->title_list($c->stash->{all_areas}); - my $body = JSON->new->utf8(1)->encode( + my $body = encode_json( { councils_text => $councils_text, category => $category, @@ -216,11 +213,7 @@ sub category_extras_ajax : Path('category_extras') : Args(0) { $c->forward('initialize_report'); if ( ! $c->forward('determine_location') ) { - my $body = JSON->new->utf8(1)->encode( - { - error => _("Sorry, we could not find that location."), - } - ); + my $body = encode_json({ error => _("Sorry, we could not find that location.") }); $c->res->content_type('application/json; charset=utf-8'); $c->res->body($body); return 1; @@ -244,11 +237,7 @@ sub category_extras_ajax : Path('category_extras') : Args(0) { $category_extra = $c->render_fragment( 'report/new/category_extras.html'); } - my $body = JSON->new->utf8(1)->encode( - { - category_extra => $category_extra, - } - ); + my $body = encode_json({ category_extra => $category_extra }); $c->res->content_type('application/json; charset=utf-8'); $c->res->body($body); @@ -403,6 +392,12 @@ sub report_import : Path('/import') { return 1; } +sub oauth_callback : Private { + my ( $self, $c, $token_code ) = @_; + $c->stash->{oauth_report} = $token_code; + $c->detach('report_new'); +} + =head2 initialize_report Create the report and set up some basics in it. If there is a partial report @@ -426,26 +421,21 @@ sub initialize_report : Private { for (1) { # use as pseudo flow control - # did we find a token - last unless $partial; - # is it in the database my $token = $c->model("DB::Token") - ->find( { scope => 'partial', token => $partial } ) # + ->find( { scope => 'partial', token => $partial } ) || last; # can we get an id from it? - my $id = $token->data # - || last; + my $id = $token->data || last; # load the related problem - $report = $c->cobrand->problems # - ->search( { id => $id, state => 'partial' } ) # + $report = $c->cobrand->problems + ->search( { id => $id, state => 'partial' } ) ->first; if ($report) { - # log the problem creation user in to the site $c->authenticate( { email => $report->user->email }, 'no_password' ); @@ -453,27 +443,32 @@ sub initialize_report : Private { # save the token to delete at the end $c->stash->{partial_token} = $token if $report; - } - else { - + } else { # no point keeping it if it is done. $token->delete; } } } - if ( !$report ) { + if (!$report && $c->stash->{oauth_report}) { + my $auth_token = $c->forward( '/tokens/load_auth_token', + [ $c->stash->{oauth_report}, 'problem/social' ] ); + $report = $c->model("DB::Problem")->new($auth_token->data); + } - # If we didn't find a partial then create a new one + if ($report) { + # Stash the photo IDs for "already got" display + $c->stash->{upload_fileid} = $report->get_photoset->data; + } else { + # If we didn't find one otherwise, start with a blank report $report = $c->model('DB::Problem')->new( {} ); + } - # If we have a user logged in let's prefill some values for them. - if ( $c->user ) { - my $user = $c->user->obj; - $report->user($user); - $report->name( $user->name ); - } - + # If we have a user logged in let's prefill some values for them. + if (!$report->user && $c->user) { + my $user = $c->user->obj; + $report->user($user); + $report->name( $user->name ); } if ( $c->get_param('first_name') && $c->get_param('last_name') ) { @@ -859,12 +854,6 @@ sub process_report : Private { $bodies = join( ',', @{ $c->stash->{bodies_to_list} } ) || -1; $report->bodies_str( $bodies ); - my %extra; - $c->cobrand->process_extras( $c, undef, \%extra ); - if ( %extra ) { - $report->extra( \%extra ); - } - } elsif ( $report->category ) { # FIXME All contacts were fetched in setup_categories_and_bodies, @@ -936,7 +925,7 @@ sub process_report : Private { $report->non_public( 1 ); } - $c->cobrand->process_extras( $c, $contacts[0]->body_id, \@extra ); + $c->cobrand->process_open311_extras( $c, $contacts[0]->body_id, \@extra ); if ( @extra ) { $c->stash->{report_meta} = { map { $_->{name} => $_ } @extra }; @@ -955,6 +944,15 @@ sub process_report : Private { } + # Get a list of custom form fields we want and store them in extra metadata + foreach my $field ($c->cobrand->report_form_extras) { + my $form_name = $field->{name}; + my $value = $c->get_param($form_name) || ''; + $c->stash->{field_errors}->{$form_name} = _('This information is required') + if $field->{required} && !$value; + $report->set_extra_metadata( $form_name => $value ); + } + # set defaults that make sense $report->state('unconfirmed'); @@ -1005,6 +1003,13 @@ sub check_for_errors : Private { delete $field_errors{name}; } + # if using social login then we don't care about name and email errors + $c->stash->{is_social_user} = $c->get_param('facebook_sign_in') || $c->get_param('twitter_sign_in'); + if ( $c->stash->{is_social_user} ) { + delete $field_errors{name}; + delete $field_errors{email}; + } + # add the photo error if there is one. if ( my $photo_error = delete $c->stash->{photo_error} ) { $field_errors{photo} = $photo_error; @@ -1018,6 +1023,19 @@ sub check_for_errors : Private { return; } +# Store changes in token for when token is validated. +sub tokenize_user : Private { + my ($self, $c, $report) = @_; + $c->stash->{token_data} = { + name => $report->user->name, + phone => $report->user->phone, + password => $report->user->password, + title => $report->user->title, + }; + $c->stash->{token_data}{facebook_id} = $c->session->{oauth}{facebook_id} + if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id}; +} + =head2 save_user_and_report Save the user and the report. @@ -1029,7 +1047,41 @@ before or they are currently logged in. Otherwise discard any changes. sub save_user_and_report : Private { my ( $self, $c ) = @_; - my $report = $c->stash->{report}; + my $report = $c->stash->{report}; + + # If there was a photo add that + if ( my $fileid = $c->stash->{upload_fileid} ) { + $report->photo($fileid); + } + + # Set a default if possible + $report->category( _('Other') ) unless $report->category; + + # Set unknown to DB unknown + $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+$/) { + $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} ) ; + } + + if ( $c->stash->{is_social_user} ) { + my $token = $c->model("DB::Token")->create( { + scope => 'problem/social', + data => { $report->get_inflated_columns }, + } ); + + $c->stash->{detach_to} = '/report/new/oauth_callback'; + $c->stash->{detach_args} = [$token->token]; + + if ( $c->get_param('facebook_sign_in') ) { + $c->detach('/auth/facebook_sign_in'); + } elsif ( $c->get_param('twitter_sign_in') ) { + $c->detach('/auth/twitter_sign_in'); + } + } # Save or update the user if appropriate if ( $c->cobrand->never_confirm_reports ) { @@ -1039,15 +1091,10 @@ sub save_user_and_report : Private { $report->user->insert(); } $report->confirm(); + } elsif ( !$report->user->in_storage ) { # User does not exist. - # Store changes in token for when token is validated. - $c->stash->{token_data} = { - name => $report->user->name, - phone => $report->user->phone, - password => $report->user->password, - title => $report->user->title, - }; + $c->forward('tokenize_user', [ $report ]); $report->user->name( undef ); $report->user->phone( undef ); $report->user->password( '', 1 ); @@ -1064,35 +1111,11 @@ sub save_user_and_report : Private { } else { # User exists and we are not logged in as them. - # Store changes in token for when token is validated. - $c->stash->{token_data} = { - name => $report->user->name, - phone => $report->user->phone, - password => $report->user->password, - title => $report->user->title, - }; + $c->forward('tokenize_user', [ $report ]); $report->user->discard_changes(); $c->log->info($report->user->id . ' exists, but is not logged in for this report'); } - # If there was a photo add that too - if ( my $fileid = $c->stash->{upload_fileid} ) { - $report->photo($fileid); - } - - # Set a default if possible - $report->category( _('Other') ) unless $report->category; - - # Set unknown to DB unknown - $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+$/) { - $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} ) ; - } - # save the report; $report->in_storage ? $report->update : $report->insert(); @@ -1118,8 +1141,8 @@ sub generate_map : Private { my $longitude = $c->stash->{longitude}; # Don't do anything if the user skipped the map + $c->stash->{page} = 'new'; if ( $c->stash->{report}->used_map ) { - $c->stash->{page} = 'new'; FixMyStreet::Map::display_map( $c, latitude => $latitude, diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm index 445723fec..8d6bc2019 100644 --- a/perllib/FixMyStreet/App/Controller/Report/Update.pm +++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm @@ -20,12 +20,16 @@ Creates an update to a report sub report_update : Path : Args(0) { my ( $self, $c ) = @_; - $c->forward( '/report/load_problem_or_display_error', [ $c->get_param('id') ] ); + $c->forward('initialize_update'); + $c->forward('load_problem'); + $c->forward('check_form_submitted') + or $c->go( '/report/display', [ $c->stash->{problem}->id ] ); + $c->forward('process_update'); $c->forward('process_user'); $c->forward('/photo/process_photo'); $c->forward('check_for_errors') - or $c->go( '/report/display', [ $c->get_param('id') ] ); + or $c->go( '/report/display', [ $c->stash->{problem}->id ] ); $c->forward('save_update'); $c->forward('redirect_or_confirm_creation'); @@ -151,19 +155,46 @@ sub process_user : Private { return 1; } -=head2 process_update +=head2 oauth_callback -Take the submitted params and create a new update item. Does not save -anything to the database. +Called when we successfully login via OAuth. Stores the token so we can look up +what we have so far. -NB: relies on their being a problem and update_user in the stash. May -want to move adding these elsewhere +=cut + +sub oauth_callback : Private { + my ( $self, $c, $token_code ) = @_; + $c->stash->{oauth_update} = $token_code; + $c->detach('report_update'); +} + +=head2 initialize_update + +Create an initial update object, either empty or from stored OAuth data. =cut -sub process_update : Private { +sub initialize_update : Private { my ( $self, $c ) = @_; + my $update; + if ($c->stash->{oauth_update}) { + my $auth_token = $c->forward( '/tokens/load_auth_token', + [ $c->stash->{oauth_update}, 'update/social' ] ); + $update = $c->model("DB::Comment")->new($auth_token->data); + } + + if ($update) { + $c->stash->{upload_fileid} = $update->get_photoset->data; + } else { + $update = $c->model('DB::Comment')->new({ + state => 'unconfirmed', + cobrand => $c->cobrand->moniker, + cobrand_data => '', + lang => $c->stash->{lang_code}, + }); + } + if ( $c->get_param('first_name') && $c->get_param('last_name') ) { my $first_name = $c->get_param('first_name'); my $last_name = $c->get_param('last_name'); @@ -173,6 +204,48 @@ sub process_update : Private { $c->stash->{last_name} = $last_name; } + $c->stash->{update} = $update; +} + +=head2 load_problem + +Our update could be prefilled, or we could be submitting a form containing an +ID. Look up the relevant report either way. + +=cut + +sub load_problem : Private { + my ( $self, $c ) = @_; + + my $update = $c->stash->{update}; + # Problem ID could come from existing update in token, or from query parameter + my $problem_id = $update->problem_id || $c->get_param('id'); + $c->forward( '/report/load_problem_or_display_error', [ $problem_id ] ); + $update->problem($c->stash->{problem}); +} + +=head2 check_form_submitted + +This makes sure we only proceed to processing if we've had the form submitted +(we may have come here via an OAuth login, for example). + +=cut + +sub check_form_submitted : Private { + my ( $self, $c ) = @_; + return $c->get_param('submit_update') || ''; +} + +=head2 process_update + +Take the submitted params and updates our update item. Does not save +anything to the database. + +=cut + +sub process_update : Private { + my ( $self, $c ) = @_; + my %params = map { $_ => $c->get_param($_) } ( 'update', 'name', 'fixed', 'state', 'reopen' ); @@ -184,20 +257,12 @@ sub process_update : Private { $params{reopen} = 0 unless $c->user && $c->user->id == $c->stash->{problem}->user->id; - my $update = $c->model('DB::Comment')->new( - { - text => $params{update}, - name => $name, - problem => $c->stash->{problem}, - state => 'unconfirmed', - mark_fixed => $params{fixed} ? 1 : 0, - mark_open => $params{reopen} ? 1 : 0, - cobrand => $c->cobrand->moniker, - cobrand_data => '', - lang => $c->stash->{lang_code}, - anonymous => $anonymous, - } - ); + my $update = $c->stash->{update}; + $update->text($params{update}); + $update->name($name); + $update->mark_fixed($params{fixed} ? 1 : 0); + $update->mark_open($params{reopen} ? 1 : 0); + $update->anonymous($anonymous); if ( $params{state} ) { $params{state} = 'fixed - council' @@ -221,9 +286,9 @@ sub process_update : Private { my @extra; # Next function fills this, but we don't need it here. - # This is just so that the error checkign for these extra fields runs. + # This is just so that the error checking for these extra fields runs. # TODO Use extra here as it is used on reports. - $c->cobrand->process_extras( $c, $update->problem->bodies_str, \@extra ); + $c->cobrand->process_open311_extras( $c, $update->problem->bodies_str, \@extra ); if ( $c->get_param('fms_extra_title') ) { my %extras = (); @@ -241,7 +306,6 @@ sub process_update : Private { $c->log->debug( 'name is ' . $c->get_param('name') ); - $c->stash->{update} = $update; $c->stash->{add_alert} = $c->get_param('add_alert'); return 1; @@ -283,6 +347,13 @@ sub check_for_errors : Private { %{ $c->stash->{update}->check_for_errors }, ); + # if using social login then we don't care about name and email errors + $c->stash->{is_social_user} = $c->get_param('facebook_sign_in') || $c->get_param('twitter_sign_in'); + if ( $c->stash->{is_social_user} ) { + delete $field_errors{name}; + delete $field_errors{email}; + } + if ( my $photo_error = delete $c->stash->{photo_error} ) { $field_errors{photo} = $photo_error; } @@ -302,6 +373,17 @@ sub check_for_errors : Private { return; } +# Store changes in token for when token is validated. +sub tokenize_user : Private { + my ($self, $c, $update) = @_; + $c->stash->{token_data} = { + name => $update->user->name, + password => $update->user->password, + }; + $c->stash->{token_data}{facebook_id} = $c->session->{oauth}{facebook_id} + if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id}; +} + =head2 save_update Save the update and the user as appropriate. @@ -313,6 +395,27 @@ sub save_update : Private { my $update = $c->stash->{update}; + # If there was a photo add that too + if ( my $fileid = $c->stash->{upload_fileid} ) { + $update->photo($fileid); + } + + if ( $c->stash->{is_social_user} ) { + my $token = $c->model("DB::Token")->create( { + scope => 'update/social', + data => { $update->get_inflated_columns }, + } ); + + $c->stash->{detach_to} = '/report/update/oauth_callback'; + $c->stash->{detach_args} = [$token->token]; + + if ( $c->get_param('facebook_sign_in') ) { + $c->detach('/auth/facebook_sign_in'); + } elsif ( $c->get_param('twitter_sign_in') ) { + $c->detach('/auth/twitter_sign_in'); + } + } + if ( $c->cobrand->never_confirm_updates ) { if ( $update->user->in_storage() ) { $update->user->update(); @@ -322,11 +425,7 @@ sub save_update : Private { $update->confirm(); } elsif ( !$update->user->in_storage ) { # User does not exist. - # Store changes in token for when token is validated. - $c->stash->{token_data} = { - name => $update->user->name, - password => $update->user->password, - }; + $c->forward('tokenize_user', [ $update ]); $update->user->name( undef ); $update->user->password( '', 1 ); $update->user->insert; @@ -338,19 +437,10 @@ sub save_update : Private { $update->confirm; } else { # User exists and we are not logged in as them. - # Store changes in token for when token is validated. - $c->stash->{token_data} = { - name => $update->user->name, - password => $update->user->password, - }; + $c->forward('tokenize_user', [ $update ]); $update->user->discard_changes(); } - # If there was a photo add that too - if ( my $fileid = $c->stash->{upload_fileid} ) { - $update->photo($fileid); - } - if ( $update->in_storage ) { $update->update; } diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm index 407fc625e..027b0d5a4 100644 --- a/perllib/FixMyStreet/App/Controller/Reports.pm +++ b/perllib/FixMyStreet/App/Controller/Reports.pm @@ -3,6 +3,7 @@ use Moose; use namespace::autoclean; use File::Slurp; +use JSON::MaybeXS; use List::MoreUtils qw(any); use POSIX qw(strcoll); use RABX; @@ -68,7 +69,7 @@ sub index : Path : Args(0) { my $data = File::Slurp::read_file( FixMyStreet->path_to( '../data/all-reports.json' )->stringify ); - my $j = JSON->new->utf8->decode($data); + my $j = decode_json($data); $c->stash->{fixed} = $j->{fixed}; $c->stash->{open} = $j->{open}; }; diff --git a/perllib/FixMyStreet/App/Controller/Rss.pm b/perllib/FixMyStreet/App/Controller/Rss.pm index b817fe326..6047f063b 100755 --- a/perllib/FixMyStreet/App/Controller/Rss.pm +++ b/perllib/FixMyStreet/App/Controller/Rss.pm @@ -218,7 +218,7 @@ sub query_main : Private { . ($alert_type->head_table ? $alert_type->head_table . '_id=? and ' : '') . $alert_type->item_where . ' order by ' . $alert_type->item_order; - my $rss_limit = mySociety::Config::get('RSS_LIMIT'); + my $rss_limit = FixMyStreet->config('RSS_LIMIT'); $query .= " limit $rss_limit" unless $c->stash->{type} =~ /^all/; my $q = $c->model('DB::Alert')->result_source->storage->dbh->prepare($query); @@ -260,9 +260,9 @@ sub add_row : Private { $row->{confirmed} =~ s/^(\d+)/ordinal($1)/e if $c->stash->{lang_code} eq 'en-gb'; } - (my $title = _($alert_type->item_title)) =~ s/{{(.*?)}}/$row->{$1}/g; - (my $link = $alert_type->item_link) =~ s/{{(.*?)}}/$row->{$1}/g; - (my $desc = _($alert_type->item_description)) =~ s/{{(.*?)}}/$row->{$1}/g; + (my $title = _($alert_type->item_title)) =~ s/\{\{(.*?)}}/$row->{$1}/g; + (my $link = $alert_type->item_link) =~ s/\{\{(.*?)}}/$row->{$1}/g; + (my $desc = _($alert_type->item_description)) =~ s/\{\{(.*?)}}/$row->{$1}/g; my $base_url = $c->cobrand->base_url_for_report($row); my $url = $base_url . $link; @@ -317,9 +317,9 @@ sub add_parameters : Private { $row->{$_} = $c->stash->{title_params}->{$_}; } - (my $title = _($alert_type->head_title)) =~ s/{{(.*?)}}/$row->{$1}/g; - (my $link = $alert_type->head_link) =~ s/{{(.*?)}}/$row->{$1}/g; - (my $desc = _($alert_type->head_description)) =~ s/{{(.*?)}}/$row->{$1}/g; + (my $title = _($alert_type->head_title)) =~ s/\{\{(.*?)}}/$row->{$1}/g; + (my $link = $alert_type->head_link) =~ s/\{\{(.*?)}}/$row->{$1}/g; + (my $desc = _($alert_type->head_description)) =~ s/\{\{(.*?)}}/$row->{$1}/g; $c->stash->{rss}->channel( title => ent($title), diff --git a/perllib/FixMyStreet/App/Controller/Static.pm b/perllib/FixMyStreet/App/Controller/Static.pm index d91a07fea..5054809c7 100755 --- a/perllib/FixMyStreet/App/Controller/Static.pm +++ b/perllib/FixMyStreet/App/Controller/Static.pm @@ -10,56 +10,23 @@ FixMyStreet::App::Controller::Static - Catalyst Controller =head1 DESCRIPTION -Static pages Catalyst Controller. FAQ does some smarts to choose the correct -template depending on language, will need extending at some point. +Old static pages Catalyst Controller. =head1 METHODS =cut -sub about : Global : Args(0) { - my ( $self, $c ) = @_; - - my $lang_code = $c->stash->{lang_code}; - my $template = "static/about-$lang_code.html"; - $c->stash->{template} = $template; -} - -sub privacy : Global : Args(0) { +sub about_redirect : Private { my ( $self, $c ) = @_; + $c->res->redirect( $c->uri_for_action('/about/page', [ $c->action->name ] )); } -sub faq : Global : Args(0) { - my ( $self, $c ) = @_; - - # There should be a faq template for each language in a cobrand or default. - # This is because putting the FAQ translations into the PO files is - # overkill. - - # We rely on the list of languages for the site being restricted so that there - # will be a faq template for that language/cobrand combo. - - my $lang_code = $c->stash->{lang_code}; - my $template = "faq/faq-$lang_code.html"; - $c->stash->{template} = $template; -} - -sub fun : Global : Args(0) { - my ( $self, $c ) = @_; - # don't need to do anything here - should just pass through. -} - -sub posters : Global : Args(0) { - my ( $self, $c ) = @_; -} - -sub iphone : Global : Args(0) { - my ( $self, $c ) = @_; -} - -sub council : Global : Args(0) { - my ( $self, $c ) = @_; -} +sub faq : Global : Args(0) { $_[1]->forward('/about/page', ['faq']) } +sub privacy : Global : Args(0) { $_[1]->detach('about_redirect') } +sub fun : Global : Args(0) { $_[1]->detach('about_redirect') } +sub posters : Global : Args(0) { $_[1]->detach('about_redirect') } +sub iphone : Global : Args(0) { $_[1]->detach('about_redirect') } +sub council : Global : Args(0) { $_[1]->detach('about_redirect') } sub unresponsive : Global : Args(0) { my ( $self, $c ) = @_; diff --git a/perllib/FixMyStreet/App/Controller/Status.pm b/perllib/FixMyStreet/App/Controller/Status.pm index 907fe5456..931c7bd47 100755 --- a/perllib/FixMyStreet/App/Controller/Status.pm +++ b/perllib/FixMyStreet/App/Controller/Status.pm @@ -3,7 +3,7 @@ use Moose; use namespace::autoclean; use HTTP::Negotiate; -use JSON; +use JSON::MaybeXS; BEGIN { extends 'Catalyst::Controller'; } @@ -27,6 +27,9 @@ sub index_json : Path('/status.json') : Args(0) { sub index : Path : Args(0) { my ($self, $c, $format) = @_; + # Workaround that the admin summary page is only displayed to Zurich + # superusers. It doesn't have anything sensitive + $c->stash->{admin_type} = 'super'; # Fetch summary stats from admin front page $c->forward('/admin/index'); diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm index ba15162ce..eb35fd152 100644 --- a/perllib/FixMyStreet/App/Controller/Tokens.pm +++ b/perllib/FixMyStreet/App/Controller/Tokens.pm @@ -34,6 +34,7 @@ sub confirm_problem : Path('/P') { title => 'Title of Report', bodies_str => 'True', url => '/report/123', + service => $c->get_param('service'), }; return; } @@ -104,6 +105,7 @@ sub confirm_problem : Path('/P') { $problem->user->phone( $data->{phone} ) if $data->{phone}; $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->update; } $c->authenticate( { email => $problem->user->email }, 'no_password' ); @@ -229,6 +231,7 @@ sub confirm_update : Path('/C') { if ( $data->{name} || $data->{password} ) { $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->update; } @@ -323,11 +326,7 @@ sub load_auth_token : Private { } ); - unless ( $token ) { - $c->stash->{template} = 'errors/generic.html'; - $c->stash->{message} = _("I'm afraid we couldn't validate that token. If you've copied the URL from an email, please check that you copied it exactly.\n"); - $c->detach; - } + $c->detach('token_too_old') unless $token; return $token; } diff --git a/perllib/FixMyStreet/App/Model/DB.pm b/perllib/FixMyStreet/App/Model/DB.pm index f9e43172f..ac1f98dc9 100644 --- a/perllib/FixMyStreet/App/Model/DB.pm +++ b/perllib/FixMyStreet/App/Model/DB.pm @@ -8,7 +8,7 @@ use FixMyStreet; __PACKAGE__->config( schema_class => 'FixMyStreet::DB', - connect_info => FixMyStreet->dbic_connect_info, + connect_info => sub { FixMyStreet::DB->storage->dbh }, ); =head1 NAME diff --git a/perllib/FixMyStreet/App/Model/EmailSend.pm b/perllib/FixMyStreet/App/Model/EmailSend.pm index 475026267..93751d4a6 100644 --- a/perllib/FixMyStreet/App/Model/EmailSend.pm +++ b/perllib/FixMyStreet/App/Model/EmailSend.pm @@ -4,67 +4,16 @@ use base 'Catalyst::Model::Factory'; use strict; use warnings; -use FixMyStreet; -use Email::Send; - =head1 NAME FixMyStreet::App::Model::EmailSend =head1 DESCRIPTION -Thin wrapper around Email::Send - configuring it correctly acording to our config. - -If the config value 'SMTP_SMARTHOST' is set then email is routed via SMTP to -that. Otherwise it is sent using a 'sendmail' like binary on the local system. - -And finally if if FixMyStreet->test_mode returns true then emails are not sent -at all but are stored in memory for the test suite to inspect (using -Email::Send::Test). +Catalyst Model wrapper around FixMyStreet::EmailSend =cut -my $args = undef; - -if ( FixMyStreet->test_mode ) { - - # Email::Send::Test - $args = { mailer => 'Test', }; -} -elsif ( my $smtp_host = FixMyStreet->config('SMTP_SMARTHOST') ) { - - # Email::Send::SMTP - my $type = FixMyStreet->config('SMTP_TYPE') || ''; - my $port = FixMyStreet->config('SMTP_PORT') || ''; - my $username = FixMyStreet->config('SMTP_USERNAME') || ''; - my $password = FixMyStreet->config('SMTP_PASSWORD') || ''; - - unless ($port) { - $port = 25; - $port = 465 if $type eq 'ssl'; - $port = 587 if $type eq 'tls'; - } - - my $mailer_args = [ - Host => $smtp_host, - Port => $port, - ]; - push @$mailer_args, ssl => 1 if $type eq 'ssl'; - push @$mailer_args, tls => 1 if $type eq 'tls'; - push @$mailer_args, username => $username, password => $password - if $username && $password; - $args = { - mailer => 'FixMyStreet::EmailSend::DoNotReply', - mailer_args => $mailer_args, - }; -} -else { - - # Email::Send::Sendmail - $args = { mailer => 'Sendmail' }; -} - __PACKAGE__->config( - class => 'Email::Send', - args => $args, + class => 'FixMyStreet::EmailSend', ); diff --git a/perllib/FixMyStreet/App/Model/PhotoSet.pm b/perllib/FixMyStreet/App/Model/PhotoSet.pm index b18460821..54457bae9 100644 --- a/perllib/FixMyStreet/App/Model/PhotoSet.pm +++ b/perllib/FixMyStreet/App/Model/PhotoSet.pm @@ -14,27 +14,32 @@ has c => ( is => 'ro', ); +# The attached report, for using its ID has object => ( is => 'ro', ); -has data => ( # generic data from DB field +# If a PhotoSet is generated from a database row, db_data is set, which then +# fills data_items -> ids -> data. If it is generated during creation, +# data_items is set, which then similarly fills ids -> data. + +has db_data => ( # generic data from DB field + is => 'ro', +); + +has data => ( # String of photo hashes is => 'ro', lazy => 1, default => sub { - # yes, this is a little circular: data -> data_items -> items -> data - # e.g. if not provided, then we're presumably uploading/etc., so calculate from - # the stored cached fileids - # (obviously if you provide none of these, then you'll get an infinite loop) my $self = shift; - my $data = join ',', map { $_->[0] } $self->all_images; + my $data = join ',', $self->all_ids; return $data; } ); -has data_items => ( # either a) split from data or b) provided by photo upload +has data_items => ( # either a) split from db_data or b) provided by photo upload isa => 'ArrayRef', - is => 'rw', + is => 'ro', traits => ['Array'], lazy => 1, handles => { @@ -42,8 +47,7 @@ has data_items => ( # either a) split from data or b) provided by photo upload }, default => sub { my $self = shift; - my $data = $self->data - or return []; + my $data = $self->db_data or return []; return [$data] if (_jpeg_magic($data)); @@ -56,7 +60,7 @@ has upload_dir => ( lazy => 1, default => sub { my $self = shift; - my $cache_dir = path( $self->c->config->{UPLOAD_DIR} ); + my $cache_dir = path( FixMyStreet->config('UPLOAD_DIR') ); $cache_dir->mkpath; unless ( -d $cache_dir && -w $cache_dir ) { warn "Can't find/write to photo cache directory '$cache_dir'"; @@ -72,33 +76,29 @@ sub _jpeg_magic { # and \x{49}\x{49} (Tiff, 3 results in live DB) ? } -=head2 C<images>, C<num_images>, C<get_raw_image_data>, C<all_images> +=head2 C<ids>, C<num_images>, C<get_id>, C<all_ids> -C<$photoset-E<GT>images> is an AoA containing the filed and the binary image data. +C<$photoset-E<GT>ids> is an arrayref containing the fileid data. - [ - [ $fileid1, $binary_data ], - [ $fileid2, $binary_data ], - ... - ] + [ $fileid1, $fileid2, ... ] Various accessors are provided onto it: num_images: count - get_raw_image_data ($index): return the [$fileid, $binary_data] tuple - all_images: return AoA as an array (e.g. rather than arrayref) + get_id ($index): return the correct id + all_ids: array of elements, rather than arrayref =cut -has images => ( # AoA of [$fileid, $binary_data] tuples +has ids => ( # Arrayref of $fileid tuples (always, so post upload/raw data processing) isa => 'ArrayRef', - is => 'rw', + is => 'ro', traits => ['Array'], lazy => 1, handles => { num_images => 'count', - get_raw_image_data => 'get', - all_images => 'elements', + get_id => 'get', + all_ids => 'elements', }, default => sub { my $self = shift; @@ -159,7 +159,7 @@ has images => ( # AoA of [$fileid, $binary_data] tuples my $fileid = $self->get_fileid($photo_blob); my $file = $self->get_file($fileid); $upload->copy_to( $file ); - return [$fileid, $photo_blob]; + return $fileid; } if (_jpeg_magic($part)) { @@ -167,21 +167,18 @@ has images => ( # AoA of [$fileid, $binary_data] tuples my $fileid = $self->get_fileid($photo_blob); my $file = $self->get_file($fileid); $file->spew_raw($photo_blob); - return [$fileid, $photo_blob]; + return $fileid; } if (length($part) == 40) { my $fileid = $part; my $file = $self->get_file($fileid); if ($file->exists) { - my $photo = $file->slurp_raw; - [$fileid, $photo]; - } - else { + $fileid; + } else { warn "File $fileid doesn't exist"; (); } - } - else { + } else { warn sprintf "Received bad photo hash of length %d", length($part); (); } @@ -201,15 +198,23 @@ sub get_file { return path( $cache_dir, "$fileid.jpeg" ); } +sub get_raw_image_data { + my ($self, $index) = @_; + my $fileid = $self->get_id($index); + my $file = $self->get_file($fileid); + if ($file->exists) { + my $photo = $file->slurp_raw; + return $photo; + } +} + sub get_image_data { my ($self, %args) = @_; my $num = $args{num} || 0; - my $data = $self->get_raw_image_data( $num ) + my $photo = $self->get_raw_image_data( $num ) or return; - my ($fileid, $photo) = @$data; - my $size = $args{size}; if ( $size eq 'tn' ) { $photo = _shrink( $photo, 'x100' ); @@ -218,7 +223,7 @@ sub get_image_data { } elsif ( $size eq 'full' ) { # do nothing } else { - $photo = _shrink( $photo, $self->c->cobrand->default_photo_resize || '250x250' ); + $photo = _shrink( $photo, $args{default} || '250x250' ); } return $photo; @@ -235,18 +240,37 @@ sub delete_cached { ); } +sub remove_images { + my ($self, $ids) = @_; + + my @images = $self->all_ids; + my $dec = 0; + for (sort { $a <=> $b } @$ids) { + splice(@images, $_ + $dec, 1); + --$dec; + } + + 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 +} + sub rotate_image { my ($self, $index, $direction) = @_; - my @images = $self->all_images; + my @images = $self->all_ids; return if $index > $#images; - my @items = map $_->[0], @images; - $items[$index] = _rotate_image( $images[$index][1], $direction ); + my $image_data = $self->get_raw_image_data($index); + $images[$index] = _rotate_image( $image_data, $direction ); my $new_set = (ref $self)->new({ - data_items => \@items, - c => $self->c, + data_items => \@images, object => $self->object, }); @@ -268,11 +292,6 @@ sub _rotate_image { } - - - -# NB: These 2 subs stolen from A::C::Photo, should be purged from there! -# # Shrinks a picture to the specified size, but keeping in proportion. sub _shrink { my ($photo, $size) = @_; |