diff options
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Around.pm | 5 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report/New.pm | 14 | ||||
-rw-r--r-- | t/app/controller/report_new.t | 149 | ||||
-rw-r--r-- | templates/web/base/maps/openlayers.html | 3 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/assets.js | 96 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/fixmystreet.js | 3 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/map.js | 5 | ||||
-rw-r--r-- | web/js/map-OpenLayers.js | 42 |
8 files changed, 260 insertions, 57 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm index a2d7ed025..db2eceda3 100644 --- a/perllib/FixMyStreet/App/Controller/Around.pm +++ b/perllib/FixMyStreet/App/Controller/Around.pm @@ -9,6 +9,7 @@ use Encode; use JSON::MaybeXS; use Utils; use Try::Tiny; +use Text::CSV; =head1 NAME @@ -230,6 +231,10 @@ sub check_and_stash_category : Private { my $all_areas = $c->stash->{all_areas}; my @bodies = $c->model('DB::Body')->active->for_areas(keys %$all_areas)->all; my %bodies = map { $_->id => $_ } @bodies; + my @list_of_names = map { $_->name } values %bodies; + my $csv = Text::CSV->new(); + $csv->combine(@list_of_names); + $c->{stash}->{list_of_names_as_string} = $csv->string; my @categories = $c->model('DB::Contact')->not_deleted->search( { diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 9172de5b6..5d24bc980 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -208,6 +208,7 @@ sub report_form_ajax : Path('ajax') : Args(0) { my $extra_titles_list = $c->cobrand->title_list($c->stash->{all_areas}); + my @list_of_names = map { $_->name } values %{$c->stash->{bodies}}; my $contribute_as = {}; if ($c->user_exists) { my @bodies = keys %{$c->stash->{bodies}}; @@ -221,6 +222,7 @@ sub report_form_ajax : Path('ajax') : Args(0) { my $body = encode_json( { + bodies => \@list_of_names, councils_text => $councils_text, councils_text_private => $councils_text_private, category => $category, @@ -254,8 +256,10 @@ sub category_extras_ajax : Path('category_extras') : Args(0) { $category = '' if $category eq _('-- Pick a category --'); my $bodies = $c->forward('contacts_to_bodies', [ $category ]); + + my $list_of_names = [ map { $_->name } ($category ? @$bodies : values %{$c->stash->{bodies_to_list}}) ]; my $vars = { - $category ? (list_of_names => [ map { $_->name } @$bodies ]) : (), + $category ? (list_of_names => $list_of_names) : (), }; my $category_extra = ''; @@ -281,12 +285,14 @@ sub category_extras_ajax : Path('category_extras') : Args(0) { my $councils_text_private = $c->render_fragment( 'report/new/councils_text_private.html'); $unresponsive = $c->stash->{unresponsive}->{$category} || $c->stash->{unresponsive}->{ALL} || ''; + my $body = encode_json({ category_extra => $category_extra, councils_text => $councils_text, councils_text_private => $councils_text_private, category_extra_json => $category_extra_json, unresponsive => $unresponsive, + bodies => $list_of_names, }); $c->res->content_type('application/json; charset=utf-8'); @@ -707,6 +713,12 @@ sub setup_categories_and_bodies : Private { $c->stash->{non_public_categories} = \%non_public_categories; $c->stash->{extra_name_info} = $first_area->{id} == COUNCIL_ID_BROMLEY ? 1 : 0; + # escape these so we can then split on , cleanly in the template. + my @list_of_names = map { $_->name } values %bodies_to_list; + my $csv = Text::CSV->new(); + $csv->combine(@list_of_names); + $c->stash->{list_of_names_as_string} = $csv->string; + my @missing_details_bodies = grep { !$bodies_to_list{$_->id} } values %bodies; my @missing_details_body_names = map { $_->name } @missing_details_bodies; diff --git a/t/app/controller/report_new.t b/t/app/controller/report_new.t index 4f229dc8c..8c4deec56 100644 --- a/t/app/controller/report_new.t +++ b/t/app/controller/report_new.t @@ -40,6 +40,7 @@ for my $body ( { area_id => 2482, name => 'Bromley Council' }, { area_id => 2227, name => 'Hampshire County Council' }, { area_id => 2333, name => 'Hart Council' }, + { area_id => 2535, name => 'Sandwell Borough Council' }, ) { my $body_obj = $mech->create_body_ok($body->{area_id}, $body->{name}); push @bodies, $body_obj; @@ -1177,24 +1178,48 @@ subtest "test report creation for a category that is non public" => sub { $contact2->category( "Pothol\xc3\xa9s" ); $contact2->update; -my $extra_details; -FixMyStreet::override_config { - ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], - MAPIT_URL => 'http://mapit.uk/', -}, sub { - $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=' . $saved_lat . '&longitude=' . $saved_lon ); -}; -$mech->content_contains( "Pothol\xc3\xa9s" ); -like $extra_details->{councils_text}, qr/<strong>Cheltenham/; -ok !$extra_details->{titles_list}, 'Non Bromley does not send back list of titles'; - -FixMyStreet::override_config { - ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], - MAPIT_URL => 'http://mapit.uk/', -}, sub { - $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.4021&longitude=0.01578'); +subtest "check map click ajax response" => sub { + my $extra_details; + FixMyStreet::override_config { + ALLOWED_COBRANDS => 'fixmystreet', + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=' . $saved_lat . '&longitude=' . $saved_lon ); + }; + # this order seems to be random so check individually/sort + like $extra_details->{councils_text}, qr/Cheltenham Borough Council/, 'correct council text for two tier'; + like $extra_details->{councils_text}, qr/Gloucestershire County Council/, 'correct council text for two tier'; + like $extra_details->{category}, qr/Pothol\x{00E9}s.*Street lighting/, 'category looks correct for two tier council'; + my @sorted_bodies = sort @{ $extra_details->{bodies} }; + is_deeply \@sorted_bodies, [ "Cheltenham Borough Council", "Gloucestershire County Council" ], 'correct bodies for two tier'; + ok !$extra_details->{titles_list}, 'Non Bromley does not send back list of titles'; + + FixMyStreet::override_config { + ALLOWED_COBRANDS => 'fixmystreet', + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.4021&longitude=0.01578'); + }; + ok $extra_details->{titles_list}, 'Bromley sends back list of titles'; + like $extra_details->{councils_text}, qr/Bromley Council/, 'correct council text'; + like $extra_details->{councils_text_private}, qr/^These will be sent to the council, but will never be shown online/, 'correct private council text'; + like $extra_details->{category}, qr/Trees/, 'category looks correct'; + is_deeply $extra_details->{bodies}, [ "Bromley Council" ], 'correct bodies'; + ok !$extra_details->{contribute_as}, 'no contribute as section'; + ok !$extra_details->{top_message}, 'no top message'; + ok $extra_details->{extra_name_info}, 'extra name info'; + + FixMyStreet::override_config { + ALLOWED_COBRANDS => 'fixmystreet', + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=52.563074&longitude=-1.991032' ); + }; + like $extra_details->{councils_text}, qr/^These will be published online for others to see/, 'correct council text for council with no contacts'; + is $extra_details->{category}, '', 'category is empty for council with no contacts'; + is_deeply $extra_details->{bodies}, [ "Sandwell Borough Council" ], 'correct bodies for council with no contacts'; + ok !$extra_details->{extra_name_info}, 'no extra name info'; }; -ok $extra_details->{titles_list}, 'Bromley sends back list of titles'; #### test uploading an image @@ -1885,6 +1910,7 @@ subtest "extra google analytics code displayed on email confirmation problem cre }; }; +my $inspector = $mech->create_user_ok('inspector@example.org', name => 'inspector', from_body => $bodies[0]); foreach my $test ( { non_public => 0 }, { non_public => 1 }, @@ -1897,12 +1923,11 @@ foreach my $test ( }, sub { $mech->log_out_ok; - my $user = $mech->create_user_ok('inspector@example.org', name => 'inspector', from_body => $bodies[0]); - $user->user_body_permissions->find_or_create({ + $inspector->user_body_permissions->find_or_create({ body => $bodies[0], permission_type => 'planned_reports', }); - $user->user_body_permissions->find_or_create({ + $inspector->user_body_permissions->find_or_create({ body => $bodies[0], permission_type => 'report_inspect', }); @@ -1937,4 +1962,88 @@ foreach my $test ( }; } +subtest "check map click ajax response for inspector" => sub { + $mech->log_out_ok; + + my $extra_details; + $inspector->user_body_permissions->find_or_create({ + body => $bodies[0], + permission_type => 'planned_reports', + }); + $inspector->user_body_permissions->find_or_create({ + body => $bodies[0], + permission_type => 'report_inspect', + }); + + $mech->log_in_ok('inspector@example.org'); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' ); + }; + like $extra_details->{category}, qr/data-role="inspector/, 'category has correct data-role'; + ok !$extra_details->{contribute_as}, 'no contribute as section'; +}; + +for my $test ( + { + desc => 'map click ajax for contribute_as_another_user', + permissions => { + contribute_as_another_user => 1, + contribute_as_anonymous_user => undef, + contribute_as_body => undef, + } + }, + { + desc => 'map click ajax for contribute_as_anonymous_user', + permissions => { + contribute_as_another_user => undef, + contribute_as_anonymous_user => 1, + contribute_as_body => undef, + } + }, + { + desc => 'map click ajax for contribute_as_body', + permissions => { + contribute_as_another_user => undef, + contribute_as_anonymous_user => undef, + contribute_as_body => 1, + } + }, +) { + subtest $test->{desc} => sub { + $mech->log_out_ok; + my $extra_details; + (my $name = $test->{desc}) =~ s/.*(contri.*)/$1/; + my $user = $mech->create_user_ok("$name\@example.org", name => 'test user', from_body => $bodies[0]); + for my $p ( keys %{$test->{permissions}} ) { + next unless $test->{permissions}->{$p}; + $user->user_body_permissions->find_or_create({ + body => $bodies[0], + permission_type => $p, + }); + } + $mech->log_in_ok("$name\@example.org"); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' ); + }; + for my $p ( keys %{$test->{permissions}} ) { + (my $key = $p) =~ s/contribute_as_//; + is $extra_details->{contribute_as}->{$key}, $test->{permissions}->{$p}, "$key correctly set"; + } + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.754926&longitude=-1.256179' ); + }; + ok !$extra_details->{contribute_as}, 'no contribute as section for other council'; + }; +} + done_testing(); diff --git a/templates/web/base/maps/openlayers.html b/templates/web/base/maps/openlayers.html index 12ae88c05..524075371 100644 --- a/templates/web/base/maps/openlayers.html +++ b/templates/web/base/maps/openlayers.html @@ -25,6 +25,9 @@ [% IF include_key -%] data-key='[% c.config.BING_MAPS_API_KEY %]' [%- END -%] +[% IF list_of_names_as_string -%] + data-bodies='[% list_of_names_as_string | html %]' +[%- END -%] > </div> <div id="map_box" aria-hidden="true"> diff --git a/web/cobrands/fixmystreet/assets.js b/web/cobrands/fixmystreet/assets.js index 7ce12cbe3..11ea1c80e 100644 --- a/web/cobrands/fixmystreet/assets.js +++ b/web/cobrands/fixmystreet/assets.js @@ -16,12 +16,59 @@ var fixmystreet = fixmystreet || {}; }; })(); +OpenLayers.Layer.VectorAsset = OpenLayers.Class(OpenLayers.Layer.Vector, { + initialize: function(name, options) { + OpenLayers.Layer.Vector.prototype.initialize.apply(this, arguments); + $(fixmystreet).on('report_new:category_change', this.update_layer_visibility.bind(this)); + }, + + update_layer_visibility: function() { + if (!fixmystreet.map) { + return; + } + + if (!this.fixmystreet.always_visible) { + // Show/hide the asset layer when the category is chosen + var category = $('#problem_form select#form_category').val(); + if (fixmystreet.assets.check_layer_relevant(this.fixmystreet, category)) { + this.setVisibility(true); + if (this.fixmystreet.fault_layer) { + this.fixmystreet.fault_layer.setVisibility(true); + } + this.zoom_to_assets(); + } else { + this.setVisibility(false); + if (this.fixmystreet.fault_layer) { + this.fixmystreet.fault_layer.setVisibility(false); + } + } + } else { + if (this.fixmystreet.body) { + this.setVisibility(OpenLayers.Util.indexOf(fixmystreet.bodies, this.fixmystreet.body) != -1 ); + } + } + }, + + zoom_to_assets: function() { + // This function is called when the asset category is + // selected, and will zoom the map in to the first level that + // makes the asset layer visible if it's not already shown. + if (!this.inRange) { + var firstVisibleResolution = this.resolutions[0]; + var zoomLevel = fixmystreet.map.getZoomForResolution(firstVisibleResolution); + fixmystreet.map.zoomTo(zoomLevel); + } + }, + + CLASS_NAME: 'OpenLayers.Layer.VectorAsset' +}); + // Handles layers such as USRN, TfL roads, and the like -OpenLayers.Layer.VectorNearest = OpenLayers.Class(OpenLayers.Layer.Vector, { +OpenLayers.Layer.VectorNearest = OpenLayers.Class(OpenLayers.Layer.VectorAsset, { selected_feature: null, initialize: function(name, options) { - OpenLayers.Layer.Vector.prototype.initialize.apply(this, arguments); + OpenLayers.Layer.VectorAsset.prototype.initialize.apply(this, arguments); $(fixmystreet).on('maps:update_pin', this.checkFeature.bind(this)); $(fixmystreet).on('assets:selected', this.checkFeature.bind(this)); // Might only be able to fill in fields once they've been returned from the server @@ -31,10 +78,13 @@ OpenLayers.Layer.VectorNearest = OpenLayers.Class(OpenLayers.Layer.Vector, { }, checkFeature: function(evt, lonlat) { + if (!this.getVisibility()) { + return; + } this.getNearest(lonlat); this.updateUSRNField(); if (this.fixmystreet.road) { - var valid_category = this.fixmystreet.all_categories || (this.fixmystreet.asset_category && this.fixmystreet.asset_category.indexOf($('select#form_category').val()) != -1); + var valid_category = this.fixmystreet.all_categories || (this.fixmystreet.asset_category && fixmystreet.assets.check_layer_relevant( this.fixmystreet, $('select#form_category').val() ) ); if (!valid_category || !this.selected_feature) { this.road_not_found(); } else { @@ -111,6 +161,7 @@ var fault_popup = null; * Called as part of fixmystreet.assets.init for each asset layer on the map. */ function init_asset_layer(layer, pins_layer) { + layer.update_layer_visibility(); fixmystreet.map.addLayer(layer); if (layer.fixmystreet.asset_category) { fixmystreet.map.events.register( 'zoomend', layer, check_zoom_message_visibility); @@ -140,28 +191,8 @@ function init_asset_layer(layer, pins_layer) { layer.events.register( 'loadend', layer, layer.one_time_select ); } - if (!layer.fixmystreet.always_visible) { - // Show/hide the asset layer when the category is chosen - $("#problem_form").on("change.category", "select#form_category", function(){ - var category = $(this).val(); - if (layer.fixmystreet.asset_category.indexOf(category) != -1) { - layer.setVisibility(true); - if (layer.fixmystreet.fault_layer) { - layer.fixmystreet.fault_layer.setVisibility(true); - } - zoom_to_assets(layer); - } else { - layer.setVisibility(false); - if (layer.fixmystreet.fault_layer) { - layer.fixmystreet.fault_layer.setVisibility(false); - } - } - }); - } - } - function close_fault_popup() { if (!!fault_popup) { fixmystreet.map.removePopup(fault_popup); @@ -267,7 +298,7 @@ function check_zoom_message_visibility() { prefix = category.replace(/[^a-z]/gi, ''), id = "category_meta_message_" + prefix, $p = $('#' + id); - if (this.fixmystreet.asset_category.indexOf(category) != -1) { + if (fixmystreet.assets.check_layer_relevant(this.fixmystreet, category)) { if ($p.length === 0) { $p = $("<p>").prop("id", id).prop('class', 'category_meta_message'); $p.insertAfter('#form_category_row'); @@ -313,16 +344,6 @@ function layer_visibilitychanged() { } } -function zoom_to_assets(layer) { - // This function is called when the asset category is - // selected, and will zoom the map in to the first level that - // makes the asset layer visible if it's not already shown. - if (!layer.inRange) { - var firstVisibleResolution = layer.resolutions[0]; - var zoomLevel = fixmystreet.map.getZoomForResolution(firstVisibleResolution); - fixmystreet.map.zoomTo(zoomLevel); - } -} function get_select_control(layer) { var controls = fixmystreet.map.getControlsByClass('OpenLayers.Control.SelectFeature'); @@ -503,7 +524,7 @@ fixmystreet.assets = { } } - var layer_class = OpenLayers.Layer.Vector; + var layer_class = OpenLayers.Layer.VectorAsset; if (options.usrn || options.road) { layer_class = OpenLayers.Layer.VectorNearest; } @@ -631,6 +652,11 @@ fixmystreet.assets = { fixmystreet.map.addControl(fixmystreet.assets.controls[i]); fixmystreet.assets.controls[i].activate(); } + }, + + check_layer_relevant: function(layer, category) { + return OpenLayers.Util.indexOf(layer.asset_category, category) != -1 && + ( !layer.body || OpenLayers.Util.indexOf(fixmystreet.bodies, layer.body) != -1 ); } }; diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index 26b0b293a..5f9dd9699 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -409,6 +409,7 @@ $.extend(fixmystreet.set_up, { } else { $category_meta.empty(); } + fixmystreet.bodies = data.bodies || []; $(fixmystreet).trigger('report_new:category_change:extras_received'); }); @@ -940,6 +941,8 @@ fixmystreet.update_pin = function(lonlat, savePushState) { lb.before(data.extra_name_info); } + fixmystreet.bodies = data.bodies || []; + // If the category filter appears on the map and the user has selected // something from it, then pre-fill the category field in the report, // if it's a value already present in the drop-down. diff --git a/web/cobrands/fixmystreet/map.js b/web/cobrands/fixmystreet/map.js index 9303c22b7..7c3aeb55e 100644 --- a/web/cobrands/fixmystreet/map.js +++ b/web/cobrands/fixmystreet/map.js @@ -3,7 +3,7 @@ var fixmystreet = fixmystreet || {}; (function(){ var map_data = document.getElementById('js-map-data'), - map_keys = [ 'area', 'latitude', 'longitude', 'zoomToBounds', 'zoom', 'pin_prefix', 'pin_new_report_colour', 'numZoomLevels', 'zoomOffset', 'map_type', 'key' ], + map_keys = [ 'area', 'latitude', 'longitude', 'zoomToBounds', 'zoom', 'pin_prefix', 'pin_new_report_colour', 'numZoomLevels', 'zoomOffset', 'map_type', 'key', 'bodies' ], numeric = { zoom: 1, numZoomLevels: 1, zoomOffset: 1, id: 1 }, pin_keys = [ 'lat', 'lon', 'colour', 'id', 'title', 'type' ]; @@ -18,6 +18,9 @@ var fixmystreet = fixmystreet || {}; } }); + + fixmystreet.bodies = fixmystreet.bodies ? fixmystreet.utils.csv_to_array(fixmystreet.bodies)[0] : []; + fixmystreet.area = fixmystreet.area ? fixmystreet.area.split(',') : []; if (fixmystreet.map_type) { var s = fixmystreet.map_type.split('.'); diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js index 3c9e3fb91..07acf248c 100644 --- a/web/js/map-OpenLayers.js +++ b/web/js/map-OpenLayers.js @@ -27,6 +27,48 @@ $.extend(fixmystreet.utils, { return out.join(','); }, + // https://stackoverflow.com/questions/1293147/javascript-code-to-parse-csv-data/1293163#1293163 + csv_to_array: function( strData, strDelimiter ) { + strDelimiter = (strDelimiter || ","); + + var objPattern = new RegExp( + ( + "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" + + "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + + "([^\"\\" + strDelimiter + "\\r\\n]*))" + ), + "gi" + ); + + var arrData = [[]]; + + var arrMatches = objPattern.exec( strData ); + while (arrMatches) { + + var strMatchedDelimiter = arrMatches[ 1 ]; + + if ( strMatchedDelimiter.length && + strMatchedDelimiter !== strDelimiter) { + arrData.push( [] ); + } + + var strMatchedValue; + if (arrMatches[ 2 ]) { + strMatchedValue = arrMatches[ 2 ].replace( + new RegExp( "\"\"", "g" ), + "\"" + ); + } else { + strMatchedValue = arrMatches[ 3 ]; + } + + arrData[ arrData.length - 1 ].push( strMatchedValue ); + arrMatches = objPattern.exec( strData ); + } + + return( arrData ); + }, + parse_query_string: function() { var qs = {}; if (!location.search) { |