diff options
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm | 21 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/ManifestTheme.pm | 39 | ||||
-rw-r--r-- | perllib/FixMyStreet/PhotoStorage.pm | 6 | ||||
-rw-r--r-- | t/app/controller/admin/manifesttheme.t | 103 | ||||
-rw-r--r-- | templates/web/base/admin/manifesttheme/form.html | 29 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/admin.js | 7 | ||||
-rw-r--r-- | web/cobrands/sass/_admin.scss | 5 |
7 files changed, 199 insertions, 11 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm index d2eed4839..66e22b483 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm @@ -39,7 +39,14 @@ sub item :PathPart('admin/manifesttheme') :Chained :CaptureArgs(1) { sub edit :PathPart('') :Chained('item') :Args(0) { my ($self, $c) = @_; - return $self->form($c, $c->stash->{obj}); + + 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->forward('/offline/_stash_manifest_icons', [ $c->stash->{obj}->cobrand, 1 ]); + + return $form; } @@ -59,6 +66,7 @@ sub form { my ($self, $c, $theme) = @_; if ($c->get_param('delete_theme')) { + $c->forward('_delete_all_manifest_icons'); $theme->delete; $c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', 'delete' ]); $c->response->redirect($c->uri_for($self->action_for('index'))); @@ -68,13 +76,22 @@ sub form { 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); - $form->process(item => $theme, params => $c->req->params); + 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->response->redirect($c->uri_for($self->action_for('index'))); } +sub _delete_all_manifest_icons :Private { + my ($self, $c) = @_; + $c->forward('/offline/_stash_manifest_icons', [ $c->stash->{obj}->cobrand, 1 ]); + foreach my $icon ( @{ $c->stash->{manifest_icons} } ) { + unlink FixMyStreet->path_to('web', $icon->{src}); + } +} 1; diff --git a/perllib/FixMyStreet/App/Form/ManifestTheme.pm b/perllib/FixMyStreet/App/Form/ManifestTheme.pm index 17e43ff58..7f3b629c0 100644 --- a/perllib/FixMyStreet/App/Form/ManifestTheme.pm +++ b/perllib/FixMyStreet/App/Form/ManifestTheme.pm @@ -1,5 +1,9 @@ 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; extends 'HTML::FormHandler::Model::DBIC'; @@ -15,6 +19,8 @@ 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' ); before 'update_model' => sub { my $self = shift; @@ -23,6 +29,39 @@ before 'update_model' => sub { sub _build_language_handle { FixMyStreet::App::Form::I18N->new } +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/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 (); } } diff --git a/t/app/controller/admin/manifesttheme.t b/t/app/controller/admin/manifesttheme.t index ba798f64f..11802cffc 100644 --- a/t/app/controller/admin/manifesttheme.t +++ b/t/app/controller/admin/manifesttheme.t @@ -1,5 +1,6 @@ -use FixMyStreet::TestMech; +use Path::Tiny; use FixMyStreet::DB; +use FixMyStreet::TestMech; my $mech = FixMyStreet::TestMech->new; @@ -72,21 +73,92 @@ subtest "cobrand admin lets you create a new theme" => sub { is $log->action, "add"; is $log->object_summary, "lincolnshire"; is $log->link, "/admin/manifesttheme/lincolnshire"; +}; - $fields = { +subtest "cobrand admin lets you update an existing theme" => sub { + $mech->get_ok("/admin/manifesttheme/lincolnshire"); + + my $fields = { background_colour => "#663399", theme_colour => "rgb(102, 51, 153)", }; $mech->submit_form_ok( { with_fields => $fields } ); - $theme->discard_changes; + + my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' }); is $theme->background_colour, "#663399"; is $theme->theme_colour, "rgb(102, 51, 153)"; - $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first; + my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first; is $log->object_id, $theme->id; is $log->action, "edit"; }; +subtest "cobrand admin lets you add an icon to an existing theme" => sub { + $mech->get_ok("/admin/manifesttheme/lincolnshire"); + + my $sample_jpeg = path(__FILE__)->parent->parent->child("sample.jpg"); + ok $sample_jpeg->exists, "sample image $sample_jpeg exists"; + my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg'; + + $mech->post( '/admin/manifesttheme/lincolnshire', + Content_Type => 'form-data', + Content => { + name => "Lincolnshire FixMyStreet", + short_name => "Lincs FMS", + background_colour => "#663399", + theme_colour => "rgb(102, 51, 153)", + icon => [ $sample_jpeg, undef, Content_Type => 'image/jpeg' ], + }, + ); + ok $mech->success, 'Posted request successfully'; + + is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page"; + $mech->content_contains($icon_filename); + $mech->content_contains("133x100"); + my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename)); + ok $icon_dest->exists, "Icon stored on disk"; +}; + +subtest "cobrand admin lets you delete an icon from an existing theme" => sub { + my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg'; + my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename)); + ok $icon_dest->exists, "Icon exists on disk"; + + $mech->get_ok("/admin/manifesttheme/lincolnshire"); + my $fields = { + delete_icon => "/theme/lincolnshire/$icon_filename", + }; + $mech->submit_form_ok( { with_fields => $fields } ); + + is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page"; + $mech->content_lacks($icon_filename); + $mech->content_lacks("133x100"); + ok !$icon_dest->exists, "Icon removed from disk"; +}; + +subtest "cobrand admin rejects non-images" => sub { + $mech->get_ok("/admin/manifesttheme/lincolnshire"); + + my $sample_pdf = path(__FILE__)->parent->parent->child("sample.pdf"); + ok $sample_pdf->exists, "sample image $sample_pdf exists"; + + $mech->post( '/admin/manifesttheme/lincolnshire', + Content_Type => 'form-data', + Content => { + name => "Lincolnshire FixMyStreet", + short_name => "Lincs FMS", + background_colour => "#663399", + theme_colour => "rgb(102, 51, 153)", + icon => [ $sample_pdf, undef, Content_Type => 'application/pdf' ], + }, + ); + ok $mech->success, 'Posted request successfully'; + + is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page"; + $mech->content_lacks("90f7a64043fb458d58de1a0703a6355e2856b15e.pdf"); + $mech->content_contains("File type not recognised. Please upload an image."); +}; + subtest "theme link on cobrand admin goes to edit form when theme exists" => sub { is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" ); @@ -110,13 +182,34 @@ subtest "create page on cobrand admin redirects to edit form when theme exists" subtest "can delete theme" => sub { is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" ); - $mech->get_ok("/admin/manifesttheme/lincolnshire"); my $theme_id = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' })->id; + # Add an icon so we can test it gets deleted when the theme is deleted + my $sample_jpeg = path(__FILE__)->parent->parent->child("sample.jpg"); + ok $sample_jpeg->exists, "sample image $sample_jpeg exists"; + my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg'; + + $mech->post( '/admin/manifesttheme/lincolnshire', + Content_Type => 'form-data', + Content => { + name => "Lincolnshire FixMyStreet", + short_name => "Lincs FMS", + background_colour => "#663399", + theme_colour => "rgb(102, 51, 153)", + icon => [ $sample_jpeg, undef, Content_Type => 'image/jpeg' ], + }, + ); + ok $mech->success, 'Posted request successfully'; + + is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page"; + my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename)); + ok $icon_dest->exists, "Icon stored on disk"; + $mech->submit_form_ok({ button => 'delete_theme' }); is $mech->uri->path, '/admin/manifesttheme/create', "redirected to create page"; is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme deleted" ); + ok !$icon_dest->exists, "Icon removed from disk"; my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first; is $log->object_id, $theme_id; diff --git a/templates/web/base/admin/manifesttheme/form.html b/templates/web/base/admin/manifesttheme/form.html index 72a703ab2..13ed60e04 100644 --- a/templates/web/base/admin/manifesttheme/form.html +++ b/templates/web/base/admin/manifesttheme/form.html @@ -1,6 +1,6 @@ [% INCLUDE 'admin/header.html' title=loc('Theme') -%] -<form method="post"> +<form method="post" enctype="multipart/form-data"> <div class="admin-hint"> <p>[% loc("The <strong>name</strong> is a string that represents the name of the web application as it is usually displayed to the user (e.g., amongst a list of other applications, or as a label for an icon).") %]</p> </div> @@ -25,6 +25,33 @@ [% form.field('cobrand').render | safe %] [% END %] + <table> + <thead> + <tr> + <th>Icon</th> + <th>Size</th> + <th>Delete?</th> + </tr> + </thead> + <tbody> + [% FOREACH icon IN manifest_icons %] + <tr> + <td><img src="[% icon.src %]" /></td> + <td class="icon-size">[% icon.sizes %]</td> + <td><input type=checkbox name=delete_icon value='[% icon.src %]' /></td> + </tr> + [% END %] + <tr> + <td colspan=3> + <div class="admin-hint"> + <p>[% loc("The <strong>icons</strong> are used when the application is installed to the user's home screen. Icons must be <strong>square</strong>, with <strong>512x512</strong>px and <strong>192x192</strong>px being the most common sizes.") %]</p> + </div> + [% form.field('icon').render | safe %] + </td> + </tr> + </tbody> + </table> + <p> <input class="btn" type="submit" name="submit" value="[% loc('Save changes') %]"> </p> diff --git a/web/cobrands/fixmystreet/admin.js b/web/cobrands/fixmystreet/admin.js index 8210f002f..52f4dd292 100644 --- a/web/cobrands/fixmystreet/admin.js +++ b/web/cobrands/fixmystreet/admin.js @@ -190,5 +190,12 @@ $(function(){ $('.js-metadata-items').on('click', '.js-metadata-option-remove', function(){ $(this).parents('.js-metadata-option').remove(); }); + + // On the manifest theme editing page we have tickboxes for deleting individual + // icons - ticking one of these should grey out that row to indicate it will be + // deleted upon form submission. + $("input[name=delete_icon]").change(function() { + $(this).closest("tr").toggleClass("is-deleted", this.checked); + }); }); diff --git a/web/cobrands/sass/_admin.scss b/web/cobrands/sass/_admin.scss index b1f914ca8..4ae019776 100644 --- a/web/cobrands/sass/_admin.scss +++ b/web/cobrands/sass/_admin.scss @@ -61,7 +61,10 @@ $button_bg_col: #a1a1a1; // also search bar (tables) } tr.is-deleted { background-color: #ffdddd; - td.contact-category { + img { + filter: grayscale(1); + } + td.contact-category, td.icon-size { text-decoration: line-through; } } |