aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm99
-rw-r--r--perllib/FixMyStreet/App/Controller/Location.pm20
-rw-r--r--perllib/FixMyStreet/App/Controller/Offline.pm106
-rw-r--r--perllib/FixMyStreet/App/Controller/Root.pm2
-rw-r--r--perllib/FixMyStreet/App/Form/ManifestTheme.pm68
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm1
-rw-r--r--perllib/FixMyStreet/DB/Result/AdminLog.pm6
-rw-r--r--perllib/FixMyStreet/DB/Result/Comment.pm1
-rw-r--r--perllib/FixMyStreet/Geocode/Bexley.pm2
-rw-r--r--perllib/FixMyStreet/Geocode/Bing.pm1
-rw-r--r--perllib/FixMyStreet/Geocode/Google.pm1
-rw-r--r--perllib/FixMyStreet/Geocode/OSM.pm1
-rw-r--r--perllib/FixMyStreet/Geocode/Zurich.pm1
-rw-r--r--perllib/FixMyStreet/PhotoStorage.pm6
14 files changed, 278 insertions, 37 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm
new file mode 100644
index 000000000..9e3bdc33e
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm
@@ -0,0 +1,99 @@
+package FixMyStreet::App::Controller::Admin::ManifestTheme;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+use FixMyStreet::App::Form::ManifestTheme;
+
+sub auto :Private {
+ my ($self, $c) = @_;
+
+ if ( $c->cobrand->moniker eq 'fixmystreet' ) {
+ $c->stash(rs => $c->model('DB::ManifestTheme')->search_rs({}), show_all => 1);
+ } else {
+ $c->stash(rs => $c->model('DB::ManifestTheme')->search_rs({ cobrand => $c->cobrand->moniker }));
+ }
+}
+
+sub index :Path :Args(0) {
+ my ( $self, $c ) = @_;
+
+ unless ( $c->stash->{show_all} ) {
+ if ( $c->stash->{rs}->count ) {
+ $c->res->redirect($c->uri_for($self->action_for('edit'), [ $c->stash->{rs}->first->cobrand ]));
+ } else {
+ $c->res->redirect($c->uri_for($self->action_for('create')));
+ }
+ $c->detach;
+ }
+}
+
+sub item :PathPart('admin/manifesttheme') :Chained :CaptureArgs(1) {
+ my ($self, $c, $cobrand) = @_;
+
+ my $obj = $c->stash->{rs}->find({ cobrand => $cobrand })
+ or $c->detach('/page_error_404_not_found', []);
+ $c->stash(obj => $obj);
+}
+
+sub edit :PathPart('') :Chained('item') :Args(0) {
+ my ($self, $c) = @_;
+
+ my $form = $self->form($c, $c->stash->{obj});
+
+ # We need to do this after form processing, in case a form POST has deleted
+ # an icon.
+ $c->stash->{editing_manifest_theme} = $c->forward('/offline/_find_manifest_theme', [ $c->stash->{obj}->cobrand, 1 ]);
+
+ return $form;
+}
+
+
+sub create :Local :Args(0) {
+ my ($self, $c) = @_;
+
+ unless ( $c->stash->{show_all} || $c->stash->{rs}->count == 0) {
+ $c->res->redirect($c->uri_for($self->action_for('edit'), [ $c->stash->{rs}->first->cobrand ]));
+ $c->detach;
+ }
+
+ my $theme = $c->stash->{rs}->new_result({});
+ return $self->form($c, $theme);
+}
+
+sub form {
+ my ($self, $c, $theme) = @_;
+
+ if ($c->get_param('delete_theme')) {
+ $c->forward('_delete_all_manifest_icons');
+ $c->forward('/offline/_clear_manifest_theme_cache', [ $theme->cobrand ]);
+ $theme->delete;
+ $c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', 'delete' ]);
+ $c->response->redirect($c->uri_for($self->action_for('index')));
+ $c->detach;
+ }
+
+ my $action = $theme->in_storage ? 'edit' : 'add';
+ my $form = FixMyStreet::App::Form::ManifestTheme->new( cobrand => $c->cobrand->moniker );
+ $c->stash(template => 'admin/manifesttheme/form.html', form => $form);
+ my $params = $c->req->params;
+ $params->{icon} = $c->req->upload('icon') if $params->{icon};
+ $form->process(item => $theme, params => $params);
+ return unless $form->validated;
+
+ $c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', $action ]);
+ $c->forward('/offline/_clear_manifest_theme_cache', [ $theme->cobrand ]);
+ $c->response->redirect($c->uri_for($self->action_for('index')));
+}
+
+sub _delete_all_manifest_icons :Private {
+ my ($self, $c) = @_;
+
+ my $theme = $c->forward('/offline/_find_manifest_theme', [ $c->stash->{obj}->cobrand, 1 ]);
+ foreach my $icon ( @{ $theme->{icons} } ) {
+ unlink FixMyStreet->path_to('web', $icon->{src});
+ }
+}
+
+1;
diff --git a/perllib/FixMyStreet/App/Controller/Location.pm b/perllib/FixMyStreet/App/Controller/Location.pm
index 8d5b0b147..81c2c33fc 100644
--- a/perllib/FixMyStreet/App/Controller/Location.pm
+++ b/perllib/FixMyStreet/App/Controller/Location.pm
@@ -6,6 +6,7 @@ BEGIN {extends 'Catalyst::Controller'; }
use Encode;
use FixMyStreet::Geocode;
+use Try::Tiny;
use Utils;
=head1 NAME
@@ -107,6 +108,25 @@ sub determine_location_from_pc : Private {
# pass errors back to the template
$c->stash->{location_error_pc_lookup} = 1;
$c->stash->{location_error} = $error;
+
+ # Log failure in a log db
+ try {
+ my $dbfile = FixMyStreet->path_to('../data/analytics.sqlite');
+ my $db = DBI->connect("dbi:SQLite:dbname=$dbfile", undef, undef) or die "$DBI::errstr\n";
+ my $sth = $db->prepare("INSERT INTO location_searches_with_no_results
+ (datetime, cobrand, geocoder, url, user_input)
+ VALUES (?, ?, ?, ?, ?)") or die $db->errstr . "\n";
+ my $rv = $sth->execute(
+ POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime(time())),
+ $c->cobrand->moniker,
+ $c->cobrand->get_geocoder(),
+ $c->stash->{geocoder_url},
+ $pc,
+ );
+ } catch {
+ $c->log->debug("Unable to log to analytics.sqlite: $_");
+ };
+
return;
}
diff --git a/perllib/FixMyStreet/App/Controller/Offline.pm b/perllib/FixMyStreet/App/Controller/Offline.pm
index 57cbe201c..adb3de14d 100644
--- a/perllib/FixMyStreet/App/Controller/Offline.pm
+++ b/perllib/FixMyStreet/App/Controller/Offline.pm
@@ -33,42 +33,12 @@ sub manifest: Path("/.well-known/manifest.webmanifest") {
my ($self, $c) = @_;
$c->res->content_type('application/manifest+json');
- my $theme = $c->model('DB::ManifestTheme')->find({ cobrand => $c->cobrand->moniker });
- unless ( $theme ) {
- $theme = $c->model('DB::ManifestTheme')->new({
- name => $c->stash->{site_name},
- short_name => $c->stash->{site_name},
- background_colour => '#ffffff',
- theme_colour => '#ffd000',
- });
- }
-
- my @icons;
- my $uri = '/theme/' . $c->cobrand->moniker;
- my $theme_path = path(FixMyStreet->path_to('web' . $uri));
- $theme_path->visit(
- sub {
- my ($x, $y, $typ) = Image::Size::imgsize($_->stringify);
- push @icons, {
- src => join('/', $uri, $_->basename),
- sizes => join('x', $x, $y),
- type => $typ eq 'PNG' ? 'image/png' : $typ eq 'GIF' ? 'image/gif' : $typ eq 'JPG' ? 'image/jpeg' : '',
- };
- }
- );
-
- unless (@icons) {
- push @icons,
- { src => "/cobrands/fixmystreet/images/192.png", sizes => "192x192", type => "image/png" },
- { src => "/cobrands/fixmystreet/images/512.png", sizes => "512x512", type => "image/png" };
- }
-
my $data = {
- name => $theme->name,
- short_name => $theme->short_name,
- background_color => $theme->background_colour,
- theme_color => $theme->theme_colour,
- icons => \@icons,
+ name => $c->stash->{manifest_theme}->{name},
+ short_name => $c->stash->{manifest_theme}->{short_name},
+ background_color => $c->stash->{manifest_theme}->{background_colour},
+ theme_color => $c->stash->{manifest_theme}->{theme_colour},
+ icons => $c->stash->{manifest_theme}->{icons},
lang => $c->stash->{lang_code},
display => "minimal-ui",
start_url => "/?pwa",
@@ -82,6 +52,72 @@ sub manifest: Path("/.well-known/manifest.webmanifest") {
$c->res->body($json);
}
+sub _stash_manifest_theme : Private {
+ my ($self, $c, $cobrand) = @_;
+
+ $c->stash->{manifest_theme} = $c->forward('_find_manifest_theme', [ $cobrand ]);
+}
+
+sub _find_manifest_theme : Private {
+ my ($self, $c, $cobrand, $ignore_cache_and_defaults) = @_;
+
+ my $key = "manifest_theme:$cobrand";
+ # ignore_cache_and_defaults is only used in the admin, so no harm bypassing cache
+ my $manifest_theme = $ignore_cache_and_defaults ? undef : Memcached::get($key);
+
+ unless ( $manifest_theme ) {
+ my $theme = $c->model('DB::ManifestTheme')->find({ cobrand => $cobrand });
+ unless ( $theme ) {
+ $theme = $c->model('DB::ManifestTheme')->new({
+ name => $c->stash->{site_name},
+ short_name => $c->stash->{site_name},
+ background_colour => '#ffffff',
+ theme_colour => '#ffd000',
+ });
+ }
+
+ my @icons;
+ my $uri = '/theme/' . $cobrand;
+ my $theme_path = path(FixMyStreet->path_to('web' . $uri));
+ $theme_path->visit(
+ sub {
+ my ($x, $y, $typ) = Image::Size::imgsize($_->stringify);
+ push @icons, {
+ src => join('/', $uri, $_->basename),
+ sizes => join('x', $x, $y),
+ type => $typ eq 'PNG' ? 'image/png' : $typ eq 'GIF' ? 'image/gif' : $typ eq 'JPG' ? 'image/jpeg' : '',
+ };
+ }
+ );
+
+ unless (@icons || $ignore_cache_and_defaults) {
+ push @icons,
+ { src => "/cobrands/fixmystreet/images/192.png", sizes => "192x192", type => "image/png" },
+ { src => "/cobrands/fixmystreet/images/512.png", sizes => "512x512", type => "image/png" };
+ }
+
+ $manifest_theme = {
+ icons => \@icons,
+ background_colour => $theme->background_colour,
+ theme_colour => $theme->theme_colour,
+ name => $theme->name,
+ short_name => $theme->short_name,
+ };
+
+ unless ($ignore_cache_and_defaults) {
+ Memcached::set($key, $manifest_theme);
+ }
+ }
+
+ return $manifest_theme;
+}
+
+sub _clear_manifest_theme_cache : Private {
+ my ($self, $c, $cobrand ) = @_;
+
+ Memcached::delete("manifest_theme:$cobrand");
+}
+
__PACKAGE__->meta->make_immutable;
1;
diff --git a/perllib/FixMyStreet/App/Controller/Root.pm b/perllib/FixMyStreet/App/Controller/Root.pm
index caaa260ff..71dcf8e27 100644
--- a/perllib/FixMyStreet/App/Controller/Root.pm
+++ b/perllib/FixMyStreet/App/Controller/Root.pm
@@ -42,6 +42,8 @@ sub auto : Private {
$c->forward('check_password_expiry');
$c->detach('/auth/redirect') if $c->cobrand->call_hook('check_login_disallowed');
+ $c->forward('/offline/_stash_manifest_theme', [ $c->cobrand->moniker ]);
+
return 1;
}
diff --git a/perllib/FixMyStreet/App/Form/ManifestTheme.pm b/perllib/FixMyStreet/App/Form/ManifestTheme.pm
new file mode 100644
index 000000000..aa2d467d6
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/ManifestTheme.pm
@@ -0,0 +1,68 @@
+package FixMyStreet::App::Form::ManifestTheme;
+
+use Path::Tiny;
+use File::Copy;
+use Digest::SHA qw(sha1_hex);
+use File::Basename;
+use HTML::FormHandler::Moose;
+use FixMyStreet::App::Form::I18N;
+use List::MoreUtils qw(uniq);
+extends 'HTML::FormHandler::Model::DBIC';
+use namespace::autoclean;
+
+has 'cobrand' => ( isa => 'Str', is => 'ro' );
+
+has '+widget_name_space' => ( default => sub { ['FixMyStreet::App::Form::Widget'] } );
+has '+widget_tags' => ( default => sub { { wrapper_tag => 'p' } } );
+has '+item_class' => ( default => 'ManifestTheme' );
+has_field 'cobrand' => ( type => 'Select', empty_select => 'Select a cobrand', required => 1 );
+has_field 'name' => ( required => 1 );
+has_field 'short_name' => ( required => 1 );
+has_field 'background_colour' => ( required => 0 );
+has_field 'theme_colour' => ( required => 0 );
+has_field 'icon' => ( required => 0, type => 'Upload', label => "Add icon" );
+has_field 'delete_icon' => ( type => 'Multiple' );
+
+sub _build_language_handle { FixMyStreet::App::Form::I18N->new }
+
+sub options_cobrand {
+ my @cobrands = uniq sort map { $_->{moniker} } FixMyStreet::Cobrand->available_cobrand_classes;
+ return map { $_ => $_ } @cobrands;
+}
+
+sub validate {
+ my $self = shift;
+
+ my $value = $self->value;
+ my $cobrand = $value->{cobrand} || $self->cobrand;
+ my $upload = $value->{icon};
+
+ if ( $upload ) {
+ if( $upload->type !~ /^image/ ) {
+ $self->field('icon')->add_error( _("File type not recognised. Please upload an image.") );
+ return;
+ }
+
+ my $uri = '/theme/' . $cobrand;
+ my $theme_path = path(FixMyStreet->path_to('web' . $uri));
+ $theme_path->mkpath;
+ FixMyStreet::PhotoStorage::base64_decode_upload(undef, $upload);
+ my ($p, $n, $ext) = fileparse($upload->filename, qr/\.[^.]*/);
+ my $key = sha1_hex($upload->slurp) . $ext;
+ my $out = path($theme_path, $key);
+ unless (copy($upload->tempname, $out)) {
+ $self->field('icon')->add_error( _("Sorry, we couldn't save your file(s), please try again.") );
+ return;
+ }
+ }
+
+ foreach my $delete_icon ( @{ $value->{delete_icon} } ) {
+ unlink FixMyStreet->path_to('web', $delete_icon);
+ }
+
+ return 1;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 9851b4896..695487268 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -685,6 +685,7 @@ sub admin_pages {
$pages->{flagged} = [ _('Flagged'), 7 ];
$pages->{states} = [ _('States'), 8 ];
$pages->{config} = [ _('Configuration'), 9];
+ $pages->{manifesttheme} = [ _('Manifest Theme'), 11];
$pages->{user_import} = [ undef, undef ];
};
# And some that need special permissions
diff --git a/perllib/FixMyStreet/DB/Result/AdminLog.pm b/perllib/FixMyStreet/DB/Result/AdminLog.pm
index 5564d829a..4c89138c9 100644
--- a/perllib/FixMyStreet/DB/Result/AdminLog.pm
+++ b/perllib/FixMyStreet/DB/Result/AdminLog.pm
@@ -91,6 +91,10 @@ sub link {
my $category = $self->object;
return "/admin/body/" . $category->body_id . '/' . $category->category;
}
+ if ($type eq 'manifesttheme') {
+ my $theme = $self->object;
+ return "/admin/manifesttheme/" . $theme->cobrand;
+ }
return '';
}
@@ -114,6 +118,7 @@ sub object_summary {
role => 'name',
template => 'title',
category => 'category',
+ manifesttheme => 'cobrand',
};
my $thing = $type_to_thing->{$self->object_type} || 'id';
@@ -130,6 +135,7 @@ sub object {
template => 'ResponseTemplate',
category => 'Contact',
update => 'Comment',
+ manifesttheme => 'ManifestTheme',
};
$type = $type_to_object->{$type} || ucfirst $type;
my $object = $self->result_source->schema->resultset($type)->find($id);
diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm
index bac183271..b217bf96c 100644
--- a/perllib/FixMyStreet/DB/Result/Comment.pm
+++ b/perllib/FixMyStreet/DB/Result/Comment.pm
@@ -292,6 +292,7 @@ sub is_latest {
{ problem_id => $self->problem_id, state => 'confirmed' },
{ order_by => [ { -desc => 'confirmed' }, { -desc => 'id' } ] }
)->first;
+ return unless $latest_update;
return $latest_update->id == $self->id;
}
diff --git a/perllib/FixMyStreet/Geocode/Bexley.pm b/perllib/FixMyStreet/Geocode/Bexley.pm
index a70a42cd1..8a1a886bb 100644
--- a/perllib/FixMyStreet/Geocode/Bexley.pm
+++ b/perllib/FixMyStreet/Geocode/Bexley.pm
@@ -23,6 +23,8 @@ sub string {
my $js = query_layer($s);
return $osm unless $js && @{$js->{features}};
+ $c->stash->{geocoder_url} = $s;
+
my ( $error, @valid_locations, $latitude, $longitude, $address );
foreach (sort { $a->{properties}{ADDRESS} cmp $b->{properties}{ADDRESS} } @{$js->{features}}) {
my @lines = @{$_->{geometry}{coordinates}};
diff --git a/perllib/FixMyStreet/Geocode/Bing.pm b/perllib/FixMyStreet/Geocode/Bing.pm
index ee5e15f8c..1d39d911f 100644
--- a/perllib/FixMyStreet/Geocode/Bing.pm
+++ b/perllib/FixMyStreet/Geocode/Bing.pm
@@ -38,6 +38,7 @@ sub string {
$url .= '&userLocation=' . $params->{centre} if $params->{centre};
$url .= '&c=' . $params->{bing_culture} if $params->{bing_culture};
+ $c->stash->{geocoder_url} = $url;
my $js = FixMyStreet::Geocode::cache('bing', $url, 'key=' . FixMyStreet->config('BING_MAPS_API_KEY'));
if (!$js) {
return { error => _('Sorry, we could not parse that location. Please try again.') };
diff --git a/perllib/FixMyStreet/Geocode/Google.pm b/perllib/FixMyStreet/Geocode/Google.pm
index 455d9cec0..ffbad96ba 100644
--- a/perllib/FixMyStreet/Geocode/Google.pm
+++ b/perllib/FixMyStreet/Geocode/Google.pm
@@ -49,6 +49,7 @@ sub string {
$url .= '&components=' . $components if $components;
+ $c->stash->{geocoder_url} = $url;
my $args = 'key=' . FixMyStreet->config('GOOGLE_MAPS_API_KEY');
my $js = FixMyStreet::Geocode::cache('google', $url, $args, qr/"status"\s*:\s*"(OVER_QUERY_LIMIT|REQUEST_DENIED|INVALID_REQUEST|UNKNOWN_ERROR)"/);
if (!$js) {
diff --git a/perllib/FixMyStreet/Geocode/OSM.pm b/perllib/FixMyStreet/Geocode/OSM.pm
index b979e2a10..20e653cf6 100644
--- a/perllib/FixMyStreet/Geocode/OSM.pm
+++ b/perllib/FixMyStreet/Geocode/OSM.pm
@@ -47,6 +47,7 @@ sub string {
if $params->{country};
$url .= join('&', map { "$_=$query_params{$_}" } sort keys %query_params);
+ $c->stash->{geocoder_url} = $url;
my $js = FixMyStreet::Geocode::cache('osm', $url);
if (!$js) {
return { error => _('Sorry, we could not find that location.') };
diff --git a/perllib/FixMyStreet/Geocode/Zurich.pm b/perllib/FixMyStreet/Geocode/Zurich.pm
index 0b85ab7b2..b0c0b528e 100644
--- a/perllib/FixMyStreet/Geocode/Zurich.pm
+++ b/perllib/FixMyStreet/Geocode/Zurich.pm
@@ -97,6 +97,7 @@ sub string {
my $cache_dir = path(FixMyStreet->config('GEO_CACHE'), 'zurich')->absolute(FixMyStreet->path_to());
my $cache_file = $cache_dir->child(md5_hex($s));
my $result;
+ $c->stash->{geocoder_url} = $s;
if (-s $cache_file && -M $cache_file <= 7 && !FixMyStreet->config('STAGING_SITE')) {
$result = retrieve($cache_file);
} else {
diff --git a/perllib/FixMyStreet/PhotoStorage.pm b/perllib/FixMyStreet/PhotoStorage.pm
index 9b0e5c9c3..256d46361 100644
--- a/perllib/FixMyStreet/PhotoStorage.pm
+++ b/perllib/FixMyStreet/PhotoStorage.pm
@@ -58,8 +58,10 @@ sub base64_decode_upload {
print $fh $decoded;
close $fh
} else {
- $c->log->info('Couldn\'t open temp file to save base64 decoded image: ' . $!);
- $c->stash->{photo_error} = _("Sorry, we couldn't save your file(s), please try again.");
+ if ($c) {
+ $c->log->info('Couldn\'t open temp file to save base64 decoded image: ' . $!);
+ $c->stash->{photo_error} = _("Sorry, we couldn't save your file(s), please try again.");
+ }
return ();
}
}