aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.mailmap33
-rwxr-xr-xbin/comment-backfill21
-rwxr-xr-xbin/fetch-comments15
-rwxr-xr-xbin/kasabi4
-rwxr-xr-xbin/open311-populate-service-list4
-rwxr-xr-xbin/send-comments100
-rwxr-xr-xbin/send-reports746
-rw-r--r--conf/crontab.ugly5
-rw-r--r--conf/general.yml-example3
-rw-r--r--data/dashboard.json236
-rw-r--r--db/schema.sql16
-rw-r--r--db/schema_0013-add_external_id_to_comment.sql6
-rw-r--r--db/schema_0015-add_extra_to_comment.sql6
-rw-r--r--db/schema_0016-add_whensent_and_send_fail_to_comment.sql11
-rw-r--r--db/schema_0017-add_send_comments_to_open311conf.sql6
-rw-r--r--db/schema_0018-add_comment_user_to_open311conf.sql6
-rw-r--r--db/schema_0019-add_title_to_users.sql8
-rw-r--r--db/schema_0020-add_suppress_alerts_to_open311.sql6
-rw-r--r--perllib/FixMyStreet/App.pm18
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm27
-rw-r--r--perllib/FixMyStreet/App/Controller/Alert.pm9
-rw-r--r--perllib/FixMyStreet/App/Controller/Around.pm3
-rw-r--r--perllib/FixMyStreet/App/Controller/Contact.pm4
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm245
-rw-r--r--perllib/FixMyStreet/App/Controller/My.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Photo.pm97
-rwxr-xr-xperllib/FixMyStreet/App/Controller/Questionnaire.pm4
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm30
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm130
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/Update.pm39
-rw-r--r--perllib/FixMyStreet/App/Controller/Root.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Tokens.pm1
-rw-r--r--perllib/FixMyStreet/App/Model/EmailSend.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand.pm18
-rw-r--r--perllib/FixMyStreet/Cobrand/Bromley.pm58
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm49
-rw-r--r--perllib/FixMyStreet/Cobrand/FixMyStreet.pm43
-rw-r--r--perllib/FixMyStreet/Cobrand/UKCouncils.pm22
-rw-r--r--perllib/FixMyStreet/DB/Result/Comment.pm37
-rw-r--r--perllib/FixMyStreet/DB/Result/Open311conf.pm21
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm15
-rw-r--r--perllib/FixMyStreet/DB/Result/User.pm30
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Problem.pm215
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm3
-rw-r--r--perllib/FixMyStreet/EmailSend.pm12
-rw-r--r--perllib/FixMyStreet/Geocode/Bing.pm12
-rw-r--r--perllib/FixMyStreet/Map/Bromley.pm22
-rw-r--r--perllib/FixMyStreet/Map/FMS.pm21
-rw-r--r--perllib/FixMyStreet/Map/OSM.pm10
-rw-r--r--perllib/FixMyStreet/SendReport.pm45
-rw-r--r--perllib/FixMyStreet/SendReport/Barnet.pm227
-rw-r--r--perllib/FixMyStreet/SendReport/EastHants.pm62
-rw-r--r--perllib/FixMyStreet/SendReport/Email.pm111
-rw-r--r--perllib/FixMyStreet/SendReport/EmptyHomes.pm51
-rw-r--r--perllib/FixMyStreet/SendReport/London.pm113
-rw-r--r--perllib/FixMyStreet/SendReport/Open311.pm133
-rw-r--r--perllib/FixMyStreet/TestMech.pm1
-rw-r--r--perllib/Open311.pm257
-rw-r--r--perllib/Open311/GetServiceRequestUpdates.pm132
-rw-r--r--perllib/Open311/PopulateServiceList.pm43
-rw-r--r--t/app/controller/about.t2
-rw-r--r--t/app/controller/admin.t49
-rw-r--r--t/app/controller/alert_new.t32
-rw-r--r--t/app/controller/dashboard.t639
-rw-r--r--t/app/controller/questionnaire.t132
-rw-r--r--t/app/controller/report_import.t2
-rw-r--r--t/app/controller/report_new.t228
-rw-r--r--t/app/controller/report_updates.t20
-rw-r--r--t/app/controller/reports.t4
-rw-r--r--t/app/model/problem.t130
-rw-r--r--t/app/uri_for.t57
-rw-r--r--t/cobrand/get_council_sender.t30
-rw-r--r--t/open311.t451
-rw-r--r--t/open311/getservicerequestupdates.t537
-rw-r--r--t/open311/populate-service-list.t298
-rw-r--r--templates/email/bromley/questionnaire.txt22
-rw-r--r--templates/email/bromley/signature.txt3
-rw-r--r--templates/email/default/alert-confirm.txt7
-rw-r--r--templates/email/default/alert-problem-area.txt3
-rw-r--r--templates/email/default/alert-problem-council.txt3
-rw-r--r--templates/email/default/alert-problem-nearby.txt3
-rw-r--r--templates/email/default/alert-problem-ward.txt3
-rw-r--r--templates/email/default/alert-problem.txt3
-rw-r--r--templates/email/default/alert-update.txt3
-rw-r--r--templates/email/default/login.txt3
-rw-r--r--templates/email/default/partial.txt3
-rw-r--r--templates/email/default/problem-confirm.txt7
-rw-r--r--templates/email/default/questionnaire.txt3
-rw-r--r--templates/email/default/reply-autoresponse3
-rw-r--r--templates/email/default/signature.txt2
-rw-r--r--templates/email/default/submit-brent.txt3
-rw-r--r--templates/email/default/submit.txt3
-rw-r--r--templates/email/default/update-confirm.txt5
-rw-r--r--templates/web/bromley/auth/sign_out.html8
-rw-r--r--templates/web/bromley/contact/address.html (renamed from templates/web/fixmystreet/report/new/form_heading.html)0
-rw-r--r--templates/web/bromley/contact/submit.html19
-rwxr-xr-xtemplates/web/bromley/faq/faq-en-gb.html111
-rw-r--r--templates/web/bromley/footer.html43
-rw-r--r--templates/web/bromley/header.html71
-rw-r--r--templates/web/bromley/report/display.html217
-rw-r--r--templates/web/bromley/report/new/fill_in_details_form.html228
-rw-r--r--templates/web/default/admin/council_contacts.html25
-rw-r--r--templates/web/default/admin/report_edit.html1
-rw-r--r--templates/web/default/auth/sign_out.html6
-rw-r--r--templates/web/default/common_header_tags.html3
-rw-r--r--templates/web/default/dashboard/index.html152
-rwxr-xr-xtemplates/web/default/faq/faq-en-gb.html42
-rw-r--r--templates/web/default/index.html2
-rw-r--r--templates/web/default/js/validation_strings.html5
-rw-r--r--templates/web/default/report/new/councils_text_all.html2
-rw-r--r--templates/web/default/report/new/extra_name.html18
-rw-r--r--templates/web/default/report/updates.html10
-rwxr-xr-xtemplates/web/default/reports/council.html18
-rw-r--r--templates/web/default/tokens/confirm_problem.html11
-rw-r--r--templates/web/fixmystreet/alert/list.html1
-rw-r--r--templates/web/fixmystreet/around/around_index.html45
-rwxr-xr-xtemplates/web/fixmystreet/around/display_location.html7
-rwxr-xr-xtemplates/web/fixmystreet/faq/faq-en-gb.html39
-rw-r--r--templates/web/fixmystreet/footer.html1
-rw-r--r--templates/web/fixmystreet/header.html4
-rw-r--r--templates/web/fixmystreet/index.html36
-rw-r--r--templates/web/fixmystreet/report/display.html4
-rw-r--r--templates/web/fixmystreet/report/new/category.html10
-rw-r--r--templates/web/fixmystreet/report/new/councils_text.html9
-rw-r--r--templates/web/fixmystreet/report/new/councils_text_all.html19
-rw-r--r--templates/web/fixmystreet/report/new/councils_text_none.html20
-rw-r--r--templates/web/fixmystreet/report/new/councils_text_some.html28
-rw-r--r--templates/web/fixmystreet/report/new/fill_in_details_form.html12
-rw-r--r--templates/web/fixmystreet/report/new/notes.html2
-rw-r--r--templates/web/fixmystreet/report/new/report_import.html92
-rw-r--r--templates/web/fixmystreet/report/updates-sidebar-notes.html6
-rw-r--r--web/cobrands/bromley/bromley-logo.jpgbin0 -> 20898 bytes
-rw-r--r--web/cobrands/bromley/bromley-logo.s.jpgbin0 -> 5908 bytes
-rw-r--r--web/cobrands/bromley/bromley.scss110
-rw-r--r--web/cobrands/bromley/favicon.icobin0 -> 61798 bytes
-rw-r--r--web/cobrands/bromley/favicon.pngbin0 -> 840 bytes
-rw-r--r--web/cobrands/bromley/fms-logo.pngbin0 -> 4740 bytes
-rw-r--r--web/cobrands/bromley/layout.scss64
-rw-r--r--web/cobrands/bromley/main-menu-hover-home-right.gifbin0 -> 4299 bytes
-rw-r--r--web/cobrands/bromley/main-menu1.gifbin0 -> 12809 bytes
-rw-r--r--web/cobrands/bromley/tab-blue.pngbin0 -> 826 bytes
-rw-r--r--web/cobrands/fixmystreet/_base.scss95
-rw-r--r--web/cobrands/fixmystreet/_colours.scss1
-rw-r--r--web/cobrands/fixmystreet/_layout.scss116
-rw-r--r--web/cobrands/fixmystreet/fixmystreet.js214
-rw-r--r--web/cobrands/fixmystreet/position_map.js25
-rw-r--r--web/css/core.scss6
-rwxr-xr-xweb/fixmystreet_app_cgi.cgi7
-rwxr-xr-xweb/fixmystreet_app_fastcgi.cgi7
-rw-r--r--web/i/pin-green.pngbin1806 -> 1316 bytes
-rw-r--r--web/i/pin-red.pngbin1841 -> 1353 bytes
-rw-r--r--web/i/pin-yellow-big.pngbin2364 -> 1242 bytes
-rw-r--r--web/i/pin-yellow.pngbin1475 -> 828 bytes
-rw-r--r--web/js/fancybox/jquery.fancybox-1.3.4.js6
-rw-r--r--web/js/fancybox/jquery.fancybox-1.3.4.pack.js6
-rw-r--r--web/js/fixmystreet-old-box.js39
-rw-r--r--web/js/fixmystreet.js171
-rw-r--r--web/js/map-OpenLayers.js15
-rw-r--r--web/js/map-bing-ol.js25
-rw-r--r--web/js/modernizr.custom.76759.js4
-rw-r--r--web/js/modernizr.custom.js4
162 files changed, 6694 insertions, 1851 deletions
diff --git a/.gitignore b/.gitignore
index b55a7046f..4bd5e9d39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.ttc
/cities_bin
/private_locale
+*~
._*
.DS_Store
.carton
@@ -20,6 +21,7 @@ tags
/web/css/main.css
/web/cobrands/fixmystreet/*.css
/web/cobrands/bromley/*.css
+/web/photo
/web/cobrands/barnet/*.css
/local
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 000000000..b279f97a8
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,33 @@
+Anders Einar Hilden <hildenae@gmail.com>
+Andrew Perry <me@andrewperry.com.au>
+Anna Powell-Smith <annapowellsmith@gmail.com> <anna>
+Anna Powell-Smith <annapowellsmith@gmail.com> <annapowellsmith@gmail.com>
+bci-cities <bci-cities>
+Chris Lightfoot <chris@ex-parrot.com> <chris>
+Dave Whiteland <dave@mysociety.org> <dave@balti.ukcod.org.uk>
+Dave Whiteland <dave@mysociety.org> <dave@fury.ukcod.org.uk>
+Duncan Parkes <duncan@mysociety.org> <duncan@fury.ukcod.org.uk>
+Edmund von der Burg <evdb@mysociety.org> <evdb@ecclestoad.co.uk>
+Francis Irving <francis@mysociety.org> <francis>
+Guillaume Rischard <fixmystreet@stereo.lu> <fixmystreet@stereo.lu>
+Guillaume Rischard <fixmystreet@stereo.lu> <git@stereo.lu>
+Josh Angell <josh@supercooldesign.co.uk> <jbangell@gmail.com>
+Kagee <hildenae@gmail.com>
+Keith Garrett <keith@mysociety.org> <keith>
+Kevin Brubeck Unhammer <unhammer@fsfe.org>
+Louise Crow <louise@mysociety.org> <louise.crow@gmail.com>
+Louise Crow <louise@mysociety.org> <louise@peas.ukcod.org.uk>
+Louise Crow <louise@mysociety.org> <louise>
+Mark Longair <mark@mysociety.org> <mhl@pobox.com>
+Matthew Somerville <matthew@mysociety.org> <matthew>
+Matthew Somerville <matthew@mysociety.org> <matthew@balti.ukcod.org.uk>
+Matthew Somerville <matthew@mysociety.org> <matthew@cake.ukcod.org.uk>
+Matthew Somerville <matthew@mysociety.org> <matthew@dracos.co.uk>
+Matthew Somerville <matthew@mysociety.org> <matthew@fury.ukcod.org.uk>
+Matthew Somerville <matthew@mysociety.org> <matthew-github@dracos.co.uk>
+Matthew Somerville <matthew@mysociety.org> <matthew@mysociety.org>
+Matthew Somerville <matthew@mysociety.org> <matthew@rocket.ukcod.org.uk>
+Struan Donald <struan@mysociety.org> <struan@exo.org.uk>
+Struan Donald <struan@mysociety.org><struan@fury.ukcod.org.uk>
+Tim Morley <t_morley@argonet.co.uk> <timsk>
+Tony Bowden <tony@tmtm.com>
diff --git a/bin/comment-backfill b/bin/comment-backfill
new file mode 100755
index 000000000..e296d7756
--- /dev/null
+++ b/bin/comment-backfill
@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+require 5.8.0;
+use DateTime;
+
+use FixMyStreet::App;
+
+use Open311;
+use Open311::GetServiceRequestUpdates;
+
+my $start_time = DateTime->now->subtract( days => 1, hours => 1 );
+my $end_time = DateTime->now;
+
+my $updates = Open311::GetServiceRequestUpdates->new(
+ start_date => $start_time,
+ end_date => $end_time,
+);
+
+$updates->fetch;
diff --git a/bin/fetch-comments b/bin/fetch-comments
new file mode 100755
index 000000000..4bbcc9d21
--- /dev/null
+++ b/bin/fetch-comments
@@ -0,0 +1,15 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+require 5.8.0;
+
+use FixMyStreet::App;
+
+use Open311;
+use Open311::GetServiceRequestUpdates;
+
+
+my $updates = Open311::GetServiceRequestUpdates->new;
+
+$updates->fetch;
diff --git a/bin/kasabi b/bin/kasabi
index 5b99ba4ff..456b2f4d1 100755
--- a/bin/kasabi
+++ b/bin/kasabi
@@ -63,7 +63,7 @@ def main():
report['detail'] = tidy_string(report['detail'])
report['confirmed'] = report['confirmed'].replace(microsecond=0).isoformat() # Don't want microseconds
report['lastupdate'] = report['lastupdate'].replace(microsecond=0).isoformat()
- report['council'] = sorted(re.sub('\|.*', '', report['council']).split(',')) # Remove missing councils
+ report['council'] = sorted(re.sub('\|.*', '', report['council'] or '').split(',')) # Remove missing councils
# Fetch updates to note state changes
states = [ { 'state': 'confirmed', 'time': report['confirmed'] } ]
@@ -166,6 +166,7 @@ class FixMyStreetChangeSet(object):
# Get info for the councils
council_data = { 'sentTo': '', 'areaNames': [] }
for council in report['council']:
+ if not council: continue
js = json.load(urllib.urlopen('http://mapit.mysociety.org/area/{0}'.format(council)))
os_id = int(js['codes']['unit_id']) + 7000000000000000
if report['whensent']:
@@ -173,6 +174,7 @@ class FixMyStreetChangeSet(object):
council_data['areaNames'].append(js['name'])
council_data.setdefault('firstCouncil', council)
council_data['areaNames'] = ' / '.join(council_data['areaNames'])
+ council_data.setdefault('firstCouncil', '0')
# easting/northing
diff --git a/bin/open311-populate-service-list b/bin/open311-populate-service-list
index 36e04f5a6..33be61af7 100755
--- a/bin/open311-populate-service-list
+++ b/bin/open311-populate-service-list
@@ -6,7 +6,9 @@ use FixMyStreet::App;
use Open311::PopulateServiceList;
-my $council_list = FixMyStreet::App->model('DB::Open311conf');
+my $council_list = FixMyStreet::App->model('DB::Open311conf')->search( {
+ send_method => 'Open311'
+} );
my $p = Open311::PopulateServiceList->new( council_list => $council_list );
$p->process_councils;
diff --git a/bin/send-comments b/bin/send-comments
new file mode 100755
index 000000000..b1c1463ce
--- /dev/null
+++ b/bin/send-comments
@@ -0,0 +1,100 @@
+#!/usr/bin/env perl
+
+# send-reports:
+# Send new problem reports to councils
+#
+# Copyright (c) 2011 UK Citizens Online Democracy. All rights reserved.
+# Email: matthew@mysociety.org. WWW: http://www.mysociety.org
+
+use strict;
+use warnings;
+require 5.8.0;
+
+use Digest::MD5;
+use Encode;
+use Error qw(:try);
+use CronFns;
+
+use FixMyStreet::App;
+
+use Utils;
+use mySociety::Config;
+use mySociety::EmailUtil;
+
+use Open311;
+
+# maximum number of webservice attempts to send before not trying any more (XXX may be better in config?)
+use constant SEND_FAIL_RETRIES_CUTOFF => 3;
+
+# send_method config values found in by-area config data, for selecting to appropriate method
+use constant SEND_METHOD_EMAIL => 'email';
+use constant SEND_METHOD_OPEN311 => 'Open311';
+
+# Set up site, language etc.
+my ($verbose, $nomail) = CronFns::options();
+my $base_url = mySociety::Config::get('BASE_URL');
+my $site = CronFns::site($base_url);
+
+my $councils = FixMyStreet::App->model('DB::Open311Conf')->search( {
+ send_method => SEND_METHOD_OPEN311,
+ send_comments => 1,
+} );
+
+while ( my $council = $councils->next ) {
+ my $comments = FixMyStreet::App->model('DB::Comment')->search( {
+ 'me.whensent' => undef,
+ 'me.external_id' => undef,
+ 'me.state' => 'confirmed',
+ 'me.confirmed' => { '!=' => undef },
+ 'problem.whensent' => { '!=' => undef },
+ 'problem.external_id' => { '!=' => undef },
+ 'problem.council' => { -like => '%' . $council->area_id .'%' },
+ 'problem.send_method_used' => 'Open311',
+ },
+ {
+ join => 'problem',
+ }
+ );
+
+ my $o = Open311->new(
+ endpoint => $council->endpoint,
+ jurisdiction => $council->jurisdiction,
+ api_key => $council->api_key,
+ );
+
+ while ( my $comment = $comments->next ) {
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($comment->cobrand)->new();
+
+ if ( $comment->send_fail_count ) {
+ next if bromley_retry_timeout( $comment );
+ }
+
+ my $id = $o->post_service_request_update( $comment );
+
+ if ( $id ) {
+ $comment->update( {
+ external_id => $id,
+ whensent => \'ms_current_timestamp()',
+ } );
+ } else {
+ $comment->update( {
+ send_fail_count => $comment->send_fail_count + 1,
+ send_fail_timestamp => \'ms_current_timestamp()',
+ send_fail_reason => 'Failed to post over Open311',
+ } );
+ }
+ }
+}
+
+sub bromley_retry_timeout {
+ my $row = shift;
+
+ my $tz = DateTime::TimeZone->new( name => 'local' );
+ my $now = DateTime->now( time_zone => $tz );
+ my $diff = $now - $row->send_fail_timestamp;
+ if ( $diff->in_units( 'minutes' ) < 30 ) {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/bin/send-reports b/bin/send-reports
index eb881d169..16296dc5b 100755
--- a/bin/send-reports
+++ b/bin/send-reports
@@ -10,749 +10,13 @@ use strict;
use warnings;
require 5.8.0;
-use Digest::MD5;
-use Encode;
-use Error qw(:try);
-use JSON;
-use LWP::UserAgent;
-use LWP::Simple;
-use CGI; # Trying awkward kludge
+use CGI; # XXX
use CronFns;
-use FixMyStreet::App;
-
-use EastHantsWSDL;
-use BarnetInterfaces::service::ZLBB_SERVICE_ORDER;
-use Utils;
use mySociety::Config;
-use mySociety::EmailUtil;
-use mySociety::MaPit;
-use mySociety::Web qw(ent);
-
-use Open311;
-
-# maximum number of webservice attempts to send before not trying any more (XXX may be better in config?)
-use constant SEND_FAIL_RETRIES_CUTOFF => 3;
-
-# specific council numbers
-use constant COUNCIL_ID_BARNET => 2489;
-use constant COUNCIL_ID_EAST_HANTS => 2330;
-
-use constant MAX_LINE_LENGTH => 132;
-
-# send_method config values found in by-area config data, for selecting to appropriate method
-use constant SEND_METHOD_EMAIL => 'email';
-use constant SEND_METHOD_OPEN311 => 'open311';
-use constant SEND_METHOD_BARNET => 'barnet';
-use constant SEND_METHOD_EAST_HANTS => 'easthants';
-use constant SEND_METHOD_LONDON => 'london';
-
-# Set up site, language etc.
-my ($verbose, $nomail) = CronFns::options();
-my $base_url = mySociety::Config::get('BASE_URL');
-my $site = CronFns::site($base_url);
-
-my $unsent = FixMyStreet::App->model("DB::Problem")->search( {
- state => [ 'confirmed', 'fixed' ],
- whensent => undef,
- council => { '!=', undef },
-} );
-
-my %sending_skipped_by_method = ();
-
-my (%notgot, %note);
-while (my $row = $unsent->next) {
-
- my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($row->cobrand)->new();
-
- # Cobranded and non-cobranded messages can share a database. In this case, the conf file
- # should specify a vhost to send the reports for each cobrand, so that they don't get sent
- # more than once if there are multiple vhosts running off the same database. The email_host
- # call checks if this is the host that sends mail for this cobrand.
- next unless $cobrand->email_host();
- $cobrand->set_lang_and_domain($row->lang, 1);
- if ( $row->is_from_abuser ) {
- $row->update( { state => 'hidden' } );
- next;
- }
-
- # Due to multiple councils, it's possible to want to send both by email *and* another method
- # NB: might need to revist this if multiple councils have custom send methods
- my $send_email = 0;
- my $send_method = 0;
-
- # Template variables for the email
- my $email_base_url = $cobrand->base_url_for_emails($row->cobrand_data);
- my %h = map { $_ => $row->$_ } qw/id title detail name category latitude longitude used_map/;
- map { $h{$_} = $row->user->$_ } qw/email phone/;
- $h{confirmed} = DateTime::Format::Pg->format_datetime( $row->confirmed->truncate (to => 'second' ) );
-
- $h{query} = $row->postcode;
- $h{url} = $email_base_url . '/report/' . $row->id;
- $h{phone_line} = $h{phone} ? _('Phone:') . " $h{phone}\n\n" : '';
- if ($row->photo) {
- $h{has_photo} = _("This web page also contains a photo of the problem, provided by the user.") . "\n\n";
- $h{image_url} = $email_base_url . '/photo/' . $row->id . '.full.jpeg';
- } else {
- $h{has_photo} = '';
- $h{image_url} = '';
- }
- $h{fuzzy} = $row->used_map ? _('To view a map of the precise location of this issue')
- : _('The user could not locate the problem on a map, but to see the area around the location they entered');
- $h{closest_address} = '';
-
- # If we are in the UK include eastings and northings, and nearest stuff
- $h{easting_northing} = '';
- if ( $cobrand->country eq 'GB' ) {
-
- ( $h{easting}, $h{northing} ) = Utils::convert_latlon_to_en( $h{latitude}, $h{longitude} );
-
- # email templates don't have conditionals so we need to farmat this here
- $h{easting_northing} #
- = "Easting: $h{easting}\n\n" #
- . "Northing: $h{northing}\n\n";
-
- }
-
- if ( $row->used_map ) {
- $h{closest_address} = $cobrand->find_closest( $h{latitude}, $h{longitude}, $row );
- }
-
- my (@to, @recips, $template, $areas_info, @open311_councils);
- if ($site eq 'emptyhomes') {
-
- my $council = $row->council;
- $areas_info = mySociety::MaPit::call('areas', $council);
- my $name = $areas_info->{$council}->{name};
- my $contact = FixMyStreet::App->model("DB::Contact")->find( {
- deleted => 0,
- area_id => $council,
- category => 'Empty property',
- } );
- my ($council_email, $confirmed, $note) = ( $contact->email, $contact->confirmed, $contact->note );
- unless ($confirmed) {
- $note = 'Council ' . $council . ' deleted' unless $note;
- $council_email = 'N/A' unless $council_email;
- $notgot{$council_email}{$row->category}++;
- $note{$council_email}{$row->category} = $note;
- next;
- }
-
- push @to, [ $council_email, $name ];
- @recips = ($council_email);
- $send_method = 0;
- $send_email = 1;
- $template = Utils::read_file("$FindBin::Bin/../templates/email/emptyhomes/" . $row->lang . "/submit.txt");
-
- } else {
-
- # XXX Needs locks!
- my @all_councils = split /,|\|/, $row->council;
- my ($councils, $missing) = $row->council =~ /^([\d,]+)(?:\|([\d,]+))?/;
- my @councils = split(/,/, $councils);
- $areas_info = mySociety::MaPit::call('areas', \@all_councils);
- my (@dear, %recips);
- my $all_confirmed = 1;
- foreach my $council (@councils) {
- my $name = $areas_info->{$council}->{name};
- push @dear, $name;
-
- # look in the DB to determine if there is a special handler for this council (e.g., open311, or custom)
- my $council_config = FixMyStreet::App->model("DB::Open311conf")->search( { area_id => $council} )->first;
- $send_method = $council_config->send_method if ($council_config and $council_config->send_method);
- if ($council == COUNCIL_ID_EAST_HANTS) { # E. Hants have a web service
- $send_method = SEND_METHOD_EAST_HANTS; # TODO: delete? should be in the db
- $h{category} = 'Customer Services' if $h{category} eq 'Other';
- }
-
- # if council lookup provided no explicit send_method, maybe there's some other criterion for setting it:
- if (! $send_method) {
- if ($areas_info->{$council}->{type} eq 'LBO') { # London
- $send_method = SEND_METHOD_LONDON;
- }
- }
- $send_email = 1 unless $send_method; # default to email if nothing explicit was provided
-
- # currently: open311 or Barnet without an endpoint is useless, so check the endpoint is set
- if ($send_method eq SEND_METHOD_OPEN311 or $send_method eq SEND_METHOD_BARNET) {
- if ($council_config->endpoint) {
- if ($send_method eq SEND_METHOD_OPEN311) {
- push @open311_councils, $council_config;
- }
- } else {
- print "Warning: no endpoint specified in config data for council=$council (will try email instead)\n";
- $send_method = 0;
- $send_email = 1;
- }
- }
-
- if ($send_email) {
- my $contact = FixMyStreet::App->model("DB::Contact")->find( {
- deleted => 0,
- area_id => $council,
- category => $row->category
- } );
- my ($council_email, $confirmed, $note) = ( $contact->email, $contact->confirmed, $contact->note );
- $council_email = essex_contact($row->latitude, $row->longitude) if $council == 2225;
- $council_email = oxfordshire_contact($row->latitude, $row->longitude) if $council == 2237 && $council_email eq 'SPECIAL';
- unless ($confirmed) {
- $all_confirmed = 0;
- $note = 'Council ' . $row->council . ' deleted'
- unless $note;
- $council_email = 'N/A' unless $council_email;
- $notgot{$council_email}{$row->category}++;
- $note{$council_email}{$row->category} = $note;
- }
- push @to, [ $council_email, $name ];
- $recips{$council_email} = 1;
- }
- }
- @recips = keys %recips;
- next unless $all_confirmed;
-
- $template = 'submit.txt';
- $template = 'submit-brent.txt' if $row->council eq 2488 || $row->council eq 2237;
- my $template_path = FixMyStreet->path_to( "templates", "email", $cobrand->moniker, $template )->stringify;
- $template_path = FixMyStreet->path_to( "templates", "email", "default", $template )->stringify
- unless -e $template_path;
- $template = Utils::read_file( $template_path );
-
- if ($h{category} eq _('Other')) {
- $h{category_footer} = _('this type of local problem');
- $h{category_line} = '';
- } else {
- $h{category_footer} = "'" . $h{category} . "'";
- $h{category_line} = sprintf(_("Category: %s"), $h{category}) . "\n\n";
- }
-
- $h{councils_name} = join(_(' and '), @dear);
- if ($h{category} eq _('Other')) {
- $h{multiple} = @dear>1 ? "[ " . _("This email has been sent to both councils covering the location of the problem, as the user did not categorise it; please ignore it if you're not the correct council to deal with the issue, or let us know what category of problem this is so we can add it to our system.") . " ]\n\n"
- : '';
- } else {
- $h{multiple} = @dear>1 ? "[ " . _("This email has been sent to several councils covering the location of the problem, as the category selected is provided for all of them; please ignore it if you're not the correct council to deal with the issue.") . " ]\n\n"
- : '';
- }
- $h{missing} = '';
- if ($missing) {
- my $name = $areas_info->{$missing}->{name};
- $h{missing} = '[ '
- . sprintf(_('We realise this problem might be the responsibility of %s; however, we don\'t currently have any contact details for them. If you know of an appropriate contact address, please do get in touch.'), $name)
- . " ]\n\n";
- }
-
- }
-
- unless ($send_email || $send_method) {
- die 'Report not going anywhere for ID ' . $row->id . '!';
- }
-
- if (mySociety::Config::get('STAGING_SITE')) {
- # on a staging server send emails to ourselves rather than the councils
- # ...webservice calls will only go through if explictly allowed here:
- my @testing_councils = (COUNCIL_ID_BARNET);
- unless (grep {$row->council eq $_} @testing_councils) {
- @recips = ( mySociety::Config::get('CONTACT_EMAIL') );
- $send_method = 0;
- $send_email = 1;
- }
- } elsif ($site eq 'emptyhomes') {
- my $council = $row->council;
- my $country = $areas_info->{$council}->{country};
- if ($country eq 'W') {
- push @recips, 'shelter@' . mySociety::Config::get('EMAIL_DOMAIN');
- } else {
- push @recips, 'eha@' . mySociety::Config::get('EMAIL_DOMAIN');
- }
- }
-
- # Special case for this parish council
- # if ($address && $address =~ /Sprowston/ && $row->council == 2233 && $row->category eq 'Street lighting') {
- # $h{councils_name} = 'Sprowston Parish Council';
- # my $e = 'parishclerk' . '@' . 'sprowston-pc.gov.uk';
- # @to = ( [ $e, $h{councils_name} ] );
- # @recips = ($e);
- # }
-
- # Multiply results together, so one success counts as a success.
- my $result = -1;
-
- if ($send_email) {
- $result *= FixMyStreet::App->send_email_cron(
- {
- _template_ => $template,
- _parameters_ => \%h,
- To => \@to,
- From => [ $row->user->email, $row->name ],
- },
- mySociety::Config::get('CONTACT_EMAIL'),
- \@recips,
- $nomail
- );
- }
-
- if ($send_method eq SEND_METHOD_EAST_HANTS) {
- $h{message} = construct_easthants_message(%h);
- if (!$nomail) {
- $result *= post_easthants_message(%h);
- }
- } elsif ($send_method eq SEND_METHOD_BARNET) {
- $h{message} = construct_barnet_message(%h);
- if (!$nomail) {
- if (my $cutoff_msg = does_exceed_cutoff_limit($row, "barnet")) {
- print "$cutoff_msg\n" if $verbose;
- } else {
- my ($barnet_result, $err_msg) = post_barnet_message( $row, %h );
- update_send_fail_data($row, $err_msg) if $barnet_result;
- $result *= $barnet_result;
- }
- }
- } elsif ($send_method eq SEND_METHOD_LONDON) {
- $h{message} = construct_london_message(%h);
- if (!$nomail) {
- $result *= post_london_report( $row, %h );
- }
- } elsif ($send_method eq SEND_METHOD_OPEN311) {
- foreach my $conf ( @open311_councils ) {
- print 'posting to end point for ' . $conf->area_id . "\n" if $verbose;
-
- my $contact = FixMyStreet::App->model("DB::Contact")->find( {
- deleted => 0,
- area_id => $conf->area_id,
- category => $row->category
- } );
-
- my $open311 = Open311->new(
- jurisdiction => $conf->jurisdiction,
- endpoint => $conf->endpoint,
- api_key => $conf->api_key,
- );
-
- # non standard west berks end points
- if ( $row->council =~ /2619/ ) {
- $open311->endpoints( { services => 'Services', requests => 'Requests' } );
- }
-
- # required to get round issues with CRM constraints
- if ( $row->council =~ /2218/ ) {
- $row->user->name( $row->user->id . ' ' . $row->user->name );
- }
-
- my $resp = $open311->send_service_request( $row, \%h, $contact->email );
-
- # make sure we don't save user changes from above
- if ( $row->council =~ /2218/ ) {
- $row->discard_changes();
- }
-
- if ( $resp ) {
- $row->external_id( $resp );
- $result *= 0;
- } else {
- $result *= 1;
- # temporary fix to resolve some issues with west berks
- if ( $row->council =~ /2619/ ) {
- $result *= 0;
- }
- }
- }
- }
-
- if ($result == mySociety::EmailUtil::EMAIL_SUCCESS) {
- $row->update( {
- whensent => \'ms_current_timestamp()',
- lastupdate => \'ms_current_timestamp()',
- } );
- }
-}
-
-if ($verbose) {
- print "Council email addresses that need checking:\n" if keys %notgot;
- foreach my $e (keys %notgot) {
- foreach my $c (keys %{$notgot{$e}}) {
- print $notgot{$e}{$c} . " problem, to $e category $c (" . $note{$e}{$c}. ")\n";
- }
- }
-}
-
-if ($verbose and keys %sending_skipped_by_method) {
- my $c = 0;
- print "\nProblem reports that send-reports did not attempt to send because retries >= " . SEND_FAIL_RETRIES_CUTOFF . ":\n";
- foreach my $send_method (sort keys %sending_skipped_by_method) {
- printf " %-24s %4d\n", "$send_method:", $sending_skipped_by_method{$send_method};
- $c+=$sending_skipped_by_method{$send_method};
- }
- printf " %-24s %4d\n", "Total:", $c;
-}
-
-sub _get_district_for_contact {
- my ( $lat, $lon ) = @_;
- my $district =
- mySociety::MaPit::call( 'point', "4326/$lon,$lat", type => 'DIS' );
- ($district) = keys %$district;
- return $district;
-}
-
-# Essex has different contact addresses depending upon the district
-# Might be easier if we start storing in the db all areas covered by a point
-# Will do for now :)
-sub essex_contact {
- my $district = _get_district_for_contact(@_);
- my $email;
- $email = 'eastarea' if $district == 2315 || $district == 2312;
- $email = 'midarea' if $district == 2317 || $district == 2314 || $district == 2316;
- $email = 'southarea' if $district == 2319 || $district == 2320 || $district == 2310;
- $email = 'westarea' if $district == 2309 || $district == 2311 || $district == 2318 || $district == 2313;
- die "Returned district $district which is not in Essex!" unless $email;
- return "highways.$email\@essexcc.gov.uk";
-}
-
-# Oxfordshire has different contact addresses depending upon the district
-sub oxfordshire_contact {
- my $district = _get_district_for_contact(@_);
- my $email;
- $email = 'northernarea' if $district == 2419 || $district == 2420 || $district == 2421;
- $email = 'southernarea' if $district == 2417 || $district == 2418;
- die "Returned district $district which is not in Oxfordshire!" unless $email;
- return "$email\@oxfordshire.gov.uk";
-}
-
-# East Hampshire
-
-sub construct_easthants_message {
- my %h = @_;
- my $message = '';
- $message .= "[ This report was also sent to the district council covering the location of the problem, as the user did not categorise it; please ignore if you're not the correct council to deal with the issue. ]\n\n"
- if $h{multiple};
- $message .= <<EOF;
-Subject: $h{title}
-
-Category: $h{category}
-
-Details: $h{detail}
-
-$h{fuzzy}, or to provide an update on the problem, please visit the following link:
-
-$h{url}
-
-$h{closest_address}
-EOF
- return $message;
-}
-
-my $eh_service;
-sub post_easthants_message {
- my %h = @_;
- my $return = 1;
- $eh_service ||= EastHantsWSDL->on_fault(sub { my($soap, $res) = @_; die ref $res ? $res->faultstring : $soap->transport->status, "\n"; });
- try {
- # ServiceName, RemoteCreatedBy, Salutation, FirstName, Name, Email, Telephone, HouseNoName, Street, Town, County, Country, Postcode, Comments, FurtherInfo, ImageURL
- my $message = ent(encode_utf8($h{message}));
- my $name = ent(encode_utf8($h{name}));
- my $result = $eh_service->INPUTFEEDBACK(
- $h{category}, 'FixMyStreet', '', '', $name, $h{email}, $h{phone},
- '', '', '', '', '', '', $message, 'Yes', $h{image_url}
- );
- $return = 0 if $result eq 'Report received';
- } otherwise {
- my $e = shift;
- print "Caught an error: $e\n";
- };
- return $return;
-}
-
-# currently just blind copy of construct_easthants_message
-sub construct_barnet_message {
- my %h = @_;
- my $message = <<EOF;
-Subject: $h{title}
-
-Details: $h{detail}
-
-$h{fuzzy}, or to provide an update on the problem, please visit the following link:
-
-$h{url}
-
-$h{closest_address}
-EOF
-}
-
-sub post_barnet_message {
- my ( $problem, %h ) = @_;
- my $return = 1;
- my $err_msg = "";
-
- my $default_kbid = 14; # This is the default, "Street Scene"
- my $kbid = sprintf( "%050d", Utils::barnet_categories()->{$h{category}} || $default_kbid);
-
- my $geo_code = "$h{easting} $h{northing}";
-
- my $interface = BarnetInterfaces::service::ZLBB_SERVICE_ORDER->new();
-
- my ($nearest_postcode, $nearest_street);
- for ($h{closest_address}) {
- $nearest_postcode = sprintf("%-10s", $1) if /Nearest postcode [^:]+: ((\w{1,4}\s?\w+|\w+))/;
- # use partial postcode or comma as delimiter, strip leading number (possible letter 221B) off too
- # "99 Foo Street, London N11 1XX" becomes Foo Street
- # "99 Foo Street N11 1XX" becomes Foo Street
- $nearest_street = sprintf("%-30s", $1) if /Nearest road [^:]+: (?:\d+\w? )?(.*?)(\b[A-Z]+\d|,|$)/m;
- }
- my $postcode = $h{postcode} || $nearest_postcode; # use given postcode if available
-
- # note: endpoint can be of form 'https://username:password@:url'
- my $council_config = FixMyStreet::App->model("DB::Open311conf")->search( { area_id => COUNCIL_ID_BARNET} )->first;
- if ($council_config and $council_config->endpoint) {
- $interface->set_proxy($council_config->endpoint);
- # Barnet web service doesn't like namespaces in the elements so use a prefix
- $interface->set_prefix('urn');
- } else {
- die "Barnet webservice FAIL: looks like you're missing some config data: no endpoint (URL) found for area_id=" . COUNCIL_ID_BARNET;
- }
-
- eval {
- my $result = $interface->Z_CRM_SERVICE_ORDER_CREATE( {
- ET_RETURN => { # ignored by server
- item => {
- TYPE => "", ID => "", NUMBER => "", MESSAGE => "", LOG_NO => "", LOG_MSG_NO => "",
- MESSAGE_V1 => "", MESSAGE_V2 => "", MESSAGE_V3 => "", MESSAGE_V4 => "", PARAMETER => "",
- ROW => "", FIELD => "", SYSTEM => "",
- },
- },
- IT_PROBLEM_DESC => { # MyTypes::TABLE_OF_CRMT_SERVICE_REQUEST_TEXT
- item => [ # MyTypes::CRMT_SERVICE_REQUEST_TEXT
- map { { TEXT_LINE => $_ } } split_text_with_entities(ent(encode_utf8($h{message})), 132) # char132
- ],
- },
- IV_CUST_EMAIL => truncate_string_with_entities(ent(encode_utf8($h{email})), 241), # char241
- IV_CUST_NAME => truncate_string_with_entities(ent(encode_utf8($h{name})), 50), # char50
- IV_KBID => $kbid, # char50
- IV_PROBLEM_ID => $h{id}, # char35
- IV_PROBLEM_LOC => { # MyTypes::BAPI_TTET_ADDRESS_COM
- COUNTRY2 => 'GB', # char2
- REGION => "", # char3
- COUNTY => "", # char30
- CITY => "", # char30
- POSTALCODE => $postcode, # char10
- STREET => $nearest_street, # char30
- STREETNUMBER => "", # char5
- GEOCODE => $geo_code, # char32
- },
- IV_PROBLEM_SUB => truncate_string_with_entities(ent(encode_utf8($h{title})), 40), # char40
- },
- );
- if ($result) {
- # currently not using this: get_EV_ORDER_GUID (maybe that's the customer number in the CRM)
- if (my $barnet_id = $result->get_EV_ORDER_NO()) {
- $problem->external_id( $barnet_id );
- $problem->external_body( 'Barnet Borough Council' ); # better to use $problem->body()?
- $problem->send_method_used('barnet');
- $return = 0;
- } else {
- my @returned_items = split /<item[^>]*>/, $result->get_ET_RETURN;
- my @messages = ();
- foreach my $item (@returned_items) {
- if ($item=~/<MESSAGE [^>]*>\s*(\S.*?)<\/MESSAGE>/) { # if there's a non-null MESSAGE in there, grab it
- push @messages, $1; # best stab at extracting useful error message back from convoluted response
- }
- }
- push @messages, "service returned no external id" unless @messages;
- $err_msg = "Failed (problem id $h{id}): " . join(" \n ", @messages);
- }
- } else {
- my %fault = (
- 'code' => $result->get_faultcode(),
- 'actor' => $result->get_faultactor(),
- 'string' => $result->get_faultstring(),
- 'detail' => $result->get_detail(), # possibly only contains debug info
- );
- $fault{$_}=~s/^\s*|\s*$//g foreach keys %fault;
- $fault{actor}&&=" (actor: $fault{actor})";
- $fault{'detail'} &&= "\n" . $fault{'detail'};
- $err_msg = "Failed (problem id $h{id}): Fault $fault{code}$fault{actor}\n$fault{string}$fault{detail}";
- }
-
- };
- print "$err_msg\n" if $err_msg;
- if ($@) {
- my $e = shift;
- print "Caught an error: $@\n";
- }
- return ($return, $err_msg);
-}
-
-# London
-
-sub construct_london_message {
- my %h = @_;
- return <<EOF,
-A user of FixMyStreet has submitted the following report of a local
-problem that they believe might require your attention.
-
-Subject: $h{title}
-
-Details: $h{detail}
-
-$h{fuzzy}, or to provide an update on the problem, please visit the
-following link:
-
-$h{url}
-
-$h{closest_address}
-Yours,
-The FixMyStreet team
-EOF
-}
-
-sub post_london_report {
- my ( $problem, %h ) = @_;
- my $phone = $h{phone};
- my $mobile = '';
- if ($phone && $phone =~ /^\s*07/) {
- $mobile = $phone;
- $phone = '';
- }
- my ($first, $last) = $h{name} =~ /^(\S*)(?: (.*))?$/;
- my %params = (
- Key => mySociety::Config::get('LONDON_REPORTIT_KEY'),
- Signature => Digest::MD5::md5_hex( $h{confirmed} . mySociety::Config::get('LONDON_REPORTIT_SECRET') ),
- Type => Utils::london_categories()->{$h{category}},
- RequestDate => $h{confirmed},
- RequestMethod => 'Web',
- ExternalId => $h{url},
- 'Customer.Title' => '',
- 'Customer.FirstName' => $first,
- 'Customer.Surname' => $last,
- 'Customer.Email' => $h{email},
- 'Customer.Phone' => $phone,
- 'Customer.Mobile' => $mobile,
- 'ProblemDescription' => $h{message},
- );
- if ($h{used_map}) {
- $params{'Location.Latitude'} = $h{latitude};
- $params{'Location.Longitude'} = $h{longitude};
- } elsif (mySociety::PostcodeUtil::is_valid_postcode($h{query})) {
- # Didn't use map, and entered postcode, so use that.
- $params{'Location.Postcode'} = $h{query};
- } else {
- # Otherwise, lat/lon is all we have, even if it's wrong.
- $params{'Location.Latitude'} = $h{latitude};
- $params{'Location.Longitude'} = $h{longitude};
- }
- if ($h{has_photo}) {
- $params{'Document1.Name'} = 'Photograph';
- $params{'Document1.MimeType'} = 'image/jpeg';
- $params{'Document1.URL'} = $h{image_url};
- $params{'Document1.URLPublic'} = 'true';
- }
- my $browser = LWP::UserAgent->new;
- my $response = $browser->post( mySociety::Config::get('LONDON_REPORTIT_URL'), \%params );
- my $out = $response->content;
- if ($response->code ne 200) {
- print "Failed to post $h{id} to London API, response was " . $response->code . " $out\n";
- return 1;
- }
- my ($id) = $out =~ /<caseid>(.*?)<\/caseid>/;
- my ($org) = $out =~ /<organisation>(.*?)<\/organisation>/;
- my ($team) = $out =~ /<team>(.*?)<\/team>/;
-
- $org = london_lookup($org);
- $problem->external_id( $id );
- $problem->external_body( $org );
- $problem->external_team( $team );
- return 0;
-}
-
-# Nearest things
-
-sub london_lookup {
- my $org = shift || '';
- my $str = "Unknown ($org)";
- open(FP, "$FindBin::Bin/../data/dft.csv");
- while (<FP>) {
- /^(.*?),(.*)/;
- if ($org eq $1) {
- $str = $2;
- last;
- }
- }
- close FP;
- return $str;
-}
-
-# for barnet webservice: max-length fields require truncate and split
-
-# truncate_string_with_entities
-# args: text to truncate
-# max number of chars
-# returns: string truncated
-# Note: must not partially truncate an entity (e.g., &amp;)
-sub truncate_string_with_entities {
- my ($str, $max_len) = @_;
- my $retVal = "";
- foreach my $chunk (split /(\&(?:\#\d+|\w+);)/, $str) {
- if ($chunk=~/^\&(\#\d+|\w+);$/){
- my $next = $retVal.$chunk;
- last if length $next > $max_len;
- $retVal=$next
- } else {
- $retVal.=$chunk;
- if (length $retVal > $max_len) {
- $retVal = substr($retVal, 0, $max_len);
- last
- }
- }
- }
- return $retVal
-}
-
-# split_text_with_entities into lines
-# args: text to be broken into lines
-# max length (option: uses constant MAX_LINE_LENGTH)
-# returns: array of lines
-# Must not to split an entity (e.g., &amp;)
-# Not worrying about hyphenating here, since a word is only ever split if
-# it's longer than the whole line, which is uncommon in genuine problem reports
-sub split_text_with_entities {
- my ($text, $max_line_length) = @_;
- $max_line_length ||= MAX_LINE_LENGTH;
- my @lines;
- foreach my $line (split "\n", $text) {
- while (length $line > $max_line_length) {
- if (! ($line =~ s/^(.{1,$max_line_length})\s// # break on a space
- or $line =~ s/^(.{1,$max_line_length})(\&(\#\d+|\w+);)/$2/ # break before an entity
- or $line =~ s/(.{$max_line_length})//)) { # break the word ruthlessly
- $line =~ s/(.*)//; # otherwise gobble whole line (which is now shorter than max length)
- }
- push @lines, $1;
- }
- push @lines, $line;
- }
- return @lines;
-}
+use FixMyStreet::App;
-# tests send_fail_count agains cutoff limit
-# args: problem (row from problem db)
-# returns false if there is no cutoff, otherwise error message
-sub does_exceed_cutoff_limit {
- my ($problem, $council_name) = @_;
- my $err_msg = "";
- if ($problem->send_fail_count >= SEND_FAIL_RETRIES_CUTOFF) {
- $sending_skipped_by_method{$council_name || '?'}++;
- $council_name &&= " to $council_name";
- $err_msg = "skipped: problem id=" . $problem->id . " send$council_name has failed "
- . $problem->send_fail_count . " times, cutoff is " . SEND_FAIL_RETRIES_CUTOFF;
- }
- return $err_msg;
-}
+my $site = CronFns::site(mySociety::Config::get('BASE_URL'));
+CronFns::language($site);
-# update_send_fail_data records the failure (of a webservice send)
-# args: problem (row from problem db)
-# returns: no return value (updates record)
-sub update_send_fail_data {
- my ($problem, $err_msg) = @_;
- $problem->update( {
- send_fail_count => $problem->send_fail_count + 1,
- send_fail_timestamp => \'ms_current_timestamp()',
- send_fail_reason => $err_msg
- } );
-}
+FixMyStreet::App->model('DB::Problem')->send_reports();
diff --git a/conf/crontab.ugly b/conf/crontab.ugly
index 1a4e82cee..087021fd1 100644
--- a/conf/crontab.ugly
+++ b/conf/crontab.ugly
@@ -22,12 +22,15 @@ MAILTO=!!(*= $user *)!!@mysociety.org
#2 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/send-alerts.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper send-alerts" || echo "stalled?"
0,30 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/send-questionnaires.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper send-questionnaires" || echo "stalled?"
-!!(* } elsif (($vhost eq 'www.fixmystreet.com') || ($vhost eq 'matthew.fixmystreet.com') || ($vhost eq 'integration-staging.fixmystreet.com')) { *)!!
+!!(* } elsif (($vhost eq 'www.fixmystreet.com') || ($vhost eq 'bromley.test.mysociety.org') || ($vhost eq 'integration-staging.fixmystreet.com')) { *)!!
5,10,15,20,25,30,35,40,45,50,55 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/send-reports.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper send-reports" || echo "stalled?"
0 0-8,10,11,13,14,16,17,19-23 * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/send-reports.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper send-reports" || echo "stalled?"
0 9,12,15,18 * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/send-reports.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper send-reports --verbose" || echo "stalled?"
2 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/send-alerts.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper send-alerts" || echo "stalled?"
0,30 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/send-questionnaires.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper send-questionnaires" || echo "stalled?"
+5,10,15,20,25,30,35,40,45,50,55 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/send-comments.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper send-comments" || echo "stalled?"
+5,10,15,20,25,30,35,40,45,50,55 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/fetch-comments.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper fetch-comments" || echo "stalled?"
+0,30 * * * * !!(*= $user *)!! run-with-lockfile -n /data/vhost/!!(*= $vhost *)!!/open311-populate-service-list.lock "/data/vhost/!!(*= $vhost *)!!/fixmystreet/bin/cron-wrapper open311-populate-service-list" || echo "stalled?"
!!(* } *)!!
diff --git a/conf/general.yml-example b/conf/general.yml-example
index ec77e6ba8..93b54a3e7 100644
--- a/conf/general.yml-example
+++ b/conf/general.yml-example
@@ -62,4 +62,5 @@ RSS_LIMIT: '20'
# Should problem reports link to the council summary pages?
AREA_LINKS_FROM_PROBLEMS: '0'
-
+# used to override the STAGING SERVER safety mechanism in send-reports
+TESTING_COUNCILS:
diff --git a/data/dashboard.json b/data/dashboard.json
new file mode 100644
index 000000000..1da667356
--- /dev/null
+++ b/data/dashboard.json
@@ -0,0 +1,236 @@
+{
+ "council": { "name": "South Borsetshire District Council" },
+ "wards": {
+ "1": { "id": 1, "name": "West Ward" },
+ "2": { "id": 2, "name": "East Ward" },
+ "3": { "id": 3, "name": "North Ward" },
+ "4": { "id": 4, "name": "South Ward" }
+ },
+ "category_options": [
+ "Abandoned vehicles",
+ "Grafitti",
+ "Potholes",
+ "Street lighting",
+ "Trees",
+ "Other"
+ ],
+ "counts_all": {
+ "wtd": {
+ "total": 10,
+ "planned": 2,
+ "in progress": 1,
+ "investigating": 1,
+ "fixed - council": 3,
+ "fixed_user": 2,
+ "time_to_fix": 2,
+ "time_to_mark": 2,
+ "not_marked": 1
+ },
+ "week": {
+ "total": 21,
+ "planned": 5,
+ "in progress": 3,
+ "investigating": 4,
+ "fixed - council": 6,
+ "fixed_user": 3,
+ "time_to_fix": 3,
+ "time_to_mark": 2,
+ "not_marked": 1
+ },
+ "weeks": {
+ "total": 57,
+ "planned": 5,
+ "in progress": 16,
+ "investigating": 4,
+ "fixed - council": 23,
+ "fixed_user": 9,
+ "time_to_fix": 2,
+ "time_to_mark": 2,
+ "not_marked": 1
+ },
+ "ytd": {
+ "total": 171,
+ "planned": 23,
+ "in progress": 34,
+ "investigating": 9,
+ "fixed - council": 72,
+ "fixed_user": 33,
+ "time_to_fix": 2,
+ "time_to_mark": 2,
+ "not_marked": 1
+ }
+ },
+ "counts_some": {
+ "wtd": {
+ "total": 5,
+ "planned": 0,
+ "in progress": 1,
+ "investigating": 1,
+ "fixed - council": 2,
+ "fixed_user": 1,
+ "time_to_fix": 2,
+ "time_to_mark": 2,
+ "not_marked": 0
+ },
+ "week": {
+ "total": 7,
+ "planned": 1,
+ "in progress": 2,
+ "investigating": 1,
+ "fixed - council": 2,
+ "fixed_user": 1,
+ "time_to_fix": 3,
+ "time_to_mark": 2,
+ "not_marked": 0
+ },
+ "weeks": {
+ "total": 57,
+ "planned": 5,
+ "in progress": 16,
+ "investigating": 4,
+ "fixed - council": 23,
+ "fixed_user": 9,
+ "time_to_fix": 2,
+ "time_to_mark": 2,
+ "not_marked": 1
+ },
+ "ytd": {
+ "total": 57,
+ "planned": 5,
+ "in progress": 16,
+ "investigating": 4,
+ "fixed - council": 23,
+ "fixed_user": 9,
+ "time_to_fix": 2,
+ "time_to_mark": 2,
+ "not_marked": 1
+ }
+ },
+ "lists": {
+ "all": {
+ "1": [
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Fly tipping" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Abandoned car" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Uneven paving" }
+ ],
+ "2": [
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Fly tipping" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Uneven paving" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Abandoned car" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Blocked drain" }
+ ],
+ "3": [
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Fly tipping" },
+ { "id": 0, "title": "Uneven paving" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Abandoned car" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Fly tipping" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Abandoned car" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Uneven paving" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Pothole" }
+ ]
+ },
+ "filtered": {
+ "1": [
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Abandoned car" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Uneven paving" }
+ ],
+ "2": [
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Fly tipping" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Fly tipping" },
+ { "id": 0, "title": "Abandoned car" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Uneven paving" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Pothole" }
+ ],
+ "3": [
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Uneven paving" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Loose kerb" },
+ { "id": 0, "title": "Abandoned car" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Fallen Tree" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Fly tipping" },
+ { "id": 0, "title": "Burst pipe" },
+ { "id": 0, "title": "Abandoned car" },
+ { "id": 0, "title": "Pothole" },
+ { "id": 0, "title": "Uneven paving" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Blocked drain" },
+ { "id": 0, "title": "Pothole" }
+ ]
+ }
+ }
+}
diff --git a/db/schema.sql b/db/schema.sql
index e1205099d..832104991 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -135,7 +135,8 @@ create table users (
phone text,
password text not null default '',
from_council integer, -- id of council user is from or null/0 if not
- flagged boolean not null default 'f'
+ flagged boolean not null default 'f',
+ title text
);
-- Problems reported by users of site
@@ -311,9 +312,15 @@ create table comment (
or problem_state = 'fixed'
or problem_state = 'fixed - council'
or problem_state = 'fixed - user'
- )
+ ),
-- other fields? one to indicate whether this was written by the council
-- and should be highlighted in the display?
+ external_id text,
+ extra text,
+ send_fail_count integer not null default 0,
+ send_fail_reason text,
+ send_fail_timestamp timestamp,
+ whensent timestamp
);
create index comment_user_id_idx on comment(user_id);
@@ -428,5 +435,8 @@ create table open311conf (
endpoint text not null,
jurisdiction text,
api_key text,
- send_method text
+ send_method text,
+ send_comments boolean not null default 'f',
+ comment_user_id int references users(id),
+ suppress_alerts boolean not null default 'f'
);
diff --git a/db/schema_0013-add_external_id_to_comment.sql b/db/schema_0013-add_external_id_to_comment.sql
new file mode 100644
index 000000000..a1cd11c3f
--- /dev/null
+++ b/db/schema_0013-add_external_id_to_comment.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER TABLE comment
+ ADD COLUMN external_id TEXT;
+
+commit;
diff --git a/db/schema_0015-add_extra_to_comment.sql b/db/schema_0015-add_extra_to_comment.sql
new file mode 100644
index 000000000..5c1789ea2
--- /dev/null
+++ b/db/schema_0015-add_extra_to_comment.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER TABLE comment
+ ADD COLUMN extra TEXT;
+
+commit;
diff --git a/db/schema_0016-add_whensent_and_send_fail_to_comment.sql b/db/schema_0016-add_whensent_and_send_fail_to_comment.sql
new file mode 100644
index 000000000..792ff2e38
--- /dev/null
+++ b/db/schema_0016-add_whensent_and_send_fail_to_comment.sql
@@ -0,0 +1,11 @@
+begin;
+
+ALTER table comment
+ ADD column send_fail_count integer not null default 0;
+ALTER table comment
+ ADD column send_fail_reason text;
+ALTER table comment
+ ADD column send_fail_timestamp timestamp;
+ALTER table comment
+ ADD column whensent timestamp;
+commit;
diff --git a/db/schema_0017-add_send_comments_to_open311conf.sql b/db/schema_0017-add_send_comments_to_open311conf.sql
new file mode 100644
index 000000000..0ee015575
--- /dev/null
+++ b/db/schema_0017-add_send_comments_to_open311conf.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER table open311conf
+ ADD column send_comments BOOL NOT NULL DEFAULT 'f';
+
+commit;
diff --git a/db/schema_0018-add_comment_user_to_open311conf.sql b/db/schema_0018-add_comment_user_to_open311conf.sql
new file mode 100644
index 000000000..93851e2a3
--- /dev/null
+++ b/db/schema_0018-add_comment_user_to_open311conf.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER TABLE open311conf
+ ADD COLUMN comment_user_id INT REFERENCES users(id);
+
+commit;
diff --git a/db/schema_0019-add_title_to_users.sql b/db/schema_0019-add_title_to_users.sql
new file mode 100644
index 000000000..1cc7dd16d
--- /dev/null
+++ b/db/schema_0019-add_title_to_users.sql
@@ -0,0 +1,8 @@
+-- These are changes needed to the schema to support moving over to DBIx::Class
+
+begin;
+
+AlTER TABLE users
+ ADD COLUMN title text;
+
+commit;
diff --git a/db/schema_0020-add_suppress_alerts_to_open311.sql b/db/schema_0020-add_suppress_alerts_to_open311.sql
new file mode 100644
index 000000000..26fc54617
--- /dev/null
+++ b/db/schema_0020-add_suppress_alerts_to_open311.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER table open311conf
+ ADD column suppress_alerts BOOL NOT NULL DEFAULT 'f';
+
+commit;
diff --git a/perllib/FixMyStreet/App.pm b/perllib/FixMyStreet/App.pm
index 262379b79..6ccc801ce 100644
--- a/perllib/FixMyStreet/App.pm
+++ b/perllib/FixMyStreet/App.pm
@@ -53,6 +53,7 @@ __PACKAGE__->config(
'Plugin::Session' => { # Catalyst::Plugin::Session::Store::DBIC
dbic_class => 'DB::Session',
expires => 3600 * 24 * 7 * 4, # 4 weeks
+ cookie_secure => 2,
},
'Plugin::Authentication' => {
@@ -180,12 +181,7 @@ sub setup_request {
Memcached::set_namespace( FixMyStreet->config('FMS_DB_NAME') . ":" );
- my $map = $host =~ /^osm\./ ? 'OSM' : $c->req->param('map_override');
- #if ($c->sessionid) {
- # $map = $c->session->{map};
- # $map = undef unless $map eq 'OSM';
- #}
- FixMyStreet::Map::set_map_class( $map );
+ FixMyStreet::Map::set_map_class( $cobrand->map_type || $c->req->param('map_override') );
return $c;
}
@@ -323,6 +319,16 @@ sub send_email_cron {
unpack('h*', random_bytes(5, 1))
);
+ $params->{_parameters_}->{signature} = '';
+ #$params->{_parameters_}->{signature} = $c->view('Email')->render(
+ # $c, 'signature.txt', {
+ # additional_template_paths => [
+ # FixMyStreet->path_to( 'templates', 'email', $c->cobrand->moniker, $c->stash->{lang_code} )->stringify,
+ # FixMyStreet->path_to( 'templates', 'email', $c->cobrand->moniker )->stringify,
+ # ]
+ # }
+ #);
+
my $email = mySociety::Locale::in_gb_locale { mySociety::Email::construct_email($params) };
if ( FixMyStreet->test_mode ) {
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 198acade6..998cb83a8 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -8,6 +8,8 @@ use POSIX qw(strftime strcoll);
use Digest::MD5 qw(md5_hex);
use mySociety::EmailUtil qw(is_valid_email);
+use FixMyStreet::SendReport;
+
=head1 NAME
FixMyStreet::App::Controller::Admin- Catalyst Controller
@@ -338,7 +340,7 @@ sub update_contacts : Private {
} elsif ( $posted eq 'open311' ) {
$c->forward('check_token');
- my %params = map { $_ => $c->req->param($_) } qw/open311_id endpoint jurisdiction api_key area_id/;
+ my %params = map { $_ => $c->req->param($_) || '' } qw/open311_id endpoint jurisdiction api_key area_id send_method send_comments suppress_alerts comment_user_id/;
if ( $params{open311_id} ) {
my $conf = $c->model('DB::Open311Conf')->find( { id => $params{open311_id} } );
@@ -346,6 +348,10 @@ sub update_contacts : Private {
$conf->endpoint( $params{endpoint} );
$conf->jurisdiction( $params{jurisdiction} );
$conf->api_key( $params{api_key} );
+ $conf->send_method( $params{send_method} );
+ $conf->send_comments( $params{send_comments} || 0);
+ $conf->suppress_alerts( $params{suppress_alerts} || 0);
+ $conf->comment_user_id( $params{comment_user_id} || undef );
$conf->update();
@@ -356,6 +362,10 @@ sub update_contacts : Private {
$conf->endpoint( $params{endpoint} );
$conf->jurisdiction( $params{jurisdiction} );
$conf->api_key( $params{api_key} );
+ $conf->send_method( $params{send_method} );
+ $conf->send_comments( $params{send_comments} || 0);
+ $conf->suppress_alerts( $params{suppress_alerts} || 0);
+ $conf->comment_user_id( $params{comment_user_id} || undef );
$conf->insert();
@@ -378,6 +388,9 @@ sub display_contacts : Private {
$c->stash->{contacts} = $contacts;
+ my @methods = map { $_ =~ s/FixMyStreet::SendReport:://; $_ } keys %{ FixMyStreet::SendReport->get_senders };
+ $c->stash->{send_methods} = \@methods;
+
my $open311 = $c->model('DB::Open311Conf')->search(
{ area_id => $area_id }
);
@@ -492,7 +505,7 @@ sub search_reports : Path('search_reports') {
'me.id' => $search_n,
'user.email' => { ilike => $like_search },
'me.name' => { ilike => $like_search },
- title => { ilike => $like_search },
+ 'me.title' => { ilike => $like_search },
detail => { ilike => $like_search },
council => { like => $like_search },
cobrand_data => { like => $like_search },
@@ -571,8 +584,7 @@ sub report_edit : Path('report_edit') : Args(1) {
}
)->first;
- $c->detach( '/page_error_404_not_found',
- [ _('The requested URL was not found on this server.') ] )
+ $c->detach( '/page_error_404_not_found' )
unless $problem;
$c->stash->{problem} = $problem;
@@ -734,8 +746,7 @@ sub update_edit : Path('update_edit') : Args(1) {
}
)->first;
- $c->detach( '/page_error_404_not_found',
- [ _('The requested URL was not found on this server.') ] )
+ $c->detach( '/page_error_404_not_found' )
unless $update;
$c->forward('get_token');
@@ -1068,7 +1079,7 @@ sub check_token : Private {
my ( $self, $c ) = @_;
if ( !$c->req->param('token') || $c->req->param('token' ) ne $c->stash->{token} ) {
- $c->detach( '/page_error_404_not_found', [ _('The requested URL was not found on this server.') ] );
+ $c->detach( '/page_error_404_not_found' );
}
return 1;
@@ -1238,7 +1249,7 @@ sub check_page_allowed : Private {
$page ||= 'summary';
if ( !grep { $_ eq $page } keys %{ $c->stash->{allowed_pages} } ) {
- $c->detach( '/page_error_404_not_found', [ _('The requested URL was not found on this server.') ] );
+ $c->detach( '/page_error_404_not_found' );
}
return 1;
diff --git a/perllib/FixMyStreet/App/Controller/Alert.pm b/perllib/FixMyStreet/App/Controller/Alert.pm
index 2698c6ac0..6e9ae819c 100644
--- a/perllib/FixMyStreet/App/Controller/Alert.pm
+++ b/perllib/FixMyStreet/App/Controller/Alert.pm
@@ -191,7 +191,11 @@ sub create_alert : Private {
$alert->insert();
}
- $alert->confirm() if $c->user && $c->user->id == $alert->user->id;
+ if ( $c->user && $c->user->id == $alert->user->id ) {
+ $alert->confirm();
+ } else {
+ $alert->confirmed(0);
+ }
$c->stash->{alert} = $alert;
}
@@ -470,6 +474,7 @@ sub add_recent_photos : Private {
{
$c->stash->{photos} = $c->cobrand->recent_photos(
+ 'alert',
$num_photos,
$c->stash->{latitude},
$c->stash->{longitude},
@@ -477,7 +482,7 @@ sub add_recent_photos : Private {
);
}
else {
- $c->stash->{photos} = $c->cobrand->recent_photos($num_photos);
+ $c->stash->{photos} = $c->cobrand->recent_photos('alert', $num_photos);
}
return 1;
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm
index e110cf8d7..3047b195c 100644
--- a/perllib/FixMyStreet/App/Controller/Around.pm
+++ b/perllib/FixMyStreet/App/Controller/Around.pm
@@ -186,10 +186,11 @@ sub display_location : Private {
@pins = map {
# Here we might have a DB::Problem or a DB::Nearby, we always want the problem.
my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_;
+ my $colour = $c->cobrand->pin_colour( $p, 'around' );
{
latitude => $p->latitude,
longitude => $p->longitude,
- colour => 'yellow', # $p->is_fixed ? 'green' : 'red',
+ colour => $colour,
id => $p->id,
title => $p->title,
}
diff --git a/perllib/FixMyStreet/App/Controller/Contact.pm b/perllib/FixMyStreet/App/Controller/Contact.pm
index f28d37989..91580f05a 100644
--- a/perllib/FixMyStreet/App/Controller/Contact.pm
+++ b/perllib/FixMyStreet/App/Controller/Contact.pm
@@ -183,7 +183,7 @@ generally required to stash
sub setup_request : Private {
my ( $self, $c ) = @_;
- $c->stash->{contact_email} = $c->cobrand->contact_email;
+ $c->stash->{contact_email} = $c->cobrand->contact_email( 'contact' );
$c->stash->{contact_email} =~ s/\@/&#64;/;
for my $param (qw/em subject message/) {
@@ -205,7 +205,7 @@ Sends the email
sub send_email : Private {
my ( $self, $c ) = @_;
- my $recipient = $c->cobrand->contact_email();
+ my $recipient = $c->cobrand->contact_email( 'contact' );
my $recipient_name = $c->cobrand->contact_name();
$c->stash->{host} = $c->req->header('HOST');
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
new file mode 100644
index 000000000..e4266dc64
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -0,0 +1,245 @@
+package FixMyStreet::App::Controller::Dashboard;
+use Moose;
+use namespace::autoclean;
+
+use DateTime;
+use File::Slurp;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+FixMyStreet::App::Controller::Dashboard - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+sub example : Local : Args(0) {
+ my ( $self, $c ) = @_;
+ $c->stash->{template} = 'dashboard/index.html';
+
+ $c->stash->{children} = {};
+ for my $i (1..3) {
+ $c->stash->{children}{$i} = { id => $i, name => "Ward $i" };
+ }
+
+ # TODO Set up manual version of what the below would do
+ #$c->forward( '/report/new/setup_categories_and_councils' );
+
+ # See if we've had anything from the dropdowns - perhaps vary results if so
+ $c->stash->{ward} = $c->req->param('ward');
+ $c->stash->{category} = $c->req->param('category');
+ $c->stash->{q_state} = $c->req->param('state');
+
+ eval {
+ my $data = File::Slurp::read_file(
+ FixMyStreet->path_to( 'data/dashboard.json' )->stringify
+ );
+ my $j = JSON->new->utf8->decode($data);
+ if ( !$c->stash->{ward} && !$c->stash->{category} ) {
+ $c->stash->{problems} = $j->{counts_all};
+ } else {
+ $c->stash->{problems} = $j->{counts_some};
+ }
+ $c->stash->{council} = $j->{council};
+ $c->stash->{children} = $j->{wards};
+ $c->stash->{category_options} = $j->{category_options};
+ if ( lc($c->stash->{q_state}) eq 'all' or !$c->stash->{q_state} ) {
+ $c->stash->{lists} = $j->{lists}->{all};
+ } else {
+ $c->stash->{lists} = $j->{lists}->{filtered};
+ }
+ };
+ if ($@) {
+ $c->stash->{message} = _("There was a problem showing this page. Please try again later.") . ' ' .
+ sprintf(_('The error was: %s'), $@);
+ $c->stash->{template} = 'errors/generic.html';
+ }
+}
+
+=head2 check_page_allowed
+
+Checks if we can view this page, and if not redirect to 404.
+
+=cut
+
+sub check_page_allowed : Private {
+ my ( $self, $c ) = @_;
+
+ $c->detach( '/auth/redirect' ) unless $c->user_exists;
+
+ $c->detach( '/page_error_404_not_found' )
+ unless $c->user_exists && $c->user->from_council;
+
+ return $c->user->from_council;
+}
+
+=head2 index
+
+Show the dashboard table.
+
+=cut
+
+sub index : Path : Args(0) {
+ my ( $self, $c ) = @_;
+
+ my $council = $c->forward('check_page_allowed');
+
+ # Set up the data for the dropdowns
+
+ my $council_detail = mySociety::MaPit::call('area', $council );
+ $c->stash->{council} = $council_detail;
+
+ my $children = mySociety::MaPit::call('area/children', $council,
+ type => $mySociety::VotingArea::council_child_types,
+ );
+ $c->stash->{children} = $children;
+
+ $c->stash->{all_councils} = { $council => $council_detail };
+ $c->forward( '/report/new/setup_categories_and_councils' );
+
+ # See if we've had anything from the dropdowns
+
+ $c->stash->{ward} = $c->req->param('ward');
+ $c->stash->{category} = $c->req->param('category');
+
+ my %where = (
+ council => $council, # XXX This will break in a two tier council. Restriction needs looking at...
+ 'problem.state' => [ FixMyStreet::DB::Result::Problem->visible_states() ],
+ );
+ $where{areas} = { 'like', '%,' . $c->stash->{ward} . ',%' }
+ if $c->stash->{ward};
+ $where{category} = $c->stash->{category}
+ if $c->stash->{category};
+ $c->stash->{where} = \%where;
+ my $prob_where = { %where };
+ $prob_where->{state} = $prob_where->{'problem.state'};
+ delete $prob_where->{'problem.state'};
+ $c->stash->{prob_where} = $prob_where;
+
+ my %counts;
+ my $t = DateTime->today;
+ $counts{wtd} = $c->forward( 'updates_search', [ $t->subtract( days => $t->dow - 1 ) ] );
+ $counts{week} = $c->forward( 'updates_search', [ DateTime->now->subtract( weeks => 1 ) ] );
+ $counts{weeks} = $c->forward( 'updates_search', [ DateTime->now->subtract( weeks => 4 ) ] );
+ $counts{ytd} = $c->forward( 'updates_search', [ DateTime->today->set( day => 1, month => 1 ) ] );
+
+ $c->stash->{problems} = \%counts;
+
+ # List of reports underneath summary table
+
+ $c->stash->{q_state} = $c->req->param('state') || '';
+ if ( $c->stash->{q_state} eq 'fixed' ) {
+ $prob_where->{state} = [ FixMyStreet::DB::Result::Problem->fixed_states() ];
+ } elsif ( $c->stash->{q_state} ) {
+ $prob_where->{state} = $c->stash->{q_state};
+ }
+ my $params = {
+ %$prob_where,
+ 'me.confirmed' => { '>=', DateTime->now->subtract( days => 30 ) },
+ };
+ my @problems = $c->cobrand->problems->search( $params )->all;
+ my %problems;
+ foreach (@problems) {
+ if ($_->confirmed >= DateTime->now->subtract(days => 7)) {
+ push @{$problems{1}}, $_;
+ } elsif ($_->confirmed >= DateTime->now->subtract(days => 14)) {
+ push @{$problems{2}}, $_;
+ } else {
+ push @{$problems{3}}, $_;
+ }
+ }
+ $c->stash->{lists} = \%problems;
+}
+
+sub updates_search : Private {
+ my ( $self, $c, $time ) = @_;
+
+ my $params = {
+ %{$c->stash->{where}},
+ 'me.confirmed' => { '>=', $time },
+ };
+
+ my $comments = $c->model('DB::Comment')->search(
+ $params,
+ {
+ group_by => [ 'problem_state' ],
+ select => [ 'problem_state', { count => 'me.id' } ],
+ as => [ qw/state state_count/ ],
+ join => 'problem'
+ }
+ );
+
+ my %counts =
+ map { ($_->state||'-') => $_->get_column('state_count') } $comments->all;
+ %counts =
+ map { $_ => $counts{$_} || 0 }
+ ('confirmed', 'investigating', 'in progress', 'closed', 'fixed - council',
+ 'fixed - user', 'fixed', 'unconfirmed', 'hidden',
+ 'partial', 'planned');
+
+ for my $vars (
+ [ 'time_to_fix', 'fixed - council' ],
+ [ 'time_to_mark', 'in progress', 'planned', 'investigating', 'closed' ],
+ ) {
+ my $col = shift @$vars;
+ my $substmt = "select min(id) from comment where me.problem_id=comment.problem_id and problem_state in ('"
+ . join("','", @$vars) . "')";
+ $comments = $c->model('DB::Comment')->search(
+ { %$params,
+ problem_state => $vars,
+ 'me.id' => \"= ($substmt)",
+ },
+ {
+ select => [
+ { count => 'me.id' },
+ { avg => { extract => "epoch from me.confirmed-problem.confirmed" } },
+ ],
+ as => [ qw/state_count time/ ],
+ join => 'problem'
+ }
+ )->first;
+ $counts{$col} = int( ($comments->get_column('time')||0) / 60 / 60 / 24 + 0.5 );
+ }
+
+ $counts{fixed_user} = $c->model('DB::Comment')->search(
+ { %$params, mark_fixed => 1, problem_state => undef }, { join => 'problem' }
+ )->count;
+
+ $params = {
+ %{$c->stash->{prob_where}},
+ 'me.confirmed' => { '>=', $time },
+ };
+ $counts{total} = $c->cobrand->problems->search( $params )->count;
+
+ $params = {
+ %{$c->stash->{prob_where}},
+ 'me.confirmed' => { '>=', $time },
+ state => 'confirmed',
+ '(select min(id) from comment where me.id=problem_id and problem_state is not null)' => undef,
+ };
+ $counts{not_marked} = $c->cobrand->problems->search( $params )->count;
+
+ return \%counts;
+}
+
+=head1 AUTHOR
+
+Matthew Somerville
+
+=head1 LICENSE
+
+Copyright (c) 2012 UK Citizens Online Democracy. All rights reserved.
+Licensed under the Affero GPL.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
+
diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm
index 3de83b265..c00264315 100644
--- a/perllib/FixMyStreet/App/Controller/My.pm
+++ b/perllib/FixMyStreet/App/Controller/My.pm
@@ -48,7 +48,7 @@ sub my : Path : Args(0) {
push @$pins, {
latitude => $problem->latitude,
longitude => $problem->longitude,
- colour => $problem->is_fixed ? 'green' : 'red',
+ colour => $c->cobrand->pin_colour( $problem, 'my' ),
id => $problem->id,
title => $problem->title,
};
diff --git a/perllib/FixMyStreet/App/Controller/Photo.pm b/perllib/FixMyStreet/App/Controller/Photo.pm
index 5d5832b08..fc4c3fde7 100644
--- a/perllib/FixMyStreet/App/Controller/Photo.pm
+++ b/perllib/FixMyStreet/App/Controller/Photo.pm
@@ -5,6 +5,9 @@ use namespace::autoclean;
BEGIN {extends 'Catalyst::Controller'; }
use DateTime::Format::HTTP;
+use Digest::SHA1 qw(sha1_hex);
+use File::Path;
+use File::Slurp;
use Path::Class;
=head1 NAME
@@ -94,6 +97,10 @@ sub index :LocalRegex('^(c/)?(\d+)(?:\.(full|tn|fp))?\.jpeg$') {
sub output : Private {
my ( $self, $c, $photo ) = @_;
+ # Save to file
+ File::Path::make_path( FixMyStreet->path_to( 'web', 'photo', 'c' )->stringify );
+ File::Slurp::write_file( FixMyStreet->path_to( 'web', $c->req->path )->stringify, \$photo );
+
my $dt = DateTime->now()->add( years => 1 );
$c->res->content_type( 'image/jpeg' );
@@ -136,6 +143,96 @@ sub _crop {
return $blobs[0];
}
+=head2 process_photo
+
+Handle the photo - either checking and storing it after an upload or retrieving
+it from the cache.
+
+Store any error message onto 'photo_error' in stash.
+=cut
+
+sub process_photo : Private {
+ my ( $self, $c ) = @_;
+
+ return
+ $c->forward('process_photo_upload')
+ || $c->forward('process_photo_cache')
+ || 1; # always return true
+}
+
+sub process_photo_upload : Private {
+ my ( $self, $c ) = @_;
+
+ # check for upload or return
+ my $upload = $c->req->upload('photo')
+ || return;
+
+ # check that the photo is a jpeg
+ my $ct = $upload->type;
+ $ct =~ s/x-citrix-//; # Thanks, Citrix
+ # Had a report of a JPEG from an Android 2.1 coming through as a byte stream
+ unless ( $ct eq 'image/jpeg' || $ct eq 'image/pjpeg' || $ct eq 'application/octet-stream' ) {
+ $c->log->info('Bad photo tried to upload, type=' . $ct);
+ $c->stash->{photo_error} = _('Please upload a JPEG image only');
+ return;
+ }
+
+ # get the photo into a variable
+ my $photo_blob = eval {
+ my $filename = $upload->tempname;
+ my $out = `jhead -se -autorot $filename 2>&1`;
+ die _("Please upload a JPEG image only"."\n") if $out =~ /Not JPEG:/;
+ my $photo = $upload->slurp;
+ return $photo;
+ };
+ if ( my $error = $@ ) {
+ my $format = _(
+"That image doesn't appear to have uploaded correctly (%s), please try again."
+ );
+ $c->stash->{photo_error} = sprintf( $format, $error );
+ return;
+ }
+
+ # we have an image we can use - save it to the upload dir for storage
+ my $cache_dir = dir( $c->config->{UPLOAD_DIR} );
+ $cache_dir->mkpath;
+ unless ( -d $cache_dir && -w $cache_dir ) {
+ warn "Can't find/write to photo cache directory '$cache_dir'";
+ return;
+ }
+
+ my $fileid = sha1_hex($photo_blob);
+ $upload->copy_to( file($cache_dir, $fileid . '.jpeg') );
+
+ # stick the hash on the stash, so don't have to reupload in case of error
+ $c->stash->{upload_fileid} = $fileid;
+
+ return 1;
+}
+
+=head2 process_photo_cache
+
+Look for the upload_fileid parameter and check it matches a file on disk. If it
+does return true and put fileid on stash, otherwise false.
+
+=cut
+
+sub process_photo_cache : Private {
+ my ( $self, $c ) = @_;
+
+ # get the fileid and make sure it is just a hex number
+ my $fileid = $c->req->param('upload_fileid') || '';
+ $fileid =~ s{[^0-9a-f]}{}gi;
+ return unless $fileid;
+
+ my $file = file( $c->config->{UPLOAD_DIR}, "$fileid.jpeg" );
+ return unless -e $file;
+
+ $c->stash->{upload_fileid} = $fileid;
+ return 1;
+}
+
+
=head1 AUTHOR
Struan Donald
diff --git a/perllib/FixMyStreet/App/Controller/Questionnaire.pm b/perllib/FixMyStreet/App/Controller/Questionnaire.pm
index 6ed7ddd9d..6aa4f7604 100755
--- a/perllib/FixMyStreet/App/Controller/Questionnaire.pm
+++ b/perllib/FixMyStreet/App/Controller/Questionnaire.pm
@@ -244,7 +244,7 @@ sub process_questionnaire : Private {
push @errors, _('Please provide some explanation as to why you\'re reopening this report')
if $c->stash->{been_fixed} eq 'No' && $c->stash->{problem}->is_fixed() && !$c->stash->{update};
- $c->forward('/report/new/process_photo');
+ $c->forward('/photo/process_photo');
push @errors, $c->stash->{photo_error}
if $c->stash->{photo_error};
@@ -294,7 +294,7 @@ sub display : Private {
pins => [ {
latitude => $problem->latitude,
longitude => $problem->longitude,
- colour => $problem->is_fixed() ? 'green' : 'red',
+ colour => $c->cobrand->pin_colour( $problem, 'questionnaire' ),
} ],
);
}
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index afe180c29..cca625bd5 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -153,6 +153,36 @@ sub generate_map_tags : Private {
return 1;
}
+sub delete :Local :Args(1) {
+ my ( $self, $c, $id ) = @_;
+
+ $c->forward( 'load_problem_or_display_error', [ $id ] );
+ my $p = $c->stash->{problem};
+
+ my $uri = $c->uri_for( '/report', $id );
+
+ return $c->res->redirect($uri) unless $c->user_exists;
+
+ my $council = $c->user->obj->from_council;
+ return $c->res->redirect($uri) unless $council;
+
+ my %councils = map { $_ => 1 } @{$p->councils};
+ return $c->res->redirect($uri) unless $councils{$council};
+
+ $p->state('hidden');
+ $p->lastupdate( \'ms_current_timestamp()' );
+ $p->update;
+
+ $c->model('DB::AdminLog')->create( {
+ admin_user => $c->user->email,
+ object_type => 'problem',
+ action => 'state_change',
+ object_id => $id,
+ } );
+
+ return $c->res->redirect($uri);
+}
+
__PACKAGE__->meta->make_immutable;
1;
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 6d22a0556..daaaca499 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -5,7 +5,6 @@ use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller'; }
use FixMyStreet::Geocode;
-use Digest::SHA1 qw(sha1_hex);
use Encode;
use Image::Magick;
use List::MoreUtils qw(uniq);
@@ -99,7 +98,7 @@ sub report_new : Path : Args(0) {
return unless $c->forward('check_form_submitted');
$c->forward('process_user');
$c->forward('process_report');
- $c->forward('process_photo');
+ $c->forward('/photo/process_photo');
return unless $c->forward('check_for_errors');
$c->forward('save_user_and_report');
$c->forward('redirect_or_confirm_creation');
@@ -126,12 +125,16 @@ sub report_form_ajax : Path('ajax') : Args(0) {
my $category = $c->render_fragment( 'report/new/category.html');
my $councils_text = $c->render_fragment( 'report/new/councils_text.html');
my $has_open311 = keys %{ $c->stash->{category_extras} };
+ my $extra_name_info = $c->stash->{extra_name_info}
+ ? $c->render_fragment('report/new/extra_name.html')
+ : '';
my $body = JSON->new->utf8(1)->encode(
{
councils_text => $councils_text,
category => $category,
has_open311 => $has_open311,
+ extra_name_info => $extra_name_info,
}
);
@@ -156,7 +159,7 @@ sub category_extras_ajax : Path('category_extras') : Args(0) {
$c->forward('setup_categories_and_councils');
my $category_extra = '';
- if ( $c->stash->{category_extras}->{ $c->req->param('category') } ) {
+ if ( $c->stash->{category_extras}->{ $c->req->param('category') } && @{ $c->stash->{category_extras}->{ $c->req->param('category') } } >= 1 ) {
$c->stash->{report_meta} = {};
$c->stash->{report} = { category => $c->req->param('category') };
$c->stash->{category_extras} = { $c->req->param('category' ) => $c->stash->{category_extras}->{ $c->req->param('category') } };
@@ -211,7 +214,7 @@ sub report_import : Path('/import') {
}
# handle the photo upload
- $c->forward( 'process_photo_upload' );
+ $c->forward( '/photo/process_photo_upload' );
my $fileid = $c->stash->{upload_fileid};
if ( my $error = $c->stash->{photo_error} ) {
push @errors, $error;
@@ -396,6 +399,13 @@ sub initialize_report : Private {
}
+ if ( $c->req->param('first_name') && $c->req->param('last_name') ) {
+ $c->stash->{first_name} = $c->req->param('first_name');
+ $c->stash->{last_name} = $c->req->param('last_name');
+
+ $c->req->param( 'name', sprintf( '%s %s', $c->req->param('first_name'), $c->req->param('last_name') ) );
+ }
+
# Capture whether the map was used
$report->used_map( $c->req->param('skipped') ? 0 : 1 );
@@ -544,7 +554,7 @@ sub setup_categories_and_councils : Private {
);
$category_label = _('Property type:');
- } elsif ($first_council->{type} eq 'LBO') {
+ } elsif ($first_council->{id} != 2482 && $first_council->{type} eq 'LBO') {
$area_ids_to_list{ $first_council->{id} } = 1;
my @local_categories;
@@ -557,7 +567,7 @@ sub setup_categories_and_councils : Private {
_('-- Pick a category --'),
@local_categories
);
- $category_label = _('Category:');
+ $category_label = _('Category');
} else {
@@ -585,9 +595,10 @@ sub setup_categories_and_councils : Private {
}
if (@category_options) {
- @category_options =
- ( _('-- Pick a category --'), @category_options, _('Other') );
- $category_label = _('Category:');
+ @category_options = ( _('-- Pick a category --'), @category_options );
+ push @category_options, _('Other')
+ unless $first_council->{id} == 2482;
+ $category_label = _('Category');
}
}
@@ -597,6 +608,7 @@ sub setup_categories_and_councils : Private {
$c->stash->{category_options} = \@category_options;
$c->stash->{category_extras} = \%category_extras;
$c->stash->{category_extras_json} = encode_json \%category_extras;
+ $c->stash->{extra_name_info} = $first_council->{id} == 2482 ? 1 : 0;
my @missing_details_councils =
grep { !$area_ids_to_list{$_} } #
@@ -639,9 +651,10 @@ sub process_user : Private {
# The user is already signed in
if ( $c->user_exists ) {
my $user = $c->user->obj;
- my %params = map { $_ => scalar $c->req->param($_) } ( 'name', 'phone' );
+ my %params = map { $_ => scalar $c->req->param($_) } ( 'name', 'phone', 'fms_extra_title' );
$user->name( Utils::trim_text( $params{name} ) ) if $params{name};
$user->phone( Utils::trim_text( $params{phone} ) );
+ $user->title( Utils::trim_text( $params{fms_extra_title} ) );
$report->user( $user );
$report->name( $user->name );
return 1;
@@ -649,7 +662,7 @@ sub process_user : Private {
# Extract all the params to a hash to make them easier to work with
my %params = map { $_ => scalar $c->req->param($_) }
- ( 'email', 'name', 'phone', 'password_register' );
+ ( 'email', 'name', 'phone', 'password_register', 'fms_extra_title' );
# cleanup the email address
my $email = $params{email} ? lc $params{email} : '';
@@ -677,6 +690,7 @@ sub process_user : Private {
$report->user->phone( Utils::trim_text( $params{phone} ) );
$report->user->password( Utils::trim_text( $params{password_register} ) )
if $params{password_register};
+ $report->user->title( Utils::trim_text( $params{fms_extra_title} ) );
$report->name( Utils::trim_text( $params{name} ) );
return 1;
@@ -748,7 +762,7 @@ sub process_report : Private {
}
$report->council( $first_council->{id} );
- } elsif ( $first_council->{type} eq 'LBO') {
+ } elsif ( $first_council->{id} != 2482 && $first_council->{type} eq 'LBO') {
unless ( Utils::london_categories()->{ $report->category } ) {
$c->stash->{field_errors}->{category} = _('Please choose a category');
@@ -800,6 +814,8 @@ sub process_report : Private {
};
}
+ $c->cobrand->process_extras( $c, \@contacts, \@extra );
+
if ( @extra ) {
$c->stash->{report_meta} = \@extra;
$report->extra( \@extra );
@@ -828,91 +844,6 @@ sub process_report : Private {
return 1;
}
-=head2 process_photo
-
-Handle the photo - either checking and storing it after an upload or retrieving
-it from the cache.
-
-Store any error message onto 'photo_error' in stash.
-=cut
-
-sub process_photo : Private {
- my ( $self, $c ) = @_;
-
- return
- $c->forward('process_photo_upload')
- || $c->forward('process_photo_cache')
- || 1; # always return true
-}
-
-sub process_photo_upload : Private {
- my ( $self, $c ) = @_;
-
- # check for upload or return
- my $upload = $c->req->upload('photo')
- || return;
-
- # check that the photo is a jpeg
- my $ct = $upload->type;
- unless ( $ct eq 'image/jpeg' || $ct eq 'image/pjpeg' ) {
- $c->stash->{photo_error} = _('Please upload a JPEG image only');
- return;
- }
-
- # get the photo into a variable
- my $photo_blob = eval {
- my $filename = $upload->tempname;
- my $out = `jhead -se -autorot $filename`;
- my $photo = $upload->slurp;
- return $photo;
- };
- if ( my $error = $@ ) {
- my $format = _(
-"That image doesn't appear to have uploaded correctly (%s), please try again."
- );
- $c->stash->{photo_error} = sprintf( $format, $error );
- return;
- }
-
- # we have an image we can use - save it to the upload dir for storage
- my $cache_dir = dir( $c->config->{UPLOAD_DIR} );
- $cache_dir->mkpath;
- unless ( -d $cache_dir && -w $cache_dir ) {
- warn "Can't find/write to photo cache directory '$cache_dir'";
- return;
- }
-
- my $fileid = sha1_hex($photo_blob);
- $upload->copy_to( file($cache_dir, $fileid . '.jpeg') );
-
- # stick the hash on the stash, so don't have to reupload in case of error
- $c->stash->{upload_fileid} = $fileid;
-
- return 1;
-}
-
-=head2 process_photo_cache
-
-Look for the upload_fileid parameter and check it matches a file on disk. If it
-does return true and put fileid on stash, otherwise false.
-
-=cut
-
-sub process_photo_cache : Private {
- my ( $self, $c ) = @_;
-
- # get the fileid and make sure it is just a hex number
- my $fileid = $c->req->param('upload_fileid') || '';
- $fileid =~ s{[^0-9a-f]}{}gi;
- return unless $fileid;
-
- my $file = file( $c->config->{UPLOAD_DIR}, "$fileid.jpeg" );
- return unless -e $file;
-
- $c->stash->{upload_fileid} = $fileid;
- return 1;
-}
-
=head2 check_for_errors
Examine the user and the report for errors. If found put them on stash and
@@ -931,6 +862,8 @@ sub check_for_errors : Private {
%{ $c->stash->{report}->check_for_errors },
);
+ # FIXME: need to check for required bromley fields here
+
# if they're got the login details wrong when signing in then
# we don't care about the name field even though it's validated
# by the user object
@@ -972,10 +905,12 @@ sub save_user_and_report : Private {
name => $report->user->name,
phone => $report->user->phone,
password => $report->user->password,
+ title => $report->user->title,
};
$report->user->name( undef );
$report->user->phone( undef );
$report->user->password( '', 1 );
+ $report->user->title( undef );
$report->user->insert();
$c->log->info($report->user->id . ' created for this report');
}
@@ -991,6 +926,7 @@ sub save_user_and_report : Private {
name => $report->user->name,
phone => $report->user->phone,
password => $report->user->password,
+ title => $report->user->title,
};
$report->user->discard_changes();
$c->log->info($report->user->id . ' exists, but is not logged in for this report');
diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm
index 15444f556..5b0dad170 100644
--- a/perllib/FixMyStreet/App/Controller/Report/Update.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm
@@ -23,7 +23,7 @@ sub report_update : Path : Args(0) {
$c->forward( '/report/load_problem_or_display_error', [ $c->req->param('id') ] );
$c->forward('process_update');
$c->forward('process_user');
- $c->forward('/report/new/process_photo');
+ $c->forward('/photo/process_photo');
$c->forward('check_for_errors')
or $c->go( '/report/display', [ $c->req->param('id') ] );
@@ -104,13 +104,18 @@ sub process_user : Private {
my $user = $c->user->obj;
my $name = scalar $c->req->param('name');
$user->name( Utils::trim_text( $name ) ) if $name;
+ my $title = scalar $c->req->param('fms_extra_title');
+ if ( $title ) {
+ $c->log->debug( 'user exists and title is ' . $title );
+ $user->title( Utils::trim_text( $title ) );
+ }
$update->user( $user );
return 1;
}
# Extract all the params to a hash to make them easier to work with
my %params = map { $_ => scalar $c->req->param($_) }
- ( 'rznvy', 'name', 'password_register' );
+ ( 'rznvy', 'name', 'password_register', 'fms_extra_title' );
# cleanup the email address
my $email = $params{rznvy} ? lc $params{rznvy} : '';
@@ -136,6 +141,8 @@ sub process_user : Private {
if $params{name};
$update->user->password( Utils::trim_text( $params{password_register} ) )
if $params{password_register};
+ $update->user->title( Utils::trim_text( $params{fms_extra_title} ) )
+ if $params{fms_extra_title};
return 1;
}
@@ -153,6 +160,15 @@ want to move adding these elsewhere
sub process_update : Private {
my ( $self, $c ) = @_;
+ if ( $c->req->param('first_name' ) && $c->req->param('last_name' ) ) {
+ my $first_name = $c->req->param('first_name');
+ my $last_name = $c->req->param('last_name');
+ $c->req->param('name', sprintf( '%s %s', $first_name, $last_name ) );
+
+ $c->stash->{first_name} = $first_name;
+ $c->stash->{last_name} = $last_name;
+ }
+
my %params =
map { $_ => scalar $c->req->param($_) } ( 'update', 'name', 'fixed', 'state', 'reopen' );
@@ -185,6 +201,24 @@ sub process_update : Private {
$update->problem_state( $params{state} );
}
+ if ( $c->req->param('fms_extra_title') ) {
+ my %extras = ();
+ $extras{title} = $c->req->param('fms_extra_title');
+ $extras{email_alerts_required} = $c->req->param('add_alert');
+ $update->extra( \%extras );
+
+ $c->stash->{fms_extra_title} = $c->req->param('fms_extra_title');
+ }
+
+ if ( $c->stash->{ first_name } && $c->stash->{ last_name } ) {
+ my $extra = $update->extra || {};
+ $extra->{first_name} = $c->stash->{ first_name };
+ $extra->{last_name} = $c->stash->{ last_name };
+ $update->extra( $extra );
+ }
+
+ $c->log->debug( 'name is ' . $c->req->param('name') );
+
$c->stash->{update} = $update;
$c->stash->{add_alert} = $c->req->param('add_alert');
@@ -268,6 +302,7 @@ sub save_update : Private {
}
elsif ( $c->user && $c->user->id == $update->user->id ) {
# Logged in and same user, so can confirm update straight away
+ $c->log->debug( 'user exists' );
$update->user->update;
$update->confirm;
} else {
diff --git a/perllib/FixMyStreet/App/Controller/Root.pm b/perllib/FixMyStreet/App/Controller/Root.pm
index 9cdf0b523..7f7d7f5fd 100644
--- a/perllib/FixMyStreet/App/Controller/Root.pm
+++ b/perllib/FixMyStreet/App/Controller/Root.pm
@@ -68,7 +68,7 @@ Forward to the standard 404 error page
sub default : Path {
my ( $self, $c ) = @_;
- $c->detach('/page_error_404_not_found');
+ $c->detach('/page_error_404_not_found', []);
}
=head2 page_error_404_not_found, page_error_410_gone
diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm
index b974f94e6..1434838f2 100644
--- a/perllib/FixMyStreet/App/Controller/Tokens.pm
+++ b/perllib/FixMyStreet/App/Controller/Tokens.pm
@@ -71,6 +71,7 @@ sub confirm_problem : Path('/P') {
$problem->user->name( $data->{name} ) if $data->{name};
$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->update;
}
$c->authenticate( { email => $problem->user->email }, 'no_password' );
diff --git a/perllib/FixMyStreet/App/Model/EmailSend.pm b/perllib/FixMyStreet/App/Model/EmailSend.pm
index 73086c65f..761856bde 100644
--- a/perllib/FixMyStreet/App/Model/EmailSend.pm
+++ b/perllib/FixMyStreet/App/Model/EmailSend.pm
@@ -35,7 +35,7 @@ elsif ( my $smtp_host = FixMyStreet->config('SMTP_SMARTHOST') ) {
# Email::Send::SMTP
$args = {
- mailer => 'SMTP',
+ mailer => 'FixMyStreet::EmailSend',
mailer_args => [ Host => $smtp_host ],
};
}
diff --git a/perllib/FixMyStreet/Cobrand.pm b/perllib/FixMyStreet/Cobrand.pm
index 647261e32..b88f6facc 100644
--- a/perllib/FixMyStreet/Cobrand.pm
+++ b/perllib/FixMyStreet/Cobrand.pm
@@ -109,4 +109,22 @@ sub get_class_for_moniker {
return 'FixMyStreet::Cobrand::Default';
}
+=head2 exists
+
+ FixMyStreet::Cobrand->exists( $moniker );
+
+Given a moniker, returns true if that cobrand is available to us for use
+
+=cut
+
+sub exists {
+ my ( $class, $moniker ) = @_;
+
+ foreach my $avail ( $class->available_cobrand_classes ) {
+ return 1 if $moniker eq $avail->{moniker};
+ }
+
+ return 0;
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Bromley.pm b/perllib/FixMyStreet/Cobrand/Bromley.pm
index 75174b638..4f2535437 100644
--- a/perllib/FixMyStreet/Cobrand/Bromley.pm
+++ b/perllib/FixMyStreet/Cobrand/Bromley.pm
@@ -1,5 +1,7 @@
package FixMyStreet::Cobrand::Bromley;
-use base 'FixMyStreet::Cobrand::UKCouncils';
+use mro 'c3';
+use parent 'FixMyStreet::Cobrand::FixMyStreet';
+use parent 'FixMyStreet::Cobrand::UKCouncils';
use strict;
use warnings;
@@ -9,6 +11,10 @@ sub council_area { return 'Bromley'; }
sub council_name { return 'Bromley Council'; }
sub council_url { return 'bromley'; }
+sub base_url {
+ 'https://fix.bromley.gov.uk';
+}
+
sub path_to_web_templates {
my $self = shift;
return [
@@ -17,10 +23,19 @@ sub path_to_web_templates {
];
}
+sub site_title {
+ my ($self) = @_;
+ return "London Borough of Bromley - Report a problem in Bromley\x{2019}s streets or parks";
+}
+sub site_name {
+ return 'Bromley FixMyStreet';
+}
+
sub disambiguate_location {
my $self = shift;
return {
%{ $self->SUPER::disambiguate_location() },
+ town => 'Bromley',
centre => '51.366836,0.040623',
span => '0.154963,0.24347',
bounds => [ '51.289355,-0.081112', '51.444318,0.162358' ],
@@ -31,5 +46,46 @@ sub example_places {
return ( 'BR1 3UH', 'Glebe Rd, Bromley' );
}
+sub map_type {
+ 'Bromley';
+}
+
+sub on_map_default_max_pin_age {
+ return '1 month';
+}
+
+# Bromley pins always yellow
+sub pin_colour {
+ my ( $self, $p, $context ) = @_;
+ return 'yellow';
+}
+
+sub recent_photos {
+ my ( $self, $area, $num, $lat, $lon, $dist ) = @_;
+ $num = 3 if $num > 3 && $area eq 'alert';
+ return $self->problems->recent_photos( $num, $lat, $lon, $dist );
+}
+
+sub send_questionnaires {
+ return 0;
+}
+
+sub ask_ever_reported {
+ return 0;
+}
+
+sub process_extras {
+ my $self = shift;
+ $self->SUPER::process_extras( @_, [ 'first_name', 'last_name' ] );
+}
+
+sub contact_email {
+ my $self = shift;
+ my $type = shift || '';
+ return join( '@', 'info', 'bromley.gov.uk' ) if $type eq 'contact';
+ return $self->next::method();
+}
+sub contact_name { 'Bromley Council (do not reply)'; }
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 25ad37a0b..2a49e9246 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -182,6 +182,7 @@ EASTING and NORTHING.
sub recent_photos {
my $self = shift;
+ my $area = shift;
return $self->problems->recent_photos(@_);
}
@@ -359,6 +360,27 @@ Return the title to be used in page heads.
sub site_title { 'FixMyStreet' }
+=head2 site_name
+
+Return short name for use in emails.
+
+=cut
+sub site_name {
+ my $self = shift;
+ $self->site_title;
+}
+
+=head2 map_type
+
+Return an override type of map if necessary.
+
+=cut
+sub map_type {
+ my $self = shift;
+ return 'OSM' if $self->{c}->req->uri->host =~ /^osm\./;
+ return;
+}
+
=head2 on_map_list_limit
Return the maximum number of items to be given in the list of reports on the map
@@ -585,6 +607,14 @@ first time they' ve reported a problem
sub ask_ever_reported { 1 }
+=head2 send_questionnaires
+
+Return a boolean indicating whether people should be sent questionnaire emails.
+
+=cut
+
+sub send_questionnaires { 1 }
+
=head2 admin_pages
List of names of pages to display on the admin interface
@@ -749,6 +779,8 @@ sub council_rss_alert_options {
( $ward->{id_name} = $ward->{short_name} ) =~ tr/+/_/;
}
}
+ $council->{name} = 'London Borough of Bromley'
+ if $council->{name} eq 'Bromley Council';
push @options,
{
@@ -925,9 +957,26 @@ Get stats to display on the council reports page
sub get_report_stats { return 0; }
+sub get_council_sender { return 'Email' };
+
sub example_places {
return [ 'B2 4QA', 'Tib St, Manchester' ];
}
+sub process_extras {}
+
+=head 2 pin_colour
+
+Returns the colour of pin to be used for a particular report
+(so perhaps different depending upon the age of the report).
+
+=cut
+sub pin_colour {
+ my ( $self, $p, $context ) = @_;
+ #return 'green' if time() - $p->confirmed_local->epoch < 7 * 24 * 60 * 60;
+ return 'yellow' if $context eq 'around';
+ return $p->is_fixed ? 'green' : 'red';
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
index 384b1b05b..8d6eb9ec8 100644
--- a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
+++ b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
@@ -6,6 +6,21 @@ sub restriction {
return {};
}
+sub get_council_sender {
+ my ( $self, $area_id, $area_info ) = @_;
+
+ my $send_method;
+
+ my $council_config = FixMyStreet::App->model("DB::Open311conf")->search( { area_id => $area_id } )->first;
+ $send_method = $council_config->send_method if $council_config;
+
+ return $send_method if $send_method;
+
+ return 'London' if $area_info->{type} eq 'LBO';
+
+ return 'Email';
+}
+
sub all_reports_style { return 'detailed'; }
sub generate_problem_banner {
@@ -34,5 +49,33 @@ sub generate_problem_banner {
return $banner;
}
+sub process_extras {
+ my $self = shift;
+ my $ctx = shift;
+ my $contacts = shift;
+ my $extra = shift;
+ my $fields = shift || [];
+
+ if ( $contacts->[0]->area_id == 2482 ) {
+ my @fields = ( 'fms_extra_title', @$fields );
+ for my $field ( @fields ) {
+ my $value = $ctx->request->param( $field );
+
+ if ( !$value ) {
+ $ctx->stash->{field_errors}->{ $field } = _('This information is required');
+ }
+ push @$extra, {
+ name => $field,
+ description => uc( $field),
+ value => $value || '',
+ };
+ }
+
+ if ( $ctx->request->param('fms_extra_title') ) {
+ $ctx->stash->{fms_extra_title} = $ctx->request->param('fms_extra_title');
+ $ctx->stash->{extra_name_info} = 1;
+ }
+ }
+}
1;
diff --git a/perllib/FixMyStreet/Cobrand/UKCouncils.pm b/perllib/FixMyStreet/Cobrand/UKCouncils.pm
index 9c2899c21..8a6954418 100644
--- a/perllib/FixMyStreet/Cobrand/UKCouncils.pm
+++ b/perllib/FixMyStreet/Cobrand/UKCouncils.pm
@@ -8,7 +8,7 @@ use Carp;
use URI::Escape;
sub is_council {
- return 1;
+ 1;
}
sub site_restriction {
@@ -60,7 +60,7 @@ sub council_check {
return 1;
}
my $url = 'http://www.fixmystreet.com/';
- if ( $context eq 'alert' ) {
+ if ($context eq 'alert') {
$url .= 'alert';
} else {
$url .= 'around';
@@ -81,10 +81,24 @@ sub all_councils_report {
}
sub recent_photos {
- my ( $self, $num, $lat, $lon, $dist ) = @_;
+ my ( $self, $area, $num, $lat, $lon, $dist ) = @_;
$num = 2 if $num == 3;
return $self->problems->recent_photos( $num, $lat, $lon, $dist );
}
-1;
+sub get_council_sender {
+ my ( $self, $area_id, $area_info ) = @_;
+
+ my $send_method;
+
+ my $council_config = FixMyStreet::App->model("DB::Open311conf")->search( { area_id => $area_id } )->first;
+ $send_method = $council_config->send_method if $council_config;
+
+ return $send_method if $send_method;
+ return 'London' if $area_info->{type} eq 'LBO';
+
+ return 'Email';
+}
+
+1;
diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm
index 5b45c63a8..91695d7d0 100644
--- a/perllib/FixMyStreet/DB/Result/Comment.pm
+++ b/perllib/FixMyStreet/DB/Result/Comment.pm
@@ -54,6 +54,18 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"problem_state",
{ data_type => "text", is_nullable => 1 },
+ "external_id",
+ { data_type => "text", is_nullable => 1 },
+ "extra",
+ { data_type => "text", is_nullable => 1 },
+ "send_fail_count",
+ { data_type => "integer", default_value => 0, is_nullable => 0 },
+ "send_fail_reason",
+ { data_type => "text", is_nullable => 1 },
+ "send_fail_timestamp",
+ { data_type => "timestamp", is_nullable => 1 },
+ "whensent",
+ { data_type => "timestamp", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to(
@@ -70,13 +82,34 @@ __PACKAGE__->belongs_to(
);
-# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-03-08 17:19:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:E+96vo/AB0zz1jAEPj/OKw
+# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-03-26 15:44:18
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nvkElEgSU6XcLd9znSqhmQ
+
+__PACKAGE__->filter_column(
+ extra => {
+ filter_from_storage => sub {
+ my $self = shift;
+ my $ser = shift;
+ return undef unless defined $ser;
+ my $h = new IO::String($ser);
+ return RABX::wire_rd($h);
+ },
+ filter_to_storage => sub {
+ my $self = shift;
+ my $data = shift;
+ my $ser = '';
+ my $h = new IO::String($ser);
+ RABX::wire_wr( $data, $h );
+ return $ser;
+ },
+ }
+);
use DateTime::TimeZone;
use Image::Size;
use Moose;
use namespace::clean -except => [ 'meta' ];
+use RABX;
with 'FixMyStreet::Roles::Abuser';
diff --git a/perllib/FixMyStreet/DB/Result/Open311conf.pm b/perllib/FixMyStreet/DB/Result/Open311conf.pm
index 742a12ebd..c95b0c8f2 100644
--- a/perllib/FixMyStreet/DB/Result/Open311conf.pm
+++ b/perllib/FixMyStreet/DB/Result/Open311conf.pm
@@ -28,13 +28,30 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"send_method",
{ data_type => "text", is_nullable => 1 },
+ "send_comments",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "comment_user_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
+ "suppress_alerts",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("open311conf_area_id_key", ["area_id"]);
+__PACKAGE__->belongs_to(
+ "comment_user",
+ "FixMyStreet::DB::Result::User",
+ { id => "comment_user_id" },
+ {
+ is_deferrable => 1,
+ join_type => "LEFT",
+ on_delete => "CASCADE",
+ on_update => "CASCADE",
+ },
+);
-# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-03-08 17:19:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ClYnPB2gsKapnfHuco5d/w
+# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-05-11 13:30:31
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ByJbRe/Y/9Z1WHdG8kaIHg
# You can replace this text with custom code or comments, and it will be preserved on regeneration
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index 192e539bd..9cbaef6c2 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -85,7 +85,7 @@ __PACKAGE__->add_columns(
"geocode",
{ data_type => "bytea", is_nullable => 1 },
"send_fail_count",
- { data_type => "integer", default_value => 0, is_nullable => 0 },
+ { data_type => "integer", is_nullable => 1 },
"send_fail_reason",
{ data_type => "text", is_nullable => 1 },
"send_fail_timestamp",
@@ -578,7 +578,7 @@ sub body {
# Note: this only makes sense when called on a problem that has been sent!
sub can_display_external_id {
my $self = shift;
- if ($self->external_id && $self->send_method_used eq 'barnet') {
+ if ($self->external_id && $self->send_method_used && $self->send_method_used eq 'barnet') {
return 1;
}
return 0;
@@ -704,6 +704,17 @@ sub update_from_open311_service_request {
return 1;
}
+sub update_send_failed {
+ my $self = shift;
+ my $msg = shift;
+
+ $self->update( {
+ send_fail_count => $self->send_fail_count + 1,
+ send_fail_timestamp => \'ms_current_timestamp()',
+ send_fail_reason => $msg
+ } );
+}
+
# we need the inline_constructor bit as we don't inherit from Moose
__PACKAGE__->meta->make_immutable( inline_constructor => 0 );
diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm
index e13d88b88..7f43d1a52 100644
--- a/perllib/FixMyStreet/DB/Result/User.pm
+++ b/perllib/FixMyStreet/DB/Result/User.pm
@@ -30,6 +30,8 @@ __PACKAGE__->add_columns(
{ data_type => "integer", is_nullable => 1 },
"flagged",
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "title",
+ { data_type => "text", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("users_email_key", ["email"]);
@@ -46,6 +48,12 @@ __PACKAGE__->has_many(
{ cascade_copy => 0, cascade_delete => 0 },
);
__PACKAGE__->has_many(
+ "open311confs",
+ "FixMyStreet::DB::Result::Open311conf",
+ { "foreign.comment_user_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+__PACKAGE__->has_many(
"problems",
"FixMyStreet::DB::Result::Problem",
{ "foreign.user_id" => "self.id" },
@@ -53,8 +61,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-03-08 17:19:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:tM1LUGrqDeQnF4BDgnYXGQ
+# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-05-01 16:20:29
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:LKi8u5IYnHW1+Mez64nvGg
__PACKAGE__->add_columns(
"password" => {
@@ -172,4 +180,22 @@ sub belongs_to_council {
return 0;
}
+=head2 split_name
+
+ $name = $user->split_name;
+ printf( 'Welcome %s %s', $name->{first}, $name->{last} );
+
+Returns a hashref with first and last keys with first name(s) and last name.
+NB: the spliting algorithm is extremely basic.
+
+=cut
+
+sub split_name {
+ my $self = shift;
+
+ my ($first, $last) = $self->name =~ /^(\S*)(?: (.*))?$/;
+
+ return { first => $first || '', last => $last || '' };
+}
+
1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Problem.pm b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
index 3557e77c7..a7738becf 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Problem.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
@@ -4,6 +4,16 @@ use base 'DBIx::Class::ResultSet';
use strict;
use warnings;
+use CronFns;
+
+use Utils;
+use mySociety::Config;
+use mySociety::EmailUtil;
+use mySociety::MaPit;
+
+use FixMyStreet::App;
+use FixMyStreet::SendReport;
+
my $site_restriction;
my $site_key;
@@ -209,4 +219,209 @@ sub categories_summary {
return \%categories;
}
+sub send_reports {
+ # Set up site, language etc.
+ my ($verbose, $nomail) = CronFns::options();
+ my $base_url = mySociety::Config::get('BASE_URL');
+ my $site = CronFns::site($base_url);
+
+ my $unsent = FixMyStreet::App->model("DB::Problem")->search( {
+ state => [ 'confirmed', 'fixed' ],
+ whensent => undef,
+ council => { '!=', undef },
+ } );
+ my (%notgot, %note);
+
+ my $send_report = FixMyStreet::SendReport->new();
+ my $senders = $send_report->get_senders;
+ my %sending_skipped_by_method;
+
+ while (my $row = $unsent->next) {
+
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($row->cobrand)->new();
+
+ # Cobranded and non-cobranded messages can share a database. In this case, the conf file
+ # should specify a vhost to send the reports for each cobrand, so that they don't get sent
+ # more than once if there are multiple vhosts running off the same database. The email_host
+ # call checks if this is the host that sends mail for this cobrand.
+ next unless $cobrand->email_host();
+ $cobrand->set_lang_and_domain($row->lang, 1);
+ if ( $row->is_from_abuser ) {
+ $row->update( { state => 'hidden' } );
+ next;
+ }
+
+ # Template variables for the email
+ my $email_base_url = $cobrand->base_url_for_emails($row->cobrand_data);
+ my %h = map { $_ => $row->$_ } qw/id title detail name category latitude longitude used_map/;
+ map { $h{$_} = $row->user->$_ } qw/email phone/;
+ $h{confirmed} = DateTime::Format::Pg->format_datetime( $row->confirmed->truncate (to => 'second' ) );
+
+ $h{query} = $row->postcode;
+ $h{url} = $email_base_url . '/report/' . $row->id;
+ $h{phone_line} = $h{phone} ? _('Phone:') . " $h{phone}\n\n" : '';
+ if ($row->photo) {
+ $h{has_photo} = _("This web page also contains a photo of the problem, provided by the user.") . "\n\n";
+ $h{image_url} = $email_base_url . '/photo/' . $row->id . '.full.jpeg';
+ } else {
+ $h{has_photo} = '';
+ $h{image_url} = '';
+ }
+ $h{fuzzy} = $row->used_map ? _('To view a map of the precise location of this issue')
+ : _('The user could not locate the problem on a map, but to see the area around the location they entered');
+ $h{closest_address} = '';
+
+ # If we are in the UK include eastings and northings, and nearest stuff
+ $h{easting_northing} = '';
+ if ( $cobrand->country eq 'GB' ) {
+
+ ( $h{easting}, $h{northing} ) = Utils::convert_latlon_to_en( $h{latitude}, $h{longitude} );
+
+ # email templates don't have conditionals so we need to farmat this here
+ $h{easting_northing} #
+ = "Easting: $h{easting}\n\n" #
+ . "Northing: $h{northing}\n\n";
+
+ }
+
+ if ( $row->used_map ) {
+ $h{closest_address} = $cobrand->find_closest( $h{latitude}, $h{longitude}, $row );
+ }
+
+ my %reporters = ();
+ my (@to, @recips, $template, $areas_info, $sender_count );
+ if ($site eq 'emptyhomes') {
+
+ my $council = $row->council;
+ $areas_info = mySociety::MaPit::call('areas', $council);
+ my $name = $areas_info->{$council}->{name};
+ my $sender = "FixMyStreet::SendReport::EmptyHomes";
+ $reporters{ $sender } = $sender->new() unless $reporters{$sender};
+ $reporters{ $sender }->add_council( $council, $name );
+ $template = Utils::read_file("$FindBin::Bin/../templates/email/emptyhomes/" . $row->lang . "/submit.txt");
+
+ } else {
+
+ # XXX Needs locks!
+ my @all_councils = split /,|\|/, $row->council;
+ my ($councils, $missing) = $row->council =~ /^([\d,]+)(?:\|([\d,]+))?/;
+ my @councils = split(/,/, $councils);
+ $areas_info = mySociety::MaPit::call('areas', \@all_councils);
+ my @dear;
+
+ foreach my $council (@councils) {
+ my $name = $areas_info->{$council}->{name};
+
+ my $sender = $cobrand->get_council_sender( $council, $areas_info->{$council} );
+ $sender = "FixMyStreet::SendReport::$sender";
+
+ if ( ! exists $senders->{ $sender } ) {
+ warn "No such sender [ $sender ] for council $name ( $council )";
+ next;
+ }
+ $reporters{ $sender } ||= $sender->new();
+
+ if ( $reporters{ $sender }->should_skip( $row ) ) {
+ $sending_skipped_by_method{ $sender }++ if
+ $reporters{ $sender }->skipped;
+ } else {
+ push @dear, $name;
+ $reporters{ $sender }->add_council( $council, $name );
+ }
+ }
+
+ $template = 'submit.txt';
+ $template = 'submit-brent.txt' if $row->council eq 2488 || $row->council eq 2237;
+ my $template_path = FixMyStreet->path_to( "templates", "email", $cobrand->moniker, $template )->stringify;
+ $template_path = FixMyStreet->path_to( "templates", "email", "default", $template )->stringify
+ unless -e $template_path;
+ $template = Utils::read_file( $template_path );
+
+ if ($h{category} eq _('Other')) {
+ $h{category_footer} = _('this type of local problem');
+ $h{category_line} = '';
+ } else {
+ $h{category_footer} = "'" . $h{category} . "'";
+ $h{category_line} = sprintf(_("Category: %s"), $h{category}) . "\n\n";
+ }
+
+ $h{councils_name} = join(_(' and '), @dear);
+ if ($h{category} eq _('Other')) {
+ $h{multiple} = @dear>1 ? "[ " . _("This email has been sent to both councils covering the location of the problem, as the user did not categorise it; please ignore it if you're not the correct council to deal with the issue, or let us know what category of problem this is so we can add it to our system.") . " ]\n\n"
+ : '';
+ } else {
+ $h{multiple} = @dear>1 ? "[ " . _("This email has been sent to several councils covering the location of the problem, as the category selected is provided for all of them; please ignore it if you're not the correct council to deal with the issue.") . " ]\n\n"
+ : '';
+ }
+ $h{missing} = '';
+ if ($missing) {
+ my $name = $areas_info->{$missing}->{name};
+ $h{missing} = '[ '
+ . sprintf(_('We realise this problem might be the responsibility of %s; however, we don\'t currently have any contact details for them. If you know of an appropriate contact address, please do get in touch.'), $name)
+ . " ]\n\n";
+ }
+
+ $sender_count = scalar @dear;
+ }
+
+ unless ( keys %reporters ) {
+ die 'Report not going anywhere for ID ' . $row->id . '!';
+ }
+
+ next unless $sender_count;
+
+ if (mySociety::Config::get('STAGING_SITE')) {
+ # on a staging server send emails to ourselves rather than the councils
+ my @testing_councils = split( '\|', mySociety::Config::get('TESTING_COUNCILS') );
+ unless ( grep { $row->council eq $_ } @testing_councils ) {
+ %reporters = (
+ 'FixMyStreet::SendReport::Email' => $reporters{ 'FixMyStreet::SendReport::Email' } || FixMyStreet::SendReport::Email->new()
+ );
+ }
+ }
+
+ # Multiply results together, so one success counts as a success.
+ my $result = -1;
+
+ for my $sender ( keys %reporters ) {
+ $result *= $reporters{ $sender }->send(
+ $row, \%h, \@to, $template, \@recips, $nomail, $areas_info
+ );
+ }
+
+ if ($result == mySociety::EmailUtil::EMAIL_SUCCESS) {
+ $row->update( {
+ whensent => \'ms_current_timestamp()',
+ lastupdate => \'ms_current_timestamp()',
+ } );
+ } else {
+ my @errors;
+ for my $sender ( keys %reporters ) {
+ unless ( $reporters{ $sender }->success ) {
+ push @errors, $reporters{ $sender }->error;
+ }
+ }
+ $row->update_send_failed( join( '|', @errors ) );
+ }
+ }
+
+ if ($verbose) {
+ print "Council email addresses that need checking:\n" if keys %notgot;
+ foreach my $e (keys %notgot) {
+ foreach my $c (keys %{$notgot{$e}}) {
+ print $notgot{$e}{$c} . " problem, to $e category $c (" . $note{$e}{$c}. ")\n";
+ }
+ }
+ if (keys %sending_skipped_by_method) {
+ my $c = 0;
+ print "\nProblem reports that send-reports did not attempt to send the following:\n";
+ foreach my $send_method (sort keys %sending_skipped_by_method) {
+ printf " %-24s %4d\n", "$send_method:", $sending_skipped_by_method{$send_method};
+ $c+=$sending_skipped_by_method{$send_method};
+ }
+ printf " %-24s %4d\n", "Total:", $c;
+ }
+ }
+}
+
1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm b/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm
index 0cf01b6d1..753eb2084 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm
@@ -48,6 +48,9 @@ sub send_questionnaires_period {
my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($row->cobrand)->new();
$cobrand->set_lang_and_domain($row->lang, 1);
+ # Not all cobrands send questionnaires
+ next unless $cobrand->send_questionnaires;
+
# Cobranded and non-cobranded messages can share a database. In this case, the conf file
# should specify a vhost to send the reports for each cobrand, so that they don't get sent
# more than once if there are multiple vhosts running off the same database. The email_host
diff --git a/perllib/FixMyStreet/EmailSend.pm b/perllib/FixMyStreet/EmailSend.pm
new file mode 100644
index 000000000..61d8a70c2
--- /dev/null
+++ b/perllib/FixMyStreet/EmailSend.pm
@@ -0,0 +1,12 @@
+package FixMyStreet::EmailSend;
+use base Email::Send::SMTP;
+
+sub get_env_sender {
+ # Should really use cobrand's contact_email function, but not sure how
+ # best to access that from in here.
+ my $sender = FixMyStreet->config('CONTACT_EMAIL');
+ $sender =~ s/team/fms-DO-NOT-REPLY/;
+ return $sender;
+}
+
+1;
diff --git a/perllib/FixMyStreet/Geocode/Bing.pm b/perllib/FixMyStreet/Geocode/Bing.pm
index a24f7c102..3bbb9dcdc 100644
--- a/perllib/FixMyStreet/Geocode/Bing.pm
+++ b/perllib/FixMyStreet/Geocode/Bing.pm
@@ -44,8 +44,6 @@ sub string {
if (!$js) {
return { error => _('Sorry, we could not parse that location. Please try again.') };
- } elsif ($js =~ /BT\d/ && $params->{bing_country} eq 'United Kingdom') {
- return { error => _("We do not currently cover Northern Ireland, I'm afraid.") };
}
$js = JSON->new->utf8->allow_nonref->decode($js);
@@ -54,11 +52,15 @@ sub string {
}
my $results = $js->{resourceSets}->[0]->{resources};
- my ( $error, @valid_locations, $latitude, $longitude );
+ my ( $error, @valid_locations, $latitude, $longitude, $ni );
foreach (@$results) {
my $address = $_->{name};
next unless $_->{address}->{countryRegion} eq $params->{bing_country};
+ if ($params->{bing_country} eq 'United Kingdom' && $_->{address}{adminDistrict} eq 'Northern Ireland') {
+ $ni = 1;
+ next;
+ }
# Getting duplicate, yet different, results from Bing sometimes
next if @valid_locations
@@ -80,6 +82,10 @@ sub string {
push (@valid_locations, $_);
}
+ if ($ni && !scalar @valid_locations) {
+ return { error => _("We do not currently cover Northern Ireland, I'm afraid.") };
+ }
+
return { latitude => $latitude, longitude => $longitude } if scalar @valid_locations == 1;
return { error => $error };
}
diff --git a/perllib/FixMyStreet/Map/Bromley.pm b/perllib/FixMyStreet/Map/Bromley.pm
new file mode 100644
index 000000000..20821236f
--- /dev/null
+++ b/perllib/FixMyStreet/Map/Bromley.pm
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+#
+# FixMyStreet:Map::Bromley
+# Bromley have slightly different tiles, with trees etc.
+#
+# Copyright (c) 2012 UK Citizens Online Democracy. All rights reserved.
+# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/
+
+package FixMyStreet::Map::Bromley;
+use base 'FixMyStreet::Map::FMS';
+
+use strict;
+
+sub map_type {
+ return '[ [ "", "a-" ], "https://{S}fix.bromley.gov.uk/tilma" ]';
+}
+
+sub map_tile_base {
+ '-', "https://%sfix.bromley.gov.uk/tilma/%d/%d/%d.png";
+}
+
+1;
diff --git a/perllib/FixMyStreet/Map/FMS.pm b/perllib/FixMyStreet/Map/FMS.pm
index 24842c861..b1dd29002 100644
--- a/perllib/FixMyStreet/Map/FMS.pm
+++ b/perllib/FixMyStreet/Map/FMS.pm
@@ -37,23 +37,28 @@ sub get_quadkey {
return $key;
}
+sub map_tile_base {
+ '.', "http://%stilma.mysociety.org/sv/%d/%d/%d.png";
+}
+
sub map_tiles {
my ($self, $x, $y, $z) = @_;
if ($z >= 16) {
+ my ($tile_sep, $tile_base) = $self->map_tile_base;
return [
- "http://a.tilma.mysociety.org/sv/$z/" . ($x-1) . "/" . ($y-1) . ".png",
- "http://b.tilma.mysociety.org/sv/$z/$x/" . ($y-1) . ".png",
- "http://c.tilma.mysociety.org/sv/$z/" . ($x-1) . "/$y.png",
- "http://tilma.mysociety.org/sv/$z/$x/$y.png",
+ sprintf($tile_base, 'a' . $tile_sep, $z, $x-1, $y-1),
+ sprintf($tile_base, 'b' . $tile_sep, $z, $x, $y-1),
+ sprintf($tile_base, 'c' . $tile_sep, $z, $x-1, $y),
+ sprintf($tile_base, '', $z, $x, $y),
];
} else {
my $url = "g=701";
$url .= "&productSet=mmOS" if $z > 10;
return [
- "http://ecn.t0.tiles.virtualearth.net/tiles/r" . get_quadkey($x-1, $y-1, $z) . ".png?$url",
- "http://ecn.t1.tiles.virtualearth.net/tiles/r" . get_quadkey($x, $y-1, $z) . ".png?$url",
- "http://ecn.t2.tiles.virtualearth.net/tiles/r" . get_quadkey($x-1, $y, $z) . ".png?$url",
- "http://ecn.t3.tiles.virtualearth.net/tiles/r" . get_quadkey($x, $y, $z) . ".png?$url",
+ "//ecn.t0.tiles.virtualearth.net/tiles/r" . get_quadkey($x-1, $y-1, $z) . ".png?$url",
+ "//ecn.t1.tiles.virtualearth.net/tiles/r" . get_quadkey($x, $y-1, $z) . ".png?$url",
+ "//ecn.t2.tiles.virtualearth.net/tiles/r" . get_quadkey($x-1, $y, $z) . ".png?$url",
+ "//ecn.t3.tiles.virtualearth.net/tiles/r" . get_quadkey($x, $y, $z) . ".png?$url",
];
}
}
diff --git a/perllib/FixMyStreet/Map/OSM.pm b/perllib/FixMyStreet/Map/OSM.pm
index 8ca5949d8..119337e37 100644
--- a/perllib/FixMyStreet/Map/OSM.pm
+++ b/perllib/FixMyStreet/Map/OSM.pm
@@ -109,15 +109,9 @@ sub map_pins {
my @pins = map {
# Here we might have a DB::Problem or a DB::Nearby, we always want the problem.
my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_;
- #{
- # latitude => $p->latitude,
- # longitude => $p->longitude,
- # colour => $p->state eq 'fixed' ? 'green' : 'red',
- # id => $p->id,
- # title => $p->title,
- #}
+ my $colour = $c->cobrand->pin_colour( $p, 'around' );
[ $p->latitude, $p->longitude,
- 'yellow', # $p->is_fixed ? 'green' : 'red',
+ $colour,
$p->id, $p->title
]
} @$around_map, @$nearby;
diff --git a/perllib/FixMyStreet/SendReport.pm b/perllib/FixMyStreet/SendReport.pm
new file mode 100644
index 000000000..915fe4a20
--- /dev/null
+++ b/perllib/FixMyStreet/SendReport.pm
@@ -0,0 +1,45 @@
+package FixMyStreet::SendReport;
+
+use Moose;
+
+use Module::Pluggable
+ sub_name => 'senders',
+ search_path => __PACKAGE__,
+ require => 1;
+
+has 'councils' => ( is => 'rw', isa => 'HashRef', default => sub { {} } );
+has 'to' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] } );
+has 'success' => ( is => 'rw', isa => 'Bool', default => 0 );
+has 'error' => ( is => 'rw', isa => 'Str', default => '' );
+has 'skipped' => ( 'is' => 'rw', isa => 'Str', default => '' );
+
+
+sub should_skip {
+ return 0;
+}
+
+sub get_senders {
+ my $self = shift;
+
+ my %senders = map { $_ => 1 } $self->senders;
+
+ return \%senders;
+}
+
+sub reset {
+ my $self = shift;
+
+ $self->councils( {} );
+ $self->to( [] );
+}
+
+sub add_council {
+ my $self = shift;
+ my $council = shift;
+ my $name = shift;
+
+ $self->councils->{ $council } = $name;
+}
+
+
+1;
diff --git a/perllib/FixMyStreet/SendReport/Barnet.pm b/perllib/FixMyStreet/SendReport/Barnet.pm
new file mode 100644
index 000000000..311c09318
--- /dev/null
+++ b/perllib/FixMyStreet/SendReport/Barnet.pm
@@ -0,0 +1,227 @@
+package FixMyStreet::SendReport::Barnet;
+
+use Moose;
+
+BEGIN { extends 'FixMyStreet::SendReport'; }
+
+use BarnetInterfaces::service::ZLBB_SERVICE_ORDER;
+use Encode;
+use Utils;
+use mySociety::Config;
+use mySociety::Web qw(ent);
+
+# maximum number of webservice attempts to send before not trying any more (XXX may be better in config?)
+use constant SEND_FAIL_RETRIES_CUTOFF => 3;
+
+# specific council numbers
+use constant COUNCIL_ID_BARNET => 2489;
+use constant MAX_LINE_LENGTH => 132;
+
+sub should_skip {
+ my $self = shift;
+ my $problem = shift;
+
+ my $council_name = 'Barnet';
+ my $err_msg = "";
+
+ if ($problem->send_fail_count >= SEND_FAIL_RETRIES_CUTOFF) {
+ $council_name &&= " to $council_name";
+ $err_msg = "skipped: problem id=" . $problem->id . " send$council_name has failed "
+ . $problem->send_fail_count . " times, cutoff is " . SEND_FAIL_RETRIES_CUTOFF;
+
+ $self->skipped( $err_msg );
+
+ return 1;
+ }
+}
+
+sub construct_message {
+ my %h = @_;
+ my $message = <<EOF;
+Subject: $h{title}
+
+Details: $h{detail}
+
+$h{fuzzy}, or to provide an update on the problem, please visit the following link:
+
+$h{url}
+
+$h{closest_address}
+EOF
+}
+
+
+sub send {
+ my ( $self, $problem, $h, $to, $template, $recips, $nomail ) = @_;
+
+ my %h = %$h;
+
+ $h{message} = construct_message(%h);
+
+ my $return = 1;
+ my $err_msg = "";
+
+ my $default_kbid = 14; # This is the default, "Street Scene"
+ my $kbid = sprintf( "%050d", Utils::barnet_categories()->{$h{category}} || $default_kbid);
+
+ my $geo_code = "$h{easting} $h{northing}";
+
+ my $interface = BarnetInterfaces::service::ZLBB_SERVICE_ORDER->new();
+
+ my ($nearest_postcode, $nearest_street) = ('', '');
+ for ($h{closest_address}) {
+ $nearest_postcode = sprintf("%-10s", $1) if /Nearest postcode [^:]+: ((\w{1,4}\s?\w+|\w+))/;
+ # use partial postcode or comma as delimiter, strip leading number (possible letter 221B) off too
+ # "99 Foo Street, London N11 1XX" becomes Foo Street
+ # "99 Foo Street N11 1XX" becomes Foo Street
+ $nearest_street = $1 if /Nearest road [^:]+: (?:\d+\w? )?(.*?)(\b[A-Z]+\d|,|$)/m;
+ }
+ my $postcode = mySociety::PostcodeUtil::is_valid_postcode($h{query})
+ ? $h{query} : $nearest_postcode; # use given postcode if available
+
+ # note: endpoint can be of form 'https://username:password@url'
+ my $council_config = FixMyStreet::App->model("DB::Open311conf")->search( { area_id => COUNCIL_ID_BARNET} )->first;
+ if ($council_config and $council_config->endpoint) {
+ $interface->set_proxy($council_config->endpoint);
+ # Barnet web service doesn't like namespaces in the elements so use a prefix
+ $interface->set_prefix('urn');
+ # uncomment these lines to print XML that will be sent rather
+ # than connecting to the endpoint
+ #$interface->outputxml(1);
+ #$interface->no_dispatch(1);
+ } else {
+ die "Barnet webservice FAIL: looks like you're missing some config data: no endpoint (URL) found for area_id=" . COUNCIL_ID_BARNET;
+ }
+
+ eval {
+ my $result = $interface->Z_CRM_SERVICE_ORDER_CREATE( {
+ ET_RETURN => { # ignored by server
+ item => {
+ TYPE => "", ID => "", NUMBER => "", MESSAGE => "", LOG_NO => "", LOG_MSG_NO => "",
+ MESSAGE_V1 => "", MESSAGE_V2 => "", MESSAGE_V3 => "", MESSAGE_V4 => "", PARAMETER => "",
+ ROW => "", FIELD => "", SYSTEM => "",
+ },
+ },
+ IT_PROBLEM_DESC => { # MyTypes::TABLE_OF_CRMT_SERVICE_REQUEST_TEXT
+ item => [ # MyTypes::CRMT_SERVICE_REQUEST_TEXT
+ map { { TEXT_LINE => $_ } } split_text_with_entities(ent(encode_utf8($h{message})), 132) # char132
+ ],
+ },
+ IV_CUST_EMAIL => truncate_string_with_entities(ent(encode_utf8($h{email})), 241), # char241
+ IV_CUST_NAME => truncate_string_with_entities(ent(encode_utf8($h{name})), 50), # char50
+ IV_KBID => $kbid, # char50
+ IV_PROBLEM_ID => $h{id}, # char35
+ IV_PROBLEM_LOC => { # MyTypes::BAPI_TTET_ADDRESS_COM
+ COUNTRY2 => 'GB', # char2
+ REGION => "", # char3
+ COUNTY => "", # char30
+ CITY => "", # char30
+ POSTALCODE => $postcode, # char10
+ STREET => truncate_string_with_entities(ent(encode_utf8($nearest_street)), 30), # char30
+ STREETNUMBER => "", # char5
+ GEOCODE => $geo_code, # char32
+ },
+ IV_PROBLEM_SUB => truncate_string_with_entities(ent(encode_utf8($h{title})), 40), # char40
+ },
+ );
+ if ($result) {
+ # currently not using this: get_EV_ORDER_GUID (maybe that's the customer number in the CRM)
+ if (my $barnet_id = $result->get_EV_ORDER_NO()) {
+ $problem->external_id( $barnet_id );
+ $problem->external_body( 'Barnet Borough Council' ); # better to use $problem->body()?
+ $problem->send_method_used('barnet');
+ $return = 0;
+ } else {
+ my @returned_items = split /<item[^>]*>/, $result->get_ET_RETURN;
+ my @messages = ();
+ foreach my $item (@returned_items) {
+ if ($item=~/<MESSAGE [^>]*>\s*(\S.*?)<\/MESSAGE>/) { # if there's a non-null MESSAGE in there, grab it
+ push @messages, $1; # best stab at extracting useful error message back from convoluted response
+ }
+ }
+ push @messages, "service returned no external id" unless @messages;
+ $err_msg = "Failed (problem id $h{id}): " . join(" \n ", @messages);
+ }
+ } else {
+ my %fault = (
+ 'code' => $result->get_faultcode(),
+ 'actor' => $result->get_faultactor(),
+ 'string' => $result->get_faultstring(),
+ 'detail' => $result->get_detail(), # possibly only contains debug info
+ );
+ foreach (keys %fault) {
+ $fault{$_}="" unless defined $fault{$_};
+ $fault{$_}=~s/^\s*|\s*$//g;
+ }
+ $fault{actor}&&=" (actor: $fault{actor})";
+ $fault{'detail'} &&= "\n" . $fault{'detail'};
+ $err_msg = "Failed (problem id $h{id}): Fault $fault{code}$fault{actor}\n$fault{string}$fault{detail}";
+ }
+
+ };
+ print "$err_msg\n" if $err_msg;
+ if ($@) {
+ my $e = shift;
+ print "Caught an error: $@\n";
+ }
+ if ( $return ) {
+ # for timeouts, we can tidy the message a wee bit (i.e. strip the 'error deserializing...' message)
+ $err_msg=~s/(?:Error deserializing message:.*)(Can't connect to [a-zA-Z0-9.:]+\s*\(Connection timed out\)).*/$1/s;
+ $self->error( "Error sending to Barnet: $err_msg" );
+ }
+ $self->success( !$return );
+ return $return;
+}
+
+# for barnet webservice: max-length fields require truncate and split
+
+# truncate_string_with_entities
+# args: text to truncate
+# max number of chars
+# returns: string truncated
+# Note: must not partially truncate an entity (e.g., &amp;)
+sub truncate_string_with_entities {
+ my ($str, $max_len) = @_;
+ my $retVal = "";
+ foreach my $chunk (split /(\&(?:\#\d+|\w+);)/, $str) {
+ if ($chunk=~/^\&(\#\d+|\w+);$/){
+ my $next = $retVal.$chunk;
+ last if length $next > $max_len;
+ $retVal=$next
+ } else {
+ $retVal.=$chunk;
+ if (length $retVal > $max_len) {
+ $retVal = substr($retVal, 0, $max_len);
+ last
+ }
+ }
+ }
+ return $retVal
+}
+
+# split_text_with_entities into lines
+# args: text to be broken into lines
+# max length (option: uses constant MAX_LINE_LENGTH)
+# returns: array of lines
+# Must not to split an entity (e.g., &amp;)
+# Not worrying about hyphenating here, since a word is only ever split if
+# it's longer than the whole line, which is uncommon in genuine problem reports
+sub split_text_with_entities {
+ my ($text, $max_line_length) = @_;
+ $max_line_length ||= MAX_LINE_LENGTH;
+ my @lines;
+ foreach my $line (split "\n", $text) {
+ while (length $line > $max_line_length) {
+ if (! ($line =~ s/^(.{1,$max_line_length})\s// # break on a space
+ or $line =~ s/^(.{1,$max_line_length})(\&(\#\d+|\w+);)/$2/ # break before an entity
+ or $line =~ s/(.{$max_line_length})//)) { # break the word ruthlessly
+ $line =~ s/(.*)//; # otherwise gobble whole line (which is now shorter than max length)
+ }
+ push @lines, $1;
+ }
+ push @lines, $line;
+ }
+ return @lines;
+}
+
+1;
diff --git a/perllib/FixMyStreet/SendReport/EastHants.pm b/perllib/FixMyStreet/SendReport/EastHants.pm
new file mode 100644
index 000000000..681a9d4c4
--- /dev/null
+++ b/perllib/FixMyStreet/SendReport/EastHants.pm
@@ -0,0 +1,62 @@
+package FixMyStreet::SendReport::EastHants;
+
+use Moose;
+
+BEGIN { extends 'FixMyStreet::SendReport'; }
+
+# export just what we need as error if we use :try
+use Error qw(try otherwise);
+use Encode;
+use mySociety::Web qw(ent);
+use EastHantsWSDL;
+
+sub construct_message {
+ my %h = @_;
+ my $message = '';
+ $message .= "[ This report was also sent to the district council covering the location of the problem, as the user did not categorise it; please ignore if you're not the correct council to deal with the issue. ]\n\n"
+ if $h{multiple};
+ $message .= <<EOF;
+Subject: $h{title}
+
+Details: $h{detail}
+
+$h{fuzzy}, or to provide an update on the problem, please visit the following link:
+
+$h{url}
+
+$h{closest_address}
+EOF
+ return $message;
+}
+
+sub send {
+ return if mySociety::Config::get('STAGING_SITE');
+
+ my ( $self, $row, $h, $to, $template, $recips, $nomail ) = @_;
+
+ # FIXME: should not recreate this each time
+ my $eh_service;
+
+ $h->{category} = 'Customer Services' if $h->{category} eq 'Other';
+ $h->{message} = construct_message( %$h );
+ my $return = 1;
+ $eh_service ||= EastHantsWSDL->on_fault(sub { my($soap, $res) = @_; die ref $res ? $res->faultstring : $soap->transport->status, "\n"; });
+ try {
+ # ServiceName, RemoteCreatedBy, Salutation, FirstName, Name, Email, Telephone, HouseNoName, Street, Town, County, Country, Postcode, Comments, FurtherInfo, ImageURL
+ my $message = ent(encode_utf8($h->{message}));
+ my $name = ent(encode_utf8($h->{name}));
+ my $result = $eh_service->INPUTFEEDBACK(
+ $h->{category}, 'FixMyStreet', '', '', $name, $h->{email}, $h->{phone},
+ '', '', '', '', '', '', $message, 'Yes', $h->{image_url}
+ );
+ $return = 0 if $result eq 'Report received';
+ } otherwise {
+ my $e = shift;
+ print "Caught an error: $e\n";
+ $self->error( "Error sending to East Hants: $e" );
+ };
+ $self->success( !$return );
+ return $return;
+}
+
+1;
diff --git a/perllib/FixMyStreet/SendReport/Email.pm b/perllib/FixMyStreet/SendReport/Email.pm
new file mode 100644
index 000000000..239bee715
--- /dev/null
+++ b/perllib/FixMyStreet/SendReport/Email.pm
@@ -0,0 +1,111 @@
+package FixMyStreet::SendReport::Email;
+
+use Moose;
+
+BEGIN { extends 'FixMyStreet::SendReport'; }
+
+use mySociety::EmailUtil;
+
+sub build_recipient_list {
+ my $self = shift;
+ my $row = shift;
+ my %recips;
+
+ my $all_confirmed = 1;
+ foreach my $council ( keys %{ $self->councils } ) {
+ my $contact = FixMyStreet::App->model("DB::Contact")->find( {
+ deleted => 0,
+ area_id => $council,
+ category => $row->category
+ } );
+
+ my ($council_email, $confirmed, $note) = ( $contact->email, $contact->confirmed, $contact->note );
+
+ $council_email = essex_contact($row->latitude, $row->longitude) if $council == 2225;
+ $council_email = oxfordshire_contact($row->latitude, $row->longitude) if $council == 2237 && $council_email eq 'SPECIAL';
+
+ unless ($confirmed) {
+ $all_confirmed = 0;
+ #$note = 'Council ' . $row->council . ' deleted'
+ #unless $note;
+ $council_email = 'N/A' unless $council_email;
+ #$notgot{$council_email}{$row->category}++;
+ #$note{$council_email}{$row->category} = $note;
+ }
+
+ push @{ $self->to }, [ $council_email, $self->councils->{ $council } ];
+ $recips{$council_email} = 1;
+ }
+
+ return () unless $all_confirmed;
+ return keys %recips;
+}
+
+sub send {
+ my $self = shift;
+ my ( $row, $h, $to, $template, $recips, $nomail, $areas_info ) = @_;
+
+ my @recips;
+
+ @recips = $self->build_recipient_list( $row, $areas_info );
+
+ # on a staging server send emails to ourselves rather than the councils
+ if (mySociety::Config::get('STAGING_SITE')) {
+ @recips = ( mySociety::Config::get('CONTACT_EMAIL') );
+ }
+
+ return unless @recips;
+
+ my $result = FixMyStreet::App->send_email_cron(
+ {
+ _template_ => $template,
+ _parameters_ => $h,
+ To => $self->to,
+ From => [ $row->user->email, $row->name ],
+ },
+ mySociety::Config::get('CONTACT_EMAIL'),
+ \@recips,
+ $nomail
+ );
+
+ if ( $result == mySociety::EmailUtil::EMAIL_SUCCESS ) {
+ $self->success(1);
+ } else {
+ $self->error( 'Failed to send email' );
+ }
+
+ return $result;
+}
+
+# Essex has different contact addresses depending upon the district
+# Might be easier if we start storing in the db all areas covered by a point
+# Will do for now :)
+sub essex_contact {
+ my $district = _get_district_for_contact(@_);
+ my $email;
+ $email = 'eastarea' if $district == 2315 || $district == 2312;
+ $email = 'midarea' if $district == 2317 || $district == 2314 || $district == 2316;
+ $email = 'southarea' if $district == 2319 || $district == 2320 || $district == 2310;
+ $email = 'westarea' if $district == 2309 || $district == 2311 || $district == 2318 || $district == 2313;
+ die "Returned district $district which is not in Essex!" unless $email;
+ return "highways.$email\@essexcc.gov.uk";
+}
+
+# Oxfordshire has different contact addresses depending upon the district
+sub oxfordshire_contact {
+ my $district = _get_district_for_contact(@_);
+ my $email;
+ $email = 'northernarea' if $district == 2419 || $district == 2420 || $district == 2421;
+ $email = 'southernarea' if $district == 2417 || $district == 2418;
+ die "Returned district $district which is not in Oxfordshire!" unless $email;
+ return "$email\@oxfordshire.gov.uk";
+}
+
+sub _get_district_for_contact {
+ my ( $lat, $lon ) = @_;
+ my $district =
+ mySociety::MaPit::call( 'point', "4326/$lon,$lat", type => 'DIS' );
+ ($district) = keys %$district;
+ return $district;
+}
+1;
diff --git a/perllib/FixMyStreet/SendReport/EmptyHomes.pm b/perllib/FixMyStreet/SendReport/EmptyHomes.pm
new file mode 100644
index 000000000..9453b4ca5
--- /dev/null
+++ b/perllib/FixMyStreet/SendReport/EmptyHomes.pm
@@ -0,0 +1,51 @@
+package FixMyStreet::SendReport::EmptyHomes;
+
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'FixMyStreet::SendReport::Email'; }
+
+sub build_recipient_list {
+ my $self = shift;
+ my $row = shift;
+ my $areas_info = shift;
+ my %recips;
+
+ my $all_confirmed = 1;
+ foreach my $council ( keys %{ $self->councils } ) {
+ my $contact = FixMyStreet::App->model("DB::Contact")->find( {
+ deleted => 0,
+ area_id => $council,
+ category => 'Empty property',
+ } );
+
+ my ($council_email, $confirmed, $note) = ( $contact->email, $contact->confirmed, $contact->note );
+
+ $council_email = essex_contact($row->latitude, $row->longitude) if $council == 2225;
+ $council_email = oxfordshire_contact($row->latitude, $row->longitude) if $council == 2237 && $council_email eq 'SPECIAL';
+
+ unless ($confirmed) {
+ $all_confirmed = 0;
+ #$note = 'Council ' . $row->council . ' deleted'
+ #unless $note;
+ $council_email = 'N/A' unless $council_email;
+ #$notgot{$council_email}{$row->category}++;
+ #$note{$council_email}{$row->category} = $note;
+ }
+
+ push @{ $self->to }, [ $council_email, $self->councils->{ $council } ];
+ $recips{$council_email} = 1;
+
+ my $country = $areas_info->{$council}->{country};
+ if ($country eq 'W') {
+ $recips{ 'shelter@' . mySociety::Config::get('EMAIL_DOMAIN') } = 1;
+ } else {
+ $recips{ 'eha@' . mySociety::Config::get('EMAIL_DOMAIN') } = 1;
+ }
+ }
+
+ return () unless $all_confirmed;
+ return keys %recips;
+}
+
+1;
diff --git a/perllib/FixMyStreet/SendReport/London.pm b/perllib/FixMyStreet/SendReport/London.pm
new file mode 100644
index 000000000..58ecb2375
--- /dev/null
+++ b/perllib/FixMyStreet/SendReport/London.pm
@@ -0,0 +1,113 @@
+package FixMyStreet::SendReport::London;
+
+use Moose;
+
+BEGIN { extends 'FixMyStreet::SendReport'; }
+
+use Digest::MD5;
+use LWP::UserAgent;
+use LWP::Simple;
+
+use Utils;
+
+sub construct_message {
+ my %h = @_;
+ return <<EOF,
+A user of FixMyStreet has submitted the following report of a local
+problem that they believe might require your attention.
+
+Subject: $h{title}
+
+Details: $h{detail}
+
+$h{fuzzy}, or to provide an update on the problem, please visit the
+following link:
+
+$h{url}
+
+$h{closest_address}
+Yours,
+The FixMyStreet team
+EOF
+}
+
+sub send {
+ return if mySociety::Config::get('STAGING_SITE');
+ my ( $self, $row, $h, $to, $template, $recips, $nomail ) = @_;
+
+ $h->{message} = construct_message( %$h );
+ my $phone = $h->{phone};
+ my $mobile = '';
+ if ($phone && $phone =~ /^\s*07/) {
+ $mobile = $phone;
+ $phone = '';
+ }
+ my ($first, $last) = $h->{name} =~ /^(\S*)(?: (.*))?$/;
+ my %params = (
+ Key => mySociety::Config::get('LONDON_REPORTIT_KEY'),
+ Signature => Digest::MD5::md5_hex( $h->{confirmed} . mySociety::Config::get('LONDON_REPORTIT_SECRET') ),
+ Type => Utils::london_categories()->{$h->{category}},
+ RequestDate => $h->{confirmed},
+ RequestMethod => 'Web',
+ ExternalId => $h->{url},
+ 'Customer.Title' => '',
+ 'Customer.FirstName' => $first,
+ 'Customer.Surname' => $last,
+ 'Customer.Email' => $h->{email},
+ 'Customer.Phone' => $phone,
+ 'Customer.Mobile' => $mobile,
+ 'ProblemDescription' => $h->{message},
+ );
+ if ($h->{used_map}) {
+ $params{'Location.Latitude'} = $h->{latitude};
+ $params{'Location.Longitude'} = $h->{longitude};
+ } elsif (mySociety::PostcodeUtil::is_valid_postcode($h->{query})) {
+ # Didn't use map, and entered postcode, so use that.
+ $params{'Location.Postcode'} = $h->{query};
+ } else {
+ # Otherwise, lat/lon is all we have, even if it's wrong.
+ $params{'Location.Latitude'} = $h->{latitude};
+ $params{'Location.Longitude'} = $h->{longitude};
+ }
+ if ($h->{has_photo}) {
+ $params{'Document1.Name'} = 'Photograph';
+ $params{'Document1.MimeType'} = 'image/jpeg';
+ $params{'Document1.URL'} = $h->{image_url};
+ $params{'Document1.URLPublic'} = 'true';
+ }
+ my $browser = LWP::UserAgent->new;
+ my $response = $browser->post( mySociety::Config::get('LONDON_REPORTIT_URL'), \%params );
+ my $out = $response->content;
+ if ($response->code ne 200) {
+ print "Failed to post $h->{id} to London API, response was " . $response->code . " $out\n";
+ $self->error( "Failed to post $h->{id} to London API, response was " . $response->code . " $out" );
+ return 1;
+ }
+ my ($id) = $out =~ /<caseid>(.*?)<\/caseid>/;
+ my ($org) = $out =~ /<organisation>(.*?)<\/organisation>/;
+ my ($team) = $out =~ /<team>(.*?)<\/team>/;
+
+ $org = london_lookup($org);
+ $row->external_id( $id );
+ $row->external_body( $org );
+ $row->external_team( $team );
+ $self->success(1);
+ return 0;
+}
+
+sub london_lookup {
+ my $org = shift || '';
+ my $str = "Unknown ($org)";
+ open(FP, "$FindBin::Bin/../data/dft.csv");
+ while (<FP>) {
+ /^(.*?),(.*)/;
+ if ($org eq $1) {
+ $str = $2;
+ last;
+ }
+ }
+ close FP;
+ return $str;
+}
+
+1;
diff --git a/perllib/FixMyStreet/SendReport/Open311.pm b/perllib/FixMyStreet/SendReport/Open311.pm
new file mode 100644
index 000000000..52268fd91
--- /dev/null
+++ b/perllib/FixMyStreet/SendReport/Open311.pm
@@ -0,0 +1,133 @@
+package FixMyStreet::SendReport::Open311;
+
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'FixMyStreet::SendReport'; }
+
+use FixMyStreet::App;
+use mySociety::Config;
+use DateTime::Format::W3CDTF;
+use Open311;
+
+sub should_skip {
+ my $self = shift;
+ my $row = shift;
+
+ if ( $row->send_fail_count > 0 ) {
+ if ( bromley_retry_timeout($row) ) {
+ return 1;
+ }
+ }
+}
+
+sub send {
+ my $self = shift;
+ my ( $row, $h, $to, $template, $recips, $nomail ) = @_;
+
+ my $result = -1;
+
+ foreach my $council ( keys %{ $self->councils } ) {
+ my $conf = FixMyStreet::App->model("DB::Open311conf")->search( { area_id => $council, endpoint => { '!=', '' } } )->first;
+
+ my $always_send_latlong = 1;
+ my $send_notpinpointed = 0;
+
+ my $basic_desc = 0;
+
+ # Extra bromley fields
+ if ( $row->council =~ /2482/ ) {
+
+ my $extra = $row->extra;
+ if ( $row->used_map || ( !$row->used_map && !$row->postcode ) ) {
+ push @$extra, { name => 'northing', value => $h->{northing} };
+ push @$extra, { name => 'easting', value => $h->{easting} };
+ }
+ push @$extra, { name => 'report_url', value => $h->{url} };
+ push @$extra, { name => 'service_request_id_ext', value => $row->id };
+ push @$extra, { name => 'report_title', value => $row->title };
+ push @$extra, { name => 'public_anonymity_required', value => $row->anonymous ? 'TRUE' : 'FALSE' };
+ push @$extra, { name => 'email_alerts_requested', value => 'FALSE' }; # always false as can never request them
+ push @$extra, { name => 'requested_datetime', value => DateTime::Format::W3CDTF->format_datetime($row->confirmed_local->set_nanosecond(0)) };
+ push @$extra, { name => 'email', value => $row->user->email };
+ $row->extra( $extra );
+
+ $always_send_latlong = 0;
+ $send_notpinpointed = 1;
+
+ # make sure we have first_name and last_name attributes
+ if ( $row->cobrand ne 'bromley' ) {
+ my ( $firstname, $lastname ) = ( $row->user->name =~ /(\w+)\s+(.+)/ );
+ push @$extra, { name => 'first_name', value => $firstname };
+ push @$extra, { name => 'last_name', value => $lastname };
+ }
+
+ $basic_desc = 1;
+ }
+
+ # FIXME: we've already looked this up before
+ my $contact = FixMyStreet::App->model("DB::Contact")->find( {
+ deleted => 0,
+ area_id => $conf->area_id,
+ category => $row->category
+ } );
+
+ my $open311 = Open311->new(
+ jurisdiction => $conf->jurisdiction,
+ endpoint => $conf->endpoint,
+ api_key => $conf->api_key,
+ always_send_latlong => $always_send_latlong,
+ send_notpinpointed => $send_notpinpointed,
+ basic_description => $basic_desc,
+ );
+
+ # non standard west berks end points
+ if ( $row->council =~ /2619/ ) {
+ $open311->endpoints( { services => 'Services', requests => 'Requests' } );
+ }
+
+ # required to get round issues with CRM constraints
+ if ( $row->council =~ /2218/ ) {
+ $row->user->name( $row->user->id . ' ' . $row->user->name );
+ }
+
+ my $resp = $open311->send_service_request( $row, $h, $contact->email );
+
+ # make sure we don't save user changes from above
+ if ( $row->council =~ /2218/ || $row->council =~ /2482/ ) {
+ $row->discard_changes();
+ }
+
+ if ( $resp ) {
+ $row->external_id( $resp );
+ $row->send_method_used('Open311');
+ $result *= 0;
+ $self->success( 1 );
+ } else {
+ $result *= 1;
+ # temporary fix to resolve some issues with west berks
+ if ( $row->council =~ /2619/ ) {
+ $result *= 0;
+ }
+ }
+ }
+
+ $self->error( 'Failed to send over Open311' ) unless $self->success;
+
+ return $result;
+}
+
+sub bromley_retry_timeout {
+ my $row = shift;
+
+ my $tz = DateTime::TimeZone->new( name => 'local' );
+ my $now = DateTime->now( time_zone => $tz );
+ my $diff = $now - $row->send_fail_timestamp;
+ if ( $diff->in_units( 'minutes' ) < 30 ) {
+ return 1;
+ }
+
+ return 0;
+}
+
+1;
diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm
index 7daf01f56..dc42c1aba 100644
--- a/perllib/FixMyStreet/TestMech.pm
+++ b/perllib/FixMyStreet/TestMech.pm
@@ -225,6 +225,7 @@ sub form_errors {
my $result = scraper {
process 'div.form-error', 'errors[]', 'TEXT';
process 'p.form-error', 'errors[]', 'TEXT';
+ process 'p.error', 'errors[]', 'TEXT';
}
->scrape( $mech->response );
return $result->{errors} || [];
diff --git a/perllib/Open311.pm b/perllib/Open311.pm
index e26e3e4c6..2df6ac5dd 100644
--- a/perllib/Open311.pm
+++ b/perllib/Open311.pm
@@ -5,6 +5,7 @@ use Moose;
use XML::Simple;
use LWP::Simple;
use LWP::UserAgent;
+use DateTime::Format::W3CDTF;
use HTTP::Request::Common qw(POST);
has jurisdiction => ( is => 'ro', isa => 'Str' );;
@@ -12,10 +13,23 @@ has api_key => ( is => 'ro', isa => 'Str' );
has endpoint => ( is => 'ro', isa => 'Str' );
has test_mode => ( is => 'ro', isa => 'Bool' );
has test_uri_used => ( is => 'rw', 'isa' => 'Str' );
+has test_req_used => ( is => 'rw' );
has test_get_returns => ( is => 'rw' );
-has endpoints => ( is => 'rw', default => sub { { services => 'services.xml', requests => 'requests.xml' } } );
+has endpoints => ( is => 'rw', default => sub { { services => 'services.xml', requests => 'requests.xml', service_request_updates => 'update.xml', update => 'update.xml' } } );
has debug => ( is => 'ro', isa => 'Bool', default => 0 );
has debug_details => ( is => 'rw', 'isa' => 'Str', default => '' );
+has success => ( is => 'rw', 'isa' => 'Bool', default => 0 );
+has error => ( is => 'rw', 'isa' => 'Str', default => '' );
+has always_send_latlong => ( is => 'ro', isa => 'Bool', default => 1 );
+has send_notpinpointed => ( is => 'ro', isa => 'Bool', default => 0 );
+has basic_description => ( is => 'ro', isa => 'Bool', default => 0 );
+
+before [
+ qw/get_service_list get_service_meta_info get_service_requests get_service_request_updates
+ send_service_request post_service_request_update/
+ ] => sub {
+ shift->debug_details('');
+ };
sub get_service_list {
my $self = shift;
@@ -39,22 +53,49 @@ sub send_service_request {
my $extra = shift;
my $service_code = shift;
- my $description = <<EOT;
-title: @{[$problem->title()]}
+ my $params = $self->_populate_service_request_params(
+ $problem, $extra, $service_code
+ );
-detail: @{[$problem->detail()]}
+ my $response = $self->_post( $self->endpoints->{requests}, $params );
-url: $extra->{url}
+ if ( $response ) {
+ my $obj = $self->_get_xml_object( $response );
-Submitted via FixMyStreet
-EOT
-;
+ if ( $obj ) {
+ if ( $obj->{ request }->{ service_request_id } ) {
+ return $obj->{ request }->{ service_request_id };
+ } else {
+ my $token = $obj->{ request }->{ token };
+ if ( $token ) {
+ return $self->get_service_request_id_from_token( $token );
+ }
+ }
+ }
+
+ warn sprintf( "Failed to submit problem %s over Open311, response\n: %s\n%s", $problem->id, $response, $self->debug_details );
+ return 0;
+ }
+}
+
+sub _populate_service_request_params {
+ my $self = shift;
+ my $problem = shift;
+ my $extra = shift;
+ my $service_code = shift;
+
+ my $description;
+ if ( $self->basic_description ) {
+ $description = $problem->detail;
+ } else {
+ $description = $self->_generate_service_request_description(
+ $problem, $extra
+ );
+ }
my ( $firstname, $lastname ) = ( $problem->user->name =~ /(\w+)\s+(.+)/ );
my $params = {
- lat => $problem->latitude,
- long => $problem->longitude,
email => $problem->user->email,
description => $description,
service_code => $service_code,
@@ -62,6 +103,25 @@ EOT
last_name => $lastname || '',
};
+ # if you click nearby reports > skip map then it's possible
+ # to end up with used_map = f and nothing in postcode
+ if ( $problem->used_map || $self->always_send_latlong
+ || ( !$self->send_notpinpointed && !$problem->used_map
+ && !$problem->postcode ) )
+ {
+ $params->{lat} = $problem->latitude;
+ $params->{long} = $problem->longitude;
+ # this is a special case for sending to Bromley so they can
+ # report accuracy levels correctly. We include easting and
+ # northing as attributes elsewhere.
+ } elsif ( $self->send_notpinpointed && !$problem->used_map
+ && !$problem->postcode )
+ {
+ $params->{address_id} = '#NOTPINPOINTED#';
+ } else {
+ $params->{address_string} = $problem->postcode;
+ }
+
if ( $problem->user->phone ) {
$params->{ phone } = $problem->user->phone;
}
@@ -74,30 +134,37 @@ EOT
my $extras = $problem->extra;
for my $attr ( @$extras ) {
- my $name = sprintf( 'attribute[%s]', $attr->{name} );
+ my $attr_name = $attr->{name};
+ if ( $attr_name eq 'first_name' || $attr_name eq 'last_name' ) {
+ $params->{$attr_name} = $attr->{value} if $attr->{value};
+ next if $attr_name eq 'first_name';
+ }
+ $attr_name =~ s/fms_extra_//;
+ my $name = sprintf( 'attribute[%s]', $attr_name );
$params->{ $name } = $attr->{value};
}
}
- my $response = $self->_post( $self->endpoints->{requests}, $params );
+ return $params;
+}
- if ( $response ) {
- my $obj = $self->_get_xml_object( $response );
+sub _generate_service_request_description {
+ my $self = shift;
+ my $problem = shift;
+ my $extra = shift;
- if ( $obj ) {
- if ( $obj->{ request }->{ service_request_id } ) {
- return $obj->{ request }->{ service_request_id };
- } else {
- my $token = $obj->{ request }->{ token };
- if ( $token ) {
- return $self->get_service_request_id_from_token( $token );
- }
- }
- }
+ my $description = <<EOT;
+title: @{[$problem->title()]}
- warn sprintf( "Failed to submit problem %s over Open311, response\n: %s\n%s", $problem->id, $response, $self->debug_details );
- return 0;
- }
+detail: @{[$problem->detail()]}
+
+url: $extra->{url}
+
+Submitted via FixMyStreet
+EOT
+;
+
+ return $description;
}
sub get_service_requests {
@@ -129,6 +196,107 @@ sub get_service_request_id_from_token {
}
}
+sub get_service_request_updates {
+ my $self = shift;
+ my $start_date = shift;
+ my $end_date = shift;
+
+ my $params = {
+ api_key => $self->api_key,
+ jurisdiction => $self->jurisdiction,
+ };
+
+ if ( $start_date || $end_date ) {
+ return 0 unless $start_date && $end_date;
+
+ $params->{start_date} = $start_date;
+ $params->{end_date} = $end_date;
+ }
+
+ my $xml = $self->_get( $self->endpoints->{service_request_updates}, $params || undef );
+ my $service_requests = $self->_get_xml_object( $xml );
+ my $requests;
+ if ( ref $service_requests->{request_update } eq 'ARRAY' ) {
+ $requests = $service_requests->{request_update};
+ }
+ else {
+ $requests = [ $service_requests->{request_update} ];
+ }
+
+ return $requests;
+}
+
+sub post_service_request_update {
+ my $self = shift;
+ my $comment = shift;
+
+ my $params = $self->_populate_service_request_update_params( $comment );
+
+ my $response = $self->_post( $self->endpoints->{update}, $params );
+
+ if ( $response ) {
+ my $obj = $self->_get_xml_object( $response );
+
+ if ( $obj ) {
+ if ( $obj->{ request_update }->{ update_id } ) {
+ my $update_id = $obj->{request_update}->{update_id};
+
+ # if there's nothing in the update_id element we get a HASHREF back
+ unless ( ref $update_id ) {
+ return $obj->{ request_update }->{ update_id };
+ }
+ } else {
+ my $token = $obj->{ request_update }->{ token };
+ if ( $token ) {
+ return $self->get_service_request_id_from_token( $token );
+ }
+ }
+ }
+
+ warn sprintf( "Failed to submit comment %s over Open311, response - %s\n%s\n", $comment->id, $response, $self->debug_details );
+ return 0;
+ }
+}
+
+sub _populate_service_request_update_params {
+ my $self = shift;
+ my $comment = shift;
+
+ my $name = $comment->name || $comment->user->name;
+ my ( $firstname, $lastname ) = ( $name =~ /(\w+)\s+(.+)/ );
+
+ my $params = {
+ update_id_ext => $comment->id,
+ updated_datetime => DateTime::Format::W3CDTF->format_datetime($comment->confirmed_local->set_nanosecond(0)),
+ service_request_id => $comment->problem->external_id,
+ service_request_id_ext => $comment->problem->id,
+ status => $comment->problem->is_open ? 'OPEN' : 'CLOSED',
+ email => $comment->user->email,
+ description => $comment->text,
+ public_anonymity_required => $comment->anonymous ? 'TRUE' : 'FALSE',
+ last_name => $lastname,
+ first_name => $firstname,
+ };
+
+ if ( $comment->photo ) {
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($comment->cobrand)->new();
+ my $email_base_url = $cobrand->base_url_for_emails($comment->cobrand_data);
+ my $url = $email_base_url . '/photos/c/' . $comment->id . '.full.jpeg';
+ $params->{media_url} = $url;
+ }
+
+ if ( $comment->extra ) {
+ $params->{'email_alerts_requested'}
+ = $comment->extra->{email_alerts_requested} ? 'TRUE' : 'FALSE';
+ $params->{'title'} = $comment->extra->{title};
+
+ $params->{first_name} = $comment->extra->{first_name} if $comment->extra->{first_name};
+ $params->{last_name} = $comment->extra->{last_name} if $comment->extra->{last_name};
+ }
+
+ return $params;
+}
+
sub _get {
my $self = shift;
my $path = shift;
@@ -144,10 +312,25 @@ sub _get {
my $content;
if ( $self->test_mode ) {
+ $self->success(1);
$content = $self->test_get_returns->{ $path };
$self->test_uri_used( $uri->as_string );
} else {
- $content = get( $uri->as_string );
+ my $ua = LWP::UserAgent->new;
+
+ my $req = HTTP::Request->new(
+ GET => $uri->as_string
+ );
+
+ my $res = $ua->request( $req );
+
+ if ( $res->is_success ) {
+ $content = $res->decoded_content;
+ $self->success(1);
+ } else {
+ $self->success(0);
+ $self->error( $res->status_line );
+ }
}
return $content;
@@ -171,17 +354,27 @@ sub _post {
$self->debug_details( $self->debug_details . "\nrequest:" . $req->as_string );
my $ua = LWP::UserAgent->new();
- my $res = $ua->request( $req );
+ my $res;
+
+ if ( $self->test_mode ) {
+ $res = $self->test_get_returns->{ $path };
+ $self->test_req_used( $req );
+ } else {
+ $res = $ua->request( $req );
+ }
if ( $res->is_success ) {
+ $self->success(1);
return $res->decoded_content;
} else {
- warn sprintf(
- "request failed: %s\nerror: %s\n%s",
+ $self->success(0);
+ $self->error( sprintf(
+ "request failed: %s\nerror: %s\n%s\n",
$res->status_line,
$self->_process_error( $res->decoded_content ),
$self->debug_details
- );
+ ) );
+ warn $self->error;
return 0;
}
}
diff --git a/perllib/Open311/GetServiceRequestUpdates.pm b/perllib/Open311/GetServiceRequestUpdates.pm
new file mode 100644
index 000000000..c30d05b22
--- /dev/null
+++ b/perllib/Open311/GetServiceRequestUpdates.pm
@@ -0,0 +1,132 @@
+package Open311::GetServiceRequestUpdates;
+
+use Moose;
+use Open311;
+use FixMyStreet::App;
+use DateTime::Format::W3CDTF;
+
+has council_list => ( is => 'ro' );
+has system_user => ( is => 'rw' );
+has start_date => ( is => 'ro', default => undef );
+has end_date => ( is => 'ro', default => undef );
+has suppress_alerts => ( is => 'rw', default => 0 );
+
+sub fetch {
+ my $self = shift;
+
+ my $councils = FixMyStreet::App->model('DB::Open311Conf')->search(
+ {
+ send_method => 'Open311',
+ send_comments => 1,
+ comment_user_id => { '!=', undef },
+ endpoint => { '!=', '' },
+ }
+ );
+
+ while ( my $council = $councils->next ) {
+
+ my $o = Open311->new(
+ endpoint => $council->endpoint,
+ api_key => $council->api_key,
+ jurisdiction => $council->jurisdiction,
+ );
+
+ $self->suppress_alerts( $council->suppress_alerts );
+ $self->system_user( $council->comment_user );
+ $self->update_comments( $o, { areaid => $council->area_id }, );
+ }
+}
+
+sub update_comments {
+ my ( $self, $open311, $council_details ) = @_;
+
+ my @args = ();
+
+ if ( $self->start_date || $self->end_date ) {
+ return 0 unless $self->start_date && $self->end_date;
+
+ push @args, $self->start_date;
+ push @args, $self->end_date;
+ }
+
+ my $requests = $open311->get_service_request_updates( @args );
+
+ unless ( $open311->success ) {
+ warn "Failed to fetch ServiceRequest Updates: " . $open311->error;
+ return 0;
+ }
+
+ for my $request (@$requests) {
+ my $request_id = $request->{service_request_id};
+
+ # If there's no request id then we can't work out
+ # what problem it belongs to so just skip
+ next unless $request_id;
+
+ my $problem =
+ FixMyStreet::App->model('DB::Problem')
+ ->search( {
+ external_id => $request_id,
+ council => { like => '%' . $council_details->{areaid} . '%' },
+ } );
+
+ if (my $p = $problem->first) {
+ my $c = $p->comments->search( { external_id => $request->{update_id} } );
+
+ if ( !$c->first ) {
+ my $comment_time = DateTime::Format::W3CDTF->parse_datetime( $request->{updated_datetime} );
+
+ my $comment = FixMyStreet::App->model('DB::Comment')->new(
+ {
+ problem => $p,
+ user => $self->system_user,
+ external_id => $request->{update_id},
+ text => $request->{description},
+ mark_fixed => 0,
+ mark_open => 0,
+ anonymous => 0,
+ name => $self->system_user->name,
+ confirmed => $comment_time,
+ created => $comment_time,
+ state => 'confirmed',
+ }
+ );
+
+ # if the comment is older than the last update
+ # do not change the status of the problem as it's
+ # tricky to determine the right thing to do.
+ if ( $comment->created_local > $p->lastupdate_local ) {
+ if ( $p->is_open and lc($request->{status}) eq 'closed' ) {
+ $p->state( 'fixed - council' );
+ $comment->problem_state( 'fixed - council' );
+ } elsif ( ( $p->is_closed || $p->is_fixed ) and lc($request->{status}) eq 'open' ) {
+ $p->state( 'confirmed' );
+ $comment->problem_state( 'confirmed' );
+ }
+ }
+
+ $p->lastupdate( $comment->created );
+ $p->update;
+ $comment->insert();
+
+ if ( $self->suppress_alerts ) {
+ my $alert = FixMyStreet::App->model('DB::Alert')->find( {
+ alert_type => 'new_updates',
+ parameter => $p->id,
+ confirmed => 1,
+ user_id => $p->user->id,
+ } );
+
+ my $alerts_sent = FixMyStreet::App->model('DB::AlertSent')->find_or_create( {
+ alert_id => $alert->id,
+ parameter => $comment->id,
+ } );
+ }
+ }
+ }
+ }
+
+ return 1;
+}
+
+1;
diff --git a/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm
index cfec9005d..7b5f4c7fe 100644
--- a/perllib/Open311/PopulateServiceList.pm
+++ b/perllib/Open311/PopulateServiceList.pm
@@ -8,6 +8,7 @@ use Open311;
has council_list => ( is => 'ro' );
has found_contacts => ( is => 'rw', default => sub { [] } );
+has verbose => ( is => 'ro', default => 0 );
has _current_council => ( is => 'rw' );
has _current_open311 => ( is => 'rw' );
@@ -81,7 +82,7 @@ sub process_service {
$self->_current_service->{description} :
$self->_current_service->{service_name};
- print $self->_current_service->{service_code} . ': ' . $category . "\n";
+ print $self->_current_service->{service_code} . ': ' . $category . "\n" if $self->verbose;
my $contacts = FixMyStreet::App->model( 'DB::Contact')->search(
{
area_id => $self->_current_council->area_id,
@@ -121,7 +122,7 @@ sub _handle_existing_contact {
my $service_name = $self->_normalize_service_name;
- print $self->_current_council->area_id . " already has a contact for service code " . $self->_current_service->{service_code} . "\n";
+ print $self->_current_council->area_id . " already has a contact for service code " . $self->_current_service->{service_code} . "\n" if $self->verbose;
if ( $contact->deleted || $service_name ne $contact->category || $self->_current_service->{service_code} ne $contact->email ) {
eval {
@@ -179,16 +180,22 @@ sub _create_contact {
if ( $contact ) {
push @{ $self->found_contacts }, $self->_current_service->{service_code};
- print "created contact for service code " . $self->_current_service->{service_code} . " for council @{[$self->_current_council->area_id]}\n";
+ print "created contact for service code " . $self->_current_service->{service_code} . " for council @{[$self->_current_council->area_id]}\n" if $self->verbose;
}
}
-sub _add_contact_to_meta {
+sub _add_meta_to_contact {
my ( $self, $contact ) = @_;
- print "Fetching meta data for $self->_current_service->{service_code}\n";
+ print "Fetching meta data for $self->_current_service->{service_code}\n" if $self->verbose;
my $meta_data = $self->_current_open311->get_service_meta_info( $self->_current_service->{service_code} );
+ if ( ref $meta_data->{ attributes }->{ attribute } eq 'HASH' ) {
+ $meta_data->{ attributes }->{ attribute } = [
+ $meta_data->{ attributes }->{ attribute }
+ ];
+ }
+
# turn the data into something a bit more friendly to use
my @meta =
# remove trailing colon as we add this when we display so we don't want 2
@@ -197,7 +204,31 @@ sub _add_contact_to_meta {
sort { $a->{order} <=> $b->{order} }
@{ $meta_data->{attributes}->{attribute} };
- $contact->extra( \@meta );
+ # we add these later on from bromley so don't list them here
+ # as we don't want to display them
+ if ( $self->_current_council->area_id == 2482 ) {
+ my %ignore = map { $_ => 1 } qw/
+ service_request_id_ext
+ requested_datetime
+ report_url
+ title
+ last_name
+ email
+ easting
+ northing
+ report_title
+ public_anonymity_required
+ email_alerts_requested
+ /;
+
+ @meta = grep { ! $ignore{ $_->{ code } } } @meta;
+ }
+
+ if ( @meta ) {
+ $contact->extra( \@meta );
+ } else {
+ $contact->extra( undef );
+ }
$contact->update;
}
diff --git a/t/app/controller/about.t b/t/app/controller/about.t
index ea7b1af20..4e49cdac9 100644
--- a/t/app/controller/about.t
+++ b/t/app/controller/about.t
@@ -13,7 +13,7 @@ $mech->content_contains('html class="no-js" lang="en-gb"');
SKIP: {
skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 8 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{emptyhomes};
+ unless FixMyStreet::Cobrand->exists('emptyhomes');
# check that geting the page as EHA produces a different page
ok $mech->host("reportemptyhomes.co.uk"), 'change host to reportemptyhomes';
diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t
index d8a1c24a1..09d99cfdf 100644
--- a/t/app/controller/admin.t
+++ b/t/app/controller/admin.t
@@ -105,34 +105,39 @@ subtest 'check summary counts' => sub {
$mech->content_contains( "$q_count questionnaires sent" );
- ok $mech->host('barnet.fixmystreet.com');
+ SKIP: {
+ skip( "Need 'barnet' in ALLOWED_COBRANDS config", 7 )
+ unless FixMyStreet::Cobrand->exists('barnet');
- $mech->get_ok('/admin');
- $mech->title_like(qr/Summary/);
+ ok $mech->host('barnet.fixmystreet.com');
- my ($num_live) = $mech->content =~ /(\d+)<\/strong> live problems/;
- my ($num_alerts) = $mech->content =~ /(\d+) confirmed alerts/;
- my ($num_qs) = $mech->content =~ /(\d+) questionnaires sent/;
+ $mech->get_ok('/admin');
+ $mech->title_like(qr/Summary/);
- $report->council(2489);
- $report->cobrand('barnet');
- $report->update;
+ my ($num_live) = $mech->content =~ /(\d+)<\/strong> live problems/;
+ my ($num_alerts) = $mech->content =~ /(\d+) confirmed alerts/;
+ my ($num_qs) = $mech->content =~ /(\d+) questionnaires sent/;
- $alert->cobrand('barnet');
- $alert->update;
+ $report->council(2489);
+ $report->cobrand('barnet');
+ $report->update;
- $mech->get_ok('/admin');
+ $alert->cobrand('barnet');
+ $alert->update;
- $mech->content_contains( ($num_live+1) . "</strong> live problems" );
- $mech->content_contains( ($num_alerts+1) . " confirmed alerts" );
- $mech->content_contains( ($num_qs+1) . " questionnaires sent" );
+ $mech->get_ok('/admin');
- $report->council(2504);
- $report->cobrand('');
- $report->update;
+ $mech->content_contains( ($num_live+1) . "</strong> live problems" );
+ $mech->content_contains( ($num_alerts+1) . " confirmed alerts" );
+ $mech->content_contains( ($num_qs+1) . " questionnaires sent" );
+
+ $report->council(2504);
+ $report->cobrand('');
+ $report->update;
- $alert->cobrand('');
- $alert->update;
+ $alert->cobrand('');
+ $alert->update;
+ }
FixMyStreet::App->model('DB::Problem')->search( { council => 1 } )->update( { council => 2489 } );
ok $mech->host('fixmystreet.com');
@@ -222,6 +227,8 @@ subtest 'check open311 configuring' => sub {
api_key => 'api key',
endpoint => 'http://example.com/open311',
jurisdiction => 'mySociety',
+ send_comments => 0,
+ send_method => 'Open311',
}
}
);
@@ -244,6 +251,8 @@ subtest 'check open311 configuring' => sub {
api_key => 'new api key',
endpoint => 'http://example.org/open311',
jurisdiction => 'open311',
+ send_comments => 0,
+ send_method => 'Open311',
}
}
);
diff --git a/t/app/controller/alert_new.t b/t/app/controller/alert_new.t
index 3a4c2ef81..7ba887824 100644
--- a/t/app/controller/alert_new.t
+++ b/t/app/controller/alert_new.t
@@ -142,25 +142,18 @@ foreach my $test (
}
foreach my $test (
- {
- email => 'test-new@example.com',
- type => 'area',
- content => 'your alert will not be activated',
- email_text => 'confirm the alert',
- uri =>
-'/alert/subscribe?type=local&rznvy=test-new@example.com&feed=area:1000:A_Location',
- param1 => 1000
- }
+ { exist => 0 },
+ { exist => 1 },
)
{
- subtest "use existing unlogged in user in a alert" => sub {
+ subtest "use existing unlogged in user in a alert ($test->{exist})" => sub {
$mech->log_out_ok();
- my $type = $test->{type} . '_problems';
+ my $type = 'area_problems';
my $user =
FixMyStreet::App->model('DB::User')
- ->find_or_create( { email => $test->{email} } );
+ ->find_or_create( { email => 'test-new@example.com' } );
my $alert = FixMyStreet::App->model('DB::Alert')->find(
{
@@ -169,24 +162,26 @@ foreach my $test (
}
);
# clear existing data so we can be sure we're creating it
- ok $alert->delete() if $alert;
+ ok $alert->delete() if $alert && !$test->{exist};
- $mech->get_ok( $test->{uri} );
+ $mech->get_ok( '/alert/subscribe?type=local&rznvy=test-new@example.com&feed=area:1000:A_Location' );
$alert = FixMyStreet::App->model('DB::Alert')->find(
{
user => $user,
alert_type => $type,
- parameter => $test->{param1},
- parameter2 => $test->{param2},
- confirmed => 0,
+ parameter => 1000,
+ parameter2 => undef,
+ confirmed => $test->{exist},
}
);
$mech->content_contains( 'Now check your email' );
+ $alert->confirm();
ok $alert, 'New alert created with existing user';
- $mech->delete_user($user);
+
+ $mech->delete_user($user) if $test->{exist};
};
}
@@ -445,6 +440,7 @@ subtest "Test normal alert signups and that alerts are sent" => sub {
ok $update, "created test update - $update_id";
FixMyStreet::App->model('DB::AlertType')->email_alerts();
+ # TODO Note the below will fail if the db has an existing alert that matches
$mech->email_count_is(3);
my @emails = $mech->get_email;
my $count;
diff --git a/t/app/controller/dashboard.t b/t/app/controller/dashboard.t
new file mode 100644
index 000000000..7033fa02c
--- /dev/null
+++ b/t/app/controller/dashboard.t
@@ -0,0 +1,639 @@
+use strict;
+use warnings;
+use Test::More;
+
+use FixMyStreet::TestMech;
+use Web::Scraper;
+
+my $mech = FixMyStreet::TestMech->new;
+
+my $test_user = 'council_user@example.com';
+my $test_pass = 'password';
+my $test_council = 2651;
+my $test_ward = 20723;
+
+$mech->delete_user( $test_user );
+my $user = FixMyStreet::App->model('DB::User')->create( {
+ email => $test_user,
+ password => $test_pass,
+} );
+
+my $p_user = FixMyStreet::App->model('DB::User')->find_or_create( {
+ email => 'p_user@example.com'
+} );
+
+$mech->not_logged_in_ok;
+$mech->get_ok('/dashboard');
+
+$mech->content_contains( 'sign in' );
+
+$mech->submit_form(
+ with_fields => { email => $test_user, password_sign_in => $test_pass }
+);
+
+is $mech->status, '404', 'If not council user get 404';
+
+$user->from_council( $test_council );
+$user->update;
+
+$mech->log_out_ok;
+$mech->get_ok('/dashboard');
+$mech->submit_form_ok( {
+ with_fields => { email => $test_user, password_sign_in => $test_pass }
+} );
+
+$mech->content_contains( 'Summary Statistics for City of Edinburgh' );
+
+FixMyStreet::App->model('DB::Contact')->search( { area_id => $test_council } )
+ ->delete;
+
+delete_problems();
+
+my @cats = qw( Grafitti Litter Potholes );
+for my $contact ( @cats ) {
+ FixMyStreet::App->model('DB::Contact')->create(
+ {
+ area_id => $test_council,
+ category => $contact,
+ email => "$contact\@example.org",
+ confirmed => 1,
+ whenedited => DateTime->now,
+ deleted => 0,
+ editor => 'test',
+ note => 'test',
+ }
+ );
+}
+
+$mech->get_ok('/dashboard');
+
+my $categories = scraper {
+ process "select[name=category] > option", 'cats[]' => 'TEXT',
+ process "select[name=ward] > option", 'wards[]' => 'TEXT',
+ process "table[id=overview] > tr", 'rows[]' => scraper {
+ process 'td', 'cols[]' => 'TEXT'
+ },
+ process "tr[id=total] > td", 'totals[]' => 'TEXT',
+ process "tr[id=fixed_council] > td", 'council[]' => 'TEXT',
+ process "tr[id=fixed_user] > td", 'user[]' => 'TEXT',
+ process "tr[id=total_fixed] > td", 'total_fixed[]' => 'TEXT',
+ process "tr[id=in_progress] > td", 'in_progress[]' => 'TEXT',
+ process "tr[id=planned] > td", 'planned[]' => 'TEXT',
+ process "tr[id=investigating] > td", 'investigating[]' => 'TEXT',
+ process "tr[id=marked] > td", 'marked[]' => 'TEXT',
+ process "tr[id=avg_marked] > td", 'avg_marked[]' => 'TEXT',
+ process "tr[id=avg_fixed] > td", 'avg_fixed[]' => 'TEXT',
+ process "tr[id=not_marked] > td", 'not_marked[]' => 'TEXT',
+ process "tr[id=closed] > td", 'closed[]' => 'TEXT',
+ process "table[id=reports] > tr > td", 'report_lists[]' => scraper {
+ process 'ul > li', 'reports[]' => 'TEXT'
+ },
+};
+
+my $expected_cats = [ 'All', '-- Pick a category --', @cats, 'Other' ];
+my $res = $categories->scrape( $mech->content );
+is_deeply( $res->{cats}, $expected_cats, 'correct list of categories' );
+
+foreach my $row ( @{ $res->{rows} }[1 .. 11] ) {
+ foreach my $col ( @{ $row->{cols} } ) {
+ is $col, 0;
+ }
+}
+
+for my $reports ( @{ $res->{report_lists} } ) {
+ is_deeply $reports, {}, 'No reports';
+}
+
+foreach my $test (
+ {
+ desc => 'confirmed today with no state',
+ dt => DateTime->now,
+ counts => [1,1,1,1],
+ report_counts => [1, 0, 0],
+ },
+ {
+ desc => 'confirmed last 7 days with no state',
+ dt => DateTime->now->subtract( days => 6, hours => 23 ),
+ counts => [1,2,2,2],
+ report_counts => [2, 0, 0],
+ },
+ {
+ desc => 'confirmed last 8 days with no state',
+ dt => DateTime->now->subtract( days => 8 ),
+ counts => [1,2,3,3],
+ report_counts => [2, 1, 0],
+ },
+ {
+ desc => 'confirmed last 4 weeks with no state',
+ dt => DateTime->now->subtract( weeks => 2 ),
+ counts => [1,2,4,4],
+ report_counts => [2, 1, 1],
+ },
+ {
+ desc => 'confirmed this year with no state',
+ dt => DateTime->now->subtract( weeks => 7 ),
+ counts => [1,2,4,5],
+ report_counts => [2, 1, 1],
+ },
+) {
+ subtest $test->{desc} => sub {
+ make_problem( { state => 'confirmed', conf_dt => $test->{dt} } );
+
+ $mech->get_ok('/dashboard');
+ $res = $categories->scrape( $mech->content );
+
+ check_row( $res, 'totals', $test->{counts} );
+ check_row( $res, 'not_marked', $test->{counts} );
+
+ check_report_counts( $res, $test->{report_counts} );
+ };
+}
+
+delete_problems();
+
+foreach my $test (
+ {
+ desc => 'user fixed today',
+ confirm_dt => DateTime->now->subtract( days => 1 ),
+ mark_dt => DateTime->now,
+ state => 'fixed - user',
+ counts => {
+ totals => [1,1,1,1],
+ user => [1,1,1,1],
+ council => [0,0,0,0],
+ avg_fixed => [0,0,0,0],
+ total_fixed => [1,1,1,1],
+ }
+ },
+ {
+ desc => 'council fixed today',
+ confirm_dt => DateTime->now->subtract( days => 1 ),
+ mark_dt => DateTime->now,
+ state => 'fixed - council',
+ counts => {
+ totals => [2,2,2,2],
+ user => [1,1,1,1],
+ council => [1,1,1,1],
+ avg_fixed => [1,1,1,1],
+ total_fixed => [2,2,2,2],
+ }
+ },
+ {
+ desc => 'marked investigating today',
+ confirm_dt => DateTime->now->subtract( days => 1 ),
+ mark_dt => DateTime->now,
+ state => 'investigating',
+ counts => {
+ totals => [3,3,3,3],
+ user => [1,1,1,1],
+ council => [1,1,1,1],
+ total_fixed => [2,2,2,2],
+ avg_marked => [1,1,1,1],
+ investigating => [1,1,1,1],
+ marked => [1,1,1,1]
+ }
+ },
+ {
+ desc => 'marked in progress today',
+ confirm_dt => DateTime->now->subtract( days => 1 ),
+ mark_dt => DateTime->now,
+ state => 'in progress',
+ counts => {
+ totals => [4,4,4,4],
+ user => [1,1,1,1],
+ council => [1,1,1,1],
+ total_fixed => [2,2,2,2],
+ avg_marked => [1,1,1,1],
+ investigating => [1,1,1,1],
+ in_progress => [1,1,1,1],
+ marked => [2,2,2,2]
+ }
+ },
+ {
+ desc => 'marked as planned today',
+ confirm_dt => DateTime->now->subtract( days => 1 ),
+ mark_dt => DateTime->now,
+ state => 'planned',
+ counts => {
+ totals => [5,5,5,5],
+ user => [1,1,1,1],
+ council => [1,1,1,1],
+ total_fixed => [2,2,2,2],
+ avg_marked => [1,1,1,1],
+ investigating => [1,1,1,1],
+ in_progress => [1,1,1,1],
+ planned => [1,1,1,1],
+ marked => [3,3,3,3]
+ }
+ },
+ {
+ desc => 'marked as planned today, confirmed a week ago',
+ confirm_dt => DateTime->now->subtract( days => 8 ),
+ mark_dt => DateTime->now,
+ state => 'planned',
+ counts => {
+ totals => [5,5,6,6],
+ user => [1,1,1,1],
+ council => [1,1,1,1],
+ total_fixed => [2,2,2,2],
+ avg_marked => [3,3,3,3],
+ investigating => [1,1,1,1],
+ in_progress => [1,1,1,1],
+ planned => [2,2,2,2],
+ marked => [4,4,4,4]
+ }
+ },
+ {
+ desc => 'marked as council fixed today, confirmed a week ago',
+ confirm_dt => DateTime->now->subtract( days => 8 ),
+ mark_dt => DateTime->now,
+ state => 'fixed - council',
+ counts => {
+ totals => [5,5,7,7],
+ user => [1,1,1,1],
+ council => [2,2,2,2],
+ total_fixed => [3,3,3,3],
+ avg_fixed => [5,5,5,5],
+ avg_marked => [3,3,3,3],
+ investigating => [1,1,1,1],
+ in_progress => [1,1,1,1],
+ planned => [2,2,2,2],
+ marked => [4,4,4,4]
+ }
+ },
+ {
+ desc => 'marked as council fixed a week ago, confirmed 3 weeks ago',
+ confirm_dt => DateTime->now->subtract( days => 21),
+ mark_dt => DateTime->now->subtract( days => 8 ),
+ state => 'fixed - council',
+ counts => {
+ totals => [5,5,8,8],
+ user => [1,1,1,1],
+ council => [2,2,3,3],
+ total_fixed => [3,3,4,4],
+ avg_fixed => [5,5,7,7],
+ avg_marked => [3,3,3,3],
+ investigating => [1,1,1,1],
+ in_progress => [1,1,1,1],
+ planned => [2,2,2,2],
+ marked => [4,4,4,4]
+ }
+ },
+ {
+ desc => 'marked as user fixed 6 weeks ago, confirmed 7 weeks ago',
+ confirm_dt => DateTime->now->subtract( weeks => 6 ),
+ mark_dt => DateTime->now->subtract( weeks => 7 ),
+ state => 'fixed - user',
+ counts => {
+ totals => [5,5,8,9],
+ user => [1,1,1,2],
+ council => [2,2,3,3],
+ total_fixed => [3,3,4,5],
+ avg_fixed => [5,5,7,7],
+ avg_marked => [3,3,3,3],
+ investigating => [1,1,1,1],
+ in_progress => [1,1,1,1],
+ planned => [2,2,2,2],
+ marked => [4,4,4,4]
+ }
+ },
+ {
+ desc => 'marked as closed',
+ confirm_dt => DateTime->now->subtract( days => 1 ),
+ mark_dt => DateTime->now,
+ state => 'closed',
+ counts => {
+ totals => [6,6,9,10],
+ user => [1,1,1,2],
+ council => [2,2,3,3],
+ total_fixed => [3,3,4,5],
+ avg_fixed => [5,5,7,7],
+ avg_marked => [2,2,2,2],
+ investigating => [1,1,1,1],
+ in_progress => [1,1,1,1],
+ planned => [2,2,2,2],
+ closed => [1,1,1,1],
+ marked => [5,5,5,5]
+ }
+ },
+) {
+ subtest $test->{desc} => sub {
+ make_problem(
+ {
+ state => $test->{state},
+ conf_dt => $test->{confirm_dt},
+ mark_dt => $test->{mark_dt},
+ }
+ );
+
+ $mech->get_ok('/dashboard');
+ $res = $categories->scrape( $mech->content );
+
+ foreach my $row ( keys %{ $test->{counts} } ) {
+ check_row( $res, $row, $test->{counts}->{$row} );
+ }
+ };
+}
+
+delete_problems();
+
+for my $test (
+ {
+ desc => 'Selecting no category does nothing',
+ p1 => {
+ state => 'confirmed',
+ conf_dt => DateTime->now(),
+ category => 'Potholes',
+ },
+ p2 => {
+ state => 'confirmed',
+ conf_dt => DateTime->now(),
+ category => 'Litter',
+ },
+ category => '',
+ counts => {
+ totals => [2,2,2,2],
+ },
+ counts_after => {
+ totals => [2,2,2,2],
+ },
+ report_counts => [2,0,0],
+ report_counts_after => [2,0,0],
+ },
+ {
+ desc => 'Limit display by category',
+ category => 'Potholes',
+ counts => {
+ totals => [2,2,2,2],
+ },
+ counts_after => {
+ totals => [1,1,1,1],
+ },
+ report_counts => [2,0,0],
+ report_counts_after => [1,0,0],
+ },
+ {
+ desc => 'Limit display for category with no entries',
+ category => 'Grafitti',
+ counts => {
+ totals => [2,2,2,2],
+ },
+ counts_after => {
+ totals => [0,0,0,0],
+ },
+ report_counts => [2,0,0],
+ report_counts_after => [0,0,0],
+ },
+ {
+ desc => 'Limit display by category for council fixed',
+ p1 => {
+ state => 'fixed - council',
+ conf_dt => DateTime->now()->subtract( weeks => 1 ),
+ mark_dt => DateTime->now()->subtract( weeks => 1 ),
+ category => 'Potholes',
+ },
+ p2 => {
+ state => 'fixed - council',
+ conf_dt => DateTime->now()->subtract( weeks => 1 ),
+ mark_dt => DateTime->now()->subtract( weeks => 1 ),
+ category => 'Litter',
+ },
+ category => 'Potholes',
+ counts => {
+ council => [0,0,2,2],
+ totals => [2,2,4,4],
+ },
+ counts_after => {
+ council => [0,0,1,1],
+ totals => [1,1,2,2],
+ },
+ report_counts => [2,2,0],
+ report_counts_after => [1,1,0],
+ },
+ {
+ desc => 'Limit display by category for user fixed',
+ p1 => {
+ state => 'fixed - user',
+ conf_dt => DateTime->now()->subtract( weeks => 1 ),
+ mark_dt => DateTime->now()->subtract( weeks => 1 ),
+ category => 'Potholes',
+ },
+ p2 => {
+ state => 'fixed - user',
+ conf_dt => DateTime->now()->subtract( weeks => 1 ),
+ mark_dt => DateTime->now()->subtract( weeks => 1 ),
+ category => 'Litter',
+ },
+ category => 'Potholes',
+ counts => {
+ user => [0,0,2,2],
+ council => [0,0,2,2],
+ totals => [2,2,6,6],
+ },
+ counts_after => {
+ user => [0,0,1,1],
+ council => [0,0,1,1],
+ totals => [1,1,3,3],
+ },
+ report_counts => [2,4,0],
+ report_counts_after => [1,2,0],
+ },
+ {
+ desc => 'Limit display by ward',
+ p1 => {
+ state => 'confirmed',
+ conf_dt => DateTime->now()->subtract( weeks => 1 ),
+ category => 'Potholes',
+ # in real life it has commas around it and the search
+ # uses them
+ areas => ',20720,',
+ },
+ p2 => {
+ state => 'fixed - council',
+ conf_dt => DateTime->now()->subtract( weeks => 1 ),
+ mark_dt => DateTime->now()->subtract( weeks => 1 ),
+ category => 'Litter',
+ areas => ',20720,',
+ },
+ ward => 20720,
+ counts => {
+ user => [0,0,2,2],
+ council => [0,0,3,3],
+ totals => [2,2,8,8],
+ },
+ counts_after => {
+ user => [0,0,0,0],
+ council => [0,0,1,1],
+ totals => [0,0,2,2],
+ },
+ report_counts => [2,6,0],
+ report_counts_after => [0,2,0],
+ },
+) {
+ subtest $test->{desc} => sub {
+ make_problem( $test->{p1} ) if $test->{p1};
+ make_problem( $test->{p2} ) if $test->{p2};
+
+ $mech->get_ok('/dashboard');
+
+ $res = $categories->scrape( $mech->content );
+
+ foreach my $row ( keys %{ $test->{counts} } ) {
+ check_row( $res, $row, $test->{counts}->{$row} );
+ }
+
+ check_report_counts( $res, $test->{report_counts} );
+
+ $mech->submit_form_ok( {
+ with_fields => {
+ category => $test->{category},
+ ward => $test->{ward},
+ }
+ } );
+
+ $res = $categories->scrape( $mech->content );
+
+ foreach my $row ( keys %{ $test->{counts_after} } ) {
+ check_row( $res, $row, $test->{counts_after}->{$row} );
+ }
+ check_report_counts( $res, $test->{report_counts_after} );
+ };
+}
+
+delete_problems();
+
+for my $test (
+ {
+ desc => 'Selecting no state does nothing',
+ p1 => {
+ state => 'fixed - user',
+ conf_dt => DateTime->now(),
+ category => 'Potholes',
+ },
+ p2 => {
+ state => 'confirmed',
+ conf_dt => DateTime->now(),
+ category => 'Litter',
+ },
+ state => '',
+ report_counts => [2,0,0],
+ report_counts_after => [2,0,0],
+ },
+ {
+ desc => 'limit by state works',
+ state => 'fixed',
+ report_counts => [2,0,0],
+ report_counts_after => [1,0,0],
+ },
+ {
+ desc => 'All fixed states count as fixed',
+ p1 => {
+ state => 'fixed - council',
+ conf_dt => DateTime->now(),
+ category => 'Potholes',
+ },
+ p2 => {
+ state => 'fixed',
+ conf_dt => DateTime->now(),
+ category => 'Potholes',
+ },
+ state => 'fixed',
+ report_counts => [4,0,0],
+ report_counts_after => [3,0,0],
+ },
+) {
+ subtest $test->{desc} => sub {
+ make_problem( $test->{p1} ) if $test->{p1};
+ make_problem( $test->{p2} ) if $test->{p2};
+
+ $mech->get_ok('/dashboard');
+
+ $res = $categories->scrape( $mech->content );
+
+ check_report_counts( $res, $test->{report_counts} );
+
+ $mech->submit_form_ok( {
+ with_fields => {
+ state => $test->{state},
+ }
+ } );
+
+ $res = $categories->scrape( $mech->content );
+
+ check_report_counts( $res, $test->{report_counts_after} );
+ };
+}
+
+sub make_problem {
+ my $args = shift;
+
+ my $p = FixMyStreet::App->model('DB::Problem')->create( {
+ title => 'a problem',
+ name => 'a user',
+ anonymous => 1,
+ detail => 'some detail',
+ state => $args->{state},
+ confirmed => $args->{conf_dt},
+ whensent => $args->{conf_dt},
+ lastupdate => $args->{mark_dt} || $args->{conf_dt},
+ council => $test_council,
+ postcode => 'EH99 1SP',
+ latitude => '51',
+ longitude => '1',
+ areas => $args->{areas} || $test_ward,
+ used_map => 0,
+ user_id => $p_user->id,
+ category => $args->{category} || 'Other',
+ } );
+
+ if ( $args->{state} ne 'confirmed' ) {
+ my $c = FixMyStreet::App->model('DB::Comment')->create( {
+ problem => $p,
+ user_id => $p_user->id,
+ state => 'confirmed',
+ problem_state => $args->{state} =~ /^fixed - user|fixed$/ ? undef : $args->{state},
+ confirmed => $args->{mark_dt},
+ text => 'an update',
+ mark_fixed => $args->{state} =~ /fixed/ ? 1 : 0,
+ anonymous => 1,
+ } );
+ }
+}
+
+sub check_row {
+ my $res = shift;
+ my $row = shift;
+ my $totals = shift;
+
+ is $res->{ $row }->[0], $totals->[0], "Correct count in $row for WTD";
+ is $res->{ $row }->[1], $totals->[1], "Correct count in $row for last 7 days";
+ is $res->{ $row }->[2], $totals->[2], "Correct count in $row for last 4 weeks";
+ is $res->{ $row }->[3], $totals->[3], "Correct count in $row for YTD";
+}
+
+sub check_report_counts {
+ my $res = shift;
+ my $counts = shift;
+
+ for my $i ( 0 .. 2 ) {
+ if ( $counts->[$i] == 0 ) {
+ is_deeply $res->{report_lists}->[$i], {}, "No reports for column $i";
+ } else {
+ if ( ref( $res->{report_lists}->[$i]->{reports} ) eq 'ARRAY' ) {
+ is scalar @{ $res->{report_lists}->[$i]->{reports} }, $counts->[$i], "Correct report count for column $i";
+ } else {
+ fail "Correct report count for column $i ( no reports )";
+ }
+ }
+ }
+}
+
+sub delete_problems {
+ FixMyStreet::App->model('DB::Comment')
+ ->search( { 'problem.council' => $test_council }, { join => 'problem' } )
+ ->delete;
+ FixMyStreet::App->model('DB::Problem')
+ ->search( { council => $test_council } )->delete();
+}
+
+done_testing;
diff --git a/t/app/controller/questionnaire.t b/t/app/controller/questionnaire.t
index 0e2a71184..2d4fdb711 100644
--- a/t/app/controller/questionnaire.t
+++ b/t/app/controller/questionnaire.t
@@ -322,67 +322,79 @@ for my $test (
};
}
-# EHA extra checking
-ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes';
-
-# Reset, and all the questionaire sending function - FIXME should it detect site itself somehow?
-$report->send_questionnaire( 1 );
-$report->update;
-$questionnaire->delete;
-FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires( {
- site => 'emptyhomes'
-} );
-$email = $mech->get_email;
-ok $email, "got an email";
-$mech->clear_emails_ok;
-
-like $email->body, qr/fill in this short questionnaire/i, "got questionnaire email";
-($token) = $email->body =~ m{http://.*?/Q/(\S+)};
-ok $token, "extracted questionnaire token '$token'";
-
-$mech->get_ok("/Q/" . $token);
-$mech->content_contains( 'should have reported what they have done' );
-
-# Test already answered the ever reported question, so not shown again
-$dt = $dt->add( weeks => 4 );
-my $questionnaire2 = FixMyStreet::App->model('DB::Questionnaire')->find_or_create(
- {
- problem_id => $report->id,
- whensent => $dt->ymd . ' ' . $dt->hms,
- ever_reported => 1,
- }
-);
-ok $questionnaire2, 'added another questionnaire';
-ok $mech->host("fixmystreet.com"), 'change host to fixmystreet';
-$mech->get_ok("/Q/" . $token);
-$mech->title_like( qr/Questionnaire/ );
-$mech->content_contains( 'Has this problem been fixed?' );
-$mech->content_lacks( 'ever reported' );
-
-# EHA extra checking
-ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes';
-$mech->get_ok("/Q/" . $token);
-$mech->content_contains( 'made a lot of progress' );
-
-$token = FixMyStreet::App->model("DB::Token")->find( { scope => 'questionnaire', token => $token } );
-ok $token, 'found token for questionnaire';
-$questionnaire = FixMyStreet::App->model('DB::Questionnaire')->find( { id => $token->data } );
-ok $questionnaire, 'found questionnaire';
-
-# I18N Unicode extra testing using FiksGataMi
-$report->send_questionnaire( 1 );
-$report->cobrand( 'fiksgatami' );
-$report->update;
-$questionnaire->delete;
-$questionnaire2->delete;
-FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires( { site => 'fixmystreet' } ); # It's either fixmystreet or emptyhomes
-$email = $mech->get_email;
-ok $email, "got an email";
-$mech->clear_emails_ok;
+SKIP: {
+ skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 18 )
+ unless FixMyStreet::Cobrand->exists('emptyhomes');
+
+ # EHA extra checking
+ ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes';
+
+ # Reset, and all the questionaire sending function - FIXME should it detect site itself somehow?
+ $report->send_questionnaire( 1 );
+ $report->update;
+ $questionnaire->delete;
+
+ FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires( {
+ site => 'emptyhomes'
+ } );
+ $email = $mech->get_email;
+ ok $email, "got an email";
+ $mech->clear_emails_ok;
+
+ like $email->body, qr/fill in this short questionnaire/i, "got questionnaire email";
+ ($token) = $email->body =~ m{http://.*?/Q/(\S+)};
+ ok $token, "extracted questionnaire token '$token'";
+
+ $mech->get_ok("/Q/" . $token);
+ $mech->content_contains( 'should have reported what they have done' );
+
+ # Test already answered the ever reported question, so not shown again
+ $dt = $dt->add( weeks => 4 );
+ my $questionnaire2 = FixMyStreet::App->model('DB::Questionnaire')->find_or_create(
+ {
+ problem_id => $report->id,
+ whensent => $dt->ymd . ' ' . $dt->hms,
+ ever_reported => 1,
+ }
+ );
+ ok $questionnaire2, 'added another questionnaire';
+ ok $mech->host("fixmystreet.com"), 'change host to fixmystreet';
+ $mech->get_ok("/Q/" . $token);
+ $mech->title_like( qr/Questionnaire/ );
+ $mech->content_contains( 'Has this problem been fixed?' );
+ $mech->content_lacks( 'ever reported' );
+
+ # EHA extra checking
+ ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes';
+ $mech->get_ok("/Q/" . $token);
+ $mech->content_contains( 'made a lot of progress' );
+
+ $token = FixMyStreet::App->model("DB::Token")->find( { scope => 'questionnaire', token => $token } );
+ ok $token, 'found token for questionnaire';
+ $questionnaire = FixMyStreet::App->model('DB::Questionnaire')->find( { id => $token->data } );
+ ok $questionnaire, 'found questionnaire';
+
+ $questionnaire2->delete;
+}
-like $email->body, qr/Testing =96 Detail/, 'email contains encoded character from user';
-like $email->body, qr/sak p=E5 FiksGataMi/, 'email contains encoded character from template';
-is $email->header('Content-Type'), 'text/plain; charset="windows-1252"', 'email is in right encoding';
+SKIP: {
+ skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 5 )
+ unless FixMyStreet::Cobrand->exists('fiksgatami');
+
+ # I18N Unicode extra testing using FiksGataMi
+ $report->send_questionnaire( 1 );
+ $report->cobrand( 'fiksgatami' );
+ $report->update;
+ $questionnaire->delete;
+ FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires( { site => 'fixmystreet' } ); # It's either fixmystreet or emptyhomes
+ $email = $mech->get_email;
+ ok $email, "got an email";
+ $mech->clear_emails_ok;
+
+ like $email->body, qr/Testing =96 Detail/, 'email contains encoded character from user';
+ like $email->body, qr/sak p=E5 FiksGataMi/, 'email contains encoded character from template';
+ is $email->header('Content-Type'), 'text/plain; charset="windows-1252"', 'email is in right encoding';
+}
$mech->delete_user('test@example.com');
done_testing();
diff --git a/t/app/controller/report_import.t b/t/app/controller/report_import.t
index 934bf0346..eb686b44e 100644
--- a/t/app/controller/report_import.t
+++ b/t/app/controller/report_import.t
@@ -266,7 +266,7 @@ subtest "Submit a correct entry (with location) to cobrand" => sub {
SKIP: {
skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 20 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{fiksgatami};
+ unless FixMyStreet::Cobrand->exists('fiksgatami');
mySociety::MaPit::configure('http://mapit.nuug.no/');
ok $mech->host("fiksgatami.no"), 'change host to fiksgatami';
diff --git a/t/app/controller/report_new.t b/t/app/controller/report_new.t
index 625c7531f..29fb650e5 100644
--- a/t/app/controller/report_new.t
+++ b/t/app/controller/report_new.t
@@ -6,10 +6,14 @@ use utf8;
use FixMyStreet::TestMech;
use Web::Scraper;
+use Path::Class;
my $mech = FixMyStreet::TestMech->new;
$mech->get_ok('/report/new');
+my $sample_file = file(__FILE__)->parent->file("sample.jpg")->stringify;
+ok -e $sample_file, "sample file $sample_file exists";
+
subtest "test that bare requests to /report/new get redirected" => sub {
$mech->get_ok('/report/new');
@@ -48,9 +52,23 @@ my $contact3 = FixMyStreet::App->model('DB::Contact')->find_or_create( {
category => 'Trees',
email => 'trees@example.com',
} );
+my $contact4 = FixMyStreet::App->model('DB::Contact')->find_or_create( {
+ %contact_params,
+ area_id => 2482, # Bromley
+ category => 'Trees',
+ email => 'trees@example.com',
+} );
+my $contact5 = FixMyStreet::App->model('DB::Contact')->find_or_create( {
+ %contact_params,
+ area_id => 2651, # Edinburgh
+ category => 'Trees',
+ email => 'trees@example.com',
+} );
ok $contact1, "created test contact 1";
ok $contact2, "created test contact 2";
ok $contact3, "created test contact 3";
+ok $contact4, "created test contact 4";
+ok $contact5, "created test contact 5";
# test that the various bit of form get filled in and errors correctly
# generated.
@@ -282,6 +300,69 @@ foreach my $test (
},
errors => [ 'Please enter a subject', 'Please enter some details', ],
},
+ {
+ msg => 'non-photo upload gives error',
+ pc => 'SW1A 1AA',
+ fields => {
+ title => 'Title',
+ detail => 'Detail',
+ photo => [ [ undef, 'bad.txt', Content => 'This is not a JPEG', Content_Type => 'text/plain' ], 1 ],
+ name => 'Bob Jones',
+ may_show_name => '1',
+ email => 'bob@example.com',
+ phone => '',
+ category => 'Street lighting',
+ password_sign_in => '',
+ password_register => '',
+ remember_me => undef,
+ },
+ changes => {
+ photo => '',
+ },
+ errors => [ "Please upload a JPEG image only" ],
+ },
+ {
+ msg => 'bad photo upload gives error',
+ pc => 'SW1A 1AA',
+ fields => {
+ title => 'Title',
+ detail => 'Detail',
+ photo => [ [ undef, 'fake.jpeg', Content => 'This is not a JPEG', Content_Type => 'image/jpeg' ], 1 ],
+ name => 'Bob Jones',
+ may_show_name => '1',
+ email => 'bob@example.com',
+ phone => '',
+ category => 'Street lighting',
+ password_sign_in => '',
+ password_register => '',
+ remember_me => undef,
+ },
+ changes => {
+ photo => '',
+ },
+ errors => [ "That image doesn't appear to have uploaded correctly (Please upload a JPEG image only ), please try again." ],
+ },
+ {
+ msg => 'photo with octet-stream gets through okay',
+ pc => 'SW1A 1AA',
+ fields => {
+ title => '',
+ detail => 'Detail',
+ photo => [ [ $sample_file, undef, Content_Type => 'application/octet-stream' ], 1 ],
+ name => 'Bob Jones',
+ may_show_name => '1',
+ email => 'bob@example.com',
+ phone => '',
+ category => 'Street lighting',
+ password_sign_in => '',
+ password_register => '',
+ remember_me => undef,
+ },
+ changes => {
+ photo => '',
+ },
+ errors => [ "Please enter a subject" ],
+ },
)
{
subtest "check form errors where $test->{msg}" => sub {
@@ -293,7 +374,7 @@ foreach my $test (
is_deeply $mech->form_errors, [], "no errors for pc '$test->{pc}'";
# click through to the report page
- $mech->follow_link_ok( { text => 'skip this step', },
+ $mech->follow_link_ok( { text_regex => qr/skip this step/i, },
"follow 'skip this step' link" );
# submit the main form
@@ -473,7 +554,7 @@ subtest "test password errors for a user who is signing in as they report" => su
"submit location" );
# click through to the report page
- $mech->follow_link_ok( { text => 'Skip this step', },
+ $mech->follow_link_ok( { text_regex => qr/skip this step/i, },
"follow 'skip this step' link" );
$mech->submit_form_ok(
@@ -520,7 +601,7 @@ subtest "test report creation for a user who is signing in as they report" => su
"submit location" );
# click through to the report page
- $mech->follow_link_ok( { text => 'Skip this step', },
+ $mech->follow_link_ok( { text_regex => qr/skip this step/i, },
"follow 'skip this step' link" );
$mech->submit_form_ok(
@@ -614,7 +695,7 @@ foreach my $test (
"submit location" );
# click through to the report page
- $mech->follow_link_ok( { text => 'Skip this step', },
+ $mech->follow_link_ok( { text_regex => qr/skip this step/i, },
"follow 'skip this step' link" );
# check that the fields are correctly prefilled
@@ -717,7 +798,7 @@ subtest "check that a lat/lon off coast leads to /around" => sub {
is $mech->uri->path, '/around', "redirected to '/around'";
is_deeply #
- $mech->page_errors,
+ $mech->form_errors,
[ 'That spot does not appear to be covered by a council. If you have'
. ' tried to report an issue past the shoreline, for example, please'
. ' specify the closest point on land.' ], #
@@ -725,8 +806,145 @@ subtest "check that a lat/lon off coast leads to /around" => sub {
};
+for my $test (
+ {
+ desc => 'user title not set if not bromley problem',
+ host => 'http://www.fixmystreet.com',
+ postcode => 'EH99 1SP',
+ fms_extra_title => '',
+ extra => undef,
+ user_title => undef,
+ },
+ {
+ desc => 'title shown for bromley problem on main site',
+ host => 'http://www.fixmystreet.com',
+ postcode => 'BR1 3UH',
+ fms_extra_title => 'MR',
+ extra => [
+ {
+ name => 'fms_extra_title',
+ value => 'MR',
+ description => 'FMS_EXTRA_TITLE',
+ },
+ ],
+ user_title => 'MR',
+ },
+ {
+ desc =>
+ 'title, first and last name shown for bromley problem on cobrand',
+ host => 'http://bromley.fixmystreet.com',
+ postcode => 'BR1 3UH',
+ first_name => 'Test',
+ last_name => 'User',
+ fms_extra_title => 'MR',
+ extra => [
+ {
+ name => 'fms_extra_title',
+ value => 'MR',
+ description => 'FMS_EXTRA_TITLE',
+ },
+ {
+ name => 'first_name',
+ value => 'Test',
+ description => 'FIRST_NAME',
+ },
+ {
+ name => 'last_name',
+ value => 'User',
+ description => 'LAST_NAME',
+ },
+ ],
+ user_title => 'MR',
+ },
+ )
+{
+ subtest $test->{desc} => sub {
+ $mech->host( $test->{host} );
+
+ $mech->log_out_ok;
+ $mech->clear_emails_ok;
+
+ $mech->get_ok('/');
+ $mech->submit_form_ok( { with_fields => { pc => $test->{postcode}, } },
+ "submit location" );
+ $mech->follow_link_ok(
+ { text_regex => qr/skip this step/i, },
+ "follow 'skip this step' link"
+ );
+
+ my $fields = $mech->visible_form_values('mapSkippedForm');
+ if ( $test->{fms_extra_title} ) {
+ ok exists( $fields->{fms_extra_title} ), 'user title field displayed';
+ } else {
+ ok !exists( $fields->{fms_extra_title} ), 'user title field not displayed';
+ }
+ if ( $test->{first_name} ) {
+ ok exists( $fields->{first_name} ), 'first name field displayed';
+ ok exists( $fields->{last_name} ), 'last name field displayed';
+ ok !exists( $fields->{name} ), 'no name field displayed';
+ }
+ else {
+ ok !exists( $fields->{first_name} ),
+ 'first name field not displayed';
+ ok !exists( $fields->{last_name} ), 'last name field not displayed';
+ ok exists( $fields->{name} ), 'name field displayed';
+ }
+
+ my $submission_fields = {
+ title => "Test Report",
+ detail => 'Test report details.',
+ photo => '',
+ email => 'firstlast@example.com',
+ may_show_name => '1',
+ phone => '07903 123 456',
+ category => 'Trees',
+ password_register => '',
+ };
+
+ $submission_fields->{fms_extra_title} = $test->{fms_extra_title}
+ if $test->{fms_extra_title};
+
+ if ( $test->{first_name} ) {
+ $submission_fields->{first_name} = $test->{first_name};
+ $submission_fields->{last_name} = $test->{last_name};
+ }
+ else {
+ $submission_fields->{name} = 'Test User';
+ }
+
+ $mech->submit_form_ok( { with_fields => $submission_fields },
+ "submit good details" );
+
+ my $email = $mech->get_email;
+ ok $email, "got an email";
+ like $email->body, qr/confirm the problem/i, "confirm the problem";
+
+ my ($url) = $email->body =~ m{(https?://\S+)};
+ ok $url, "extracted confirm url '$url'";
+
+ # confirm token in order to update the user details
+ $mech->get_ok($url);
+
+ my $user =
+ FixMyStreet::App->model('DB::User')
+ ->find( { email => 'firstlast@example.com' } );
+
+ my $report = $user->problems->first;
+ ok $report, "Found the report";
+ my $extras = $report->extra;
+ is $user->title, $test->{'user_title'}, 'user title correct';
+ is_deeply $extras, $test->{extra}, 'extra contains correct values';
+
+ $user->problems->delete;
+ $user->alerts->delete;
+ $user->delete;
+ };
+}
+
$contact1->delete;
$contact2->delete;
$contact3->delete;
+$contact4->delete;
+$contact5->delete;
done_testing();
diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t
index 9c2b0861b..0337a881b 100644
--- a/t/app/controller/report_updates.t
+++ b/t/app/controller/report_updates.t
@@ -872,7 +872,11 @@ for my $test (
is_deeply $values, $test->{initial_values}, 'initial form values';
- is $mech->extract_problem_banner->{text}, $test->{initial_banner}, 'initial banner';
+ if ( !defined( $test->{initial_banner} ) ) {
+ is $mech->extract_problem_banner->{text}, undef, 'initial banner';
+ } else {
+ like $mech->extract_problem_banner->{text}, qr/@{[ $test->{initial_banner} ]}/i, 'initial banner';
+ }
$mech->submit_form_ok(
{
@@ -883,7 +887,11 @@ for my $test (
is $mech->uri->path, "/report/" . $report_id, "redirected to report page";
- is $mech->extract_problem_banner->{text}, $test->{endstate_banner}, 'submitted banner';
+ if ( !defined( $test->{endstate_banner} ) ) {
+ is $mech->extract_problem_banner->{text}, undef, 'endstate banner';
+ } else {
+ like $mech->extract_problem_banner->{text}, qr/@{[ $test->{endstate_banner} ]}/i, 'endstate banner';
+ }
$mech->email_count_is(0);
@@ -1029,8 +1037,12 @@ foreach my $test (
is_deeply $values, $test->{initial_values}, 'initial form values';
- is $mech->extract_problem_banner->{text}, $test->{initial_banner},
- 'initial banner';
+ if ( !defined( $test->{initial_banner} ) ) {
+ is $mech->extract_problem_banner->{text}, undef, 'initial banner';
+ } else {
+ like $mech->extract_problem_banner->{text}, qr/@{[ $test->{initial_banner} ]}/i,
+ 'initial banner';
+ }
$mech->submit_form_ok( { with_fields => $test->{fields}, },
'submit update' );
diff --git a/t/app/controller/reports.t b/t/app/controller/reports.t
index 58803d778..801dbeddb 100644
--- a/t/app/controller/reports.t
+++ b/t/app/controller/reports.t
@@ -17,14 +17,14 @@ $mech->follow_link_ok( { text_regex => qr/Birmingham/ } );
SKIP: {
skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 8 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{emptyhomes};
+ unless FixMyStreet::Cobrand->exists('emptyhomes');
ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes';
$mech->get_ok('/reports');
# EHA lacks one column the others have
$mech->content_lacks('state unknown');
skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 8 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{fiksgatami};
+ unless FixMyStreet::Cobrand->exists('fiksgatami');
mySociety::MaPit::configure('http://mapit.nuug.no/');
ok $mech->host("fiksgatami.no"), 'change host to fiksgatami';
$mech->get_ok('/reports');
diff --git a/t/app/model/problem.t b/t/app/model/problem.t
index 638e89200..a7415851b 100644
--- a/t/app/model/problem.t
+++ b/t/app/model/problem.t
@@ -7,6 +7,7 @@ use Test::More;
use FixMyStreet;
use FixMyStreet::App;
+use FixMyStreet::TestMech;
use mySociety::Locale;
mySociety::Locale::gettext_domain('FixMyStreet');
@@ -16,8 +17,8 @@ my $problem_rs = FixMyStreet::App->model('DB::Problem');
my $problem = $problem_rs->new(
{
postcode => 'EH99 1SP',
- latitude => 1,
- longitude => 1,
+ latitude => '51.5016605453401',
+ longitude => '-0.142497580865087',
areas => 1,
title => '',
detail => '',
@@ -345,6 +346,131 @@ for my $test (
};
}
+my $mech = FixMyStreet::TestMech->new();
+
+FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 2651,
+ category => 'potholes',
+ email => 'test@example.org',
+ confirmed => 1,
+ deleted => 0,
+ editor => 'test',
+ whenedited => \'ms_current_timestamp()',
+ note => '',
+ }
+);
+
+FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 2226,
+ category => 'potholes',
+ email => '2226@example.org',
+ confirmed => 1,
+ deleted => 0,
+ editor => 'test',
+ whenedited => \'ms_current_timestamp()',
+ note => '',
+ }
+);
+
+FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 2326,
+ category => 'potholes',
+ email => '2326@example.org',
+ confirmed => 1,
+ deleted => 0,
+ editor => 'test',
+ whenedited => \'ms_current_timestamp()',
+ note => '',
+ }
+);
+
+foreach my $test ( {
+ desc => 'sends an email',
+ unset_whendef => 1,
+ email_count => 1,
+ email => 'system_user@example.com',
+ name => 'Andrew Smith',
+ dear => qr'Dear City of Edinburgh Council',
+ to => qr'City of Edinburgh Council',
+ council => 2651,
+ },
+ {
+ desc => 'no email sent if no unsent problems',
+ unset_whendef => 0,
+ email_count => 0,
+ email => 'system_user@example.com',
+ name => 'Andrew Smith',
+ council => 2651,
+ },
+ {
+ desc => 'email to two tier council',
+ unset_whendef => 1,
+ email_count => 1,
+ email => 'system_user@example.com',
+ name => 'Andrew Smith',
+ to => qr'Gloucestershire County Council.*Cheltenham Borough Council',
+ dear => qr'Dear Gloucestershire County Council and Cheltenham Borough',
+ council => '2226,2326',
+ multiple => 1,
+ },
+ {
+ desc => 'email to two tier council with one missing details',
+ unset_whendef => 1,
+ email_count => 1,
+ email => 'system_user@example.com',
+ name => 'Andrew Smith',
+ to => qr'Gloucestershire County Council',
+ dear => qr'Dear Gloucestershire County Council,',
+ council => '2226|2649',
+ missing => qr'problem might be the responsibility of Fife.*Council'ms,
+ },
+) {
+ subtest $test->{ desc } => sub {
+ $mech->clear_emails_ok;
+
+ FixMyStreet::App->model('DB::Problem')->search(
+ {
+ whensent => undef
+ }
+ )->update( { whensent => \'ms_current_timestamp()' } );
+
+ $problem->discard_changes;
+ $problem->update( {
+ council => $test->{ council },
+ state => 'confirmed',
+ confirmed => \'ms_current_timestamp()',
+ whensent => $test->{ unset_whendef } ? undef : \'ms_current_timestamp()',
+ category => 'potholes',
+ name => $test->{ name },
+ } );
+
+ FixMyStreet::App->model('DB::Problem')->send_reports();
+
+ $mech->email_count_is( $test->{ email_count } );
+ if ( $test->{ email_count } ) {
+ my $email = $mech->get_email;
+ like $email->header('To'), $test->{ to }, 'to line looks correct';
+ is $email->header('From'), sprintf('"%s" <%s>', $test->{ name }, $test->{ email } ), 'from line looks correct';
+ like $email->header('Subject'), qr/A Title/, 'subject line looks correct';
+ like $email->body, qr/A user of FixMyStreet/, 'email body looks a bit like a report';
+ like $email->body, qr/Subject: A Title/, 'more email body checking';
+ like $email->body, $test->{ dear }, 'Salutation looks correct';
+
+ if ( $test->{multiple} ) {
+ like $email->body, qr/This email has been sent to several councils /, 'multiple council text correct';
+ } elsif ( $test->{ missing } ) {
+ like $email->body, $test->{ missing }, 'missing council information correct';
+ }
+
+ $problem->discard_changes;
+ ok defined( $problem->whensent ), 'whensent set';
+ }
+ };
+}
+
$problem->comments->delete;
$problem->delete;
$user->delete;
diff --git a/t/app/uri_for.t b/t/app/uri_for.t
index 51a6e8a0e..eecf30e32 100644
--- a/t/app/uri_for.t
+++ b/t/app/uri_for.t
@@ -78,31 +78,36 @@ is(
'FiksGataMi url with lat not zoom'
);
-like(
- $reh_en_c->uri_for_email( '/foo' ),
- qr{^http://en.},
- 'adds en to retain language'
-);
-
-# instantiate this here otherwise sets locale to cy and breaks test
-# above
-my $reh_cy_c = FixMyStreet::App->new(
- {
- request => Catalyst::Request->new(
- {
- base => URI->new('http://cy.reportemptyhomes.com/'),
- uri => URI->new('http://cy.reportemptyhomes.com/test_namespace')
- }
- ),
- namespace => 'test_namespace',
- }
-);
-$reh_cy_c->setup_request();
-
-like(
- $reh_cy_c->uri_for_email( '/foo' ),
- qr{^http://cy.},
- 'retains language'
-);
+SKIP: {
+ skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 2 )
+ unless FixMyStreet::Cobrand->exists('emptyhomes');
+
+ like(
+ $reh_en_c->uri_for_email( '/foo' ),
+ qr{^http://en.},
+ 'adds en to retain language'
+ );
+
+ # instantiate this here otherwise sets locale to cy and breaks test
+ # above
+ my $reh_cy_c = FixMyStreet::App->new(
+ {
+ request => Catalyst::Request->new(
+ {
+ base => URI->new('http://cy.reportemptyhomes.com/'),
+ uri => URI->new('http://cy.reportemptyhomes.com/test_namespace')
+ }
+ ),
+ namespace => 'test_namespace',
+ }
+ );
+ $reh_cy_c->setup_request();
+
+ like(
+ $reh_cy_c->uri_for_email( '/foo' ),
+ qr{^http://cy.},
+ 'retains language'
+ );
+}
done_testing();
diff --git a/t/cobrand/get_council_sender.t b/t/cobrand/get_council_sender.t
new file mode 100644
index 000000000..9004a47f5
--- /dev/null
+++ b/t/cobrand/get_council_sender.t
@@ -0,0 +1,30 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+use mySociety::Locale;
+use FixMyStreet::App;
+
+use_ok 'FixMyStreet::Cobrand';
+
+mySociety::Locale::gettext_domain( 'FixMyStreet' );
+
+my $c = FixMyStreet::Cobrand::FixMyStreet->new();
+
+
+is $c->get_council_sender( '1000', { type => 'DIS' } ), 'Email', 'defaults to email';
+is $c->get_council_sender( '1000', { type => 'LBO' } ), 'London', 'returns london report it if London borough';
+
+my $conf = FixMyStreet::App->model('DB::Open311Conf')->find_or_create(
+ area_id => 1000,
+ endpoint => '',
+ send_method => 'TestMethod'
+);
+
+is $c->get_council_sender( '1000', { type => 'LBO' } ), 'TestMethod', 'uses send_method in preference to London';
+is $c->get_council_sender( '1000', { type => 'DIS' } ), 'TestMethod', 'uses send_method in preference to Email';
+
+$conf->delete;
+
+done_testing();
diff --git a/t/open311.t b/t/open311.t
index 30de330b6..814b11f82 100644
--- a/t/open311.t
+++ b/t/open311.t
@@ -5,6 +5,10 @@ use warnings;
use Test::More;
use Test::Warn;
use FixMyStreet::App;
+use CGI::Simple;
+use HTTP::Response;
+use DateTime;
+use DateTime::Format::W3CDTF;
use FindBin;
use lib "$FindBin::Bin/../perllib";
@@ -39,4 +43,451 @@ my $expected_error = qr{.*request failed: 500 Can.t connect to 192.168.50.1:80 \
warning_like {$o2->send_service_request( $p, { url => 'http://example.com/' }, 1 )} $expected_error, 'warning generated on failed call';
+my $dt = DateTime->now();
+
+my $user = FixMyStreet::App->model('DB::User')->new( {
+ name => 'Test User',
+ email => 'test@example.com',
+} );
+
+my $problem = FixMyStreet::App->model('DB::Problem')->new( {
+ id => 80,
+ external_id => 81,
+ state => 'confirmed',
+ title => 'a problem',
+ detail => 'problem detail',
+ category => 'pothole',
+ latitude => 1,
+ longitude => 2,
+ user => $user,
+} );
+
+subtest 'posting service request' => sub {
+ my $extra = {
+ url => 'http://example.com/report/1',
+ };
+
+ my $results = make_service_req( $problem, $extra, $problem->category, '<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>' );
+
+ is $results->{ res }, 248, 'got request id';
+
+ my $req = $o->test_req_used;
+
+ my $description = <<EOT;
+title: a problem
+
+detail: problem detail
+
+url: http://example.com/report/1
+
+Submitted via FixMyStreet
+EOT
+;
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+
+ is $c->param('email'), $user->email, 'correct email';
+ is $c->param('first_name'), 'Test', 'correct first name';
+ is $c->param('last_name'), 'User', 'correct last name';
+ is $c->param('lat'), 1, 'latitide correct';
+ is $c->param('long'), 2, 'longitude correct';
+ is $c->param('description'), $description, 'description correct';
+ is $c->param('service_code'), 'pothole', 'service code correct';
+};
+
+subtest 'posting service request with basic_description' => sub {
+ my $extra = {
+ url => 'http://example.com/report/1',
+ };
+
+ my $results = make_service_req(
+ $problem,
+ $extra,
+ $problem->category,
+ '<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>',
+ { basic_description => 1 },
+ );
+
+ is $results->{ res }, 248, 'got request id';
+
+ my $req = $o->test_req_used;
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+
+ is $c->param('description'), $problem->detail, 'description correct';
+};
+
+for my $test (
+ {
+ desc => 'extra values in service request',
+ extra => [
+ {
+ name => 'title',
+ value => 'A title',
+ }
+ ],
+ params => [
+ [ 'attribute[title]', 'A title', 'extra paramater used correctly' ]
+ ]
+ },
+ {
+ desc => 'first and last names in extra used correctly',
+ extra => [
+ {
+ name => 'first_name',
+ value => 'First',
+ },
+ {
+ name => 'last_name',
+ value => 'Last',
+ },
+ ],
+ params => [
+ [ 'first_name', 'First', 'first name correct' ],
+ [ 'last_name', 'Last', 'last name correct' ],
+ [ 'attribute[first_name]', undef, 'no first_name attribute param' ],
+ [ 'attribute[last_name]', 'Last', 'last_name attribute param correct' ],
+ ],
+ },
+ {
+ title => 'magic fms_extra parameters handled correctly',
+ extra => [
+ {
+ name => 'fms_extra_title',
+ value => 'Extra title',
+ }
+ ],
+ params => [
+ [
+ 'attribute[title]',
+ 'Extra title',
+ 'fms_extra extra param used correctly'
+ ]
+ ],
+ },
+ )
+{
+ subtest $test->{desc} => sub {
+ $problem->extra( $test->{extra} );
+
+ my $extra = { url => 'http://example.com/report/1', };
+
+ my $results = make_service_req( $problem, $extra, $problem->category,
+'<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>'
+ );
+ my $req = $o->test_req_used;
+ my $c = CGI::Simple->new( $results->{req}->content );
+
+ for my $param ( @{ $test->{params} } ) {
+ is $c->param( $param->[0] ), $param->[1], $param->[2];
+ }
+ };
+}
+
+my $comment = FixMyStreet::App->model('DB::Comment')->new( {
+ id => 38362,
+ user => $user,
+ problem => $problem,
+ anonymous => 0,
+ text => 'this is a comment',
+ confirmed => $dt,
+ extra => { title => 'Mr', email_alerts_requested => 0 },
+} );
+
+subtest 'basic request update post parameters' => sub {
+ my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' );
+
+ is $results->{ res }, 248, 'got update id';
+
+ my $req = $o->test_req_used;
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+
+ is $c->param('description'), 'this is a comment', 'email correct';
+ is $c->param('email'), 'test@example.com', 'email correct';
+ is $c->param('status'), 'OPEN', 'status correct';
+ is $c->param('service_request_id_ext'), 80, 'external request id correct';
+ is $c->param('service_request_id'), 81, 'request id correct';
+ is $c->param('public_anonymity_required'), 'FALSE', 'anon status correct';
+ is $c->param('updated_datetime'), DateTime::Format::W3CDTF->format_datetime($dt), 'correct date';
+ is $c->param('title'), 'Mr', 'correct title';
+ is $c->param('last_name'), 'User', 'correct first name';
+ is $c->param('first_name'), 'Test', 'correct second name';
+ is $c->param('email_alerts_requested'), 'FALSE', 'email alerts flag correct';
+ is $c->param('media_url'), undef, 'no media url';
+};
+
+subtest 'check media url set' => sub {
+ $comment->photo(1);
+ $comment->cobrand('fixmystreet');
+
+ my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' );
+
+ is $results->{ res }, 248, 'got update id';
+
+ my $req = $o->test_req_used;
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+ my $expected_path = '/c/' . $comment->id . '.full.jpeg';
+ like $c->param('media_url'), qr/$expected_path/, 'image url included';
+};
+
+foreach my $test (
+ {
+ desc => 'comment with fixed state sends status of CLOSED',
+ state => 'fixed',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'comment with fixed - user state sends status of CLOSED',
+ state => 'fixed - user',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'comment with fixed - council state sends status of CLOSED',
+ state => 'fixed - council',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'comment with closed state sends status of CLOSED',
+ state => 'closed',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'comment with investigating state sends status of OPEN',
+ state => 'investigating',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'comment with planned state sends status of OPEN',
+ state => 'planned',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'comment with in progress state sends status of OPEN',
+ state => 'in progress',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'anonymous commment sets public_anonymity_required to true',
+ state => 'confirmed',
+ anon => 1,
+ status => 'OPEN',
+ },
+) {
+ subtest $test->{desc} => sub {
+ $comment->problem->state( $test->{state} );
+ $comment->anonymous( $test->{anon} );
+
+ my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' );
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+ is $c->param('status'), $test->{status}, 'correct status';
+ is $c->param('public_anonymity_required'), $test->{anon} ? 'TRUE' : 'FALSE', 'correct anonymity';
+ };
+}
+
+
+for my $test (
+ {
+ desc => 'update name name taken from comment over user',
+ comment_name => 'First Last',
+ user_name => 'Personal Family',
+ extra => undef,
+ first_name => 'First',
+ last_name => 'Last'
+ },
+ {
+ desc => 'update name name taken from user if no comment name',
+ comment_name => '',
+ user_name => 'Personal Family',
+ extra => undef,
+ first_name => 'Personal',
+ last_name => 'Family'
+ },
+ {
+ desc => 'update name taken from extra if available',
+ comment_name => 'First Last',
+ user_name => 'Personal Family',
+ extra => { first_name => 'Forename', last_name => 'Surname' },
+ first_name => 'Forename',
+ last_name => 'Surname'
+ },
+ )
+{
+ subtest $test->{desc} => sub {
+ $comment->name( $test->{comment_name} );
+ $user->name( $test->{user_name} );
+ $comment->extra( $test->{ extra } );
+
+ my $results = make_update_req( $comment,
+'<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>'
+ );
+
+ my $c = CGI::Simple->new( $results->{req}->content );
+ is $c->param('first_name'), $test->{first_name}, 'first name correct';
+ is $c->param('last_name'), $test->{last_name}, 'last name correct';
+ };
+}
+
+for my $test (
+ {
+ desc => 'use lat long forces lat long even if map not used',
+ use_latlong => 1,
+ postcode => 'EH99 1SP',
+ used_map => 0,
+ includes_latlong => 1,
+ },
+ {
+ desc => 'no use lat long and no map sends address instead of lat long',
+ use_latlong => 0,
+ postcode => 'EH99 1SP',
+ used_map => 0,
+ includes_latlong => 0,
+ },
+ {
+ desc => 'no use lat long but used map sends lat long',
+ use_latlong => 0,
+ postcode => 'EH99 1SP',
+ used_map => 1,
+ includes_latlong => 1,
+ },
+ {
+ desc => 'no use lat long, no map and no postcode sends lat long',
+ use_latlong => 0,
+ postcode => '',
+ used_map => 0,
+ includes_latlong => 1,
+ },
+ {
+ desc => 'no use lat long, no map and no postcode sends lat long',
+ use_latlong => 0,
+ notpinpoint => 1,
+ postcode => '',
+ used_map => 0,
+ includes_latlong => 0,
+ }
+) {
+ subtest $test->{desc} => sub {
+ my $extra = { url => 'http://example.com/report/1', };
+ $problem->used_map( $test->{used_map} );
+ $problem->postcode( $test->{postcode} );
+
+ my $results = make_service_req(
+ $problem,
+ $extra,
+ $problem->category,
+ '<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>',
+ { always_send_latlong => $test->{use_latlong},
+ send_notpinpointed => $test->{notpinpoint} },
+ );
+
+ is $results->{ res }, 248, 'got request id';
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+
+ if ( $test->{notpinpoint} ) {
+ is $c->param('lat'), undef, 'no latitude';
+ is $c->param('long'), undef, 'no longitude';
+ is $c->param('address_string'), undef, 'no address';
+ is $c->param('address_id'), '#NOTPINPOINTED#', 'has not pinpointed';
+ } elsif ( $test->{includes_latlong} ) {
+ ok $c->param('lat'), 'has latitude';
+ ok $c->param('long'), 'has longitude';
+ is $c->param('address_string'), undef, 'no address';
+ } else {
+ is $c->param('lat'), undef, 'no latitude';
+ is $c->param('long'), undef, 'no latitude';
+ is $c->param('address_string'), $test->{postcode}, 'has address';
+ }
+ };
+}
+
+subtest 'No update id in reponse' => sub {
+ my $results;
+ warning_like {
+ $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id></update_id></request_update></service_request_updates>' )
+ } qr/Failed to submit comment \d+ over Open311/, 'correct error message';
+
+ is $results->{ res }, 0, 'No update_id is a failure';
+};
+
+subtest 'error reponse' => sub {
+ my $results;
+ warning_like {
+ $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><errors><error><code>400</code><description>There was an error</description</error></errors>' )
+ } qr/Failed to submit comment \d+ over Open311.*There was an error/, 'correct error messages';
+
+ is $results->{ res }, 0, 'error in response is a failure';
+};
+
done_testing();
+
+sub make_update_req {
+ my $comment = shift;
+ my $xml = shift;
+
+ return make_req(
+ {
+ object => $comment,
+ xml => $xml,
+ method => 'post_service_request_update',
+ path => 'update.xml',
+ }
+ );
+}
+
+sub make_service_req {
+ my $problem = shift;
+ my $extra = shift;
+ my $service_code = shift;
+ my $xml = shift;
+ my $open311_args = shift || {};
+
+ return make_req(
+ {
+ object => $problem,
+ xml => $xml,
+ method => 'send_service_request',
+ path => 'requests.xml',
+ method_args => [ $extra, $service_code ],
+ open311_conf => $open311_args,
+ }
+ );
+}
+
+sub make_req {
+ my $args = shift;
+
+ my $object = $args->{object};
+ my $xml = $args->{xml};
+ my $method = $args->{method};
+ my $path = $args->{path};
+ my %open311_conf = %{ $args->{open311_conf} || {} };
+ my @args = @{ $args->{method_args} || [] };
+
+ $open311_conf{'test_mode'} = 1;
+ $open311_conf{'end_point'} = 'http://localhost/o311';
+ my $o =
+ Open311->new( %open311_conf );
+
+ my $test_res = HTTP::Response->new();
+ $test_res->code(200);
+ $test_res->message('OK');
+ $test_res->content($xml);
+
+ $o->test_get_returns( { $path => $test_res } );
+
+ my $res = $o->$method($object, @args);
+
+ my $req = $o->test_req_used;
+
+ return { res => $res, req => $req };
+}
diff --git a/t/open311/getservicerequestupdates.t b/t/open311/getservicerequestupdates.t
new file mode 100644
index 000000000..7ec8d5ae0
--- /dev/null
+++ b/t/open311/getservicerequestupdates.t
@@ -0,0 +1,537 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use Test::More;
+use CGI::Simple;
+
+use FindBin;
+use lib "$FindBin::Bin/../perllib";
+use lib "$FindBin::Bin/../commonlib/perllib";
+
+use_ok( 'Open311' );
+
+use_ok( 'Open311::GetServiceRequestUpdates' );
+use DateTime;
+use FixMyStreet::App;
+
+my $user = FixMyStreet::App->model('DB::User')->find_or_create(
+ {
+ email => 'system_user@example.com'
+ }
+);
+
+my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+<service_requests_updates>
+<request_update>
+<update_id>638344</update_id>
+<service_request_id>1</service_request_id>
+<service_request_id_ext>1</service_request_id_ext>
+<status>open</status>
+<description>This is a note</description>
+UPDATED_DATETIME
+</request_update>
+</service_requests_updates>
+};
+
+
+my $dt = DateTime->now;
+
+# basic xml -> perl object tests
+for my $test (
+ {
+ desc => 'basic parsing - element missing',
+ updated_datetime => '',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note' },
+ },
+ {
+ desc => 'basic parsing - empty element',
+ updated_datetime => '<updated_datetime />',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => {} } ,
+ },
+ {
+ desc => 'basic parsing - element with no content',
+ updated_datetime => '<updated_datetime></updated_datetime>',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => {} } ,
+ },
+ {
+ desc => 'basic parsing - element with content',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => $dt } ,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ my $res = $o->get_service_request_updates;
+ is_deeply $res->[0], $test->{ res }, 'result looks correct';
+
+ };
+}
+
+my $problem_rs = FixMyStreet::App->model('DB::Problem');
+my $problem = $problem_rs->new(
+ {
+ postcode => 'EH99 1SP',
+ latitude => 1,
+ longitude => 1,
+ areas => 1,
+ title => '',
+ detail => '',
+ used_map => 1,
+ user_id => 1,
+ name => '',
+ state => 'confirmed',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ user => $user,
+ created => DateTime->now()->subtract( days => 1 ),
+ lastupdate => DateTime->now()->subtract( days => 1 ),
+ anonymous => 1,
+ external_id => time(),
+ council => 2482,
+ }
+);
+
+$problem->insert;
+
+for my $test (
+ {
+ desc => 'element with content',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'confirmed',
+ close_comment => 0,
+ mark_fixed=> 0,
+ mark_open => 0,
+ problem_state => undef,
+ end_state => 'confirmed',
+ },
+ {
+ desc => 'comment closes report',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'confirmed',
+ close_comment => 1,
+ mark_fixed=> 0,
+ mark_open => 0,
+ problem_state => 'fixed - council',
+ end_state => 'fixed - council',
+ },
+ {
+ desc => 'comment re-opens fixed report',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'fixed - user',
+ close_comment => 0,
+ mark_fixed => 0,
+ mark_open => 0,
+ problem_state => 'confirmed',
+ end_state => 'confirmed',
+ },
+ {
+ desc => 'comment re-opens closed report',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'closed',
+ close_comment => 0,
+ mark_fixed => 0,
+ mark_open => 0,
+ problem_state => 'confirmed',
+ end_state => 'confirmed',
+ },
+ {
+ desc => 'comment leaves report closed',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'closed',
+ close_comment => 1,
+ mark_fixed => 0,
+ mark_open => 0,
+ end_state => 'closed',
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/;
+ $local_requests_xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>@{[$problem->external_id]}</service_request_id>#;
+ $local_requests_xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>@{[$problem->id]}</service_request_id_ext>#;
+ $local_requests_xml =~ s#<status>\w+</status>#<status>closed</status># if $test->{close_comment};
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ $problem->comments->delete;
+ $problem->lastupdate( DateTime->now()->subtract( days => 1 ) );
+ $problem->state( $test->{start_state} );
+ $problem->update;
+
+ my $council_details = { areaid => 2482 };
+ my $update = Open311::GetServiceRequestUpdates->new( system_user => $user );
+ $update->update_comments( $o, $council_details );
+
+ is $problem->comments->count, 1, 'comment count';
+ $problem->discard_changes;
+
+ my $c = FixMyStreet::App->model('DB::Comment')->search( { external_id => $test->{external_id} } )->first;
+ ok $c, 'comment exists';
+ is $c->text, $test->{description}, 'text correct';
+ is $c->mark_fixed, $test->{mark_fixed}, 'mark_closed correct';
+ is $c->problem_state, $test->{problem_state}, 'problem_state correct';
+ is $c->mark_open, $test->{mark_open}, 'mark_open correct';
+ is $problem->state, $test->{end_state}, 'correct problem state';
+ };
+}
+
+
+foreach my $test (
+ {
+ desc => 'date for comment correct',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ external_id => 638344,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $dt = DateTime->now();
+ $dt->subtract( minutes => 10 );
+ my $local_requests_xml = $requests_xml;
+
+ my $updated = sprintf( '<updated_datetime>%s</updated_datetime>', $dt );
+ $local_requests_xml =~ s/UPDATED_DATETIME/$updated/;
+ $local_requests_xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>@{[$problem->external_id]}</service_request_id>#;
+ $local_requests_xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>@{[$problem->id]}</service_request_id_ext>#;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ $problem->comments->delete;
+
+ my $council_details = { areaid => 2482 };
+ my $update = Open311::GetServiceRequestUpdates->new( system_user => $user );
+ $update->update_comments( $o, $council_details );
+
+ my $comment = $problem->comments->first;
+ is $comment->created, $dt, 'created date set to date from XML';
+ is $comment->confirmed, $dt, 'confirmed date set to date from XML';
+ };
+}
+
+my $problem2 = $problem_rs->new(
+ {
+ postcode => 'EH99 1SP',
+ latitude => 1,
+ longitude => 1,
+ areas => 1,
+ title => '',
+ detail => '',
+ used_map => 1,
+ user_id => 1,
+ name => '',
+ state => 'confirmed',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ user => $user,
+ created => DateTime->now(),
+ lastupdate => DateTime->now(),
+ anonymous => 1,
+ external_id => $problem->external_id,
+ council => 2651,
+ }
+);
+
+$problem2->insert();
+$problem->comments->delete;
+$problem2->comments->delete;
+
+for my $test (
+ {
+ desc => 'identical external_ids on problem resolved using council',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ external_id => 638344,
+ area_id => 2651,
+ request_id => $problem2->external_id,
+ request_id_ext => $problem2->id,
+ p1_comments => 0,
+ p2_comments => 1,
+ },
+ {
+ desc => 'identical external_ids on comments resolved',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ external_id => 638344,
+ area_id => 2482,
+ request_id => $problem->external_id,
+ request_id_ext => $problem->id,
+ p1_comments => 1,
+ p2_comments => 1,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/;
+ $local_requests_xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>$test->{request_id}</service_request_id>#;
+ $local_requests_xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>$test->{request_id_ext}</service_request_id_ext>#;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+
+ my $council_details = { areaid => $test->{area_id} };
+ my $update = Open311::GetServiceRequestUpdates->new( system_user => $user );
+ $update->update_comments( $o, $council_details );
+
+ is $problem->comments->count, $test->{p1_comments}, 'comment count for first problem';
+ is $problem2->comments->count, $test->{p2_comments}, 'comment count for second problem';
+ };
+}
+
+subtest 'using start and end date' => sub {
+ my $local_requests_xml = $requests_xml;
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ my $start_dt = DateTime->now();
+ $start_dt->subtract( days => 1 );
+ my $end_dt = DateTime->now();
+
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ start_date => $start_dt,
+ );
+
+ my $res = $update->update_comments( $o );
+ is $res, 0, 'returns 0 if start but no end date';
+
+ $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ end_date => $end_dt,
+ );
+
+ $res = $update->update_comments( $o );
+ is $res, 0, 'returns 0 if end but no start date';
+
+ $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ start_date => $start_dt,
+ end_date => $end_dt,
+ );
+
+ my $council_details = { areaid => 2482 };
+ $update->update_comments( $o, $council_details );
+
+ my $start = $start_dt . '';
+ my $end = $end_dt . '';
+
+ my $uri = URI->new( $o->test_uri_used );
+ my $c = CGI::Simple->new( $uri->query );
+
+ is $c->param('start_date'), $start, 'start date used';
+ is $c->param('end_date'), $end, 'end date used';
+};
+
+subtest 'check that existing comments are not duplicated' => sub {
+ my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+ <service_requests_updates>
+ <request_update>
+ <update_id>638344</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>open</status>
+ <description>This is a note</description>
+ <updated_datetime>UPDATED_DATETIME</updated_datetime>
+ </request_update>
+ <request_update>
+ <update_id>638354</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>open</status>
+ <description>This is a different note</description>
+ <updated_datetime>UPDATED_DATETIME2</updated_datetime>
+ </request_update>
+ </service_requests_updates>
+ };
+
+ $problem->comments->delete;
+
+ my $comment = FixMyStreet::App->model('DB::Comment')->new(
+ {
+ problem => $problem,
+ external_id => 638344,
+ text => 'This is a note',
+ user => $user,
+ state => 'confirmed',
+ mark_fixed => 0,
+ anonymous => 0,
+ confirmed => $dt,
+ }
+ );
+ $comment->insert;
+
+ is $problem->comments->count, 1, 'one comment before fetching updates';
+
+ $requests_xml =~ s/UPDATED_DATETIME2/$dt/;
+ $requests_xml =~ s/UPDATED_DATETIME/@{[ $comment->confirmed ]}/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $requests_xml } );
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ );
+
+ my $council_details = { areaid => 2482 };
+ $update->update_comments( $o, $council_details );
+
+ $problem->discard_changes;
+ is $problem->comments->count, 2, 'two comments after fetching updates';
+
+ $update->update_comments( $o, $council_details );
+ $problem->discard_changes;
+ is $problem->comments->count, 2, 're-fetching updates does not add comments';
+
+ $problem->comments->delete;
+ $update->update_comments( $o, $council_details );
+ $problem->discard_changes;
+ is $problem->comments->count, 2, 'if comments are deleted then they are added';
+};
+
+foreach my $test ( {
+ desc => 'check that closed and then open comment results in correct state',
+ dt1 => $dt->subtract( hours => 1 ),
+ dt2 => $dt,
+ },
+ {
+ desc => 'check that old comments do not change problem status',
+ dt1 => $dt->subtract( hours => 2 ),
+ dt2 => $dt,
+ }
+) {
+ subtest $test->{desc} => sub {
+ my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+ <service_requests_updates>
+ <request_update>
+ <update_id>638344</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>closed</status>
+ <description>This is a note</description>
+ <updated_datetime>UPDATED_DATETIME</updated_datetime>
+ </request_update>
+ <request_update>
+ <update_id>638354</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>open</status>
+ <description>This is a different note</description>
+ <updated_datetime>UPDATED_DATETIME2</updated_datetime>
+ </request_update>
+ </service_requests_updates>
+ };
+
+ $problem->comments->delete;
+ $problem->state( 'confirmed' );
+ $problem->lastupdate( $dt->subtract( hours => 3 ) );
+ $problem->update;
+
+ $requests_xml =~ s/UPDATED_DATETIME/$test->{dt1}/;
+ $requests_xml =~ s/UPDATED_DATETIME2/$test->{dt2}/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $requests_xml } );
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ );
+
+ my $council_details = { areaid => 2482 };
+ $update->update_comments( $o, $council_details );
+
+ $problem->discard_changes;
+ is $problem->comments->count, 2, 'two comments after fetching updates';
+ is $problem->state, 'confirmed', 'correct problem status';
+ };
+}
+
+foreach my $test ( {
+ desc => 'normally alerts are not suppressed',
+ suppress_alerts => 0,
+ },
+ {
+ desc => 'alerts suppressed if suppress_alerts set',
+ suppress_alerts => 1,
+ }
+) {
+ subtest $test->{desc} => sub {
+ my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+ <service_requests_updates>
+ <request_update>
+ <update_id>638344</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>closed</status>
+ <description>This is a note</description>
+ <updated_datetime>UPDATED_DATETIME</updated_datetime>
+ </request_update>
+ </service_requests_updates>
+ };
+
+ $problem->comments->delete;
+ $problem->state( 'confirmed' );
+ $problem->lastupdate( $dt->subtract( hours => 3 ) );
+ $problem->update;
+
+ my $alert = FixMyStreet::App->model('DB::Alert')->find_or_create( {
+ alert_type => 'new_updates',
+ parameter => $problem->id,
+ confirmed => 1,
+ user_id => $problem->user->id,
+ } );
+
+ $requests_xml =~ s/UPDATED_DATETIME/$dt/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $requests_xml } );
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ suppress_alerts => $test->{suppress_alerts},
+ );
+
+ my $council_details = { areaid => 2482 };
+ $update->update_comments( $o, $council_details );
+ $problem->discard_changes;
+
+ my $alerts_sent = FixMyStreet::App->model('DB::AlertSent')->search(
+ {
+ alert_id => $alert->id,
+ parameter => $problem->comments->first->id,
+ }
+ );
+
+ if ( $test->{suppress_alerts} ) {
+ ok $alerts_sent->count(), 'alerts suppressed';
+ } else {
+ is $alerts_sent->count(), 0, 'alerts not suppressed';
+ }
+
+ $alerts_sent->delete;
+ $alert->delete;
+ }
+}
+
+$problem2->comments->delete();
+$problem->comments->delete();
+$problem2->delete;
+$problem->delete;
+$user->comments->delete;
+$user->problems->delete;
+$user->delete;
+
+done_testing();
diff --git a/t/open311/populate-service-list.t b/t/open311/populate-service-list.t
index bdb7404f9..fbd729f3a 100644
--- a/t/open311/populate-service-list.t
+++ b/t/open311/populate-service-list.t
@@ -203,6 +203,304 @@ subtest 'check conflicting contacts not changed' => sub {
is $contact_count, 4, 'correct number of contacts';
};
+subtest 'check meta data population' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 2482
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_meta_to_contact( $contact );
+
+ my $extra = [ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ } ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'meta data saved';
+};
+
+subtest 'check attribute ordering' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>colour</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Colour of bin</datatype_description>
+ <order>3</order>
+ <description>Colour of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>size</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Size of bin</datatype_description>
+ <order>2</order>
+ <description>Size of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 1
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_meta_to_contact( $contact );
+
+ my $extra = [
+ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'size',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Size of bin',
+ order => 2,
+ description => 'Size of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'colour',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Colour of bin',
+ order => 3,
+ description => 'Colour of bin'
+
+ },
+ ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'meta data re-ordered correctly';
+};
+
+subtest 'check bromely skip code' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>title</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>report_url</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 2482
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_meta_to_contact( $contact );
+
+ my $extra = [ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ } ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'only non std bromley meta data saved';
+
+ $council->area_id(1);
+
+ $processor->_current_council( $council );
+ $processor->_add_meta_to_contact( $contact );
+
+ $extra = [
+ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'title',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'report_url',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'all meta data saved for non bromley';
+};
+
sub get_standard_xml {
return qq{<?xml version="1.0" encoding="utf-8"?>
<services>
diff --git a/templates/email/bromley/questionnaire.txt b/templates/email/bromley/questionnaire.txt
new file mode 100644
index 000000000..4328479e1
--- /dev/null
+++ b/templates/email/bromley/questionnaire.txt
@@ -0,0 +1,22 @@
+Subject: Questionnaire about '<?=$values['title']?>'
+
+Hi <?=$values['name']?>,
+
+<?=$values['created']?> ago, you reported a problem. To keep the
+site up to date and relevant, please fill in a short questionnaire
+updating the status of your problem:
+
+ <?=$values['url']?>
+
+This email has been sent automatically from an unmonitored
+mailbox, please do not reply.
+
+[% INCLUDE 'signature.txt' %]
+
+
+Your problem was as follows:
+
+<?=$values['title']?>
+
+<?=$values['detail']?>
+
diff --git a/templates/email/bromley/signature.txt b/templates/email/bromley/signature.txt
new file mode 100644
index 000000000..776ba3527
--- /dev/null
+++ b/templates/email/bromley/signature.txt
@@ -0,0 +1,3 @@
+Customer Service Centre
+London Borough of Bromley
+Email: csc@bromley.gov.uk
diff --git a/templates/email/default/alert-confirm.txt b/templates/email/default/alert-confirm.txt
index fae3f2963..abf7801f5 100644
--- a/templates/email/default/alert-confirm.txt
+++ b/templates/email/default/alert-confirm.txt
@@ -1,14 +1,13 @@
-Subject: Confirm your alert on FixMyStreet
+Subject: Confirm your alert on [% c.cobrand.site_name %]
Hi,
Please click on the link below to confirm the alert you just
-asked to subscribe to on FixMyStreet:
+asked to subscribe to on [% c.cobrand.site_name %]:
[% token_url %]
If you can't click the link, please copy and paste it to the
address bar of your web browser.
-Yours,
-The FixMyStreet team
+[% INCLUDE 'signature.txt' %]
diff --git a/templates/email/default/alert-problem-area.txt b/templates/email/default/alert-problem-area.txt
index 8c41aaf5e..3696c87d4 100644
--- a/templates/email/default/alert-problem-area.txt
+++ b/templates/email/default/alert-problem-area.txt
@@ -5,8 +5,7 @@ The following new problems have been added within
<?=$values['data']?>
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
To stop receiving emails when there are new problems in
<?=$values['area_name']?>, please follow this link:
diff --git a/templates/email/default/alert-problem-council.txt b/templates/email/default/alert-problem-council.txt
index 572e057a6..5544a2043 100644
--- a/templates/email/default/alert-problem-council.txt
+++ b/templates/email/default/alert-problem-council.txt
@@ -4,8 +4,7 @@ The following new problems have been reported to <?=$values['area_name']?>:
<?=$values['data']?>
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
To stop receiving emails when there are new problems reported to
<?=$values['area_name']?>, please follow this link:
diff --git a/templates/email/default/alert-problem-nearby.txt b/templates/email/default/alert-problem-nearby.txt
index 0bf8483a7..0c88dab5a 100644
--- a/templates/email/default/alert-problem-nearby.txt
+++ b/templates/email/default/alert-problem-nearby.txt
@@ -4,8 +4,7 @@ The following nearby problems have been added:
<?=$values['data']?>
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
To stop receiving emails when there are nearby problems,
please follow this link: <?=$values['unsubscribe_url']?>
diff --git a/templates/email/default/alert-problem-ward.txt b/templates/email/default/alert-problem-ward.txt
index efcf15993..9793bcdac 100644
--- a/templates/email/default/alert-problem-ward.txt
+++ b/templates/email/default/alert-problem-ward.txt
@@ -5,8 +5,7 @@ within <?=$values['ward_name']?>:
<?=$values['data']?>
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
To stop receiving emails when there are new problems reported to
<?=$values['area_name']?> within <?=$values['ward_name']?>,
diff --git a/templates/email/default/alert-problem.txt b/templates/email/default/alert-problem.txt
index 8369b8e28..01e184e0a 100644
--- a/templates/email/default/alert-problem.txt
+++ b/templates/email/default/alert-problem.txt
@@ -4,8 +4,7 @@ The following new problems have been added:
<?=$values['data']?>
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
To stop receiving emails when there are new problems,
please follow this link: <?=$values['unsubscribe_url']?>
diff --git a/templates/email/default/alert-update.txt b/templates/email/default/alert-update.txt
index ac0ddd50a..1140de1fa 100644
--- a/templates/email/default/alert-update.txt
+++ b/templates/email/default/alert-update.txt
@@ -11,8 +11,7 @@ To view or reply to these updates, please visit the following URL:
You cannot contact anyone by replying to this email.
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
To stop receiving emails when there are new updates on this problem,
please follow this link: <?=$values['unsubscribe_url']?>
diff --git a/templates/email/default/login.txt b/templates/email/default/login.txt
index d24f1cc4f..608d99ee9 100644
--- a/templates/email/default/login.txt
+++ b/templates/email/default/login.txt
@@ -5,6 +5,5 @@ able to view your problem reports and manage them more easily.
[% c.uri_for_action( 'auth/token', token ) %]
-Yours,
-The FixMyStreet.com team
+[% INCLUDE 'signature.txt' %]
diff --git a/templates/email/default/partial.txt b/templates/email/default/partial.txt
index 279d76ea0..82bc0cf0e 100644
--- a/templates/email/default/partial.txt
+++ b/templates/email/default/partial.txt
@@ -10,5 +10,4 @@ please visit the following URL:
Thanks!
-Yours,
-The FixMyStreet team
+[% INCLUDE 'signature.txt' %]
diff --git a/templates/email/default/problem-confirm.txt b/templates/email/default/problem-confirm.txt
index 58c1c75d1..5f5bd511a 100644
--- a/templates/email/default/problem-confirm.txt
+++ b/templates/email/default/problem-confirm.txt
@@ -1,9 +1,9 @@
-Subject: Confirm your problem on FixMyStreet
+Subject: Confirm your problem on [% c.cobrand.site_name %]
Hi [% report.name %],
Please click on the link below to confirm the problem you just
-added to FixMyStreet:
+added to [% c.cobrand.site_name %]:
[% token_url %]
@@ -16,5 +16,4 @@ Your problem had the title:
And details:
[% report.detail %]
-Yours,
-The FixMyStreet team
+[% INCLUDE 'signature.txt' %]
diff --git a/templates/email/default/questionnaire.txt b/templates/email/default/questionnaire.txt
index 7ff184c41..88d4cc1b5 100644
--- a/templates/email/default/questionnaire.txt
+++ b/templates/email/default/questionnaire.txt
@@ -13,8 +13,7 @@ updating the status of your problem:
Please do not reply to this email; there is a public comment
box on the questionnaire.
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
Your problem was as follows:
diff --git a/templates/email/default/reply-autoresponse b/templates/email/default/reply-autoresponse
index 672c1f3c9..cdd7288ed 100644
--- a/templates/email/default/reply-autoresponse
+++ b/templates/email/default/reply-autoresponse
@@ -19,5 +19,4 @@ unsubscribe link at the bottom of the email.
If you have a question or comment about the site, please send your
email to team@fixmystreet.com
-Yours,
-The FixMyStreet team
+[% INCLUDE 'signature.txt' %]
diff --git a/templates/email/default/signature.txt b/templates/email/default/signature.txt
new file mode 100644
index 000000000..69ee38767
--- /dev/null
+++ b/templates/email/default/signature.txt
@@ -0,0 +1,2 @@
+Yours,
+The FixMyStreet team
diff --git a/templates/email/default/submit-brent.txt b/templates/email/default/submit-brent.txt
index 4b3e20968..7936af568 100644
--- a/templates/email/default/submit-brent.txt
+++ b/templates/email/default/submit-brent.txt
@@ -29,8 +29,7 @@ Longitude: <?=$values['longitude']?>
Replies to this email will go to the user who submitted the problem.
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
This message was sent via FixMyStreet, a project of UKCOD, registered charity
number 1076346. If there is a more appropriate email address for messages about
diff --git a/templates/email/default/submit.txt b/templates/email/default/submit.txt
index 20fa17096..d40c5f055 100644
--- a/templates/email/default/submit.txt
+++ b/templates/email/default/submit.txt
@@ -29,8 +29,7 @@ Longitude: <?=$values['longitude']?>
Replies to this email will go to the user who submitted the problem.
-Yours,
-The FixMyStreet team
+<?=$values['signature']?>
This message was sent via FixMyStreet, a project of UKCOD, registered charity
number 1076346. If there is a more appropriate email address for messages about
diff --git a/templates/email/default/update-confirm.txt b/templates/email/default/update-confirm.txt
index c7571e76c..fdcd68edb 100644
--- a/templates/email/default/update-confirm.txt
+++ b/templates/email/default/update-confirm.txt
@@ -1,4 +1,4 @@
-Subject: Confirm your update on FixMyStreet
+Subject: Confirm your update on [% c.cobrand.site_name %]
Hi [% update.name %],
@@ -14,5 +14,4 @@ Your update reads:
[% update.text %]
-Yours,
-The FixMyStreet team
+[% INCLUDE 'signature.txt' %]
diff --git a/templates/web/bromley/auth/sign_out.html b/templates/web/bromley/auth/sign_out.html
new file mode 100644
index 000000000..63749b233
--- /dev/null
+++ b/templates/web/bromley/auth/sign_out.html
@@ -0,0 +1,8 @@
+[% INCLUDE 'header.html', title = loc('Sign out'), bodyclass = 'fullwidthpage' %]
+
+<p>You have been signed out of Bromley’s street and parks problem reporting system.</p>
+
+<p>Please feel free to <a href="[% c.uri_for('/auth') %]">sign in again</a>, or
+go back to the <a href="/">main street and parks reporting page</a>.</p>
+
+[% INCLUDE 'footer.html' %]
diff --git a/templates/web/fixmystreet/report/new/form_heading.html b/templates/web/bromley/contact/address.html
index e69de29bb..e69de29bb 100644
--- a/templates/web/fixmystreet/report/new/form_heading.html
+++ b/templates/web/bromley/contact/address.html
diff --git a/templates/web/bromley/contact/submit.html b/templates/web/bromley/contact/submit.html
new file mode 100644
index 000000000..781862866
--- /dev/null
+++ b/templates/web/bromley/contact/submit.html
@@ -0,0 +1,19 @@
+[% INCLUDE 'header.html', title = loc('Contact Us') %]
+
+<h1>[% loc('Contact the team') %]</h1>
+
+[% IF success %]
+
+<p>Thank you for letting us know about this report. We will review, and if
+necessary, edit the report, or remove it from the site. You can contact us at
+<a href="mailto:[% contact_email %]">[% contact_email %]</a>.
+
+[% ELSE %]
+
+ <p>
+ [% tprintf( loc('Failed to send message. Please try again, or <a href="mailto:%s">email us</a>.'), contact_email ) %]
+ </p>
+
+[% END %]
+
+[% INCLUDE 'footer.html' %]
diff --git a/templates/web/bromley/faq/faq-en-gb.html b/templates/web/bromley/faq/faq-en-gb.html
new file mode 100755
index 000000000..ae418de37
--- /dev/null
+++ b/templates/web/bromley/faq/faq-en-gb.html
@@ -0,0 +1,111 @@
+[% INCLUDE 'header.html', title = 'Help', bodyclass = 'fullwidthpage' %]
+
+<p>These pages are for reporting things which are broken, dirty, damaged or dumped, and need fixing, cleaning or clearing, such as:</p>
+ <ul><li>abandoned vehicles
+ <li>graffiti
+ <li>flytipping and dumped rubbish
+ <li>street cleaning, such as dog fouling and street sweeping
+ <li>unlit lamp posts
+ <li>park repairs, such as broken fences or playground equipment
+ <li>potholes
+ </ul>
+
+<dl>
+ <dt>What issues can&rsquo;t be reported here?</dt>
+ <dd><p>The following problems should be reported via
+ <a href="http://www.bromley.gov.uk/report">http://www.bromley.gov.uk/report</a>:</p>
+ <ul>
+ <li>missed rubbish or recycling collections
+ <li>anti-social behaviour
+ <li>noise pollution or barking dogs
+ <li>fires and smoke/smell pollution
+ <li>proposals for speed bumps/ CCTV/ pedestrian crossings/ new road layouts/ etc.
+ <li>complaining about your neighbours
+ <li>complaining or commenting about the council
+ </ul>
+ <p>Do not use this to report urgent or emergency problems because reports are not always dealt with immediately outside of working hours.</p>
+ <p>Note, the council does not maintain responsibility for problems on private land or TFL roads (A21 and part of the A232, West Wickham to Locksbottom). TFL road enquiries should be directed to <a href="http://reportit.tfl.gov.uk/">http://reportit.tfl.gov.uk/</a>.
+
+ </dd>
+
+ <dt>How do I report a problem here?</dt>
+ <dd>After entering a postcode or location, you are shown
+a map of that area. You can view problems already reported in that area,
+or report ones of your own by clicking on the map at the location of
+the problem.</dd>
+ <dt>How are the problems solved?</dt>
+ <dd>They are reported directly to us so we can then resolve the problem.
+ You can also discuss the problem on the website with others if you wish.</dd>
+
+ <dt>Do you remove silly or illegal content?</dt>
+ <dd>Bromley Council and FixMyStreet are not responsible for the content and accuracy
+of material submitted by its users. We reserve the right to edit or remove any
+problems or updates which we consider to be inappropriate upon being informed
+by a user of the site.</dd>
+
+ <dt>Can I use FixMyStreet on my mobile?</dt>
+ <dd>Yes a special mobile friendly version is available to report problem in Bromley’s streets or parks.
+ </dd>
+
+ <dt>Can I use these pages to report problems outside of the London Borough of Bromley?</dd>
+ <dd>Yes, if you enter a postcode or address outside the borough you will be re-directed to the main FixMyStreet site that will enable you to report problems elsewhere. FixMyStreet will forward these on to the relevant council.</dd>
+
+ <dt>The site is powered by FixMyStreet, who are they?</dt>
+ <dd>FixMyStreet was built by <a href="http://www.mysociety.org/">mySociety</a>, in conjunction with the <a href="http://www.youngfoundation.org.uk/">Young Foundation</a>.
+mySociety is the project of a registered charity which has grown out of the community of
+volunteers who built sites like <a href="http://www.theyworkforyou.com/">TheyWorkForYou</a>.
+mySociety&rsquo;s primary mission is to build Internet projects which give people simple, tangible
+benefits in the civic and community aspects of their lives.
+The charity is called UK Citizens Online Democracy and is charity number 1076346. mySociety
+can be contacted by email at <a href="mailto:hello&#64;mysociety.org">hello&#64;mysociety.org</a>,
+or by post at mySociety, 483 Green Lanes, London, N13 4BS.</dd>
+
+ <dt>Why does the site use kilometres for measurements?</dt>
+ <dd>The British national
+ grid reference system, devised by Ordnance Survey (the British national
+ mapping agency) around the time of the second world war, uses eastings and
+ northings measured in metres and kilometres; the maps we use are from
+ Ordnance Survey and so this is what we use to display distances.
+ There you have it: not everything British is in miles!</dd>
+
+ <dt>Why can&rsquo;t I zoom out more on the reporting map?</dt>
+ <dd>We want to keep reports locally focused, so we restrict the ability to
+ move radically between areas. The map on <a href="/my">Your Reports</a> will let you see all
+ the reports you&rsquo;ve made, wherever they are.</dd>
+
+ <dt>I&rsquo;d like a site like this for my own location/ where&rsquo;s the "source code" to this site?</dt>
+ <dd>
+The mySociety software behind this site is open source, and available
+under the GNU Affero GPL software license. You can <a
+href="http://github.com/mysociety/fixmystreet">download the
+source code</a> and help mySociety develop it.
+You&rsquo;re welcome to use it in your own projects, although you must also
+make available the source code to any such projects.
+<a href="http://www.fiksgatami.no/">Fiksgatami</a> is an example of our code
+being used in a Norwegian version of this site.
+</dd>
+
+</dl>
+
+ <h2>Privacy Questions</h2>
+
+ <dl>
+ <dt>Who gets to see my email address?</dt>
+ <dd>
+<p>If you submit a problem, your details, and details of the problem, will be
+submitted to Bromley Council. Other than the council, only people we authorise
+to view the FixMyStreet administration interface will be able to see your email
+address and they will never use it for anything other than to help administer
+FixMyStreet.</p>
+<p>We will never give or sell your email address to anyone else, unless we are
+obliged to by law. Your name will not be published anywhere unless you let us.</p>
+</dd>
+
+ <dt>What emails will you send to me?</dt>
+ <dd>We will email you when we have received your report, and when it has
+ been investigated and actioned. We will only send you emails that
+ relate to an issue you have reported.</dd>
+
+ </dl>
+
+[% INCLUDE 'footer.html' pagefooter = 'yes' %]
diff --git a/templates/web/bromley/footer.html b/templates/web/bromley/footer.html
index 9be0741bf..d60854daa 100644
--- a/templates/web/bromley/footer.html
+++ b/templates/web/bromley/footer.html
@@ -1,23 +1,8 @@
- [% IF pagefooter %]
- <footer role="content-info">
- <div class="tablewrapper bordered">
- <div id="footer-mobileapps">
- <h4>Mobile apps</h4>
-
- <ul>
- <li><a class="m-app-iphone" href="http://itunes.apple.com/gb/app/fixmystreet/id297456545">iPhone</a></li>
- <li><a class="m-app-droid" href="https://market.android.com/details?id=com.android.fixmystreet">Android</a></li>
- <li><a class="m-app-nokia" href="http://store.ovi.com/content/107557">Nokia</a></li>
- <li><a class="m-app-iphone-streetreport" href="http://itunes.apple.com/gb/app/streetreport/id371891859">iPhone Street Report</a></li>
- </ul>
- </div>
-
- <div id="footer-help">
- </div>
- </div>
- </footer>
- [% END %]
</div><!-- .content role=main -->
+ <p id="bromley-powered-by" class="desk-only">
+ <a href="http://www.fixmystreet.com/">Powered by <img src="/cobrands/bromley/fms-logo.png" alt="FixMyStreet" style="height:20px;"></a>
+ </p>
+
</div><!-- .container -->
</div><!-- .table-cell -->
@@ -25,9 +10,8 @@
<div class="nav-wrapper-2">
<div id="main-nav" role="navigation">
<ul id="mysoc-menu">
- <li><a href="http://www.fixmystreet.com/">Powered by FixMyStreet</a></li>
+ <li><a href="http://www.fixmystreet.com/">Powered by <img src="/cobrands/bromley/fms-logo.png" alt="FixMyStreet" style="height:20px;"></a></li>
</ul>
-
<ul id="main-menu">
<li><[% IF c.req.uri.path == '/' %]span[% ELSE %]a href="/"[% END %] class="report-a-problem-btn"
>[% loc("Report a problem") %]</[% c.req.uri.path == '/' ? 'span' : 'a' %]></li>[%
@@ -44,7 +28,22 @@
</div>
</div>
-<!-- [% INCLUDE 'debug_footer.html' %] -->
</div> <!-- .wrapper -->
+
+<div id="bromley-footer" class="desk-only">
+ <p class="copy">&copy; 2012 London Borough of Bromley</p>
+
+ <a href="http://www.direct.gov.uk/"><img src="https://www.bromley.gov.uk/site/images/directgov.jpg" alt="Directgov website link - Public services all in one place" /></a>
+
+ <ul class="footer-nav">
+ <li><a href="#bromley-header">To the top</a></li>
+ <li><a href="http://www.bromley.gov.uk/terms">Disclaimer</a></li>
+ <li><a href="http://www.bromley.gov.uk/privacy" rel="nofollow">Privacy and cookies</a></li>
+ <li><a href="http://www.bromley.gov.uk/accesstoinfo">Access to information</a></li>
+ </ul>
+
+</div>
+
+ </div></div>
</body>
</html>
diff --git a/templates/web/bromley/header.html b/templates/web/bromley/header.html
index f7beabd8c..1034916d9 100644
--- a/templates/web/bromley/header.html
+++ b/templates/web/bromley/header.html
@@ -6,39 +6,76 @@
<!--[if gt IE 9]><!--><html class="no-js" lang="[% lang_code %]"><!--<![endif]-->
<head>
<meta name="viewport" content="initial-scale=1.0">
-
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="HandHeldFriendly" content="true">
<meta name="mobileoptimized" content="0">
<link rel="stylesheet" href="[% version('/cobrands/bromley/base.css') %]">
- <!-- <link rel="stylesheet" href="[% version('/cobrands/fixmystreet/layout.css') %]" media="(min-width:48em)"> -->
- <link rel="stylesheet" href="[% version('/js/fancybox/jquery.fancybox-1.3.4.css') %]">
+ <link rel="stylesheet" href="[% version('/cobrands/bromley/layout.css') %]" media="(min-width:48em)">
+ <link rel="stylesheet" href="[% version('/cobrands/bromley/bromley.css') %]">
+ [% extra_css %]
<!--[if (lt IE 9) & (!IEMobile)]>
- <!-- <link rel="stylesheet" href="[% version('/cobrands/fixmystreet/layout.css') %]"> -->
+ <link rel="stylesheet" href="[% version('/cobrands/bromley/layout.css') %]">
<![endif]-->
- <script src="[% version('/js/modernizr.custom.76759.js') %]" charset="utf-8"></script>
+ <script src="[% version('/js/modernizr.custom.js') %]" charset="utf-8"></script>
[% INCLUDE 'common_header_tags.html', js_override = '/cobrands/fixmystreet/fixmystreet.js' %]
- <script src="[% version('/js/fancybox/jquery.fancybox-1.3.4.pack.js') %]" charset="utf-8"></script>
+ [% extra_js %]
- [% IF c.req.uri.host == 'osm.fixmystreet.com' %]
- <link rel="canonical" href="http://www.fixmystreet.com[% c.req.uri.path_query %]">
- [% END %]
-
- [% INCLUDE 'tracking_code.html' %]
+ <link rel="Shortcut Icon" type="image/x-icon" href="/cobrands/bromley/favicon.ico">
</head>
<body class="[% bodyclass | html IF bodyclass %]">
+ [%# ie_wrapper is to prevent a horizontal scrollbar in IE7, it appears (from Bromley site) %]
+ <div id="bromley-wrapper"><div id="bromley-ie_wrapper">
+
+<div id="bromley-header" class="desk-only">
+ <ul class="header-nav">
+ <li><a href="http://www.bromley.gov.uk/accessibility">Accessibility</a></li>
+ <li><a class="skiplink" href="http://www.bromley.gov.uk/#user-meta" rel="nofollow">Skip to content</a></li>
+ <!-- <li><a rel="nofollow" href="http://www.bromley.gov.uk/a_to_z?previewstyle=generic/print.css">Printer friendly</a></li> -->
+ <!-- <li><a href="http://www.bromley.gov.uk/accessibility/settings">Text size: AAA</a></li> -->
+ <li><div id="google_translate_element"></div></li>
+ </ul>
+
+ <div class="logo">
+ <a href="http://www.bromley.gov.uk"><img src="/cobrands/bromley/bromley-logo.jpg" alt="London Borough of Bromley logo" width="159" height="114" style="width:159px; height:114px;" /></a>
+ </div>
+
+ <div class="sign-in">
+ [% IF c.user_exists %]
+ <p>
+ [% tprintf(loc('Hi %s'), c.user.name || c.user.email) %]
+ <a href="/auth/sign_out">[% loc('sign out') %]</a>
+ </p>
+ [% END %]
+ </div>
+
+ <div class="main-menu">
+ <ul>
+ <li class="home"><[% IF c.req.uri.path == '/' %]span[% ELSE %]a href="/"[% END %] class="report-a-problem-btn"
+ >[% "Report" %]</[% c.req.uri.path == '/' ? 'span' : 'a' %]></li>[%
+ %]<li><[% IF c.req.uri.path == '/my' OR ( c.req.uri.path == '/auth' AND c.req.params.r == 'my' ) %]span[% ELSE %]a href="/my"[% END
+ %]>[% loc("Your reports") %]</[% ( c.req.uri.path == '/my' OR ( c.req.uri.path == '/auth' AND c.req.params.r == 'my' ) ) ? 'span' : 'a' %]></li>[%
+ %]<li><[% IF c.req.uri.path == '/reports/Bromley' %]span[% ELSE %]a href="/reports/Bromley"[% END
+ %]>[% loc("All reports") %]</[% c.req.uri.path == '/reports' ? 'span' : 'a' %]></li>[%
+ %]<li><[% IF c.req.uri.path == '/alert' %]span[% ELSE %]a href="/alert[% pc ? '/list?pc=' : '' %][% pc | uri %]"[% END
+ %]>[% loc("Local alerts") %]</[% c.req.uri.path == '/alert' ? 'span' : 'a' %]></li>[%
+ %]<li class="last"><[% IF c.req.uri.path == '/faq' %]span[% ELSE %]a href="/faq"[% END
+ %]>[% loc("Help") %]</[% c.req.uri.path == '/faq' ? 'span' : 'a' %]></li>
+ </ul>
+ </div>
+</div>
+
<div class="wrapper">
<div class="table-cell">
- <header id="site-header" role="banner">
+ <div id="site-header" role="banner">
<div class="container">
<a href="/" id="site-logo">FixMyStreet</a>
<a href="#main-nav" id="nav-link">Main Navigation</a>
</div>
- </header>
+ </div>
<div id="user-meta">
[% IF c.user_exists %]
@@ -46,12 +83,14 @@
[% tprintf(loc('Hi %s'), c.user.name || c.user.email) %]
<a href="/auth/sign_out">[% loc('sign out') %]</a>
</p>
- [% ELSE %]
- <!-- <a href="/auth">[% loc('Sign in') %]</a> -->
[% END %]
</div>
+ <h1 class="main desk-only">Reporting a problem in Bromley&rsquo;s streets or parks</h1>
+
+ [% pre_container_extra %]
+
<div class="container">
<div class="content[% " $mainclass" | html IF mainclass %]" role="main">
- <!-- [% INCLUDE 'debug_header.html' %] -->
+
diff --git a/templates/web/bromley/report/display.html b/templates/web/bromley/report/display.html
new file mode 100644
index 000000000..071f9b3ca
--- /dev/null
+++ b/templates/web/bromley/report/display.html
@@ -0,0 +1,217 @@
+[%
+ PROCESS "report/photo-js.html";
+ PROCESS "maps/${map.type}.html";
+
+ problem_title = problem.title _ ' - ' _ loc('Viewing a problem');
+ INCLUDE 'header.html'
+ title = problem_title
+ rss = [ loc('Updates to this problem, FixMyStreet'), "/rss/$problem.id" ]
+ robots = 'index, nofollow'
+ bodyclass = 'mappage'
+%]
+
+[% map_html %]
+
+</div>
+
+
+[% IF banner.id %]
+ <div class="banner">
+ <p id="[% banner.id %]">[% banner.text %]</p>
+ </div>
+[% END %]
+
+[% INCLUDE 'report/_main.html' %]
+
+<div class="shadow-wrap">
+ <ul id="key-tools">
+ [% IF c.user_exists AND c.user.council == 'Bromley Council' %]
+ <li><form method="post" action="/report/delete/[% problem.id %]">
+ <input type="submit" class="abuse" value="Remove from site">
+ </form></li>
+ [% ELSE %]
+ <li><a rel="nofollow" id="key-tool-report-abuse" class="abuse" href="[% c.uri_for( '/contact', { id => problem.id } ) %]">[% loc('Report abuse') %]</a></li>
+ [% END %]
+ <li><a rel="nofollow" id="key-tool-report-updates" class="feed" href="[% c.uri_for( '/alert/subscribe', { id => problem.id } ) %]">[% loc('Get updates' ) %]</a></li>
+ <li><a class="chevron" id="key-tool-problems-nearby" href="[% c.uri_for( '/around', { lat => short_latitude, lon => short_longitude } ) %]">[% loc( 'Problems nearby' ) %]</a></li>
+ </ul>
+
+<div id="report-updates-data" class="hidden-js">
+ <form action="[% c.uri_for( '/alert/subscribe' ) %]" method="post">
+ <p><a href="[% c.uri_for( '/rss', problem.id ) %]">
+ <img src="/i/feed.png" width="16" height="16" title="[% loc('RSS feed') %]" alt="[% loc('RSS feed of updates to this problem' ) %]" border="0" style="float: right">
+ </a></p>
+ <p>[% loc('Receive email when updates are left on this problem.' ) %]</p>
+ <fieldset>
+ <label class="hidden n" for="alert_rznvy">[% loc('Your email') %]</label>
+ <div class="form-txt-submit-box">
+ <input type="email" name="rznvy" id="alert_rznvy" value="[% email | html %]" size="30" placeholder="[% loc('Your email') %]">
+ <input class="green-btn" type="submit" value="[% loc('Subscribe') %]">
+ </div>
+ <input type="hidden" name="id" value="[% problem.id %]">
+ <input type="hidden" name="type" value="updates">
+ </fieldset>
+ </form>
+</div>
+
+</div>
+
+[% INCLUDE 'report/updates.html' %]
+
+<div id="update_form">
+ <h2>[% loc( 'Provide an update') %]</h2>
+
+ [% INCLUDE 'errors.html' %]
+
+ <form method="post" action="[% c.uri_for( '/report/update' ) %]" name="updateForm" class="validate"[% IF c.cobrand.allow_photo_upload %] enctype="multipart/form-data"[% END %]>
+ <fieldset>
+ <input type="hidden" name="submit_update" value="1">
+ <input type="hidden" name="id" value="[% problem.id | html %]">
+
+ <label for="form_update">[% loc( 'Update' ) %]</label>
+ [% IF field_errors.update %]
+ <div class='form-error'>[% field_errors.update %]</div>
+ [% END %]
+ <textarea rows="7" cols="30" name="update" id="form_update" placeholder="[% loc('Please write your update here') %]" required>[% update.text | html %]</textarea>
+
+ <div class="general-sidebar-notes">
+ <p>Please note this comments box can only be used for this report.
+ <br><a href="http://www.bromley.gov.uk/report">Report a different issue</a>
+ </div>
+
+ [% IF c.user && c.user.belongs_to_council( problem.council ) %]
+ <label for="form_state">[% loc( 'State' ) %]</label>
+ <select name="state" id="form_state">
+ [% FOREACH state IN [ ['confirmed', loc('Open')], ['investigating',
+ loc('Investigating')], ['planned', loc('Planned')], ['in progress',
+ loc('In Progress')], ['closed', loc('Closed')], ['fixed', loc('Fixed')] ] %]
+ <option [% 'selected ' IF state.0 == problem.state %] value="[% state.0 %]">[% state.1 %]</option>
+ [% END %]
+ </select>
+ [% ELSE %]
+ [% IF problem.is_fixed AND c.user_exists AND c.user.id == problem.user_id %]
+
+ <input type="checkbox" name="reopen" id="form_reopen" value="1"[% ' checked' IF update.mark_open %]>
+ <label class="inline" for="form_reopen">[% loc('This problem has not been fixed') %]</label>
+
+ [% ELSIF !problem.is_fixed %]
+
+ <div class="checkbox-group">
+ <input type="checkbox" name="fixed" id="form_fixed" value="1"[% ' checked' IF update.mark_fixed %]>
+ <label class="inline" for="form_fixed">[% loc('This problem has been fixed') %]</label>
+ </div>
+
+ [% END %]
+ [% END %]
+
+ [% IF c.cobrand.allow_photo_upload %]
+ <div id="fileupload_normalUI">
+ [% IF upload_fileid %]
+ <img align="right" src="/photo/[% upload_fileid %].temp.jpeg" alt="">
+ <p>[% loc('You have already attached a photo to this update, attaching another one will replace it.') %]</p>
+ <input type="hidden" name="upload_fileid" value="[% upload_fileid %]">
+ [% END %]
+ <label for="form_photo">[% loc('Photo') %]</label>
+ [% IF field_errors.photo %]
+ <p class='form-error'>[% field_errors.photo %]</p>
+ [% END %]
+ <input type="file" name="photo" id="form_photo">
+ </div>
+ [% END %]
+
+ [% IF c.user_exists %]
+
+ [% INCLUDE name %]
+
+ <input class="final-submit green-btn" type="submit" id="update_post" value="[% loc('Post') %]">
+
+
+ [% ELSE %]
+
+ <label for="form_rznvy">[% loc('Email' ) %]</label>
+ [% IF field_errors.email %]
+ <p class='form-error'>[% field_errors.email %]</p>
+ [% END %]
+ <input type="email" name="rznvy" id="form_rznvy" value="[% update.user.email | html %]" placeholder="[% loc('Your email address' ) %]" required>
+
+ <div id="form_sign_in">
+ <p>To submit your update you now need to confirm it either by email or by using a FixMyStreet password.</p>
+
+ <div id="form_sign_in_no" class="form-box">
+ <h5>Confirm my report by email</h5>
+
+ [% INCLUDE name %]
+
+ <div class="general-sidebar-notes">
+ <p class="dark">[% loc('We never show your email') %]</p>
+ <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p>
+ </div>
+
+ <label for="password_register">[% loc('Password (optional)') %]</label>
+
+ <div class="form-txt-submit-box">
+ <input type="password" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]">
+ <input class="green-btn" type="submit" name="submit_register" id="submit_register" value="[% loc('Post') %]">
+ </div>
+ </div>
+ <div id="form_sign_in_yes" class="form-box">
+ <h5>Confirm my report with my FixMyStreet password</h5>
+
+ <label class="hidden-js n" for="password_sign_in">[% loc('Yes I have a password') %]</label>
+ [% IF field_errors.password %]
+ <p class='form-error'>[% field_errors.password %]</p>
+ [% END %]
+ <div class="form-txt-submit-box">
+ <input type="password" name="password_sign_in" id="password_sign_in" value="" placeholder="[% loc('Your password') %]">
+ <input class="green-btn" type="submit" name="submit_sign_in" id="submit_sign_in" value="[% loc('Post') %]">
+ </div>
+
+ <div class="checkbox-group">
+ <input type="checkbox" id="remember_me" name="remember_me" value='1'[% ' checked' IF remember_me %]>
+ <label class="inline n" for="remember_me">[% loc('Keep me signed in on this computer') %]</label>
+ </div>
+ </div>
+ </div>
+
+ [% END %]
+
+ <p>Your information will only be used in accordance with our <a href="/faq#privacy">privacy policy</a>.</p>
+
+ </fieldset>
+ </form>
+</div>
+
+
+
+[% INCLUDE 'footer.html' %]
+
+[% BLOCK name %]
+ [% IF problem.council == '2482' %]
+ [% extra_name_info = 1 %]
+ [% INCLUDE 'report/new/extra_name.html' %]
+ [% END %]
+
+ [% IF c.user_exists %]
+ [% names = c.user.split_name %]
+ [% END %]
+ <label for="form_first_name">[% loc('First Name') %]</label>
+ [% IF field_errors.first_name %]
+ <p class='form-error'>[% field_errors.first_name %]</p>
+ [% END %]
+ <input type="text" value="[% names.first || first_name | html %]" name="first_name" id="form_first_name" placeholder="[% loc('Your first name') %]">
+
+ <label for="form_last_name">[% loc('Last Name') %]</label>
+ [% IF field_errors.last_name %]
+ <p class='form-error'>[% field_errors.last_name %]</p>
+ [% END %]
+ <input type="text" value="[% names.last || last_name | html %]" name="last_name" id="form_last_name" placeholder="[% loc('Your last name') %]">
+
+ <div class="checkbox-group">
+ <input type="checkbox" name="may_show_name" id="form_may_show_name" value="1"[% ' checked' IF update AND NOT update.anonymous %]>
+ <label class="inline" for="form_may_show_name">[% loc('Show my name publicly') %]</label>
+ </div>
+ <div class="checkbox-group">
+ <input type="checkbox" name="add_alert" id="form_add_alert" value="1"[% ' checked' IF add_alert %]>
+ <label class="inline" for="form_add_alert">[% loc( 'Alert me to future updates' ) %]</label>
+ </div>
+[% END %]
diff --git a/templates/web/bromley/report/new/fill_in_details_form.html b/templates/web/bromley/report/new/fill_in_details_form.html
new file mode 100644
index 000000000..1ceb0ac10
--- /dev/null
+++ b/templates/web/bromley/report/new/fill_in_details_form.html
@@ -0,0 +1,228 @@
+<div id="report-a-problem-main">
+ [% IF js %]
+ <p id="councils_text">
+ [%
+ tprintf(
+ loc('All the information you provide here will be sent to <strong>%s</strong>.'),
+ loc('the local council')
+ );
+ %]
+ Details of the problem will be public, plus your name if you give us permission.
+ </p>
+ [% ELSE %]
+ [% PROCESS 'report/new/councils_text.html' %]
+ [% END %]
+
+ <div id="report-a-problem-sidebar">
+ <!--
+ The text for this section needs checking, but I can't work out which bit comes from where
+ -->
+ <div class="sidebar-tips">
+ [% IF report.used_map %]
+ [% IF partial_token %]
+ <p id="unknown">[% loc('Please note your report has <strong>not yet been sent</strong>. Choose a category and add further information below, then submit.') %]</p>
+ [% END %]
+ <p>[% loc('You have located the problem at the point marked with a green pin on the map. If this is not the correct location, simply click on the map again. ') %]</p>
+ [% ELSE %]
+ <p>
+ [% loc('Please fill in the form below with details of the problem, and describe the location as precisely as possible in the details box.') %]
+ </p>
+ [% END %]
+ </div>
+
+ </div>
+
+ [% INCLUDE 'errors.html' %]
+ <fieldset>
+ <div id="problem_form">
+
+ [% INCLUDE 'report/new/form_heading.html' %]
+
+
+ [% IF field_errors.council %]
+ <p class='form-error'>[% field_errors.council %]</p>
+ [% END %]
+
+
+
+ <label for="form_title">[% loc('Subject') %]</label>
+ [% IF field_errors.title %]
+ <p class='form-error'>[% field_errors.title %]</p>
+ [% END %]
+ <input type="text" value="[% report.title | html %]" name="title" id="form_title" placeholder="[% loc('Provide a title') %]" required>
+
+ <label for="form_detail">[% loc('Details') %]</label>
+ [% IF report.used_map %]
+ <p>
+ We won&rsquo;t be able to help unless you leave as much
+detail as you can. Please describe the exact location of the problem (e.g. on a
+wall).
+ </p>
+ [% END %]
+ [% IF field_errors.detail %]
+ <p class='form-error'>[% field_errors.detail %]</p>
+ [% END %]
+ <textarea rows="7" cols="26" name="detail" id="form_detail" placeholder="[% loc('Please fill in details of the problem.') %]" required>[% report.detail | html %]</textarea>
+
+ [% IF js %]
+ <div id="form_category_row">
+ <label for="form_category">[% loc('Category') %]</label>
+ <select name="category" id="form_category" required><option>[% loc('Loading...') %]</option></select>
+ </div>
+ [% ELSE %]
+ [% IF category_options.size %]
+ [% IF field_errors.category %]
+ <p class='form-error'>[% field_errors.category %]</p>
+ [% END %]
+
+ [% PROCESS "report/new/category.html" %]
+ [% END %]
+ [% END %]
+
+ [%- IF category_extras %]
+ [% PROCESS "report/new/category_extras.html" %]
+ [%- END %]
+
+ [% IF c.cobrand.allow_photo_upload %]
+ [% IF field_errors.photo %]
+ <p class='form-error'>[% field_errors.photo %]</p>
+ [% END %]
+
+
+ [% IF upload_fileid || report.photo %]
+ [% IF upload_fileid %]
+ <img align="right" src="/photo/[% upload_fileid %].temp.jpeg" alt="">
+ <input type="hidden" name="upload_fileid" value="[% upload_fileid %]">
+ [% END %]
+
+ <p>[% loc('You have already attached a photo to this report, attaching another one will replace it.') %]</p>
+
+ [% IF report.photo %]
+ <img align="right" src="/photo/[% report.id %].jpeg">
+ [% END %]
+ [% END %]
+
+ <label for="form_photo">[% loc('Photo') %]</label>
+ <input type="file" name="photo" id="form_photo">
+ [% END %]
+
+ [% IF c.user_exists %]
+ <div class="form-box">
+ [% INCLUDE 'report/new/extra_name.html' %]
+
+ [% names = c.user.split_name %]
+ <label for="form_first_name">[% loc('First Name') %]</label>
+ [% IF field_errors.first_name %]
+ <p class='form-error'>[% field_errors.first_name %]</p>
+ [% END %]
+ <input type="text" value="[% ( first_name || names.first ) | html %]" name="first_name" id="form_first_name" placeholder="[% loc('Your first name') %]">
+
+ <label for="form_last_name">[% loc('Last Name') %]</label>
+ [% IF field_errors.last_name %]
+ <p class='form-error'>[% field_errors.last_name %]</p>
+ [% END %]
+ <input type="text" value="[% ( last_name || names.last ) | html %]" name="last_name" id="form_last_name" placeholder="[% loc('Your last name') %]">
+
+
+ [%# if there is nothing in the name field then set check box as default on form %]
+ <div class="checkbox-group">
+ <input type="checkbox" name="may_show_name" id="form_may_show_name" value="1"[% ' checked' IF !report.anonymous %]>
+ <label class="inline" for="form_may_show_name">[% loc('Show my name publicly') %]</label>
+ </div>
+
+ <div class="general-sidebar-notes">
+ <p>[% loc('We never show your email address or phone number.') %]</p>
+ </div>
+
+ <label for="form_phone">[% loc('Phone number (optional)') %]</label>
+ <input class="" type="text" value="[% report.user.phone | html %]" name="phone" id="form_phone" placeholder="[% loc('Your phone number') %]">
+
+ <div class="form-txt-submit-box">
+ <input class="green-btn" type="submit" id="submit_register" name="submit_register" value="[% loc('Submit') %]">
+ </div>
+ </div>
+ [% ELSE %]
+
+ <label for="form_email">[% loc('Your email') %]</label>
+ [% IF field_errors.email %]
+ <p class='form-error'>[% field_errors.email %]</p>
+ [% END %]
+ <input type="email" value="[% report.user.email | html %]" name="email" id="form_email" placeholder="[% loc('Please enter your email address') %]" required>
+
+ <div id="form_sign_in">
+
+ <p>To submit your report you now need to confirm it either by email or by using a FixMyStreet password.</p>
+
+ <div id="form_sign_in_no" class="form-box">
+ <h5>Confirm my report by email</h5>
+
+ [% INCLUDE 'report/new/extra_name.html' %]
+
+ <label for="form_first_name">[% loc('First Name') %]</label>
+ [% IF field_errors.first_name %]
+ <p class='form-error'>[% field_errors.first_name %]</p>
+ [% END %]
+ <input type="text" class="form-focus-trigger" value="[% first_name | html %]" name="first_name" id="form_first_name" placeholder="[% loc('Your first name') %]">
+
+ <label for="form_last_name">[% loc('Last Name') %]</label>
+ [% IF field_errors.last_name %]
+ <p class='form-error'>[% field_errors.last_name %]</p>
+ [% END %]
+ <input type="text" class="form-focus-trigger" value="[% last_name | html %]" name="last_name" id="form_last_name" placeholder="[% loc('Your last name') %]">
+
+ [%# if there is nothing in the name field then set check box as default on form %]
+ <div class="checkbox-group">
+ <input type="checkbox" name="may_show_name" id="form_may_show_name" value="1"[% ' checked' IF !report.anonymous %]>
+ <label class="inline" for="form_may_show_name">[% loc('Show my name publicly') %]</label>
+ </div>
+
+ <div class="general-sidebar-notes form-focus-hidden">
+ <p class="dark">[% loc('We never show your email address or phone number.') %]</p>
+ <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report future problems, leave updates and manage your reports.') %]</p>
+ </div>
+
+ <label class="form-focus-hidden" for="form_phone">[% loc('Phone number (optional)') %]</label>
+ <input class="form-focus-hidden" type="text" value="[% report.user.phone | html %]" name="phone" id="form_phone" placeholder="[% loc('Your phone number') %]">
+
+ <label class="form-focus-hidden" for="password_register">[% loc('Password (optional)') %]</label>
+
+ <div class="form-txt-submit-box form-focus-hidden">
+ <input type="password" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]">
+ <input class="green-btn" type="submit" id="submit_register" name="submit_register" value="[% loc('Submit') %]">
+ </div>
+ </div>
+
+ <div id="form_sign_in_yes" class="form-box">
+
+ <h5>Confirm my report with my FixMyStreet password</h5>
+
+ <label class="hidden-js n" for="password_sign_in">[% loc('Yes I have a password') %]</label>
+ <div class="form-txt-submit-box">
+ [% IF field_errors.password %]
+ <p class='form-error'>[% field_errors.password %]</p>
+ [% END %]
+ <input type="password" name="password_sign_in" id="password_sign_in" placeholder="[% loc('Your password') %]" value="">
+ <input class="green-btn" type="submit" id="submit_sign_in" name="submit_sign_in" value="[% loc('Submit') %]">
+ </div>
+
+ <div class="checkbox-group">
+ <input type="checkbox" id="remember_me" name="remember_me" value='1'[% ' checked' IF remember_me %]>
+ <label class="n inline" for="remember_me">[% loc('Keep me signed in on this computer') %]</label>
+ </div>
+ </div>
+
+ </div>
+
+ [% END %]
+
+ <p>[% loc("We will only use your personal information in accordance with our <a href=\"/faq#privacy\">privacy policy.</a>") %]</p>
+
+ </div>
+ </fieldset>
+
+ [% IF partial_token %]
+ <input type="hidden" name="partial" value="[% partial_token.token %]">
+ [% END %]
+
+ <input type="hidden" name="submit_problem" value="1">
+</div>
diff --git a/templates/web/default/admin/council_contacts.html b/templates/web/default/admin/council_contacts.html
index da7223aa6..e35c8cda2 100644
--- a/templates/web/default/admin/council_contacts.html
+++ b/templates/web/default/admin/council_contacts.html
@@ -121,6 +121,31 @@
</p>
<p>
+ <label for="send_method">Send Method</label>:
+ <select name="send_method">
+ <option value=""> -- Select a method -- </option>
+ [% FOR method IN send_methods %]
+ <option value="[% method %]"[% ' selected' IF conf.send_method == method %]>[% method %]</option>
+ [% END %]
+ </select>
+ </p>
+
+ <p>
+ <label for="send_comments">Use Open311 comment sending extension</label>:
+ <input type="checkbox" name="send_comments"[% ' checked' IF conf.send_comments %]>
+ </p>
+
+ <p>
+ <label for"comment_user_id">User to attribute fetched comments to</label>:
+ <input type="text" name="comment_user_id" value="[% conf.comment_user_id %]">
+ </p>
+
+ <p>
+ <label for="suppress_alerts">Do not send email alerts on fetched comments to problem creator</label>:
+ <input type="checkbox" name="suppress_alerts"[% ' checked' IF conf.suppress_alerts %]>
+ </p>
+
+ <p>
<input type="hidden" name="open311_id" value="[% conf.id %]">
<input type="hidden" name="area_id" value="[% area_id %]">
<input type="hidden" name="posted" value="open311">
diff --git a/templates/web/default/admin/report_edit.html b/templates/web/default/admin/report_edit.html
index cbba1b3b0..9ef7e8248 100644
--- a/templates/web/default/admin/report_edit.html
+++ b/templates/web/default/admin/report_edit.html
@@ -37,6 +37,7 @@
<li>[% loc('Service:') %] [% problem.service %]</li>
<li>[% loc('Cobrand:') %] [% problem.cobrand %]</li>
<li>[% loc('Cobrand data:') %] [% problem.cobrand_data %]</li>
+<li>[% loc('Extra data:') %] [% problem.extra ? 'Yes' : 'No' %]</li>
<li>[% loc('Going to send questionnaire?') %] [% IF problem.send_questionnaire %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]</li>
<li><label for="flagged">[% loc('Flagged:') %]</label> <input type="checkbox" name="flagged"[% ' checked' IF problem.flagged %]></li>
diff --git a/templates/web/default/auth/sign_out.html b/templates/web/default/auth/sign_out.html
index 3d8df60e4..23d39aa2d 100644
--- a/templates/web/default/auth/sign_out.html
+++ b/templates/web/default/auth/sign_out.html
@@ -1,8 +1,8 @@
-[% INCLUDE 'header.html', title => loc('Sign out') %]
+[% INCLUDE 'header.html', title = loc('Sign out'), bodyclass = 'fullwidthpage' %]
<h1>[% loc('You have been signed out') %]</h1>
-<p>Please feel free to <a href="[% c.uri_for('/auth') %]">sign in again</a>.</p>
-
+<p>Please feel free to <a href="[% c.uri_for('/auth') %]">sign in again</a>, or
+go back to the <a href="/">FixMyStreet front page</a>.</p>
[% INCLUDE 'footer.html' %]
diff --git a/templates/web/default/common_header_tags.html b/templates/web/default/common_header_tags.html
index c87f93994..582afd422 100644
--- a/templates/web/default/common_header_tags.html
+++ b/templates/web/default/common_header_tags.html
@@ -7,7 +7,8 @@
<script src="[% version('/js/jquery.validate.min.js') %]" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" src="[% version('/js/geo.min.js') %]"></script>
-<script type="text/javascript" src="[% version(js_override || '/js/fixmystreet.js') %]"></script>
+<script type="text/javascript" src="[% version('/js/fixmystreet.js') %]"></script>
+<script type="text/javascript" src="[% version(js_override || '/js/fixmystreet-old-box.js') %]"></script>
[% map_js %]
diff --git a/templates/web/default/dashboard/index.html b/templates/web/default/dashboard/index.html
new file mode 100644
index 000000000..308042d0d
--- /dev/null
+++ b/templates/web/default/dashboard/index.html
@@ -0,0 +1,152 @@
+[%
+ INCLUDE 'header.html'
+ title = loc('Dashboard')
+ robots = 'noindex, nofollow'
+ bodyclass = 'fullwidthpage'
+%]
+
+<style>
+ th[scope=row] { text-align: left; }
+ tr.subtotal { background-color: #eee; }
+ #overview tr:nth-child(2) { background-color: #fee; }
+ select { width: auto; }
+</style>
+
+<form>
+
+<p>Ward: <select name="ward"><option value=''>All</option>
+ [% FOR w IN children.values.sort('name') %]
+ <option value="[% w.id %]"[% ' selected' IF w.id == ward %]>[% w.name %]</option>
+ [% END %]
+</select>
+
+<p>Report category: <select name="category"><option value=''>All</option>
+ [% FOR cat_op IN category_options %]
+ <option value='[% cat_op | html %]'[% ' selected' IF category == cat_op %]>[% cat_op | html %]</option>
+ [% END %]
+ </select>
+
+<p><input type="submit" value="Look up">
+
+<h2>Summary Statistics for [% council.name %]</h2>
+
+<table width="100%" id="overview">
+ <tr>
+ <td>&nbsp;</td>
+ <th scope="col">WTD</th>
+ <th scope="col">Last 7 days</th>
+ <th scope="col">Last 4 weeks</th>
+ <th scope="col">YTD</th>
+ </tr>
+
+ [%
+ rows = {
+ '0' => [ "total", "Total reports received" ]
+ '1' => [ "fixed - council", "Council has marked as fixed" ]
+ '2' => [ "fixed_user", "User has marked as fixed" ]
+ };
+ FOR row IN rows %]
+ <tr id="[% row.value.0.replace('[^\w]+', '_' ) %]">
+ <th scope="row">[% row.value.1 %]</th>
+ <td>[% problems.wtd.${row.value.0} %]</td>
+ <td>[% problems.week.${row.value.0} %]</td>
+ <td>[% problems.weeks.${row.value.0} %]</td>
+ <td>[% problems.ytd.${row.value.0} %]</td>
+ </tr>
+ [% END %]
+
+ <tr class='subtotal' id="total_fixed">
+ <th scope="row">Total marked as fixed</th>
+ <td>[% problems.wtd.${"fixed - council"} + problems.wtd.fixed_user %]</td>
+ <td>[% problems.week.${"fixed - council"} + problems.week.fixed_user %]</td>
+ <td>[% problems.weeks.${"fixed - council"} + problems.weeks.fixed_user %]</td>
+ <td>[% problems.ytd.${"fixed - council"} + problems.ytd.fixed_user %]</td>
+ </tr>
+
+ [%
+ rows = {
+ '0' => [ "in progress", "Council has marked as in progress" ]
+ '1' => [ "planned", "Council has marked as planned" ]
+ '2' => [ "investigating", "Council has marked as investigating" ]
+ '3' => [ "closed", "Council has marked as closed" ]
+ };
+ wtd = 0, week = 0, weeks = 0, ytd = 0;
+ FOR row IN rows %]
+ <tr id="[% row.value.0.replace('[^\w]+', '_' ) %]">
+ <th scope="row">[% row.value.1 %]</th>
+ <td>[% problems.wtd.${row.value.0} %]</td>
+ <td>[% problems.week.${row.value.0} %]</td>
+ <td>[% problems.weeks.${row.value.0} %]</td>
+ <td>[% problems.ytd.${row.value.0} %]</td>
+ </tr>
+ [% END %]
+
+ <tr class='subtotal' id="marked">
+ <th scope="row">Total marked</th>
+ <td>[% problems.wtd.${"in progress"} + problems.wtd.planned + problems.wtd.investigating + problems.wtd.closed %]</td>
+ <td>[% problems.week.${"in progress"} + problems.week.planned + problems.week.investigating + problems.wtd.closed %]</td>
+ <td>[% problems.weeks.${"in progress"} + problems.weeks.planned + problems.weeks.investigating + problems.wtd.closed %]</td>
+ <td>[% problems.ytd.${"in progress"} + problems.ytd.planned + problems.ytd.investigating + problems.wtd.closed %]</td>
+ </tr>
+
+ <tr id="avg_fixed">
+ <th scope="row">Average time to council marking fixed (days)</th>
+ <td>[% problems.wtd.time_to_fix %]</td>
+ <td>[% problems.week.time_to_fix %]</td>
+ <td>[% problems.weeks.time_to_fix %]</td>
+ <td>[% problems.ytd.time_to_fix %]</td>
+ </tr>
+
+ <tr id="avg_marked">
+ <th scope="row">Average time to first council state change (days)</th>
+ <td>[% problems.wtd.time_to_mark %]</td>
+ <td>[% problems.week.time_to_mark %]</td>
+ <td>[% problems.weeks.time_to_mark %]</td>
+ <td>[% problems.ytd.time_to_mark %]</td>
+ </tr>
+
+ <tr class='subtotal' id="not_marked">
+ <th scope="row">Total not marked</th>
+ <td>[% problems.wtd.not_marked %]</td>
+ <td>[% problems.week.not_marked %]</td>
+ <td>[% problems.weeks.not_marked %]</td>
+ <td>[% problems.ytd.not_marked %]</td>
+ </tr>
+
+</table>
+
+<h2>Reports</h2>
+
+ </select>
+<p>Report state: <select name="state">
+<option value=''>All</option>
+ [% FOREACH state IN [ ['confirmed', loc('Open')], ['investigating',
+ loc('Investigating')], ['planned', loc('Planned')], ['in progress',
+ loc('In Progress')], ['closed', loc('Closed')], ['fixed', loc('Fixed')] ] %]
+ <option [% 'selected ' IF state.0 == q_state %] value="[% state.0 %]">[% state.1 %]</option>
+ [% END %]
+</select>
+<input type="submit" value="Look up">
+
+<table width="100%" id="reports">
+ <tr>
+ <th scope="col">Less than 7 days old</th>
+ <th scope="col">7-14 days old</th>
+ <th scope="col">14-30 days old</th>
+ </tr>
+ <tr>
+ <td width="34%"><ul>[% INCLUDE list, list = lists.1 %]</ul></td>
+ <td width="33%"><ul>[% INCLUDE list, list = lists.2 %]</ul></td>
+ <td width="33%"><ul>[% INCLUDE list, list = lists.3 %]</ul></td>
+ </tr>
+</table>
+
+</form>
+
+[% INCLUDE 'footer.html' %]
+
+[% BLOCK list %]
+[% FOR p IN list %]
+<li><a href="/report/[% p.id %]">[% p.title | html %]</a></li>
+[% END %]
+[% END %]
diff --git a/templates/web/default/faq/faq-en-gb.html b/templates/web/default/faq/faq-en-gb.html
index e85ffe853..4302add0d 100755
--- a/templates/web/default/faq/faq-en-gb.html
+++ b/templates/web/default/faq/faq-en-gb.html
@@ -61,29 +61,27 @@ by a registered charity, though, so if you want to make a contribution, <a
href="https://secure.mysociety.org/donate/">please do</a>.</dd>
<dt>Can I use FixMyStreet on my mobile?</dt>
- <dd><ul>
- <li><em>iPhone:</em> There are two apps for FixMyStreet, one written by us
- in 2008 and another much more recently by a volunteer, Martin Stephenson.
- Both are available for download on the App Store:
+ <dd>
+ <p>The FixMyStreet website should work on your mobile phone, adapting to
+ the size of your screen automatically. We plan to release updated native
+ apps in the near future.
+ <ul>
+ <li><em>iPhone:</em> Our basic app from 2008 is available for download
+ on the App Store:
<a href="http://itunes.apple.com/gb/app/fixmystreet/id297456545">FixMyStreet</a>,
- <a href="http://itunes.apple.com/gb/app/streetreport/id371891859">StreetReport</a>.
<li><em>Android:</em> A volunteer, Anna Powell-Smith, has written an app
available from the
<a href="https://market.android.com/details?id=com.android.fixmystreet">Android Market</a>.
<li><em>Nokia:</em> A volunteer, Thomas Forth, has written an app available from the
<a href="http://store.ovi.com/content/107557">Ovi Store</a>.
</ul>
- <p>We also hope to make the website itself much more mobile friendly in the future.</p>
</dd>
<dt>Why do you only cover the countries of Great Britain?</dt>
- <dd>We would love to cover Northern Ireland, but as we were funded for
- FixMyStreet by the Department for Constitutional Affairs (now the Ministry
- of Justice), we were covered for Ordnance Survey data (but not OSNI data)
- by the Pan-Governmental Agreement. The cost for these maps would be
- prohibitively expensive for the small charity that we are &ndash; if you know of
- any way we could get access to the Ordnance Survey for Northern Ireland's
- maps so that we can add them to the site, that'd be great.</dd>
+ <dd>We would love to cover Northern Ireland, but we have only been able
+ to locate boundaries for Great Britain (from Ordnance Survey). If you
+ know of a source for Northern Ireland council boundaries
+ so that we can add them to the site, that'd be great.</dd>
</dl>
<h2>Practical Questions</h2>
@@ -93,7 +91,8 @@ href="https://secure.mysociety.org/donate/">please do</a>.</dd>
to find out where reports go at the moment. Also <a href="/contact">contact us</a>
to update the address or addresses we use.</dd>
<dt>I&rsquo;m from a council, can we have FixMyStreet on our website?</dt>
- <dd>Yes you can! We offer branded, hosted versions of FixMyStreet for local council websites. <a href="/for-councils">Full details</a>.</dd>
+ <dd>Yes you can! We offer branded, hosted versions of FixMyStreet for local council websites.
+ <a href="http://www.fixmystreet.com/for-councils">Full details</a>.</dd>
<dt>Do you remove silly or illegal content?</dt>
<dd>FixMyStreet is not responsible for the content and accuracy
of material submitted by its users. We reserve the right to edit or remove any
@@ -141,7 +140,8 @@ send you emails in relation to your problem.</dd>
<h2>Organisation Questions</h2>
<dl>
<dt>Who built FixMyStreet?</dt>
- <dd>This site was built by <a href="http://www.mysociety.org/">mySociety</a>, in conjunction with the <a href="http://www.youngfoundation.org.uk/">Young Foundation</a>.
+ <dd>This site was built by <a href="http://www.mysociety.org/">mySociety</a>,
+ in conjunction with the <a href="http://www.youngfoundation.org.uk/">Young Foundation</a>.
mySociety is the project of a registered charity which has grown out of the community of
volunteers who built sites like <a href="http://www.theyworkforyou.com/">TheyWorkForYou.com</a>.
mySociety&rsquo;s primary mission is to build Internet projects which give people simple, tangible
@@ -149,15 +149,11 @@ benefits in the civic and community aspects of their lives. Our first project
was <a href="http://www.writetothem.com/">WriteToThem</a>, where you can write to any of your
elected representatives, for free. The charity is called UK Citizens Online Democracy and is charity number 1076346. mySociety
can be contacted by email at <a href="mailto:hello&#64;mysociety.org">hello&#64;mysociety.org</a>,
-or by post at:<br>
-mySociety<br>
-483 Green Lanes<br>
-London<br>
-N13 4BS<br>
-UK</dd>
+or by post at mySociety, 483 Green Lanes, London, N13 4BS, UK.</dd>
<dt><img src="/i/moj.png" align="right" alt="Ministry of Justice" hspace="10">Who pays for it?</dt>
- <dd>FixMyStreet was paid for via the Department for
-Constitutional Affairs Innovations Fund.</dd>
+ <dd>FixMyStreet was originally paid for via the Department for
+ Constitutional Affairs Innovations Fund. It is now funded by a variety of means, from commercial
+ work to <a href="http://www.mysociety.org/donate/">donations</a>.</dd>
<dt><a name="nfi"></a>Wasn&rsquo;t this site called Neighbourhood Fix-It?</dt>
<dd>Yes, we changed the name mid June 2007. We decided
Neighbourhood Fix-It was a bit of a mouthful, hard to spell, and hard to publicise (does the URL have a dash in it or not?). The domain FixMyStreet became available, and everyone liked the name.</dd>
diff --git a/templates/web/default/index.html b/templates/web/default/index.html
index e5f98b0c5..4d178d192 100644
--- a/templates/web/default/index.html
+++ b/templates/web/default/index.html
@@ -51,7 +51,7 @@
</div>
[%
- recent_photos = c.cobrand.recent_photos(3);
+ recent_photos = c.cobrand.recent_photos('front', 3);
probs = c.cobrand.recent();
%]
diff --git a/templates/web/default/js/validation_strings.html b/templates/web/default/js/validation_strings.html
index 718d10d56..3148d1993 100644
--- a/templates/web/default/js/validation_strings.html
+++ b/templates/web/default/js/validation_strings.html
@@ -14,5 +14,8 @@
email: {
required: '[% loc('Please enter your email') | replace("'", "\\'") %]',
email: '[% loc('Please enter a valid email') | replace("'", "\\'") %]'
- }
+ },
+ fms_extra_title: '[% loc('Please enter your title') | replace("'", "\\'") %]',
+ first_name: '[% loc('Please enter your first name') | replace("'", "\\'") %]',
+ last_name: '[% loc('Please enter your second name') | replace("'", "\\'") %]'
};
diff --git a/templates/web/default/report/new/councils_text_all.html b/templates/web/default/report/new/councils_text_all.html
index 8514e0b0a..df3388bf3 100644
--- a/templates/web/default/report/new/councils_text_all.html
+++ b/templates/web/default/report/new/councils_text_all.html
@@ -1,5 +1,5 @@
<p>
-[% IF all_councils.${area_ids_to_list.0}.type == 'LBO' %]
+[% IF area_ids_to_list.0 != 2489 && area_ids_to_list.0 != 2482 && all_councils.${area_ids_to_list.0}.type == 'LBO' %]
[%
tprintf(
loc('All the information you provide here will be sent to <strong>%s</strong> or a relevant local body such as <strong>TfL</strong>, via the London Report-It system.'),
diff --git a/templates/web/default/report/new/extra_name.html b/templates/web/default/report/new/extra_name.html
new file mode 100644
index 000000000..e8c2cadaf
--- /dev/null
+++ b/templates/web/default/report/new/extra_name.html
@@ -0,0 +1,18 @@
+[% IF extra_name_info %]
+<label for="form_fms_extra_title">Title</label>
+[% IF field_errors.fms_extra_title %]
+ <p class='form-error'>[% field_errors.fms_extra_title %]</p>
+[% END %]
+[% title = '' %]
+[% IF fms_extra_title %][% title = fms_extra_title | upper %]
+[% ELSIF c.user && c.user.title %][% title = c.user.title | upper %][% END %]
+<select class="form-focus-trigger" id="form_fms_extra_title"
+ name="fms_extra_title">
+ <option></option>
+ <option value="MR"[% ' selected' IF title == 'MR' %]>Mr</option>
+ <option value="MISS"[% ' selected' IF title == 'MISS' %]>Miss</option>
+ <option value="MRS"[% ' selected' IF title == 'MRS' %]>Mrs</option>
+ <option value="MS"[% ' selected' IF title == 'MS' %]>Ms</option>
+ <option value="DR"[% ' selected' IF title == 'DR' %]>Dr</option>
+</select>
+[% END %]
diff --git a/templates/web/default/report/updates.html b/templates/web/default/report/updates.html
index 374a7c570..2a65a3e3e 100644
--- a/templates/web/default/report/updates.html
+++ b/templates/web/default/report/updates.html
@@ -12,9 +12,13 @@
[% IF update.anonymous || update.name == '' %]
[% tprintf( loc( 'Posted anonymously at %s' ), prettify_epoch( update.confirmed_local.epoch ) ) -%]
- [%- ELSIF update.user.from_council %]
- [% user_name = update.user.name | html %]
- [% tprintf( loc( 'Posted by %s (<strong>%s</strong>) at %s' ), user_name, update.user.council, prettify_epoch( update.confirmed_local.epoch ) ) -%]
+ [%- ELSIF update.user.from_council;
+ user_name = update.user.name | html;
+ council = update.user.council;
+ IF council == 'Bromley Council';
+ council = "$council <img src='/cobrands/bromley/favicon.png' alt=''>";
+ END %]
+ [% tprintf( loc( 'Posted by %s (<strong>%s</strong>) at %s' ), user_name, council, prettify_epoch( update.confirmed_local.epoch ) ) -%]
[%- ELSE %]
[% tprintf( loc( 'Posted by %s at %s' ), update.name, prettify_epoch( update.confirmed_local.epoch ) ) | html -%]
[%- END -%]
diff --git a/templates/web/default/reports/council.html b/templates/web/default/reports/council.html
index 5fec0b00b..75c379bd6 100755
--- a/templates/web/default/reports/council.html
+++ b/templates/web/default/reports/council.html
@@ -1,3 +1,9 @@
+[% IF c.cobrand.moniker == 'fixmystreet' OR c.cobrand.moniker == 'bromley';
+ style = 'new';
+ ELSE;
+ style = 'old';
+ END;
+%]
[% IF ward %]
[% name = "$ward.name, $council.name"
thing = loc('ward')
@@ -75,7 +81,17 @@ Its area is now covered by <a href="/reports/Bedford">Bedford Borough Council</a
[% ELSIF c.cobrand.all_reports_style == 'detailed' %]
<div class="shadow-wrap">
<ul id="key-tools"[% IF NOT children.size %] class="singleton"[% END %]>
- <li><a rel="nofollow" id="key-tool-updates-area" class="feed" href="[% rss_url %]">[% IF c.cobrand.is_council %][% tprintf(loc('Get updates of %s problems'), thing) %][% ELSE %][% tprintf(loc('Get updates of problems in this %s'), thing) %][% END %]</a></li>
+ <li><a rel="nofollow" id="key-tool-updates-area" class="feed" href="[% rss_url %]">[%
+ IF c.cobrand.moniker == 'bromley' AND thing == 'council';
+ 'Get updates of reports in Bromley';
+ ELSIF c.cobrand.moniker == 'bromley';
+ 'Get updates of reports in this ward';
+ ELSIF c.cobrand.is_council;
+ tprintf(loc('Get updates of %s problems'), thing);
+ ELSE;
+ tprintf(loc('Get updates of problems in this %s'), thing);
+ END
+ %]</a></li>
[% IF children.size %]
<li><a href="#council_wards" id="key-tool-wards" class="chevron">[% ward_text %]</a></li>
[% END %]
diff --git a/templates/web/default/tokens/confirm_problem.html b/templates/web/default/tokens/confirm_problem.html
index 756958380..1e3c6c535 100644
--- a/templates/web/default/tokens/confirm_problem.html
+++ b/templates/web/default/tokens/confirm_problem.html
@@ -1,8 +1,16 @@
-[% INCLUDE 'header.html', title => loc('Confirmation') %]
+[% INCLUDE 'header.html', title = loc('Confirmation') %]
<h1>[% loc('Confirmation') %]</h1>
<p class="confirmed">
+[% IF c.cobrand.is_council %]
+Thank you. You have successfully confirmed your report
+and this will now be investigated by the council.
+You can <a href="[% c.uri_for( '/report', problem.id ) %]">view the problem on this site</a>.
+</p>
+
+<p>Your reference for this problem is [% problem.id %], please quote it in any enquiries.
+[% ELSE %]
[%
loc('You have successfully confirmed your problem');
@@ -15,6 +23,7 @@
c.uri_for( '/report', problem.id )
);
%]
+[% END %]
</p>
[% display_crosssell_advert( problem.user.email, problem.name ) %]
diff --git a/templates/web/fixmystreet/alert/list.html b/templates/web/fixmystreet/alert/list.html
index 8305fff6f..84899d457 100644
--- a/templates/web/fixmystreet/alert/list.html
+++ b/templates/web/fixmystreet/alert/list.html
@@ -20,7 +20,6 @@
<form id="alerts" name="alerts" method="post" action="/alert/subscribe">
-[%# XXX These photos should perhaps be in a vertical column on the right hand side, as currently. %]
[% IF photos.size %]
<div class="sticky-sidebar">
<aside>
diff --git a/templates/web/fixmystreet/around/around_index.html b/templates/web/fixmystreet/around/around_index.html
index 331f34325..d91ba928a 100644
--- a/templates/web/fixmystreet/around/around_index.html
+++ b/templates/web/fixmystreet/around/around_index.html
@@ -1,34 +1,37 @@
-[% INCLUDE 'header.html', title => loc('Reporting a problem'), bodyclass => 'mappage' %]
-
-[%
- # NOTE ON PARTIAL REPORTS:
- #
- # partial reports get a bit of extra text added, the form goes to
- # '/report/new' and the partial hidden field is added to the form.
-%]
-
+[% pre_container_extra = BLOCK %]
+<div id="front-main"><div id="front-main-container">
[%
question
= c.cobrand.enter_postcode_text()
|| loc("Enter a nearby GB postcode, or street name and area");
%]
-
<form action="[% c.uri_for('/around') %]" method="get" name="postcodeForm" id="postcodeForm">
- <fieldset>
- <label for="pc">[% question %]:</label>
+ <label for="pc">[% question %]:</label>
+ <div>
<input type="text" name="pc" value="[% pc | html %]" id="pc" size="10" maxlength="200">
- <input class="green-btn final-submit" type="submit" value="[% loc('Go') %]" id="submit">
+ <input type="submit" value="[% loc('Go') %]" id="submit">
+ </div>
- [% IF partial_token %]
- <input type="hidden" name="partial" value="[% partial_token.token %]">
- [% END %]
- </fieldset>
+ [% IF partial_token %]
+ <input type="hidden" name="partial" value="[% partial_token.token %]">
+ [% END %]
</form>
+</div></div>
+[% END %]
+
+[% INCLUDE 'header.html', title = loc('Reporting a problem'), bodyclass = 'frontpage fullwidthpage' %]
+
+[%
+ # NOTE ON PARTIAL REPORTS:
+ #
+ # partial reports get a bit of extra text added, the form goes to
+ # '/report/new' and the partial hidden field is added to the form.
+%]
[% IF location_offshore %]
- <p class="error">[% loc('That spot does not appear to be covered by a council. If you have tried to report an issue past the shoreline, for example, please specify the closest point on land.') %]</p>
+ <p class="form-error">[% loc('That spot does not appear to be covered by a council. If you have tried to report an issue past the shoreline, for example, please specify the closest point on land.') %]</p>
[% ELSIF location_error %]
- <p class="error">[% location_error %]</p>
+ <p class="form-error">[% location_error %]</p>
[% END %]
[% IF possible_location_matches %]
@@ -43,10 +46,8 @@
[% IF partial_token %]
<p style="margin-top: 0; color: #cc0000;">
<img align="right" src="/photo/[% partial_report.id %].jpeg" hspace="5">
- [% loc("Thanks for uploading your photo. We now need to locate your problem, so please enter a nearby street name or postcode in the box below&nbsp;:") %]
+ [% loc("Thanks for uploading your photo. We now need to locate your problem, so please enter a nearby street name or postcode in the box above&nbsp;:") %]
</p>
[% END %]
-
-
[% INCLUDE 'footer.html' %]
diff --git a/templates/web/fixmystreet/around/display_location.html b/templates/web/fixmystreet/around/display_location.html
index c98982a60..a7240688f 100755
--- a/templates/web/fixmystreet/around/display_location.html
+++ b/templates/web/fixmystreet/around/display_location.html
@@ -69,7 +69,12 @@
<div id="side">
- <h1 class="big-green-banner">[% loc( 'Click map to report a problem' ) %]</h1>
+ <h1 class="big-green-banner">[%
+ loc( 'Click map to report a problem' );
+ IF c.cobrand.moniker == 'bromley';
+ '<span>Yellow pins show existing reports</span>';
+ END
+ %]</h1>
<p id="skip-this-step">
[%
diff --git a/templates/web/fixmystreet/faq/faq-en-gb.html b/templates/web/fixmystreet/faq/faq-en-gb.html
index 984e747fd..badbed06b 100755
--- a/templates/web/fixmystreet/faq/faq-en-gb.html
+++ b/templates/web/fixmystreet/faq/faq-en-gb.html
@@ -72,29 +72,27 @@ by a registered charity, though, so if you want to make a contribution, <a
href="https://secure.mysociety.org/donate/">please do</a>.</dd>
<dt>Can I use FixMyStreet on my mobile?</dt>
- <dd><ul>
- <li><em>iPhone:</em> There are two apps for FixMyStreet, one written by us
- in 2008 and another much more recently by a volunteer, Martin Stephenson.
- Both are available for download on the App Store:
+ <dd>
+ <p>The FixMyStreet website should work on your mobile phone, adapting to
+ the size of your screen automatically. We plan to release updated native
+ apps in the near future.
+ <ul>
+ <li><em>iPhone:</em> Our basic app from 2008 is available for download
+ on the App Store:
<a href="http://itunes.apple.com/gb/app/fixmystreet/id297456545">FixMyStreet</a>,
- <a href="http://itunes.apple.com/gb/app/streetreport/id371891859">StreetReport</a>.
<li><em>Android:</em> A volunteer, Anna Powell-Smith, has written an app
available from the
<a href="https://market.android.com/details?id=com.android.fixmystreet">Android Market</a>.
<li><em>Nokia:</em> A volunteer, Thomas Forth, has written an app available from the
<a href="http://store.ovi.com/content/107557">Ovi Store</a>.
</ul>
- <p>We also hope to make the website itself much more mobile friendly in the future.</p>
</dd>
<dt>Why do you only cover the countries of Great Britain?</dt>
- <dd>We would love to cover Northern Ireland, but as we were funded for
- FixMyStreet by the Department for Constitutional Affairs (now the Ministry
- of Justice), we were covered for Ordnance Survey data (but not OSNI data)
- by the Pan-Governmental Agreement. The cost for these maps would be
- prohibitively expensive for the small charity that we are &ndash; if you know of
- any way we could get access to the Ordnance Survey for Northern Ireland's
- maps so that we can add them to the site, that'd be great.</dd>
+ <dd>We would love to cover Northern Ireland, but we have only been able
+ to locate boundaries for Great Britain (from Ordnance Survey). If you
+ know of a source for Northern Ireland council boundaries
+ so that we can add them to the site, that'd be great.</dd>
</dl>
<h2><a name="practical"></a>Practical Questions</h2>
@@ -136,7 +134,8 @@ by a user of the site.</dd>
<h2><a name="organisation"></a>Organisation Questions</h2>
<dl>
<dt>Who built FixMyStreet?</dt>
- <dd>This site was built by <a href="http://www.mysociety.org/">mySociety</a>, in conjunction with the <a href="http://www.youngfoundation.org.uk/">Young Foundation</a>.
+ <dd>This site was built by <a href="http://www.mysociety.org/">mySociety</a>,
+ in conjunction with the <a href="http://www.youngfoundation.org.uk/">Young Foundation</a>.
mySociety is the project of a registered charity which has grown out of the community of
volunteers who built sites like <a href="http://www.theyworkforyou.com/">TheyWorkForYou.com</a>.
mySociety&rsquo;s primary mission is to build Internet projects which give people simple, tangible
@@ -144,15 +143,11 @@ benefits in the civic and community aspects of their lives. Our first project
was <a href="http://www.writetothem.com/">WriteToThem</a>, where you can write to any of your
elected representatives, for free. The charity is called UK Citizens Online Democracy and is charity number 1076346. mySociety
can be contacted by email at <a href="mailto:hello&#64;mysociety.org">hello&#64;mysociety.org</a>,
-or by post at:<br>
-mySociety<br>
-483 Green Lanes<br>
-London<br>
-N13 4BS<br>
-UK</dd>
+or by post at mySociety, 483 Green Lanes, London, N13 4BS, UK.</dd>
<dt><img src="/i/moj.png" align="right" alt="Ministry of Justice" hspace="10">Who pays for it?</dt>
- <dd>FixMyStreet was paid for via the Department for
-Constitutional Affairs Innovations Fund.</dd>
+ <dd>FixMyStreet was originally paid for via the Department for
+ Constitutional Affairs Innovations Fund. It is now funded by a variety of means, from commercial
+ work to <a href="http://www.mysociety.org/donate/">donations</a>.</dd>
<dt><a name="nfi"></a>Wasn&rsquo;t this site called Neighbourhood Fix-It?</dt>
<dd>Yes, we changed the name mid June 2007. We decided
Neighbourhood Fix-It was a bit of a mouthful, hard to spell, and hard to publicise (does the URL have a dash in it or not?). The domain FixMyStreet became available, and everyone liked the name.</dd>
diff --git a/templates/web/fixmystreet/footer.html b/templates/web/fixmystreet/footer.html
index ada4b5d7e..903d8c73d 100644
--- a/templates/web/fixmystreet/footer.html
+++ b/templates/web/fixmystreet/footer.html
@@ -8,7 +8,6 @@
<li><a class="m-app-iphone" href="http://itunes.apple.com/gb/app/fixmystreet/id297456545">iPhone</a></li>
<li><a class="m-app-droid" href="https://market.android.com/details?id=com.android.fixmystreet">Android</a></li>
<li><a class="m-app-nokia" href="http://store.ovi.com/content/107557">Nokia</a></li>
- <li><a class="m-app-iphone-streetreport" href="http://itunes.apple.com/gb/app/streetreport/id371891859">iPhone Street Report</a></li>
</ul>
</div>
diff --git a/templates/web/fixmystreet/header.html b/templates/web/fixmystreet/header.html
index 4b1a40f71..731af797a 100644
--- a/templates/web/fixmystreet/header.html
+++ b/templates/web/fixmystreet/header.html
@@ -18,7 +18,7 @@
<link rel="stylesheet" href="[% version('/cobrands/fixmystreet/layout.css') %]">
<![endif]-->
- <script src="[% version('/js/modernizr.custom.76759.js') %]" charset="utf-8"></script>
+ <script src="[% version('/js/modernizr.custom.js') %]" charset="utf-8"></script>
<script src="[% version('/cobrands/fixmystreet/position_map.js') %]" charset="utf-8"></script>
[% INCLUDE 'common_header_tags.html', js_override = '/cobrands/fixmystreet/fixmystreet.js' %]
[% extra_js %]
@@ -52,6 +52,8 @@
[% END %]
</div>
+ [% pre_container_extra %]
+
<div class="container">
<div class="content[% " $mainclass" | html IF mainclass %]" role="main">
diff --git a/templates/web/fixmystreet/index.html b/templates/web/fixmystreet/index.html
index 04b23ca7f..476c426b0 100644
--- a/templates/web/fixmystreet/index.html
+++ b/templates/web/fixmystreet/index.html
@@ -16,18 +16,15 @@ Modernizr.load({
</script>
[% END %]
-[% INCLUDE 'header.html', title => '' , bodyclass => 'frontpage fullwidthpage' %]
-
-[% IF error %]
- <p class="error">[% error %]</p>
-[% END %]
-
-
+[% pre_container_extra = BLOCK %]
<div id="front-main">
<div id="front-main-container">
- <h1>[% loc('Report, view, or discuss local problems') %]</h1>
-
- <h2>[% loc('(like graffiti, fly tipping, broken paving slabs, or street lighting)') %]</h2>
+ [% IF c.cobrand.moniker == 'bromley' %]
+ <h1 class="main mob-only">Reporting a problem in Bromley&rsquo;s streets or parks</h1>
+ [% ELSE %]
+ <h1>[% loc('Report, view, or discuss local problems') %]</h1>
+ <h2>[% loc('(like graffiti, fly tipping, broken paving slabs, or street lighting)') %]</h2>
+ [% END %]
[%
question
@@ -44,6 +41,17 @@ Modernizr.load({
</form>
</div>
</div>
+[% END %]
+
+[% INCLUDE 'header.html', title = '', bodyclass = 'frontpage fullwidthpage' %]
+
+[% IF c.cobrand.moniker == 'bromley' %]
+<p>This is primarily for reporting physical problems that can be fixed
+like graffiti, cleansing issues or road defects. To report other
+kinds of problems like missed bins use our
+<a href="http://www.bromley.gov.uk/report">online report it forms</a>.
+</p>
+[% END %]
<div class="tablewrapper">
<div id="front-howto">
@@ -53,7 +61,11 @@ Modernizr.load({
<li>[% question %]</li>
<li>[% loc('Locate the problem on a map of the area') %]</li>
<li>[% loc('Enter details of the problem') %]</li>
- <li>[% loc('We send it to the council on your behalf') %]</li>
+ [% IF c.cobrand.is_council %]
+ <li>Confirm the report and [% c.cobrand.council_name %] will investigate</li>
+ [% ELSE %]
+ <li>[% loc('We send it to the council on your behalf') %]</li>
+ [% END %]
</ol>
<section class="full-width">
@@ -64,7 +76,7 @@ Modernizr.load({
[%
- recent_photos = c.cobrand.recent_photos(5);
+ recent_photos = c.cobrand.recent_photos('front', 5);
%]
[% IF recent_photos.size %]
diff --git a/templates/web/fixmystreet/report/display.html b/templates/web/fixmystreet/report/display.html
index c0d89973c..26e041d61 100644
--- a/templates/web/fixmystreet/report/display.html
+++ b/templates/web/fixmystreet/report/display.html
@@ -179,6 +179,10 @@
[% INCLUDE 'footer.html' %]
[% BLOCK name %]
+ [% IF problem.council == '2482' %]
+ [% extra_name_info = 1 %]
+ [% INCLUDE 'report/new/extra_name.html' %]
+ [% END %]
<label for="form_name">[% loc('Name') %]</label>
[% IF field_errors.name %]
<p class='form-error'>[% field_errors.name %]</p>
diff --git a/templates/web/fixmystreet/report/new/category.html b/templates/web/fixmystreet/report/new/category.html
deleted file mode 100644
index 095cd7c2e..000000000
--- a/templates/web/fixmystreet/report/new/category.html
+++ /dev/null
@@ -1,10 +0,0 @@
-[% FILTER collapse %]
-[% IF category_options.size %]
- <label for='form_category'>[% category_label | html %]</label>
- <select name='category' id='form_category'[% ' onchange="form_category_onchange()"' IF category_extras.size %]>
- [% FOREACH cat_op IN category_options %]
- <option value='[% cat_op | html %]'[% ' selected' IF report.category == cat_op %]>[% cat_op | html %]</option>
- [% END %]
- </select>
-[% END %]
-[% END -%]
diff --git a/templates/web/fixmystreet/report/new/councils_text.html b/templates/web/fixmystreet/report/new/councils_text.html
deleted file mode 100644
index 1da5746c1..000000000
--- a/templates/web/fixmystreet/report/new/councils_text.html
+++ /dev/null
@@ -1,9 +0,0 @@
-[% FILTER collapse %]
-[% IF area_ids_to_list.size == 0 %]
- [% PROCESS 'report/new/councils_text_none.html' %]
-[% ELSIF area_ids_to_list.size == all_councils.size %]
- [% PROCESS 'report/new/councils_text_all.html' %]
-[% ELSE %]
- [% PROCESS 'report/new/councils_text_some.html' %]
-[% END %]
-[% END -%]
diff --git a/templates/web/fixmystreet/report/new/councils_text_all.html b/templates/web/fixmystreet/report/new/councils_text_all.html
deleted file mode 100644
index fe2d5be12..000000000
--- a/templates/web/fixmystreet/report/new/councils_text_all.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<p>
-[% IF area_ids_to_list.0 != 2489 && all_councils.${area_ids_to_list.0}.type == 'LBO' %]
-[%
- tprintf(
- loc('All the information you provide here will be sent to <strong>%s</strong> or a relevant local body such as <strong>TfL</strong>, via the London Report-It system.'),
- all_council_names.join( '</strong>' _ loc(' or ') _ '<strong>' )
- );
-%]
-[% ELSE %]
-[%
- tprintf(
- loc('All the information you provide here will be sent to <strong>%s</strong>.'),
- all_council_names.join( '</strong>' _ loc(' or ') _ '<strong>' )
- );
-%]
-[% END %]
-
-[% loc('The subject and details of the problem will be public, plus your name if you give us permission.') %]
-</p>
diff --git a/templates/web/fixmystreet/report/new/councils_text_none.html b/templates/web/fixmystreet/report/new/councils_text_none.html
deleted file mode 100644
index 992d030c0..000000000
--- a/templates/web/fixmystreet/report/new/councils_text_none.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<p>[%
-
- nget(
- "We do not yet have details for the council that covers this location.",
- "We do not yet have details for the councils that cover this location.",
- all_councils.size
- );
-%]
-[%
- loc("If you submit a problem here the subject and details of the problem will be public, but the problem will <strong>not</strong> be reported to the council.");
-%]
-[%
- tprintf(
- loc("You can help us by finding a contact email address for local problems for %s and emailing it to us at <a href='mailto:%s'>%s</a>."),
- all_council_names.join( loc(' or ') ),
- c.cobrand.contact_email,
- c.cobrand.contact_email
- );
-
-%]</p> \ No newline at end of file
diff --git a/templates/web/fixmystreet/report/new/councils_text_some.html b/templates/web/fixmystreet/report/new/councils_text_some.html
deleted file mode 100644
index 9f123fb9a..000000000
--- a/templates/web/fixmystreet/report/new/councils_text_some.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<p>
-[% loc('All the information you provide here will be sent to') %]
-
-[% FOREACH council_id IN area_ids_to_list %]
- [% loc( ' or ') IF ! loop.first %]
- <strong>[% all_councils.$council_id.name %]</strong>
- [%- '.' IF loop.last %]
-[% END %]
-
-[%
- loc('The subject and details of the problem will be public, plus your name if you give us permission.');
-%]
-[%
- nget(
- "We do <strong>not</strong> yet have details for the other council that covers this location.",
- "We do <strong>not</strong> yet have details for the other councils that cover this location.",
- missing_details_councils.size
- );
-%]
-[%
- tprintf(
- loc("You can help us by finding a contact email address for local problems for %s and emailing it to us at <a href='mailto:%s'>%s</a>."),
- missing_details_council_names.join( loc(' or ') ),
- c.cobrand.contact_email,
- c.cobrand.contact_email
- );
-%]
-</p> \ No newline at end of file
diff --git a/templates/web/fixmystreet/report/new/fill_in_details_form.html b/templates/web/fixmystreet/report/new/fill_in_details_form.html
index 283f748ba..237533657 100644
--- a/templates/web/fixmystreet/report/new/fill_in_details_form.html
+++ b/templates/web/fixmystreet/report/new/fill_in_details_form.html
@@ -69,7 +69,7 @@
[% IF js %]
<div id="form_category_row">
- <label class="inline" for="form_category">[% loc('Category') %]</label>
+ <label for="form_category">[% loc('Category') %]</label>
<select name="category" id="form_category" required><option>[% loc('Loading...') %]</option></select>
</div>
[% ELSE %]
@@ -111,7 +111,10 @@
[% IF c.user_exists %]
<div class="form-box">
- <label for="form_may_show_nameme">[% loc('Name') %]</label>
+
+ [% INCLUDE 'report/new/extra_name.html' %]
+
+ <label for="form_name">[% loc('Name') %]</label>
[% IF field_errors.name %]
<p class='form-error'>[% field_errors.name %]</p>
[% END %]
@@ -168,10 +171,13 @@
<div id="form_sign_in_no" class="form-box">
<h5>[% loc('<strong>No</strong> Let me confirm my report by email') %]</h5>
- <label for="form_may_show_nameme">[% loc('Name') %]</label>
+ [% INCLUDE 'report/new/extra_name.html' %]
+
+ <label for="form_name">[% loc('Name') %]</label>
[% IF field_errors.name %]
<p class='form-error'>[% field_errors.name %]</p>
[% END %]
+
<input type="text" class="form-focus-trigger validName" value="[% report.name | html %]" name="name" id="form_name" placeholder="[% loc('Your name') %]">
[%# if there is nothing in the name field then set check box as default on form %]
diff --git a/templates/web/fixmystreet/report/new/notes.html b/templates/web/fixmystreet/report/new/notes.html
index 5a885abd0..289520be3 100644
--- a/templates/web/fixmystreet/report/new/notes.html
+++ b/templates/web/fixmystreet/report/new/notes.html
@@ -6,4 +6,4 @@
<li>[% loc("Please do not be abusive &mdash; abusing your council devalues the service for all users.") %]</li>
<li>[% loc("Writing your message entirely in block capitals makes it hard to read, as does a lack of punctuation.") %]</li>
<li>[% loc("Remember that FixMyStreet is primarily for reporting physical problems that can be fixed. If your problem is not appropriate for submission via this site remember that you can contact your council directly using their own website.") %]</li>
-</ul> \ No newline at end of file
+</ul>
diff --git a/templates/web/fixmystreet/report/new/report_import.html b/templates/web/fixmystreet/report/new/report_import.html
deleted file mode 100644
index 7aa105afe..000000000
--- a/templates/web/fixmystreet/report/new/report_import.html
+++ /dev/null
@@ -1,92 +0,0 @@
-[% INCLUDE 'header.html', title => 'External import' %]
-
-<h1>External import</h1>
-
-<p>You may inject problem reports into FixMyStreet programatically using this
-simple interface. Upon receipt, an email will be sent to the address given,
-with a link the user must click in order to check the details of their report,
-add any other information they wish, and then submit to the council.
-
-<p>This interface returns a plain text response; either <samp>SUCCESS</samp> if
-the report has been successfully received, or if not, a list of errors, one per
-line each starting with <samp>ERROR:</samp>.
-
-<p>You may submit the following information by POST to this URL
-(i.e. <samp>[% c.uri_for('/import') %]</samp> ):</p>
-
-<style type="text/css" media="screen">
- input {
- /* Hide the form elements - they are just here for simpler testing */
- display: none;
- }
-</style>
-
-<form method="POST" action="/import" enctype="multipart/form-data">
-
-<dl>
- <dt>service</dt>
- <dd>
- <em>Required</em>.
- Name of application/service using this interface.
- <input type="text" name="service" />
- </dd>
-
- <dt>id</dt>
- <dd>
- Unique ID of a user/device, for possible future use.<br>
- <small>(e.g. used by Flickr import to know which accounts to look at)</small>
- <input type="text" name="id" />
- </dd>
-
- <dt>subject</dt>
- <dd>
- <em>Required</em>. Subject of problem report.
- <input type="text" name="subject" />
- </dd>
-
- <dt>detail</dt>
- <dd>
- Main body and details of problem report.
- <input type="text" name="detail" />
- </dd>
-
- <dt>name</dt>
- <dd>
- <em>Required</em>. Name of problem reporter.
- <input type="text" name="name" />
- </dd>
-
- <dt>email</dt>
- <dd>
- <em>Required</em>. Email address of problem reporter.
- <input type="text" name="email" />
- </dd>
-
- <dt>phone</dt>
- <dd>
- Telephone number of problem reporter.
- <input type="text" name="phone" />
- </dd>
-
- <dt>easting / northing</dt>
- <dt>lat / lon</dt>
- <dd>
- Location of problem report. You can either supply eastings/northings, or WGS84 latitude/longitude.
- <input type="text" name="easting" />
- <input type="text" name="northing" />
- <input type="text" name="lat" />
- <input type="text" name="lon" />
- </dd>
-
- <dt>photo</dt>
- <dd>
- Photo of problem (JPEG only).
- <input type="file" name="photo" />
- </dd>
-</dl>
-
-<input type="submit" />
-
-</form>
-
-[% INCLUDE 'footer.html' %] \ No newline at end of file
diff --git a/templates/web/fixmystreet/report/updates-sidebar-notes.html b/templates/web/fixmystreet/report/updates-sidebar-notes.html
index a622cc7da..7dd6a235c 100644
--- a/templates/web/fixmystreet/report/updates-sidebar-notes.html
+++ b/templates/web/fixmystreet/report/updates-sidebar-notes.html
@@ -1 +1,5 @@
- <p>[% loc( 'Please note that updates are not sent to the council. If you leave your name it will be public. Your information will only be used in accordance with our <a href="/faq#privacy">privacy policy</a>' ) %]</p>
+ <p>
+ [% IF problem.council != '2482' OR problem.send_method_used != 'Open311' %]
+ [% loc( 'Please note that updates are not sent to the council.' ) %]
+ [% END %]
+ [% loc( 'Your information will only be used in accordance with our <a href="/faq#privacy">privacy policy</a>' ) %]</p>
diff --git a/web/cobrands/bromley/bromley-logo.jpg b/web/cobrands/bromley/bromley-logo.jpg
new file mode 100644
index 000000000..28da8b7ea
--- /dev/null
+++ b/web/cobrands/bromley/bromley-logo.jpg
Binary files differ
diff --git a/web/cobrands/bromley/bromley-logo.s.jpg b/web/cobrands/bromley/bromley-logo.s.jpg
new file mode 100644
index 000000000..16f632848
--- /dev/null
+++ b/web/cobrands/bromley/bromley-logo.s.jpg
Binary files differ
diff --git a/web/cobrands/bromley/bromley.scss b/web/cobrands/bromley/bromley.scss
new file mode 100644
index 000000000..91ec75fea
--- /dev/null
+++ b/web/cobrands/bromley/bromley.scss
@@ -0,0 +1,110 @@
+/* Parts of Bromley's main CSS needed for its header/footer and adjusted (see
+ * bottom) to not be affected by main FixMyStreet CSS. Not very sustainable;
+ * perhaps we should wrap all council CSS within a SCSS #council ID? Hmm.
+ */
+
+@import "compass";
+
+// Bits of Bromley's forms.css, adjusted
+input[type=text],
+input[type=password],
+input[type=email],
+input[type=file],
+textarea,
+select { background: #fff; border: 1px solid #768b9a; border-color: #768b9a #d1dee8 #d1dee8 #768b9a; color: #333; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; padding: 8px;
+@include border-radius(0); }
+
+.green-btn, button.green-btn, input.green-btn {
+ background: #5b7189 url("https://www.bromley.gov.uk/site/styles/css_img/button.gif") repeat-x 0 -1px; border: 1px solid #8e9eb0; color: #fff; font-family: 'Gill Sans', 'Trebuchet MS', Calibri, sans-serif; font-weight: normal; margin: 0; min-height: 23px; outline: 1px solid #405062; padding: 4px 8px; text-transform: uppercase; }
+.green-btn:hover, button.green-btn:hover, input.green-btn:hover { background: #5b7189; border: 1px solid #8e9eb0; }
+.form-txt-submit-box input[type=submit] {
+ padding-top: 0; padding-bottom: 0; width: auto;
+}
+
+
+h1, h2, h3, h4, h5, h6, legend { font-family: 'Gill Sans', 'Trebuchet MS', Calibri, sans-serif; }
+body { color: #333; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; line-height: 1.4; }
+
+// #header renamed to #bromley-header
+#bromley-header { padding: 0 18px 0 25px; }
+
+.header-nav {float:right; background:url('https://www.bromley.gov.uk/site/styles/css_img/header-nav.gif') no-repeat; overflow:hidden; width:651px; height:34px; line-height:34px; padding:0 20px;}
+.header-nav li {float:left; width:130px; text-align:center; background:url('https://www.bromley.gov.uk/site/styles/css_img/header-nav-divider.gif') top right no-repeat;}
+.header-nav li:last-child {background:none;}
+.header-nav a:link, .header-nav a:visited {color:#fff; text-decoration:none;}
+.header-nav a:hover, .header-nav a:active {text-decoration:underline;}
+
+/* -------- For Google translate select box only */
+.header-nav div#google_translate_element .goog-te-gadget { font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; }
+.header-nav div#google_translate_element .goog-te-gadget .goog-te-combo { margin:5px 0 0 5px; padding:0 0 0 2px; background: #9b9b9b; color: #fff; border: 1px solid #606060; font-size: 12px; width: 138px;}
+.header-nav div#google_translate_element .goog-te-gadget .goog-te-combo option { background:#fff; color: #666; padding: 1px 0; margin: 0; }
+/* --- */
+
+.logo {padding:10px 0;}
+
+.sign-in {float:right; margin-top:-87px; width: 650px; text-align: right;}
+.sign-in a:link, .sign-in a:visited {color:#333; font-weight:bold; text-decoration:none;}
+.sign-in a:hover, .sign-in a:active {text-decoration:underline;}
+
+.main-menu {background:url('https://www.bromley.gov.uk/site/styles/css_img/main-menu.gif') no-repeat; width:689px; height:45px; margin-top:-60px; float:right; clear:right;}
+.main-menu li {float:left; width:126px; padding-right:2px; text-align:center; font:150%/45px 'Gill Sans', 'Trebuchet MS', Calibri, sans-serif;}
+.main-menu li a:link, .main-menu li a:visited {color:#fff; display:block; text-decoration:none;}
+.main-menu li a:hover, .main-menu li a:active {background:url('https://www.bromley.gov.uk/site/styles/css_img/main-menu-hover.gif') repeat-x;}
+.main-menu li.home a:hover, .main-menu li.home a:active {background:url('https://www.bromley.gov.uk/site/styles/css_img/main-menu-hover-home.gif') repeat-x;}
+
+//#search { float: right; padding: 10px 17px; width: 270px; }
+//#search label {display:none;}
+//#search input { background: #ececec; border: 1px solid #768b9a; border-color: #768b9a #d1dee8 #d1dee8 #768b9a; line-height: 9px; padding: 6px 5px 4px; width: 184px; }
+//#search input.button { background: #fff; border: 1px solid #8596a8; color: #333; font-family: 'Gill Sans', 'Trebuchet MS', Calibri, sans-serif; font-size:133%; height: 27px; line-height: 27px; padding: 0; vertical-align: top; width: 70px; }
+
+// #footer renamed to #bromley-footer, fixed font size.
+#bromley-footer { background: #666 url('https://www.bromley.gov.uk/site/styles/css_img/footer.gif') top center no-repeat; clear: both; width:100%; padding:30px 15px 50px; margin-left:-15px; color:#fff; font-size:92%;}
+#bromley-footer a:link,
+#bromley-footer a:visited { color: #fff; text-decoration: none; }
+#bromley-footer a:hover,
+#bromley-footer a:active { text-decoration: underline;}
+#bromley-footer img { float: right; margin-top: -18px; }
+#bromley-footer .footer-nav { float: right; height:35px; margin-top: -15px; }
+#bromley-footer .footer-nav li { border-right:1px solid #fff; float: left; line-height: 1; padding: 0 20px; }
+#bromley-footer .footer-nav li:last-child { border: none; }
+
+// Bromley IE specific CSS
+.ie6 {
+ div { zoom: 1; }
+ #bromley-wrapper { padding: 0; }
+ #bromley-footer { margin: 0; }
+ //#search input { padding: 8px 5px 2px; }
+ //#search input.button { padding: 0; width: 68px; line-height: 24px; }
+ input.button { overflow: visible; width: 1%; }
+}
+.ie7 {
+ div { zoom: 1; }
+ //#search input { padding: 8px 5px 2px; }
+ //#search input.button { padding: 0; width: 68px; line-height: 24px; }
+ input.button { overflow: visible; }
+}
+.ie8 {
+ //#search input { padding: 8px 5px 2px; }
+ //#search input.button { padding: 0; line-height: 24px; }
+}
+
+// mySociety additions
+#bromley-header { font-size: 12px; }
+.header-nav ul { margin: 0; }
+.header-nav li { list-style-type: none; }
+// Width is actually 637, but that causes wrap-around, need to ask for fixes and corner image
+.main-menu { background: url('/cobrands/bromley/main-menu1.gif') no-repeat; width: 638px; }
+.main-menu ul { margin: 0; }
+.main-menu li { list-style-type: none; color: #ccf; }
+.main-menu li.last { padding-right: 0; }
+.main-menu li.last a:hover, .main-menu li.last a:active {background:url('/cobrands/bromley/main-menu-hover-home-right.gif') repeat-x;}
+//#search input { display: inline; margin: 0; @include border-radius(0em); }
+//#search input.button { font-weight: normal; text-transform: none; }
+#bromley-footer { padding-bottom: 60px; }
+#bromley-footer .footer-nav li { list-style-type: none; }
+#bromley-footer p { margin: 0; }
+
+#bromley-powered-by {
+ clear: both;
+}
+
diff --git a/web/cobrands/bromley/favicon.ico b/web/cobrands/bromley/favicon.ico
new file mode 100644
index 000000000..cf0577755
--- /dev/null
+++ b/web/cobrands/bromley/favicon.ico
Binary files differ
diff --git a/web/cobrands/bromley/favicon.png b/web/cobrands/bromley/favicon.png
new file mode 100644
index 000000000..0acd804e7
--- /dev/null
+++ b/web/cobrands/bromley/favicon.png
Binary files differ
diff --git a/web/cobrands/bromley/fms-logo.png b/web/cobrands/bromley/fms-logo.png
new file mode 100644
index 000000000..23bea6b0f
--- /dev/null
+++ b/web/cobrands/bromley/fms-logo.png
Binary files differ
diff --git a/web/cobrands/bromley/layout.scss b/web/cobrands/bromley/layout.scss
index f773b47f1..eef0ee679 100644
--- a/web/cobrands/bromley/layout.scss
+++ b/web/cobrands/bromley/layout.scss
@@ -1,8 +1,8 @@
@import "_colours";
@import "../fixmystreet/_layout";
-body { background: #9b9b9b url('http://www.bromley.gov.uk/site/styles/css_img/repeater.gif') repeat-x; }
-#bromley-wrapper { background: #fff url('http://www.bromley.gov.uk/site/styles/css_img/header-corners.gif') center 110px no-repeat; margin: 1px auto 0; padding: 0 15px; width: 955px;}
+body { background: #9b9b9b url('https://www.bromley.gov.uk/site/styles/css_img/repeater.gif') repeat-x; }
+#bromley-wrapper { background: #fff url('https://www.bromley.gov.uk/site/styles/css_img/header-corners.gif') center 110px no-repeat; margin: 1px auto 0; padding: 0 15px; width: 955px;}
.offline #bromley-wrapper { padding: 0 15px 20px; }
// So that map appears underneath the header
@@ -15,15 +15,46 @@ body { background: #9b9b9b url('http://www.bromley.gov.uk/site/styles/css_img/re
}
}
+// To prevent font size larger interfering with the fixed Bromley layout
+.container { width: auto; }
+.full-width { width: 464px; }
+.shadow-wrap { width: 464px; }
+#map_box { width: 464px; }
+.content { width: 432px; }
+body.fullwidthpage .content { width: auto; }
+body.twothirdswidthpage .content {
+ width: 640px;
+ aside {
+ left: 672px;
+ width: 208px;
+ padding: 16px;
+ }
+ .sticky-sidebar {
+ left: 672px;
+ aside {
+ top:7em;
+ }
+ }
+}
+.ie6, .ie7 {
+ body.mappage .container {
+ width: 464px;
+ margin-left: 0;
+ }
+}
+
#front-main {
background: $primary;
@include border-radius(1em 1em 0 0);
- margin-bottom: 1em;
+ margin: 2em 1em 0;
padding-top: 0;
// layout sets this because base has it slightly lighter
h2 {
color: $primary_text;
}
+ a#geolocate_link {
+ color: $primary_text;
+ }
}
// Don't want gap that normal site has
@@ -57,6 +88,7 @@ body.frontpage {
}
.ie6, .ie7, .ie8 {
.content {
+ margin: 0;
border: none;
}
}
@@ -71,7 +103,7 @@ body.frontpage {
// Perhaps fix map location (should be in central?)
.ie6 #map_box, .ie7 #map_box {
- right: -32em;
+ right: -480px;
}
.general-sidebar-notes,
@@ -101,6 +133,7 @@ body.twothirdswidthpage {
@include box-shadow(none);
}
.sticky-sidebar {
+ top: 1em;
aside {
position: absolute;
top: 0;
@@ -108,24 +141,15 @@ body.twothirdswidthpage {
}
}
}
-@media only screen and (min-width: 48em) and (max-width: 61em) {
- body.twothirdswidthpage {
- .content {
- .sticky-sidebar {
- top: auto;
- }
- }
- }
-}
// So as not to interfere with the Bromley footer, make the fixed nav static.
.shadow-wrap {
- position: static;
- padding-top: 0;
- margin-bottom: 1em;
- ul#key-tools {
- border-top: none;
- border-bottom: 1px solid $primary;
- }
+ position: static;
+ padding-top: 0;
+ margin-bottom: 1em;
+ ul#key-tools {
+ border-top: none;
+ border-bottom: 1px solid $primary;
+ }
}
diff --git a/web/cobrands/bromley/main-menu-hover-home-right.gif b/web/cobrands/bromley/main-menu-hover-home-right.gif
new file mode 100644
index 000000000..80c11c782
--- /dev/null
+++ b/web/cobrands/bromley/main-menu-hover-home-right.gif
Binary files differ
diff --git a/web/cobrands/bromley/main-menu1.gif b/web/cobrands/bromley/main-menu1.gif
new file mode 100644
index 000000000..21ab45668
--- /dev/null
+++ b/web/cobrands/bromley/main-menu1.gif
Binary files differ
diff --git a/web/cobrands/bromley/tab-blue.png b/web/cobrands/bromley/tab-blue.png
new file mode 100644
index 000000000..62e6285b7
--- /dev/null
+++ b/web/cobrands/bromley/tab-blue.png
Binary files differ
diff --git a/web/cobrands/fixmystreet/_base.scss b/web/cobrands/fixmystreet/_base.scss
index 644b015b5..4d3b36888 100644
--- a/web/cobrands/fixmystreet/_base.scss
+++ b/web/cobrands/fixmystreet/_base.scss
@@ -31,9 +31,6 @@ h1 {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
-#front-main h1 {
- margin-top: 0.7em;
-}
h1#reports_heading span {
display: none;
}
@@ -145,12 +142,23 @@ img {
//do this otherwise IE will just not display
//any img without a height defined
height:auto;
+ max-width: 100%;
+}
+// So that map popups display correctly
+#popup img {
+ max-width: none;
}
select, input, textarea {
font-size: 99%;
max-width: 95%;
}
+.ie7 {
+ select, input, textarea {
+ max-width: none;
+ }
+}
+
// To deal with bug from drop-down being wider than holder
select {
width: 100%;
@@ -247,10 +255,27 @@ input[type=password],
input[type=email],
input[type=file],
textarea {
+ @include box-sizing(border-box);
width: 100%;
// adjust so the sides line up
padding: 0.5em;
- margin: 0 0 0 -0.5em;
+}
+.ie7 {
+ input[type=text],
+ input[type=password],
+ input[type=email],
+ input[type=file],
+ textarea {
+ max-width: 95%;
+ }
+ // In order to work around the IE7 specific issue of inheriting left margins
+ // http://techblog.willshouse.com/2009/07/12/ie6ie7-form-element-margin-inheritance-bug/
+ fieldset > input[type=text],
+ fieldset > input[type=password],
+ fieldset > input[type=email],
+ fieldset > textarea {
+ margin-left: -1em;
+ }
}
textarea {
@@ -294,7 +319,6 @@ label{
margin: 0 -2em 0.25em -2em;
background:#eeeeee;
padding:1em 2em 1em 2em;
- max-width:26em;
>input[type=text] {
margin-bottom:1em;
}
@@ -303,7 +327,7 @@ label{
margin:0.5em 0;
}
h5 {
- margin:0;
+ margin:0 0 1em;
font: {
size:1.125em;
weight:normal;
@@ -319,6 +343,14 @@ label{
// it looks okay.
.ie6 .form-box {
margin: 0 0 0.25em 0;
+ padding: 1em;
+}
+// Prevent grey displaying oddly by giving it a width, and stop odd left margin issue
+.ie7 .form-box {
+ width: 100%;
+ > input[type=text] {
+ margin-left: 2em;
+ }
}
.form-txt-submit-box {
@@ -350,8 +382,14 @@ p.form-error {
background:#ff0000;
color:#fff;
padding:0 0.5em;
- margin:0 0 0 -0.5em;
@include border-radius(0.25em 0.25em 0 0);
+ a {
+ color: white;
+ text-decoration: underline;
+ }
+ a:hover {
+ text-decoration: none;
+ }
}
input.form-error,
@@ -364,7 +402,6 @@ ul.error {
background:#ff0000;
color:#fff;
padding:0 0.5em;
- margin:0 0 0 -0.5em;
@include border-radius(0.25em);
}
@@ -457,7 +494,8 @@ p.label-valid {
&#mysoc-menu{
li {
a {
- background:$primary;
+ color: $primary_text;
+ background: $primary;
&#mysoc-logo {
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIkAAAAyCAMAAABf9whNAAAACW9GRnMAAADwAAAAJgAMZizzAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAF5AAAQZgCwCYXlAAAANlBMVEUAAAAiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiL///90WH2CAAAAEHRSTlMADx8vP09fb3+Pn6+/z9/v+t8hjgAAAAFiS0dEEeK1PboAAAGqSURBVFjD7ZbNjtwgEAY/oIE2YKj3f9ocPDOZ3eN6FCUKdWsJQ+H+saXNZrPZbDabzf/GovwdIhH8LUzHn/PqI32Jcw1v0fjidQf30EZPqmMURXeT5F5kbYwqSQbeWpAU6xg961pT+mgm+WR4Ke6SzG9JwQRWB6iaTClCzQBLknT0AibZAnCBSx2ALACGQ5JOznsmq1TA82LKIalC6Jwh5seaMGdUWKxiJQpclWVqLNlJsxThUIB6z8Qft3FQgEODrs4qUuj9qoljpAr2euKkmxVIjzrpTBUI90xMGgzJQGrMAEUGrGKQn6c7/HbngT1MMqRO1wdNDA6WpNgW5Ld38s1kuLt7fPbO5Lg7Z76ZaLJoV3WAa6wsxbmiyrMMruyMVxc3SaqvAv+YSQWS1Fs9oAi8jQKmsGB4zwJXgdO9VWnAkaUAHJ81CTAkLYAm5ZbB/QhSmm9d7Ff7Splr5jaI90zMgpQsScHsGmRFUsheH6O1jecR5m5BZlFSzF5NkpJ7lMJ85etDlPWjHdMJ6aMiFdYPdgzw6Q90Yvwo2+fM+3dps9lsNpvNP8gvi7UYry9B6TcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTItMDUtMDJUMTk6NDY6NTErMDE6MDAcdbR4AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEyLTA1LTAyVDE5OjQ2OjUxKzAxOjAwbSgMxAAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAASUVORK5CYII=');
background-repeat:no-repeat;
@@ -485,7 +523,8 @@ p.label-valid {
#user-meta {
p {
position: relative;
- background:$primary;
+ color: $primary_text;
+ background: $primary;
padding:1em 6em 1em 1em;
a {
position: absolute;
@@ -518,7 +557,11 @@ p.label-valid {
&:last-child {
border-right:none;
}
- a {
+ input[type=submit] {
+ width: 100%;
+ border: none;
+ }
+ a, input[type=submit] {
display: block;
background-color: #f5f5f5;
background-repeat: no-repeat;
@@ -529,7 +572,7 @@ p.label-valid {
size:0.6875em;
family: Helmet, Freesans, sans-serif;
}
- &:hover, &.hover, &.active {
+ &:hover, &.hover {
text-decoration:none;
background-color:#333;
color:#fff;
@@ -546,7 +589,7 @@ p.label-valid {
background-image:url('/cobrands/fixmystreet/images/sprite.png');
background-position:center -2716px;
}
- &.hover, &.active {
+ &.hover {
background-image:url('/cobrands/fixmystreet/images/sprite.png');
background-position:center -2064px;
}
@@ -717,7 +760,7 @@ a:hover.button-left {
width: 0;
height: 0;
border-left: 0.5em solid transparent;
- border-bottom: 0.5em solid #4B8304;
+ border-bottom: 0.5em solid $contrast1_dark;
}
}
@@ -753,7 +796,7 @@ a:hover.button-left {
color:#fff;
background: $contrast1;
&:before {
- border-bottom: 0.5em solid #4B8304;
+ border-bottom: 0.5em solid $contrast1_dark;
}
}
}
@@ -838,6 +881,7 @@ a:hover.button-left {
}
.img {
text-align:right;
+ width: 90px;
img {
height:auto;
}
@@ -906,7 +950,7 @@ a:hover.button-left {
width:20px;
height:20px;
opacity: 0.5;
- background:#fff url(images/sprite.png) -16px -1098px no-repeat;
+ background:#fff url(/cobrands/fixmystreet/images/sprite.png) -16px -1098px no-repeat;
//hide text - http://nicolasgallagher.com/another-css-image-replacement-technique/
font: 0/0 a;
color: transparent;
@@ -1286,7 +1330,8 @@ table.nicetable {
.promo {
@extend .full-width;
- background:$primary;
+ color: $primary_text;
+ background: $primary;
padding:1em;
margin-bottom:1em;
overflow:hidden;
@@ -1299,10 +1344,10 @@ table.nicetable {
width:16px;
height:16px;
text-indent:-999999px;
- background:url(images/sprite.png) -341px -263px no-repeat;
+ background:url(/cobrands/fixmystreet/images/sprite.png) -341px -263px no-repeat;
@include border-radius(4px);
&:hover {
- background:#222 url(images/sprite.png) -341px -223px no-repeat;
+ background:#222 url(/cobrands/fixmystreet/images/sprite.png) -341px -223px no-repeat;
}
}
}
@@ -1333,7 +1378,8 @@ table.nicetable {
}
a {
@include inline-block;
- background:$primary;
+ color: $primary_text;
+ background: $primary;
padding-left:0.5em;
padding-right:0.5em;
color:#1a1a1a;
@@ -1347,7 +1393,7 @@ table.nicetable {
// this is a bit of a hack to get some differentation between desk and mobile
.desk-only {
- display:none !important;
+ display:none;
}
// hide anything with this class if js is working
@@ -1366,6 +1412,7 @@ table.nicetable {
/* Front page */
#front-main {
text-align:center;
+ margin: 1em;
h2 {
font: {
style:italic;
@@ -1374,6 +1421,9 @@ table.nicetable {
}
color:#4d4d4d;
}
+ p {
+ margin: 0.5em 0 0;
+ }
#postcodeForm {
@extend .full-width;
padding:1em;
@@ -1392,7 +1442,7 @@ table.nicetable {
display:table-cell;
margin:0;
padding:0.25em 2%;
- width:82%;
+ width:86%;
border:none;
background:none;
line-height:1.5em;
@@ -1460,7 +1510,6 @@ table.nicetable {
#front-recently {
.issue-list-a {
border-bottom:none;
- margin-bottom:0;
}
}
diff --git a/web/cobrands/fixmystreet/_colours.scss b/web/cobrands/fixmystreet/_colours.scss
index 2463cdeeb..1a62d0282 100644
--- a/web/cobrands/fixmystreet/_colours.scss
+++ b/web/cobrands/fixmystreet/_colours.scss
@@ -5,4 +5,5 @@ $primary_b: #F3B11E;
$primary_text: #222;
$contrast1: #00BD08;
+$contrast1_dark: #4B8304;
$contrast2: #AA8D11;
diff --git a/web/cobrands/fixmystreet/_layout.scss b/web/cobrands/fixmystreet/_layout.scss
index d9084d928..56b2f41d5 100644
--- a/web/cobrands/fixmystreet/_layout.scss
+++ b/web/cobrands/fixmystreet/_layout.scss
@@ -3,10 +3,10 @@
//hacks for desk/mob only stuff
.desk-only {
- display:block !important;
+ display: block;
}
.mob-only {
- display:none !important;
+ display: none;
}
body {
@@ -147,7 +147,8 @@ h1 {
@include background(linear-gradient(#000, #444 10%, #444 95%, #111));
}
a.report-a-problem-btn {
- background:$primary;
+ color: $primary_text;
+ background: $primary;
padding:0.25em;
margin:0.5em;
color:#333;
@@ -197,28 +198,23 @@ h1 {
// .content Is the white box
// The narrow single column box
-.content{
+.content {
width: 27em;
margin-top: 3em;
- background: #fff;
- padding: 1em;
- padding-bottom: 3em;
- margin-left: 0.5em;
margin-bottom: -1em;
+ margin-left: 0.5em;
+ padding: 1em 1em 3em;
+ background: #fff;
@include box-shadow(0px 0px 6px 1px #000);
}
.ie6, .ie7, .ie8 {
.content {
// If no box-shadow, just want a boring black border to stand it out from the map.
- border: 1px solid black;
+ border: 1px solid #666;
//take off margins so we line up properly
- margin: 0;
+ margin: 0 0 0 0.5em;
}
}
-//weird margining thing for ie8
-.ie8 .content {
- margin-top:3em;
-}
// map page - has fixed header and different styling
body.mappage {
@@ -253,7 +249,7 @@ body.mappage {
.container {
float: left;
width: 27em;
- margin-left: 1.4em;
+ margin-left: 0.7em;
}
.nav-wrapper{
z-index:1;
@@ -266,13 +262,6 @@ body.mappage {
}
}
}
-.ie6 {
- body.mappage {
- .container {
- margin-left: 0.7em;
- }
- }
-}
//ie8 needs different stuff on .nav-wrapper so we
//have to define all the rest of it again as this resets
//the z-index base yet again :S
@@ -414,10 +403,6 @@ body.twothirdswidthpage {
&.m-app-iphone {
background-position: -12px -3610px;
}
- &.m-app-iphone-streetreport {
- background-position: -12px -3678px;
- height:50px;
- }
&.m-app-droid {
background-position: -12px -3756px;
}
@@ -437,10 +422,6 @@ body.twothirdswidthpage {
}
.ie6 #footer-mobileapps ul li a {
background:url(/cobrands/fixmystreet/images/ie_mobileapps.gif) -1px -1px no-repeat;
- &.m-app-iphone-streetreport {
- background-position: -1px -69px;
- height:50px;
- }
&.m-app-droid {
background-position: -1px -148px;
}
@@ -595,7 +576,7 @@ body.twothirdswidthpage {
@include box-shadow(-0em 0px 1em 1em #fff);
li {
border:none;
- a {
+ a, input[type=submit] {
font-size: 0.75em;
color:#666;
padding: 0.5em 1.5em 0.5em 0;
@@ -651,9 +632,14 @@ body.twothirdswidthpage {
top:-0.5em;
position: absolute;
border-top: 0.5em solid transparent;
- border-left: 0.5em solid #4B8304;
+ border-left: 0.5em solid $contrast1_dark;
border-bottom:none;
}
+ span {
+ display: block;
+ font-size: 80%;
+ padding-top: 0.5em;
+ }
}
.ie6 .big-green-banner {
background: $contrast1 url(/cobrands/fixmystreet/images/ie_green_chevron.gif) right center no-repeat;
@@ -675,7 +661,7 @@ body.twothirdswidthpage {
background-position:-324px -326px;
background-repeat:no-repeat;
&:before {
- border-bottom: 0.75em solid #4B8304;
+ border-bottom: 0.75em solid $contrast1_dark;
}
}
}
@@ -701,18 +687,13 @@ input[type=text],
input[type=password],
input[type=email],
textarea{
- width: 25em;
-}
-.form-box {
- max-width:25em;
+ max-width: 25em;
}
-
/* form errors */
div.form-error,
p.form-error {
display:block;
- width:24.7em;
}
input.form-error,
@@ -742,7 +723,7 @@ textarea.form-error {
}
}
.ie6, .ie7 {
- #report-a-problem-sidebar {
+ #report-a-problem-sidebar, .general-sidebar-notes {
left: 29em; // 0.5em left margin gone on .content in IE6/7, so reduce this accordingly.
}
}
@@ -781,19 +762,15 @@ textarea.form-error {
body.frontpage {
.table-cell {
- // we do this so we can have full screen width elements
- >.container {
- width:100%;
- }
.content {
- margin:14em auto 0 auto;
+ margin: 1em auto 0;
}
}
.nav-wrapper-2{
height:6em;
}
#site-header{
- height:9em;
+ height:8em;
}
#site-logo{
top:3em;
@@ -806,7 +783,7 @@ body.frontpage {
p {
top:-4em;
right:0;
- color:#FFD000;
+ color:$primary;
background:none;
@include box-shadow(rgba(0, 0, 0, 0) 0 0 0);
a {
@@ -832,7 +809,7 @@ body.frontpage {
.ie6, .ie7 {
body.frontpage {
#site-header {
- height:3em;
+ height:4em;
}
#site-logo {
top:-1em;
@@ -845,24 +822,13 @@ body.frontpage {
.ie6 body.frontpage #site-logo {
background:url(/cobrands/fixmystreet/images/ie_front_logo.gif) 0 0 no-repeat;
}
-//weird margining thing for ie8
-.ie8 body.frontpage {
- #front-main {
- top:-12em;
- }
- .table-cell .content {
- margin-top:12em;
- }
-}
// big yellow bit full screen width
#front-main {
color: $primary_text;
background: $primary url(/cobrands/fixmystreet/images/tile-y.jpg);
- position:absolute;
- width:100%;
- left:0;
- top:-15em;
+ margin: 0;
+ padding: 1em;
#front-main-container {
max-width: 57em;
margin:0 auto;
@@ -875,6 +841,7 @@ body.frontpage {
#postcodeForm {
background:none;
overflow:hidden;
+ padding-bottom: 0;
margin-right:0.5em;
label {
margin:0.5em 0;
@@ -888,8 +855,8 @@ body.frontpage {
display:block;
float:left;
padding:0.25em 0.5em;
- height:1.5em;
- width:16em;
+ height:2em;
+ width:17em;
}
input#submit {
display:block;
@@ -900,27 +867,40 @@ body.frontpage {
}
}
}
+ a {
+ color: $primary_text;
+ text-decoration: underline;
+ &:hover {
+ text-decoration: none;
+ }
+ }
a#geolocate_link {
background:none;
color:#222;
- margin:-1em 0 0.5em 0;
+ text-decoration: none;
+ padding-bottom: 0;
&:hover {
text-decoration:underline;
background:none;
}
}
}
-.ie6, .ie7 {
- #front-main {
- top:2em;
+.ie7 #front-main {
+ #postcodeForm {
+ div {
+ input#pc {
+ height:1.5em;
+ width:16em;
+ }
+ }
}
}
-
#front-howto {
border-right:1em solid #fff;
#front_stats {
background:none;
+ color: #222;
border-top:0.25em solid $primary;
padding-top:1em;
div {
diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js
index 085765c75..4c795cd05 100644
--- a/web/cobrands/fixmystreet/fixmystreet.js
+++ b/web/cobrands/fixmystreet/fixmystreet.js
@@ -3,33 +3,6 @@
* FixMyStreet JavaScript
*/
-function form_category_onchange() {
- var cat = $('#form_category');
- var args = {
- category: cat.val()
- };
-
- if ( typeof fixmystreet !== 'undefined' ) {
- args.latitude = fixmystreet.latitude;
- args.longitude = fixmystreet.longitude;
- } else {
- args.latitude = $('input[name="latitude"]').val();
- args.longitude = $('input[name="longitude"]').val();
- }
-
- $.getJSON('/report/new/category_extras', args, function(data) {
- if ( data.category_extra ) {
- if ( $('#category_meta').size() ) {
- $('#category_meta').html( data.category_extra);
- } else {
- $('#form_category_row').after( data.category_extra );
- }
- } else {
- $('#category_meta').empty();
- }
- });
-}
-
/*
* general height fixing function
*
@@ -80,12 +53,12 @@ function tabs(elem, indirect) {
$(function(){
var $html = $('html');
- $html.removeClass('no-js').addClass('js');
-
-
- // Preload the new report pin
- document.createElement('img').src = '/i/pin-green.png';
+ var cobrand;
+ if (window.location.href.indexOf('bromley') != -1) {
+ cobrand = 'bromley';
+ }
+ // Deal with switching between mobile and desktop versions on resize
var last_type;
$(window).resize(function(){
var type = $('#site-header').css('borderTopWidth');
@@ -106,7 +79,9 @@ $(function(){
}
if (typeof fixmystreet !== 'undefined' && fixmystreet.page == 'around') {
// Immediately go full screen map if on around page
- $('#site-header').hide();
+ if (cobrand != 'bromley') {
+ $('#site-header').hide();
+ }
$('#map_box').prependTo('.wrapper').css({
position: 'absolute',
top: 0, left: 0, right: 0, bottom: 0,
@@ -130,155 +105,31 @@ $(function(){
$html.removeClass('mobile');
position_map_box();
if (typeof fixmystreet !== 'undefined') {
- fixmystreet.state_map = 'full';
+ if (cobrand == 'bromley') {
+ //$('#bromley-footer').hide();
+ } else {
+ fixmystreet.state_map = 'full';
+ }
}
if (typeof fixmystreet !== 'undefined' && fixmystreet.page == 'around') {
// Remove full-screen-ness
- $('#site-header').show();
+ var banner_text;
+ if (cobrand == 'bromley') {
+ banner_text = 'Click map to report a problem<span>Yellow pins show existing reports</span>';
+ } else {
+ $('#site-header').show();
+ banner_text = 'Click map to report a problem';
+ }
$('#fms_pan_zoom').css({ top: '4.75em !important' });
$('.big-green-banner')
.removeClass('mobile-map-banner')
.prependTo('#side')
- .text('Click map to report a problem');
+ .html(banner_text);
}
$('span.report-a-problem-btn').css({ cursor:'' }).off('.reportBtn');
}
last_type = type;
- });
-
- //add mobile class if small screen
- $(window).resize();
-
- $('#pc').focus();
-
- $('input[type=submit]').removeAttr('disabled');
- /*
- $('#mapForm').submit(function() {
- if (this.submit_problem) {
- $('input[type=submit]', this).prop("disabled", true);
- }
- return true;
- });
- */
-
- if (!$('#been_fixed_no').prop('checked') && !$('#been_fixed_unknown').prop('checked')) {
- $('#another_qn').hide();
- }
- $('#been_fixed_no').click(function() {
- $('#another_qn').show('fast');
- });
- $('#been_fixed_unknown').click(function() {
- $('#another_qn').show('fast');
- });
- $('#been_fixed_yes').click(function() {
- $('#another_qn').hide('fast');
- });
-
- // FIXME - needs to use translated string
- jQuery.validator.addMethod('validCategory', function(value, element) {
- return this.optional(element) || value != '-- Pick a category --'; }, validation_strings.category );
-
- jQuery.validator.addMethod('validName', function(value, element) {
- var validNamePat = /\ba\s*n+on+((y|o)mo?u?s)?(ly)?\b/i;
- return this.optional(element) || value.length > 5 && value.match( /\S/ ) && !value.match( validNamePat ); }, validation_strings.category );
-
- var form_submitted = 0;
- var submitted = false;
-
- $("form.validate").validate({
- rules: {
- title: { required: true },
- detail: { required: true },
- email: { required: true },
- update: { required: true },
- rznvy: { required: true }
- },
- messages: validation_strings,
- onkeyup: false,
- onfocusout: false,
- errorElement: 'div',
- errorClass: 'form-error',
- // we do this to stop things jumping around on blur
- success: function (err) { if ( form_submitted ) { err.addClass('label-valid').removeClass('label-valid-hidden').html( '&nbsp;' ); } else { err.addClass('label-valid-hidden'); } },
- errorPlacement: function( error, element ) {
- element.before( error );
- },
- submitHandler: function(form) {
- if (form.submit_problem) {
- $('input[type=submit]', form).prop("disabled", true);
- }
-
- form.submit();
- },
- // make sure we can see the error message when we focus on invalid elements
- showErrors: function( errorMap, errorList ) {
- if ( submitted && errorList.length ) {
- $(window).scrollTop( $(errorList[0].element).offset().top - 120 );
- }
- this.defaultShowErrors();
- submitted = false;
- },
- invalidHandler: function(form, validator) { submitted = true; }
- });
-
- $('input[type=submit]').click( function(e) { form_submitted = 1; } );
-
- /* set correct required status depending on what we submit
- * NB: need to add things to form_category as the JS updating
- * of this we do after a map click removes them */
- $('#submit_sign_in').click( function(e) {
- $('#form_category').addClass('required validCategory').removeClass('valid');
- $('#form_name').removeClass();
- } );
-
- $('#submit_register').click( function(e) {
- $('#form_category').addClass('required validCategory').removeClass('valid');
- $('#form_name').addClass('required validName');
- } );
-
- $('#problem_submit > input[type="submit"]').click( function(e) {
- $('#form_category').addClass('required validCategory').removeClass('valid');
- $('#form_name').addClass('required validName');
- } );
-
- $('#update_post').click( function(e) {
- $('#form_name').addClass('required').removeClass('valid');
- } );
-
- $('#form_category').change( form_category_onchange );
-
- // Geolocation
- if (geo_position_js.init()) {
- $('#postcodeForm').after('<a href="#" id="geolocate_link">&hellip; or locate me automatically</a>');
- $('#geolocate_link').click(function(e) {
- e.preventDefault();
- // Spinny thing!
- if($('.mobile').length){
- $(this).append(' <img src="/cobrands/fixmystreet/images/spinner-black.gif" alt="" align="bottom">');
- }else{
- $(this).append(' <img src="/cobrands/fixmystreet/images/spinner-yellow.gif" alt="" align="bottom">');
- }
- geo_position_js.getCurrentPosition(function(pos) {
- $('img', this).remove();
- var latitude = pos.coords.latitude;
- var longitude = pos.coords.longitude;
- location.href = '/around?latitude=' + latitude + ';longitude=' + longitude;
- }, function(err) {
- $('img', this).remove();
- if (err.code == 1) { // User said no
- } else if (err.code == 2) { // No position
- $(this).html("Could not look up location");
- } else if (err.code == 3) { // Too long
- $('this').html("No result returned");
- } else { // Unknown
- $('this').html("Unknown error");
- }
- }, {
- enableHighAccuracy: true,
- timeout: 10000
- });
- });
- }
+ }).resize();
/*
* Report a problem page
@@ -351,10 +202,15 @@ $(function(){
/*
* Show stuff on input focus
*/
- $('.form-focus-hidden').hide();
- $('.form-focus-trigger').on('focus', function(){
- $('.form-focus-hidden').fadeIn(500);
- });
+ var form_focus_data = $('.form-focus-trigger').map(function() {
+ return $(this).val();
+ }).get().join('');
+ if (!form_focus_data) {
+ $('.form-focus-hidden').hide();
+ $('.form-focus-trigger').on('focus', function(){
+ $('.form-focus-hidden').fadeIn(500);
+ });
+ }
/*
* Show on click - pretty generic
@@ -474,7 +330,7 @@ $.fn.drawer = function(id, ajax) {
$('#key-tool-wards').click(function(e){
e.preventDefault();
$('#council_wards').slideToggle('800', function(){
- $('#key-tool-wards').toggleClass('active');
+ $('#key-tool-wards').toggleClass('hover');
});
});
} else {
@@ -565,7 +421,11 @@ $.fn.drawer = function(id, ajax) {
*/
if (!$('html.mobile').length) {
if (!($('body').hasClass('frontpage'))){
- heightFix(window, '.content', -176);
+ var offset = -176;
+ if (cobrand == 'bromley') {
+ offset = -110;
+ }
+ heightFix(window, '.content', offset);
// in case we have a map that isn't full screen
map_fix();
}
diff --git a/web/cobrands/fixmystreet/position_map.js b/web/cobrands/fixmystreet/position_map.js
index 9fee518db..4a18d9f9a 100644
--- a/web/cobrands/fixmystreet/position_map.js
+++ b/web/cobrands/fixmystreet/position_map.js
@@ -1,15 +1,20 @@
function position_map_box() {
- var map_pos = 'fixed', map_height = '100%';
- if ($('html').hasClass('ie6')) {
- map_pos = 'absolute';
- map_height = $(window).height();
+ var $html = $('html');
+ if ($html.hasClass('ie6')) {
+ $('#map_box').prependTo('.wrapper').css({
+ zIndex: 0, position: 'absolute',
+ top: 0, left: 0, right: 0, bottom: 0,
+ width: '100%', height: $(window).height(),
+ margin: 0
+ });
+ } else {
+ $('#map_box').prependTo('.wrapper').css({
+ zIndex: 0, position: 'fixed',
+ top: 0, left: 0, right: 0, bottom: 0,
+ width: '100%', height: '100%',
+ margin: 0
+ });
}
- $('#map_box').prependTo('.wrapper').css({
- zIndex: 0, position: map_pos,
- top: 0, left: 0, right: 0, bottom: 0,
- width: '100%', height: map_height,
- margin: 0
- });
}
function map_fix() {}
diff --git a/web/css/core.scss b/web/css/core.scss
index a38a188ce..72e483dfa 100644
--- a/web/css/core.scss
+++ b/web/css/core.scss
@@ -114,7 +114,9 @@ $map_width: 500px;
}
}
- #geolocate_para {
+ #geolocate_link {
+ display: block;
+ border: none;
font-size: 70%;
margin: 2px 0 0 0;
text-align: right;
@@ -609,7 +611,7 @@ $map_width: 500px;
}
}
- #geolocate_para {
+ #geolocate_link {
font-size: 100%;
}
diff --git a/web/fixmystreet_app_cgi.cgi b/web/fixmystreet_app_cgi.cgi
index dd0984e15..7d60ce673 100755
--- a/web/fixmystreet_app_cgi.cgi
+++ b/web/fixmystreet_app_cgi.cgi
@@ -1,11 +1,8 @@
#!/usr/bin/env perl
BEGIN { # set all the paths to the perl code
- use File::Spec;
- use File::Basename;
- my $root = dirname(File::Spec->rel2abs(__FILE__));
-
- require "$root/../setenv.pl";
+ use FindBin;
+ require "$FindBin::Bin/../setenv.pl";
}
use Catalyst::ScriptRunner;
diff --git a/web/fixmystreet_app_fastcgi.cgi b/web/fixmystreet_app_fastcgi.cgi
index 63cb78918..1059cbd34 100755
--- a/web/fixmystreet_app_fastcgi.cgi
+++ b/web/fixmystreet_app_fastcgi.cgi
@@ -1,11 +1,8 @@
#!/usr/bin/env perl
BEGIN { # set all the paths to the perl code
- use File::Spec;
- use File::Basename;
- my $root = dirname(File::Spec->rel2abs(__FILE__));
-
- require "$root/../setenv.pl";
+ use FindBin;
+ require "$FindBin::Bin/../setenv.pl";
}
use Catalyst::ScriptRunner;
diff --git a/web/i/pin-green.png b/web/i/pin-green.png
index 8b64d5160..47a0a6cc1 100644
--- a/web/i/pin-green.png
+++ b/web/i/pin-green.png
Binary files differ
diff --git a/web/i/pin-red.png b/web/i/pin-red.png
index 9b754e42e..298f4d3f6 100644
--- a/web/i/pin-red.png
+++ b/web/i/pin-red.png
Binary files differ
diff --git a/web/i/pin-yellow-big.png b/web/i/pin-yellow-big.png
index 59ed29dbd..d1642d644 100644
--- a/web/i/pin-yellow-big.png
+++ b/web/i/pin-yellow-big.png
Binary files differ
diff --git a/web/i/pin-yellow.png b/web/i/pin-yellow.png
index f9359b0b3..196cb0f5f 100644
--- a/web/i/pin-yellow.png
+++ b/web/i/pin-yellow.png
Binary files differ
diff --git a/web/js/fancybox/jquery.fancybox-1.3.4.js b/web/js/fancybox/jquery.fancybox-1.3.4.js
index be7727537..728aa6a51 100644
--- a/web/js/fancybox/jquery.fancybox-1.3.4.js
+++ b/web/js/fancybox/jquery.fancybox-1.3.4.js
@@ -943,7 +943,7 @@
$(window).unbind("resize.fb scroll.fb");
$(document).unbind('keydown.fb');
- content.find('iframe').attr('src', isIE6 && /^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank');
+ content.find('iframe').attr('src', isIE6 && /^https/i.test(window.location.href || '') ? '/js/fancybox/blank.gif' : 'about:blank');
if (currentOpts.titlePosition !== 'inside') {
title.empty();
@@ -1090,7 +1090,7 @@
loading.addClass('fancybox-ie6');
wrap.addClass('fancybox-ie6');
- $('<iframe id="fancybox-hide-sel-frame" src="' + (/^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank' ) + '" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(outer);
+ $('<iframe id="fancybox-hide-sel-frame" src="' + (/^https/i.test(window.location.href || '') ? '/js/fancybox/blank.gif' : 'about:blank' ) + '" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(outer);
}
};
@@ -1153,4 +1153,4 @@
$.fancybox.init();
});
-})(jQuery); \ No newline at end of file
+})(jQuery);
diff --git a/web/js/fancybox/jquery.fancybox-1.3.4.pack.js b/web/js/fancybox/jquery.fancybox-1.3.4.pack.js
index 1373ed083..d5bd61fe9 100644
--- a/web/js/fancybox/jquery.fancybox-1.3.4.pack.js
+++ b/web/js/fancybox/jquery.fancybox-1.3.4.pack.js
@@ -37,10 +37,10 @@ j.css({width:c.width-d.padding*2,height:c.height-y*a-d.padding*2})},U=function()
b.fn.fancybox=function(a){if(!b(this).length)return this;b(this).data("fancybox",b.extend({},a,b.metadata?b(this).metadata():{})).unbind("click.fb").bind("click.fb",function(c){c.preventDefault();if(!h){h=true;b(this).blur();o=[];q=0;c=b(this).attr("rel")||"";if(!c||c==""||c==="nofollow")o.push(this);else{o=b("a[rel="+c+"], area[rel="+c+"]");q=o.index(this)}I()}});return this};b.fancybox=function(a,c){var g;if(!h){h=true;g=typeof c!=="undefined"?c:{};o=[];q=parseInt(g.index,10)||0;if(b.isArray(a)){for(var k=
0,C=a.length;k<C;k++)if(typeof a[k]=="object")b(a[k]).data("fancybox",b.extend({},g,a[k]));else a[k]=b({}).data("fancybox",b.extend({content:a[k]},g));o=jQuery.merge(o,a)}else{if(typeof a=="object")b(a).data("fancybox",b.extend({},g,a));else a=b({}).data("fancybox",b.extend({content:a},g));o.push(a)}if(q>o.length||q<0)q=0;I()}};b.fancybox.showActivity=function(){clearInterval(K);t.show();K=setInterval(Z,66)};b.fancybox.hideActivity=function(){t.hide()};b.fancybox.next=function(){return b.fancybox.pos(p+
1)};b.fancybox.prev=function(){return b.fancybox.pos(p-1)};b.fancybox.pos=function(a){if(!h){a=parseInt(a);o=l;if(a>-1&&a<l.length){q=a;I()}else if(d.cyclic&&l.length>1){q=a>=l.length?0:l.length-1;I()}}};b.fancybox.cancel=function(){if(!h){h=true;b.event.trigger("fancybox-cancel");N();e.onCancel(o,q,e);h=false}};b.fancybox.close=function(){function a(){u.fadeOut("fast");n.empty().hide();f.hide();b.event.trigger("fancybox-cleanup");j.empty();d.onClosed(l,p,d);l=e=[];p=q=0;d=e={};h=false}if(!(h||f.is(":hidden"))){h=
-true;if(d&&false===d.onCleanup(l,p,d))h=false;else{N();b(E.add(z).add(A)).hide();b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");j.find("iframe").attr("src",M&&/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank");d.titlePosition!=="inside"&&n.empty();f.stop();if(d.transitionOut=="elastic"){r=V();var c=f.position();i={top:c.top,left:c.left,width:f.width(),height:f.height()};if(d.opacity)i.opacity=1;n.empty().hide();B.prop=1;
+true;if(d&&false===d.onCleanup(l,p,d))h=false;else{N();b(E.add(z).add(A)).hide();b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");j.find("iframe").attr("src",M&&/^https/i.test(window.location.href||"")?"/js/fancybox/blank.gif":"about:blank");d.titlePosition!=="inside"&&n.empty();f.stop();if(d.transitionOut=="elastic"){r=V();var c=f.position();i={top:c.top,left:c.left,width:f.width(),height:f.height()};if(d.opacity)i.opacity=1;n.empty().hide();B.prop=1;
b(B).animate({prop:0},{duration:d.speedOut,easing:d.easingOut,step:T,complete:a})}else f.fadeOut(d.transitionOut=="none"?0:d.speedOut,a)}}};b.fancybox.resize=function(){u.is(":visible")&&u.css("height",b(document).height());b.fancybox.center(true)};b.fancybox.center=function(a){var c,g;if(!h){g=a===true?1:0;c=U();!g&&(f.width()>c[0]||f.height()>c[1])||f.stop().animate({top:parseInt(Math.max(c[3]-20,c[3]+(c[1]-j.height()-40)*0.5-d.padding)),left:parseInt(Math.max(c[2]-20,c[2]+(c[0]-j.width()-40)*0.5-
d.padding))},typeof a=="number"?a:200)}};b.fancybox.init=function(){if(!b("#fancybox-wrap").length){b("body").append(m=b('<div id="fancybox-tmp"></div>'),t=b('<div id="fancybox-loading"><div></div></div>'),u=b('<div id="fancybox-overlay"></div>'),f=b('<div id="fancybox-wrap"></div>'));D=b('<div id="fancybox-outer"></div>').append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>').appendTo(f);
D.append(j=b('<div id="fancybox-content"></div>'),E=b('<a id="fancybox-close"></a>'),n=b('<div id="fancybox-title"></div>'),z=b('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'),A=b('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>'));E.click(b.fancybox.close);t.click(b.fancybox.cancel);z.click(function(a){a.preventDefault();b.fancybox.prev()});A.click(function(a){a.preventDefault();b.fancybox.next()});
-b.fn.mousewheel&&f.bind("mousewheel.fb",function(a,c){if(h)a.preventDefault();else if(b(a.target).get(0).clientHeight==0||b(a.target).get(0).scrollHeight===b(a.target).get(0).clientHeight){a.preventDefault();b.fancybox[c>0?"prev":"next"]()}});b.support.opacity||f.addClass("fancybox-ie");if(M){t.addClass("fancybox-ie6");f.addClass("fancybox-ie6");b('<iframe id="fancybox-hide-sel-frame" src="'+(/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank")+'" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(D)}}};
+b.fn.mousewheel&&f.bind("mousewheel.fb",function(a,c){if(h)a.preventDefault();else if(b(a.target).get(0).clientHeight==0||b(a.target).get(0).scrollHeight===b(a.target).get(0).clientHeight){a.preventDefault();b.fancybox[c>0?"prev":"next"]()}});b.support.opacity||f.addClass("fancybox-ie");if(M){t.addClass("fancybox-ie6");f.addClass("fancybox-ie6");b('<iframe id="fancybox-hide-sel-frame" src="'+(/^https/i.test(window.location.href||"")?"/js/fancybox/blank.gif":"about:blank")+'" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(D)}}};
b.fn.fancybox.defaults={padding:10,margin:40,opacity:false,modal:false,cyclic:false,scrolling:"auto",width:560,height:340,autoScale:true,autoDimensions:true,centerOnScroll:false,ajax:{},swf:{wmode:"transparent"},hideOnOverlayClick:true,hideOnContentClick:false,overlayShow:true,overlayOpacity:0.7,overlayColor:"#777",titleShow:true,titlePosition:"float",titleFormat:null,titleFromAlt:false,transitionIn:"fade",transitionOut:"fade",speedIn:300,speedOut:300,changeSpeed:300,changeFade:"fast",easingIn:"swing",
-easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,enableKeyboardNav:true,onStart:function(){},onCancel:function(){},onComplete:function(){},onCleanup:function(){},onClosed:function(){},onError:function(){}};b(document).ready(function(){b.fancybox.init()})})(jQuery); \ No newline at end of file
+easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,enableKeyboardNav:true,onStart:function(){},onCancel:function(){},onComplete:function(){},onCleanup:function(){},onClosed:function(){},onError:function(){}};b(document).ready(function(){b.fancybox.init()})})(jQuery);
diff --git a/web/js/fixmystreet-old-box.js b/web/js/fixmystreet-old-box.js
new file mode 100644
index 000000000..72eeafb4d
--- /dev/null
+++ b/web/js/fixmystreet-old-box.js
@@ -0,0 +1,39 @@
+/*
+ * fixmystreet-old-box.js
+ * Create the 'email me updates' pop up box on old-style report display pages.
+ */
+
+$(function(){
+
+ if (!$('#email_alert_box').length) {
+ return;
+ }
+
+ var timer;
+ function email_alert_close() {
+ $('#email_alert_box').hide('fast');
+ }
+
+ $('#email_alert').click(function(e) {
+ e.preventDefault();
+ if ($('#email_alert_box').is(':visible')) {
+ email_alert_close();
+ } else {
+ var pos = $(this).position();
+ $('#email_alert_box').css( { 'left': ( pos.left - 20 ) + 'px', 'top': ( pos.top + 20 ) + 'px' } );
+ $('#email_alert_box').show('fast');
+ $('#alert_rznvy').focus();
+ }
+ }).hover(function() {
+ window.clearTimeout(timer);
+ }, function() {
+ timer = window.setTimeout(email_alert_close, 2000);
+ });
+
+ $('#email_alert_box').hover(function() {
+ window.clearTimeout(timer);
+ }, function() {
+ timer = window.setTimeout(email_alert_close, 2000);
+ });
+
+});
diff --git a/web/js/fixmystreet.js b/web/js/fixmystreet.js
index 834aa4ed3..36d47b5b2 100644
--- a/web/js/fixmystreet.js
+++ b/web/js/fixmystreet.js
@@ -1,51 +1,31 @@
/*
* fixmystreet.js
- * FixMyStreet JavaScript
+ * FixMyStreet JavaScript used by all cobrands.
+ * With the JavaScript written more proper like.
*/
-function form_category_onchange() {
- var cat = $('#form_category');
- var args = {
- category: cat.val()
- };
-
- if ( typeof fixmystreet !== 'undefined' ) {
- args.latitude = fixmystreet.latitude;
- args.longitude = fixmystreet.longitude;
- } else {
- args.latitude = $('input[name="latitude"]').val();
- args.longitude = $('input[name="longitude"]').val();
- }
-
- $.getJSON('/report/new/category_extras', args, function(data) {
- if ( data.category_extra ) {
- if ( $('#category_meta').size() ) {
- $('#category_meta').html( data.category_extra);
- } else {
- $('#form_category_row').after( data.category_extra );
- }
- } else {
- $('#category_meta').empty();
- }
- });
-}
+(function($){
+/*
+ Deal with changes to category by asking for details from the server.
+ */
$(function(){
- $('html').removeClass('no-js').addClass('js');
+ var $html = $('html');
+
+ // Add a class to the whole page saying JavaScript is enabled (for CSS and so on)
+ $html.removeClass('no-js').addClass('js');
+ // Preload the new report pin
+ document.createElement('img').src = '/i/pin-green.png';
+
+ // Focus on postcode box on front page
$('#pc').focus();
+ // In case we've come here by clicking back to a form that disabled a submit button
$('input[type=submit]').removeAttr('disabled');
- /*
- $('#mapForm').submit(function() {
- if (this.submit_problem) {
- $('input[type=submit]', this).prop("disabled", true);
- }
- return true;
- });
- */
+ // Questionnaire hide/showings
if (!$('#been_fixed_no').prop('checked') && !$('#been_fixed_unknown').prop('checked')) {
$('#another_qn').hide();
}
@@ -59,10 +39,7 @@ $(function(){
$('#another_qn').hide('fast');
});
- var timer;
- function email_alert_close() {
- $('#email_alert_box').hide('fast');
- }
+ // Form validation
// FIXME - needs to use translated string
jQuery.validator.addMethod('validCategory', function(value, element) {
@@ -85,12 +62,18 @@ $(function(){
},
messages: validation_strings,
onkeyup: false,
+ onfocusout: false,
errorElement: 'div',
errorClass: 'form-error',
// we do this to stop things jumping around on blur
success: function (err) { if ( form_submitted ) { err.addClass('label-valid').removeClass('label-valid-hidden').html( '&nbsp;' ); } else { err.addClass('label-valid-hidden'); } },
errorPlacement: function( error, element ) {
- element.parent('div').before( error );
+ // Different for old/new style design
+ if ($('.form-field').length) {
+ element.parent('div.form-field').before( error );
+ } else {
+ element.before( error );
+ }
},
submitHandler: function(form) {
if (form.submit_problem) {
@@ -102,7 +85,7 @@ $(function(){
// make sure we can see the error message when we focus on invalid elements
showErrors: function( errorMap, errorList ) {
if ( submitted && errorList.length ) {
- $(window).scrollTop( $(errorList[0].element).offset().top - 40 );
+ $(window).scrollTop( $(errorList[0].element).offset().top - 120 );
}
this.defaultShowErrors();
submitted = false;
@@ -112,83 +95,105 @@ $(function(){
$('input[type=submit]').click( function(e) { form_submitted = 1; } );
- /* set correct required status depending on what we submit
- * NB: need to add things to form_category as the JS updating
+ /* set correct required status depending on what we submit
+ * NB: need to add things to form_category as the JS updating
* of this we do after a map click removes them */
$('#submit_sign_in').click( function(e) {
$('#form_category').addClass('required validCategory').removeClass('valid');
$('#form_name').removeClass();
+ $('#form_first_name').removeClass();
+ $('#form_last_name').removeClass();
+ $('#form_fms_extra_title').removeClass();
} );
- $('#submit_register').click( function(e) {
+ $('#submit_register').click( function(e) {
$('#form_category').addClass('required validCategory').removeClass('valid');
$('#form_name').addClass('required validName');
+ $('#form_first_name').addClass('required');
+ $('#form_last_name').addClass('required');
+ $('#form_fms_extra_title').addClass('required');
} );
- $('#problem_submit > input[type="submit"]').click( function(e) {
+ $('#problem_submit > input[type="submit"]').click( function(e) {
$('#form_category').addClass('required validCategory').removeClass('valid');
$('#form_name').addClass('required validName');
+ $('#form_first_name').addClass('required');
+ $('#form_last_name').addClass('required');
+ $('#form_fms_extra_title').addClass('required');
} );
- $('#update_post').click( function(e) {
+ $('#update_post').click( function(e) {
$('#form_name').addClass('required').removeClass('valid');
} );
- $('#email_alert').click(function(e) {
- if (!$('#email_alert_box').length) {
- return true;
- }
- e.preventDefault();
- if ($('#email_alert_box').is(':visible')) {
- email_alert_close();
- } else {
- var pos = $(this).position();
- $('#email_alert_box').css( { 'left': ( pos.left - 20 ) + 'px', 'top': ( pos.top + 20 ) + 'px' } );
- $('#email_alert_box').show('fast');
- $('#alert_rznvy').focus();
- }
- }).hover(function() {
- window.clearTimeout(timer);
- }, function() {
- timer = window.setTimeout(email_alert_close, 2000);
- });
-
- $('#email_alert_box').hover(function() {
- window.clearTimeout(timer);
- }, function() {
- timer = window.setTimeout(email_alert_close, 2000);
- });
-
- // Using delegate here because we *might* be running under a cobrand which isn't jQuery 1.7+
- // Delegation is necessary because #form_category may be replaced during the lifetime of the page
- $("#problem_form").delegate("select#form_category", "change", form_category_onchange );
-
// Geolocation
if (geo_position_js.init()) {
- $('#postcodeForm').append('<p id="geolocate_para">Or <a href="#" id="geolocate_link">locate me automatically</a>').css({ "padding-bottom": "0.5em" });
+ if ($('body.frontpage').length) {
+ $('#postcodeForm').after('<a href="#" id="geolocate_link">&hellip; or locate me automatically</a>');
+ } else{
+ $('#postcodeForm').append('<a href="#" id="geolocate_link">&hellip; or locate me automatically</a>');
+ }
$('#geolocate_link').click(function(e) {
+ var $link = $(this);
e.preventDefault();
// Spinny thing!
- $('#geolocate_para').append(' <img src="/i/flower.gif" alt="" align="bottom">');
+ if($('.mobile').length){
+ $link.append(' <img src="/cobrands/fixmystreet/images/spinner-black.gif" alt="" align="bottom">');
+ }else{
+ $link.append(' <img src="/cobrands/fixmystreet/images/spinner-yellow.gif" alt="" align="bottom">');
+ }
geo_position_js.getCurrentPosition(function(pos) {
- $('#geolocate_para img').remove();
+ $link.find('img').remove();
var latitude = pos.coords.latitude;
var longitude = pos.coords.longitude;
location.href = '/around?latitude=' + latitude + ';longitude=' + longitude;
}, function(err) {
- $('#geolocate_para img').remove();
+ $link.find('img').remove();
if (err.code == 1) { // User said no
+ $link.html("You declined; please fill in the box above");
} else if (err.code == 2) { // No position
- $('#geolocate_para').html("Could not look up location");
+ $link.html("Could not look up location");
} else if (err.code == 3) { // Too long
- $('#geolocate_para').html("No result returned");
+ $link.html("No result returned");
} else { // Unknown
- $('#geolocate_para').html("Unknown error");
+ $link.html("Unknown error");
}
}, {
+ enableHighAccuracy: true,
timeout: 10000
});
});
}
+ // Delegation is necessary because #form_category may be replaced during the lifetime of the page
+ $("#problem_form").on("change.category", "select#form_category", function(){
+ var args = {
+ category: $(this).val()
+ };
+
+ if ( typeof fixmystreet !== 'undefined' ) {
+ args.latitude = fixmystreet.latitude;
+ args.longitude = fixmystreet.longitude;
+ } else {
+ args.latitude = $('input[name="latitude"]').val();
+ args.longitude = $('input[name="longitude"]').val();
+ }
+
+ $.getJSON('/report/new/category_extras', args, function(data) {
+ var $category_meta = $('#category_meta');
+ if ( data.category_extra ) {
+ if ( $category_meta.length ) {
+ $category_meta.html( data.category_extra );
+ } else {
+ $('#form_category_row').after( data.category_extra );
+ }
+ } else {
+ $category_meta.empty();
+ }
+ });
+ });
+
});
+
+})(jQuery);
+
diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js
index 35020e1f7..d98994d84 100644
--- a/web/js/map-OpenLayers.js
+++ b/web/js/map-OpenLayers.js
@@ -23,9 +23,10 @@ function fixmystreet_update_pin(lonlat) {
$('#side-form, #site-logo').show();
$('#councils_text').html(data.councils_text);
$('#form_category_row').html(data.category);
- if ( data.extra_name_info ) {
+ if ( data.extra_name_info && !$('#form_fms_extra_title').length ) {
// there might be a first name field on some cobrands
- var lb = $('#form_first_name').prev() || $('#form_name').prev();
+ var lb = $('#form_first_name').prev();
+ if ( lb.length == 0 ) { lb = $('#form_name').prev(); }
lb.before(data.extra_name_info);
}
});
@@ -275,15 +276,16 @@ $(function(){
if (fixmystreet.state_map && fixmystreet.state_map == 'full') {
// TODO Work better with window resizing, this is pretty 'set up' only at present
- var $content = $('.content'),
- q = ( $content.offset().left + $content.width() ) / 2;
+ var $content = $('.content'), mb = $('#map_box'),
+ q = ( $content.offset().left - mb.offset().left + $content.width() ) / 2;
+ if (q < 0) { q = 0; }
// Need to try and fake the 'centre' being 75% from the left
fixmystreet.map.pan(-q, -25, { animate: false });
fixmystreet.map.events.register("movestart", null, function(e){
fixmystreet.map.moveStart = { zoom: this.getZoom(), center: this.getCenter() };
});
fixmystreet.map.events.register("zoomend", null, function(e){
- if ( fixmystreet.map.moveStart && !fixmystreet.map.moveStart.zoom ) {
+ if ( fixmystreet.map.moveStart && !fixmystreet.map.moveStart.zoom && fixmystreet.map.moveStart.zoom !== 0 ) {
return true; // getZoom() on Firefox appears to return null at first?
}
if ( !fixmystreet.map.moveStart || !this.getCenter().equals(fixmystreet.map.moveStart.center) ) {
@@ -458,6 +460,7 @@ OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
fixmystreet.markers.addFeatures( markers );
fixmystreet_activate_drag();
}
+
// check to see if markers are visible. We click the
// link so that it updates the text in case they go
// back
@@ -506,7 +509,7 @@ OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
fixmystreet.map.getProjectionObject()
);
var p = fixmystreet.map.getViewPortPxFromLonLat(lonlat);
- p.x -= ( o.left + w ) / 2;
+ p.x -= ( o.left - bo.left + w ) / 2;
lonlat = fixmystreet.map.getLonLatFromViewPortPx(p);
fixmystreet.map.panTo(lonlat);
}
diff --git a/web/js/map-bing-ol.js b/web/js/map-bing-ol.js
index 89b274b18..7d40f8afc 100644
--- a/web/js/map-bing-ol.js
+++ b/web/js/map-bing-ol.js
@@ -1,3 +1,5 @@
+var tile_base = [ [ '', 'a.', 'b.', 'c.' ], 'http://{S}tilma.mysociety.org/sv' ];
+
function set_map_config(perm) {
var permalink_id;
if ($('#map_permalink').length) {
@@ -17,6 +19,9 @@ function set_map_config(perm) {
new OpenLayers.Control.Permalink(permalink_id),
new OpenLayers.Control.PanZoomFMS({id: 'fms_pan_zoom' })
];
+ if (fixmystreet.map_type) {
+ tile_base = fixmystreet.map_type;
+ }
fixmystreet.map_type = OpenLayers.Layer.Bing;
}
@@ -36,7 +41,7 @@ OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, {
if (z >= 16) {
copyrights = 'Contains Ordnance Survey data &copy; Crown copyright and database right 2010';
} else {
- logo = '<a href="http://www.bing.com/maps/"><img border=0 src="http://dev.virtualearth.net/Branding/logo_powered_by.png"></a>';
+ logo = '<a href="http://www.bing.com/maps/"><img border=0 src="//dev.virtualearth.net/Branding/logo_powered_by.png"></a>';
copyrights = '&copy; 2011 <a href="http://www.bing.com/maps/">Microsoft</a>. &copy; AND, Navteq, Ordnance Survey';
}
this.attribution = OpenLayers.String.format(this.attributionTemplate, {
@@ -94,20 +99,18 @@ OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, {
var url;
if (z >= 16) {
- url = [
- "http://tilma.mysociety.org/sv/${z}/${x}/${y}.png",
- "http://a.tilma.mysociety.org/sv/${z}/${x}/${y}.png",
- "http://b.tilma.mysociety.org/sv/${z}/${x}/${y}.png",
- "http://c.tilma.mysociety.org/sv/${z}/${x}/${y}.png"
- ];
+ url = [];
+ for (var i=0; i< tile_base[0].length; i++) {
+ url.push( tile_base[1].replace('{S}', tile_base[0][i]) + "/${z}/${x}/${y}.png" );
+ }
} else {
var type = '';
if (z > 10) { type = '&productSet=mmOS'; }
url = [
- "http://ecn.t0.tiles.virtualearth.net/tiles/r${id}.png?g=701" + type,
- "http://ecn.t1.tiles.virtualearth.net/tiles/r${id}.png?g=701" + type,
- "http://ecn.t2.tiles.virtualearth.net/tiles/r${id}.png?g=701" + type,
- "http://ecn.t3.tiles.virtualearth.net/tiles/r${id}.png?g=701" + type
+ "//ecn.t0.tiles.virtualearth.net/tiles/r${id}.png?g=701" + type,
+ "//ecn.t1.tiles.virtualearth.net/tiles/r${id}.png?g=701" + type,
+ "//ecn.t2.tiles.virtualearth.net/tiles/r${id}.png?g=701" + type,
+ "//ecn.t3.tiles.virtualearth.net/tiles/r${id}.png?g=701" + type
];
}
var s = '' + x + y + z;
diff --git a/web/js/modernizr.custom.76759.js b/web/js/modernizr.custom.76759.js
deleted file mode 100644
index 20a22d8fa..000000000
--- a/web/js/modernizr.custom.76759.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/* Modernizr 2.5.3 (Custom Build) | MIT & BSD
- * Build: http://www.modernizr.com/download/#-input-shiv-mq-teststyles-load
- */
-;window.Modernizr=function(a,b,c){function v(a){i.cssText=a}function w(a,b){return v(prefixes.join(a+";")+(b||""))}function x(a,b){return typeof a===b}function y(a,b){return!!~(""+a).indexOf(b)}function z(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:x(f,"function")?f.bind(d||b):f}return!1}function A(){e.input=function(c){for(var d=0,e=c.length;d<e;d++)n[c[d]]=c[d]in j;return n.list&&(n.list=!!b.createElement("datalist")&&!!a.HTMLDataListElement),n}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" "))}var d="2.5.3",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j=b.createElement("input"),k={}.toString,l={},m={},n={},o=[],p=o.slice,q,r=function(a,c,d,e){var h,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),k.appendChild(j);return h=["&#173;","<style>",a,"</style>"].join(""),k.id=g,m.innerHTML+=h,m.appendChild(k),l||(m.style.background="",f.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},s=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return r("@media "+b+" { #"+g+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},t={}.hasOwnProperty,u;!x(t,"undefined")&&!x(t.call,"undefined")?u=function(a,b){return t.call(a,b)}:u=function(a,b){return b in a&&x(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=p.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(p.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(p.call(arguments)))};return e});for(var B in l)u(l,B)&&(q=B.toLowerCase(),e[q]=l[B](),o.push((e[q]?"":"no-")+q));return e.input||A(),v(""),h=j=null,function(a,b){function g(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function h(){var a=k.elements;return typeof a=="string"?a.split(" "):a}function i(a){var b={},c=a.createElement,e=a.createDocumentFragment,f=e();a.createElement=function(a){var e=(b[a]||(b[a]=c(a))).cloneNode();return k.shivMethods&&e.canHaveChildren&&!d.test(a)?f.appendChild(e):e},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+h().join().replace(/\w+/g,function(a){return b[a]=c(a),f.createElement(a),'c("'+a+'")'})+");return n}")(k,f)}function j(a){var b;return a.documentShived?a:(k.shivCSS&&!e&&(b=!!g(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),f||(b=!i(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea)$/i,e,f;(function(){var a=b.createElement("a");a.innerHTML="<xyz></xyz>",e="hidden"in a,f=a.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var k={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:j};a.html5=k,j(b)}(this,b),e._version=d,e.mq=s,e.testStyles=r,e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,i){var j=b(a),l=j.autoCallback;j.url.split(".").pop().split("?").shift(),j.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]||h),j.instead?j.instead(a,e,f,g,i):(y[j.url]?j.noexec=!0:y[j.url]=1,f.load(j.url,j.forceCSS||!j.forceJS&&"css"==j.url.split(".").pop().split("?").shift()?"c":c,j.noexec,j.attrs,j.timeout),(d(e)||d(l))&&f.load(function(){k(),e&&e(j.origUrl,i,g),l&&l(j.origUrl,i,g),y[j.url]=2})))}function i(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var j,l,m=this.yepnope.loader;if(e(a))g(a,0,m,0);else if(w(a))for(j=0;j<a.length;j++)l=a[j],e(l)?g(l,0,m,0):w(l)?B(l):Object(l)===l&&i(l,m);else Object(a)===a&&i(a,m)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,b.readyState==null&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))}; \ No newline at end of file
diff --git a/web/js/modernizr.custom.js b/web/js/modernizr.custom.js
new file mode 100644
index 000000000..c4d6ffdfc
--- /dev/null
+++ b/web/js/modernizr.custom.js
@@ -0,0 +1,4 @@
+/* Modernizr 2.5.3 (Custom Build) | MIT & BSD
+ * Build: http://www.modernizr.com/download/#-shiv-load
+ */
+;window.Modernizr=function(a,b,c){function t(a){i.cssText=a}function u(a,b){return t(prefixes.join(a+";")+(b||""))}function v(a,b){return typeof a===b}function w(a,b){return!!~(""+a).indexOf(b)}function x(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:v(f,"function")?f.bind(d||b):f}return!1}var d="2.5.3",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l={},m={},n={},o=[],p=o.slice,q,r={}.hasOwnProperty,s;!v(r,"undefined")&&!v(r.call,"undefined")?s=function(a,b){return r.call(a,b)}:s=function(a,b){return b in a&&v(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=p.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(p.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(p.call(arguments)))};return e});for(var y in l)s(l,y)&&(q=y.toLowerCase(),e[q]=l[y](),o.push((e[q]?"":"no-")+q));return t(""),h=j=null,function(a,b){function g(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function h(){var a=k.elements;return typeof a=="string"?a.split(" "):a}function i(a){var b={},c=a.createElement,e=a.createDocumentFragment,f=e();a.createElement=function(a){var e=(b[a]||(b[a]=c(a))).cloneNode();return k.shivMethods&&e.canHaveChildren&&!d.test(a)?f.appendChild(e):e},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+h().join().replace(/\w+/g,function(a){return b[a]=c(a),f.createElement(a),'c("'+a+'")'})+");return n}")(k,f)}function j(a){var b;return a.documentShived?a:(k.shivCSS&&!e&&(b=!!g(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),f||(b=!i(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea)$/i,e,f;(function(){var a=b.createElement("a");a.innerHTML="<xyz></xyz>",e="hidden"in a,f=a.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var k={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:j};a.html5=k,j(b)}(this,b),e._version=d,e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,i){var j=b(a),l=j.autoCallback;j.url.split(".").pop().split("?").shift(),j.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]||h),j.instead?j.instead(a,e,f,g,i):(y[j.url]?j.noexec=!0:y[j.url]=1,f.load(j.url,j.forceCSS||!j.forceJS&&"css"==j.url.split(".").pop().split("?").shift()?"c":c,j.noexec,j.attrs,j.timeout),(d(e)||d(l))&&f.load(function(){k(),e&&e(j.origUrl,i,g),l&&l(j.origUrl,i,g),y[j.url]=2})))}function i(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var j,l,m=this.yepnope.loader;if(e(a))g(a,0,m,0);else if(w(a))for(j=0;j<a.length;j++)l=a[j],e(l)?g(l,0,m,0):w(l)?B(l):Object(l)===l&&i(l,m);else Object(a)===a&&i(a,m)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,b.readyState==null&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))}; \ No newline at end of file